activerecord 8.0.2 → 8.1.0.beta1

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 (159) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +459 -413
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +1 -1
  5. data/lib/active_record/associations/association.rb +1 -1
  6. data/lib/active_record/associations/belongs_to_association.rb +9 -1
  7. data/lib/active_record/associations/builder/association.rb +16 -5
  8. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  9. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  10. data/lib/active_record/associations/builder/has_one.rb +1 -1
  11. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  12. data/lib/active_record/associations/collection_association.rb +3 -3
  13. data/lib/active_record/associations/collection_proxy.rb +22 -4
  14. data/lib/active_record/associations/deprecation.rb +88 -0
  15. data/lib/active_record/associations/errors.rb +3 -0
  16. data/lib/active_record/associations/join_dependency.rb +2 -0
  17. data/lib/active_record/associations/preloader/branch.rb +1 -0
  18. data/lib/active_record/associations.rb +159 -21
  19. data/lib/active_record/attribute_methods/query.rb +34 -0
  20. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  21. data/lib/active_record/attributes.rb +38 -24
  22. data/lib/active_record/base.rb +0 -1
  23. data/lib/active_record/coders/json.rb +14 -5
  24. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +2 -4
  25. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +15 -0
  26. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
  27. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +384 -49
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +26 -30
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -1
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  31. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  32. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
  33. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  34. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +89 -23
  35. data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
  36. data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -13
  37. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -11
  38. data/lib/active_record/connection_adapters/column.rb +17 -4
  39. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  40. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  41. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  42. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
  43. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
  44. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
  45. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -16
  46. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  47. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  48. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  49. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -1
  50. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
  51. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +65 -30
  52. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +74 -38
  53. data/lib/active_record/connection_adapters/postgresql_adapter.rb +12 -7
  54. data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
  55. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -27
  56. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  57. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
  58. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +56 -32
  59. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
  60. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
  61. data/lib/active_record/connection_adapters.rb +1 -0
  62. data/lib/active_record/connection_handling.rb +1 -1
  63. data/lib/active_record/core.rb +13 -10
  64. data/lib/active_record/counter_cache.rb +33 -8
  65. data/lib/active_record/database_configurations/database_config.rb +5 -1
  66. data/lib/active_record/database_configurations/hash_config.rb +56 -9
  67. data/lib/active_record/database_configurations/url_config.rb +13 -3
  68. data/lib/active_record/database_configurations.rb +7 -3
  69. data/lib/active_record/delegated_type.rb +2 -2
  70. data/lib/active_record/dynamic_matchers.rb +54 -69
  71. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  72. data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
  73. data/lib/active_record/encryption/encryptor.rb +27 -25
  74. data/lib/active_record/encryption/scheme.rb +1 -1
  75. data/lib/active_record/enum.rb +37 -20
  76. data/lib/active_record/errors.rb +20 -4
  77. data/lib/active_record/explain_registry.rb +0 -1
  78. data/lib/active_record/filter_attribute_handler.rb +73 -0
  79. data/lib/active_record/fixture_set/table_row.rb +19 -2
  80. data/lib/active_record/fixtures.rb +2 -2
  81. data/lib/active_record/gem_version.rb +3 -3
  82. data/lib/active_record/inheritance.rb +1 -1
  83. data/lib/active_record/insert_all.rb +12 -7
  84. data/lib/active_record/locking/optimistic.rb +7 -0
  85. data/lib/active_record/locking/pessimistic.rb +5 -0
  86. data/lib/active_record/log_subscriber.rb +1 -5
  87. data/lib/active_record/middleware/shard_selector.rb +34 -17
  88. data/lib/active_record/migration/command_recorder.rb +14 -1
  89. data/lib/active_record/migration/compatibility.rb +34 -24
  90. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  91. data/lib/active_record/migration.rb +31 -21
  92. data/lib/active_record/model_schema.rb +10 -7
  93. data/lib/active_record/nested_attributes.rb +2 -0
  94. data/lib/active_record/persistence.rb +34 -3
  95. data/lib/active_record/query_cache.rb +22 -15
  96. data/lib/active_record/query_logs.rb +7 -7
  97. data/lib/active_record/querying.rb +4 -4
  98. data/lib/active_record/railtie.rb +34 -5
  99. data/lib/active_record/railties/databases.rake +23 -19
  100. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  101. data/lib/active_record/railties/job_runtime.rb +10 -11
  102. data/lib/active_record/reflection.rb +42 -3
  103. data/lib/active_record/relation/batches.rb +26 -12
  104. data/lib/active_record/relation/calculations.rb +35 -25
  105. data/lib/active_record/relation/delegation.rb +0 -1
  106. data/lib/active_record/relation/finder_methods.rb +41 -24
  107. data/lib/active_record/relation/merger.rb +2 -2
  108. data/lib/active_record/relation/predicate_builder.rb +2 -2
  109. data/lib/active_record/relation/query_attribute.rb +3 -1
  110. data/lib/active_record/relation/query_methods.rb +43 -33
  111. data/lib/active_record/relation/spawn_methods.rb +6 -6
  112. data/lib/active_record/relation/where_clause.rb +7 -10
  113. data/lib/active_record/relation.rb +37 -15
  114. data/lib/active_record/result.rb +44 -21
  115. data/lib/active_record/sanitization.rb +2 -0
  116. data/lib/active_record/schema_dumper.rb +12 -10
  117. data/lib/active_record/scoping.rb +0 -1
  118. data/lib/active_record/secure_token.rb +3 -3
  119. data/lib/active_record/signed_id.rb +46 -18
  120. data/lib/active_record/statement_cache.rb +13 -9
  121. data/lib/active_record/store.rb +44 -19
  122. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  123. data/lib/active_record/tasks/database_tasks.rb +24 -35
  124. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
  125. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  126. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  127. data/lib/active_record/test_databases.rb +11 -3
  128. data/lib/active_record/test_fixtures.rb +27 -2
  129. data/lib/active_record/testing/query_assertions.rb +8 -2
  130. data/lib/active_record/timestamp.rb +4 -2
  131. data/lib/active_record/transaction.rb +2 -5
  132. data/lib/active_record/transactions.rb +34 -10
  133. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  134. data/lib/active_record/type/internal/timezone.rb +7 -0
  135. data/lib/active_record/type/json.rb +15 -2
  136. data/lib/active_record/type/serialized.rb +11 -4
  137. data/lib/active_record/type/type_map.rb +1 -1
  138. data/lib/active_record/type_caster/connection.rb +2 -1
  139. data/lib/active_record/validations/associated.rb +1 -1
  140. data/lib/active_record.rb +68 -5
  141. data/lib/arel/alias_predication.rb +2 -0
  142. data/lib/arel/crud.rb +8 -11
  143. data/lib/arel/delete_manager.rb +5 -0
  144. data/lib/arel/nodes/count.rb +2 -2
  145. data/lib/arel/nodes/delete_statement.rb +4 -2
  146. data/lib/arel/nodes/function.rb +4 -10
  147. data/lib/arel/nodes/named_function.rb +2 -2
  148. data/lib/arel/nodes/node.rb +1 -1
  149. data/lib/arel/nodes/update_statement.rb +4 -2
  150. data/lib/arel/nodes.rb +0 -2
  151. data/lib/arel/select_manager.rb +13 -4
  152. data/lib/arel/update_manager.rb +5 -0
  153. data/lib/arel/visitors/dot.rb +2 -3
  154. data/lib/arel/visitors/postgresql.rb +55 -0
  155. data/lib/arel/visitors/sqlite.rb +55 -8
  156. data/lib/arel/visitors/to_sql.rb +5 -21
  157. data/lib/arel.rb +3 -1
  158. metadata +15 -11
  159. data/lib/active_record/normalization.rb +0 -163
@@ -112,6 +112,7 @@ module ActiveRecord
112
112
  def closed?; true; end
113
113
  def open?; false; end
114
114
  def joinable?; false; end
115
+ def isolation; nil; end
115
116
  def add_record(record, _ = true); end
116
117
  def restartable?; false; end
117
118
  def dirty?; false; end
@@ -150,6 +151,11 @@ module ActiveRecord
150
151
 
151
152
  delegate :invalidate!, :invalidated?, to: :@state
152
153
 
154
+ # Returns the isolation level if it was explicitly set, nil otherwise
155
+ def isolation
156
+ @isolation_level
157
+ end
158
+
153
159
  def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
154
160
  super()
155
161
  @connection = connection
@@ -175,11 +181,11 @@ module ActiveRecord
175
181
  end
176
182
 
177
183
  def open?
178
- true
184
+ !closed?
179
185
  end
180
186
 
181
187
  def closed?
182
- false
188
+ @state.finalized?
183
189
  end
184
190
 
185
191
  def add_record(record, ensure_finalize = true)
@@ -386,7 +392,7 @@ module ActiveRecord
386
392
  @parent.state.add_child(@state)
387
393
  end
388
394
 
389
- delegate :materialize!, :materialized?, :restart, to: :@parent
395
+ delegate :materialize!, :materialized?, :restart, :isolation, to: :@parent
390
396
 
391
397
  def rollback
392
398
  @state.rollback!
@@ -405,6 +411,7 @@ module ActiveRecord
405
411
  def initialize(connection, savepoint_name, parent_transaction, **options)
406
412
  super(connection, **options)
407
413
 
414
+ @parent_transaction = parent_transaction
408
415
  parent_transaction.state.add_child(@state)
409
416
 
410
417
  if isolation_level
@@ -414,6 +421,11 @@ module ActiveRecord
414
421
  @savepoint_name = savepoint_name
415
422
  end
416
423
 
424
+ # Delegates to parent transaction's isolation level
425
+ def isolation
426
+ @parent_transaction.isolation
427
+ end
428
+
417
429
  def materialize!
418
430
  connection.create_savepoint(savepoint_name)
419
431
  super
@@ -620,6 +632,7 @@ module ActiveRecord
620
632
  end
621
633
 
622
634
  def within_new_transaction(isolation: nil, joinable: true)
635
+ isolation ||= @connection.pool.pool_transaction_isolation_level
623
636
  @connection.lock.synchronize do
624
637
  transaction = begin_transaction(isolation: isolation, joinable: joinable)
625
638
  begin
@@ -42,6 +42,7 @@ module ActiveRecord
42
42
 
43
43
  attr_reader :pool
44
44
  attr_reader :visitor, :owner, :logger, :lock
45
+ attr_accessor :allow_preconnect
45
46
  alias :in_use? :owner
46
47
 
47
48
  def pool=(value)
@@ -119,7 +120,7 @@ module ActiveRecord
119
120
 
120
121
  # Opens a database console session.
121
122
  def self.dbconsole(config, options = {})
122
- raise NotImplementedError
123
+ raise NotImplementedError.new("#{self.class} should define `dbconsole` that accepts a db config and options to implement connecting to the db console")
123
124
  end
124
125
 
125
126
  def initialize(config_or_deprecated_connection, deprecated_logger = nil, deprecated_connection_options = nil, deprecated_config = nil) # :nodoc:
@@ -127,6 +128,7 @@ module ActiveRecord
127
128
 
128
129
  @raw_connection = nil
129
130
  @unconfigured_connection = nil
131
+ @connected_since = nil
130
132
 
131
133
  if config_or_deprecated_connection.is_a?(Hash)
132
134
  @config = config_or_deprecated_connection.symbolize_keys
@@ -139,6 +141,7 @@ module ActiveRecord
139
141
  # Soft-deprecated for now; we'll probably warn in future.
140
142
 
141
143
  @unconfigured_connection = config_or_deprecated_connection
144
+ @connected_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
142
145
  @logger = deprecated_logger || ActiveRecord::Base.logger
143
146
  if deprecated_config
144
147
  @config = (deprecated_config || {}).symbolize_keys
@@ -152,6 +155,7 @@ module ActiveRecord
152
155
  @owner = nil
153
156
  @pool = ActiveRecord::ConnectionAdapters::NullPool.new
154
157
  @idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
158
+ @allow_preconnect = true
155
159
  @visitor = arel_visitor
156
160
  @statements = build_statement_pool
157
161
  self.lock_thread = nil
@@ -169,6 +173,8 @@ module ActiveRecord
169
173
  @raw_connection_dirty = false
170
174
  @last_activity = nil
171
175
  @verified = false
176
+
177
+ @pool_jitter = rand * max_jitter
172
178
  end
173
179
 
174
180
  def inspect # :nodoc:
@@ -196,6 +202,11 @@ module ActiveRecord
196
202
  end
197
203
  end
198
204
 
205
+ MAX_JITTER = 0.0..1.0 # :nodoc:
206
+ def max_jitter
207
+ (@config[:pool_jitter] || 0.2).to_f.clamp(MAX_JITTER)
208
+ end
209
+
199
210
  def replica?
200
211
  @config[:replica] || false
201
212
  end
@@ -260,7 +271,11 @@ module ActiveRecord
260
271
  end
261
272
 
262
273
  def valid_type?(type) # :nodoc:
263
- !native_database_types[type].nil?
274
+ self.class.valid_type?(type)
275
+ end
276
+
277
+ def native_database_types # :nodoc:
278
+ self.class.native_database_types
264
279
  end
265
280
 
266
281
  # this method must only be called while holding connection pool's mutex
@@ -299,8 +314,12 @@ module ActiveRecord
299
314
  @pool.schema_cache || (@schema_cache ||= BoundSchemaReflection.for_lone_connection(@pool.schema_reflection, self))
300
315
  end
301
316
 
317
+ def pool_jitter(duration)
318
+ duration * (1.0 - @pool_jitter)
319
+ end
320
+
302
321
  # this method must only be called while holding connection pool's mutex
303
- def expire
322
+ def expire(update_idle = true) # :nodoc:
304
323
  if in_use?
305
324
  if @owner != ActiveSupport::IsolatedExecutionState.context
306
325
  raise ActiveRecordError, "Cannot expire connection, " \
@@ -308,7 +327,7 @@ module ActiveRecord
308
327
  "Current thread: #{ActiveSupport::IsolatedExecutionState.context}."
309
328
  end
310
329
 
311
- @idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
330
+ @idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC) if update_idle
312
331
  @owner = nil
313
332
  else
314
333
  raise ActiveRecordError, "Cannot expire connection, it is not currently leased."
@@ -341,6 +360,21 @@ module ActiveRecord
341
360
  end
342
361
  end
343
362
 
363
+ # Seconds since this connection was established. nil if not
364
+ # connected; infinity if the connection has been explicitly
365
+ # retired.
366
+ def connection_age # :nodoc:
367
+ if @raw_connection && @connected_since
368
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) - @connected_since
369
+ end
370
+ end
371
+
372
+ # Mark the connection as needing to be retired, as if the age has
373
+ # exceeded the maximum allowed.
374
+ def force_retirement # :nodoc:
375
+ @connected_since &&= -Float::INFINITY
376
+ end
377
+
344
378
  def unprepared_statement
345
379
  cache = prepared_statements_disabled_cache.add?(object_id) if @prepared_statements
346
380
  yield
@@ -555,6 +589,10 @@ module ActiveRecord
555
589
  false
556
590
  end
557
591
 
592
+ def supports_disabling_indexes?
593
+ false
594
+ end
595
+
558
596
  def return_value_after_insert?(column) # :nodoc:
559
597
  column.auto_populated?
560
598
  end
@@ -664,12 +702,15 @@ module ActiveRecord
664
702
  deadline = retry_deadline && Process.clock_gettime(Process::CLOCK_MONOTONIC) + retry_deadline
665
703
 
666
704
  @lock.synchronize do
705
+ @allow_preconnect = false
706
+
667
707
  reconnect
668
708
 
669
709
  enable_lazy_transactions!
670
710
  @raw_connection_dirty = false
671
- @last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
711
+ @last_activity = @connected_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
672
712
  @verified = true
713
+ @allow_preconnect = true
673
714
 
674
715
  reset_transaction(restore: restore_transactions) do
675
716
  clear_cache!(new_connection: true)
@@ -702,6 +743,7 @@ module ActiveRecord
702
743
  clear_cache!(new_connection: true)
703
744
  reset_transaction
704
745
  @raw_connection_dirty = false
746
+ @connected_since = nil
705
747
  end
706
748
  end
707
749
 
@@ -765,6 +807,7 @@ module ActiveRecord
765
807
  attempt_configure_connection
766
808
  @last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
767
809
  @verified = true
810
+ @allow_preconnect = true
768
811
  return
769
812
  end
770
813
 
@@ -772,6 +815,7 @@ module ActiveRecord
772
815
  end
773
816
  end
774
817
 
818
+ @last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
775
819
  @verified = true
776
820
  end
777
821
 
@@ -785,6 +829,10 @@ module ActiveRecord
785
829
  @verified = nil
786
830
  end
787
831
 
832
+ def verified? # :nodoc:
833
+ @verified
834
+ end
835
+
788
836
  # Provides access to the underlying database driver for this adapter. For
789
837
  # example, this method returns a Mysql2::Client object in case of Mysql2Adapter,
790
838
  # and a PG::Connection object in case of PostgreSQLAdapter.
@@ -870,7 +918,7 @@ module ActiveRecord
870
918
  def register_class_with_precision(mapping, key, klass, **kwargs) # :nodoc:
871
919
  mapping.register_type(key) do |*args|
872
920
  precision = extract_precision(args.last)
873
- klass.new(precision: precision, **kwargs)
921
+ klass.new(precision: precision, **kwargs).freeze
874
922
  end
875
923
  end
876
924
 
@@ -882,6 +930,10 @@ module ActiveRecord
882
930
  end
883
931
  end
884
932
 
933
+ def valid_type?(type) # :nodoc:
934
+ !native_database_types[type].nil?
935
+ end
936
+
885
937
  private
886
938
  def initialize_type_map(m)
887
939
  register_class_with_limit m, %r(boolean)i, Type::Boolean
@@ -901,7 +953,7 @@ module ActiveRecord
901
953
  m.alias_type %r(number)i, "decimal"
902
954
  m.alias_type %r(double)i, "float"
903
955
 
904
- m.register_type %r(^json)i, Type::Json.new
956
+ m.register_type %r(^json)i, Type::Json.new.freeze
905
957
 
906
958
  m.register_type(%r(decimal)i) do |sql_type|
907
959
  scale = extract_scale(sql_type)
@@ -909,9 +961,9 @@ module ActiveRecord
909
961
 
910
962
  if scale == 0
911
963
  # FIXME: Remove this class as well
912
- Type::DecimalWithoutScale.new(precision: precision)
964
+ Type::DecimalWithoutScale.new(precision: precision).freeze
913
965
  else
914
- Type::Decimal.new(precision: precision, scale: scale)
966
+ Type::Decimal.new(precision: precision, scale: scale).freeze
915
967
  end
916
968
  end
917
969
  end
@@ -919,7 +971,7 @@ module ActiveRecord
919
971
  def register_class_with_limit(mapping, key, klass)
920
972
  mapping.register_type(key) do |*args|
921
973
  limit = extract_limit(args.last)
922
- klass.new(limit: limit)
974
+ klass.new(limit: limit).freeze
923
975
  end
924
976
  end
925
977
 
@@ -1080,7 +1132,7 @@ module ActiveRecord
1080
1132
  end
1081
1133
 
1082
1134
  def reconnect
1083
- raise NotImplementedError
1135
+ raise NotImplementedError.new("#{self.class} should define `reconnect` to implement adapter-specific logic for reconnecting to the database")
1084
1136
  end
1085
1137
 
1086
1138
  # Returns a raw connection for internal use with methods that are known
@@ -1131,7 +1183,7 @@ module ActiveRecord
1131
1183
  active_record_error
1132
1184
  end
1133
1185
 
1134
- def log(sql, name = "SQL", binds = [], type_casted_binds = [], async: false, &block) # :doc:
1186
+ def log(sql, name = "SQL", binds = [], type_casted_binds = [], async: false, allow_retry: false, &block) # :doc:
1135
1187
  instrumenter.instrument(
1136
1188
  "sql.active_record",
1137
1189
  sql: sql,
@@ -1139,8 +1191,10 @@ module ActiveRecord
1139
1191
  binds: binds,
1140
1192
  type_casted_binds: type_casted_binds,
1141
1193
  async: async,
1194
+ allow_retry: allow_retry,
1142
1195
  connection: self,
1143
1196
  transaction: current_transaction.user_transaction.presence,
1197
+ affected_rows: 0,
1144
1198
  row_count: 0,
1145
1199
  &block
1146
1200
  )
@@ -1215,7 +1269,7 @@ module ActiveRecord
1215
1269
 
1216
1270
  def attempt_configure_connection
1217
1271
  configure_connection
1218
- rescue
1272
+ rescue Exception # Need to handle things such as Timeout::ExitException
1219
1273
  disconnect!
1220
1274
  raise
1221
1275
  end
@@ -42,7 +42,7 @@ module ActiveRecord
42
42
  date: { name: "date" },
43
43
  binary: { name: "blob" },
44
44
  blob: { name: "blob" },
45
- boolean: { name: "tinyint", limit: 1 },
45
+ boolean: { name: "boolean" },
46
46
  json: { name: "json" },
47
47
  }
48
48
 
@@ -81,6 +81,10 @@ module ActiveRecord
81
81
 
82
82
  find_cmd_and_exec(ActiveRecord.database_cli[:mysql], *args)
83
83
  end
84
+
85
+ def native_database_types # :nodoc:
86
+ NATIVE_DATABASE_TYPES
87
+ end
84
88
  end
85
89
 
86
90
  def get_database_version # :nodoc:
@@ -178,6 +182,16 @@ module ActiveRecord
178
182
  supports_insert_returning? ? column.auto_populated? : column.auto_increment?
179
183
  end
180
184
 
185
+ # See https://dev.mysql.com/doc/refman/8.0/en/invisible-indexes.html for more details on MySQL feature.
186
+ # See https://mariadb.com/kb/en/ignored-indexes/ for more details on the MariaDB feature.
187
+ def supports_disabling_indexes?
188
+ if mariadb?
189
+ database_version >= "10.6.0"
190
+ else
191
+ database_version >= "8.0.0"
192
+ end
193
+ end
194
+
181
195
  def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
182
196
  query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
183
197
  end
@@ -186,10 +200,6 @@ module ActiveRecord
186
200
  query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
187
201
  end
188
202
 
189
- def native_database_types
190
- NATIVE_DATABASE_TYPES
191
- end
192
-
193
203
  def index_algorithms
194
204
  {
195
205
  default: "ALGORITHM = DEFAULT",
@@ -457,6 +467,24 @@ module ActiveRecord
457
467
  CreateIndexDefinition.new(index, algorithm)
458
468
  end
459
469
 
470
+ def enable_index(table_name, index_name) # :nodoc:
471
+ raise NotImplementedError unless supports_disabling_indexes?
472
+
473
+ query = <<~SQL
474
+ ALTER TABLE #{quote_table_name(table_name)} ALTER INDEX #{index_name} #{mariadb? ? "NOT IGNORED" : "VISIBLE"}
475
+ SQL
476
+ execute(query)
477
+ end
478
+
479
+ def disable_index(table_name, index_name) # :nodoc:
480
+ raise NotImplementedError unless supports_disabling_indexes?
481
+
482
+ query = <<~SQL
483
+ ALTER TABLE #{quote_table_name(table_name)} ALTER INDEX #{index_name} #{mariadb? ? "IGNORED" : "INVISIBLE"}
484
+ SQL
485
+ execute(query)
486
+ end
487
+
460
488
  def add_sql_comment!(sql, comment) # :nodoc:
461
489
  sql << " COMMENT #{quote(comment)}" if comment.present?
462
490
  sql
@@ -732,12 +760,12 @@ module ActiveRecord
732
760
  m.alias_type %r(bit)i, "binary"
733
761
  end
734
762
 
735
- def register_integer_type(mapping, key, **options)
763
+ def register_integer_type(mapping, key, limit:)
736
764
  mapping.register_type(key) do |sql_type|
737
765
  if /\bunsigned\b/.match?(sql_type)
738
- Type::UnsignedInteger.new(**options)
766
+ Type::UnsignedInteger.new(limit: limit)
739
767
  else
740
- Type::Integer.new(**options)
768
+ Type::Integer.new(limit: limit)
741
769
  end
742
770
  end
743
771
  end
@@ -767,13 +795,13 @@ module ActiveRecord
767
795
  end
768
796
  end
769
797
 
770
- def handle_warnings(sql)
798
+ def handle_warnings(_initial_result, sql)
771
799
  return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
772
800
 
773
801
  warning_count = @raw_connection.warning_count
774
802
  result = @raw_connection.query("SHOW WARNINGS")
775
803
  result = [
776
- ["Warning", nil, "Query had warning_count=#{warning_count} but SHOW WARNINGS did not return the warnings. Check MySQL logs or database configuration."],
804
+ ["Warning", nil, "Query had warning_count=#{warning_count} but `SHOW WARNINGS` did not return the warnings. Check MySQL logs or database configuration."],
777
805
  ] if result.count == 0
778
806
  result.each do |level, code, message|
779
807
  warning = SQLWarning.new(message, code, level, sql, @pool)
@@ -810,6 +838,8 @@ module ActiveRecord
810
838
  CR_SERVER_LOST = 2013
811
839
  ER_QUERY_TIMEOUT = 3024
812
840
  ER_FK_INCOMPATIBLE_COLUMNS = 3780
841
+ ER_CHECK_CONSTRAINT_VIOLATED = 3819
842
+ ER_CONSTRAINT_FAILED = 4025
813
843
  ER_CLIENT_INTERACTION_TIMEOUT = 4031
814
844
 
815
845
  def translate_exception(exception, message:, sql:, binds:)
@@ -842,6 +872,8 @@ module ActiveRecord
842
872
  RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
843
873
  when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
844
874
  NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
875
+ when ER_CHECK_CONSTRAINT_VIOLATED, ER_CONSTRAINT_FAILED
876
+ CheckViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
845
877
  when ER_LOCK_DEADLOCK
846
878
  Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
847
879
  when ER_LOCK_WAIT_TIMEOUT
@@ -960,7 +992,7 @@ module ActiveRecord
960
992
  end
961
993
 
962
994
  def column_definitions(table_name) # :nodoc:
963
- internal_exec_query("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA")
995
+ internal_exec_query("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA", allow_retry: true)
964
996
  end
965
997
 
966
998
  def create_table_info(table_name) # :nodoc:
@@ -17,16 +17,22 @@ module ActiveRecord
17
17
  # +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
18
18
  # +sql_type_metadata+ is various information about the type of the column
19
19
  # +null+ determines if this column allows +NULL+ values.
20
- def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, **)
20
+ def initialize(name, cast_type, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, **)
21
21
  @name = name.freeze
22
+ @cast_type = cast_type
22
23
  @sql_type_metadata = sql_type_metadata
23
24
  @null = null
24
- @default = default
25
+ @default = default.nil? || cast_type.mutable? ? default : cast_type.deserialize(default)
25
26
  @default_function = default_function
26
27
  @collation = collation
27
28
  @comment = comment
28
29
  end
29
30
 
31
+ def fetch_cast_type(connection) # :nodoc:
32
+ # TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
33
+ @cast_type || connection.lookup_cast_type(sql_type)
34
+ end
35
+
30
36
  def has_default?
31
37
  !default.nil? || default_function
32
38
  end
@@ -45,6 +51,7 @@ module ActiveRecord
45
51
 
46
52
  def init_with(coder)
47
53
  @name = coder["name"]
54
+ @cast_type = coder["cast_type"]
48
55
  @sql_type_metadata = coder["sql_type_metadata"]
49
56
  @null = coder["null"]
50
57
  @default = coder["default"]
@@ -55,6 +62,7 @@ module ActiveRecord
55
62
 
56
63
  def encode_with(coder)
57
64
  coder["name"] = @name
65
+ coder["cast_type"] = @cast_type
58
66
  coder["sql_type_metadata"] = @sql_type_metadata
59
67
  coder["null"] = @null
60
68
  coder["default"] = @default
@@ -75,6 +83,7 @@ module ActiveRecord
75
83
  def ==(other)
76
84
  other.is_a?(Column) &&
77
85
  name == other.name &&
86
+ cast_type == other.cast_type &&
78
87
  default == other.default &&
79
88
  sql_type_metadata == other.sql_type_metadata &&
80
89
  null == other.null &&
@@ -88,6 +97,7 @@ module ActiveRecord
88
97
  Column.hash ^
89
98
  name.hash ^
90
99
  name.encoding.hash ^
100
+ cast_type.hash ^
91
101
  default.hash ^
92
102
  sql_type_metadata.hash ^
93
103
  null.hash ^
@@ -100,11 +110,14 @@ module ActiveRecord
100
110
  false
101
111
  end
102
112
 
113
+ protected
114
+ attr_reader :cast_type
115
+
103
116
  private
104
117
  def deduplicated
105
118
  @name = -name
106
119
  @sql_type_metadata = sql_type_metadata.deduplicate if sql_type_metadata
107
- @default = -default if default
120
+ @default = -default if String === default
108
121
  @default_function = -default_function if default_function
109
122
  @collation = -collation if collation
110
123
  @comment = -comment if comment
@@ -114,7 +127,7 @@ module ActiveRecord
114
127
 
115
128
  class NullColumn < Column
116
129
  def initialize(name, **)
117
- super(name, nil)
130
+ super(name, nil, nil)
118
131
  end
119
132
  end
120
133
  end
@@ -45,16 +45,16 @@ module ActiveRecord
45
45
  end
46
46
  end
47
47
 
48
+ def default_insert_value(column) # :nodoc:
49
+ super unless column.auto_increment?
50
+ end
51
+
48
52
  private
49
53
  # https://mariadb.com/kb/en/analyze-statement/
50
54
  def analyze_without_explain?
51
55
  mariadb? && database_version >= "10.1.0"
52
56
  end
53
57
 
54
- def default_insert_value(column)
55
- super unless column.auto_increment?
56
- end
57
-
58
58
  def returning_column_values(result)
59
59
  if supports_insert_returning?
60
60
  result.rows.first
@@ -49,6 +49,8 @@ module ActiveRecord
49
49
  sql << "USING #{o.using}" if o.using
50
50
  sql << "ON #{quote_table_name(o.table)}" if create
51
51
  sql << "(#{quoted_columns(o)})"
52
+ sql << "INVISIBLE" if o.disabled? && !mariadb?
53
+ sql << "IGNORED" if o.disabled? && mariadb?
52
54
 
53
55
  add_sql_comment!(sql.join(" "), o.comment)
54
56
  end
@@ -5,6 +5,7 @@ module ActiveRecord
5
5
  module MySQL
6
6
  module ColumnMethods
7
7
  extend ActiveSupport::Concern
8
+ extend ConnectionAdapters::ColumnMethods::ClassMethods
8
9
 
9
10
  ##
10
11
  # :method: blob
@@ -42,12 +43,26 @@ module ActiveRecord
42
43
  # :method: unsigned_bigint
43
44
  # :call-seq: unsigned_bigint(*names, **options)
44
45
 
45
- included do
46
- define_column_methods :blob, :tinyblob, :mediumblob, :longblob,
47
- :tinytext, :mediumtext, :longtext, :unsigned_integer, :unsigned_bigint,
48
- :unsigned_float, :unsigned_decimal
46
+ define_column_methods :blob, :tinyblob, :mediumblob, :longblob,
47
+ :tinytext, :mediumtext, :longtext, :unsigned_integer, :unsigned_bigint
48
+ end
49
+
50
+ # = Active Record MySQL Adapter \Index Definition
51
+ class IndexDefinition < ActiveRecord::ConnectionAdapters::IndexDefinition
52
+ attr_accessor :enabled
53
+
54
+ def initialize(*args, **kwargs)
55
+ @enabled = kwargs.key?(:enabled) ? kwargs.delete(:enabled) : true
56
+ super
57
+ end
49
58
 
50
- deprecate :unsigned_float, :unsigned_decimal, deprecator: ActiveRecord.deprecator
59
+ def defined_for?(columns = nil, name: nil, unique: nil, valid: nil, include: nil, nulls_not_distinct: nil, enabled: nil, **options)
60
+ super(columns, name:, unique:, valid:, include:, nulls_not_distinct:, **options) &&
61
+ (enabled.nil? || self.enabled == enabled)
62
+ end
63
+
64
+ def disabled?
65
+ !@enabled
51
66
  end
52
67
  end
53
68
 
@@ -100,6 +115,28 @@ module ActiveRecord
100
115
  # = Active Record MySQL Adapter \Table
101
116
  class Table < ActiveRecord::ConnectionAdapters::Table
102
117
  include ColumnMethods
118
+
119
+ # Enables an index to be used by query optimizers.
120
+ #
121
+ # t.enable_index(:email)
122
+ #
123
+ # Note: only supported by MySQL version 8.0.0 and greater, and MariaDB version 10.6.0 and greater.
124
+ #
125
+ # See {connection.enable_index}[rdoc-ref:SchemaStatements#enable_index]
126
+ def enable_index(index_name)
127
+ @base.enable_index(name, index_name)
128
+ end
129
+
130
+ # Disables an index not to be used by query optimizers.
131
+ #
132
+ # t.disable_index(:email)
133
+ #
134
+ # Note: only supported by MySQL version 8.0.0 and greater, and MariaDB version 10.6.0 and greater.
135
+ #
136
+ # See {connection.disable_index}[rdoc-ref:SchemaStatements#disable_index]
137
+ def disable_index(index_name)
138
+ @base.disable_index(name, index_name)
139
+ end
103
140
  end
104
141
  end
105
142
  end