activerecord 7.0.0.alpha2 → 7.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +504 -10
  3. data/lib/active_record/associations/association.rb +2 -8
  4. data/lib/active_record/associations/builder/collection_association.rb +9 -2
  5. data/lib/active_record/associations/collection_association.rb +10 -2
  6. data/lib/active_record/associations/preloader/association.rb +68 -48
  7. data/lib/active_record/associations/preloader/batch.rb +3 -6
  8. data/lib/active_record/associations/preloader/through_association.rb +19 -9
  9. data/lib/active_record/associations/preloader.rb +14 -24
  10. data/lib/active_record/associations/through_association.rb +2 -2
  11. data/lib/active_record/associations.rb +16 -3
  12. data/lib/active_record/asynchronous_queries_tracker.rb +3 -0
  13. data/lib/active_record/attribute_methods/dirty.rb +9 -1
  14. data/lib/active_record/attribute_methods.rb +7 -5
  15. data/lib/active_record/autosave_association.rb +3 -3
  16. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +6 -26
  17. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -2
  18. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +18 -5
  19. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
  20. data/lib/active_record/connection_adapters/abstract/database_statements.rb +1 -1
  21. data/lib/active_record/connection_adapters/abstract/quoting.rb +33 -70
  22. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -0
  23. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +9 -2
  24. data/lib/active_record/connection_adapters/abstract/transaction.rb +12 -19
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +37 -8
  26. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1 -0
  27. data/lib/active_record/connection_adapters/column.rb +4 -0
  28. data/lib/active_record/connection_adapters/mysql/database_statements.rb +2 -2
  29. data/lib/active_record/connection_adapters/mysql/quoting.rb +23 -24
  30. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +1 -1
  31. data/lib/active_record/connection_adapters/pool_config.rb +7 -5
  32. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
  33. data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -44
  34. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  35. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +18 -1
  36. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  37. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -4
  38. data/lib/active_record/connection_adapters/postgresql_adapter.rb +48 -5
  39. data/lib/active_record/connection_adapters/schema_cache.rb +3 -1
  40. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +2 -2
  41. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +15 -16
  42. data/lib/active_record/connection_handling.rb +31 -19
  43. data/lib/active_record/core.rb +13 -24
  44. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  45. data/lib/active_record/database_configurations/database_config.rb +0 -9
  46. data/lib/active_record/database_configurations/hash_config.rb +40 -8
  47. data/lib/active_record/database_configurations.rb +2 -27
  48. data/lib/active_record/delegated_type.rb +19 -0
  49. data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
  50. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +1 -2
  51. data/lib/active_record/encryption/message_serializer.rb +11 -1
  52. data/lib/active_record/encryption/scheme.rb +1 -1
  53. data/lib/active_record/enum.rb +8 -1
  54. data/lib/active_record/errors.rb +1 -1
  55. data/lib/active_record/explain_registry.rb +11 -6
  56. data/lib/active_record/fixture_set/table_row.rb +1 -1
  57. data/lib/active_record/fixtures.rb +1 -9
  58. data/lib/active_record/future_result.rb +2 -2
  59. data/lib/active_record/gem_version.rb +1 -1
  60. data/lib/active_record/insert_all.rb +52 -15
  61. data/lib/active_record/integration.rb +3 -2
  62. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  63. data/lib/active_record/locking/pessimistic.rb +9 -3
  64. data/lib/active_record/log_subscriber.rb +8 -1
  65. data/lib/active_record/middleware/shard_selector.rb +60 -0
  66. data/lib/active_record/model_schema.rb +1 -28
  67. data/lib/active_record/nested_attributes.rb +11 -10
  68. data/lib/active_record/no_touching.rb +1 -1
  69. data/lib/active_record/persistence.rb +99 -21
  70. data/lib/active_record/query_logs.rb +18 -83
  71. data/lib/active_record/railtie.rb +11 -1
  72. data/lib/active_record/railties/databases.rake +4 -91
  73. data/lib/active_record/reflection.rb +22 -6
  74. data/lib/active_record/relation/calculations.rb +1 -10
  75. data/lib/active_record/relation/finder_methods.rb +0 -13
  76. data/lib/active_record/relation/query_methods.rb +5 -14
  77. data/lib/active_record/relation/record_fetch_warning.rb +5 -7
  78. data/lib/active_record/relation/where_clause.rb +2 -15
  79. data/lib/active_record/relation.rb +11 -13
  80. data/lib/active_record/result.rb +0 -5
  81. data/lib/active_record/runtime_registry.rb +10 -12
  82. data/lib/active_record/schema_dumper.rb +7 -0
  83. data/lib/active_record/scoping.rb +34 -22
  84. data/lib/active_record/suppressor.rb +11 -15
  85. data/lib/active_record/tasks/database_tasks.rb +18 -44
  86. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -1
  87. data/lib/active_record/validations/uniqueness.rb +1 -1
  88. data/lib/active_record.rb +41 -33
  89. data/lib/arel/crud.rb +12 -2
  90. data/lib/arel/delete_manager.rb +16 -0
  91. data/lib/arel/filter_predications.rb +9 -0
  92. data/lib/arel/nodes/delete_statement.rb +5 -1
  93. data/lib/arel/nodes/filter.rb +10 -0
  94. data/lib/arel/nodes/function.rb +1 -0
  95. data/lib/arel/nodes/update_statement.rb +5 -1
  96. data/lib/arel/nodes.rb +1 -0
  97. data/lib/arel/predications.rb +10 -2
  98. data/lib/arel/update_manager.rb +16 -0
  99. data/lib/arel/visitors/mysql.rb +2 -1
  100. data/lib/arel/visitors/to_sql.rb +15 -0
  101. data/lib/arel.rb +1 -0
  102. metadata +14 -10
@@ -10,7 +10,14 @@ module ActiveRecord
10
10
  end
11
11
 
12
12
  def visit_AddForeignKey(o)
13
- super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
13
+ super.dup.tap do |sql|
14
+ if o.deferrable
15
+ sql << " DEFERRABLE"
16
+ sql << " INITIALLY #{o.deferrable.to_s.upcase}" unless o.deferrable == true
17
+ end
18
+
19
+ sql << " NOT VALID" unless o.validate?
20
+ end
14
21
  end
15
22
 
16
23
  def visit_CheckConstraintDefinition(o)
@@ -61,6 +68,19 @@ module ActiveRecord
61
68
  if options[:collation]
62
69
  sql << " COLLATE \"#{options[:collation]}\""
63
70
  end
71
+
72
+ if as = options[:as]
73
+ sql << " GENERATED ALWAYS AS (#{as})"
74
+
75
+ if options[:stored]
76
+ sql << " STORED"
77
+ else
78
+ raise ArgumentError, <<~MSG
79
+ PostgreSQL currently does not support VIRTUAL (not persisted) generated columns.
80
+ Specify 'stored: true' option for '#{options[:column].name}'
81
+ MSG
82
+ end
83
+ end
64
84
  super
65
85
  end
66
86
 
@@ -173,11 +173,19 @@ module ActiveRecord
173
173
  # :method: xml
174
174
  # :call-seq: xml(*names, **options)
175
175
 
176
+ ##
177
+ # :method: timestamptz
178
+ # :call-seq: timestamptz(*names, **options)
179
+
180
+ ##
181
+ # :method: enum
182
+ # :call-seq: enum(*names, **options)
183
+
176
184
  included do
177
185
  define_column_methods :bigserial, :bit, :bit_varying, :cidr, :citext, :daterange,
178
186
  :hstore, :inet, :interval, :int4range, :int8range, :jsonb, :ltree, :macaddr,
179
187
  :money, :numrange, :oid, :point, :line, :lseg, :box, :path, :polygon, :circle,
180
- :serial, :tsrange, :tstzrange, :tsvector, :uuid, :xml, :timestamptz
188
+ :serial, :tsrange, :tstzrange, :tsvector, :uuid, :xml, :timestamptz, :enum
181
189
  end
182
190
  end
183
191
 
@@ -191,6 +199,15 @@ module ActiveRecord
191
199
  @unlogged = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables
192
200
  end
193
201
 
202
+ def new_column_definition(name, type, **options) # :nodoc:
203
+ case type
204
+ when :virtual
205
+ type = options[:type]
206
+ end
207
+
208
+ super
209
+ end
210
+
194
211
  private
195
212
  def aliased_types(name, fallback)
196
213
  fallback
@@ -16,9 +16,30 @@ module ActiveRecord
16
16
  end
17
17
  end
18
18
 
19
+ def types(stream)
20
+ types = @connection.enum_types
21
+ if types.any?
22
+ stream.puts " # Custom types defined in this database."
23
+ stream.puts " # Note that some types may not work with other database engines. Be careful if changing database."
24
+ types.sort.each do |name, values|
25
+ stream.puts " create_enum #{name.inspect}, #{values.split(",").inspect}"
26
+ end
27
+ stream.puts
28
+ end
29
+ end
30
+
19
31
  def prepare_column_options(column)
20
32
  spec = super
21
33
  spec[:array] = "true" if column.array?
34
+
35
+ if @connection.supports_virtual_columns? && column.virtual?
36
+ spec[:as] = extract_expression_for_virtual_column(column)
37
+ spec[:stored] = true
38
+ spec = { type: schema_type(column).inspect }.merge!(spec)
39
+ end
40
+
41
+ spec[:enum_type] = "\"#{column.sql_type}\"" if column.enum?
42
+
22
43
  spec
23
44
  end
24
45
 
@@ -43,6 +64,10 @@ module ActiveRecord
43
64
  def schema_expression(column)
44
65
  super unless column.serial?
45
66
  end
67
+
68
+ def extract_expression_for_virtual_column(column)
69
+ column.default_function.inspect
70
+ end
46
71
  end
47
72
  end
48
73
  end
@@ -483,7 +483,7 @@ module ActiveRecord
483
483
  def foreign_keys(table_name)
484
484
  scope = quoted_scope(table_name)
485
485
  fk_info = exec_query(<<~SQL, "SCHEMA")
486
- 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
486
+ 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
487
487
  FROM pg_constraint c
488
488
  JOIN pg_class t1 ON c.conrelid = t1.oid
489
489
  JOIN pg_class t2 ON c.confrelid = t2.oid
@@ -505,6 +505,8 @@ module ActiveRecord
505
505
 
506
506
  options[:on_delete] = extract_foreign_key_action(row["on_delete"])
507
507
  options[:on_update] = extract_foreign_key_action(row["on_update"])
508
+ options[:deferrable] = extract_foreign_key_deferrable(row["deferrable"], row["deferred"])
509
+
508
510
  options[:validate] = row["valid"]
509
511
 
510
512
  ForeignKeyDefinition.new(table_name, row["to_table"], options)
@@ -542,7 +544,7 @@ module ActiveRecord
542
544
  end
543
545
 
544
546
  # Maps logical Rails types to PostgreSQL-specific data types.
545
- def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
547
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, enum_type: nil, **) # :nodoc:
546
548
  sql = \
547
549
  case type.to_s
548
550
  when "binary"
@@ -566,6 +568,10 @@ module ActiveRecord
566
568
  when 5..8; "bigint"
567
569
  else raise ArgumentError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead."
568
570
  end
571
+ when "enum"
572
+ raise ArgumentError "enum_type is required for enums" if enum_type.nil?
573
+
574
+ enum_type
569
575
  else
570
576
  super
571
577
  end
@@ -654,7 +660,7 @@ module ActiveRecord
654
660
  end
655
661
 
656
662
  def new_column_from_field(table_name, field)
657
- column_name, type, default, notnull, oid, fmod, collation, comment = field
663
+ column_name, type, default, notnull, oid, fmod, collation, comment, attgenerated = field
658
664
  type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
659
665
  default_value = extract_value_from_default(default)
660
666
  default_function = extract_default_function(default_value, default)
@@ -671,7 +677,8 @@ module ActiveRecord
671
677
  default_function,
672
678
  collation: collation,
673
679
  comment: comment.presence,
674
- serial: serial
680
+ serial: serial,
681
+ generated: attgenerated
675
682
  )
676
683
  end
677
684
 
@@ -711,6 +718,10 @@ module ActiveRecord
711
718
  end
712
719
  end
713
720
 
721
+ def extract_foreign_key_deferrable(deferrable, deferred)
722
+ deferrable && (deferred ? :deferred : true)
723
+ end
724
+
714
725
  def add_column_for_alter(table_name, column_name, type, **options)
715
726
  return super unless options.key?(:comment)
716
727
  [super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
@@ -125,6 +125,7 @@ module ActiveRecord
125
125
  string: { name: "character varying" },
126
126
  text: { name: "text" },
127
127
  integer: { name: "integer", limit: 4 },
128
+ bigint: { name: "bigint" },
128
129
  float: { name: "float" },
129
130
  decimal: { name: "decimal" },
130
131
  datetime: {}, # set dynamically based on datetime_type
@@ -163,6 +164,7 @@ module ActiveRecord
163
164
  money: { name: "money" },
164
165
  interval: { name: "interval" },
165
166
  oid: { name: "oid" },
167
+ enum: {} # special type https://www.postgresql.org/docs/current/datatype-enum.html
166
168
  }
167
169
 
168
170
  OID = PostgreSQL::OID # :nodoc:
@@ -181,7 +183,7 @@ module ActiveRecord
181
183
  end
182
184
 
183
185
  def supports_partitioned_indexes?
184
- database_version >= 110_000
186
+ database_version >= 110_000 # >= 11.0
185
187
  end
186
188
 
187
189
  def supports_partial_index?
@@ -208,6 +210,10 @@ module ActiveRecord
208
210
  true
209
211
  end
210
212
 
213
+ def supports_deferrable_constraints?
214
+ true
215
+ end
216
+
211
217
  def supports_views?
212
218
  true
213
219
  end
@@ -233,12 +239,16 @@ module ActiveRecord
233
239
  end
234
240
 
235
241
  def supports_insert_on_conflict?
236
- database_version >= 90500
242
+ database_version >= 90500 # >= 9.5
237
243
  end
238
244
  alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
239
245
  alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
240
246
  alias supports_insert_conflict_target? supports_insert_on_conflict?
241
247
 
248
+ def supports_virtual_columns?
249
+ database_version >= 120_000 # >= 12.0
250
+ end
251
+
242
252
  def index_algorithms
243
253
  { concurrently: "CONCURRENTLY" }
244
254
  end
@@ -388,7 +398,7 @@ module ActiveRecord
388
398
  end
389
399
 
390
400
  def supports_pgcrypto_uuid?
391
- database_version >= 90400
401
+ database_version >= 90400 # >= 9.4
392
402
  end
393
403
 
394
404
  def supports_optimizer_hints?
@@ -444,6 +454,38 @@ module ActiveRecord
444
454
  exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
445
455
  end
446
456
 
457
+ # Returns a list of defined enum types, and their values.
458
+ def enum_types
459
+ query = <<~SQL
460
+ SELECT
461
+ type.typname AS name,
462
+ string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value
463
+ FROM pg_enum AS enum
464
+ JOIN pg_type AS type
465
+ ON (type.oid = enum.enumtypid)
466
+ GROUP BY type.typname;
467
+ SQL
468
+ exec_query(query, "SCHEMA").cast_values
469
+ end
470
+
471
+ # Given a name and an array of values, creates an enum type.
472
+ def create_enum(name, values)
473
+ sql_values = values.map { |s| "'#{s}'" }.join(", ")
474
+ query = <<~SQL
475
+ DO $$
476
+ BEGIN
477
+ IF NOT EXISTS (
478
+ SELECT 1 FROM pg_type t
479
+ WHERE t.typname = '#{name}'
480
+ ) THEN
481
+ CREATE TYPE \"#{name}\" AS ENUM (#{sql_values});
482
+ END IF;
483
+ END
484
+ $$;
485
+ SQL
486
+ exec_query(query)
487
+ end
488
+
447
489
  # Returns the configured supported identifier length supported by PostgreSQL
448
490
  def max_identifier_length
449
491
  @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
@@ -489,7 +531,7 @@ module ActiveRecord
489
531
  end
490
532
 
491
533
  def check_version # :nodoc:
492
- if database_version < 90300
534
+ if database_version < 90300 # < 9.3
493
535
  raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
494
536
  end
495
537
  end
@@ -874,7 +916,8 @@ module ActiveRecord
874
916
  query(<<~SQL, "SCHEMA")
875
917
  SELECT a.attname, format_type(a.atttypid, a.atttypmod),
876
918
  pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
877
- c.collname, col_description(a.attrelid, a.attnum) AS comment
919
+ c.collname, col_description(a.attrelid, a.attnum) AS comment,
920
+ #{supports_virtual_columns? ? 'attgenerated' : quote('')} as attgenerated
878
921
  FROM pg_attribute a
879
922
  LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
880
923
  LEFT JOIN pg_type t ON a.atttypid = t.oid
@@ -208,7 +208,7 @@ module ActiveRecord
208
208
  end
209
209
 
210
210
  def reset_version!
211
- @version = connection.migration_context.current_version
211
+ @version = connection.schema_version
212
212
  end
213
213
 
214
214
  def derive_columns_hash_and_deduplicate_values
@@ -239,6 +239,8 @@ module ActiveRecord
239
239
  end
240
240
 
241
241
  def open(filename)
242
+ FileUtils.mkdir_p(File.dirname(filename))
243
+
242
244
  File.atomic_write(filename) do |file|
243
245
  if File.extname(filename) == ".gz"
244
246
  zipper = Zlib::GzipWriter.new file
@@ -78,7 +78,7 @@ module ActiveRecord
78
78
  raise TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted
79
79
  raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache?
80
80
 
81
- Thread.current.thread_variable_set("read_uncommitted", @connection.get_first_value("PRAGMA read_uncommitted"))
81
+ ActiveSupport::IsolatedExecutionState[:active_record_read_uncommitted] = @connection.get_first_value("PRAGMA read_uncommitted")
82
82
  @connection.read_uncommitted = true
83
83
  begin_db_transaction
84
84
  end
@@ -108,7 +108,7 @@ module ActiveRecord
108
108
 
109
109
  private
110
110
  def reset_read_uncommitted
111
- read_uncommitted = Thread.current.thread_variable_get("read_uncommitted")
111
+ read_uncommitted = ActiveSupport::IsolatedExecutionState[:active_record_read_uncommitted]
112
112
  return unless read_uncommitted
113
113
 
114
114
  @connection.read_uncommitted = read_uncommitted
@@ -45,6 +45,21 @@ module ActiveRecord
45
45
  0
46
46
  end
47
47
 
48
+ def type_cast(value) # :nodoc:
49
+ case value
50
+ when BigDecimal
51
+ value.to_f
52
+ when String
53
+ if value.encoding == Encoding::ASCII_8BIT
54
+ super(value.encode(Encoding::UTF_8))
55
+ else
56
+ super
57
+ end
58
+ else
59
+ super
60
+ end
61
+ end
62
+
48
63
  def column_name_matcher
49
64
  COLUMN_NAME
50
65
  end
@@ -80,22 +95,6 @@ module ActiveRecord
80
95
  /ix
81
96
 
82
97
  private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
83
-
84
- private
85
- def _type_cast(value)
86
- case value
87
- when BigDecimal
88
- value.to_f
89
- when String
90
- if value.encoding == Encoding::ASCII_8BIT
91
- super(value.encode(Encoding::UTF_8))
92
- else
93
- super
94
- end
95
- else
96
- super
97
- end
98
- end
99
98
  end
100
99
  end
101
100
  end
@@ -153,10 +153,6 @@ module ActiveRecord
153
153
  raise ArgumentError, "must provide a `shard` and/or `role`."
154
154
  end
155
155
 
156
- unless role
157
- raise ArgumentError, "`connected_to` cannot accept a `shard` argument without a `role`."
158
- end
159
-
160
156
  with_role_and_shard(role, shard, prevent_writes, &blk)
161
157
  end
162
158
 
@@ -186,7 +182,7 @@ module ActiveRecord
186
182
 
187
183
  prevent_writes = true if role == ActiveRecord.reading_role
188
184
 
189
- connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: classes }
185
+ append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: classes)
190
186
  yield
191
187
  ensure
192
188
  connected_to_stack.pop
@@ -206,7 +202,26 @@ module ActiveRecord
206
202
 
207
203
  prevent_writes = true if role == ActiveRecord.reading_role
208
204
 
209
- self.connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self] }
205
+ append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self])
206
+ end
207
+
208
+ # Prohibit swapping shards while inside of the passed block.
209
+ #
210
+ # In some cases you may want to be able to swap shards but not allow a
211
+ # nested call to connected_to or connected_to_many to swap again. This
212
+ # is useful in cases you're using sharding to provide per-request
213
+ # database isolation.
214
+ def prohibit_shard_swapping(enabled = true)
215
+ prev_value = ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping]
216
+ ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping] = enabled
217
+ yield
218
+ ensure
219
+ ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping] = prev_value
220
+ end
221
+
222
+ # Determine whether or not shard swapping is currently prohibited
223
+ def shard_swapping_prohibited?
224
+ ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping]
210
225
  end
211
226
 
212
227
  # Prevent writing to the database regardless of role.
@@ -279,17 +294,6 @@ module ActiveRecord
279
294
  self == Base || application_record_class?
280
295
  end
281
296
 
282
- # Returns the configuration of the associated connection as a hash:
283
- #
284
- # ActiveRecord::Base.connection_config
285
- # # => {pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"}
286
- #
287
- # Please use only for reading.
288
- def connection_config
289
- connection_pool.db_config.configuration_hash
290
- end
291
- deprecate connection_config: "Use connection_db_config instead"
292
-
293
297
  # Returns the db_config object from the associated connection:
294
298
  #
295
299
  # ActiveRecord::Base.connection_db_config
@@ -361,12 +365,12 @@ module ActiveRecord
361
365
  if ActiveRecord.legacy_connection_handling
362
366
  with_handler(role.to_sym) do
363
367
  connection_handler.while_preventing_writes(prevent_writes) do
364
- self.connected_to_stack << { shard: shard, klasses: [self] }
368
+ append_to_connected_to_stack(shard: shard, klasses: [self])
365
369
  yield
366
370
  end
367
371
  end
368
372
  else
369
- self.connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self] }
373
+ append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self])
370
374
  return_value = yield
371
375
  return_value.load if return_value.is_a? ActiveRecord::Relation
372
376
  return_value
@@ -375,6 +379,14 @@ module ActiveRecord
375
379
  self.connected_to_stack.pop
376
380
  end
377
381
 
382
+ def append_to_connected_to_stack(entry)
383
+ if shard_swapping_prohibited? && entry[:shard].present?
384
+ raise ArgumentError, "cannot swap `shard` while shard swapping is prohibited."
385
+ end
386
+
387
+ connected_to_stack << entry
388
+ end
389
+
378
390
  def swap_connection_handler(handler, &blk) # :nodoc:
379
391
  old_handler, ActiveRecord::Base.connection_handler = ActiveRecord::Base.connection_handler, handler
380
392
  return_value = yield
@@ -77,6 +77,8 @@ module ActiveRecord
77
77
 
78
78
  class_attribute :default_shard, instance_writer: false
79
79
 
80
+ class_attribute :shard_selector, instance_accessor: false, default: nil
81
+
80
82
  def self.application_record_class? # :nodoc:
81
83
  if ActiveRecord.application_record_class
82
84
  self == ActiveRecord.application_record_class
@@ -90,11 +92,11 @@ module ActiveRecord
90
92
  self.filter_attributes = []
91
93
 
92
94
  def self.connection_handler
93
- Thread.current.thread_variable_get(:ar_connection_handler) || default_connection_handler
95
+ ActiveSupport::IsolatedExecutionState[:active_record_connection_handler] || default_connection_handler
94
96
  end
95
97
 
96
98
  def self.connection_handler=(handler)
97
- Thread.current.thread_variable_set(:ar_connection_handler, handler)
99
+ ActiveSupport::IsolatedExecutionState[:active_record_connection_handler] = handler
98
100
  end
99
101
 
100
102
  def self.connection_handlers
@@ -129,8 +131,8 @@ module ActiveRecord
129
131
  end
130
132
 
131
133
  def self.asynchronous_queries_tracker # :nodoc:
132
- Thread.current.thread_variable_get(:ar_asynchronous_queries_tracker) ||
133
- Thread.current.thread_variable_set(:ar_asynchronous_queries_tracker, AsynchronousQueriesTracker.new)
134
+ ActiveSupport::IsolatedExecutionState[:active_record_asynchronous_queries_tracker] ||= \
135
+ AsynchronousQueriesTracker.new
134
136
  end
135
137
 
136
138
  # Returns the symbol representing the current connected role.
@@ -148,7 +150,7 @@ module ActiveRecord
148
150
  else
149
151
  connected_to_stack.reverse_each do |hash|
150
152
  return hash[:role] if hash[:role] && hash[:klasses].include?(Base)
151
- return hash[:role] if hash[:role] && hash[:klasses].include?(connection_classes)
153
+ return hash[:role] if hash[:role] && hash[:klasses].include?(connection_class_for_self)
152
154
  end
153
155
 
154
156
  default_role
@@ -167,7 +169,7 @@ module ActiveRecord
167
169
  def self.current_shard
168
170
  connected_to_stack.reverse_each do |hash|
169
171
  return hash[:shard] if hash[:shard] && hash[:klasses].include?(Base)
170
- return hash[:shard] if hash[:shard] && hash[:klasses].include?(connection_classes)
172
+ return hash[:shard] if hash[:shard] && hash[:klasses].include?(connection_class_for_self)
171
173
  end
172
174
 
173
175
  default_shard
@@ -189,7 +191,7 @@ module ActiveRecord
189
191
  else
190
192
  connected_to_stack.reverse_each do |hash|
191
193
  return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
192
- return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(connection_classes)
194
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(connection_class_for_self)
193
195
  end
194
196
 
195
197
  false
@@ -197,11 +199,11 @@ module ActiveRecord
197
199
  end
198
200
 
199
201
  def self.connected_to_stack # :nodoc:
200
- if connected_to_stack = Thread.current.thread_variable_get(:ar_connected_to_stack)
202
+ if connected_to_stack = ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack]
201
203
  connected_to_stack
202
204
  else
203
205
  connected_to_stack = Concurrent::Array.new
204
- Thread.current.thread_variable_set(:ar_connected_to_stack, connected_to_stack)
206
+ ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack] = connected_to_stack
205
207
  connected_to_stack
206
208
  end
207
209
  end
@@ -218,7 +220,7 @@ module ActiveRecord
218
220
  self.connection_class
219
221
  end
220
222
 
221
- def self.connection_classes # :nodoc:
223
+ def self.connection_class_for_self # :nodoc:
222
224
  klass = self
223
225
 
224
226
  until klass == Base
@@ -229,14 +231,6 @@ module ActiveRecord
229
231
  klass
230
232
  end
231
233
 
232
- def self.allow_unsafe_raw_sql # :nodoc:
233
- ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_unsafe_raw_sql is deprecated and will be removed in Rails 7.0")
234
- end
235
-
236
- def self.allow_unsafe_raw_sql=(value) # :nodoc:
237
- ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_unsafe_raw_sql= is deprecated and will be removed in Rails 7.0")
238
- end
239
-
240
234
  self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
241
235
  self.default_role = ActiveRecord.writing_role
242
236
  self.default_shard = :default
@@ -427,11 +421,6 @@ module ActiveRecord
427
421
  @arel_table ||= Arel::Table.new(table_name, klass: self)
428
422
  end
429
423
 
430
- def arel_attribute(name, table = arel_table) # :nodoc:
431
- table[name]
432
- end
433
- deprecate :arel_attribute
434
-
435
424
  def predicate_builder # :nodoc:
436
425
  @predicate_builder ||= PredicateBuilder.new(table_metadata)
437
426
  end
@@ -497,7 +486,7 @@ module ActiveRecord
497
486
  # post.init_with(coder)
498
487
  # post.title # => 'hello world'
499
488
  def init_with(coder, &block)
500
- coder = LegacyYamlAdapter.convert(self.class, coder)
489
+ coder = LegacyYamlAdapter.convert(coder)
501
490
  attributes = self.class.yaml_encoder.decode(coder)
502
491
  init_with_attributes(attributes, coder["new_record"], &block)
503
492
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "uri"
3
4
  require "active_support/core_ext/enumerable"
4
5
 
5
6
  module ActiveRecord
@@ -67,7 +68,7 @@ module ActiveRecord
67
68
  database: uri.opaque
68
69
  )
69
70
  else
70
- query_hash.merge(
71
+ query_hash.reverse_merge(
71
72
  adapter: @adapter,
72
73
  username: uri.user,
73
74
  password: uri.password,
@@ -15,15 +15,6 @@ module ActiveRecord
15
15
  @name = name
16
16
  end
17
17
 
18
- def spec_name
19
- @name
20
- end
21
- deprecate spec_name: "please use name instead"
22
-
23
- def config
24
- raise NotImplementedError
25
- end
26
-
27
18
  def adapter_method
28
19
  "#{adapter}_connection"
29
20
  end