Module: ArJdbc::SQLite3

Includes:
ActiveRecord::ConnectionAdapters::SQLite3::Quoting
Included in:
ActiveRecord::ConnectionAdapters::SQLite3Adapter
Defined in:
lib/arjdbc/sqlite3/adapter.rb

Overview

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.

Defined Under Namespace

Classes: StatementPool

Constant Summary collapse

ConnectionAdapters =

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

::ActiveRecord::ConnectionAdapters
IndexDefinition =
::ActiveRecord::ConnectionAdapters::IndexDefinition
Quoting =
::ActiveRecord::ConnectionAdapters::SQLite3::Quoting
RecordNotUnique =
::ActiveRecord::RecordNotUnique
SchemaCreation =
ConnectionAdapters::SQLite3::SchemaCreation
SQLite3Adapter =
ConnectionAdapters::AbstractAdapter
ADAPTER_NAME =
'SQLite'.freeze
NATIVE_DATABASE_TYPES =
{
    primary_key:  "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL",
    string:       { name: "varchar" },
    text:         { name: "text" },
    integer:      { name: "integer" },
    float:        { name: "float" },
    decimal:      { name: "decimal" },
    datetime:     { name: "datetime" },
    time:         { name: "time" },
    date:         { name: "date" },
    binary:       { name: "blob" },
    boolean:      { name: "boolean" }
}

Instance Method Summary collapse

Instance Method Details

#active?Boolean

Returns:

  • (Boolean)


114
115
116
# File 'lib/arjdbc/sqlite3/adapter.rb', line 114

def active?
  @active != false
end

#add_column(table_name, column_name, type, options = {}) ⇒ Object

:nodoc:



349
350
351
352
353
354
355
356
357
# File 'lib/arjdbc/sqlite3/adapter.rb', line 349

def add_column(table_name, column_name, type, options = {}) #:nodoc:
  if valid_alter_table_type?(type)
    super(table_name, column_name, type, options)
  else
    alter_table(table_name) do |definition|
      definition.column(column_name, type, options)
    end
  end
end

#allowed_index_name_lengthObject

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



142
143
144
# File 'lib/arjdbc/sqlite3/adapter.rb', line 142

def allowed_index_name_length
  index_name_length - 2
end

#alter_table(table_name, options = {}) ⇒ Object (protected)

:nodoc:



411
412
413
414
415
416
417
418
419
420
# File 'lib/arjdbc/sqlite3/adapter.rb', line 411

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

#arel_visitorObject

:nodoc:



59
60
61
# File 'lib/arjdbc/sqlite3/adapter.rb', line 59

def arel_visitor # :nodoc:
  Arel::Visitors::SQLite.new(self)
end

#begin_db_transactionObject

:nodoc:



213
214
215
# File 'lib/arjdbc/sqlite3/adapter.rb', line 213

def begin_db_transaction #:nodoc:
  log("begin transaction",nil) { @connection.transaction }
end

#change_column(table_name, column_name, type, options = {}) ⇒ Object

:nodoc:



382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/arjdbc/sqlite3/adapter.rb', line 382

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)
      self.collation = options[:collation] if options.include?(:collation)
    end
  end
end

#change_column_default(table_name, column_name, default_or_changes) ⇒ Object

:nodoc:



365
366
367
368
369
370
371
# File 'lib/arjdbc/sqlite3/adapter.rb', line 365

def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
  default = extract_new_default_value(default_or_changes)

  alter_table(table_name) do |definition|
    definition[column_name].default = default
  end
end

#change_column_null(table_name, column_name, null, default = nil) ⇒ Object

:nodoc:



373
374
375
376
377
378
379
380
# File 'lib/arjdbc/sqlite3/adapter.rb', line 373

def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
  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

#clear_cache!Object

Clears the prepared statements cache.



127
128
129
# File 'lib/arjdbc/sqlite3/adapter.rb', line 127

def clear_cache!
  @statements.clear
end

#columns(table_name) ⇒ Object

Returns an array of +Column+ objects for the table specified by +table_name+.



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/arjdbc/sqlite3/adapter.rb', line 280

def columns(table_name) # :nodoc:
  table_name = table_name.to_s
  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

    collation = field["collation"]
    sql_type = field["type"]
     = (sql_type)
    new_column(field["name"], field["dflt_value"], , field["notnull"].to_i == 0, table_name, nil, collation)
  end
end

#commit_db_transactionObject

:nodoc:



217
218
219
# File 'lib/arjdbc/sqlite3/adapter.rb', line 217

def commit_db_transaction #:nodoc:
  log("commit transaction",nil) { @connection.commit }
end

#copy_table(from, to, options = {}) ⇒ Object (protected)

:nodoc:



427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# File 'lib/arjdbc/sqlite3/adapter.rb', line 427

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, 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 = {}) ⇒ Object (protected)

:nodoc:



476
477
478
479
480
481
482
483
484
485
486
487
# File 'lib/arjdbc/sqlite3/adapter.rb', line 476

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(&: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 = {}) ⇒ Object (protected)

:nodoc:



453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
# File 'lib/arjdbc/sqlite3/adapter.rb', line 453

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(&: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

#data_source_exists?(table_name) ⇒ Boolean

Returns:

  • (Boolean)


257
258
259
260
261
262
263
264
# File 'lib/arjdbc/sqlite3/adapter.rb', line 257

def data_source_exists?(table_name)
  return false unless table_name.present?

  sql = "SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'"
  sql << " AND name = #{quote(table_name)}"

  select_values(sql, "SCHEMA").any?
end

#data_sourcesObject



243
244
245
# File 'lib/arjdbc/sqlite3/adapter.rb', line 243

def data_sources
  select_values("SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'", "SCHEMA")
end

#disconnect!Object

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



120
121
122
123
124
# File 'lib/arjdbc/sqlite3/adapter.rb', line 120

def disconnect!
  super
  @active = false
  @connection.close rescue nil
end

#encodingObject

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



151
152
153
# File 'lib/arjdbc/sqlite3/adapter.rb', line 151

def encoding
  @connection.encoding.to_s
end

#exec_delete(sql, name = 'SQL', binds = []) ⇒ Object Also known as: exec_update



199
200
201
202
# File 'lib/arjdbc/sqlite3/adapter.rb', line 199

def exec_delete(sql, name = 'SQL', binds = [])
  exec_query(sql, name, binds)
  @connection.changes
end

#exec_query(sql, name = nil, binds = [], prepare: false) ⇒ Object



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/arjdbc/sqlite3/adapter.rb', line 168

def exec_query(sql, name = nil, binds = [], prepare: false)
  type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }

  log(sql, name, binds) 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
      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)
    end

    ActiveRecord::Result.new(cols, stmt.to_a)
  end
end

#exec_rollback_db_transactionObject

:nodoc:



221
222
223
# File 'lib/arjdbc/sqlite3/adapter.rb', line 221

def exec_rollback_db_transaction #:nodoc:
  log("rollback transaction",nil) { @connection.rollback }
end

#execute(sql, name = nil) ⇒ Object

:nodoc:



209
210
211
# File 'lib/arjdbc/sqlite3/adapter.rb', line 209

def execute(sql, name = nil) #:nodoc:
  log(sql, name) { @connection.execute(sql) }
end

#explain(arel, binds = []) ⇒ Object

-- DATABASE STATEMENTS ====================================== ++



163
164
165
166
# File 'lib/arjdbc/sqlite3/adapter.rb', line 163

def explain(arel, binds = [])
  sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
  ::ActiveRecord::ConnectionAdapters::SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
end

#indexes(table_name, name = nil) ⇒ Object

Returns an array of indexes for the given table.



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/arjdbc/sqlite3/adapter.rb', line 300

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

#initialize(connection, logger, config) ⇒ Object

Difference we remove connection_options because we are not using it.



64
65
66
67
68
69
# File 'lib/arjdbc/sqlite3/adapter.rb', line 64

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

#last_inserted_id(result) ⇒ Object



205
206
207
# File 'lib/arjdbc/sqlite3/adapter.rb', line 205

def last_inserted_id(result)
  @connection.last_insert_row_id
end

#move_table(from, to, options = {}, &block) ⇒ Object (protected)

:nodoc:



422
423
424
425
# File 'lib/arjdbc/sqlite3/adapter.rb', line 422

def move_table(from, to, options = {}, &block) #:nodoc:
  copy_table(from, to, options, &block)
  drop_table(from)
end

#native_database_typesObject

:nodoc:



146
147
148
# File 'lib/arjdbc/sqlite3/adapter.rb', line 146

def native_database_types #:nodoc:
  NATIVE_DATABASE_TYPES
end

#primary_keys(table_name) ⇒ Object

:nodoc:



324
325
326
327
# File 'lib/arjdbc/sqlite3/adapter.rb', line 324

def primary_keys(table_name) # :nodoc:
  pks = table_structure(table_name).select { |f| f["pk"] > 0 }
  pks.sort_by { |f| f["pk"] }.map { |f| f["name"] }
end

#remove_column(table_name, column_name, type = nil, options = {}) ⇒ Object

:nodoc:



359
360
361
362
363
# File 'lib/arjdbc/sqlite3/adapter.rb', line 359

def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
  alter_table(table_name) do |definition|
    definition.remove_column column_name
  end
end

#remove_index(table_name, options = {}) ⇒ Object

:nodoc:



329
330
331
332
# File 'lib/arjdbc/sqlite3/adapter.rb', line 329

def remove_index(table_name, options = {}) #:nodoc:
  index_name = index_name_for_remove(table_name, options)
  exec_query "DROP INDEX #{quote_column_name(index_name)}"
end

#rename_column(table_name, column_name, new_column_name) ⇒ Object

:nodoc:



397
398
399
400
401
# File 'lib/arjdbc/sqlite3/adapter.rb', line 397

def rename_column(table_name, column_name, new_column_name) #:nodoc:
  column = column_for(table_name, column_name)
  alter_table(table_name, rename: { column.name => new_column_name.to_s })
  rename_column_indexes(table_name, column.name, new_column_name)
end

#rename_table(table_name, new_name) ⇒ Object

Renames a table.

Example: rename_table('octopuses', 'octopi')



338
339
340
341
# File 'lib/arjdbc/sqlite3/adapter.rb', line 338

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?Boolean

Returns:

  • (Boolean)


98
99
100
# File 'lib/arjdbc/sqlite3/adapter.rb', line 98

def requires_reloading?
  true
end

#schema_creationObject

:nodoc:



55
56
57
# File 'lib/arjdbc/sqlite3/adapter.rb', line 55

def schema_creation # :nodoc:
  SQLite3::SchemaCreation.new self
end

#sqlite_versionObject (protected)



489
490
491
# File 'lib/arjdbc/sqlite3/adapter.rb', line 489

def sqlite_version
  @sqlite_version ||= SQLite3Adapter::Version.new(select_value("select sqlite_version(*)"))
end

#supports_datetime_with_precision?Boolean

Returns:

  • (Boolean)


106
107
108
# File 'lib/arjdbc/sqlite3/adapter.rb', line 106

def supports_datetime_with_precision?
  true
end

#supports_ddl_transactions?Boolean

Returns:

  • (Boolean)


71
72
73
# File 'lib/arjdbc/sqlite3/adapter.rb', line 71

def supports_ddl_transactions?
  true
end

#supports_explain?Boolean

Returns:

  • (Boolean)


155
156
157
# File 'lib/arjdbc/sqlite3/adapter.rb', line 155

def supports_explain?
  true
end

#supports_index_sort_order?Boolean

Returns:

  • (Boolean)


131
132
133
# File 'lib/arjdbc/sqlite3/adapter.rb', line 131

def supports_index_sort_order?
  true
end

#supports_migrations?Boolean

Returns true, since this connection adapter supports migrations.

Returns:

  • (Boolean)


90
91
92
# File 'lib/arjdbc/sqlite3/adapter.rb', line 90

def supports_migrations? #:nodoc:
  true
end

#supports_multi_insert?Boolean

Returns:

  • (Boolean)


110
111
112
# File 'lib/arjdbc/sqlite3/adapter.rb', line 110

def supports_multi_insert?
  sqlite_version >= "3.7.11"
end

#supports_partial_index?Boolean

Returns:

  • (Boolean)


79
80
81
# File 'lib/arjdbc/sqlite3/adapter.rb', line 79

def supports_partial_index?
  sqlite_version >= "3.8.0"
end

#supports_primary_key?Boolean

:nodoc:

Returns:

  • (Boolean)


94
95
96
# File 'lib/arjdbc/sqlite3/adapter.rb', line 94

def supports_primary_key? #:nodoc:
  true
end

#supports_savepoints?Boolean

Returns:

  • (Boolean)


75
76
77
# File 'lib/arjdbc/sqlite3/adapter.rb', line 75

def supports_savepoints?
  true
end

#supports_statement_cache?Boolean

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

Returns:

  • (Boolean)


85
86
87
# File 'lib/arjdbc/sqlite3/adapter.rb', line 85

def supports_statement_cache?
  true
end

#supports_views?Boolean

Returns:

  • (Boolean)


102
103
104
# File 'lib/arjdbc/sqlite3/adapter.rb', line 102

def supports_views?
  true
end

#table_exists?(table_name) ⇒ Boolean

Returns:

  • (Boolean)


247
248
249
250
251
252
253
254
255
# File 'lib/arjdbc/sqlite3/adapter.rb', line 247

def table_exists?(table_name)
  ActiveSupport::Deprecation.warn(<<-MSG.squish)
      #table_exists? currently checks both tables and views.
      This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
      Use #data_source_exists? instead.
  MSG

  data_source_exists?(table_name)
end

#table_structure(table_name) ⇒ Object (protected)

Raises:

  • (ActiveRecord::StatementInvalid)


405
406
407
408
409
# File 'lib/arjdbc/sqlite3/adapter.rb', line 405

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

#tables(name = nil) ⇒ Object

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



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/arjdbc/sqlite3/adapter.rb', line 227

def tables(name = nil) # :nodoc:
  ActiveSupport::Deprecation.warn(<<-MSG.squish)
      #tables currently returns both tables and views.
      This behavior is deprecated and will be changed with Rails 5.1 to only return tables.
      Use #data_sources instead.
  MSG

  if name
    ActiveSupport::Deprecation.warn(<<-MSG.squish)
        Passing arguments to #tables is deprecated without replacement.
    MSG
  end

  data_sources
end

#translate_exception(exception, message) ⇒ Object (protected)



493
494
495
496
497
498
499
500
501
502
503
504
# File 'lib/arjdbc/sqlite3/adapter.rb', line 493

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)
    else
      super
  end
end

#valid_alter_table_type?(type) ⇒ Boolean

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

Returns:

  • (Boolean)


345
346
347
# File 'lib/arjdbc/sqlite3/adapter.rb', line 345

def valid_alter_table_type?(type)
  type.to_sym != :primary_key
end

#valid_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


135
136
137
# File 'lib/arjdbc/sqlite3/adapter.rb', line 135

def valid_type?(type)
  true
end

#view_exists?(view_name) ⇒ Boolean

:nodoc:

Returns:

  • (Boolean)


270
271
272
273
274
275
276
277
# File 'lib/arjdbc/sqlite3/adapter.rb', line 270

def view_exists?(view_name) # :nodoc:
  return false unless view_name.present?

  sql = "SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'"
  sql << " AND name = #{quote(view_name)}"

  select_values(sql, "SCHEMA").any?
end

#viewsObject

:nodoc:



266
267
268
# File 'lib/arjdbc/sqlite3/adapter.rb', line 266

def views # :nodoc:
  select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", "SCHEMA")
end