activerecord-jdbc-adapter 70.1-java → 71.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArJdbc
4
+ module PostgreSQL
5
+ module SchemaStatements
6
+ ForeignKeyDefinition = ActiveRecord::ConnectionAdapters::ForeignKeyDefinition
7
+ Utils = ActiveRecord::ConnectionAdapters::PostgreSQL::Utils
8
+
9
+ def foreign_keys(table_name)
10
+ scope = quoted_scope(table_name)
11
+ fk_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
12
+ SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid, c.condeferrable AS deferrable, c.condeferred AS deferred, c.conkey, c.confkey, c.conrelid, c.confrelid
13
+ FROM pg_constraint c
14
+ JOIN pg_class t1 ON c.conrelid = t1.oid
15
+ JOIN pg_class t2 ON c.confrelid = t2.oid
16
+ JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
17
+ JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
18
+ JOIN pg_namespace t3 ON c.connamespace = t3.oid
19
+ WHERE c.contype = 'f'
20
+ AND t1.relname = #{scope[:name]}
21
+ AND t3.nspname = #{scope[:schema]}
22
+ ORDER BY c.conname
23
+ SQL
24
+
25
+ fk_info.map do |row|
26
+ to_table = Utils.unquote_identifier(row["to_table"])
27
+ # conkey = row["conkey"].scan(/\d+/).map(&:to_i)
28
+ # confkey = row["confkey"].scan(/\d+/).map(&:to_i)
29
+ conkey = row["conkey"]
30
+ confkey = row["confkey"]
31
+
32
+ if conkey.size > 1
33
+ column = column_names_from_column_numbers(row["conrelid"], conkey)
34
+ primary_key = column_names_from_column_numbers(row["confrelid"], confkey)
35
+ else
36
+ column = Utils.unquote_identifier(row["column"])
37
+ primary_key = row["primary_key"]
38
+ end
39
+
40
+ options = {
41
+ column: column,
42
+ name: row["name"],
43
+ primary_key: primary_key
44
+ }
45
+
46
+ options[:on_delete] = extract_foreign_key_action(row["on_delete"])
47
+ options[:on_update] = extract_foreign_key_action(row["on_update"])
48
+ options[:deferrable] = extract_constraint_deferrable(row["deferrable"], row["deferred"])
49
+
50
+ options[:validate] = row["valid"]
51
+
52
+ ForeignKeyDefinition.new(table_name, to_table, options)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -16,6 +16,9 @@ require "active_record/connection_adapters/sqlite3/schema_definitions"
16
16
  require "active_record/connection_adapters/sqlite3/schema_dumper"
17
17
  require "active_record/connection_adapters/sqlite3/schema_statements"
18
18
  require "active_support/core_ext/class/attribute"
19
+ require "arjdbc/sqlite3/column"
20
+
21
+ require "arjdbc/abstract/relation_query_attribute_monkey_patch"
19
22
 
20
23
  module SQLite3
21
24
  module Constants
@@ -89,20 +92,8 @@ module ArJdbc
89
92
  end
90
93
  end
91
94
 
92
- def initialize(connection, logger, connection_options, config)
93
- @memory_database = config[:database] == ":memory:"
94
- super(connection, logger, config)
95
- configure_connection
96
- end
97
-
98
95
  def self.database_exists?(config)
99
- config = config.symbolize_keys
100
- if config[:database] == ":memory:"
101
- true
102
- else
103
- database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database]
104
- File.exist?(database_file)
105
- end
96
+ @config[:database] == ":memory:" || File.exist?(@config[:database].to_s)
106
97
  end
107
98
 
108
99
  def supports_ddl_transactions?
@@ -153,6 +144,10 @@ module ArJdbc
153
144
  database_version >= "3.8.3"
154
145
  end
155
146
 
147
+ def supports_insert_returning?
148
+ database_version >= "3.35.0"
149
+ end
150
+
156
151
  def supports_insert_on_conflict?
157
152
  database_version >= "3.24.0"
158
153
  end
@@ -166,19 +161,22 @@ module ArJdbc
166
161
  end
167
162
 
168
163
  def active?
169
- !@raw_connection.closed?
164
+ @raw_connection && !@raw_connection.closed?
170
165
  end
171
166
 
172
- def reconnect!
173
- super
174
- connect if @connection.closed?
167
+ def return_value_after_insert?(column) # :nodoc:
168
+ column.auto_populated?
175
169
  end
176
170
 
171
+ # MISSING: alias :reset! :reconnect!
172
+
177
173
  # Disconnects from the database if already connected. Otherwise, this
178
174
  # method does nothing.
179
175
  def disconnect!
180
176
  super
181
- @connection.close rescue nil
177
+
178
+ @raw_connection&.close rescue nil
179
+ @raw_connection = nil
182
180
  end
183
181
 
184
182
  def supports_index_sort_order?
@@ -191,7 +189,7 @@ module ArJdbc
191
189
 
192
190
  # Returns the current database encoding format as a string, eg: 'UTF-8'
193
191
  def encoding
194
- @connection.encoding.to_s
192
+ any_raw_connection.encoding.to_s
195
193
  end
196
194
 
197
195
  def supports_explain?
@@ -218,8 +216,14 @@ module ArJdbc
218
216
  end
219
217
  end
220
218
 
221
- def all_foreign_keys_valid? # :nodoc:
222
- execute("PRAGMA foreign_key_check").blank?
219
+ def check_all_foreign_keys_valid! # :nodoc:
220
+ sql = "PRAGMA foreign_key_check"
221
+ result = execute(sql)
222
+
223
+ unless result.blank?
224
+ tables = result.map { |row| row["table"] }
225
+ raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql)
226
+ end
223
227
  end
224
228
 
225
229
  # SCHEMA STATEMENTS ========================================
@@ -234,7 +238,7 @@ module ArJdbc
234
238
 
235
239
  index_name = index_name_for_remove(table_name, column_name, options)
236
240
 
237
- exec_query "DROP INDEX #{quote_column_name(index_name)}"
241
+ internal_exec_query "DROP INDEX #{quote_column_name(index_name)}"
238
242
  end
239
243
 
240
244
 
@@ -242,10 +246,11 @@ module ArJdbc
242
246
  #
243
247
  # Example:
244
248
  # rename_table('octopuses', 'octopi')
245
- def rename_table(table_name, new_name)
249
+ def rename_table(table_name, new_name, **options)
250
+ validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
246
251
  schema_cache.clear_data_source_cache!(table_name.to_s)
247
252
  schema_cache.clear_data_source_cache!(new_name.to_s)
248
- exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
253
+ internal_exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
249
254
  rename_table_indexes(table_name, new_name)
250
255
  end
251
256
 
@@ -285,8 +290,10 @@ module ArJdbc
285
290
  end
286
291
 
287
292
  def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
293
+ validate_change_column_null_argument!(null)
294
+
288
295
  unless null || default.nil?
289
- exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
296
+ internal_exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
290
297
  end
291
298
  alter_table(table_name) do |definition|
292
299
  definition[column_name].null = null
@@ -295,10 +302,7 @@ module ArJdbc
295
302
 
296
303
  def change_column(table_name, column_name, type, **options) #:nodoc:
297
304
  alter_table(table_name) do |definition|
298
- definition[column_name].instance_eval do
299
- self.type = aliased_types(type.to_s, type)
300
- self.options.merge!(options)
301
- end
305
+ definition.change_column(column_name, type, **options)
302
306
  end
303
307
  end
304
308
 
@@ -308,20 +312,42 @@ module ArJdbc
308
312
  rename_column_indexes(table_name, column.name, new_column_name)
309
313
  end
310
314
 
315
+ def add_timestamps(table_name, **options)
316
+ options[:null] = false if options[:null].nil?
317
+
318
+ if !options.key?(:precision)
319
+ options[:precision] = 6
320
+ end
321
+
322
+ alter_table(table_name) do |definition|
323
+ definition.column :created_at, :datetime, **options
324
+ definition.column :updated_at, :datetime, **options
325
+ end
326
+ end
327
+
311
328
  def add_reference(table_name, ref_name, **options) # :nodoc:
312
329
  super(table_name, ref_name, type: :integer, **options)
313
330
  end
314
331
  alias :add_belongs_to :add_reference
315
332
 
316
333
  def foreign_keys(table_name)
317
- fk_info = exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
318
- fk_info.map do |row|
334
+ # SQLite returns 1 row for each column of composite foreign keys.
335
+ fk_info = internal_exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
336
+ grouped_fk = fk_info.group_by { |row| row["id"] }.values.each { |group| group.sort_by! { |row| row["seq"] } }
337
+ grouped_fk.map do |group|
338
+ row = group.first
319
339
  options = {
320
- column: row["from"],
321
- primary_key: row["to"],
322
340
  on_delete: extract_foreign_key_action(row["on_delete"]),
323
341
  on_update: extract_foreign_key_action(row["on_update"])
324
342
  }
343
+
344
+ if group.one?
345
+ options[:column] = row["from"]
346
+ options[:primary_key] = row["to"]
347
+ else
348
+ options[:column] = group.map { |row| row["from"] }
349
+ options[:primary_key] = group.map { |row| row["to"] }
350
+ end
325
351
  # DIFFERENCE: FQN
326
352
  ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(table_name, row["table"], options)
327
353
  end
@@ -342,6 +368,7 @@ module ArJdbc
342
368
  end
343
369
  end
344
370
 
371
+ sql << " RETURNING #{insert.returning}" if insert.returning
345
372
  sql
346
373
  end
347
374
 
@@ -349,6 +376,10 @@ module ArJdbc
349
376
  @config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
350
377
  end
351
378
 
379
+ def use_insert_returning?
380
+ @use_insert_returning
381
+ end
382
+
352
383
  def get_database_version # :nodoc:
353
384
  SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
354
385
  end
@@ -359,6 +390,27 @@ module ArJdbc
359
390
  end
360
391
  end
361
392
 
393
+ # DIFFERENCE: here to
394
+ def new_column_from_field(table_name, field, definitions)
395
+ default = field["dflt_value"]
396
+
397
+ type_metadata = fetch_type_metadata(field["type"])
398
+ default_value = extract_value_from_default(default)
399
+ default_function = extract_default_function(default_value, default)
400
+ rowid = is_column_the_rowid?(field, definitions)
401
+
402
+ ActiveRecord::ConnectionAdapters::SQLite3Column.new(
403
+ field["name"],
404
+ default_value,
405
+ type_metadata,
406
+ field["notnull"].to_i == 0,
407
+ default_function,
408
+ collation: field["collation"],
409
+ auto_increment: field["auto_increment"],
410
+ rowid: rowid
411
+ )
412
+ end
413
+
362
414
  private
363
415
  # See https://www.sqlite.org/limits.html,
364
416
  # the default value is 999 when not configured.
@@ -367,7 +419,7 @@ module ArJdbc
367
419
  end
368
420
 
369
421
  def table_structure(table_name)
370
- structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
422
+ structure = internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
371
423
  raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
372
424
  table_structure_with_collation(table_name, structure)
373
425
  end
@@ -377,15 +429,18 @@ module ArJdbc
377
429
  case default
378
430
  when /^null$/i
379
431
  nil
380
- # Quoted types
381
- when /^'(.*)'$/m
432
+ # Quoted types
433
+ when /^'([^|]*)'$/m
382
434
  $1.gsub("''", "'")
383
- # Quoted types
384
- when /^"(.*)"$/m
435
+ # Quoted types
436
+ when /^"([^|]*)"$/m
385
437
  $1.gsub('""', '"')
386
- # Numeric types
438
+ # Numeric types
387
439
  when /\A-?\d+(\.\d*)?\z/
388
440
  $&
441
+ # Binary columns
442
+ when /x'(.*)'/
443
+ [ $1 ].pack("H*")
389
444
  else
390
445
  # Anything else is blank or some function
391
446
  # and we can't know the value of that, so return nil.
@@ -398,7 +453,7 @@ module ArJdbc
398
453
  end
399
454
 
400
455
  def has_default_function?(default_value, default)
401
- !default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
456
+ !default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP|\|\|}.match?(default)
402
457
  end
403
458
 
404
459
  # See: https://www.sqlite.org/lang_altertable.html
@@ -464,14 +519,24 @@ module ArJdbc
464
519
  if column.has_default?
465
520
  type = lookup_cast_type_from_column(column)
466
521
  default = type.deserialize(column.default)
522
+ default = -> { column.default_function } if default.nil?
467
523
  end
468
524
 
469
- @definition.column(column_name, column.type,
470
- limit: column.limit, default: default,
471
- precision: column.precision, scale: column.scale,
472
- null: column.null, collation: column.collation,
525
+ column_options = {
526
+ limit: column.limit,
527
+ precision: column.precision,
528
+ scale: column.scale,
529
+ null: column.null,
530
+ collation: column.collation,
473
531
  primary_key: column_name == from_primary_key
474
- )
532
+ }
533
+
534
+ unless column.auto_increment?
535
+ column_options[:default] = default
536
+ end
537
+
538
+ column_type = column.bigint? ? :bigint : column.type
539
+ @definition.column(column_name, column_type, **column_options)
475
540
  end
476
541
 
477
542
  yield @definition if block_given?
@@ -519,8 +584,8 @@ module ArJdbc
519
584
  quoted_columns = columns.map { |col| quote_column_name(col) } * ","
520
585
  quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
521
586
 
522
- exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
523
- SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
587
+ internal_exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
588
+ SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
524
589
  end
525
590
 
526
591
  def translate_exception(exception, message:, sql:, binds:)
@@ -530,25 +595,32 @@ module ArJdbc
530
595
  # column *column_name* is not unique
531
596
  if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i)
532
597
  # DIFFERENCE: FQN
533
- ::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds)
598
+ ::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
534
599
  elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i)
535
600
  # DIFFERENCE: FQN
536
- ::ActiveRecord::NotNullViolation.new(message, sql: sql, binds: binds)
601
+ ::ActiveRecord::NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
537
602
  elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
538
603
  # DIFFERENCE: FQN
539
- ::ActiveRecord::InvalidForeignKey.new(message, sql: sql, binds: binds)
604
+ ::ActiveRecord::InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
540
605
  elsif exception.message.match?(/called on a closed database/i)
541
606
  # DIFFERENCE: FQN
542
- ::ActiveRecord::ConnectionNotEstablished.new(exception)
607
+ ::ActiveRecord::ConnectionNotEstablished.new(exception, connection_pool: @pool)
608
+ elsif exception.message.match?(/sql error/i)
609
+ ::ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
610
+ elsif exception.message.match?(/write a readonly database/i)
611
+ message = message.sub('org.sqlite.SQLiteException', 'SQLite3::ReadOnlyException')
612
+ ::ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
543
613
  else
544
614
  super
545
615
  end
546
616
  end
547
617
 
548
618
  COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze
619
+ PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*\"(\w+)\".+PRIMARY KEY AUTOINCREMENT/i
549
620
 
550
621
  def table_structure_with_collation(table_name, basic_structure)
551
622
  collation_hash = {}
623
+ auto_increments = {}
552
624
  sql = <<~SQL
553
625
  SELECT sql FROM
554
626
  (SELECT * FROM sqlite_master UNION ALL
@@ -570,6 +642,7 @@ module ArJdbc
570
642
  # This regex will match the column name and collation type and will save
571
643
  # the value in $1 and $2 respectively.
572
644
  collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
645
+ auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
573
646
  end
574
647
 
575
648
  basic_structure.map do |column|
@@ -579,6 +652,10 @@ module ArJdbc
579
652
  column["collation"] = collation_hash[column_name]
580
653
  end
581
654
 
655
+ if auto_increments.has_key?(column_name)
656
+ column["auto_increment"] = true
657
+ end
658
+
582
659
  column
583
660
  end
584
661
  else
@@ -594,99 +671,56 @@ module ArJdbc
594
671
  StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
595
672
  end
596
673
 
597
- def connect
598
- @connection = ::SQLite3::Database.new(
599
- @config[:database].to_s,
600
- @config.merge(results_as_hash: true)
601
- )
674
+ def reconnect
675
+ if active?
676
+ @raw_connection.rollback rescue nil
677
+ else
678
+ connect
679
+ end
602
680
  end
603
681
 
604
682
  def configure_connection
605
- # FIXME: missing from adapter
606
- # @connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
683
+ if @config[:timeout] && @config[:retries]
684
+ raise ArgumentError, "Cannot specify both timeout and retries arguments"
685
+ elsif @config[:timeout]
686
+ # FIXME: missing from adapter
687
+ # @raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout]))
688
+ elsif @config[:retries]
689
+ retries = self.class.type_cast_config_to_integer(@config[:retries])
690
+ raw_connection.busy_handler do |count|
691
+ count <= retries
692
+ end
693
+ end
607
694
 
608
- execute("PRAGMA foreign_keys = ON", "SCHEMA")
695
+ # Enforce foreign key constraints
696
+ # https://www.sqlite.org/pragma.html#pragma_foreign_keys
697
+ # https://www.sqlite.org/foreignkeys.html
698
+ raw_execute("PRAGMA foreign_keys = ON", "SCHEMA")
699
+ unless @memory_database
700
+ # Journal mode WAL allows for greater concurrency (many readers + one writer)
701
+ # https://www.sqlite.org/pragma.html#pragma_journal_mode
702
+ raw_execute("PRAGMA journal_mode = WAL", "SCHEMA")
703
+ # Set more relaxed level of database durability
704
+ # 2 = "FULL" (sync on every write), 1 = "NORMAL" (sync every 1000 written pages) and 0 = "NONE"
705
+ # https://www.sqlite.org/pragma.html#pragma_synchronous
706
+ raw_execute("PRAGMA synchronous = NORMAL", "SCHEMA")
707
+ # Set the global memory map so all processes can share some data
708
+ # https://www.sqlite.org/pragma.html#pragma_mmap_size
709
+ # https://www.sqlite.org/mmap.html
710
+ raw_execute("PRAGMA mmap_size = #{128.megabytes}", "SCHEMA")
711
+ end
712
+ # Impose a limit on the WAL file to prevent unlimited growth
713
+ # https://www.sqlite.org/pragma.html#pragma_journal_size_limit
714
+ raw_execute("PRAGMA journal_size_limit = #{64.megabytes}", "SCHEMA")
715
+ # Set the local connection cache to 2000 pages
716
+ # https://www.sqlite.org/pragma.html#pragma_cache_size
717
+ raw_execute("PRAGMA cache_size = 2000", "SCHEMA")
609
718
  end
610
-
611
719
  end
612
720
  # DIFFERENCE: A registration here is moved down to concrete class so we are not registering part of an adapter.
613
721
  end
614
722
 
615
723
  module ActiveRecord::ConnectionAdapters
616
- class SQLite3Column < JdbcColumn
617
- def initialize(name, *args)
618
- if Hash === name
619
- super
620
- else
621
- super(nil, name, *args)
622
- end
623
- end
624
-
625
- def self.string_to_binary(value)
626
- value
627
- end
628
-
629
- def self.binary_to_string(value)
630
- if value.respond_to?(:encoding) && value.encoding != Encoding::ASCII_8BIT
631
- value = value.force_encoding(Encoding::ASCII_8BIT)
632
- end
633
- value
634
- end
635
-
636
- # @override {ActiveRecord::ConnectionAdapters::JdbcColumn#init_column}
637
- def init_column(name, default, *args)
638
- if default =~ /NULL/
639
- @default = nil
640
- else
641
- super
642
- end
643
- end
644
-
645
- # @override {ActiveRecord::ConnectionAdapters::JdbcColumn#default_value}
646
- def default_value(value)
647
- # JDBC returns column default strings with actual single quotes :
648
- return $1 if value =~ /^'(.*)'$/
649
-
650
- value
651
- end
652
-
653
- # @override {ActiveRecord::ConnectionAdapters::Column#type_cast}
654
- def type_cast(value)
655
- return nil if value.nil?
656
- case type
657
- when :string then value
658
- when :primary_key
659
- value.respond_to?(:to_i) ? value.to_i : ( value ? 1 : 0 )
660
- when :float then value.to_f
661
- when :decimal then self.class.value_to_decimal(value)
662
- when :boolean then self.class.value_to_boolean(value)
663
- else super
664
- end
665
- end
666
-
667
- private
668
-
669
- # @override {ActiveRecord::ConnectionAdapters::Column#extract_limit}
670
- def extract_limit(sql_type)
671
- return nil if sql_type =~ /^(real)\(\d+/i
672
- super
673
- end
674
-
675
- def extract_precision(sql_type)
676
- case sql_type
677
- when /^(real)\((\d+)(,\d+)?\)/i then $2.to_i
678
- else super
679
- end
680
- end
681
-
682
- def extract_scale(sql_type)
683
- case sql_type
684
- when /^(real)\((\d+)\)/i then 0
685
- when /^(real)\((\d+)(,(\d+))\)/i then $4.to_i
686
- else super
687
- end
688
- end
689
- end
690
724
 
691
725
  remove_const(:SQLite3Adapter) if const_defined?(:SQLite3Adapter)
692
726
 
@@ -702,6 +736,30 @@ module ActiveRecord::ConnectionAdapters
702
736
  include ArJdbc::Abstract::StatementCache
703
737
  include ArJdbc::Abstract::TransactionSupport
704
738
 
739
+ ##
740
+ # :singleton-method:
741
+ # Configure the SQLite3Adapter to be used in a strict strings mode.
742
+ # This will disable double-quoted string literals, because otherwise typos can silently go unnoticed.
743
+ # For example, it is possible to create an index for a non existing column.
744
+ # If you wish to enable this mode you can add the following line to your application.rb file:
745
+ #
746
+ # config.active_record.sqlite3_adapter_strict_strings_by_default = true
747
+ class_attribute :strict_strings_by_default, default: false # Does not actually do anything right now
748
+
749
+ def initialize(...)
750
+ super
751
+
752
+ conn_params = @config.compact
753
+
754
+ # NOTE: strict strings is not supported by the jdbc driver yet,
755
+ # hope it will supported soon, I open a issue in their repository.
756
+ # https://github.com/xerial/sqlite-jdbc/issues/1153
757
+ #
758
+ # @config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
759
+
760
+ @connection_parameters = conn_params
761
+ end
762
+
705
763
  def self.represent_boolean_as_integer=(value) # :nodoc:
706
764
  if value == false
707
765
  raise "`.represent_boolean_as_integer=` is now always true, so make sure your application can work with it and remove this settings."
@@ -736,23 +794,15 @@ module ActiveRecord::ConnectionAdapters
736
794
  # SQLite driver doesn't support all types of insert statements with executeUpdate so
737
795
  # make it act like a regular query and the ids will be returned from #last_inserted_id
738
796
  # example: INSERT INTO "aircraft" DEFAULT VALUES
739
- def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
740
- exec_query(sql, name, binds)
797
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil)
798
+ sql, binds = sql_for_insert(sql, pk, binds, returning)
799
+ internal_exec_query(sql, name, binds)
741
800
  end
742
801
 
743
802
  def jdbc_column_class
744
803
  ::ActiveRecord::ConnectionAdapters::SQLite3Column
745
804
  end
746
805
 
747
- def jdbc_connection_class(spec)
748
- self.class.jdbc_connection_class
749
- end
750
-
751
- # @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_connection_class
752
- def self.jdbc_connection_class
753
- ::ActiveRecord::ConnectionAdapters::SQLite3JdbcConnection
754
- end
755
-
756
806
  # Note: This is not an override of ours but a moved line from AR Sqlite3Adapter to register ours vs our copied module (which would be their class).
757
807
  # ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
758
808
 
@@ -770,6 +820,24 @@ module ActiveRecord::ConnectionAdapters
770
820
  ::ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3)
771
821
 
772
822
  class << self
823
+ def jdbc_connection_class
824
+ ::ActiveRecord::ConnectionAdapters::SQLite3JdbcConnection
825
+ end
826
+
827
+ def new_client(conn_params, adapter_instance)
828
+ jdbc_connection_class.new(conn_params, adapter_instance)
829
+ end
830
+
831
+ def dbconsole(config, options = {})
832
+ args = []
833
+
834
+ args << "-#{options[:mode]}" if options[:mode]
835
+ args << "-header" if options[:header]
836
+ args << File.expand_path(config.database, const_defined?(:Rails) && Rails.respond_to?(:root) ? Rails.root : nil)
837
+
838
+ find_cmd_and_exec("sqlite3", *args)
839
+ end
840
+
773
841
  private
774
842
  def initialize_type_map(m)
775
843
  super