class ActiveRecord::ConnectionAdapters::SQLite3Adapter
-
:database- Path to the database file.
Options:
with the sqlite3-ruby drivers (available as gem from rubygems.org/gems/sqlite3).
The SQLite3 adapter works SQLite 3.6.16 or newer
def active?
def active? @active != false end
def adapter_name #:nodoc:
def adapter_name #:nodoc: 'SQLite' end
def add_column(table_name, column_name, type, options = {}) #:nodoc:
def add_column(table_name, column_name, type, options = {}) #:nodoc: if supports_add_column? && valid_alter_table_options( type, options ) super(table_name, column_name, type, options) else alter_table(table_name) do |definition| definition.column(column_name, type, options) end end end
def allowed_index_name_length
characters. The rest is used by rails internally to perform
Returns 62. SQLite supports index names up to 64
def allowed_index_name_length index_name_length - 2 end
def alter_table(table_name, options = {}) #:nodoc:
def alter_table(table_name, options = {}) #:nodoc: 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
def begin_db_transaction #:nodoc:
def begin_db_transaction #:nodoc: log('begin transaction',nil) { @connection.transaction } end
def change_column(table_name, column_name, type, options = {}) #:nodoc:
def change_column(table_name, column_name, type, options = {}) #:nodoc: alter_table(table_name) do |definition| include_default = options_include_default?(options) definition[column_name].instance_eval do self.type = type self.limit = options[:limit] if options.include?(:limit) self.default = options[:default] if include_default self.null = options[:null] if options.include?(:null) self.precision = options[:precision] if options.include?(:precision) self.scale = options[:scale] if options.include?(:scale) end end end
def change_column_default(table_name, column_name, default) #:nodoc:
def change_column_default(table_name, column_name, default) #:nodoc: alter_table(table_name) do |definition| definition[column_name].default = default end end
def change_column_null(table_name, column_name, null, default = nil)
def change_column_null(table_name, column_name, null, default = nil) unless null || default.nil? exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") end alter_table(table_name) do |definition| definition[column_name].null = null end end
def clear_cache!
def clear_cache! @statements.clear end
def columns(table_name) #:nodoc:
Returns an array of +SQLite3Column+ objects for the table specified by +table_name+.
def columns(table_name) #:nodoc: table_structure(table_name).map do |field| 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 SQLite3Column.new(field['name'], field['dflt_value'], field['type'], field['notnull'].to_i == 0) end end
def commit_db_transaction #:nodoc:
def commit_db_transaction #:nodoc: log('commit transaction',nil) { @connection.commit } end
def copy_table(from, to, options = {}) #:nodoc:
def copy_table(from, to, options = {}) #:nodoc: 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) end yield @definition if block_given? end copy_table_indexes(from, to, options[:rename] || {}) copy_table_contents(from, to, @definition.columns.map {|column| column.name}, options[:rename] || {}) end
def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
def copy_table_contents(from, to, columns, rename = {}) #:nodoc: column_mappings = Hash[columns.map {|name| [name, name]}] rename.each { |a| column_mappings[a.last] = a.first } from_columns = columns(from).collect {|col| col.name} columns = columns.find_all{|col| from_columns.include?(column_mappings[col])} quoted_columns = columns.map { |col| quote_column_name(col) } * ',' quoted_to = quote_table_name(to) raw_column_mappings = Hash[columns(from).map { |c| [c.name, c] }] exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row| sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES (" column_values = columns.map do |col| quote(row[column_mappings[col]], raw_column_mappings[col]) end sql << column_values * ', ' sql << ')' exec_query sql end end
def copy_table_indexes(from, to, rename = {}) #:nodoc:
def copy_table_indexes(from, to, rename = {}) #:nodoc: 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 { |c| c.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
def delete_sql(sql, name = nil) #:nodoc:
def delete_sql(sql, name = nil) #:nodoc: sql += " WHERE 1=1" unless sql =~ /WHERE/i super sql, name end
def disconnect!
Disconnects from the database if already connected. Otherwise, this
def disconnect! super @active = false @connection.close rescue nil end
def encoding
def encoding @connection.encoding.to_s end
def exec_delete(sql, name = 'SQL', binds = [])
def exec_delete(sql, name = 'SQL', binds = []) exec_query(sql, name, binds) @connection.changes end
def exec_query(sql, name = nil, binds = [])
def exec_query(sql, name = nil, binds = []) type_casted_binds = binds.map { |col, val| [col, type_cast(val, col)] } log(sql, name, type_casted_binds) do # Don't cache statements if they are not prepared if without_prepared_statement?(binds) stmt = @connection.prepare(sql) cols = stmt.columns records = stmt.to_a stmt.close stmt = records else cache = @statements[sql] ||= { :stmt => @connection.prepare(sql) } stmt = cache[:stmt] cols = cache[:cols] ||= stmt.columns stmt.reset! stmt.bind_params type_casted_binds.map { |_, val| val } end ActiveRecord::Result.new(cols, stmt.to_a) end end
def execute(sql, name = nil) #:nodoc:
def execute(sql, name = nil) #:nodoc: log(sql, name) { @connection.execute(sql) } end
def explain(arel, binds = [])
def explain(arel, binds = []) sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}" ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds)) end
def indexes(table_name, name = nil) #:nodoc:
Returns an array of indexes for the given table.
def indexes(table_name, name = nil) #:nodoc: exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", 'SCHEMA').map do |row| sql = <<-SQL SELECT sql FROM sqlite_master WHERE name=#{quote(row['name'])} AND type='index' UNION ALL SELECT sql FROM sqlite_temp_master WHERE name=#{quote(row['name'])} AND type='index' SQL index_sql = exec_query(sql).first['sql'] match = /\sWHERE\s+(.+)$/i.match(index_sql) where = match[1] if match IndexDefinition.new( table_name, row['name'], row['unique'] != 0, exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col| col['name'] }, nil, nil, where) end end
def initialize(connection, logger, config)
def initialize(connection, logger, config) super(connection, logger) @active = nil @statements = StatementPool.new(@connection, self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })) @config = config if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @prepared_statements = true @visitor = Arel::Visitors::SQLite.new self else @visitor = unprepared_visitor end end
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: super id_value || @connection.last_insert_row_id end
def last_inserted_id(result)
def last_inserted_id(result) @connection.last_insert_row_id end
def move_table(from, to, options = {}, &block) #:nodoc:
def move_table(from, to, options = {}, &block) #:nodoc: copy_table(from, to, options, &block) drop_table(from) end
def native_database_types #:nodoc:
def native_database_types #:nodoc: NATIVE_DATABASE_TYPES end
def primary_key(table_name) #:nodoc:
def primary_key(table_name) #:nodoc: column = table_structure(table_name).find { |field| field['pk'] == 1 } column && column['name'] end
def quote(value, column = nil)
def quote(value, column = nil) if value.kind_of?(String) && column && column.type == :binary s = value.unpack("H*")[0] "x'#{s}'" else super end end
def quote_column_name(name) #:nodoc:
def quote_column_name(name) #:nodoc: %Q("#{name.to_s.gsub('"', '""')}") end
def quote_string(s) #:nodoc:
def quote_string(s) #:nodoc: @connection.class.quote(s) end
def quote_table_name_for_assignment(table, attr)
def quote_table_name_for_assignment(table, attr) quote_column_name(attr) end
def quoted_date(value) #:nodoc:
if the value is a Time responding to usec.
Quote date/time values for use in SQL input. Includes microseconds
def quoted_date(value) #:nodoc: if value.respond_to?(:usec) "#{super}.#{sprintf("%06d", value.usec)}" else super end end
def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc: alter_table(table_name) do |definition| definition.remove_column column_name end end
def remove_index!(table_name, index_name) #:nodoc:
def remove_index!(table_name, index_name) #:nodoc: exec_query "DROP INDEX #{quote_column_name(index_name)}" end
def rename_column(table_name, column_name, new_column_name) #:nodoc:
def rename_column(table_name, column_name, new_column_name) #:nodoc: unless columns(table_name).detect{|c| c.name == column_name.to_s } raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}" end alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s}) rename_column_indexes(table_name, column_name, new_column_name) end
def rename_table(table_name, new_name)
Example:
Renames a table.
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
def requires_reloading?
def requires_reloading? true end
def rollback_db_transaction #:nodoc:
def rollback_db_transaction #:nodoc: log('rollback transaction',nil) { @connection.rollback } end
def select(sql, name = nil, binds = []) #:nodoc:
def select(sql, name = nil, binds = []) #:nodoc: exec_query(sql, name, binds) end
def select_rows(sql, name = nil, binds = [])
def select_rows(sql, name = nil, binds = []) exec_query(sql, name, binds).rows end
def sqlite_version
def sqlite_version @sqlite_version ||= SQLite3Adapter::Version.new(select_value('select sqlite_version(*)')) end
def supports_add_column?
def supports_add_column? true end
def supports_ddl_transactions?
def supports_ddl_transactions? true end
def supports_explain?
def supports_explain? true end
def supports_index_sort_order?
def supports_index_sort_order? true end
def supports_migrations? #:nodoc:
Returns true, since this connection adapter supports migrations.
def supports_migrations? #:nodoc: true end
def supports_partial_index?
def supports_partial_index? sqlite_version >= '3.8.0' end
def supports_primary_key? #:nodoc:
def supports_primary_key? #:nodoc: true end
def supports_savepoints?
def supports_savepoints? true end
def supports_statement_cache?
Returns true, since this connection adapter supports prepared statement
def supports_statement_cache? true end
def table_exists?(table_name)
def table_exists?(table_name) table_name && tables(nil, table_name).any? end
def table_structure(table_name)
def table_structure(table_name) structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty? structure end
def tables(name = nil, table_name = nil) #:nodoc:
def tables(name = nil, table_name = nil) #:nodoc: sql = <<-SQL SELECT name FROM sqlite_master WHERE type = 'table' AND NOT name = 'sqlite_sequence' SQL sql << " AND name = #{quote_table_name(table_name)}" if table_name exec_query(sql, 'SCHEMA').map do |row| row['name'] end end
def translate_exception(exception, message)
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, exception) else super end end
def type_cast(value, column) # :nodoc:
def type_cast(value, column) # :nodoc: return value.to_f if BigDecimal === value return super unless String === value return super unless column && value value = super if column.type == :string && value.encoding == Encoding::ASCII_8BIT logger.error "Binary data inserted for `string` type on column `#{column.name}`" if logger value = value.encode Encoding::UTF_8 end value end
def update_sql(sql, name = nil) #:nodoc:
def update_sql(sql, name = nil) #:nodoc: super @connection.changes end
def valid_alter_table_options( type, options)
See: http://www.sqlite.org/lang_altertable.html
def valid_alter_table_options( type, options) type.to_sym != :primary_key end