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)


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

def active?
  @active != false
end

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

:nodoc:



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

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



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

def allowed_index_name_length
  index_name_length - 2
end

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

:nodoc:



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

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:



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

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

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

:nodoc:



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

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:



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

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:



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

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.



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

def clear_cache!
  @statements.clear
end

#columns(table_name) ⇒ Object

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



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

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:



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

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

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

:nodoc:



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

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:



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

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:



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

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)


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

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



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

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.



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

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

#encodingObject

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



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

def encoding
  @connection.encoding.to_s
end

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



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

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

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



167
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
# File 'lib/arjdbc/sqlite3/adapter.rb', line 167

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:



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

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

#execute(sql, name = nil) ⇒ Object

:nodoc:



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

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

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

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



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

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.



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

def indexes(table_name, name = nil) #:nodoc:
  exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
    sql = "        SELECT sql\n        FROM sqlite_master\n        WHERE name=\#{quote(row['name'])} AND type='index'\n        UNION ALL\n        SELECT sql\n        FROM sqlite_temp_master\n        WHERE name=\#{quote(row['name'])} AND type='index'\n    SQL\n    index_sql = exec_query(sql).first[\"sql\"]\n    match = /\\sWHERE\\s+(.+)$/i.match(index_sql)\n    where = match[1] if match\n    IndexDefinition.new(\n        table_name,\n        row[\"name\"],\n        row[\"unique\"] != 0,\n        exec_query(\"PRAGMA index_info('\#{row['name']}')\", \"SCHEMA\").map { |col|\n          col[\"name\"]\n        }, nil, nil, where)\n  end\nend\n"

#initialize(connection, logger, connection_options, config) ⇒ Object



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

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]))
end

#last_inserted_id(result) ⇒ Object



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

def last_inserted_id(result)
  @connection.last_insert_row_id
end

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

:nodoc:



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

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

#native_database_typesObject

:nodoc:



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

def native_database_types #:nodoc:
  NATIVE_DATABASE_TYPES
end

#primary_keys(table_name) ⇒ Object

:nodoc:



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

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:



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

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:



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

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:



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

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')



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

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)


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

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)



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

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

#supports_datetime_with_precision?Boolean

Returns:

  • (Boolean)


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

def supports_datetime_with_precision?
  true
end

#supports_ddl_transactions?Boolean

Returns:

  • (Boolean)


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

def supports_ddl_transactions?
  true
end

#supports_explain?Boolean

Returns:

  • (Boolean)


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

def supports_explain?
  true
end

#supports_index_sort_order?Boolean

Returns:

  • (Boolean)


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

def supports_index_sort_order?
  true
end

#supports_migrations?Boolean

Returns true, since this connection adapter supports migrations.

Returns:

  • (Boolean)


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

def supports_migrations? #:nodoc:
  true
end

#supports_multi_insert?Boolean

Returns:

  • (Boolean)


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

def supports_multi_insert?
  sqlite_version >= "3.7.11"
end

#supports_partial_index?Boolean

Returns:

  • (Boolean)


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

def supports_partial_index?
  sqlite_version >= "3.8.0"
end

#supports_primary_key?Boolean

:nodoc:

Returns:

  • (Boolean)


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

def supports_primary_key? #:nodoc:
  true
end

#supports_savepoints?Boolean

Returns:

  • (Boolean)


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

def supports_savepoints?
  true
end

#supports_statement_cache?Boolean

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

Returns:

  • (Boolean)


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

def supports_statement_cache?
  true
end

#supports_views?Boolean

Returns:

  • (Boolean)


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

def supports_views?
  true
end

#table_exists?(table_name) ⇒ Boolean

Returns:

  • (Boolean)


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

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

#table_structure(table_name) ⇒ Object (protected)

Raises:

  • (ActiveRecord::StatementInvalid)


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

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 ========================================



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

def tables(name = nil) # :nodoc:
  ActiveSupport::Deprecation.warn("      #tables currently returns both tables and views.\n      This behavior is deprecated and will be changed with Rails 5.1 to only return tables.\n      Use #data_sources instead.\n  MSG\n\n  if name\n    ActiveSupport::Deprecation.warn(<<-MSG.squish)\n        Passing arguments to #tables is deprecated without replacement.\n    MSG\n  end\n\n  data_sources\nend\n".squish)

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



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

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)


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

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

#valid_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


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

def valid_type?(type)
  true
end

#view_exists?(view_name) ⇒ Boolean

:nodoc:

Returns:

  • (Boolean)


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

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:



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

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