class ActiveRecord::ConnectionAdapters::SQLite3Adapter

The SQLite3 adapter works with the sqlite3-ruby drivers (available as gem from rubygems.org/gems/sqlite3).

Options:

Constants

ADAPTER_NAME
COLLATE_REGEX
NATIVE_DATABASE_TYPES

Public Class Methods

database_exists?(config) click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 91
def self.database_exists?(config)
  config = config.symbolize_keys
  if config[:database] == ":memory:"
    true
  else
    database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database]
    File.exist?(database_file)
  end
end
new(connection, logger, connection_options, config) click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 86
def initialize(connection, logger, connection_options, config)
  super(connection, logger, config)
  configure_connection
end

Public Instance Methods

active?() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 156
def active?
  !@connection.closed?
end
disconnect!() click to toggle source

Disconnects from the database if already connected. Otherwise, this method does nothing.

# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 167
def disconnect!
  super
  @connection.close rescue nil
end
encoding() click to toggle source

Returns the current database encoding format as a string, eg: 'UTF-8'

# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 181
def encoding
  @connection.encoding.to_s
end
foreign_keys(table_name) click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 291
def foreign_keys(table_name)
  fk_info = exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
  fk_info.map do |row|
    options = {
      column: row["from"],
      primary_key: row["to"],
      on_delete: extract_foreign_key_action(row["on_delete"]),
      on_update: extract_foreign_key_action(row["on_update"])
    }
    ForeignKeyDefinition.new(table_name, row["table"], options)
  end
end
reconnect!() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 160
def reconnect!
  super
  connect if @connection.closed?
end
rename_table(table_name, new_name) click to toggle source

Renames a table.

Example:

rename_table('octopuses', 'octopi')
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 228
def rename_table(table_name, new_name)
  schema_cache.clear_data_source_cache!(table_name.to_s)
  schema_cache.clear_data_source_cache!(new_name.to_s)
  exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
  rename_table_indexes(table_name, new_name)
end
requires_reloading?() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 121
def requires_reloading?
  true
end
supports_check_constraints?() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 129
def supports_check_constraints?
  true
end
supports_common_table_expressions?() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 145
def supports_common_table_expressions?
  database_version >= "3.8.3"
end
supports_datetime_with_precision?() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 137
def supports_datetime_with_precision?
  true
end
supports_ddl_transactions?() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 101
def supports_ddl_transactions?
  true
end
supports_explain?() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 185
def supports_explain?
  true
end
supports_expression_index?() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 117
def supports_expression_index?
  database_version >= "3.9.0"
end
supports_foreign_keys?() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 125
def supports_foreign_keys?
  true
end
supports_index_sort_order?() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 172
def supports_index_sort_order?
  true
end
supports_insert_conflict_target?()
supports_insert_on_conflict?() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 149
def supports_insert_on_conflict?
  database_version >= "3.24.0"
end
supports_insert_on_duplicate_skip?()
supports_insert_on_duplicate_update?()
supports_json?() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 141
def supports_json?
  true
end
supports_lazy_transactions?() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 189
def supports_lazy_transactions?
  true
end
supports_partial_index?() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 113
def supports_partial_index?
  true
end
supports_savepoints?() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 105
def supports_savepoints?
  true
end
supports_transaction_isolation?() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 109
def supports_transaction_isolation?
  true
end
supports_views?() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 133
def supports_views?
  true
end

Private Instance Methods

alter_table( table_name, foreign_keys = foreign_keys(table_name), check_constraints = check_constraints(table_name), **options ) { |definition| ... } click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 358
def alter_table(
  table_name,
  foreign_keys = foreign_keys(table_name),
  check_constraints = check_constraints(table_name),
  **options
)
  altered_table_name = "a#{table_name}"

  caller = lambda do |definition|
    rename = options[:rename] || {}
    foreign_keys.each do |fk|
      if column = rename[fk.options[:column]]
        fk.options[:column] = column
      end
      to_table = strip_table_name_prefix_and_suffix(fk.to_table)
      definition.foreign_key(to_table, **fk.options)
    end

    check_constraints.each do |chk|
      definition.check_constraint(chk.expression, **chk.options)
    end

    yield definition if block_given?
  end

  transaction do
    disable_referential_integrity do
      move_table(table_name, altered_table_name, options.merge(temporary: true))
      move_table(altered_table_name, table_name, &caller)
    end
  end
end
arel_visitor() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 526
def arel_visitor
  Arel::Visitors::SQLite.new(self)
end
bind_params_length() click to toggle source

See www.sqlite.org/limits.html, the default value is 999 when not configured.

# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 335
def bind_params_length
  999
end
build_statement_pool() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 530
def build_statement_pool
  StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
end
column_definitions(table_name)
Alias for: table_structure
configure_connection() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 542
def configure_connection
  @connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]

  execute("PRAGMA foreign_keys = ON", "SCHEMA")
end
connect() click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 534
def connect
  @connection = ::SQLite3::Database.new(
    @config[:database].to_s,
    @config.merge(results_as_hash: true)
  )
  configure_connection
end
copy_table(from, to, options = {}) { |definition| ... } click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 396
def copy_table(from, to, options = {})
  from_primary_key = primary_key(from)
  options[:id] = false
  create_table(to, **options) do |definition|
    @definition = definition
    if from_primary_key.is_a?(Array)
      @definition.primary_keys from_primary_key
    end

    columns(from).each do |column|
      column_name = options[:rename] ?
        (options[:rename][column.name] ||
         options[:rename][column.name.to_sym] ||
         column.name) : column.name

      @definition.column(column_name, column.type,
        limit: column.limit, default: column.default,
        precision: column.precision, scale: column.scale,
        null: column.null, collation: column.collation,
        primary_key: column_name == from_primary_key
      )
    end

    yield @definition if block_given?
  end
  copy_table_indexes(from, to, options[:rename] || {})
  copy_table_contents(from, to,
    @definition.columns.map(&:name),
    options[:rename] || {})
end
copy_table_contents(from, to, columns, rename = {}) click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 454
def copy_table_contents(from, to, columns, rename = {})
  column_mappings = Hash[columns.map { |name| [name, name] }]
  rename.each { |a| column_mappings[a.last] = a.first }
  from_columns = columns(from).collect(&:name)
  columns = columns.find_all { |col| from_columns.include?(column_mappings[col]) }
  from_columns_to_copy = columns.map { |col| column_mappings[col] }
  quoted_columns = columns.map { |col| quote_column_name(col) } * ","
  quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","

  exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
             SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
end
copy_table_indexes(from, to, rename = {}) click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 427
def copy_table_indexes(from, to, rename = {})
  indexes(from).each do |index|
    name = index.name
    if to == "a#{from}"
      name = "t#{name}"
    elsif from == "a#{to}"
      name = name[1..-1]
    end

    columns = index.columns
    if columns.is_a?(Array)
      to_column_names = columns(to).map(&:name)
      columns = columns.map { |c| rename[c] || c }.select do |column|
        to_column_names.include?(column)
      end
    end

    unless columns.empty?
      # index name can't be the same
      options = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
      options[:unique] = true if index.unique
      options[:where] = index.where if index.where
      add_index(to, columns, **options)
    end
  end
end
initialize_type_map(m = type_map) click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 339
def initialize_type_map(m = type_map)
  super
  register_class_with_limit m, %r(int)i, SQLite3Integer
end
invalid_alter_table_type?(type, options) click to toggle source

See: www.sqlite.org/lang_altertable.html SQLite has an additional restriction on the ALTER TABLE statement

# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 353
def invalid_alter_table_type?(type, options)
  type.to_sym == :primary_key || options[:primary_key] ||
    options[:null] == false && options[:default].nil?
end
move_table(from, to, options = {}, &block) click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 391
def move_table(from, to, options = {}, &block)
  copy_table(from, to, options, &block)
  drop_table(from)
end
table_structure(table_name) click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 344
def table_structure(table_name)
  structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
  raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
  table_structure_with_collation(table_name, structure)
end
Also aliased as: column_definitions
table_structure_with_collation(table_name, basic_structure) click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 487
        def table_structure_with_collation(table_name, basic_structure)
          collation_hash = {}
          sql = <<~SQL
            SELECT sql FROM
              (SELECT * FROM sqlite_master UNION ALL
               SELECT * FROM sqlite_temp_master)
            WHERE type = 'table' AND name = #{quote(table_name)}
          SQL

          # Result will have following sample string
          # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
          #                       "password_digest" varchar COLLATE "NOCASE");
          result = query_value(sql, "SCHEMA")

          if result
            # Splitting with left parentheses and discarding the first part will return all
            # columns separated with comma(,).
            columns_string = result.split("(", 2).last

            columns_string.split(",").each do |column_string|
              # This regex will match the column name and collation type and will save
              # the value in $1 and $2 respectively.
              collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
            end

            basic_structure.map do |column|
              column_name = column["name"]

              if collation_hash.has_key? column_name
                column["collation"] = collation_hash[column_name]
              end

              column
            end
          else
            basic_structure.to_a
          end
        end
translate_exception(exception, message:, sql:, binds:) click to toggle source
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 467
def translate_exception(exception, message:, sql:, binds:)
  # SQLite 3.8.2 returns a newly formatted error message:
  #   UNIQUE constraint failed: *table_name*.*column_name*
  # Older versions of SQLite return:
  #   column *column_name* is not unique
  if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i)
    RecordNotUnique.new(message, sql: sql, binds: binds)
  elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i)
    NotNullViolation.new(message, sql: sql, binds: binds)
  elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
    InvalidForeignKey.new(message, sql: sql, binds: binds)
  elsif exception.message.match?(/called on a closed database/i)
    ConnectionNotEstablished.new(exception)
  else
    super
  end
end