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
Difference we remove connection_options because we are not using it.
# 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
# File lib/arjdbc/sqlite3/adapter.rb, line 121 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/arjdbc/sqlite3/adapter.rb, line 145 def allowed_index_name_length index_name_length - 2 end
Clears the prepared statements cache.
# File lib/arjdbc/sqlite3/adapter.rb, line 134 def clear_cache! @statements.clear end
Disconnects from the database if already connected. Otherwise, this method does nothing.
# File lib/arjdbc/sqlite3/adapter.rb, line 127 def disconnect! super @active = false @connection.close rescue nil end
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
# File lib/arjdbc/sqlite3/adapter.rb, line 218 def exec_delete(sql, name = 'SQL', binds = []) exec_query(sql, name, binds) @connection.changes end
# 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
# 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
# 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
# File lib/arjdbc/sqlite3/adapter.rb, line 224 def last_inserted_id(result) @connection.last_insert_row_id end
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
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
# File lib/arjdbc/sqlite3/adapter.rb, line 101 def requires_reloading? true end
# File lib/arjdbc/sqlite3/adapter.rb, line 113 def supports_datetime_with_precision? true end
# File lib/arjdbc/sqlite3/adapter.rb, line 83 def supports_ddl_transactions? true end
# File lib/arjdbc/sqlite3/adapter.rb, line 158 def supports_explain? true end
# File lib/arjdbc/sqlite3/adapter.rb, line 105 def supports_foreign_keys_in_create? sqlite_version >= "3.6.19" end
# File lib/arjdbc/sqlite3/adapter.rb, line 138 def supports_index_sort_order? true end
# File lib/arjdbc/sqlite3/adapter.rb, line 117 def supports_multi_insert? sqlite_version >= "3.7.11" end
# File lib/arjdbc/sqlite3/adapter.rb, line 91 def supports_partial_index? sqlite_version >= "3.8.0" end
# File lib/arjdbc/sqlite3/adapter.rb, line 87 def supports_savepoints? true end
Returns true, since this connection adapter supports prepared statement caching.
# File lib/arjdbc/sqlite3/adapter.rb, line 97 def supports_statement_cache? true end
# File lib/arjdbc/sqlite3/adapter.rb, line 109 def supports_views? true end
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
# File lib/arjdbc/sqlite3/adapter.rb, line 560 def configure_connection execute("PRAGMA foreign_keys = ON", "SCHEMA") end
# File lib/arjdbc/sqlite3/adapter.rb, line 547 def create_table_definition(*args) # DIFFERENCE: FQN ::ActiveRecord::ConnectionAdapters::SQLite3::TableDefinition.new(*args) end
# 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
# File lib/arjdbc/sqlite3/adapter.rb, line 482 def sqlite_version @sqlite_version ||= SQLite3Adapter::Version.new(select_value("select sqlite_version(*)")) end
# 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
# 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
# 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