activerecord 7.0.4 → 7.1.5.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 (246) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1971 -1243
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +20 -4
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +20 -14
  15. data/lib/active_record/associations/collection_proxy.rb +20 -10
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  21. data/lib/active_record/associations/join_dependency.rb +10 -10
  22. data/lib/active_record/associations/preloader/association.rb +31 -7
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  24. data/lib/active_record/associations/preloader.rb +13 -10
  25. data/lib/active_record/associations/singular_association.rb +1 -1
  26. data/lib/active_record/associations/through_association.rb +22 -11
  27. data/lib/active_record/associations.rb +333 -222
  28. data/lib/active_record/attribute_assignment.rb +0 -2
  29. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  30. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  31. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  32. data/lib/active_record/attribute_methods/query.rb +28 -16
  33. data/lib/active_record/attribute_methods/read.rb +21 -8
  34. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -4
  36. data/lib/active_record/attribute_methods/write.rb +6 -6
  37. data/lib/active_record/attribute_methods.rb +148 -26
  38. data/lib/active_record/attributes.rb +3 -3
  39. data/lib/active_record/autosave_association.rb +59 -10
  40. data/lib/active_record/base.rb +7 -2
  41. data/lib/active_record/callbacks.rb +16 -32
  42. data/lib/active_record/coders/column_serializer.rb +61 -0
  43. data/lib/active_record/coders/json.rb +1 -1
  44. data/lib/active_record/coders/yaml_column.rb +70 -42
  45. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  46. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  47. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  48. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +80 -50
  49. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +51 -7
  53. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  54. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  55. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +155 -25
  56. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +297 -127
  57. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  58. data/lib/active_record/connection_adapters/abstract_adapter.rb +509 -103
  59. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +254 -125
  60. data/lib/active_record/connection_adapters/column.rb +9 -0
  61. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  62. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  63. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -14
  64. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  65. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +19 -13
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  71. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  72. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
  74. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  78. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +2 -2
  79. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  80. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  81. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  82. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  83. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  84. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +365 -61
  85. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +354 -193
  87. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  88. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  89. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  90. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +9 -5
  91. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +213 -85
  94. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  97. data/lib/active_record/connection_adapters.rb +3 -1
  98. data/lib/active_record/connection_handling.rb +72 -95
  99. data/lib/active_record/core.rb +181 -154
  100. data/lib/active_record/counter_cache.rb +52 -27
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  102. data/lib/active_record/database_configurations/database_config.rb +9 -3
  103. data/lib/active_record/database_configurations/hash_config.rb +28 -14
  104. data/lib/active_record/database_configurations/url_config.rb +17 -11
  105. data/lib/active_record/database_configurations.rb +86 -33
  106. data/lib/active_record/delegated_type.rb +15 -10
  107. data/lib/active_record/deprecator.rb +7 -0
  108. data/lib/active_record/destroy_association_async_job.rb +3 -1
  109. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  110. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  111. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  112. data/lib/active_record/encryption/config.rb +25 -1
  113. data/lib/active_record/encryption/configurable.rb +12 -19
  114. data/lib/active_record/encryption/context.rb +10 -3
  115. data/lib/active_record/encryption/contexts.rb +5 -1
  116. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  117. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  118. data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
  119. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  120. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  121. data/lib/active_record/encryption/key_generator.rb +12 -1
  122. data/lib/active_record/encryption/message_serializer.rb +2 -0
  123. data/lib/active_record/encryption/properties.rb +3 -3
  124. data/lib/active_record/encryption/scheme.rb +22 -21
  125. data/lib/active_record/encryption.rb +3 -0
  126. data/lib/active_record/enum.rb +112 -28
  127. data/lib/active_record/errors.rb +112 -18
  128. data/lib/active_record/explain.rb +23 -3
  129. data/lib/active_record/explain_subscriber.rb +1 -1
  130. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  131. data/lib/active_record/fixture_set/render_context.rb +2 -0
  132. data/lib/active_record/fixture_set/table_row.rb +29 -8
  133. data/lib/active_record/fixtures.rb +135 -71
  134. data/lib/active_record/future_result.rb +40 -5
  135. data/lib/active_record/gem_version.rb +4 -4
  136. data/lib/active_record/inheritance.rb +30 -16
  137. data/lib/active_record/insert_all.rb +57 -10
  138. data/lib/active_record/integration.rb +8 -8
  139. data/lib/active_record/internal_metadata.rb +120 -30
  140. data/lib/active_record/locking/optimistic.rb +33 -19
  141. data/lib/active_record/locking/pessimistic.rb +5 -2
  142. data/lib/active_record/log_subscriber.rb +29 -12
  143. data/lib/active_record/marshalling.rb +59 -0
  144. data/lib/active_record/message_pack.rb +124 -0
  145. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  146. data/lib/active_record/middleware/database_selector.rb +9 -11
  147. data/lib/active_record/middleware/shard_selector.rb +3 -1
  148. data/lib/active_record/migration/command_recorder.rb +105 -7
  149. data/lib/active_record/migration/compatibility.rb +163 -58
  150. data/lib/active_record/migration/default_strategy.rb +23 -0
  151. data/lib/active_record/migration/execution_strategy.rb +19 -0
  152. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  153. data/lib/active_record/migration.rb +271 -114
  154. data/lib/active_record/model_schema.rb +69 -44
  155. data/lib/active_record/nested_attributes.rb +37 -8
  156. data/lib/active_record/normalization.rb +167 -0
  157. data/lib/active_record/persistence.rb +195 -42
  158. data/lib/active_record/promise.rb +84 -0
  159. data/lib/active_record/query_cache.rb +4 -22
  160. data/lib/active_record/query_logs.rb +87 -51
  161. data/lib/active_record/query_logs_formatter.rb +41 -0
  162. data/lib/active_record/querying.rb +15 -2
  163. data/lib/active_record/railtie.rb +107 -45
  164. data/lib/active_record/railties/controller_runtime.rb +14 -9
  165. data/lib/active_record/railties/databases.rake +144 -150
  166. data/lib/active_record/railties/job_runtime.rb +23 -0
  167. data/lib/active_record/readonly_attributes.rb +32 -5
  168. data/lib/active_record/reflection.rb +189 -45
  169. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  170. data/lib/active_record/relation/batches.rb +190 -61
  171. data/lib/active_record/relation/calculations.rb +232 -81
  172. data/lib/active_record/relation/delegation.rb +23 -9
  173. data/lib/active_record/relation/finder_methods.rb +77 -16
  174. data/lib/active_record/relation/merger.rb +2 -0
  175. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  177. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  178. data/lib/active_record/relation/predicate_builder.rb +26 -14
  179. data/lib/active_record/relation/query_attribute.rb +25 -1
  180. data/lib/active_record/relation/query_methods.rb +408 -76
  181. data/lib/active_record/relation/spawn_methods.rb +18 -1
  182. data/lib/active_record/relation.rb +103 -37
  183. data/lib/active_record/result.rb +25 -9
  184. data/lib/active_record/runtime_registry.rb +24 -1
  185. data/lib/active_record/sanitization.rb +51 -11
  186. data/lib/active_record/schema.rb +2 -3
  187. data/lib/active_record/schema_dumper.rb +50 -7
  188. data/lib/active_record/schema_migration.rb +68 -33
  189. data/lib/active_record/scoping/default.rb +15 -5
  190. data/lib/active_record/scoping/named.rb +2 -2
  191. data/lib/active_record/scoping.rb +2 -1
  192. data/lib/active_record/secure_password.rb +60 -0
  193. data/lib/active_record/secure_token.rb +21 -3
  194. data/lib/active_record/signed_id.rb +7 -5
  195. data/lib/active_record/store.rb +9 -9
  196. data/lib/active_record/suppressor.rb +3 -1
  197. data/lib/active_record/table_metadata.rb +16 -3
  198. data/lib/active_record/tasks/database_tasks.rb +152 -108
  199. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  200. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  201. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  202. data/lib/active_record/test_fixtures.rb +114 -96
  203. data/lib/active_record/timestamp.rb +30 -16
  204. data/lib/active_record/token_for.rb +113 -0
  205. data/lib/active_record/touch_later.rb +11 -6
  206. data/lib/active_record/transactions.rb +39 -13
  207. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  208. data/lib/active_record/type/internal/timezone.rb +7 -2
  209. data/lib/active_record/type/serialized.rb +8 -4
  210. data/lib/active_record/type/time.rb +4 -0
  211. data/lib/active_record/validations/absence.rb +1 -1
  212. data/lib/active_record/validations/numericality.rb +5 -4
  213. data/lib/active_record/validations/presence.rb +5 -28
  214. data/lib/active_record/validations/uniqueness.rb +47 -2
  215. data/lib/active_record/validations.rb +8 -4
  216. data/lib/active_record/version.rb +1 -1
  217. data/lib/active_record.rb +130 -17
  218. data/lib/arel/errors.rb +10 -0
  219. data/lib/arel/factory_methods.rb +4 -0
  220. data/lib/arel/filter_predications.rb +1 -1
  221. data/lib/arel/nodes/and.rb +4 -0
  222. data/lib/arel/nodes/binary.rb +6 -1
  223. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  224. data/lib/arel/nodes/cte.rb +36 -0
  225. data/lib/arel/nodes/filter.rb +1 -1
  226. data/lib/arel/nodes/fragments.rb +35 -0
  227. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  228. data/lib/arel/nodes/leading_join.rb +8 -0
  229. data/lib/arel/nodes/node.rb +111 -2
  230. data/lib/arel/nodes/sql_literal.rb +6 -0
  231. data/lib/arel/nodes/table_alias.rb +4 -0
  232. data/lib/arel/nodes.rb +4 -0
  233. data/lib/arel/predications.rb +2 -0
  234. data/lib/arel/table.rb +9 -5
  235. data/lib/arel/tree_manager.rb +5 -1
  236. data/lib/arel/visitors/mysql.rb +8 -1
  237. data/lib/arel/visitors/to_sql.rb +83 -18
  238. data/lib/arel/visitors/visitor.rb +2 -2
  239. data/lib/arel.rb +16 -2
  240. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  241. data/lib/rails/generators/active_record/migration.rb +3 -1
  242. data/lib/rails/generators/active_record/model/USAGE +113 -0
  243. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  244. metadata +51 -15
  245. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  246. data/lib/active_record/null_relation.rb +0 -63
@@ -3,6 +3,7 @@
3
3
  require "active_record/connection_adapters/abstract_adapter"
4
4
  require "active_record/connection_adapters/statement_pool"
5
5
  require "active_record/connection_adapters/mysql/column"
6
+ require "active_record/connection_adapters/mysql/database_statements"
6
7
  require "active_record/connection_adapters/mysql/explain_pretty_printer"
7
8
  require "active_record/connection_adapters/mysql/quoting"
8
9
  require "active_record/connection_adapters/mysql/schema_creation"
@@ -14,6 +15,7 @@ require "active_record/connection_adapters/mysql/type_metadata"
14
15
  module ActiveRecord
15
16
  module ConnectionAdapters
16
17
  class AbstractMysqlAdapter < AbstractAdapter
18
+ include MySQL::DatabaseStatements
17
19
  include MySQL::Quoting
18
20
  include MySQL::SchemaStatements
19
21
 
@@ -51,8 +53,34 @@ module ActiveRecord
51
53
  end
52
54
  end
53
55
 
54
- def initialize(connection, logger, connection_options, config)
55
- super(connection, logger, config)
56
+ class << self
57
+ def dbconsole(config, options = {})
58
+ mysql_config = config.configuration_hash
59
+
60
+ args = {
61
+ host: "--host",
62
+ port: "--port",
63
+ socket: "--socket",
64
+ username: "--user",
65
+ encoding: "--default-character-set",
66
+ sslca: "--ssl-ca",
67
+ sslcert: "--ssl-cert",
68
+ sslcapath: "--ssl-capath",
69
+ sslcipher: "--ssl-cipher",
70
+ sslkey: "--ssl-key",
71
+ ssl_mode: "--ssl-mode"
72
+ }.filter_map { |opt, arg| "#{arg}=#{mysql_config[opt]}" if mysql_config[opt] }
73
+
74
+ if mysql_config[:password] && options[:include_password]
75
+ args << "--password=#{mysql_config[:password]}"
76
+ elsif mysql_config[:password] && !mysql_config[:password].to_s.empty?
77
+ args << "-p"
78
+ end
79
+
80
+ args << config.database
81
+
82
+ find_cmd_and_exec(["mysql", "mysql5"], *args)
83
+ end
56
84
  end
57
85
 
58
86
  def get_database_version # :nodoc:
@@ -81,6 +109,10 @@ module ActiveRecord
81
109
  true
82
110
  end
83
111
 
112
+ def supports_restart_db_transaction?
113
+ true
114
+ end
115
+
84
116
  def supports_explain?
85
117
  true
86
118
  end
@@ -95,7 +127,7 @@ module ActiveRecord
95
127
 
96
128
  def supports_check_constraints?
97
129
  if mariadb?
98
- database_version >= "10.2.1"
130
+ database_version >= "10.3.10" || (database_version < "10.3" && database_version >= "10.2.22")
99
131
  else
100
132
  database_version >= "8.0.16"
101
133
  end
@@ -138,11 +170,6 @@ module ActiveRecord
138
170
  true
139
171
  end
140
172
 
141
- def field_ordered_value(column, values) # :nodoc:
142
- field = Arel::Nodes::NamedFunction.new("FIELD", [column, values.reverse.map { |value| Arel::Nodes.build_quoted(value) }])
143
- Arel::Nodes::Descending.new(field)
144
- end
145
-
146
173
  def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
147
174
  query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
148
175
  end
@@ -195,33 +222,36 @@ module ActiveRecord
195
222
  # DATABASE STATEMENTS ======================================
196
223
  #++
197
224
 
198
- # Executes the SQL statement in the context of this connection.
199
- def execute(sql, name = nil, async: false)
200
- raw_execute(sql, name, async: async)
201
- end
202
-
203
225
  # Mysql2Adapter doesn't have to free a result after using it, but we use this method
204
226
  # to write stuff in an abstract way without concerning ourselves about whether it
205
227
  # needs to be explicitly freed or not.
206
228
  def execute_and_free(sql, name = nil, async: false) # :nodoc:
207
- yield execute(sql, name, async: async)
229
+ sql = transform_query(sql)
230
+ check_if_write_query(sql)
231
+
232
+ mark_transaction_written_if_write(sql)
233
+ yield raw_execute(sql, name, async: async)
208
234
  end
209
235
 
210
236
  def begin_db_transaction # :nodoc:
211
- execute("BEGIN", "TRANSACTION")
237
+ internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
212
238
  end
213
239
 
214
240
  def begin_isolated_db_transaction(isolation) # :nodoc:
215
- execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
241
+ internal_execute("SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
216
242
  begin_db_transaction
217
243
  end
218
244
 
219
245
  def commit_db_transaction # :nodoc:
220
- execute("COMMIT", "TRANSACTION")
246
+ internal_execute("COMMIT", "TRANSACTION", allow_retry: false, materialize_transactions: true)
221
247
  end
222
248
 
223
249
  def exec_rollback_db_transaction # :nodoc:
224
- execute("ROLLBACK", "TRANSACTION")
250
+ internal_execute("ROLLBACK", "TRANSACTION", allow_retry: false, materialize_transactions: true)
251
+ end
252
+
253
+ def exec_restart_db_transaction # :nodoc:
254
+ internal_execute("ROLLBACK AND CHAIN", "TRANSACTION", allow_retry: false, materialize_transactions: true)
225
255
  end
226
256
 
227
257
  def empty_insert_statement_value(primary_key = nil) # :nodoc:
@@ -301,11 +331,12 @@ module ActiveRecord
301
331
  #
302
332
  # Example:
303
333
  # rename_table('octopuses', 'octopi')
304
- def rename_table(table_name, new_name)
334
+ def rename_table(table_name, new_name, **options)
335
+ validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
305
336
  schema_cache.clear_data_source_cache!(table_name.to_s)
306
337
  schema_cache.clear_data_source_cache!(new_name.to_s)
307
338
  execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
308
- rename_table_indexes(table_name, new_name)
339
+ rename_table_indexes(table_name, new_name, **options)
309
340
  end
310
341
 
311
342
  # Drops a table from the database.
@@ -339,11 +370,20 @@ module ActiveRecord
339
370
  end
340
371
 
341
372
  def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
373
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
374
+ end
375
+
376
+ def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc:
377
+ column = column_for(table_name, column_name)
378
+ return unless column
379
+
342
380
  default = extract_new_default_value(default_or_changes)
343
- change_column table_name, column_name, nil, default: default
381
+ ChangeColumnDefaultDefinition.new(column, default)
344
382
  end
345
383
 
346
384
  def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
385
+ validate_change_column_null_argument!(null)
386
+
347
387
  unless null || default.nil?
348
388
  execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
349
389
  end
@@ -360,18 +400,60 @@ module ActiveRecord
360
400
  execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, **options)}")
361
401
  end
362
402
 
403
+ # Builds a ChangeColumnDefinition object.
404
+ #
405
+ # This definition object contains information about the column change that would occur
406
+ # if the same arguments were passed to #change_column. See #change_column for information about
407
+ # passing a +table_name+, +column_name+, +type+ and other options that can be passed.
408
+ def build_change_column_definition(table_name, column_name, type, **options) # :nodoc:
409
+ column = column_for(table_name, column_name)
410
+ type ||= column.sql_type
411
+
412
+ unless options.key?(:default)
413
+ options[:default] = column.default
414
+ end
415
+
416
+ unless options.key?(:null)
417
+ options[:null] = column.null
418
+ end
419
+
420
+ unless options.key?(:comment)
421
+ options[:comment] = column.comment
422
+ end
423
+
424
+ if options[:collation] == :no_collation
425
+ options.delete(:collation)
426
+ else
427
+ options[:collation] ||= column.collation if text_type?(type)
428
+ end
429
+
430
+ unless options.key?(:auto_increment)
431
+ options[:auto_increment] = column.auto_increment?
432
+ end
433
+
434
+ td = create_table_definition(table_name)
435
+ cd = td.new_column_definition(column.name, type, **options)
436
+ ChangeColumnDefinition.new(cd, column.name)
437
+ end
438
+
363
439
  def rename_column(table_name, column_name, new_column_name) # :nodoc:
364
440
  execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
365
441
  rename_column_indexes(table_name, column_name, new_column_name)
366
442
  end
367
443
 
368
444
  def add_index(table_name, column_name, **options) # :nodoc:
445
+ create_index = build_create_index_definition(table_name, column_name, **options)
446
+ return unless create_index
447
+
448
+ execute schema_creation.accept(create_index)
449
+ end
450
+
451
+ def build_create_index_definition(table_name, column_name, **options) # :nodoc:
369
452
  index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
370
453
 
371
454
  return if if_not_exists && index_exists?(table_name, column_name, name: index.name)
372
455
 
373
- create_index = CreateIndexDefinition.new(index, algorithm)
374
- execute schema_creation.accept(create_index)
456
+ CreateIndexDefinition.new(index, algorithm)
375
457
  end
376
458
 
377
459
  def add_sql_comment!(sql, comment) # :nodoc:
@@ -384,11 +466,13 @@ module ActiveRecord
384
466
 
385
467
  scope = quoted_scope(table_name)
386
468
 
387
- fk_info = exec_query(<<~SQL, "SCHEMA")
469
+ # MySQL returns 1 row for each column of composite foreign keys.
470
+ fk_info = internal_exec_query(<<~SQL, "SCHEMA")
388
471
  SELECT fk.referenced_table_name AS 'to_table',
389
472
  fk.referenced_column_name AS 'primary_key',
390
473
  fk.column_name AS 'column',
391
474
  fk.constraint_name AS 'name',
475
+ fk.ordinal_position AS 'position',
392
476
  rc.update_rule AS 'on_update',
393
477
  rc.delete_rule AS 'on_delete'
394
478
  FROM information_schema.referential_constraints rc
@@ -401,17 +485,24 @@ module ActiveRecord
401
485
  AND rc.table_name = #{scope[:name]}
402
486
  SQL
403
487
 
404
- fk_info.map do |row|
488
+ grouped_fk = fk_info.group_by { |row| row["name"] }.values.each { |group| group.sort_by! { |row| row["position"] } }
489
+ grouped_fk.map do |group|
490
+ row = group.first
405
491
  options = {
406
- column: row["column"],
407
492
  name: row["name"],
408
- primary_key: row["primary_key"]
493
+ on_update: extract_foreign_key_action(row["on_update"]),
494
+ on_delete: extract_foreign_key_action(row["on_delete"])
409
495
  }
410
496
 
411
- options[:on_update] = extract_foreign_key_action(row["on_update"])
412
- options[:on_delete] = extract_foreign_key_action(row["on_delete"])
497
+ if group.one?
498
+ options[:column] = unquote_identifier(row["column"])
499
+ options[:primary_key] = row["primary_key"]
500
+ else
501
+ options[:column] = group.map { |row| unquote_identifier(row["column"]) }
502
+ options[:primary_key] = group.map { |row| row["primary_key"] }
503
+ end
413
504
 
414
- ForeignKeyDefinition.new(table_name, row["to_table"], options)
505
+ ForeignKeyDefinition.new(table_name, unquote_identifier(row["to_table"]), options)
415
506
  end
416
507
  end
417
508
 
@@ -431,14 +522,22 @@ module ActiveRecord
431
522
  SQL
432
523
  sql += " AND cc.table_name = #{scope[:name]}" if mariadb?
433
524
 
434
- chk_info = exec_query(sql, "SCHEMA")
525
+ chk_info = internal_exec_query(sql, "SCHEMA")
435
526
 
436
527
  chk_info.map do |row|
437
528
  options = {
438
529
  name: row["name"]
439
530
  }
440
531
  expression = row["expression"]
441
- expression = expression[1..-2] unless mariadb? # remove parentheses added by mysql
532
+ expression = expression[1..-2] if expression.start_with?("(") && expression.end_with?(")")
533
+ expression = strip_whitespace_characters(expression)
534
+
535
+ unless mariadb?
536
+ # MySQL returns check constraints expression in an already escaped form.
537
+ # This leads to duplicate escaping later (e.g. when the expression is used in the SchemaDumper).
538
+ expression = expression.gsub("\\'", "'")
539
+ end
540
+
442
541
  CheckConstraintDefinition.new(table_name, expression, options)
443
542
  end
444
543
  else
@@ -536,18 +635,38 @@ module ActiveRecord
536
635
  end
537
636
 
538
637
  def build_insert_sql(insert) # :nodoc:
539
- sql = +"INSERT #{insert.into} #{insert.values_list}"
540
-
541
- if insert.skip_duplicates?
542
- no_op_column = quote_column_name(insert.keys.first)
543
- sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
544
- elsif insert.update_duplicates?
545
- sql << " ON DUPLICATE KEY UPDATE "
546
- if insert.raw_update_sql?
547
- sql << insert.raw_update_sql
548
- else
549
- sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
550
- sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
638
+ no_op_column = quote_column_name(insert.keys.first)
639
+
640
+ # MySQL 8.0.19 replaces `VALUES(<expression>)` clauses with row and column alias names, see https://dev.mysql.com/worklog/task/?id=6312 .
641
+ # then MySQL 8.0.20 deprecates the `VALUES(<expression>)` see https://dev.mysql.com/worklog/task/?id=13325 .
642
+ if supports_insert_raw_alias_syntax?
643
+ values_alias = quote_table_name("#{insert.model.table_name}_values")
644
+ sql = +"INSERT #{insert.into} #{insert.values_list} AS #{values_alias}"
645
+
646
+ if insert.skip_duplicates?
647
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{values_alias}.#{no_op_column}"
648
+ elsif insert.update_duplicates?
649
+ if insert.raw_update_sql?
650
+ sql = +"INSERT #{insert.into} #{insert.values_list} ON DUPLICATE KEY UPDATE #{insert.raw_update_sql}"
651
+ else
652
+ sql << " ON DUPLICATE KEY UPDATE "
653
+ sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column}<=>#{values_alias}.#{column}" }
654
+ sql << insert.updatable_columns.map { |column| "#{column}=#{values_alias}.#{column}" }.join(",")
655
+ end
656
+ end
657
+ else
658
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
659
+
660
+ if insert.skip_duplicates?
661
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
662
+ elsif insert.update_duplicates?
663
+ sql << " ON DUPLICATE KEY UPDATE "
664
+ if insert.raw_update_sql?
665
+ sql << insert.raw_update_sql
666
+ else
667
+ sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
668
+ sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
669
+ end
551
670
  end
552
671
  end
553
672
 
@@ -561,15 +680,18 @@ module ActiveRecord
561
680
  end
562
681
 
563
682
  class << self
683
+ def extended_type_map(default_timezone: nil, emulate_booleans:) # :nodoc:
684
+ super(default_timezone: default_timezone).tap do |m|
685
+ if emulate_booleans
686
+ m.register_type %r(^tinyint\(1\))i, Type::Boolean.new
687
+ end
688
+ end
689
+ end
690
+
564
691
  private
565
692
  def initialize_type_map(m)
566
693
  super
567
694
 
568
- m.register_type(%r(char)i) do |sql_type|
569
- limit = extract_limit(sql_type)
570
- Type.lookup(:string, adapter: :mysql2, limit: limit)
571
- end
572
-
573
695
  m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
574
696
  m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
575
697
  m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
@@ -589,9 +711,6 @@ module ActiveRecord
589
711
 
590
712
  m.alias_type %r(year)i, "integer"
591
713
  m.alias_type %r(bit)i, "binary"
592
-
593
- m.register_type %r(^enum)i, Type.lookup(:string, adapter: :mysql2)
594
- m.register_type %r(^set)i, Type.lookup(:string, adapter: :mysql2)
595
714
  end
596
715
 
597
716
  def register_integer_type(mapping, key, **options)
@@ -613,27 +732,46 @@ module ActiveRecord
613
732
  end
614
733
  end
615
734
 
616
- TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
617
- TYPE_MAP_WITH_BOOLEAN = Type::TypeMap.new(TYPE_MAP).tap do |m|
618
- m.register_type %r(^tinyint\(1\))i, Type::Boolean.new
619
- end
735
+ EXTENDED_TYPE_MAPS = Concurrent::Map.new
736
+ EMULATE_BOOLEANS_TRUE = { emulate_booleans: true }.freeze
620
737
 
621
738
  private
622
- def type_map
623
- emulate_booleans ? TYPE_MAP_WITH_BOOLEAN : TYPE_MAP
739
+ def strip_whitespace_characters(expression)
740
+ expression = expression.gsub(/\\n|\\\\/, "")
741
+ expression = expression.gsub(/\s{2,}/, " ")
742
+ expression
624
743
  end
625
744
 
626
- def raw_execute(sql, name, async: false)
627
- materialize_transactions
628
- mark_transaction_written_if_write(sql)
745
+ def extended_type_map_key
746
+ if @default_timezone
747
+ { default_timezone: @default_timezone, emulate_booleans: emulate_booleans }
748
+ elsif emulate_booleans
749
+ EMULATE_BOOLEANS_TRUE
750
+ end
751
+ end
629
752
 
630
- log(sql, name, async: async) do
631
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
632
- @connection.query(sql)
633
- end
753
+ def handle_warnings(sql)
754
+ return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
755
+
756
+ @affected_rows_before_warnings = @raw_connection.affected_rows
757
+ result = @raw_connection.query("SHOW WARNINGS")
758
+ result.each do |level, code, message|
759
+ warning = SQLWarning.new(message, code, level, sql, @pool)
760
+ next if warning_ignored?(warning)
761
+
762
+ ActiveRecord.db_warnings_action.call(warning)
634
763
  end
635
764
  end
636
765
 
766
+ def warning_ignored?(warning)
767
+ warning.level == "Note" || super
768
+ end
769
+
770
+ # Make sure we carry over any changes to ActiveRecord.default_timezone that have been
771
+ # made since we established the connection
772
+ def sync_timezone_changes(raw_connection)
773
+ end
774
+
637
775
  # See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html
638
776
  ER_DB_CREATE_EXISTS = 1007
639
777
  ER_FILSORT_ABORT = 1028
@@ -651,77 +789,59 @@ module ActiveRecord
651
789
  ER_CANNOT_CREATE_TABLE = 1005
652
790
  ER_LOCK_WAIT_TIMEOUT = 1205
653
791
  ER_QUERY_INTERRUPTED = 1317
792
+ ER_CONNECTION_KILLED = 1927
793
+ CR_SERVER_GONE_ERROR = 2006
794
+ CR_SERVER_LOST = 2013
654
795
  ER_QUERY_TIMEOUT = 3024
655
796
  ER_FK_INCOMPATIBLE_COLUMNS = 3780
797
+ ER_CLIENT_INTERACTION_TIMEOUT = 4031
656
798
 
657
799
  def translate_exception(exception, message:, sql:, binds:)
658
800
  case error_number(exception)
659
801
  when nil
660
802
  if exception.message.match?(/MySQL client is not connected/i)
661
- ConnectionNotEstablished.new(exception)
803
+ ConnectionNotEstablished.new(exception, connection_pool: @pool)
662
804
  else
663
805
  super
664
806
  end
807
+ when ER_CONNECTION_KILLED, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
808
+ ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool)
665
809
  when ER_DB_CREATE_EXISTS
666
- DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
810
+ DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
667
811
  when ER_DUP_ENTRY
668
- RecordNotUnique.new(message, sql: sql, binds: binds)
812
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
669
813
  when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
670
- InvalidForeignKey.new(message, sql: sql, binds: binds)
814
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
671
815
  when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS
672
- mismatched_foreign_key(message, sql: sql, binds: binds)
816
+ mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
673
817
  when ER_CANNOT_CREATE_TABLE
674
818
  if message.include?("errno: 150")
675
- mismatched_foreign_key(message, sql: sql, binds: binds)
819
+ mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
676
820
  else
677
821
  super
678
822
  end
679
823
  when ER_DATA_TOO_LONG
680
- ValueTooLong.new(message, sql: sql, binds: binds)
824
+ ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
681
825
  when ER_OUT_OF_RANGE
682
- RangeError.new(message, sql: sql, binds: binds)
826
+ RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
683
827
  when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
684
- NotNullViolation.new(message, sql: sql, binds: binds)
828
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
685
829
  when ER_LOCK_DEADLOCK
686
- Deadlocked.new(message, sql: sql, binds: binds)
830
+ Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
687
831
  when ER_LOCK_WAIT_TIMEOUT
688
- LockWaitTimeout.new(message, sql: sql, binds: binds)
832
+ LockWaitTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
689
833
  when ER_QUERY_TIMEOUT, ER_FILSORT_ABORT
690
- StatementTimeout.new(message, sql: sql, binds: binds)
834
+ StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
691
835
  when ER_QUERY_INTERRUPTED
692
- QueryCanceled.new(message, sql: sql, binds: binds)
836
+ QueryCanceled.new(message, sql: sql, binds: binds, connection_pool: @pool)
693
837
  else
694
838
  super
695
839
  end
696
840
  end
697
841
 
698
842
  def change_column_for_alter(table_name, column_name, type, **options)
699
- column = column_for(table_name, column_name)
700
- type ||= column.sql_type
701
-
702
- unless options.key?(:default)
703
- options[:default] = column.default
704
- end
705
-
706
- unless options.key?(:null)
707
- options[:null] = column.null
708
- end
709
-
710
- unless options.key?(:comment)
711
- options[:comment] = column.comment
712
- end
713
-
714
- unless options.key?(:collation)
715
- options[:collation] = column.collation
716
- end
717
-
718
- unless options.key?(:auto_increment)
719
- options[:auto_increment] = column.auto_increment?
720
- end
721
-
722
- td = create_table_definition(table_name)
723
- cd = td.new_column_definition(column.name, type, **options)
724
- schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
843
+ cd = build_change_column_definition(table_name, column_name, type, **options)
844
+ schema_creation.accept(cd)
725
845
  end
726
846
 
727
847
  def rename_column_for_alter(table_name, column_name, new_column_name)
@@ -735,7 +855,7 @@ module ActiveRecord
735
855
  comment: column.comment
736
856
  }
737
857
 
738
- current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
858
+ current_type = internal_exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
739
859
  td = create_table_definition(table_name)
740
860
  cd = td.new_column_definition(new_column_name, current_type, **options)
741
861
  schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
@@ -753,6 +873,10 @@ module ActiveRecord
753
873
  "DROP INDEX #{quote_column_name(index_name)}"
754
874
  end
755
875
 
876
+ def supports_insert_raw_alias_syntax?
877
+ !mariadb? && database_version >= "8.0.19"
878
+ end
879
+
756
880
  def supports_rename_index?
757
881
  if mariadb?
758
882
  database_version >= "10.5.2"
@@ -772,9 +896,6 @@ module ActiveRecord
772
896
  def configure_connection
773
897
  variables = @config.fetch(:variables, {}).stringify_keys
774
898
 
775
- # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
776
- variables["sql_auto_is_null"] = 0
777
-
778
899
  # Increase timeout so the server doesn't disconnect us.
779
900
  wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
780
901
  wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
@@ -818,7 +939,7 @@ module ActiveRecord
818
939
  end.join(", ")
819
940
 
820
941
  # ...and send them all in one query
821
- execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
942
+ internal_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}")
822
943
  end
823
944
 
824
945
  def column_definitions(table_name) # :nodoc:
@@ -828,7 +949,7 @@ module ActiveRecord
828
949
  end
829
950
 
830
951
  def create_table_info(table_name) # :nodoc:
831
- exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
952
+ internal_exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
832
953
  end
833
954
 
834
955
  def arel_visitor
@@ -839,18 +960,17 @@ module ActiveRecord
839
960
  StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
840
961
  end
841
962
 
842
- def mismatched_foreign_key(message, sql:, binds:)
963
+ def mismatched_foreign_key_details(message:, sql:)
964
+ foreign_key_pat =
965
+ /Referencing column '(\w+)' and referenced/i =~ message ? $1 : '\w+'
966
+
843
967
  match = %r/
844
968
  (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
845
- FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
969
+ FOREIGN\s+KEY\s*\(`?(?<foreign_key>#{foreign_key_pat})`?\)\s*
846
970
  REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
847
971
  /xmi.match(sql)
848
972
 
849
- options = {
850
- message: message,
851
- sql: sql,
852
- binds: binds,
853
- }
973
+ options = {}
854
974
 
855
975
  if match
856
976
  options[:table] = match[:table]
@@ -860,20 +980,29 @@ module ActiveRecord
860
980
  options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
861
981
  end
862
982
 
983
+ options
984
+ end
985
+
986
+ def mismatched_foreign_key(message, sql:, binds:, connection_pool:)
987
+ options = {
988
+ message: message,
989
+ sql: sql,
990
+ binds: binds,
991
+ connection_pool: connection_pool
992
+ }
993
+
994
+ if sql
995
+ options.update mismatched_foreign_key_details(message: message, sql: sql)
996
+ else
997
+ options[:query_parser] = ->(sql) { mismatched_foreign_key_details(message: message, sql: sql) }
998
+ end
999
+
863
1000
  MismatchedForeignKey.new(**options)
864
1001
  end
865
1002
 
866
1003
  def version_string(full_version_string)
867
1004
  full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
868
1005
  end
869
-
870
- ActiveRecord::Type.register(:immutable_string, adapter: :mysql2) do |_, **args|
871
- Type::ImmutableString.new(true: "1", false: "0", **args)
872
- end
873
- ActiveRecord::Type.register(:string, adapter: :mysql2) do |_, **args|
874
- Type::String.new(true: "1", false: "0", **args)
875
- end
876
- ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
877
1006
  end
878
1007
  end
879
1008
  end
@@ -63,6 +63,15 @@ module ActiveRecord
63
63
  coder["comment"] = @comment
64
64
  end
65
65
 
66
+ # whether the column is auto-populated by the database using a sequence
67
+ def auto_incremented_by_db?
68
+ false
69
+ end
70
+
71
+ def auto_populated?
72
+ auto_incremented_by_db? || default_function
73
+ end
74
+
66
75
  def ==(other)
67
76
  other.is_a?(Column) &&
68
77
  name == other.name &&
@@ -17,6 +17,7 @@ module ActiveRecord
17
17
  def auto_increment?
18
18
  extra == "auto_increment"
19
19
  end
20
+ alias_method :auto_incremented_by_db?, :auto_increment?
20
21
 
21
22
  def virtual?
22
23
  /\b(?:VIRTUAL|STORED|PERSISTENT)\b/.match?(extra)