activerecord 7.2.3 → 8.1.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 (198) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +612 -1055
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/association.rb +35 -11
  6. data/lib/active_record/associations/builder/association.rb +23 -11
  7. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  8. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  9. data/lib/active_record/associations/builder/has_one.rb +1 -1
  10. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  11. data/lib/active_record/associations/collection_association.rb +1 -1
  12. data/lib/active_record/associations/collection_proxy.rb +22 -4
  13. data/lib/active_record/associations/deprecation.rb +88 -0
  14. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  15. data/lib/active_record/associations/errors.rb +3 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  17. data/lib/active_record/associations/join_dependency.rb +4 -2
  18. data/lib/active_record/associations/preloader/association.rb +2 -2
  19. data/lib/active_record/associations/preloader/batch.rb +7 -1
  20. data/lib/active_record/associations/preloader/branch.rb +1 -0
  21. data/lib/active_record/associations/singular_association.rb +8 -3
  22. data/lib/active_record/associations.rb +192 -24
  23. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  24. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  25. data/lib/active_record/attribute_methods/query.rb +34 -0
  26. data/lib/active_record/attribute_methods/serialization.rb +16 -3
  27. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  28. data/lib/active_record/attributes.rb +3 -0
  29. data/lib/active_record/autosave_association.rb +69 -27
  30. data/lib/active_record/base.rb +1 -2
  31. data/lib/active_record/coders/json.rb +14 -5
  32. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  33. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  34. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
  35. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +412 -88
  36. data/lib/active_record/connection_adapters/abstract/database_statements.rb +137 -75
  37. data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
  38. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  39. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  40. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
  41. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  42. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -32
  43. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  44. data/lib/active_record/connection_adapters/abstract_adapter.rb +150 -91
  45. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +63 -52
  46. data/lib/active_record/connection_adapters/column.rb +17 -4
  47. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  48. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  49. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  50. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  55. data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
  56. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
  57. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  58. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  59. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  60. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  61. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  62. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  63. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
  64. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
  65. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +139 -63
  66. data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
  67. data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
  68. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  69. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  70. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  71. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  72. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  73. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -14
  74. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +102 -37
  75. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  76. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
  77. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
  78. data/lib/active_record/connection_adapters.rb +1 -56
  79. data/lib/active_record/connection_handling.rb +25 -2
  80. data/lib/active_record/core.rb +33 -17
  81. data/lib/active_record/counter_cache.rb +33 -8
  82. data/lib/active_record/database_configurations/database_config.rb +9 -1
  83. data/lib/active_record/database_configurations/hash_config.rb +67 -9
  84. data/lib/active_record/database_configurations/url_config.rb +13 -3
  85. data/lib/active_record/database_configurations.rb +7 -3
  86. data/lib/active_record/delegated_type.rb +1 -1
  87. data/lib/active_record/dynamic_matchers.rb +54 -69
  88. data/lib/active_record/encryption/config.rb +3 -1
  89. data/lib/active_record/encryption/encryptable_record.rb +8 -8
  90. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  91. data/lib/active_record/encryption/encryptor.rb +28 -8
  92. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  93. data/lib/active_record/encryption/scheme.rb +9 -2
  94. data/lib/active_record/enum.rb +33 -30
  95. data/lib/active_record/errors.rb +33 -9
  96. data/lib/active_record/explain.rb +1 -1
  97. data/lib/active_record/explain_registry.rb +51 -2
  98. data/lib/active_record/filter_attribute_handler.rb +73 -0
  99. data/lib/active_record/fixtures.rb +2 -4
  100. data/lib/active_record/future_result.rb +15 -9
  101. data/lib/active_record/gem_version.rb +2 -2
  102. data/lib/active_record/inheritance.rb +1 -1
  103. data/lib/active_record/insert_all.rb +14 -9
  104. data/lib/active_record/locking/optimistic.rb +8 -1
  105. data/lib/active_record/locking/pessimistic.rb +5 -0
  106. data/lib/active_record/log_subscriber.rb +3 -13
  107. data/lib/active_record/middleware/shard_selector.rb +34 -17
  108. data/lib/active_record/migration/command_recorder.rb +45 -12
  109. data/lib/active_record/migration/compatibility.rb +37 -24
  110. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  111. data/lib/active_record/migration.rb +48 -42
  112. data/lib/active_record/model_schema.rb +38 -13
  113. data/lib/active_record/nested_attributes.rb +6 -6
  114. data/lib/active_record/persistence.rb +162 -133
  115. data/lib/active_record/query_cache.rb +22 -15
  116. data/lib/active_record/query_logs.rb +100 -52
  117. data/lib/active_record/query_logs_formatter.rb +17 -28
  118. data/lib/active_record/querying.rb +8 -8
  119. data/lib/active_record/railtie.rb +35 -30
  120. data/lib/active_record/railties/controller_runtime.rb +11 -6
  121. data/lib/active_record/railties/databases.rake +26 -38
  122. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  123. data/lib/active_record/railties/job_runtime.rb +10 -11
  124. data/lib/active_record/reflection.rb +53 -21
  125. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  126. data/lib/active_record/relation/batches.rb +147 -73
  127. data/lib/active_record/relation/calculations.rb +52 -40
  128. data/lib/active_record/relation/delegation.rb +25 -15
  129. data/lib/active_record/relation/finder_methods.rb +40 -24
  130. data/lib/active_record/relation/merger.rb +8 -8
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  132. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
  133. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  134. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  135. data/lib/active_record/relation/predicate_builder.rb +22 -7
  136. data/lib/active_record/relation/query_attribute.rb +3 -1
  137. data/lib/active_record/relation/query_methods.rb +140 -86
  138. data/lib/active_record/relation/spawn_methods.rb +7 -7
  139. data/lib/active_record/relation/where_clause.rb +2 -9
  140. data/lib/active_record/relation.rb +107 -75
  141. data/lib/active_record/result.rb +109 -24
  142. data/lib/active_record/runtime_registry.rb +42 -58
  143. data/lib/active_record/sanitization.rb +9 -6
  144. data/lib/active_record/schema_dumper.rb +18 -11
  145. data/lib/active_record/schema_migration.rb +2 -1
  146. data/lib/active_record/scoping/named.rb +5 -2
  147. data/lib/active_record/scoping.rb +0 -1
  148. data/lib/active_record/signed_id.rb +43 -15
  149. data/lib/active_record/statement_cache.rb +24 -20
  150. data/lib/active_record/store.rb +51 -22
  151. data/lib/active_record/structured_event_subscriber.rb +85 -0
  152. data/lib/active_record/table_metadata.rb +6 -23
  153. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  154. data/lib/active_record/tasks/database_tasks.rb +85 -85
  155. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  156. data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
  157. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  158. data/lib/active_record/test_databases.rb +14 -4
  159. data/lib/active_record/test_fixtures.rb +39 -2
  160. data/lib/active_record/testing/query_assertions.rb +8 -2
  161. data/lib/active_record/timestamp.rb +4 -2
  162. data/lib/active_record/token_for.rb +1 -1
  163. data/lib/active_record/transaction.rb +2 -5
  164. data/lib/active_record/transactions.rb +37 -16
  165. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  166. data/lib/active_record/type/internal/timezone.rb +7 -0
  167. data/lib/active_record/type/json.rb +13 -2
  168. data/lib/active_record/type/serialized.rb +16 -4
  169. data/lib/active_record/type/type_map.rb +1 -1
  170. data/lib/active_record/type_caster/connection.rb +2 -1
  171. data/lib/active_record/validations/associated.rb +1 -1
  172. data/lib/active_record/validations/uniqueness.rb +8 -8
  173. data/lib/active_record.rb +84 -49
  174. data/lib/arel/alias_predication.rb +2 -0
  175. data/lib/arel/collectors/bind.rb +2 -2
  176. data/lib/arel/collectors/sql_string.rb +1 -1
  177. data/lib/arel/collectors/substitute_binds.rb +2 -2
  178. data/lib/arel/crud.rb +6 -11
  179. data/lib/arel/nodes/binary.rb +1 -1
  180. data/lib/arel/nodes/count.rb +2 -2
  181. data/lib/arel/nodes/function.rb +4 -10
  182. data/lib/arel/nodes/named_function.rb +2 -2
  183. data/lib/arel/nodes/node.rb +2 -2
  184. data/lib/arel/nodes/sql_literal.rb +1 -1
  185. data/lib/arel/nodes.rb +0 -2
  186. data/lib/arel/predications.rb +1 -3
  187. data/lib/arel/select_manager.rb +7 -2
  188. data/lib/arel/table.rb +3 -7
  189. data/lib/arel/visitors/dot.rb +0 -3
  190. data/lib/arel/visitors/postgresql.rb +55 -0
  191. data/lib/arel/visitors/sqlite.rb +55 -8
  192. data/lib/arel/visitors/to_sql.rb +3 -21
  193. data/lib/arel.rb +3 -1
  194. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  195. metadata +16 -13
  196. data/lib/active_record/explain_subscriber.rb +0 -34
  197. data/lib/active_record/normalization.rb +0 -163
  198. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -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
 
@@ -79,7 +79,11 @@ module ActiveRecord
79
79
 
80
80
  args << config.database
81
81
 
82
- find_cmd_and_exec(["mysql", "mysql5"], *args)
82
+ find_cmd_and_exec(ActiveRecord.database_cli[:mysql], *args)
83
+ end
84
+
85
+ def native_database_types # :nodoc:
86
+ NATIVE_DATABASE_TYPES
83
87
  end
84
88
  end
85
89
 
@@ -98,7 +102,7 @@ module ActiveRecord
98
102
  end
99
103
 
100
104
  def supports_index_sort_order?
101
- !mariadb? && database_version >= "8.0.1"
105
+ mariadb? ? database_version >= "10.8.1" : database_version >= "8.0.1"
102
106
  end
103
107
 
104
108
  def supports_expression_index?
@@ -138,7 +142,7 @@ module ActiveRecord
138
142
  end
139
143
 
140
144
  def supports_datetime_with_precision?
141
- mariadb? || database_version >= "5.6.4"
145
+ true
142
146
  end
143
147
 
144
148
  def supports_virtual_columns?
@@ -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",
@@ -199,14 +209,6 @@ module ActiveRecord
199
209
  }
200
210
  end
201
211
 
202
- # HELPER METHODS ===========================================
203
-
204
- # The two drivers have slightly different ways of yielding hashes of results, so
205
- # this method must be implemented to provide a uniform interface.
206
- def each_hash(result) # :nodoc:
207
- raise NotImplementedError
208
- end
209
-
210
212
  # Must return the MySQL error number from the exception, if the exception has an
211
213
  # error number.
212
214
  def error_number(exception) # :nodoc:
@@ -230,24 +232,19 @@ module ActiveRecord
230
232
  # DATABASE STATEMENTS ======================================
231
233
  #++
232
234
 
233
- # Mysql2Adapter doesn't have to free a result after using it, but we use this method
234
- # to write stuff in an abstract way without concerning ourselves about whether it
235
- # needs to be explicitly freed or not.
236
- def execute_and_free(sql, name = nil, async: false, allow_retry: false) # :nodoc:
237
- sql = transform_query(sql)
238
- check_if_write_query(sql)
239
-
240
- mark_transaction_written_if_write(sql)
241
- yield raw_execute(sql, name, async: async, allow_retry: allow_retry)
242
- end
243
-
244
235
  def begin_db_transaction # :nodoc:
245
236
  internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
246
237
  end
247
238
 
248
239
  def begin_isolated_db_transaction(isolation) # :nodoc:
249
- internal_execute("SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
250
- begin_db_transaction
240
+ # From MySQL manual: The [SET TRANSACTION] statement applies only to the next single transaction performed within the session.
241
+ # So we don't need to implement #reset_isolation_level
242
+ execute_batch(
243
+ ["SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "BEGIN"],
244
+ "TRANSACTION",
245
+ allow_retry: true,
246
+ materialize_transactions: false,
247
+ )
251
248
  end
252
249
 
253
250
  def commit_db_transaction # :nodoc:
@@ -347,7 +344,7 @@ module ActiveRecord
347
344
  rename_table_indexes(table_name, new_name, **options)
348
345
  end
349
346
 
350
- # Drops a table from the database.
347
+ # Drops a table or tables from the database.
351
348
  #
352
349
  # [<tt>:force</tt>]
353
350
  # Set to +:cascade+ to drop dependent objects as well.
@@ -361,10 +358,10 @@ module ActiveRecord
361
358
  #
362
359
  # Although this command ignores most +options+ and the block if one is given,
363
360
  # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
364
- # In that case, +options+ and the block will be used by create_table.
365
- def drop_table(table_name, **options)
366
- schema_cache.clear_data_source_cache!(table_name.to_s)
367
- execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
361
+ # In that case, +options+ and the block will be used by #create_table except if you provide more than one table which is not supported.
362
+ def drop_table(*table_names, **options)
363
+ table_names.each { |table_name| schema_cache.clear_data_source_cache!(table_name.to_s) }
364
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{table_names.map { |table_name| quote_table_name(table_name) }.join(', ')}#{' CASCADE' if options[:force] == :cascade}"
368
365
  end
369
366
 
370
367
  def rename_index(table_name, old_name, new_name)
@@ -468,6 +465,24 @@ module ActiveRecord
468
465
  CreateIndexDefinition.new(index, algorithm)
469
466
  end
470
467
 
468
+ def enable_index(table_name, index_name) # :nodoc:
469
+ raise NotImplementedError unless supports_disabling_indexes?
470
+
471
+ query = <<~SQL
472
+ ALTER TABLE #{quote_table_name(table_name)} ALTER INDEX #{index_name} #{mariadb? ? "NOT IGNORED" : "VISIBLE"}
473
+ SQL
474
+ execute(query)
475
+ end
476
+
477
+ def disable_index(table_name, index_name) # :nodoc:
478
+ raise NotImplementedError unless supports_disabling_indexes?
479
+
480
+ query = <<~SQL
481
+ ALTER TABLE #{quote_table_name(table_name)} ALTER INDEX #{index_name} #{mariadb? ? "IGNORED" : "INVISIBLE"}
482
+ SQL
483
+ execute(query)
484
+ end
485
+
471
486
  def add_sql_comment!(sql, comment) # :nodoc:
472
487
  sql << " COMMENT #{quote(comment)}" if comment.present?
473
488
  sql
@@ -588,7 +603,7 @@ module ActiveRecord
588
603
 
589
604
  # SHOW VARIABLES LIKE 'name'
590
605
  def show_variable(name)
591
- query_value("SELECT @@#{name}", "SCHEMA")
606
+ query_value("SELECT @@#{name}", "SCHEMA", materialize_transactions: false, allow_retry: true)
592
607
  rescue ActiveRecord::StatementInvalid
593
608
  nil
594
609
  end
@@ -693,8 +708,8 @@ module ActiveRecord
693
708
  end
694
709
 
695
710
  def check_version # :nodoc:
696
- if database_version < "5.5.8"
697
- raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
711
+ if database_version < "5.6.4"
712
+ raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.6.4."
698
713
  end
699
714
  end
700
715
 
@@ -743,12 +758,12 @@ module ActiveRecord
743
758
  m.alias_type %r(bit)i, "binary"
744
759
  end
745
760
 
746
- def register_integer_type(mapping, key, **options)
761
+ def register_integer_type(mapping, key, limit:)
747
762
  mapping.register_type(key) do |sql_type|
748
763
  if /\bunsigned\b/.match?(sql_type)
749
- Type::UnsignedInteger.new(**options)
764
+ Type::UnsignedInteger.new(limit: limit)
750
765
  else
751
- Type::Integer.new(**options)
766
+ Type::Integer.new(limit: limit)
752
767
  end
753
768
  end
754
769
  end
@@ -778,14 +793,13 @@ module ActiveRecord
778
793
  end
779
794
  end
780
795
 
781
- def handle_warnings(sql)
796
+ def handle_warnings(_initial_result, sql)
782
797
  return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
783
798
 
784
- @affected_rows_before_warnings = @raw_connection.affected_rows
785
799
  warning_count = @raw_connection.warning_count
786
800
  result = @raw_connection.query("SHOW WARNINGS")
787
801
  result = [
788
- ["Warning", nil, "Query had warning_count=#{warning_count} but SHOW WARNINGS did not return the warnings. Check MySQL logs or database configuration."],
802
+ ["Warning", nil, "Query had warning_count=#{warning_count} but `SHOW WARNINGS` did not return the warnings. Check MySQL logs or database configuration."],
789
803
  ] if result.count == 0
790
804
  result.each do |level, code, message|
791
805
  warning = SQLWarning.new(message, code, level, sql, @pool)
@@ -799,11 +813,6 @@ module ActiveRecord
799
813
  warning.level == "Note" || super
800
814
  end
801
815
 
802
- # Make sure we carry over any changes to ActiveRecord.default_timezone that have been
803
- # made since we established the connection
804
- def sync_timezone_changes(raw_connection)
805
- end
806
-
807
816
  # See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html
808
817
  ER_DB_CREATE_EXISTS = 1007
809
818
  ER_FILSORT_ABORT = 1028
@@ -827,6 +836,8 @@ module ActiveRecord
827
836
  CR_SERVER_LOST = 2013
828
837
  ER_QUERY_TIMEOUT = 3024
829
838
  ER_FK_INCOMPATIBLE_COLUMNS = 3780
839
+ ER_CHECK_CONSTRAINT_VIOLATED = 3819
840
+ ER_CONSTRAINT_FAILED = 4025
830
841
  ER_CLIENT_INTERACTION_TIMEOUT = 4031
831
842
 
832
843
  def translate_exception(exception, message:, sql:, binds:)
@@ -859,6 +870,8 @@ module ActiveRecord
859
870
  RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
860
871
  when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
861
872
  NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
873
+ when ER_CHECK_CONSTRAINT_VIOLATED, ER_CONSTRAINT_FAILED
874
+ CheckViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
862
875
  when ER_LOCK_DEADLOCK
863
876
  Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
864
877
  when ER_LOCK_WAIT_TIMEOUT
@@ -973,13 +986,11 @@ module ActiveRecord
973
986
  end.join(", ")
974
987
 
975
988
  # ...and send them all in one query
976
- internal_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}")
989
+ raw_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
977
990
  end
978
991
 
979
992
  def column_definitions(table_name) # :nodoc:
980
- execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
981
- each_hash(result)
982
- end
993
+ internal_exec_query("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA", allow_retry: true)
983
994
  end
984
995
 
985
996
  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
@@ -77,14 +77,6 @@ module ActiveRecord
77
77
  0
78
78
  end
79
79
 
80
- def quoted_date(value)
81
- if supports_datetime_with_precision?
82
- super
83
- else
84
- super.sub(/\.\d{6}\z/, "")
85
- end
86
- end
87
-
88
80
  def quoted_binary(value)
89
81
  "x'#{value.hex}'"
90
82
  end
@@ -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,18 +43,26 @@ module ActiveRecord
42
43
  # :method: unsigned_bigint
43
44
  # :call-seq: unsigned_bigint(*names, **options)
44
45
 
45
- ##
46
- # :method: unsigned_float
47
- # :call-seq: unsigned_float(*names, **options)
46
+ define_column_methods :blob, :tinyblob, :mediumblob, :longblob,
47
+ :tinytext, :mediumtext, :longtext, :unsigned_integer, :unsigned_bigint
48
+ end
48
49
 
49
- ##
50
- # :method: unsigned_decimal
51
- # :call-seq: unsigned_decimal(*names, **options)
50
+ # = Active Record MySQL Adapter \Index Definition
51
+ class IndexDefinition < ActiveRecord::ConnectionAdapters::IndexDefinition # :nodoc:
52
+ attr_accessor :enabled
53
+
54
+ def initialize(*args, **kwargs)
55
+ @enabled = kwargs.key?(:enabled) ? kwargs.delete(:enabled) : true
56
+ super
57
+ end
52
58
 
53
- included do
54
- define_column_methods :blob, :tinyblob, :mediumblob, :longblob,
55
- :tinytext, :mediumtext, :longtext, :unsigned_integer, :unsigned_bigint,
56
- :unsigned_float, :unsigned_decimal
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
57
66
  end
58
67
  end
59
68
 
@@ -106,6 +115,28 @@ module ActiveRecord
106
115
  # = Active Record MySQL Adapter \Table
107
116
  class Table < ActiveRecord::ConnectionAdapters::Table
108
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
109
140
  end
110
141
  end
111
142
  end
@@ -8,45 +8,49 @@ module ActiveRecord
8
8
  def indexes(table_name)
9
9
  indexes = []
10
10
  current_index = nil
11
- execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
12
- each_hash(result) do |row|
13
- if current_index != row[:Key_name]
14
- next if row[:Key_name] == "PRIMARY" # skip the primary key
15
- current_index = row[:Key_name]
16
-
17
- mysql_index_type = row[:Index_type].downcase.to_sym
18
- case mysql_index_type
19
- when :fulltext, :spatial
20
- index_type = mysql_index_type
21
- when :btree, :hash
22
- index_using = mysql_index_type
23
- end
24
-
25
- indexes << [
26
- row[:Table],
27
- row[:Key_name],
28
- row[:Non_unique].to_i == 0,
29
- [],
30
- lengths: {},
31
- orders: {},
32
- type: index_type,
33
- using: index_using,
34
- comment: row[:Index_comment].presence
35
- ]
11
+ internal_exec_query("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA").each do |row|
12
+ if current_index != row["Key_name"]
13
+ next if row["Key_name"] == "PRIMARY" # skip the primary key
14
+ current_index = row["Key_name"]
15
+
16
+ mysql_index_type = row["Index_type"].downcase.to_sym
17
+ case mysql_index_type
18
+ when :fulltext, :spatial
19
+ index_type = mysql_index_type
20
+ when :btree, :hash
21
+ index_using = mysql_index_type
36
22
  end
37
23
 
38
- if row[:Expression]
39
- expression = row[:Expression].gsub("\\'", "'")
40
- expression = +"(#{expression})" unless expression.start_with?("(")
41
- indexes.last[-2] << expression
42
- indexes.last[-1][:expressions] ||= {}
43
- indexes.last[-1][:expressions][expression] = expression
44
- indexes.last[-1][:orders][expression] = :desc if row[:Collation] == "D"
45
- else
46
- indexes.last[-2] << row[:Column_name]
47
- indexes.last[-1][:lengths][row[:Column_name]] = row[:Sub_part].to_i if row[:Sub_part]
48
- indexes.last[-1][:orders][row[:Column_name]] = :desc if row[:Collation] == "D"
24
+ index = [
25
+ row["Table"],
26
+ row["Key_name"],
27
+ row["Non_unique"].to_i == 0,
28
+ [],
29
+ lengths: {},
30
+ orders: {},
31
+ type: index_type,
32
+ using: index_using,
33
+ comment: row["Index_comment"].presence,
34
+ ]
35
+
36
+ if supports_disabling_indexes?
37
+ index[-1][:enabled] = mariadb? ? row["Ignored"] == "NO" : row["Visible"] == "YES"
49
38
  end
39
+
40
+ indexes << index
41
+ end
42
+
43
+ if expression = row["Expression"]
44
+ expression = expression.gsub("\\'", "'")
45
+ expression = +"(#{expression})" unless expression.start_with?("(")
46
+ indexes.last[-2] << expression
47
+ indexes.last[-1][:expressions] ||= {}
48
+ indexes.last[-1][:expressions][expression] = expression
49
+ indexes.last[-1][:orders][expression] = :desc if row["Collation"] == "D"
50
+ else
51
+ indexes.last[-2] << row["Column_name"]
52
+ indexes.last[-1][:lengths][row["Column_name"]] = row["Sub_part"].to_i if row["Sub_part"]
53
+ indexes.last[-1][:orders][row["Column_name"]] = :desc if row["Collation"] == "D"
50
54
  end
51
55
  end
52
56
 
@@ -65,8 +69,7 @@ module ActiveRecord
65
69
  columns, order: orders, length: lengths
66
70
  ).values.join(", ")
67
71
  end
68
-
69
- IndexDefinition.new(*index, **options)
72
+ MySQL::IndexDefinition.new(*index, **options)
70
73
  end
71
74
  rescue StatementInvalid => e
72
75
  if e.message.match?(/Table '.+' doesn't exist/)
@@ -76,6 +79,16 @@ module ActiveRecord
76
79
  end
77
80
  end
78
81
 
82
+ def create_index_definition(table_name, name, unique, columns, **options)
83
+ MySQL::IndexDefinition.new(table_name, name, unique, columns, **options)
84
+ end
85
+
86
+ def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc:
87
+ index, algorithm, if_not_exists = super
88
+ index.enabled = options[:enabled] unless options[:enabled].nil?
89
+ [index, algorithm, if_not_exists]
90
+ end
91
+
79
92
  def remove_column(table_name, column_name, type = nil, **options)
80
93
  if foreign_key_exists?(table_name, column: column_name)
81
94
  remove_foreign_key(table_name, column: column_name)
@@ -87,6 +100,13 @@ module ActiveRecord
87
100
  super
88
101
  end
89
102
 
103
+ def remove_foreign_key(from_table, to_table = nil, **options)
104
+ # RESTRICT is by default in MySQL.
105
+ options.delete(:on_update) if options[:on_update] == :restrict
106
+ options.delete(:on_delete) if options[:on_delete] == :restrict
107
+ super
108
+ end
109
+
90
110
  def internal_string_options_for_primary_key
91
111
  super.tap do |options|
92
112
  if !row_format_dynamic_by_default? && CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
@@ -182,12 +202,12 @@ module ActiveRecord
182
202
  end
183
203
 
184
204
  def new_column_from_field(table_name, field, _definitions)
185
- field_name = field.fetch(:Field)
186
- type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
187
- default, default_function = field[:Default], nil
205
+ field_name = field.fetch("Field")
206
+ type_metadata = fetch_type_metadata(field["Type"], field["Extra"])
207
+ default, default_function = field["Default"], nil
188
208
 
189
209
  if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(default)
190
- default = "#{default} ON UPDATE #{default}" if /on update CURRENT_TIMESTAMP/i.match?(field[:Extra])
210
+ default = "#{default} ON UPDATE #{default}" if /on update CURRENT_TIMESTAMP/i.match?(field["Extra"])
191
211
  default, default_function = nil, default
192
212
  elsif type_metadata.extra == "DEFAULT_GENERATED"
193
213
  default = +"(#{default})" unless default.start_with?("(")
@@ -203,13 +223,14 @@ module ActiveRecord
203
223
  end
204
224
 
205
225
  MySQL::Column.new(
206
- field[:Field],
226
+ field["Field"],
227
+ lookup_cast_type(type_metadata.sql_type),
207
228
  default,
208
229
  type_metadata,
209
- field[:Null] == "YES",
230
+ field["Null"] == "YES",
210
231
  default_function,
211
- collation: field[:Collation],
212
- comment: field[:Comment].presence
232
+ collation: field["Collation"],
233
+ comment: field["Comment"].presence
213
234
  )
214
235
  end
215
236
 
@@ -228,6 +249,12 @@ module ActiveRecord
228
249
  end
229
250
  end
230
251
 
252
+ def valid_index_options
253
+ index_options = super
254
+ index_options << :enabled if supports_disabling_indexes?
255
+ index_options
256
+ end
257
+
231
258
  def add_options_for_index_columns(quoted_columns, **options)
232
259
  quoted_columns = add_index_length(quoted_columns, **options)
233
260
  super