activerecord-jdbc-alt-adapter 70.1.0-java → 71.0.0.alpha1-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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +135 -21
  3. data/.github/workflows/ruby.yml +10 -10
  4. data/.gitignore +1 -0
  5. data/.solargraph.yml +15 -0
  6. data/Gemfile +17 -4
  7. data/README.md +7 -3
  8. data/RUNNING_TESTS.md +36 -0
  9. data/activerecord-jdbc-adapter.gemspec +2 -2
  10. data/activerecord-jdbc-alt-adapter.gemspec +1 -1
  11. data/lib/arel/visitors/sqlserver.rb +10 -0
  12. data/lib/arjdbc/abstract/connection_management.rb +23 -10
  13. data/lib/arjdbc/abstract/core.rb +5 -6
  14. data/lib/arjdbc/abstract/database_statements.rb +35 -25
  15. data/lib/arjdbc/abstract/statement_cache.rb +1 -6
  16. data/lib/arjdbc/abstract/transaction_support.rb +37 -9
  17. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  18. data/lib/arjdbc/jdbc/column.rb +0 -34
  19. data/lib/arjdbc/jdbc/connection_methods.rb +1 -1
  20. data/lib/arjdbc/mssql/adapter.rb +93 -80
  21. data/lib/arjdbc/mssql/column.rb +1 -0
  22. data/lib/arjdbc/mssql/connection_methods.rb +7 -55
  23. data/lib/arjdbc/mssql/database_statements.rb +182 -71
  24. data/lib/arjdbc/mssql/explain_support.rb +8 -5
  25. data/lib/arjdbc/mssql/schema_creation.rb +1 -1
  26. data/lib/arjdbc/mssql/schema_definitions.rb +10 -0
  27. data/lib/arjdbc/mssql/schema_statements.rb +19 -11
  28. data/lib/arjdbc/mssql/server_version.rb +56 -0
  29. data/lib/arjdbc/mssql/utils.rb +23 -9
  30. data/lib/arjdbc/mysql/adapter.rb +64 -22
  31. data/lib/arjdbc/mysql/connection_methods.rb +43 -42
  32. data/lib/arjdbc/sqlite3/adapter.rb +218 -135
  33. data/lib/arjdbc/sqlite3/column.rb +103 -0
  34. data/lib/arjdbc/sqlite3/connection_methods.rb +7 -2
  35. data/lib/arjdbc/tasks/mssql_database_tasks.rb +9 -5
  36. data/lib/arjdbc/version.rb +1 -1
  37. data/rakelib/02-test.rake +1 -1
  38. data/rakelib/rails.rake +2 -0
  39. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +4 -2
  40. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +2 -1
  41. metadata +11 -14
  42. data/lib/arel/visitors/sql_server/ng42.rb +0 -294
  43. data/lib/arel/visitors/sql_server.rb +0 -124
  44. data/lib/arjdbc/mssql/limit_helpers.rb +0 -231
  45. data/lib/arjdbc/mssql/lock_methods.rb +0 -77
  46. data/lib/arjdbc/mssql/old_adapter.rb +0 -804
  47. data/lib/arjdbc/mssql/old_column.rb +0 -200
@@ -16,6 +16,7 @@ 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"
19
20
 
20
21
  module SQLite3
21
22
  module Constants
@@ -89,20 +90,8 @@ module ArJdbc
89
90
  end
90
91
  end
91
92
 
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
93
  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
94
+ @config[:database] == ":memory:" || File.exist?(@config[:database].to_s)
106
95
  end
107
96
 
108
97
  def supports_ddl_transactions?
@@ -153,6 +142,10 @@ module ArJdbc
153
142
  database_version >= "3.8.3"
154
143
  end
155
144
 
145
+ def supports_insert_returning?
146
+ database_version >= "3.35.0"
147
+ end
148
+
156
149
  def supports_insert_on_conflict?
157
150
  database_version >= "3.24.0"
158
151
  end
@@ -166,19 +159,22 @@ module ArJdbc
166
159
  end
167
160
 
168
161
  def active?
169
- !@raw_connection.closed?
162
+ @raw_connection && !@raw_connection.closed?
170
163
  end
171
164
 
172
- def reconnect!
173
- super
174
- connect if @connection.closed?
165
+ def return_value_after_insert?(column) # :nodoc:
166
+ column.auto_populated?
175
167
  end
176
168
 
169
+ # MISSING: alias :reset! :reconnect!
170
+
177
171
  # Disconnects from the database if already connected. Otherwise, this
178
172
  # method does nothing.
179
173
  def disconnect!
180
174
  super
181
- @connection.close rescue nil
175
+
176
+ @raw_connection&.close rescue nil
177
+ @raw_connection = nil
182
178
  end
183
179
 
184
180
  def supports_index_sort_order?
@@ -191,7 +187,7 @@ module ArJdbc
191
187
 
192
188
  # Returns the current database encoding format as a string, eg: 'UTF-8'
193
189
  def encoding
194
- @connection.encoding.to_s
190
+ any_raw_connection.encoding.to_s
195
191
  end
196
192
 
197
193
  def supports_explain?
@@ -218,8 +214,14 @@ module ArJdbc
218
214
  end
219
215
  end
220
216
 
221
- def all_foreign_keys_valid? # :nodoc:
222
- execute("PRAGMA foreign_key_check").blank?
217
+ def check_all_foreign_keys_valid! # :nodoc:
218
+ sql = "PRAGMA foreign_key_check"
219
+ result = execute(sql)
220
+
221
+ unless result.blank?
222
+ tables = result.map { |row| row["table"] }
223
+ raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql)
224
+ end
223
225
  end
224
226
 
225
227
  # SCHEMA STATEMENTS ========================================
@@ -234,7 +236,7 @@ module ArJdbc
234
236
 
235
237
  index_name = index_name_for_remove(table_name, column_name, options)
236
238
 
237
- exec_query "DROP INDEX #{quote_column_name(index_name)}"
239
+ internal_exec_query "DROP INDEX #{quote_column_name(index_name)}"
238
240
  end
239
241
 
240
242
 
@@ -242,10 +244,11 @@ module ArJdbc
242
244
  #
243
245
  # Example:
244
246
  # rename_table('octopuses', 'octopi')
245
- def rename_table(table_name, new_name)
247
+ def rename_table(table_name, new_name, **options)
248
+ validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
246
249
  schema_cache.clear_data_source_cache!(table_name.to_s)
247
250
  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)}"
251
+ internal_exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
249
252
  rename_table_indexes(table_name, new_name)
250
253
  end
251
254
 
@@ -285,8 +288,10 @@ module ArJdbc
285
288
  end
286
289
 
287
290
  def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
291
+ validate_change_column_null_argument!(null)
292
+
288
293
  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")
294
+ 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
295
  end
291
296
  alter_table(table_name) do |definition|
292
297
  definition[column_name].null = null
@@ -295,10 +300,7 @@ module ArJdbc
295
300
 
296
301
  def change_column(table_name, column_name, type, **options) #:nodoc:
297
302
  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
303
+ definition.change_column(column_name, type, **options)
302
304
  end
303
305
  end
304
306
 
@@ -308,20 +310,42 @@ module ArJdbc
308
310
  rename_column_indexes(table_name, column.name, new_column_name)
309
311
  end
310
312
 
313
+ def add_timestamps(table_name, **options)
314
+ options[:null] = false if options[:null].nil?
315
+
316
+ if !options.key?(:precision)
317
+ options[:precision] = 6
318
+ end
319
+
320
+ alter_table(table_name) do |definition|
321
+ definition.column :created_at, :datetime, **options
322
+ definition.column :updated_at, :datetime, **options
323
+ end
324
+ end
325
+
311
326
  def add_reference(table_name, ref_name, **options) # :nodoc:
312
327
  super(table_name, ref_name, type: :integer, **options)
313
328
  end
314
329
  alias :add_belongs_to :add_reference
315
330
 
316
331
  def foreign_keys(table_name)
317
- fk_info = exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
318
- fk_info.map do |row|
332
+ # SQLite returns 1 row for each column of composite foreign keys.
333
+ fk_info = internal_exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
334
+ grouped_fk = fk_info.group_by { |row| row["id"] }.values.each { |group| group.sort_by! { |row| row["seq"] } }
335
+ grouped_fk.map do |group|
336
+ row = group.first
319
337
  options = {
320
- column: row["from"],
321
- primary_key: row["to"],
322
338
  on_delete: extract_foreign_key_action(row["on_delete"]),
323
339
  on_update: extract_foreign_key_action(row["on_update"])
324
340
  }
341
+
342
+ if group.one?
343
+ options[:column] = row["from"]
344
+ options[:primary_key] = row["to"]
345
+ else
346
+ options[:column] = group.map { |row| row["from"] }
347
+ options[:primary_key] = group.map { |row| row["to"] }
348
+ end
325
349
  # DIFFERENCE: FQN
326
350
  ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(table_name, row["table"], options)
327
351
  end
@@ -342,6 +366,7 @@ module ArJdbc
342
366
  end
343
367
  end
344
368
 
369
+ sql << " RETURNING #{insert.returning}" if insert.returning
345
370
  sql
346
371
  end
347
372
 
@@ -349,6 +374,10 @@ module ArJdbc
349
374
  @config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
350
375
  end
351
376
 
377
+ def use_insert_returning?
378
+ @use_insert_returning
379
+ end
380
+
352
381
  def get_database_version # :nodoc:
353
382
  SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
354
383
  end
@@ -359,6 +388,27 @@ module ArJdbc
359
388
  end
360
389
  end
361
390
 
391
+ # DIFFERENCE: here to
392
+ def new_column_from_field(table_name, field, definitions)
393
+ default = field["dflt_value"]
394
+
395
+ type_metadata = fetch_type_metadata(field["type"])
396
+ default_value = extract_value_from_default(default)
397
+ default_function = extract_default_function(default_value, default)
398
+ rowid = is_column_the_rowid?(field, definitions)
399
+
400
+ ActiveRecord::ConnectionAdapters::SQLite3Column.new(
401
+ field["name"],
402
+ default_value,
403
+ type_metadata,
404
+ field["notnull"].to_i == 0,
405
+ default_function,
406
+ collation: field["collation"],
407
+ auto_increment: field["auto_increment"],
408
+ rowid: rowid
409
+ )
410
+ end
411
+
362
412
  private
363
413
  # See https://www.sqlite.org/limits.html,
364
414
  # the default value is 999 when not configured.
@@ -367,7 +417,7 @@ module ArJdbc
367
417
  end
368
418
 
369
419
  def table_structure(table_name)
370
- structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
420
+ structure = internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
371
421
  raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
372
422
  table_structure_with_collation(table_name, structure)
373
423
  end
@@ -377,15 +427,18 @@ module ArJdbc
377
427
  case default
378
428
  when /^null$/i
379
429
  nil
380
- # Quoted types
381
- when /^'(.*)'$/m
430
+ # Quoted types
431
+ when /^'([^|]*)'$/m
382
432
  $1.gsub("''", "'")
383
- # Quoted types
384
- when /^"(.*)"$/m
433
+ # Quoted types
434
+ when /^"([^|]*)"$/m
385
435
  $1.gsub('""', '"')
386
- # Numeric types
436
+ # Numeric types
387
437
  when /\A-?\d+(\.\d*)?\z/
388
438
  $&
439
+ # Binary columns
440
+ when /x'(.*)'/
441
+ [ $1 ].pack("H*")
389
442
  else
390
443
  # Anything else is blank or some function
391
444
  # and we can't know the value of that, so return nil.
@@ -398,7 +451,7 @@ module ArJdbc
398
451
  end
399
452
 
400
453
  def has_default_function?(default_value, default)
401
- !default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
454
+ !default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP|\|\|}.match?(default)
402
455
  end
403
456
 
404
457
  # See: https://www.sqlite.org/lang_altertable.html
@@ -464,14 +517,24 @@ module ArJdbc
464
517
  if column.has_default?
465
518
  type = lookup_cast_type_from_column(column)
466
519
  default = type.deserialize(column.default)
520
+ default = -> { column.default_function } if default.nil?
467
521
  end
468
522
 
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,
523
+ column_options = {
524
+ limit: column.limit,
525
+ precision: column.precision,
526
+ scale: column.scale,
527
+ null: column.null,
528
+ collation: column.collation,
473
529
  primary_key: column_name == from_primary_key
474
- )
530
+ }
531
+
532
+ unless column.auto_increment?
533
+ column_options[:default] = default
534
+ end
535
+
536
+ column_type = column.bigint? ? :bigint : column.type
537
+ @definition.column(column_name, column_type, **column_options)
475
538
  end
476
539
 
477
540
  yield @definition if block_given?
@@ -519,8 +582,8 @@ module ArJdbc
519
582
  quoted_columns = columns.map { |col| quote_column_name(col) } * ","
520
583
  quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
521
584
 
522
- exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
523
- SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
585
+ internal_exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
586
+ SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
524
587
  end
525
588
 
526
589
  def translate_exception(exception, message:, sql:, binds:)
@@ -530,25 +593,32 @@ module ArJdbc
530
593
  # column *column_name* is not unique
531
594
  if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i)
532
595
  # DIFFERENCE: FQN
533
- ::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds)
596
+ ::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
534
597
  elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i)
535
598
  # DIFFERENCE: FQN
536
- ::ActiveRecord::NotNullViolation.new(message, sql: sql, binds: binds)
599
+ ::ActiveRecord::NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
537
600
  elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
538
601
  # DIFFERENCE: FQN
539
- ::ActiveRecord::InvalidForeignKey.new(message, sql: sql, binds: binds)
602
+ ::ActiveRecord::InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
540
603
  elsif exception.message.match?(/called on a closed database/i)
541
604
  # DIFFERENCE: FQN
542
- ::ActiveRecord::ConnectionNotEstablished.new(exception)
605
+ ::ActiveRecord::ConnectionNotEstablished.new(exception, connection_pool: @pool)
606
+ elsif exception.message.match?(/sql error/i)
607
+ ::ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
608
+ elsif exception.message.match?(/write a readonly database/i)
609
+ message = message.sub('org.sqlite.SQLiteException', 'SQLite3::ReadOnlyException')
610
+ ::ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
543
611
  else
544
612
  super
545
613
  end
546
614
  end
547
615
 
548
616
  COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze
617
+ PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*\"(\w+)\".+PRIMARY KEY AUTOINCREMENT/i
549
618
 
550
619
  def table_structure_with_collation(table_name, basic_structure)
551
620
  collation_hash = {}
621
+ auto_increments = {}
552
622
  sql = <<~SQL
553
623
  SELECT sql FROM
554
624
  (SELECT * FROM sqlite_master UNION ALL
@@ -570,6 +640,7 @@ module ArJdbc
570
640
  # This regex will match the column name and collation type and will save
571
641
  # the value in $1 and $2 respectively.
572
642
  collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
643
+ auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
573
644
  end
574
645
 
575
646
  basic_structure.map do |column|
@@ -579,6 +650,10 @@ module ArJdbc
579
650
  column["collation"] = collation_hash[column_name]
580
651
  end
581
652
 
653
+ if auto_increments.has_key?(column_name)
654
+ column["auto_increment"] = true
655
+ end
656
+
582
657
  column
583
658
  end
584
659
  else
@@ -594,99 +669,86 @@ module ArJdbc
594
669
  StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
595
670
  end
596
671
 
597
- def connect
598
- @connection = ::SQLite3::Database.new(
599
- @config[:database].to_s,
600
- @config.merge(results_as_hash: true)
601
- )
602
- end
603
-
604
672
  def configure_connection
605
- # FIXME: missing from adapter
606
- # @connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
607
-
608
- execute("PRAGMA foreign_keys = ON", "SCHEMA")
609
- end
610
-
611
- end
612
- # DIFFERENCE: A registration here is moved down to concrete class so we are not registering part of an adapter.
613
- end
614
-
615
- 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)
673
+ if @config[:timeout] && @config[:retries]
674
+ raise ArgumentError, "Cannot specify both timeout and retries arguments"
675
+ elsif @config[:timeout]
676
+ # FIXME: missing from adapter
677
+ # @raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout]))
678
+ elsif @config[:retries]
679
+ retries = self.class.type_cast_config_to_integer(@config[:retries])
680
+ raw_connection.busy_handler do |count|
681
+ count <= retries
682
+ end
632
683
  end
633
- value
634
- end
635
684
 
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
685
+ # Enforce foreign key constraints
686
+ # https://www.sqlite.org/pragma.html#pragma_foreign_keys
687
+ # https://www.sqlite.org/foreignkeys.html
688
+ raw_execute("PRAGMA foreign_keys = ON", "SCHEMA")
689
+ unless @memory_database
690
+ # Journal mode WAL allows for greater concurrency (many readers + one writer)
691
+ # https://www.sqlite.org/pragma.html#pragma_journal_mode
692
+ raw_execute("PRAGMA journal_mode = WAL", "SCHEMA")
693
+ # Set more relaxed level of database durability
694
+ # 2 = "FULL" (sync on every write), 1 = "NORMAL" (sync every 1000 written pages) and 0 = "NONE"
695
+ # https://www.sqlite.org/pragma.html#pragma_synchronous
696
+ raw_execute("PRAGMA synchronous = NORMAL", "SCHEMA")
697
+ # Set the global memory map so all processes can share some data
698
+ # https://www.sqlite.org/pragma.html#pragma_mmap_size
699
+ # https://www.sqlite.org/mmap.html
700
+ raw_execute("PRAGMA mmap_size = #{128.megabytes}", "SCHEMA")
642
701
  end
702
+ # Impose a limit on the WAL file to prevent unlimited growth
703
+ # https://www.sqlite.org/pragma.html#pragma_journal_size_limit
704
+ raw_execute("PRAGMA journal_size_limit = #{64.megabytes}", "SCHEMA")
705
+ # Set the local connection cache to 2000 pages
706
+ # https://www.sqlite.org/pragma.html#pragma_cache_size
707
+ raw_execute("PRAGMA cache_size = 2000", "SCHEMA")
643
708
  end
644
709
 
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
710
+ def configure_connection
711
+ if @config[:timeout] && @config[:retries]
712
+ raise ArgumentError, "Cannot specify both timeout and retries arguments"
713
+ elsif @config[:timeout]
714
+ # FIXME:
715
+ # @raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout]))
716
+ elsif @config[:retries]
717
+ retries = self.class.type_cast_config_to_integer(@config[:retries])
718
+ raw_connection.busy_handler do |count|
719
+ count <= retries
720
+ end
679
721
  end
680
- end
681
722
 
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
723
+ # Enforce foreign key constraints
724
+ # https://www.sqlite.org/pragma.html#pragma_foreign_keys
725
+ # https://www.sqlite.org/foreignkeys.html
726
+ raw_execute("PRAGMA foreign_keys = ON", "SCHEMA")
727
+ unless @memory_database
728
+ # Journal mode WAL allows for greater concurrency (many readers + one writer)
729
+ # https://www.sqlite.org/pragma.html#pragma_journal_mode
730
+ raw_execute("PRAGMA journal_mode = WAL", "SCHEMA")
731
+ # Set more relaxed level of database durability
732
+ # 2 = "FULL" (sync on every write), 1 = "NORMAL" (sync every 1000 written pages) and 0 = "NONE"
733
+ # https://www.sqlite.org/pragma.html#pragma_synchronous
734
+ raw_execute("PRAGMA synchronous = NORMAL", "SCHEMA")
735
+ # Set the global memory map so all processes can share some data
736
+ # https://www.sqlite.org/pragma.html#pragma_mmap_size
737
+ # https://www.sqlite.org/mmap.html
738
+ raw_execute("PRAGMA mmap_size = #{128.megabytes}", "SCHEMA")
687
739
  end
740
+ # Impose a limit on the WAL file to prevent unlimited growth
741
+ # https://www.sqlite.org/pragma.html#pragma_journal_size_limit
742
+ raw_execute("PRAGMA journal_size_limit = #{64.megabytes}", "SCHEMA")
743
+ # Set the local connection cache to 2000 pages
744
+ # https://www.sqlite.org/pragma.html#pragma_cache_size
745
+ raw_execute("PRAGMA cache_size = 2000", "SCHEMA")
688
746
  end
689
747
  end
748
+ # DIFFERENCE: A registration here is moved down to concrete class so we are not registering part of an adapter.
749
+ end
750
+
751
+ module ActiveRecord::ConnectionAdapters
690
752
 
691
753
  remove_const(:SQLite3Adapter) if const_defined?(:SQLite3Adapter)
692
754
 
@@ -702,6 +764,16 @@ module ActiveRecord::ConnectionAdapters
702
764
  include ArJdbc::Abstract::StatementCache
703
765
  include ArJdbc::Abstract::TransactionSupport
704
766
 
767
+ ##
768
+ # :singleton-method:
769
+ # Configure the SQLite3Adapter to be used in a strict strings mode.
770
+ # This will disable double-quoted string literals, because otherwise typos can silently go unnoticed.
771
+ # For example, it is possible to create an index for a non existing column.
772
+ # If you wish to enable this mode you can add the following line to your application.rb file:
773
+ #
774
+ # config.active_record.sqlite3_adapter_strict_strings_by_default = true
775
+ class_attribute :strict_strings_by_default, default: false # Does not actually do anything right now
776
+
705
777
  def self.represent_boolean_as_integer=(value) # :nodoc:
706
778
  if value == false
707
779
  raise "`.represent_boolean_as_integer=` is now always true, so make sure your application can work with it and remove this settings."
@@ -736,8 +808,9 @@ module ActiveRecord::ConnectionAdapters
736
808
  # SQLite driver doesn't support all types of insert statements with executeUpdate so
737
809
  # make it act like a regular query and the ids will be returned from #last_inserted_id
738
810
  # 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)
811
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil)
812
+ sql, binds = sql_for_insert(sql, pk, binds, returning)
813
+ internal_exec_query(sql, name, binds)
741
814
  end
742
815
 
743
816
  def jdbc_column_class
@@ -770,6 +843,16 @@ module ActiveRecord::ConnectionAdapters
770
843
  ::ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3)
771
844
 
772
845
  class << self
846
+ def dbconsole(config, options = {})
847
+ args = []
848
+
849
+ args << "-#{options[:mode]}" if options[:mode]
850
+ args << "-header" if options[:header]
851
+ args << File.expand_path(config.database, const_defined?(:Rails) && Rails.respond_to?(:root) ? Rails.root : nil)
852
+
853
+ find_cmd_and_exec("sqlite3", *args)
854
+ end
855
+
773
856
  private
774
857
  def initialize_type_map(m)
775
858
  super
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord::ConnectionAdapters
4
+ class SQLite3Column < JdbcColumn
5
+
6
+ attr_reader :rowid
7
+
8
+ def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, auto_increment: nil, rowid: false, **)
9
+ super
10
+ @auto_increment = auto_increment
11
+ @default = nil if default =~ /NULL/
12
+ @rowid = rowid
13
+ end
14
+
15
+ def self.string_to_binary(value)
16
+ value
17
+ end
18
+
19
+ def self.binary_to_string(value)
20
+ if value.respond_to?(:encoding) && value.encoding != Encoding::ASCII_8BIT
21
+ value = value.force_encoding(Encoding::ASCII_8BIT)
22
+ end
23
+ value
24
+ end
25
+
26
+ # @override {ActiveRecord::ConnectionAdapters::JdbcColumn#default_value}
27
+ def default_value(value)
28
+ # JDBC returns column default strings with actual single quotes :
29
+ return $1 if value =~ /^'(.*)'$/
30
+
31
+ value
32
+ end
33
+
34
+ def auto_increment?
35
+ @auto_increment
36
+ end
37
+
38
+ def auto_incremented_by_db?
39
+ auto_increment? || rowid
40
+ end
41
+
42
+ def init_with(coder)
43
+ @auto_increment = coder["auto_increment"]
44
+ super
45
+ end
46
+
47
+ def encode_with(coder)
48
+ coder["auto_increment"] = @auto_increment
49
+ super
50
+ end
51
+
52
+ def ==(other)
53
+ other.is_a?(Column) &&
54
+ super &&
55
+ auto_increment? == other.auto_increment?
56
+ end
57
+ alias :eql? :==
58
+
59
+ def hash
60
+ Column.hash ^
61
+ super.hash ^
62
+ auto_increment?.hash ^
63
+ rowid.hash
64
+ end
65
+
66
+ # @override {ActiveRecord::ConnectionAdapters::Column#type_cast}
67
+ def type_cast(value)
68
+ return nil if value.nil?
69
+ case type
70
+ when :string then value
71
+ when :primary_key
72
+ value.respond_to?(:to_i) ? value.to_i : ( value ? 1 : 0 )
73
+ when :float then value.to_f
74
+ when :decimal then self.class.value_to_decimal(value)
75
+ when :boolean then self.class.value_to_boolean(value)
76
+ else super
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ # @override {ActiveRecord::ConnectionAdapters::Column#extract_limit}
83
+ def extract_limit(sql_type)
84
+ return nil if sql_type =~ /^(real)\(\d+/i
85
+ super
86
+ end
87
+
88
+ def extract_precision(sql_type)
89
+ case sql_type
90
+ when /^(real)\((\d+)(,\d+)?\)/i then $2.to_i
91
+ else super
92
+ end
93
+ end
94
+
95
+ def extract_scale(sql_type)
96
+ case sql_type
97
+ when /^(real)\((\d+)\)/i then 0
98
+ when /^(real)\((\d+)(,(\d+))\)/i then $4.to_i
99
+ else super
100
+ end
101
+ end
102
+ end
103
+ end