activerecord 7.1.0.beta1 → 7.1.0.rc1

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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +70 -4
  3. data/lib/active_record/associations/collection_association.rb +1 -3
  4. data/lib/active_record/associations/collection_proxy.rb +1 -1
  5. data/lib/active_record/associations.rb +107 -107
  6. data/lib/active_record/connection_adapters/abstract/database_statements.rb +20 -1
  7. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +3 -3
  8. data/lib/active_record/connection_adapters/abstract/transaction.rb +12 -9
  9. data/lib/active_record/connection_adapters/abstract_adapter.rb +1 -1
  10. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +7 -0
  11. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +1 -1
  12. data/lib/active_record/connection_adapters/pool_manager.rb +1 -1
  13. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +0 -16
  14. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -5
  15. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +24 -24
  16. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +19 -8
  17. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +25 -29
  18. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1 -6
  19. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +4 -4
  20. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +49 -4
  21. data/lib/active_record/core.rb +7 -9
  22. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +1 -1
  23. data/lib/active_record/errors.rb +19 -0
  24. data/lib/active_record/gem_version.rb +1 -1
  25. data/lib/active_record/migration/command_recorder.rb +8 -8
  26. data/lib/active_record/normalization.rb +2 -1
  27. data/lib/active_record/railtie.rb +1 -1
  28. data/lib/active_record/reflection.rb +10 -16
  29. data/lib/active_record/relation/finder_methods.rb +3 -12
  30. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  31. data/lib/active_record/schema_dumper.rb +9 -4
  32. metadata +9 -9
@@ -78,21 +78,24 @@ module ActiveRecord
78
78
  def initialize(payload = {})
79
79
  @handle = nil
80
80
  @started = false
81
- @payload = payload
81
+ @payload = nil
82
+ @base_payload = payload
82
83
  end
83
84
 
84
85
  def start
85
86
  return if @started
86
87
  @started = true
87
88
 
89
+ @payload = @base_payload.dup
88
90
  @handle = ActiveSupport::Notifications.instrumenter.build_handle("transaction.active_record", @payload)
89
91
  @handle.start
90
92
  end
91
93
 
92
- def finish
94
+ def finish(outcome)
93
95
  return unless @started
94
96
  @started = false
95
97
 
98
+ @payload[:outcome] = outcome
96
99
  @handle.finish
97
100
  end
98
101
  end
@@ -163,7 +166,7 @@ module ActiveRecord
163
166
  end
164
167
 
165
168
  def incomplete!
166
- @instrumenter.finish
169
+ @instrumenter.finish(:incomplete)
167
170
  end
168
171
 
169
172
  def materialize!
@@ -334,7 +337,7 @@ module ActiveRecord
334
337
  def restart
335
338
  return unless materialized?
336
339
 
337
- @instrumenter.finish
340
+ @instrumenter.finish(:restart)
338
341
  @instrumenter.start
339
342
 
340
343
  connection.rollback_to_savepoint(savepoint_name)
@@ -345,13 +348,13 @@ module ActiveRecord
345
348
  connection.rollback_to_savepoint(savepoint_name) if materialized?
346
349
  end
347
350
  @state.rollback!
348
- @instrumenter.finish
351
+ @instrumenter.finish(:rollback)
349
352
  end
350
353
 
351
354
  def commit
352
355
  connection.release_savepoint(savepoint_name) if materialized?
353
356
  @state.commit!
354
- @instrumenter.finish
357
+ @instrumenter.finish(:commit)
355
358
  end
356
359
 
357
360
  def full_rollback?; false; end
@@ -372,7 +375,7 @@ module ActiveRecord
372
375
  def restart
373
376
  return unless materialized?
374
377
 
375
- @instrumenter.finish
378
+ @instrumenter.finish(:restart)
376
379
 
377
380
  if connection.supports_restart_db_transaction?
378
381
  @instrumenter.start
@@ -386,13 +389,13 @@ module ActiveRecord
386
389
  def rollback
387
390
  connection.rollback_db_transaction if materialized?
388
391
  @state.full_rollback!
389
- @instrumenter.finish
392
+ @instrumenter.finish(:rollback)
390
393
  end
391
394
 
392
395
  def commit
393
396
  connection.commit_db_transaction if materialized?
394
397
  @state.full_commit!
395
- @instrumenter.finish
398
+ @instrumenter.finish(:commit)
396
399
  end
397
400
  end
398
401
 
@@ -498,7 +498,7 @@ module ActiveRecord
498
498
  end
499
499
 
500
500
  # Does this adapter support creating unique constraints?
501
- def supports_unique_keys?
501
+ def supports_unique_constraints?
502
502
  false
503
503
  end
504
504
 
@@ -531,6 +531,13 @@ module ActiveRecord
531
531
  expression = row["expression"]
532
532
  expression = expression[1..-2] if expression.start_with?("(") && expression.end_with?(")")
533
533
  expression = strip_whitespace_characters(expression)
534
+
535
+ unless mariadb?
536
+ # MySQL returns check constraints expression in an already escaped form.
537
+ # This leads to duplicate escaping later (e.g. when the expression is used in the SchemaDumper).
538
+ expression = expression.gsub("\\'", "'")
539
+ end
540
+
534
541
  CheckConstraintDefinition.new(table_name, expression, options)
535
542
  end
536
543
  else
@@ -36,7 +36,7 @@ module ActiveRecord
36
36
  end
37
37
 
38
38
  if row[:Expression]
39
- expression = row[:Expression]
39
+ expression = row[:Expression].gsub("\\'", "'")
40
40
  expression = +"(#{expression})" unless expression.start_with?("(")
41
41
  indexes.last[-2] << expression
42
42
  indexes.last[-1][:expressions] ||= {}
@@ -8,7 +8,7 @@ module ActiveRecord
8
8
  end
9
9
 
10
10
  def shard_names
11
- @role_to_shard_mapping.values.flat_map { |shard_map| shard_map.keys }
11
+ @role_to_shard_mapping.values.flat_map { |shard_map| shard_map.keys }.uniq
12
12
  end
13
13
 
14
14
  def role_names
@@ -75,22 +75,6 @@ module ActiveRecord
75
75
  end
76
76
  alias :exec_update :exec_delete
77
77
 
78
- def sql_for_insert(sql, pk, binds, returning) # :nodoc:
79
- if pk.nil?
80
- # Extract the table from the insert sql. Yuck.
81
- table_ref = extract_table_ref_from_insert_sql(sql)
82
- pk = primary_key(table_ref) if table_ref
83
- end
84
-
85
- returning_columns = returning || Array(pk)
86
-
87
- returning_columns_statement = returning_columns.map { |c| quote_column_name(c) }.join(", ")
88
- sql = "#{sql} RETURNING #{returning_columns_statement}" if returning_columns.any?
89
-
90
- super
91
- end
92
- private :sql_for_insert
93
-
94
78
  def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc:
95
79
  if use_insert_returning? || pk == false
96
80
  super
@@ -12,8 +12,8 @@ module ActiveRecord
12
12
  sql << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
13
13
  sql << o.exclusion_constraint_adds.map { |con| visit_AddExclusionConstraint con }.join(" ")
14
14
  sql << o.exclusion_constraint_drops.map { |con| visit_DropExclusionConstraint con }.join(" ")
15
- sql << o.unique_key_adds.map { |con| visit_AddUniqueKey con }.join(" ")
16
- sql << o.unique_key_drops.map { |con| visit_DropUniqueKey con }.join(" ")
15
+ sql << o.unique_constraint_adds.map { |con| visit_AddUniqueConstraint con }.join(" ")
16
+ sql << o.unique_constraint_drops.map { |con| visit_DropUniqueConstraint con }.join(" ")
17
17
  end
18
18
 
19
19
  def visit_AddForeignKey(o)
@@ -49,7 +49,7 @@ module ActiveRecord
49
49
  sql.join(" ")
50
50
  end
51
51
 
52
- def visit_UniqueKeyDefinition(o)
52
+ def visit_UniqueConstraintDefinition(o)
53
53
  column_name = Array(o.column).map { |column| quote_column_name(column) }.join(", ")
54
54
 
55
55
  sql = ["CONSTRAINT"]
@@ -77,11 +77,11 @@ module ActiveRecord
77
77
  "DROP CONSTRAINT #{quote_column_name(name)}"
78
78
  end
79
79
 
80
- def visit_AddUniqueKey(o)
80
+ def visit_AddUniqueConstraint(o)
81
81
  "ADD #{accept(o)}"
82
82
  end
83
83
 
84
- def visit_DropUniqueKey(name)
84
+ def visit_DropUniqueConstraint(name)
85
85
  "DROP CONSTRAINT #{quote_column_name(name)}"
86
86
  end
87
87
 
@@ -211,7 +211,7 @@ module ActiveRecord
211
211
  end
212
212
  end
213
213
 
214
- UniqueKeyDefinition = Struct.new(:table_name, :column, :options) do
214
+ UniqueConstraintDefinition = Struct.new(:table_name, :column, :options) do
215
215
  def name
216
216
  options[:name]
217
217
  end
@@ -239,12 +239,12 @@ module ActiveRecord
239
239
  class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
240
240
  include ColumnMethods
241
241
 
242
- attr_reader :exclusion_constraints, :unique_keys, :unlogged
242
+ attr_reader :exclusion_constraints, :unique_constraints, :unlogged
243
243
 
244
244
  def initialize(*, **)
245
245
  super
246
246
  @exclusion_constraints = []
247
- @unique_keys = []
247
+ @unique_constraints = []
248
248
  @unlogged = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables
249
249
  end
250
250
 
@@ -252,8 +252,8 @@ module ActiveRecord
252
252
  exclusion_constraints << new_exclusion_constraint_definition(expression, options)
253
253
  end
254
254
 
255
- def unique_key(column_name, **options)
256
- unique_keys << new_unique_key_definition(column_name, options)
255
+ def unique_constraint(column_name, **options)
256
+ unique_constraints << new_unique_constraint_definition(column_name, options)
257
257
  end
258
258
 
259
259
  def new_exclusion_constraint_definition(expression, options) # :nodoc:
@@ -261,9 +261,9 @@ module ActiveRecord
261
261
  ExclusionConstraintDefinition.new(name, expression, options)
262
262
  end
263
263
 
264
- def new_unique_key_definition(column_name, options) # :nodoc:
265
- options = @conn.unique_key_options(name, column_name, options)
266
- UniqueKeyDefinition.new(name, column_name, options)
264
+ def new_unique_constraint_definition(column_name, options) # :nodoc:
265
+ options = @conn.unique_constraint_options(name, column_name, options)
266
+ UniqueConstraintDefinition.new(name, column_name, options)
267
267
  end
268
268
 
269
269
  def new_column_definition(name, type, **options) # :nodoc:
@@ -315,36 +315,36 @@ module ActiveRecord
315
315
  @base.remove_exclusion_constraint(name, *args)
316
316
  end
317
317
 
318
- # Adds an unique constraint.
318
+ # Adds a unique constraint.
319
319
  #
320
- # t.unique_key(:position, name: 'unique_position', deferrable: :deferred)
320
+ # t.unique_constraint(:position, name: 'unique_position', deferrable: :deferred)
321
321
  #
322
- # See {connection.add_unique_key}[rdoc-ref:SchemaStatements#add_unique_key]
323
- def unique_key(*args)
324
- @base.add_unique_key(name, *args)
322
+ # See {connection.add_unique_constraint}[rdoc-ref:SchemaStatements#add_unique_constraint]
323
+ def unique_constraint(*args)
324
+ @base.add_unique_constraint(name, *args)
325
325
  end
326
326
 
327
327
  # Removes the given unique constraint from the table.
328
328
  #
329
- # t.remove_unique_key(name: "unique_position")
329
+ # t.remove_unique_constraint(name: "unique_position")
330
330
  #
331
- # See {connection.remove_unique_key}[rdoc-ref:SchemaStatements#remove_unique_key]
332
- def remove_unique_key(*args)
333
- @base.remove_unique_key(name, *args)
331
+ # See {connection.remove_unique_constraint}[rdoc-ref:SchemaStatements#remove_unique_constraint]
332
+ def remove_unique_constraint(*args)
333
+ @base.remove_unique_constraint(name, *args)
334
334
  end
335
335
  end
336
336
 
337
337
  # = Active Record PostgreSQL Adapter Alter \Table
338
338
  class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
339
- attr_reader :constraint_validations, :exclusion_constraint_adds, :exclusion_constraint_drops, :unique_key_adds, :unique_key_drops
339
+ attr_reader :constraint_validations, :exclusion_constraint_adds, :exclusion_constraint_drops, :unique_constraint_adds, :unique_constraint_drops
340
340
 
341
341
  def initialize(td)
342
342
  super
343
343
  @constraint_validations = []
344
344
  @exclusion_constraint_adds = []
345
345
  @exclusion_constraint_drops = []
346
- @unique_key_adds = []
347
- @unique_key_drops = []
346
+ @unique_constraint_adds = []
347
+ @unique_constraint_drops = []
348
348
  end
349
349
 
350
350
  def validate_constraint(name)
@@ -359,12 +359,12 @@ module ActiveRecord
359
359
  @exclusion_constraint_drops << constraint_name
360
360
  end
361
361
 
362
- def add_unique_key(column_name, options)
363
- @unique_key_adds << @td.new_unique_key_definition(column_name, options)
362
+ def add_unique_constraint(column_name, options)
363
+ @unique_constraint_adds << @td.new_unique_constraint_definition(column_name, options)
364
364
  end
365
365
 
366
- def drop_unique_key(unique_key_name)
367
- @unique_key_drops << unique_key_name
366
+ def drop_unique_constraint(unique_constraint_name)
367
+ @unique_constraint_drops << unique_constraint_name
368
368
  end
369
369
  end
370
370
  end
@@ -28,6 +28,17 @@ module ActiveRecord
28
28
  end
29
29
  end
30
30
 
31
+ def schemas(stream)
32
+ schema_names = @connection.schema_names - ["public"]
33
+
34
+ if schema_names.any?
35
+ schema_names.sort.each do |name|
36
+ stream.puts " create_schema #{name.inspect}"
37
+ end
38
+ stream.puts
39
+ end
40
+ end
41
+
31
42
  def exclusion_constraints_in_create(table, stream)
32
43
  if (exclusion_constraints = @connection.exclusion_constraints(table)).any?
33
44
  add_exclusion_constraint_statements = exclusion_constraints.map do |exclusion_constraint|
@@ -50,23 +61,23 @@ module ActiveRecord
50
61
  end
51
62
  end
52
63
 
53
- def unique_keys_in_create(table, stream)
54
- if (unique_keys = @connection.unique_keys(table)).any?
55
- add_unique_key_statements = unique_keys.map do |unique_key|
64
+ def unique_constraints_in_create(table, stream)
65
+ if (unique_constraints = @connection.unique_constraints(table)).any?
66
+ add_unique_constraint_statements = unique_constraints.map do |unique_constraint|
56
67
  parts = [
57
- "t.unique_key #{unique_key.column.inspect}"
68
+ "t.unique_constraint #{unique_constraint.column.inspect}"
58
69
  ]
59
70
 
60
- parts << "deferrable: #{unique_key.deferrable.inspect}" if unique_key.deferrable
71
+ parts << "deferrable: #{unique_constraint.deferrable.inspect}" if unique_constraint.deferrable
61
72
 
62
- if unique_key.export_name_on_schema_dump?
63
- parts << "name: #{unique_key.name.inspect}"
73
+ if unique_constraint.export_name_on_schema_dump?
74
+ parts << "name: #{unique_constraint.name.inspect}"
64
75
  end
65
76
 
66
77
  " #{parts.join(', ')}"
67
78
  end
68
79
 
69
- stream.puts add_unique_key_statements.sort.join("\n")
80
+ stream.puts add_unique_constraint_statements.sort.join("\n")
70
81
  end
71
82
  end
72
83
 
@@ -640,12 +640,12 @@ module ActiveRecord
640
640
  end
641
641
 
642
642
  # Returns an array of unique constraints for the given table.
643
- # The unique constraints are represented as UniqueKeyDefinition objects.
644
- def unique_keys(table_name)
643
+ # The unique constraints are represented as UniqueConstraintDefinition objects.
644
+ def unique_constraints(table_name)
645
645
  scope = quoted_scope(table_name)
646
646
 
647
647
  unique_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
648
- SELECT c.conname, c.conindid, c.condeferrable, c.condeferred
648
+ SELECT c.conname, c.conrelid, c.conkey, c.condeferrable, c.condeferred
649
649
  FROM pg_constraint c
650
650
  JOIN pg_class t ON c.conrelid = t.oid
651
651
  JOIN pg_namespace n ON n.oid = c.connamespace
@@ -655,21 +655,17 @@ module ActiveRecord
655
655
  SQL
656
656
 
657
657
  unique_info.map do |row|
658
- deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
658
+ conkey = row["conkey"].delete("{}").split(",").map(&:to_i)
659
+ columns = column_names_from_column_numbers(row["conrelid"], conkey)
659
660
 
660
- columns = query_values(<<~SQL, "SCHEMA")
661
- SELECT a.attname
662
- FROM pg_attribute a
663
- WHERE a.attrelid = #{row['conindid']}
664
- ORDER BY a.attnum
665
- SQL
661
+ deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
666
662
 
667
663
  options = {
668
664
  name: row["conname"],
669
665
  deferrable: deferrable
670
666
  }
671
667
 
672
- UniqueKeyDefinition.new(table_name, columns, options)
668
+ UniqueConstraintDefinition.new(table_name, columns, options)
673
669
  end
674
670
  end
675
671
 
@@ -721,7 +717,7 @@ module ActiveRecord
721
717
 
722
718
  # Adds a new unique constraint to the table.
723
719
  #
724
- # add_unique_key :sections, [:position], deferrable: :deferred, name: "unique_position"
720
+ # add_unique_constraint :sections, [:position], deferrable: :deferred, name: "unique_position"
725
721
  #
726
722
  # generates:
727
723
  #
@@ -729,7 +725,7 @@ module ActiveRecord
729
725
  #
730
726
  # If you want to change an existing unique index to deferrable, you can use :using_index to create deferrable unique constraints.
731
727
  #
732
- # add_unique_key :sections, deferrable: :deferred, name: "unique_position", using_index: "index_sections_on_position"
728
+ # add_unique_constraint :sections, deferrable: :deferred, name: "unique_position", using_index: "index_sections_on_position"
733
729
  #
734
730
  # The +options+ hash can include the following keys:
735
731
  # [<tt>:name</tt>]
@@ -738,15 +734,15 @@ module ActiveRecord
738
734
  # Specify whether or not the unique constraint should be deferrable. Valid values are +false+ or +:immediate+ or +:deferred+ to specify the default behavior. Defaults to +false+.
739
735
  # [<tt>:using_index</tt>]
740
736
  # To specify an existing unique index name. Defaults to +nil+.
741
- def add_unique_key(table_name, column_name = nil, **options)
742
- options = unique_key_options(table_name, column_name, options)
737
+ def add_unique_constraint(table_name, column_name = nil, **options)
738
+ options = unique_constraint_options(table_name, column_name, options)
743
739
  at = create_alter_table(table_name)
744
- at.add_unique_key(column_name, options)
740
+ at.add_unique_constraint(column_name, options)
745
741
 
746
742
  execute schema_creation.accept(at)
747
743
  end
748
744
 
749
- def unique_key_options(table_name, column_name, options) # :nodoc:
745
+ def unique_constraint_options(table_name, column_name, options) # :nodoc:
750
746
  assert_valid_deferrable(options[:deferrable])
751
747
 
752
748
  if column_name && options[:using_index]
@@ -754,22 +750,22 @@ module ActiveRecord
754
750
  end
755
751
 
756
752
  options = options.dup
757
- options[:name] ||= unique_key_name(table_name, column: column_name, **options)
753
+ options[:name] ||= unique_constraint_name(table_name, column: column_name, **options)
758
754
  options
759
755
  end
760
756
 
761
757
  # Removes the given unique constraint from the table.
762
758
  #
763
- # remove_unique_key :sections, name: "unique_position"
759
+ # remove_unique_constraint :sections, name: "unique_position"
764
760
  #
765
761
  # The +column_name+ parameter will be ignored if present. It can be helpful
766
762
  # to provide this in a migration's +change+ method so it can be reverted.
767
- # In that case, +column_name+ will be used by #add_unique_key.
768
- def remove_unique_key(table_name, column_name = nil, **options)
769
- unique_name_to_delete = unique_key_for!(table_name, column: column_name, **options).name
763
+ # In that case, +column_name+ will be used by #add_unique_constraint.
764
+ def remove_unique_constraint(table_name, column_name = nil, **options)
765
+ unique_name_to_delete = unique_constraint_for!(table_name, column: column_name, **options).name
770
766
 
771
767
  at = create_alter_table(table_name)
772
- at.drop_unique_key(unique_name_to_delete)
768
+ at.drop_unique_constraint(unique_name_to_delete)
773
769
 
774
770
  execute schema_creation.accept(at)
775
771
  end
@@ -1042,7 +1038,7 @@ module ActiveRecord
1042
1038
  raise(ArgumentError, "Table '#{table_name}' has no exclusion constraint for #{expression || options}")
1043
1039
  end
1044
1040
 
1045
- def unique_key_name(table_name, **options)
1041
+ def unique_constraint_name(table_name, **options)
1046
1042
  options.fetch(:name) do
1047
1043
  column_or_index = Array(options[:column] || options[:using_index]).map(&:to_s)
1048
1044
  identifier = "#{table_name}_#{column_or_index * '_and_'}_unique"
@@ -1052,13 +1048,13 @@ module ActiveRecord
1052
1048
  end
1053
1049
  end
1054
1050
 
1055
- def unique_key_for(table_name, **options)
1056
- name = unique_key_name(table_name, **options) unless options.key?(:column)
1057
- unique_keys(table_name).detect { |unique_key| unique_key.defined_for?(name: name, **options) }
1051
+ def unique_constraint_for(table_name, **options)
1052
+ name = unique_constraint_name(table_name, **options) unless options.key?(:column)
1053
+ unique_constraints(table_name).detect { |unique_constraint| unique_constraint.defined_for?(name: name, **options) }
1058
1054
  end
1059
1055
 
1060
- def unique_key_for!(table_name, column: nil, **options)
1061
- unique_key_for(table_name, column: column, **options) ||
1056
+ def unique_constraint_for!(table_name, column: nil, **options)
1057
+ unique_constraint_for(table_name, column: column, **options) ||
1062
1058
  raise(ArgumentError, "Table '#{table_name}' has no unique constraint for #{column || options}")
1063
1059
  end
1064
1060
 
@@ -226,7 +226,7 @@ module ActiveRecord
226
226
  true
227
227
  end
228
228
 
229
- def supports_unique_keys?
229
+ def supports_unique_constraints?
230
230
  true
231
231
  end
232
232
 
@@ -1089,11 +1089,6 @@ module ActiveRecord
1089
1089
  SQL
1090
1090
  end
1091
1091
 
1092
- def extract_table_ref_from_insert_sql(sql)
1093
- sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
1094
- $1.strip if $1
1095
- end
1096
-
1097
1092
  def arel_visitor
1098
1093
  Arel::Visitors::PostgreSQL.new(self)
1099
1094
  end
@@ -138,10 +138,6 @@ module ActiveRecord
138
138
  end
139
139
  end
140
140
 
141
- def last_inserted_id(result)
142
- @raw_connection.last_insert_row_id
143
- end
144
-
145
141
  def build_fixture_statements(fixture_set)
146
142
  fixture_set.flat_map do |table_name, fixtures|
147
143
  next if fixtures.empty?
@@ -152,6 +148,10 @@ module ActiveRecord
152
148
  def build_truncate_statement(table_name)
153
149
  "DELETE FROM #{quote_table_name(table_name)}"
154
150
  end
151
+
152
+ def returning_column_values(result)
153
+ result.rows.first
154
+ end
155
155
  end
156
156
  end
157
157
  end
@@ -126,6 +126,7 @@ module ActiveRecord
126
126
 
127
127
  @config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
128
128
  @connection_parameters = @config.merge(database: @config[:database].to_s, results_as_hash: true)
129
+ @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
129
130
  end
130
131
 
131
132
  def database_exists?
@@ -180,6 +181,10 @@ module ActiveRecord
180
181
  database_version >= "3.8.3"
181
182
  end
182
183
 
184
+ def supports_insert_returning?
185
+ database_version >= "3.35.0"
186
+ end
187
+
183
188
  def supports_insert_on_conflict?
184
189
  database_version >= "3.24.0"
185
190
  end
@@ -195,6 +200,10 @@ module ActiveRecord
195
200
  @raw_connection && !@raw_connection.closed?
196
201
  end
197
202
 
203
+ def return_value_after_insert?(column) # :nodoc:
204
+ column.auto_populated?
205
+ end
206
+
198
207
  alias :reset! :reconnect!
199
208
 
200
209
  # Disconnects from the database if already connected. Otherwise, this
@@ -393,6 +402,7 @@ module ActiveRecord
393
402
  end
394
403
  end
395
404
 
405
+ sql << " RETURNING #{insert.returning}" if insert.returning
396
406
  sql
397
407
  end
398
408
 
@@ -400,6 +410,10 @@ module ActiveRecord
400
410
  @config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
401
411
  end
402
412
 
413
+ def use_insert_returning?
414
+ @use_insert_returning
415
+ end
416
+
403
417
  def get_database_version # :nodoc:
404
418
  SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
405
419
  end
@@ -451,10 +465,10 @@ module ActiveRecord
451
465
  when /^null$/i
452
466
  nil
453
467
  # Quoted types
454
- when /^'(.*)'$/m
468
+ when /^'([^|]*)'$/m
455
469
  $1.gsub("''", "'")
456
470
  # Quoted types
457
- when /^"(.*)"$/m
471
+ when /^"([^|]*)"$/m
458
472
  $1.gsub('""', '"')
459
473
  # Numeric types
460
474
  when /\A-?\d+(\.\d*)?\z/
@@ -474,7 +488,7 @@ module ActiveRecord
474
488
  end
475
489
 
476
490
  def has_default_function?(default_value, default)
477
- !default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
491
+ !default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP|\|\|}.match?(default)
478
492
  end
479
493
 
480
494
  # See: https://www.sqlite.org/lang_altertable.html
@@ -698,9 +712,40 @@ module ActiveRecord
698
712
  end
699
713
 
700
714
  def configure_connection
701
- @raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
715
+ if @config[:timeout] && @config[:retries]
716
+ raise ArgumentError, "Cannot specify both timeout and retries arguments"
717
+ elsif @config[:timeout]
718
+ @raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout]))
719
+ elsif @config[:retries]
720
+ retries = self.class.type_cast_config_to_integer(@config[:retries])
721
+ raw_connection.busy_handler do |count|
722
+ count <= retries
723
+ end
724
+ end
702
725
 
726
+ # Enforce foreign key constraints
727
+ # https://www.sqlite.org/pragma.html#pragma_foreign_keys
728
+ # https://www.sqlite.org/foreignkeys.html
703
729
  raw_execute("PRAGMA foreign_keys = ON", "SCHEMA")
730
+ unless @memory_database
731
+ # Journal mode WAL allows for greater concurrency (many readers + one writer)
732
+ # https://www.sqlite.org/pragma.html#pragma_journal_mode
733
+ raw_execute("PRAGMA journal_mode = WAL", "SCHEMA")
734
+ # Set more relaxed level of database durability
735
+ # 2 = "FULL" (sync on every write), 1 = "NORMAL" (sync every 1000 written pages) and 0 = "NONE"
736
+ # https://www.sqlite.org/pragma.html#pragma_synchronous
737
+ raw_execute("PRAGMA synchronous = NORMAL", "SCHEMA")
738
+ # Set the global memory map so all processes can share some data
739
+ # https://www.sqlite.org/pragma.html#pragma_mmap_size
740
+ # https://www.sqlite.org/mmap.html
741
+ raw_execute("PRAGMA mmap_size = #{128.megabytes}", "SCHEMA")
742
+ end
743
+ # Impose a limit on the WAL file to prevent unlimited growth
744
+ # https://www.sqlite.org/pragma.html#pragma_journal_size_limit
745
+ raw_execute("PRAGMA journal_size_limit = #{64.megabytes}", "SCHEMA")
746
+ # Set the local connection cache to 2000 pages
747
+ # https://www.sqlite.org/pragma.html#pragma_cache_size
748
+ raw_execute("PRAGMA cache_size = 2000", "SCHEMA")
704
749
  end
705
750
  end
706
751
  ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)