activerecord 7.1.3.4 → 7.2.2.1

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 (196) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +652 -2032
  3. data/README.rdoc +15 -15
  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 +25 -19
  7. data/lib/active_record/associations/association.rb +15 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +18 -11
  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 +11 -5
  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 +3 -3
  18. data/lib/active_record/associations/has_many_through_association.rb +7 -1
  19. data/lib/active_record/associations/has_one_association.rb +2 -2
  20. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  21. data/lib/active_record/associations/join_dependency.rb +10 -12
  22. data/lib/active_record/associations/nested_error.rb +47 -0
  23. data/lib/active_record/associations/preloader/association.rb +2 -1
  24. data/lib/active_record/associations/preloader/branch.rb +7 -1
  25. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  26. data/lib/active_record/associations/singular_association.rb +6 -0
  27. data/lib/active_record/associations/through_association.rb +1 -1
  28. data/lib/active_record/associations.rb +62 -289
  29. data/lib/active_record/attribute_assignment.rb +0 -2
  30. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  31. data/lib/active_record/attribute_methods/dirty.rb +2 -2
  32. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  33. data/lib/active_record/attribute_methods/read.rb +4 -16
  34. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  36. data/lib/active_record/attribute_methods/write.rb +3 -3
  37. data/lib/active_record/attribute_methods.rb +89 -58
  38. data/lib/active_record/attributes.rb +61 -47
  39. data/lib/active_record/autosave_association.rb +17 -31
  40. data/lib/active_record/base.rb +2 -3
  41. data/lib/active_record/callbacks.rb +1 -1
  42. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  44. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +270 -58
  45. data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
  46. data/lib/active_record/connection_adapters/abstract/query_cache.rb +190 -75
  47. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  48. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +23 -10
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +38 -59
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +73 -19
  53. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  54. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  55. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +8 -1
  56. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
  57. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
  58. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  59. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  60. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  61. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  62. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  63. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  64. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  65. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +18 -12
  66. data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
  67. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  68. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  69. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  70. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  71. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  72. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  73. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  74. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  75. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +127 -77
  76. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  77. data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
  78. data/lib/active_record/connection_adapters.rb +121 -0
  79. data/lib/active_record/connection_handling.rb +56 -41
  80. data/lib/active_record/core.rb +93 -40
  81. data/lib/active_record/counter_cache.rb +23 -10
  82. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  83. data/lib/active_record/database_configurations/database_config.rb +19 -4
  84. data/lib/active_record/database_configurations/hash_config.rb +44 -36
  85. data/lib/active_record/database_configurations/url_config.rb +20 -1
  86. data/lib/active_record/database_configurations.rb +1 -1
  87. data/lib/active_record/delegated_type.rb +30 -6
  88. data/lib/active_record/destroy_association_async_job.rb +1 -1
  89. data/lib/active_record/dynamic_matchers.rb +2 -2
  90. data/lib/active_record/encryption/encryptable_record.rb +3 -3
  91. data/lib/active_record/encryption/encrypted_attribute_type.rb +26 -6
  92. data/lib/active_record/encryption/encryptor.rb +18 -3
  93. data/lib/active_record/encryption/key_provider.rb +1 -1
  94. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  95. data/lib/active_record/encryption/message_serializer.rb +4 -0
  96. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  97. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  98. data/lib/active_record/encryption/scheme.rb +8 -4
  99. data/lib/active_record/encryption.rb +2 -0
  100. data/lib/active_record/enum.rb +19 -2
  101. data/lib/active_record/errors.rb +46 -20
  102. data/lib/active_record/explain.rb +13 -24
  103. data/lib/active_record/fixtures.rb +37 -31
  104. data/lib/active_record/future_result.rb +17 -4
  105. data/lib/active_record/gem_version.rb +3 -3
  106. data/lib/active_record/inheritance.rb +4 -2
  107. data/lib/active_record/insert_all.rb +18 -15
  108. data/lib/active_record/integration.rb +4 -1
  109. data/lib/active_record/internal_metadata.rb +48 -34
  110. data/lib/active_record/locking/optimistic.rb +8 -7
  111. data/lib/active_record/log_subscriber.rb +0 -21
  112. data/lib/active_record/marshalling.rb +4 -1
  113. data/lib/active_record/message_pack.rb +2 -2
  114. data/lib/active_record/migration/command_recorder.rb +2 -3
  115. data/lib/active_record/migration/compatibility.rb +11 -3
  116. data/lib/active_record/migration/default_strategy.rb +4 -5
  117. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  118. data/lib/active_record/migration.rb +85 -76
  119. data/lib/active_record/model_schema.rb +38 -70
  120. data/lib/active_record/nested_attributes.rb +24 -5
  121. data/lib/active_record/normalization.rb +3 -7
  122. data/lib/active_record/persistence.rb +32 -354
  123. data/lib/active_record/query_cache.rb +19 -8
  124. data/lib/active_record/query_logs.rb +15 -0
  125. data/lib/active_record/query_logs_formatter.rb +1 -1
  126. data/lib/active_record/querying.rb +21 -9
  127. data/lib/active_record/railtie.rb +50 -68
  128. data/lib/active_record/railties/controller_runtime.rb +13 -4
  129. data/lib/active_record/railties/databases.rake +42 -45
  130. data/lib/active_record/reflection.rb +106 -38
  131. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  132. data/lib/active_record/relation/batches.rb +14 -8
  133. data/lib/active_record/relation/calculations.rb +96 -63
  134. data/lib/active_record/relation/delegation.rb +8 -11
  135. data/lib/active_record/relation/finder_methods.rb +16 -2
  136. data/lib/active_record/relation/merger.rb +4 -6
  137. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  138. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
  139. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
  140. data/lib/active_record/relation/predicate_builder.rb +3 -3
  141. data/lib/active_record/relation/query_methods.rb +245 -65
  142. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  143. data/lib/active_record/relation/spawn_methods.rb +2 -18
  144. data/lib/active_record/relation/where_clause.rb +7 -19
  145. data/lib/active_record/relation.rb +500 -66
  146. data/lib/active_record/result.rb +32 -45
  147. data/lib/active_record/runtime_registry.rb +39 -0
  148. data/lib/active_record/sanitization.rb +24 -19
  149. data/lib/active_record/schema.rb +8 -6
  150. data/lib/active_record/schema_dumper.rb +19 -9
  151. data/lib/active_record/schema_migration.rb +30 -14
  152. data/lib/active_record/scoping/named.rb +1 -0
  153. data/lib/active_record/signed_id.rb +20 -1
  154. data/lib/active_record/statement_cache.rb +7 -7
  155. data/lib/active_record/table_metadata.rb +1 -10
  156. data/lib/active_record/tasks/database_tasks.rb +98 -48
  157. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  158. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  159. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  160. data/lib/active_record/test_fixtures.rb +87 -89
  161. data/lib/active_record/testing/query_assertions.rb +121 -0
  162. data/lib/active_record/timestamp.rb +5 -3
  163. data/lib/active_record/token_for.rb +22 -12
  164. data/lib/active_record/touch_later.rb +1 -1
  165. data/lib/active_record/transaction.rb +132 -0
  166. data/lib/active_record/transactions.rb +70 -14
  167. data/lib/active_record/translation.rb +0 -2
  168. data/lib/active_record/type/serialized.rb +1 -3
  169. data/lib/active_record/type_caster/connection.rb +4 -4
  170. data/lib/active_record/validations/associated.rb +9 -3
  171. data/lib/active_record/validations/uniqueness.rb +15 -10
  172. data/lib/active_record/validations.rb +4 -1
  173. data/lib/active_record.rb +150 -41
  174. data/lib/arel/alias_predication.rb +1 -1
  175. data/lib/arel/collectors/bind.rb +2 -0
  176. data/lib/arel/collectors/composite.rb +7 -0
  177. data/lib/arel/collectors/sql_string.rb +1 -1
  178. data/lib/arel/collectors/substitute_binds.rb +1 -1
  179. data/lib/arel/nodes/binary.rb +0 -6
  180. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  181. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  182. data/lib/arel/nodes/node.rb +4 -3
  183. data/lib/arel/nodes/sql_literal.rb +7 -0
  184. data/lib/arel/nodes.rb +2 -2
  185. data/lib/arel/predications.rb +1 -1
  186. data/lib/arel/select_manager.rb +1 -1
  187. data/lib/arel/tree_manager.rb +8 -3
  188. data/lib/arel/update_manager.rb +2 -1
  189. data/lib/arel/visitors/dot.rb +1 -0
  190. data/lib/arel/visitors/mysql.rb +9 -4
  191. data/lib/arel/visitors/postgresql.rb +1 -12
  192. data/lib/arel/visitors/sqlite.rb +25 -0
  193. data/lib/arel/visitors/to_sql.rb +31 -17
  194. data/lib/arel.rb +7 -3
  195. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  196. metadata +21 -15
@@ -170,6 +170,10 @@ 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
+
173
177
  def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
174
178
  query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
175
179
  end
@@ -214,7 +218,7 @@ module ActiveRecord
214
218
  update("SET FOREIGN_KEY_CHECKS = 0")
215
219
  yield
216
220
  ensure
217
- update("SET FOREIGN_KEY_CHECKS = #{old}")
221
+ update("SET FOREIGN_KEY_CHECKS = #{old}") if active?
218
222
  end
219
223
  end
220
224
 
@@ -225,12 +229,12 @@ module ActiveRecord
225
229
  # Mysql2Adapter doesn't have to free a result after using it, but we use this method
226
230
  # to write stuff in an abstract way without concerning ourselves about whether it
227
231
  # needs to be explicitly freed or not.
228
- def execute_and_free(sql, name = nil, async: false) # :nodoc:
232
+ def execute_and_free(sql, name = nil, async: false, allow_retry: false) # :nodoc:
229
233
  sql = transform_query(sql)
230
234
  check_if_write_query(sql)
231
235
 
232
236
  mark_transaction_written_if_write(sql)
233
- yield raw_execute(sql, name, async: async)
237
+ yield raw_execute(sql, name, async: async, allow_retry: allow_retry)
234
238
  end
235
239
 
236
240
  def begin_db_transaction # :nodoc:
@@ -336,7 +340,7 @@ module ActiveRecord
336
340
  schema_cache.clear_data_source_cache!(table_name.to_s)
337
341
  schema_cache.clear_data_source_cache!(new_name.to_s)
338
342
  execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
339
- rename_table_indexes(table_name, new_name)
343
+ rename_table_indexes(table_name, new_name, **options)
340
344
  end
341
345
 
342
346
  # Drops a table from the database.
@@ -635,27 +639,63 @@ module ActiveRecord
635
639
  end
636
640
 
637
641
  def build_insert_sql(insert) # :nodoc:
638
- sql = +"INSERT #{insert.into} #{insert.values_list}"
639
-
640
- if insert.skip_duplicates?
641
- no_op_column = quote_column_name(insert.keys.first)
642
- sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
643
- elsif insert.update_duplicates?
644
- sql << " ON DUPLICATE KEY UPDATE "
645
- if insert.raw_update_sql?
646
- sql << insert.raw_update_sql
647
- else
648
- sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
649
- sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
642
+ no_op_column = quote_column_name(insert.keys.first) if insert.keys.first
643
+
644
+ # MySQL 8.0.19 replaces `VALUES(<expression>)` clauses with row and column alias names, see https://dev.mysql.com/worklog/task/?id=6312 .
645
+ # then MySQL 8.0.20 deprecates the `VALUES(<expression>)` see https://dev.mysql.com/worklog/task/?id=13325 .
646
+ if supports_insert_raw_alias_syntax?
647
+ values_alias = quote_table_name("#{insert.model.table_name.parameterize}_values")
648
+ sql = +"INSERT #{insert.into} #{insert.values_list} AS #{values_alias}"
649
+
650
+ if insert.skip_duplicates?
651
+ if no_op_column
652
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{values_alias}.#{no_op_column}"
653
+ end
654
+ elsif insert.update_duplicates?
655
+ if insert.raw_update_sql?
656
+ sql = +"INSERT #{insert.into} #{insert.values_list} ON DUPLICATE KEY UPDATE #{insert.raw_update_sql}"
657
+ else
658
+ sql << " ON DUPLICATE KEY UPDATE "
659
+ sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column}<=>#{values_alias}.#{column}" }
660
+ sql << insert.updatable_columns.map { |column| "#{column}=#{values_alias}.#{column}" }.join(",")
661
+ end
662
+ end
663
+ else
664
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
665
+
666
+ if insert.skip_duplicates?
667
+ if no_op_column
668
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
669
+ end
670
+ elsif insert.update_duplicates?
671
+ sql << " ON DUPLICATE KEY UPDATE "
672
+ if insert.raw_update_sql?
673
+ sql << insert.raw_update_sql
674
+ else
675
+ sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
676
+ sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
677
+ end
650
678
  end
651
679
  end
652
680
 
681
+ sql << " RETURNING #{insert.returning}" if insert.returning
653
682
  sql
654
683
  end
655
684
 
656
685
  def check_version # :nodoc:
657
686
  if database_version < "5.5.8"
658
- raise "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
687
+ raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
688
+ end
689
+ end
690
+
691
+ #--
692
+ # QUOTING ==================================================
693
+ #++
694
+
695
+ # Quotes strings for use in SQL input.
696
+ def quote_string(string)
697
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
698
+ connection.escape(string)
659
699
  end
660
700
  end
661
701
 
@@ -734,7 +774,11 @@ module ActiveRecord
734
774
  return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
735
775
 
736
776
  @affected_rows_before_warnings = @raw_connection.affected_rows
777
+ warning_count = @raw_connection.warning_count
737
778
  result = @raw_connection.query("SHOW WARNINGS")
779
+ result = [
780
+ ["Warning", nil, "Query had warning_count=#{warning_count} but ‘SHOW WARNINGS’ did not return the warnings. Check MySQL logs or database configuration."],
781
+ ] if result.count == 0
738
782
  result.each do |level, code, message|
739
783
  warning = SQLWarning.new(message, code, level, sql, @pool)
740
784
  next if warning_ignored?(warning)
@@ -756,6 +800,7 @@ module ActiveRecord
756
800
  ER_DB_CREATE_EXISTS = 1007
757
801
  ER_FILSORT_ABORT = 1028
758
802
  ER_DUP_ENTRY = 1062
803
+ ER_SERVER_SHUTDOWN = 1053
759
804
  ER_NOT_NULL_VIOLATION = 1048
760
805
  ER_NO_REFERENCED_ROW = 1216
761
806
  ER_ROW_IS_REFERENCED = 1217
@@ -784,7 +829,7 @@ module ActiveRecord
784
829
  else
785
830
  super
786
831
  end
787
- when ER_CONNECTION_KILLED, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
832
+ when ER_CONNECTION_KILLED, ER_SERVER_SHUTDOWN, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
788
833
  ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool)
789
834
  when ER_DB_CREATE_EXISTS
790
835
  DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
@@ -853,6 +898,10 @@ module ActiveRecord
853
898
  "DROP INDEX #{quote_column_name(index_name)}"
854
899
  end
855
900
 
901
+ def supports_insert_raw_alias_syntax?
902
+ !mariadb? && database_version >= "8.0.19"
903
+ end
904
+
856
905
  def supports_rename_index?
857
906
  if mariadb?
858
907
  database_version >= "10.5.2"
@@ -870,6 +919,7 @@ module ActiveRecord
870
919
  end
871
920
 
872
921
  def configure_connection
922
+ super
873
923
  variables = @config.fetch(:variables, {}).stringify_keys
874
924
 
875
925
  # Increase timeout so the server doesn't disconnect us.
@@ -977,7 +1027,11 @@ module ActiveRecord
977
1027
  end
978
1028
 
979
1029
  def version_string(full_version_string)
980
- full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
1030
+ if full_version_string && matches = full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)
1031
+ matches[1]
1032
+ else
1033
+ raise DatabaseVersionError, "Unable to parse MySQL version from #{full_version_string.inspect}"
1034
+ end
981
1035
  end
982
1036
  end
983
1037
  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
@@ -84,43 +116,6 @@ module ActiveRecord
84
116
  super
85
117
  end
86
118
  end
87
-
88
- def column_name_matcher
89
- COLUMN_NAME
90
- end
91
-
92
- def column_name_with_order_matcher
93
- COLUMN_NAME_WITH_ORDER
94
- end
95
-
96
- COLUMN_NAME = /
97
- \A
98
- (
99
- (?:
100
- # `table_name`.`column_name` | function(one or no argument)
101
- ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
102
- )
103
- (?:(?:\s+AS)?\s+(?:\w+|`\w+`))?
104
- )
105
- (?:\s*,\s*\g<1>)*
106
- \z
107
- /ix
108
-
109
- COLUMN_NAME_WITH_ORDER = /
110
- \A
111
- (
112
- (?:
113
- # `table_name`.`column_name` | function(one or no argument)
114
- ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
115
- )
116
- (?:\s+COLLATE\s+(?:\w+|"\w+"))?
117
- (?:\s+ASC|\s+DESC)?
118
- )
119
- (?:\s*,\s*\g<1>)*
120
- \z
121
- /ix
122
-
123
- private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
124
119
  end
125
120
  end
126
121
  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)
@@ -155,7 +161,7 @@ module ActiveRecord
155
161
  end
156
162
 
157
163
  def valid_primary_key_options
158
- super + [:unsigned]
164
+ super + [:unsigned, :auto_increment]
159
165
  end
160
166
 
161
167
  def create_table_definition(name, **options)
@@ -185,6 +191,7 @@ module ActiveRecord
185
191
  default, default_function = nil, default
186
192
  elsif type_metadata.extra == "DEFAULT_GENERATED"
187
193
  default = +"(#{default})" unless default.start_with?("(")
194
+ default = default.gsub("\\'", "'")
188
195
  default, default_function = nil, default
189
196
  elsif type_metadata.type == :text && default&.start_with?("'")
190
197
  # strip and unescape quotes
@@ -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
@@ -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,12 +92,14 @@ 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
@@ -113,7 +113,7 @@ 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
116
+ log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
117
117
  with_raw_connection do |conn|
118
118
  sync_timezone_changes(conn)
119
119
 
@@ -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
@@ -7,17 +7,6 @@ gem "mysql2", "~> 0.5"
7
7
  require "mysql2"
8
8
 
9
9
  module ActiveRecord
10
- module ConnectionHandling # :nodoc:
11
- def mysql2_adapter_class
12
- ConnectionAdapters::Mysql2Adapter
13
- end
14
-
15
- # Establishes a connection to the database that's used by all Active Record objects.
16
- def mysql2_connection(config)
17
- mysql2_adapter_class.new(config)
18
- end
19
- end
20
-
21
10
  module ConnectionAdapters
22
11
  # = Active Record MySQL2 Adapter
23
12
  class Mysql2Adapter < AbstractMysqlAdapter
@@ -116,22 +105,15 @@ module ActiveRecord
116
105
  end
117
106
 
118
107
  #--
119
- # QUOTING ==================================================
108
+ # CONNECTION MANAGEMENT ====================================
120
109
  #++
121
110
 
122
- # Quotes strings for use in SQL input.
123
- def quote_string(string)
124
- with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
125
- connection.escape(string)
126
- end
111
+ def connected?
112
+ !(@raw_connection.nil? || @raw_connection.closed?)
127
113
  end
128
114
 
129
- #--
130
- # CONNECTION MANAGEMENT ====================================
131
- #++
132
-
133
115
  def active?
134
- !!@raw_connection&.ping
116
+ connected? && @lock.synchronize { @raw_connection&.ping } || false
135
117
  end
136
118
 
137
119
  alias :reset! :reconnect!
@@ -139,15 +121,19 @@ module ActiveRecord
139
121
  # Disconnects from the database if already connected.
140
122
  # Otherwise, this method does nothing.
141
123
  def disconnect!
142
- super
143
- @raw_connection&.close
144
- @raw_connection = nil
124
+ @lock.synchronize do
125
+ super
126
+ @raw_connection&.close
127
+ @raw_connection = nil
128
+ end
145
129
  end
146
130
 
147
131
  def discard! # :nodoc:
148
- super
149
- @raw_connection&.automatic_close = false
150
- @raw_connection = nil
132
+ @lock.synchronize do
133
+ super
134
+ @raw_connection&.automatic_close = false
135
+ @raw_connection = nil
136
+ end
151
137
  end
152
138
 
153
139
  private
@@ -162,9 +148,11 @@ module ActiveRecord
162
148
  end
163
149
 
164
150
  def reconnect
165
- @raw_connection&.close
166
- @raw_connection = nil
167
- connect
151
+ @lock.synchronize do
152
+ @raw_connection&.close
153
+ @raw_connection = nil
154
+ connect
155
+ end
168
156
  end
169
157
 
170
158
  def configure_connection
@@ -174,7 +162,7 @@ module ActiveRecord
174
162
  end
175
163
 
176
164
  def full_version
177
- schema_cache.database_version.full_version_string
165
+ database_version.full_version_string
178
166
  end
179
167
 
180
168
  def get_full_version
@@ -3,10 +3,10 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  class PoolConfig # :nodoc:
6
- include Mutex_m
6
+ include MonitorMixin
7
7
 
8
8
  attr_reader :db_config, :role, :shard
9
- attr_writer :schema_reflection
9
+ attr_writer :schema_reflection, :server_version
10
10
  attr_accessor :connection_class
11
11
 
12
12
  def schema_reflection
@@ -28,6 +28,7 @@ module ActiveRecord
28
28
 
29
29
  def initialize(connection_class, db_config, role, shard)
30
30
  super()
31
+ @server_version = nil
31
32
  @connection_class = connection_class
32
33
  @db_config = db_config
33
34
  @role = role
@@ -36,6 +37,10 @@ module ActiveRecord
36
37
  INSTANCES[self] = self
37
38
  end
38
39
 
40
+ def server_version(connection)
41
+ @server_version || synchronize { @server_version ||= connection.get_database_version }
42
+ end
43
+
39
44
  def connection_name
40
45
  if connection_class.primary_class?
41
46
  "ActiveRecord::Base"
@@ -45,8 +50,6 @@ module ActiveRecord
45
50
  end
46
51
 
47
52
  def disconnect!(automatic_reconnect: false)
48
- ActiveSupport::ForkTracker.check!
49
-
50
53
  return unless @pool
51
54
 
52
55
  synchronize do
@@ -60,8 +63,6 @@ module ActiveRecord
60
63
  end
61
64
 
62
65
  def pool
63
- ActiveSupport::ForkTracker.check!
64
-
65
66
  @pool || synchronize { @pool ||= ConnectionAdapters::ConnectionPool.new(self) }
66
67
  end
67
68
 
@@ -14,10 +14,11 @@ module ActiveRecord
14
14
  def query(sql, name = nil) # :nodoc:
15
15
  mark_transaction_written_if_write(sql)
16
16
 
17
- log(sql, name) do
17
+ log(sql, name) do |notification_payload|
18
18
  with_raw_connection do |conn|
19
19
  result = conn.async_exec(sql).map_types!(@type_map_for_results).values
20
20
  verified!
21
+ notification_payload[:row_count] = result.count
21
22
  result
22
23
  end
23
24
  end
@@ -50,11 +51,12 @@ module ActiveRecord
50
51
  end
51
52
 
52
53
  def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
53
- log(sql, name, async: async) do
54
+ log(sql, name, async: async) do |notification_payload|
54
55
  with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
55
56
  result = conn.async_exec(sql)
56
57
  verified!
57
58
  handle_warnings(result)
59
+ notification_payload[:row_count] = result.count
58
60
  result
59
61
  end
60
62
  end
@@ -69,7 +71,7 @@ module ActiveRecord
69
71
  fmod = result.fmod i
70
72
  types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
71
73
  end
72
- build_result(columns: fields, rows: result.values, column_types: types)
74
+ build_result(columns: fields, rows: result.values, column_types: types.freeze)
73
75
  end
74
76
  end
75
77
 
@@ -122,7 +124,7 @@ module ActiveRecord
122
124
  end
123
125
 
124
126
  # From https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
125
- HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
127
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP", retryable: true).freeze # :nodoc:
126
128
  private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
127
129
 
128
130
  def high_precision_current_timestamp
@@ -135,6 +137,27 @@ module ActiveRecord
135
137
  "EXPLAIN (#{options.join(", ").upcase})"
136
138
  end
137
139
 
140
+ # Set when constraints will be checked for the current transaction.
141
+ #
142
+ # Not passing any specific constraint names will set the value for all deferrable constraints.
143
+ #
144
+ # [<tt>deferred</tt>]
145
+ # Valid values are +:deferred+ or +:immediate+.
146
+ #
147
+ # See https://www.postgresql.org/docs/current/sql-set-constraints.html
148
+ def set_constraints(deferred, *constraints)
149
+ unless %i[deferred immediate].include?(deferred)
150
+ raise ArgumentError, "deferred must be :deferred or :immediate"
151
+ end
152
+
153
+ constraints = if constraints.empty?
154
+ "ALL"
155
+ else
156
+ constraints.map { |c| quote_table_name(c) }.join(", ")
157
+ end
158
+ execute("SET CONSTRAINTS #{constraints} #{deferred.to_s.upcase}")
159
+ end
160
+
138
161
  private
139
162
  IDLE_TRANSACTION_STATUSES = [PG::PQTRANS_IDLE, PG::PQTRANS_INTRANS, PG::PQTRANS_INERROR]
140
163
  private_constant :IDLE_TRANSACTION_STATUSES
@@ -28,6 +28,12 @@ module ActiveRecord
28
28
  end
29
29
  end
30
30
 
31
+ # TODO: Remove when IPAddr#== compares IPAddr#prefix. See
32
+ # https://github.com/ruby/ipaddr/issues/21
33
+ def changed?(old_value, new_value, _new_value_before_type_cast)
34
+ !old_value.eql?(new_value) || !old_value.nil? && old_value.prefix != new_value.prefix
35
+ end
36
+
31
37
  def cast_value(value)
32
38
  if value.nil?
33
39
  nil
@@ -33,7 +33,7 @@ module ActiveRecord
33
33
  when ::Numeric
34
34
  # Sometimes operations on Times returns just float number of seconds so we need to handle that.
35
35
  # Example: Time.current - (Time.current + 1.hour) # => -3600.000001776 (Float)
36
- value.seconds.iso8601(precision: self.precision)
36
+ ActiveSupport::Duration.build(value).iso8601(precision: self.precision)
37
37
  else
38
38
  super
39
39
  end