module ArJdbc::SQLite3

All the code in this module is a copy of ConnectionAdapters::SQLite3Adapter from active_record 5. The constants at the front of this file are to allow the rest of the file to remain with no modifications from its original source. If you hack on this file try not to modify this module and instead try and put those overrides in SQL3Adapter below. We try and keep a copy of the Rails this adapter supports with the current goal of being able to diff changes easily over time and to also eventually remove this module from ARJDBC altogether.

Constants

ADAPTER_NAME
COLLATE_REGEX
ConnectionAdapters

DIFFERENCE: Some common constant names to reduce differences in rest of this module from AR5 version

IndexDefinition
NATIVE_DATABASE_TYPES
Quoting
RecordNotUnique
SQLite3Adapter
SchemaCreation

Public Class Methods

new(connection, logger, config) click to toggle source

Difference we remove connection_options because we are not using it.

Calls superclass method
# File lib/arjdbc/sqlite3/adapter.rb, line 76
def initialize(connection, logger, config)
  super(connection, logger, config)

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

Public Instance Methods

active?() click to toggle source
# File lib/arjdbc/sqlite3/adapter.rb, line 121
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 Rails internally to perform temporary rename operations

# File lib/arjdbc/sqlite3/adapter.rb, line 145
def allowed_index_name_length
  index_name_length - 2
end
clear_cache!() click to toggle source

Clears the prepared statements cache.

# File lib/arjdbc/sqlite3/adapter.rb, line 134
def clear_cache!
  @statements.clear
end
disconnect!() click to toggle source

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

Calls superclass method
# File lib/arjdbc/sqlite3/adapter.rb, line 127
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 lib/arjdbc/sqlite3/adapter.rb, line 154
def encoding
  @connection.encoding.to_s
end
exec_delete(sql, name = 'SQL', binds = []) click to toggle source
# File lib/arjdbc/sqlite3/adapter.rb, line 218
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 lib/arjdbc/sqlite3/adapter.rb, line 185
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 lib/arjdbc/sqlite3/adapter.rb, line 179
def explain(arel, binds = [])
  sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
  # DIFFERENCE: FQN
  ::ActiveRecord::ConnectionAdapters::SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
end
foreign_keys(table_name) click to toggle source
# File lib/arjdbc/sqlite3/adapter.rb, line 381
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"])
    }
    # DIFFERENCE: FQN
    ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(table_name, row["table"], options)
  end
end
last_inserted_id(result) click to toggle source
# File lib/arjdbc/sqlite3/adapter.rb, line 224
def last_inserted_id(result)
  @connection.last_insert_row_id
end
new_column_from_field(table_name, field) click to toggle source

SCHEMA STATEMENTS ========================================

# File lib/arjdbc/sqlite3/adapter.rb, line 250
def new_column_from_field(table_name, field) # :nondoc:
  case field["dflt_value"]
  when /^null$/i
    field["dflt_value"] = nil
  when /^'(.*)'$/m
    field["dflt_value"] = $1.gsub("''", "'")
  when /^"(.*)"$/m
    field["dflt_value"] = $1.gsub('""', '"')
  end

  collation = field["collation"]
  sql_type = field["type"]
  type_metadata = fetch_type_metadata(sql_type)
  new_column(field["name"], field["dflt_value"], type_metadata, field["notnull"].to_i == 0, table_name, nil, collation)
end
rename_table(table_name, new_name) click to toggle source

Renames a table.

Example:

rename_table('octopuses', 'octopi')
# File lib/arjdbc/sqlite3/adapter.rb, line 311
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 lib/arjdbc/sqlite3/adapter.rb, line 101
def requires_reloading?
  true
end
supports_datetime_with_precision?() click to toggle source
# File lib/arjdbc/sqlite3/adapter.rb, line 113
def supports_datetime_with_precision?
  true
end
supports_ddl_transactions?() click to toggle source
# File lib/arjdbc/sqlite3/adapter.rb, line 83
def supports_ddl_transactions?
  true
end
supports_explain?() click to toggle source
# File lib/arjdbc/sqlite3/adapter.rb, line 158
def supports_explain?
  true
end
supports_foreign_keys_in_create?() click to toggle source
# File lib/arjdbc/sqlite3/adapter.rb, line 105
def supports_foreign_keys_in_create?
  sqlite_version >= "3.6.19"
end
supports_index_sort_order?() click to toggle source
# File lib/arjdbc/sqlite3/adapter.rb, line 138
def supports_index_sort_order?
  true
end
supports_multi_insert?() click to toggle source
# File lib/arjdbc/sqlite3/adapter.rb, line 117
def supports_multi_insert?
  sqlite_version >= "3.7.11"
end
supports_partial_index?() click to toggle source
# File lib/arjdbc/sqlite3/adapter.rb, line 91
def supports_partial_index?
  sqlite_version >= "3.8.0"
end
supports_savepoints?() click to toggle source
# File lib/arjdbc/sqlite3/adapter.rb, line 87
def supports_savepoints?
  true
end
supports_statement_cache?() click to toggle source

Returns true, since this connection adapter supports prepared statement caching.

# File lib/arjdbc/sqlite3/adapter.rb, line 97
def supports_statement_cache?
  true
end
supports_views?() click to toggle source
# File lib/arjdbc/sqlite3/adapter.rb, line 109
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 lib/arjdbc/sqlite3/adapter.rb, line 318
def valid_alter_table_type?(type)
  type.to_sym != :primary_key
end

Private Instance Methods

column_definitions(table_name)
Alias for: table_structure
configure_connection() click to toggle source
# File lib/arjdbc/sqlite3/adapter.rb, line 560
def configure_connection
  execute("PRAGMA foreign_keys = ON", "SCHEMA")
end
create_table_definition(*args) click to toggle source
# File lib/arjdbc/sqlite3/adapter.rb, line 547
def create_table_definition(*args)
  # DIFFERENCE: FQN
  ::ActiveRecord::ConnectionAdapters::SQLite3::TableDefinition.new(*args)
end
extract_foreign_key_action(specifier) click to toggle source
# File lib/arjdbc/sqlite3/adapter.rb, line 552
def extract_foreign_key_action(specifier)
  case specifier
  when "CASCADE"; :cascade
  when "SET NULL"; :nullify
  when "RESTRICT"; :restrict
  end
end
sqlite_version() click to toggle source
# File lib/arjdbc/sqlite3/adapter.rb, line 482
def sqlite_version
  @sqlite_version ||= SQLite3Adapter::Version.new(select_value("select sqlite_version(*)"))
end
table_structure(table_name) click to toggle source
# File lib/arjdbc/sqlite3/adapter.rb, line 397
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/arjdbc/sqlite3/adapter.rb, line 508
    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
Calls superclass method
# File lib/arjdbc/sqlite3/adapter.rb, line 486
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: .*/
      # DIFFERENCE: FQN
      ::ActiveRecord::RecordNotUnique.new(message)
    when /.* may not be NULL/, /NOT NULL constraint failed: .*/
      # DIFFERENCE: FQN
      ::ActiveRecord::NotNullViolation.new(message)
    when /FOREIGN KEY constraint failed/i
      # DIFFERENCE: FQN
      ::ActiveRecord::InvalidForeignKey.new(message)
    else
      super
  end
end