class ActiveRecord::ConnectionAdapters::SQLite3Adapter

The SQLite3 adapter works SQLite 3.6.16 or newer 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

new(connection, logger, connection_options, config) click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 101
def initialize(connection, logger, connection_options, config)
  super(connection, logger, config)

  @active     = nil
  @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))

  configure_connection
end
represent_boolean_as_integer() click to toggle source

Indicates whether boolean values are stored in sqlite3 databases as 1 and 0 or 't' and 'f'. Leaving ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer set to false is deprecated. SQLite databases have used 't' and 'f' to serialize boolean values and must have old data converted to 1 and 0 (its native boolean serialization) before setting this flag to true. Conversion can be accomplished by setting up a rake task which runs

ExampleModel.where("boolean_column = 't'").update_all(boolean_column: 1)
ExampleModel.where("boolean_column = 'f'").update_all(boolean_column: 0)

for all models and all boolean columns, after which the flag must be set to true by adding the following to your application.rb file:

Quails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 91
class_attribute :represent_boolean_as_integer, default: false

Public Instance Methods

active?() click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 142
def active?
  @active != false
end
allowed_index_name_length() click to toggle source

Returns 62. SQLite supports index names up to 64 characters. The rest is used by Quails internally to perform temporary rename operations

# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 166
def allowed_index_name_length
  index_name_length - 2
end
clear_cache!() click to toggle source

Clears the prepared statements cache.

# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 155
def clear_cache!
  @statements.clear
end
disconnect!() click to toggle source

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

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

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

# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 175
def encoding
  @connection.encoding.to_s
end
exec_delete(sql, name = "SQL", binds = []) click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 238
def exec_delete(sql, name = "SQL", binds = [])
  exec_query(sql, name, binds)
  @connection.changes
end
Also aliased as: exec_update
exec_query(sql, name = nil, binds = [], prepare: false) click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 205
def exec_query(sql, name = nil, binds = [], prepare: false)
  type_casted_binds = type_casted_binds(binds)

  log(sql, name, binds, type_casted_binds) do
    ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
      # Don't cache statements if they are not prepared
      unless prepare
        stmt = @connection.prepare(sql)
        begin
          cols = stmt.columns
          unless without_prepared_statement?(binds)
            stmt.bind_params(type_casted_binds)
          end
          records = stmt.to_a
        ensure
          stmt.close
        end
      else
        cache = @statements[sql] ||= {
          stmt: @connection.prepare(sql)
        }
        stmt = cache[:stmt]
        cols = cache[:cols] ||= stmt.columns
        stmt.reset!
        stmt.bind_params(type_casted_binds)
        records = stmt.to_a
      end

      ActiveRecord::Result.new(cols, records)
    end
  end
end
exec_update(sql, name = "SQL", binds = [])
Alias for: exec_delete
explain(arel, binds = []) click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 200
def explain(arel, binds = [])
  sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
  SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
end
foreign_keys(table_name) click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 353
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
insert_fixtures(rows, table_name) click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 366
def insert_fixtures(rows, table_name)
  rows.each do |row|
    insert_fixture(row, table_name)
  end
end
last_inserted_id(result) click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 244
def last_inserted_id(result)
  @connection.last_insert_row_id
end
rename_table(table_name, new_name) click to toggle source

Renames a table.

Example:

rename_table('octopuses', 'octopi')
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 284
def rename_table(table_name, new_name)
  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 activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 122
def requires_reloading?
  true
end
supports_datetime_with_precision?() click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 134
def supports_datetime_with_precision?
  true
end
supports_ddl_transactions?() click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 110
def supports_ddl_transactions?
  true
end
supports_explain?() click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 179
def supports_explain?
  true
end
supports_foreign_keys_in_create?() click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 126
def supports_foreign_keys_in_create?
  sqlite_version >= "3.6.19"
end
supports_index_sort_order?() click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 159
def supports_index_sort_order?
  true
end
supports_multi_insert?() click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 138
def supports_multi_insert?
  sqlite_version >= "3.7.11"
end
supports_partial_index?() click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 118
def supports_partial_index?
  sqlite_version >= "3.8.0"
end
supports_savepoints?() click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 114
def supports_savepoints?
  true
end
supports_views?() click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 130
def supports_views?
  true
end
valid_alter_table_type?(type) click to toggle source

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

# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 291
def valid_alter_table_type?(type)
  type.to_sym != :primary_key
end

Private Instance Methods

alter_table(table_name, options = {}) { |definition| ... } click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 381
def alter_table(table_name, options = {})
  altered_table_name = "a#{table_name}"
  caller = lambda { |definition| yield definition if block_given? }

  transaction do
    move_table(table_name, altered_table_name,
      options.merge(temporary: true))
    move_table(altered_table_name, table_name, &caller)
  end
end
arel_visitor() click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 521
def arel_visitor
  Arel::Visitors::SQLite.new(self)
end
column_definitions(table_name)
Alias for: table_structure
configure_connection() click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 525
def configure_connection
  execute("PRAGMA foreign_keys = ON", "SCHEMA")
end
copy_table(from, to, options = {}) { |definition| ... } click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 397
def copy_table(from, to, options = {})
  from_primary_key = primary_key(from)
  options[:id] = false
  create_table(to, options) do |definition|
    @definition = definition
    @definition.primary_key(from_primary_key) if from_primary_key.present?
    columns(from).each do |column|
      column_name = options[:rename] ?
        (options[:rename][column.name] ||
         options[:rename][column.name.to_sym] ||
         column.name) : column.name
      next if column_name == from_primary_key

      @definition.column(column_name, column.type,
        limit: column.limit, default: column.default,
        precision: column.precision, scale: column.scale,
        null: column.null, collation: column.collation)
    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 activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 446
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 activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 423
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

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

    unless columns.empty?
      # index name can't be the same
      opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
      opts[:unique] = true if index.unique
      add_index(to, columns, opts)
    end
  end
end
move_table(from, to, options = {}, &block) click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 392
def move_table(from, to, options = {}, &block)
  copy_table(from, to, options, &block)
  drop_table(from)
end
sqlite_version() click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 459
def sqlite_version
  @sqlite_version ||= SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
end
table_structure(table_name) click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 374
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 activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 482
        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 = exec_query(sql, "SCHEMA").first

          if result
            # Splitting with left parentheses and picking up last will return all
            # columns separated with comma(,).
            columns_string = result["sql"].split("(").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_hash
          end
        end
translate_exception(exception, message) click to toggle source
# File activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb, line 463
def translate_exception(exception, message)
  case exception.message
  # 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
  when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
    RecordNotUnique.new(message)
  when /.* may not be NULL/, /NOT NULL constraint failed: .*/
    NotNullViolation.new(message)
  when /FOREIGN KEY constraint failed/i
    InvalidForeignKey.new(message)
  else
    super
  end
end