activerecord-jdbc-adapter 70.2-java → 71.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -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