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:
-
:database
- Path to the database file.
Constants
- ADAPTER_NAME
- COLLATE_REGEX
- NATIVE_DATABASE_TYPES
Public Class Methods
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 95 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
Public Instance Methods
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 142 def active? @active != false end
Returns 62. SQLite supports index names up to 64 characters. The rest is used by Rails internally to perform temporary rename operations
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 166 def allowed_index_name_length index_name_length - 2 end
Clears the prepared statements cache.
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 155 def clear_cache! @statements.clear end
Disconnects from the database if already connected. Otherwise, this method does nothing.
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 148 def disconnect! super @active = false @connection.close rescue nil end
Returns the current database encoding format as a string, eg: 'UTF-8'
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 175 def encoding @connection.encoding.to_s end
# File 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
# File 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
# File 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
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 400 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
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 244 def last_inserted_id(result) @connection.last_insert_row_id end
SCHEMA STATEMENTS ========================================
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 270 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
Renames a table.
Example:
rename_table('octopuses', 'octopi')
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 331 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
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 122 def requires_reloading? true end
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 134 def supports_datetime_with_precision? true end
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 104 def supports_ddl_transactions? true end
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 179 def supports_explain? true end
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 126 def supports_foreign_keys_in_create? sqlite_version >= "3.6.19" end
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 159 def supports_index_sort_order? true end
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 138 def supports_multi_insert? sqlite_version >= "3.7.11" end
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 112 def supports_partial_index? sqlite_version >= "3.8.0" end
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 108 def supports_savepoints? true end
Returns true, since this connection adapter supports prepared statement caching.
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 118 def supports_statement_cache? true end
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 130 def supports_views? true end
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 338 def valid_alter_table_type?(type) type.to_sym != :primary_key end
Private Instance Methods
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 422 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
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 574 def configure_connection execute("PRAGMA foreign_keys = ON", "SCHEMA") end
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 438 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
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 487 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
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 464 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
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 562 def create_table_definition(*args) SQLite3::TableDefinition.new(*args) end
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 566 def extract_foreign_key_action(specifier) case specifier when "CASCADE"; :cascade when "SET NULL"; :nullify when "RESTRICT"; :restrict end end
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 433 def move_table(from, to, options = {}, &block) copy_table(from, to, options, &block) drop_table(from) end
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 500 def sqlite_version @sqlite_version ||= SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)")) end
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 415 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
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 523 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
# File lib/active_record/connection_adapters/sqlite3_adapter.rb, line 504 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