activerecord 7.1.6 → 7.2.3

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 (193) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +839 -2248
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +15 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +16 -8
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/errors.rb +265 -0
  17. data/lib/active_record/associations/has_many_association.rb +1 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +7 -1
  19. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  20. data/lib/active_record/associations/nested_error.rb +47 -0
  21. data/lib/active_record/associations/preloader/association.rb +2 -1
  22. data/lib/active_record/associations/preloader/branch.rb +7 -1
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  24. data/lib/active_record/associations/singular_association.rb +6 -0
  25. data/lib/active_record/associations/through_association.rb +1 -1
  26. data/lib/active_record/associations.rb +59 -292
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  29. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  30. data/lib/active_record/attribute_methods/read.rb +1 -13
  31. data/lib/active_record/attribute_methods/serialization.rb +5 -25
  32. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  33. data/lib/active_record/attribute_methods.rb +51 -60
  34. data/lib/active_record/attributes.rb +93 -68
  35. data/lib/active_record/autosave_association.rb +25 -32
  36. data/lib/active_record/base.rb +4 -5
  37. data/lib/active_record/callbacks.rb +1 -1
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +294 -72
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +201 -75
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +6 -2
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +18 -6
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +46 -44
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +53 -15
  49. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  50. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +6 -0
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +19 -18
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -23
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  56. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  58. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  59. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +30 -8
  60. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
  61. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
  62. data/lib/active_record/connection_adapters/postgresql_adapter.rb +36 -26
  63. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  64. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  65. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  66. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
  67. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  68. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +133 -78
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
  74. data/lib/active_record/connection_adapters.rb +121 -0
  75. data/lib/active_record/connection_handling.rb +68 -49
  76. data/lib/active_record/core.rb +112 -44
  77. data/lib/active_record/counter_cache.rb +19 -10
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  79. data/lib/active_record/database_configurations/database_config.rb +19 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +38 -34
  81. data/lib/active_record/database_configurations/url_config.rb +20 -1
  82. data/lib/active_record/database_configurations.rb +1 -1
  83. data/lib/active_record/delegated_type.rb +42 -18
  84. data/lib/active_record/dynamic_matchers.rb +2 -2
  85. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  86. data/lib/active_record/encryption/encrypted_attribute_type.rb +25 -5
  87. data/lib/active_record/encryption/encryptor.rb +35 -19
  88. data/lib/active_record/encryption/key_provider.rb +1 -1
  89. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  90. data/lib/active_record/encryption/message_serializer.rb +4 -0
  91. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  93. data/lib/active_record/enum.rb +31 -13
  94. data/lib/active_record/errors.rb +49 -23
  95. data/lib/active_record/explain.rb +13 -24
  96. data/lib/active_record/fixture_set/table_row.rb +19 -2
  97. data/lib/active_record/fixtures.rb +37 -31
  98. data/lib/active_record/future_result.rb +8 -4
  99. data/lib/active_record/gem_version.rb +2 -2
  100. data/lib/active_record/inheritance.rb +4 -2
  101. data/lib/active_record/insert_all.rb +18 -15
  102. data/lib/active_record/integration.rb +4 -1
  103. data/lib/active_record/internal_metadata.rb +48 -34
  104. data/lib/active_record/locking/optimistic.rb +7 -6
  105. data/lib/active_record/log_subscriber.rb +0 -21
  106. data/lib/active_record/message_pack.rb +1 -1
  107. data/lib/active_record/migration/command_recorder.rb +2 -3
  108. data/lib/active_record/migration/compatibility.rb +5 -3
  109. data/lib/active_record/migration/default_strategy.rb +4 -5
  110. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  111. data/lib/active_record/migration.rb +87 -77
  112. data/lib/active_record/model_schema.rb +31 -68
  113. data/lib/active_record/nested_attributes.rb +11 -3
  114. data/lib/active_record/normalization.rb +3 -7
  115. data/lib/active_record/persistence.rb +30 -352
  116. data/lib/active_record/query_cache.rb +19 -8
  117. data/lib/active_record/query_logs.rb +19 -0
  118. data/lib/active_record/querying.rb +25 -13
  119. data/lib/active_record/railtie.rb +39 -57
  120. data/lib/active_record/railties/controller_runtime.rb +13 -4
  121. data/lib/active_record/railties/databases.rake +42 -44
  122. data/lib/active_record/reflection.rb +98 -36
  123. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  124. data/lib/active_record/relation/batches.rb +14 -8
  125. data/lib/active_record/relation/calculations.rb +127 -89
  126. data/lib/active_record/relation/delegation.rb +8 -11
  127. data/lib/active_record/relation/finder_methods.rb +26 -12
  128. data/lib/active_record/relation/merger.rb +4 -6
  129. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  130. data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
  131. data/lib/active_record/relation/predicate_builder.rb +3 -3
  132. data/lib/active_record/relation/query_attribute.rb +1 -1
  133. data/lib/active_record/relation/query_methods.rb +238 -65
  134. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  135. data/lib/active_record/relation/spawn_methods.rb +2 -18
  136. data/lib/active_record/relation/where_clause.rb +15 -21
  137. data/lib/active_record/relation.rb +508 -74
  138. data/lib/active_record/result.rb +31 -44
  139. data/lib/active_record/runtime_registry.rb +39 -0
  140. data/lib/active_record/sanitization.rb +24 -19
  141. data/lib/active_record/schema.rb +8 -6
  142. data/lib/active_record/schema_dumper.rb +48 -20
  143. data/lib/active_record/schema_migration.rb +30 -14
  144. data/lib/active_record/scoping/named.rb +1 -0
  145. data/lib/active_record/secure_token.rb +3 -3
  146. data/lib/active_record/signed_id.rb +27 -7
  147. data/lib/active_record/statement_cache.rb +7 -7
  148. data/lib/active_record/table_metadata.rb +1 -10
  149. data/lib/active_record/tasks/database_tasks.rb +69 -41
  150. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  151. data/lib/active_record/tasks/postgresql_database_tasks.rb +8 -1
  152. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  153. data/lib/active_record/test_fixtures.rb +86 -89
  154. data/lib/active_record/testing/query_assertions.rb +121 -0
  155. data/lib/active_record/timestamp.rb +2 -2
  156. data/lib/active_record/token_for.rb +22 -12
  157. data/lib/active_record/touch_later.rb +1 -1
  158. data/lib/active_record/transaction.rb +132 -0
  159. data/lib/active_record/transactions.rb +73 -15
  160. data/lib/active_record/translation.rb +0 -2
  161. data/lib/active_record/type/serialized.rb +1 -3
  162. data/lib/active_record/type_caster/connection.rb +4 -4
  163. data/lib/active_record/validations/associated.rb +9 -3
  164. data/lib/active_record/validations/uniqueness.rb +15 -10
  165. data/lib/active_record/validations.rb +4 -1
  166. data/lib/active_record.rb +148 -39
  167. data/lib/arel/alias_predication.rb +1 -1
  168. data/lib/arel/collectors/bind.rb +3 -1
  169. data/lib/arel/collectors/composite.rb +7 -0
  170. data/lib/arel/collectors/sql_string.rb +1 -1
  171. data/lib/arel/collectors/substitute_binds.rb +1 -1
  172. data/lib/arel/crud.rb +2 -0
  173. data/lib/arel/delete_manager.rb +5 -0
  174. data/lib/arel/nodes/binary.rb +0 -6
  175. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  176. data/lib/arel/nodes/delete_statement.rb +4 -2
  177. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  178. data/lib/arel/nodes/node.rb +4 -3
  179. data/lib/arel/nodes/sql_literal.rb +7 -0
  180. data/lib/arel/nodes/update_statement.rb +4 -2
  181. data/lib/arel/nodes.rb +2 -2
  182. data/lib/arel/predications.rb +1 -1
  183. data/lib/arel/select_manager.rb +7 -3
  184. data/lib/arel/tree_manager.rb +3 -2
  185. data/lib/arel/update_manager.rb +7 -1
  186. data/lib/arel/visitors/dot.rb +3 -0
  187. data/lib/arel/visitors/mysql.rb +9 -4
  188. data/lib/arel/visitors/postgresql.rb +1 -12
  189. data/lib/arel/visitors/sqlite.rb +25 -0
  190. data/lib/arel/visitors/to_sql.rb +31 -16
  191. data/lib/arel.rb +7 -3
  192. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  193. metadata +16 -10
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
  # and +:limit+ options, etc.
24
24
  #
25
25
  # All the concrete database adapters follow the interface laid down in this class.
26
- # {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling#connection] returns an AbstractAdapter object, which
26
+ # {ActiveRecord::Base.lease_connection}[rdoc-ref:ConnectionHandling#lease_connection] returns an AbstractAdapter object, which
27
27
  # you can use.
28
28
  #
29
29
  # Most of the methods in the adapter are useful during migrations. Most
@@ -43,14 +43,13 @@ module ActiveRecord
43
43
 
44
44
  attr_reader :pool
45
45
  attr_reader :visitor, :owner, :logger, :lock
46
+ attr_accessor :pinned # :nodoc:
46
47
  alias :in_use? :owner
47
48
 
48
49
  def pool=(value)
49
50
  return if value.eql?(@pool)
50
51
  @schema_cache = nil
51
52
  @pool = value
52
-
53
- @pool.schema_reflection.load!(self) if ActiveRecord.lazily_load_schema_cache
54
53
  end
55
54
 
56
55
  set_callback :checkin, :after, :enable_lazy_transactions!
@@ -136,7 +135,7 @@ module ActiveRecord
136
135
  @logger = ActiveRecord::Base.logger
137
136
 
138
137
  if deprecated_logger || deprecated_connection_options || deprecated_config
139
- raise ArgumentError, "when initializing an ActiveRecord adapter with a config hash, that should be the only argument"
138
+ raise ArgumentError, "when initializing an Active Record adapter with a config hash, that should be the only argument"
140
139
  end
141
140
  else
142
141
  # Soft-deprecated for now; we'll probably warn in future.
@@ -153,6 +152,7 @@ module ActiveRecord
153
152
  end
154
153
 
155
154
  @owner = nil
155
+ @pinned = false
156
156
  @instrumenter = ActiveSupport::Notifications.instrumenter
157
157
  @pool = ActiveRecord::ConnectionAdapters::NullPool.new
158
158
  @idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
@@ -171,22 +171,24 @@ module ActiveRecord
171
171
  @default_timezone = self.class.validate_default_timezone(@config[:default_timezone])
172
172
 
173
173
  @raw_connection_dirty = false
174
+ @last_activity = nil
174
175
  @verified = false
175
176
  end
176
177
 
177
- THREAD_LOCK = ActiveSupport::Concurrency::ThreadLoadInterlockAwareMonitor.new
178
- private_constant :THREAD_LOCK
178
+ def inspect # :nodoc:
179
+ name_field = " name=#{pool.db_config.name.inspect}" unless pool.db_config.name == "primary"
180
+ shard_field = " shard=#{shard.inspect}" unless shard == :default
179
181
 
180
- FIBER_LOCK = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
181
- private_constant :FIBER_LOCK
182
+ "#<#{self.class.name}:#{'%#016x' % (object_id << 1)} env_name=#{pool.db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
183
+ end
182
184
 
183
185
  def lock_thread=(lock_thread) # :nodoc:
184
186
  @lock =
185
187
  case lock_thread
186
188
  when Thread
187
- THREAD_LOCK
189
+ ActiveSupport::Concurrency::ThreadLoadInterlockAwareMonitor.new
188
190
  when Fiber
189
- FIBER_LOCK
191
+ ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
190
192
  else
191
193
  ActiveSupport::Concurrency::NullLock
192
194
  end
@@ -215,14 +217,14 @@ module ActiveRecord
215
217
  @config[:replica] || false
216
218
  end
217
219
 
218
- def use_metadata_table?
219
- @config.fetch(:use_metadata_table, true)
220
- end
221
-
222
220
  def connection_retries
223
221
  (@config[:connection_retries] || 1).to_i
224
222
  end
225
223
 
224
+ def verify_timeout
225
+ (@config[:verify_timeout] || 2).to_i
226
+ end
227
+
226
228
  def retry_deadline
227
229
  if @config[:retry_deadline]
228
230
  @config[:retry_deadline].to_f
@@ -246,22 +248,6 @@ module ActiveRecord
246
248
  connection_class.current_preventing_writes
247
249
  end
248
250
 
249
- def migrations_paths # :nodoc:
250
- @config[:migrations_paths] || Migrator.migrations_paths
251
- end
252
-
253
- def migration_context # :nodoc:
254
- MigrationContext.new(migrations_paths, schema_migration, internal_metadata)
255
- end
256
-
257
- def schema_migration # :nodoc:
258
- SchemaMigration.new(self)
259
- end
260
-
261
- def internal_metadata # :nodoc:
262
- InternalMetadata.new(self)
263
- end
264
-
265
251
  def prepared_statements?
266
252
  @prepared_statements && !prepared_statements_disabled_cache.include?(object_id)
267
253
  end
@@ -327,7 +313,7 @@ module ActiveRecord
327
313
  end
328
314
 
329
315
  def schema_cache
330
- @schema_cache ||= BoundSchemaReflection.new(@pool.schema_reflection, self)
316
+ @pool.schema_cache || (@schema_cache ||= BoundSchemaReflection.for_lone_connection(@pool.schema_reflection, self))
331
317
  end
332
318
 
333
319
  # this method must only be called while holding connection pool's mutex
@@ -365,6 +351,13 @@ module ActiveRecord
365
351
  Process.clock_gettime(Process::CLOCK_MONOTONIC) - @idle_since
366
352
  end
367
353
 
354
+ # Seconds since this connection last communicated with the server
355
+ def seconds_since_last_activity # :nodoc:
356
+ if @raw_connection && @last_activity
357
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) - @last_activity
358
+ end
359
+ end
360
+
368
361
  def unprepared_statement
369
362
  cache = prepared_statements_disabled_cache.add?(object_id) if @prepared_statements
370
363
  yield
@@ -580,7 +573,7 @@ module ActiveRecord
580
573
  end
581
574
 
582
575
  def return_value_after_insert?(column) # :nodoc:
583
- column.auto_incremented_by_db?
576
+ column.auto_populated?
584
577
  end
585
578
 
586
579
  def async_enabled? # :nodoc:
@@ -651,15 +644,6 @@ module ActiveRecord
651
644
  yield
652
645
  end
653
646
 
654
- # Override to check all foreign key constraints in a database.
655
- def all_foreign_keys_valid?
656
- check_all_foreign_keys_valid!
657
- true
658
- rescue ActiveRecord::StatementInvalid
659
- false
660
- end
661
- deprecate :all_foreign_keys_valid?, deprecator: ActiveRecord.deprecator
662
-
663
647
  # Override to check all foreign key constraints in a database.
664
648
  # The adapter should raise a +ActiveRecord::StatementInvalid+ if foreign key
665
649
  # constraints are not met.
@@ -668,6 +652,13 @@ module ActiveRecord
668
652
 
669
653
  # CONNECTION MANAGEMENT ====================================
670
654
 
655
+ # Checks whether the connection to the database was established. This doesn't
656
+ # include checking whether the database is actually capable of responding, i.e.
657
+ # whether the connection is stale.
658
+ def connected?
659
+ !@raw_connection.nil?
660
+ end
661
+
671
662
  # Checks whether the connection to the database is still active. This includes
672
663
  # checking whether the database is actually capable of responding, i.e. whether
673
664
  # the connection isn't stale.
@@ -686,6 +677,7 @@ module ActiveRecord
686
677
 
687
678
  enable_lazy_transactions!
688
679
  @raw_connection_dirty = false
680
+ @last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
689
681
  @verified = true
690
682
 
691
683
  reset_transaction(restore: restore_transactions) do
@@ -705,6 +697,7 @@ module ActiveRecord
705
697
  end
706
698
  end
707
699
 
700
+ @last_activity = nil
708
701
  @verified = false
709
702
 
710
703
  raise translated_exception
@@ -779,6 +772,7 @@ module ActiveRecord
779
772
  @raw_connection = @unconfigured_connection
780
773
  @unconfigured_connection = nil
781
774
  attempt_configure_connection
775
+ @last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
782
776
  @verified = true
783
777
  return
784
778
  end
@@ -867,7 +861,7 @@ module ActiveRecord
867
861
  end
868
862
 
869
863
  def database_version # :nodoc:
870
- schema_cache.database_version
864
+ pool.server_version(self)
871
865
  end
872
866
 
873
867
  def check_version # :nodoc:
@@ -878,7 +872,7 @@ module ActiveRecord
878
872
  # numbered migration that has been executed, or 0 if no schema
879
873
  # information is present / the database is empty.
880
874
  def schema_version
881
- migration_context.current_version
875
+ pool.migration_context.current_version
882
876
  end
883
877
 
884
878
  class << self
@@ -1008,6 +1002,9 @@ module ActiveRecord
1008
1002
  if @verified
1009
1003
  # Cool, we're confident the connection's ready to use. (Note this might have
1010
1004
  # become true during the above #materialize_transactions.)
1005
+ elsif (last_activity = seconds_since_last_activity) && last_activity < verify_timeout
1006
+ # We haven't actually verified the connection since we acquired it, but it
1007
+ # has been used very recently. We're going to assume it's still okay.
1011
1008
  elsif reconnectable
1012
1009
  if allow_retry
1013
1010
  # Not sure about the connection yet, but if anything goes wrong we can
@@ -1049,6 +1046,7 @@ module ActiveRecord
1049
1046
  # Barring a known-retryable error inside the query (regardless of
1050
1047
  # whether we were in a _position_ to retry it), we should infer that
1051
1048
  # there's likely a real problem with the connection.
1049
+ @last_activity = nil
1052
1050
  @verified = false
1053
1051
  end
1054
1052
 
@@ -1063,6 +1061,7 @@ module ActiveRecord
1063
1061
  # `with_raw_connection` block only when the block is guaranteed to
1064
1062
  # exercise the raw connection.
1065
1063
  def verified!
1064
+ @last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
1066
1065
  @verified = true
1067
1066
  end
1068
1067
 
@@ -1148,6 +1147,8 @@ module ActiveRecord
1148
1147
  statement_name: statement_name,
1149
1148
  async: async,
1150
1149
  connection: self,
1150
+ transaction: current_transaction.user_transaction.presence,
1151
+ row_count: 0,
1151
1152
  &block
1152
1153
  )
1153
1154
  rescue ActiveRecord::StatementInvalid => ex
@@ -1211,7 +1212,7 @@ module ActiveRecord
1211
1212
  #
1212
1213
  # This is an internal hook to make possible connection adapters to build
1213
1214
  # custom result objects with connection-specific data.
1214
- def build_result(columns:, rows:, column_types: {})
1215
+ def build_result(columns:, rows:, column_types: nil)
1215
1216
  ActiveRecord::Result.new(columns, rows, column_types)
1216
1217
  end
1217
1218
 
@@ -1223,6 +1224,7 @@ module ActiveRecord
1223
1224
  # Implementations may assume this method will only be called while
1224
1225
  # holding @lock (or from #initialize).
1225
1226
  def configure_connection
1227
+ check_version
1226
1228
  end
1227
1229
 
1228
1230
  def attempt_configure_connection
@@ -170,6 +170,14 @@ module ActiveRecord
170
170
  true
171
171
  end
172
172
 
173
+ def supports_insert_returning?
174
+ mariadb? && database_version >= "10.5.0"
175
+ end
176
+
177
+ def return_value_after_insert?(column) # :nodoc:
178
+ supports_insert_returning? ? column.auto_populated? : column.auto_increment?
179
+ end
180
+
173
181
  def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
174
182
  query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
175
183
  end
@@ -214,7 +222,7 @@ module ActiveRecord
214
222
  update("SET FOREIGN_KEY_CHECKS = 0")
215
223
  yield
216
224
  ensure
217
- update("SET FOREIGN_KEY_CHECKS = #{old}")
225
+ update("SET FOREIGN_KEY_CHECKS = #{old}") if active?
218
226
  end
219
227
  end
220
228
 
@@ -225,12 +233,12 @@ module ActiveRecord
225
233
  # Mysql2Adapter doesn't have to free a result after using it, but we use this method
226
234
  # to write stuff in an abstract way without concerning ourselves about whether it
227
235
  # needs to be explicitly freed or not.
228
- def execute_and_free(sql, name = nil, async: false) # :nodoc:
236
+ def execute_and_free(sql, name = nil, async: false, allow_retry: false) # :nodoc:
229
237
  sql = transform_query(sql)
230
238
  check_if_write_query(sql)
231
239
 
232
240
  mark_transaction_written_if_write(sql)
233
- yield raw_execute(sql, name, async: async)
241
+ yield raw_execute(sql, name, async: async, allow_retry: allow_retry)
234
242
  end
235
243
 
236
244
  def begin_db_transaction # :nodoc:
@@ -410,7 +418,11 @@ module ActiveRecord
410
418
  type ||= column.sql_type
411
419
 
412
420
  unless options.key?(:default)
413
- options[:default] = column.default
421
+ options[:default] = if column.default_function
422
+ -> { column.default_function }
423
+ else
424
+ column.default
425
+ end
414
426
  end
415
427
 
416
428
  unless options.key?(:null)
@@ -635,22 +647,26 @@ module ActiveRecord
635
647
  end
636
648
 
637
649
  def build_insert_sql(insert) # :nodoc:
638
- no_op_column = quote_column_name(insert.keys.first)
650
+ # Can use any column as it will be assigned to itself.
651
+ no_op_column = quote_column_name(insert.keys.first) if insert.keys.first
639
652
 
640
653
  # MySQL 8.0.19 replaces `VALUES(<expression>)` clauses with row and column alias names, see https://dev.mysql.com/worklog/task/?id=6312 .
641
654
  # then MySQL 8.0.20 deprecates the `VALUES(<expression>)` see https://dev.mysql.com/worklog/task/?id=13325 .
642
655
  if supports_insert_raw_alias_syntax?
643
- values_alias = quote_table_name("#{insert.model.table_name}_values")
656
+ quoted_table_name = insert.model.quoted_table_name
657
+ values_alias = quote_table_name("#{insert.model.table_name.parameterize}_values")
644
658
  sql = +"INSERT #{insert.into} #{insert.values_list} AS #{values_alias}"
645
659
 
646
660
  if insert.skip_duplicates?
647
- sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{values_alias}.#{no_op_column}"
661
+ if no_op_column
662
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{quoted_table_name}.#{no_op_column}"
663
+ end
648
664
  elsif insert.update_duplicates?
649
665
  if insert.raw_update_sql?
650
666
  sql = +"INSERT #{insert.into} #{insert.values_list} ON DUPLICATE KEY UPDATE #{insert.raw_update_sql}"
651
667
  else
652
668
  sql << " ON DUPLICATE KEY UPDATE "
653
- sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column}<=>#{values_alias}.#{column}" }
669
+ sql << insert.touch_model_timestamps_unless { |column| "#{quoted_table_name}.#{column}<=>#{values_alias}.#{column}" }
654
670
  sql << insert.updatable_columns.map { |column| "#{column}=#{values_alias}.#{column}" }.join(",")
655
671
  end
656
672
  end
@@ -658,7 +674,9 @@ module ActiveRecord
658
674
  sql = +"INSERT #{insert.into} #{insert.values_list}"
659
675
 
660
676
  if insert.skip_duplicates?
661
- sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
677
+ if no_op_column
678
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
679
+ end
662
680
  elsif insert.update_duplicates?
663
681
  sql << " ON DUPLICATE KEY UPDATE "
664
682
  if insert.raw_update_sql?
@@ -670,12 +688,24 @@ module ActiveRecord
670
688
  end
671
689
  end
672
690
 
691
+ sql << " RETURNING #{insert.returning}" if insert.returning
673
692
  sql
674
693
  end
675
694
 
676
695
  def check_version # :nodoc:
677
696
  if database_version < "5.5.8"
678
- raise "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
697
+ raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
698
+ end
699
+ end
700
+
701
+ #--
702
+ # QUOTING ==================================================
703
+ #++
704
+
705
+ # Quotes strings for use in SQL input.
706
+ def quote_string(string)
707
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
708
+ connection.escape(string)
679
709
  end
680
710
  end
681
711
 
@@ -737,9 +767,7 @@ module ActiveRecord
737
767
 
738
768
  private
739
769
  def strip_whitespace_characters(expression)
740
- expression = expression.gsub(/\\n|\\\\/, "")
741
- expression = expression.gsub(/\s{2,}/, " ")
742
- expression
770
+ expression.gsub('\\\n', "").gsub("x0A", "").squish
743
771
  end
744
772
 
745
773
  def extended_type_map_key
@@ -754,7 +782,11 @@ module ActiveRecord
754
782
  return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
755
783
 
756
784
  @affected_rows_before_warnings = @raw_connection.affected_rows
785
+ warning_count = @raw_connection.warning_count
757
786
  result = @raw_connection.query("SHOW WARNINGS")
787
+ result = [
788
+ ["Warning", nil, "Query had warning_count=#{warning_count} but ‘SHOW WARNINGS’ did not return the warnings. Check MySQL logs or database configuration."],
789
+ ] if result.count == 0
758
790
  result.each do |level, code, message|
759
791
  warning = SQLWarning.new(message, code, level, sql, @pool)
760
792
  next if warning_ignored?(warning)
@@ -776,6 +808,7 @@ module ActiveRecord
776
808
  ER_DB_CREATE_EXISTS = 1007
777
809
  ER_FILSORT_ABORT = 1028
778
810
  ER_DUP_ENTRY = 1062
811
+ ER_SERVER_SHUTDOWN = 1053
779
812
  ER_NOT_NULL_VIOLATION = 1048
780
813
  ER_NO_REFERENCED_ROW = 1216
781
814
  ER_ROW_IS_REFERENCED = 1217
@@ -804,7 +837,7 @@ module ActiveRecord
804
837
  else
805
838
  super
806
839
  end
807
- when ER_CONNECTION_KILLED, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
840
+ when ER_CONNECTION_KILLED, ER_SERVER_SHUTDOWN, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
808
841
  ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool)
809
842
  when ER_DB_CREATE_EXISTS
810
843
  DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
@@ -894,6 +927,7 @@ module ActiveRecord
894
927
  end
895
928
 
896
929
  def configure_connection
930
+ super
897
931
  variables = @config.fetch(:variables, {}).stringify_keys
898
932
 
899
933
  # Increase timeout so the server doesn't disconnect us.
@@ -1001,7 +1035,11 @@ module ActiveRecord
1001
1035
  end
1002
1036
 
1003
1037
  def version_string(full_version_string)
1004
- full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
1038
+ if full_version_string && matches = full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)
1039
+ matches[1]
1040
+ else
1041
+ raise DatabaseVersionError, "Unable to parse MySQL version from #{full_version_string.inspect}"
1042
+ end
1005
1043
  end
1006
1044
  end
1007
1045
  end
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
 
12
12
  # https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_current-timestamp
13
13
  # https://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-syntax.html
14
- HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)").freeze # :nodoc:
14
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)", retryable: true).freeze # :nodoc:
15
15
  private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
16
16
 
17
17
  def write_query?(sql) # :nodoc:
@@ -55,6 +55,14 @@ module ActiveRecord
55
55
  super unless column.auto_increment?
56
56
  end
57
57
 
58
+ def returning_column_values(result)
59
+ if supports_insert_returning?
60
+ result.rows.first
61
+ else
62
+ super
63
+ end
64
+ end
65
+
58
66
  def combine_multi_statements(total_sql)
59
67
  total_sql.each_with_object([]) do |sql, total_sql_chunks|
60
68
  previous_packet = total_sql_chunks.last
@@ -6,9 +6,52 @@ module ActiveRecord
6
6
  module ConnectionAdapters
7
7
  module MySQL
8
8
  module Quoting # :nodoc:
9
+ extend ActiveSupport::Concern
10
+
9
11
  QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
10
12
  QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
11
13
 
14
+ module ClassMethods # :nodoc:
15
+ def column_name_matcher
16
+ /
17
+ \A
18
+ (
19
+ (?:
20
+ # `table_name`.`column_name` | function(one or no argument)
21
+ ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
22
+ )
23
+ (?:(?:\s+AS)?\s+(?:\w+|`\w+`))?
24
+ )
25
+ (?:\s*,\s*\g<1>)*
26
+ \z
27
+ /ix
28
+ end
29
+
30
+ def column_name_with_order_matcher
31
+ /
32
+ \A
33
+ (
34
+ (?:
35
+ # `table_name`.`column_name` | function(one or no argument)
36
+ ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
37
+ )
38
+ (?:\s+COLLATE\s+(?:\w+|"\w+"))?
39
+ (?:\s+ASC|\s+DESC)?
40
+ )
41
+ (?:\s*,\s*\g<1>)*
42
+ \z
43
+ /ix
44
+ end
45
+
46
+ def quote_column_name(name)
47
+ QUOTED_COLUMN_NAMES[name] ||= "`#{name.to_s.gsub('`', '``')}`".freeze
48
+ end
49
+
50
+ def quote_table_name(name)
51
+ QUOTED_TABLE_NAMES[name] ||= "`#{name.to_s.gsub('`', '``').gsub(".", "`.`")}`".freeze
52
+ end
53
+ end
54
+
12
55
  def cast_bound_value(value)
13
56
  case value
14
57
  when Rational
@@ -21,22 +64,11 @@ module ActiveRecord
21
64
  "1"
22
65
  when false
23
66
  "0"
24
- when ActiveSupport::Duration
25
- warn_quote_duration_deprecated
26
- value.to_s
27
67
  else
28
68
  value
29
69
  end
30
70
  end
31
71
 
32
- def quote_column_name(name)
33
- QUOTED_COLUMN_NAMES[name] ||= "`#{super.gsub('`', '``')}`"
34
- end
35
-
36
- def quote_table_name(name)
37
- QUOTED_TABLE_NAMES[name] ||= super.gsub(".", "`.`").freeze
38
- end
39
-
40
72
  def unquoted_true
41
73
  1
42
74
  end
@@ -90,43 +122,6 @@ module ActiveRecord
90
122
  super
91
123
  end
92
124
  end
93
-
94
- def column_name_matcher
95
- COLUMN_NAME
96
- end
97
-
98
- def column_name_with_order_matcher
99
- COLUMN_NAME_WITH_ORDER
100
- end
101
-
102
- COLUMN_NAME = /
103
- \A
104
- (
105
- (?:
106
- # `table_name`.`column_name` | function(one or no argument)
107
- ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
108
- )
109
- (?:(?:\s+AS)?\s+(?:\w+|`\w+`))?
110
- )
111
- (?:\s*,\s*\g<1>)*
112
- \z
113
- /ix
114
-
115
- COLUMN_NAME_WITH_ORDER = /
116
- \A
117
- (
118
- (?:
119
- # `table_name`.`column_name` | function(one or no argument)
120
- ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
121
- )
122
- (?:\s+COLLATE\s+(?:\w+|"\w+"))?
123
- (?:\s+ASC|\s+DESC)?
124
- )
125
- (?:\s*,\s*\g<1>)*
126
- \z
127
- /ix
128
-
129
- private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
130
125
  end
131
126
  end
132
127
  end
@@ -68,6 +68,12 @@ module ActiveRecord
68
68
 
69
69
  IndexDefinition.new(*index, **options)
70
70
  end
71
+ rescue StatementInvalid => e
72
+ if e.message.match?(/Table '.+' doesn't exist/)
73
+ []
74
+ else
75
+ raise
76
+ end
71
77
  end
72
78
 
73
79
  def remove_column(table_name, column_name, type = nil, **options)
@@ -6,21 +6,16 @@ module ActiveRecord
6
6
  module DatabaseStatements
7
7
  # Returns an ActiveRecord::Result instance.
8
8
  def select_all(*, **) # :nodoc:
9
- result = nil
10
- with_raw_connection do |conn|
11
- result = if ExplainRegistry.collect? && prepared_statements
12
- unprepared_statement { super }
13
- else
14
- super
15
- end
16
- conn.abandon_results!
9
+ if ExplainRegistry.collect? && prepared_statements
10
+ unprepared_statement { super }
11
+ else
12
+ super
17
13
  end
18
- result
19
14
  end
20
15
 
21
- def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
16
+ def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
22
17
  if without_prepared_statement?(binds)
23
- execute_and_free(sql, name, async: async) do |result|
18
+ execute_and_free(sql, name, async: async, allow_retry: allow_retry) do |result|
24
19
  if result
25
20
  build_result(columns: result.fields, rows: result.to_a)
26
21
  else
@@ -28,7 +23,7 @@ module ActiveRecord
28
23
  end
29
24
  end
30
25
  else
31
- exec_stmt_and_free(sql, name, binds, cache_stmt: prepare, async: async) do |_, result|
26
+ exec_stmt_and_free(sql, name, binds, cache_stmt: prepare, async: async, allow_retry: allow_retry) do |_, result|
32
27
  if result
33
28
  build_result(columns: result.fields, rows: result.to_a)
34
29
  else
@@ -60,13 +55,16 @@ module ActiveRecord
60
55
  combine_multi_statements(statements).each do |statement|
61
56
  with_raw_connection do |conn|
62
57
  raw_execute(statement, name)
63
- conn.abandon_results!
64
58
  end
65
59
  end
66
60
  end
67
61
 
68
62
  def last_inserted_id(result)
69
- @raw_connection&.last_id
63
+ if supports_insert_returning?
64
+ super
65
+ else
66
+ @raw_connection&.last_id
67
+ end
70
68
  end
71
69
 
72
70
  def multi_statements_enabled?
@@ -94,18 +92,20 @@ module ActiveRecord
94
92
  end
95
93
 
96
94
  def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
97
- log(sql, name, async: async) do
95
+ log(sql, name, async: async) do |notification_payload|
98
96
  with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
99
97
  sync_timezone_changes(conn)
100
98
  result = conn.query(sql)
99
+ conn.abandon_results!
101
100
  verified!
102
101
  handle_warnings(sql)
102
+ notification_payload[:row_count] = result&.size || 0
103
103
  result
104
104
  end
105
105
  end
106
106
  end
107
107
 
108
- def exec_stmt_and_free(sql, name, binds, cache_stmt: false, async: false)
108
+ def exec_stmt_and_free(sql, name, binds, cache_stmt: false, async: false, allow_retry: false)
109
109
  sql = transform_query(sql)
110
110
  check_if_write_query(sql)
111
111
 
@@ -113,8 +113,8 @@ module ActiveRecord
113
113
 
114
114
  type_casted_binds = type_casted_binds(binds)
115
115
 
116
- log(sql, name, binds, type_casted_binds, async: async) do
117
- with_raw_connection do |conn|
116
+ log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
117
+ with_raw_connection(allow_retry: allow_retry) do |conn|
118
118
  sync_timezone_changes(conn)
119
119
 
120
120
  if cache_stmt
@@ -139,6 +139,7 @@ module ActiveRecord
139
139
  end
140
140
 
141
141
  ret = yield stmt, result
142
+ notification_payload[:row_count] = result&.size || 0
142
143
  result.free if result
143
144
  stmt.close unless cache_stmt
144
145
  ret