activerecord-jdbc-alt-adapter 70.2.0-java → 71.0.0.alpha1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) 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/arjdbc/abstract/connection_management.rb +23 -10
  12. data/lib/arjdbc/abstract/core.rb +5 -6
  13. data/lib/arjdbc/abstract/database_statements.rb +35 -25
  14. data/lib/arjdbc/abstract/statement_cache.rb +1 -6
  15. data/lib/arjdbc/abstract/transaction_support.rb +37 -9
  16. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  17. data/lib/arjdbc/jdbc/column.rb +0 -34
  18. data/lib/arjdbc/jdbc/connection_methods.rb +1 -1
  19. data/lib/arjdbc/mssql/adapter.rb +93 -80
  20. data/lib/arjdbc/mssql/column.rb +1 -0
  21. data/lib/arjdbc/mssql/connection_methods.rb +7 -55
  22. data/lib/arjdbc/mssql/database_statements.rb +182 -71
  23. data/lib/arjdbc/mssql/explain_support.rb +8 -5
  24. data/lib/arjdbc/mssql/schema_creation.rb +1 -1
  25. data/lib/arjdbc/mssql/schema_definitions.rb +10 -0
  26. data/lib/arjdbc/mssql/schema_statements.rb +19 -11
  27. data/lib/arjdbc/mssql/server_version.rb +56 -0
  28. data/lib/arjdbc/mssql/utils.rb +23 -9
  29. data/lib/arjdbc/mysql/adapter.rb +64 -22
  30. data/lib/arjdbc/sqlite3/adapter.rb +218 -135
  31. data/lib/arjdbc/sqlite3/column.rb +103 -0
  32. data/lib/arjdbc/sqlite3/connection_methods.rb +7 -2
  33. data/lib/arjdbc/tasks/mssql_database_tasks.rb +9 -5
  34. data/lib/arjdbc/version.rb +1 -1
  35. data/rakelib/02-test.rake +1 -1
  36. data/rakelib/rails.rake +2 -0
  37. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +4 -2
  38. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +2 -1
  39. metadata +9 -12
  40. data/lib/arel/visitors/sql_server/ng42.rb +0 -294
  41. data/lib/arel/visitors/sql_server.rb +0 -124
  42. data/lib/arjdbc/mssql/limit_helpers.rb +0 -231
  43. data/lib/arjdbc/mssql/lock_methods.rb +0 -77
  44. data/lib/arjdbc/mssql/old_adapter.rb +0 -804
  45. 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