activerecord 6.1.7 → 7.2.2

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 (333) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +616 -1290
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +31 -31
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +17 -14
  7. data/lib/active_record/association_relation.rb +2 -12
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +60 -21
  10. data/lib/active_record/associations/association_scope.rb +17 -12
  11. data/lib/active_record/associations/belongs_to_association.rb +37 -11
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -4
  13. data/lib/active_record/associations/builder/association.rb +11 -5
  14. data/lib/active_record/associations/builder/belongs_to.rb +41 -14
  15. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  17. data/lib/active_record/associations/builder/has_many.rb +4 -4
  18. data/lib/active_record/associations/builder/has_one.rb +4 -4
  19. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  20. data/lib/active_record/associations/collection_association.rb +46 -36
  21. data/lib/active_record/associations/collection_proxy.rb +44 -16
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/errors.rb +265 -0
  24. data/lib/active_record/associations/foreign_association.rb +10 -3
  25. data/lib/active_record/associations/has_many_association.rb +29 -19
  26. data/lib/active_record/associations/has_many_through_association.rb +19 -8
  27. data/lib/active_record/associations/has_one_association.rb +20 -10
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  30. data/lib/active_record/associations/join_dependency.rb +28 -20
  31. data/lib/active_record/associations/nested_error.rb +47 -0
  32. data/lib/active_record/associations/preloader/association.rb +212 -53
  33. data/lib/active_record/associations/preloader/batch.rb +48 -0
  34. data/lib/active_record/associations/preloader/branch.rb +153 -0
  35. data/lib/active_record/associations/preloader/through_association.rb +50 -16
  36. data/lib/active_record/associations/preloader.rb +50 -121
  37. data/lib/active_record/associations/singular_association.rb +15 -3
  38. data/lib/active_record/associations/through_association.rb +25 -14
  39. data/lib/active_record/associations.rb +429 -522
  40. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  41. data/lib/active_record/attribute_assignment.rb +1 -5
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  43. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  44. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  45. data/lib/active_record/attribute_methods/primary_key.rb +47 -27
  46. data/lib/active_record/attribute_methods/query.rb +31 -19
  47. data/lib/active_record/attribute_methods/read.rb +14 -11
  48. data/lib/active_record/attribute_methods/serialization.rb +174 -37
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +15 -9
  50. data/lib/active_record/attribute_methods/write.rb +12 -15
  51. data/lib/active_record/attribute_methods.rb +164 -52
  52. data/lib/active_record/attributes.rb +57 -54
  53. data/lib/active_record/autosave_association.rb +74 -57
  54. data/lib/active_record/base.rb +27 -5
  55. data/lib/active_record/callbacks.rb +19 -35
  56. data/lib/active_record/coders/column_serializer.rb +61 -0
  57. data/lib/active_record/coders/json.rb +1 -1
  58. data/lib/active_record/coders/yaml_column.rb +70 -46
  59. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +325 -604
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -60
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +230 -64
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -131
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +378 -143
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +361 -76
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +624 -163
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +348 -165
  75. data/lib/active_record/connection_adapters/column.rb +13 -0
  76. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  77. data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -130
  78. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -55
  79. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  81. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  82. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  83. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  84. data/lib/active_record/connection_adapters/mysql2_adapter.rb +107 -68
  85. data/lib/active_record/connection_adapters/pool_config.rb +26 -16
  86. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  87. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  88. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +114 -54
  89. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  94. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  97. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  100. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  102. data/lib/active_record/connection_adapters/postgresql/quoting.rb +137 -104
  103. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  104. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  105. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +173 -3
  106. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  107. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +403 -77
  108. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  109. data/lib/active_record/connection_adapters/postgresql_adapter.rb +520 -253
  110. data/lib/active_record/connection_adapters/schema_cache.rb +326 -102
  111. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  112. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +78 -55
  113. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +68 -54
  114. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  115. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  116. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  117. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +66 -22
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +372 -130
  119. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  120. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  121. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  122. data/lib/active_record/connection_adapters.rb +130 -6
  123. data/lib/active_record/connection_handling.rb +132 -146
  124. data/lib/active_record/core.rb +310 -253
  125. data/lib/active_record/counter_cache.rb +68 -34
  126. data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -4
  127. data/lib/active_record/database_configurations/database_config.rb +34 -10
  128. data/lib/active_record/database_configurations/hash_config.rb +107 -31
  129. data/lib/active_record/database_configurations/url_config.rb +38 -13
  130. data/lib/active_record/database_configurations.rb +96 -60
  131. data/lib/active_record/delegated_type.rb +90 -20
  132. data/lib/active_record/deprecator.rb +7 -0
  133. data/lib/active_record/destroy_association_async_job.rb +4 -2
  134. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  135. data/lib/active_record/dynamic_matchers.rb +3 -3
  136. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  137. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  138. data/lib/active_record/encryption/cipher.rb +53 -0
  139. data/lib/active_record/encryption/config.rb +68 -0
  140. data/lib/active_record/encryption/configurable.rb +60 -0
  141. data/lib/active_record/encryption/context.rb +42 -0
  142. data/lib/active_record/encryption/contexts.rb +76 -0
  143. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  144. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  145. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  146. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  147. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  148. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  149. data/lib/active_record/encryption/encryptor.rb +170 -0
  150. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  151. data/lib/active_record/encryption/errors.rb +15 -0
  152. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  153. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  154. data/lib/active_record/encryption/key.rb +28 -0
  155. data/lib/active_record/encryption/key_generator.rb +53 -0
  156. data/lib/active_record/encryption/key_provider.rb +46 -0
  157. data/lib/active_record/encryption/message.rb +33 -0
  158. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  159. data/lib/active_record/encryption/message_serializer.rb +96 -0
  160. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  161. data/lib/active_record/encryption/properties.rb +76 -0
  162. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  163. data/lib/active_record/encryption/scheme.rb +100 -0
  164. data/lib/active_record/encryption.rb +58 -0
  165. data/lib/active_record/enum.rb +170 -62
  166. data/lib/active_record/errors.rb +210 -27
  167. data/lib/active_record/explain.rb +21 -12
  168. data/lib/active_record/explain_registry.rb +11 -6
  169. data/lib/active_record/explain_subscriber.rb +1 -1
  170. data/lib/active_record/fixture_set/file.rb +15 -1
  171. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  172. data/lib/active_record/fixture_set/render_context.rb +2 -0
  173. data/lib/active_record/fixture_set/table_row.rb +70 -14
  174. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  175. data/lib/active_record/fixtures.rb +179 -112
  176. data/lib/active_record/future_result.rb +178 -0
  177. data/lib/active_record/gem_version.rb +4 -4
  178. data/lib/active_record/inheritance.rb +85 -31
  179. data/lib/active_record/insert_all.rb +148 -32
  180. data/lib/active_record/integration.rb +14 -10
  181. data/lib/active_record/internal_metadata.rb +123 -23
  182. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  183. data/lib/active_record/locking/optimistic.rb +43 -27
  184. data/lib/active_record/locking/pessimistic.rb +15 -6
  185. data/lib/active_record/log_subscriber.rb +41 -29
  186. data/lib/active_record/marshalling.rb +59 -0
  187. data/lib/active_record/message_pack.rb +124 -0
  188. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  189. data/lib/active_record/middleware/database_selector.rb +23 -13
  190. data/lib/active_record/middleware/shard_selector.rb +62 -0
  191. data/lib/active_record/migration/command_recorder.rb +113 -16
  192. data/lib/active_record/migration/compatibility.rb +235 -46
  193. data/lib/active_record/migration/default_strategy.rb +22 -0
  194. data/lib/active_record/migration/execution_strategy.rb +19 -0
  195. data/lib/active_record/migration/join_table.rb +1 -1
  196. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  197. data/lib/active_record/migration.rb +374 -177
  198. data/lib/active_record/model_schema.rb +145 -158
  199. data/lib/active_record/nested_attributes.rb +61 -23
  200. data/lib/active_record/no_touching.rb +3 -3
  201. data/lib/active_record/normalization.rb +163 -0
  202. data/lib/active_record/persistence.rb +282 -283
  203. data/lib/active_record/promise.rb +84 -0
  204. data/lib/active_record/query_cache.rb +18 -25
  205. data/lib/active_record/query_logs.rb +189 -0
  206. data/lib/active_record/query_logs_formatter.rb +41 -0
  207. data/lib/active_record/querying.rb +44 -9
  208. data/lib/active_record/railtie.rb +229 -71
  209. data/lib/active_record/railties/controller_runtime.rb +25 -11
  210. data/lib/active_record/railties/databases.rake +189 -256
  211. data/lib/active_record/railties/job_runtime.rb +23 -0
  212. data/lib/active_record/readonly_attributes.rb +41 -3
  213. data/lib/active_record/reflection.rb +332 -103
  214. data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
  215. data/lib/active_record/relation/batches.rb +200 -65
  216. data/lib/active_record/relation/calculations.rb +301 -112
  217. data/lib/active_record/relation/delegation.rb +33 -22
  218. data/lib/active_record/relation/finder_methods.rb +123 -52
  219. data/lib/active_record/relation/merger.rb +26 -19
  220. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  221. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  222. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  223. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  224. data/lib/active_record/relation/predicate_builder.rb +29 -22
  225. data/lib/active_record/relation/query_attribute.rb +30 -12
  226. data/lib/active_record/relation/query_methods.rb +870 -163
  227. data/lib/active_record/relation/record_fetch_warning.rb +10 -9
  228. data/lib/active_record/relation/spawn_methods.rb +7 -6
  229. data/lib/active_record/relation/where_clause.rb +15 -36
  230. data/lib/active_record/relation.rb +736 -145
  231. data/lib/active_record/result.rb +67 -54
  232. data/lib/active_record/runtime_registry.rb +71 -13
  233. data/lib/active_record/sanitization.rb +84 -34
  234. data/lib/active_record/schema.rb +39 -23
  235. data/lib/active_record/schema_dumper.rb +90 -31
  236. data/lib/active_record/schema_migration.rb +74 -23
  237. data/lib/active_record/scoping/default.rb +72 -15
  238. data/lib/active_record/scoping/named.rb +6 -13
  239. data/lib/active_record/scoping.rb +65 -34
  240. data/lib/active_record/secure_password.rb +60 -0
  241. data/lib/active_record/secure_token.rb +21 -3
  242. data/lib/active_record/serialization.rb +6 -1
  243. data/lib/active_record/signed_id.rb +30 -9
  244. data/lib/active_record/statement_cache.rb +7 -7
  245. data/lib/active_record/store.rb +10 -10
  246. data/lib/active_record/suppressor.rb +13 -15
  247. data/lib/active_record/table_metadata.rb +7 -3
  248. data/lib/active_record/tasks/database_tasks.rb +288 -149
  249. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  250. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  251. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  252. data/lib/active_record/test_databases.rb +1 -1
  253. data/lib/active_record/test_fixtures.rb +173 -155
  254. data/lib/active_record/testing/query_assertions.rb +121 -0
  255. data/lib/active_record/timestamp.rb +32 -19
  256. data/lib/active_record/token_for.rb +123 -0
  257. data/lib/active_record/touch_later.rb +12 -7
  258. data/lib/active_record/transaction.rb +132 -0
  259. data/lib/active_record/transactions.rb +118 -41
  260. data/lib/active_record/translation.rb +3 -5
  261. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  262. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  263. data/lib/active_record/type/internal/timezone.rb +7 -2
  264. data/lib/active_record/type/serialized.rb +9 -7
  265. data/lib/active_record/type/time.rb +4 -0
  266. data/lib/active_record/type/type_map.rb +17 -20
  267. data/lib/active_record/type.rb +1 -2
  268. data/lib/active_record/type_caster/connection.rb +4 -4
  269. data/lib/active_record/validations/absence.rb +1 -1
  270. data/lib/active_record/validations/associated.rb +13 -7
  271. data/lib/active_record/validations/numericality.rb +5 -4
  272. data/lib/active_record/validations/presence.rb +5 -28
  273. data/lib/active_record/validations/uniqueness.rb +65 -15
  274. data/lib/active_record/validations.rb +12 -5
  275. data/lib/active_record/version.rb +1 -1
  276. data/lib/active_record.rb +444 -32
  277. data/lib/arel/alias_predication.rb +1 -1
  278. data/lib/arel/attributes/attribute.rb +0 -8
  279. data/lib/arel/collectors/bind.rb +2 -0
  280. data/lib/arel/collectors/composite.rb +7 -0
  281. data/lib/arel/collectors/sql_string.rb +1 -1
  282. data/lib/arel/collectors/substitute_binds.rb +1 -1
  283. data/lib/arel/crud.rb +28 -22
  284. data/lib/arel/delete_manager.rb +18 -4
  285. data/lib/arel/errors.rb +10 -0
  286. data/lib/arel/factory_methods.rb +4 -0
  287. data/lib/arel/filter_predications.rb +9 -0
  288. data/lib/arel/insert_manager.rb +2 -3
  289. data/lib/arel/nodes/binary.rb +6 -7
  290. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  291. data/lib/arel/nodes/casted.rb +1 -1
  292. data/lib/arel/nodes/cte.rb +36 -0
  293. data/lib/arel/nodes/delete_statement.rb +12 -13
  294. data/lib/arel/nodes/filter.rb +10 -0
  295. data/lib/arel/nodes/fragments.rb +35 -0
  296. data/lib/arel/nodes/function.rb +1 -0
  297. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  298. data/lib/arel/nodes/insert_statement.rb +2 -2
  299. data/lib/arel/nodes/leading_join.rb +8 -0
  300. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  301. data/lib/arel/nodes/node.rb +115 -5
  302. data/lib/arel/nodes/select_core.rb +2 -2
  303. data/lib/arel/nodes/select_statement.rb +2 -2
  304. data/lib/arel/nodes/sql_literal.rb +13 -0
  305. data/lib/arel/nodes/table_alias.rb +4 -0
  306. data/lib/arel/nodes/update_statement.rb +8 -3
  307. data/lib/arel/nodes.rb +7 -2
  308. data/lib/arel/predications.rb +14 -4
  309. data/lib/arel/select_manager.rb +11 -5
  310. data/lib/arel/table.rb +9 -6
  311. data/lib/arel/tree_manager.rb +8 -15
  312. data/lib/arel/update_manager.rb +20 -5
  313. data/lib/arel/visitors/dot.rb +81 -90
  314. data/lib/arel/visitors/mysql.rb +23 -5
  315. data/lib/arel/visitors/postgresql.rb +1 -22
  316. data/lib/arel/visitors/sqlite.rb +25 -0
  317. data/lib/arel/visitors/to_sql.rb +170 -36
  318. data/lib/arel/visitors/visitor.rb +2 -2
  319. data/lib/arel.rb +23 -4
  320. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  321. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  322. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  323. data/lib/rails/generators/active_record/migration.rb +3 -1
  324. data/lib/rails/generators/active_record/model/USAGE +113 -0
  325. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  326. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  327. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  328. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  329. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  330. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  331. metadata +103 -17
  332. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  333. data/lib/active_record/null_relation.rb +0 -67
@@ -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
 
@@ -31,6 +33,7 @@ module ActiveRecord
31
33
  string: { name: "varchar", limit: 255 },
32
34
  text: { name: "text" },
33
35
  integer: { name: "int", limit: 4 },
36
+ bigint: { name: "bigint" },
34
37
  float: { name: "float", limit: 24 },
35
38
  decimal: { name: "decimal" },
36
39
  datetime: { name: "datetime" },
@@ -50,11 +53,37 @@ module ActiveRecord
50
53
  end
51
54
  end
52
55
 
53
- def initialize(connection, logger, connection_options, config)
54
- 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
55
84
  end
56
85
 
57
- def get_database_version #:nodoc:
86
+ def get_database_version # :nodoc:
58
87
  full_version_string = get_full_version
59
88
  version_string = version_string(full_version_string)
60
89
  Version.new(version_string, full_version_string)
@@ -80,6 +109,10 @@ module ActiveRecord
80
109
  true
81
110
  end
82
111
 
112
+ def supports_restart_db_transaction?
113
+ true
114
+ end
115
+
83
116
  def supports_explain?
84
117
  true
85
118
  end
@@ -94,7 +127,7 @@ module ActiveRecord
94
127
 
95
128
  def supports_check_constraints?
96
129
  if mariadb?
97
- database_version >= "10.2.1"
130
+ database_version >= "10.3.10" || (database_version < "10.3" && database_version >= "10.2.22")
98
131
  else
99
132
  database_version >= "8.0.16"
100
133
  end
@@ -137,6 +170,10 @@ module ActiveRecord
137
170
  true
138
171
  end
139
172
 
173
+ def supports_insert_returning?
174
+ mariadb? && database_version >= "10.5.0"
175
+ end
176
+
140
177
  def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
141
178
  query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
142
179
  end
@@ -174,65 +211,54 @@ module ActiveRecord
174
211
 
175
212
  # REFERENTIAL INTEGRITY ====================================
176
213
 
177
- def disable_referential_integrity #:nodoc:
214
+ def disable_referential_integrity # :nodoc:
178
215
  old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
179
216
 
180
217
  begin
181
218
  update("SET FOREIGN_KEY_CHECKS = 0")
182
219
  yield
183
220
  ensure
184
- update("SET FOREIGN_KEY_CHECKS = #{old}")
221
+ update("SET FOREIGN_KEY_CHECKS = #{old}") if active?
185
222
  end
186
223
  end
187
224
 
188
- # CONNECTION MANAGEMENT ====================================
189
-
190
- def clear_cache! # :nodoc:
191
- reload_type_map
192
- super
193
- end
194
-
195
225
  #--
196
226
  # DATABASE STATEMENTS ======================================
197
227
  #++
198
228
 
199
- # Executes the SQL statement in the context of this connection.
200
- def execute(sql, name = nil)
201
- materialize_transactions
202
- mark_transaction_written_if_write(sql)
203
-
204
- log(sql, name) do
205
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
206
- @connection.query(sql)
207
- end
208
- end
209
- end
210
-
211
229
  # Mysql2Adapter doesn't have to free a result after using it, but we use this method
212
230
  # to write stuff in an abstract way without concerning ourselves about whether it
213
231
  # needs to be explicitly freed or not.
214
- def execute_and_free(sql, name = nil) # :nodoc:
215
- yield execute(sql, name)
232
+ def execute_and_free(sql, name = nil, async: false, allow_retry: false) # :nodoc:
233
+ sql = transform_query(sql)
234
+ check_if_write_query(sql)
235
+
236
+ mark_transaction_written_if_write(sql)
237
+ yield raw_execute(sql, name, async: async, allow_retry: allow_retry)
216
238
  end
217
239
 
218
- def begin_db_transaction
219
- execute("BEGIN", "TRANSACTION")
240
+ def begin_db_transaction # :nodoc:
241
+ internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
220
242
  end
221
243
 
222
- def begin_isolated_db_transaction(isolation)
223
- execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
244
+ def begin_isolated_db_transaction(isolation) # :nodoc:
245
+ internal_execute("SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
224
246
  begin_db_transaction
225
247
  end
226
248
 
227
- def commit_db_transaction #:nodoc:
228
- execute("COMMIT", "TRANSACTION")
249
+ def commit_db_transaction # :nodoc:
250
+ internal_execute("COMMIT", "TRANSACTION", allow_retry: false, materialize_transactions: true)
229
251
  end
230
252
 
231
- def exec_rollback_db_transaction #:nodoc:
232
- execute("ROLLBACK", "TRANSACTION")
253
+ def exec_rollback_db_transaction # :nodoc:
254
+ internal_execute("ROLLBACK", "TRANSACTION", allow_retry: false, materialize_transactions: true)
233
255
  end
234
256
 
235
- def empty_insert_statement_value(primary_key = nil)
257
+ def exec_restart_db_transaction # :nodoc:
258
+ internal_execute("ROLLBACK AND CHAIN", "TRANSACTION", allow_retry: false, materialize_transactions: true)
259
+ end
260
+
261
+ def empty_insert_statement_value(primary_key = nil) # :nodoc:
236
262
  "VALUES ()"
237
263
  end
238
264
 
@@ -270,7 +296,7 @@ module ActiveRecord
270
296
  #
271
297
  # Example:
272
298
  # drop_database('sebastian_development')
273
- def drop_database(name) #:nodoc:
299
+ def drop_database(name) # :nodoc:
274
300
  execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
275
301
  end
276
302
 
@@ -309,11 +335,12 @@ module ActiveRecord
309
335
  #
310
336
  # Example:
311
337
  # rename_table('octopuses', 'octopi')
312
- def rename_table(table_name, new_name)
338
+ def rename_table(table_name, new_name, **options)
339
+ validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
313
340
  schema_cache.clear_data_source_cache!(table_name.to_s)
314
341
  schema_cache.clear_data_source_cache!(new_name.to_s)
315
342
  execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
316
- rename_table_indexes(table_name, new_name)
343
+ rename_table_indexes(table_name, new_name, **options)
317
344
  end
318
345
 
319
346
  # Drops a table from the database.
@@ -346,12 +373,21 @@ module ActiveRecord
346
373
  end
347
374
  end
348
375
 
349
- def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
376
+ def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
377
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
378
+ end
379
+
380
+ def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc:
381
+ column = column_for(table_name, column_name)
382
+ return unless column
383
+
350
384
  default = extract_new_default_value(default_or_changes)
351
- change_column table_name, column_name, nil, default: default
385
+ ChangeColumnDefaultDefinition.new(column, default)
352
386
  end
353
387
 
354
- def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
388
+ def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
389
+ validate_change_column_null_argument!(null)
390
+
355
391
  unless null || default.nil?
356
392
  execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
357
393
  end
@@ -364,22 +400,64 @@ module ActiveRecord
364
400
  change_column table_name, column_name, nil, comment: comment
365
401
  end
366
402
 
367
- def change_column(table_name, column_name, type, **options) #:nodoc:
403
+ def change_column(table_name, column_name, type, **options) # :nodoc:
368
404
  execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, **options)}")
369
405
  end
370
406
 
371
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
407
+ # Builds a ChangeColumnDefinition object.
408
+ #
409
+ # This definition object contains information about the column change that would occur
410
+ # if the same arguments were passed to #change_column. See #change_column for information about
411
+ # passing a +table_name+, +column_name+, +type+ and other options that can be passed.
412
+ def build_change_column_definition(table_name, column_name, type, **options) # :nodoc:
413
+ column = column_for(table_name, column_name)
414
+ type ||= column.sql_type
415
+
416
+ unless options.key?(:default)
417
+ options[:default] = column.default
418
+ end
419
+
420
+ unless options.key?(:null)
421
+ options[:null] = column.null
422
+ end
423
+
424
+ unless options.key?(:comment)
425
+ options[:comment] = column.comment
426
+ end
427
+
428
+ if options[:collation] == :no_collation
429
+ options.delete(:collation)
430
+ else
431
+ options[:collation] ||= column.collation if text_type?(type)
432
+ end
433
+
434
+ unless options.key?(:auto_increment)
435
+ options[:auto_increment] = column.auto_increment?
436
+ end
437
+
438
+ td = create_table_definition(table_name)
439
+ cd = td.new_column_definition(column.name, type, **options)
440
+ ChangeColumnDefinition.new(cd, column.name)
441
+ end
442
+
443
+ def rename_column(table_name, column_name, new_column_name) # :nodoc:
372
444
  execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
373
445
  rename_column_indexes(table_name, column_name, new_column_name)
374
446
  end
375
447
 
376
- def add_index(table_name, column_name, **options) #:nodoc:
448
+ def add_index(table_name, column_name, **options) # :nodoc:
449
+ create_index = build_create_index_definition(table_name, column_name, **options)
450
+ return unless create_index
451
+
452
+ execute schema_creation.accept(create_index)
453
+ end
454
+
455
+ def build_create_index_definition(table_name, column_name, **options) # :nodoc:
377
456
  index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
378
457
 
379
458
  return if if_not_exists && index_exists?(table_name, column_name, name: index.name)
380
459
 
381
- create_index = CreateIndexDefinition.new(index, algorithm)
382
- execute schema_creation.accept(create_index)
460
+ CreateIndexDefinition.new(index, algorithm)
383
461
  end
384
462
 
385
463
  def add_sql_comment!(sql, comment) # :nodoc:
@@ -392,11 +470,13 @@ module ActiveRecord
392
470
 
393
471
  scope = quoted_scope(table_name)
394
472
 
395
- fk_info = exec_query(<<~SQL, "SCHEMA")
473
+ # MySQL returns 1 row for each column of composite foreign keys.
474
+ fk_info = internal_exec_query(<<~SQL, "SCHEMA")
396
475
  SELECT fk.referenced_table_name AS 'to_table',
397
476
  fk.referenced_column_name AS 'primary_key',
398
477
  fk.column_name AS 'column',
399
478
  fk.constraint_name AS 'name',
479
+ fk.ordinal_position AS 'position',
400
480
  rc.update_rule AS 'on_update',
401
481
  rc.delete_rule AS 'on_delete'
402
482
  FROM information_schema.referential_constraints rc
@@ -409,17 +489,24 @@ module ActiveRecord
409
489
  AND rc.table_name = #{scope[:name]}
410
490
  SQL
411
491
 
412
- fk_info.map do |row|
492
+ grouped_fk = fk_info.group_by { |row| row["name"] }.values.each { |group| group.sort_by! { |row| row["position"] } }
493
+ grouped_fk.map do |group|
494
+ row = group.first
413
495
  options = {
414
- column: row["column"],
415
496
  name: row["name"],
416
- primary_key: row["primary_key"]
497
+ on_update: extract_foreign_key_action(row["on_update"]),
498
+ on_delete: extract_foreign_key_action(row["on_delete"])
417
499
  }
418
500
 
419
- options[:on_update] = extract_foreign_key_action(row["on_update"])
420
- options[:on_delete] = extract_foreign_key_action(row["on_delete"])
501
+ if group.one?
502
+ options[:column] = unquote_identifier(row["column"])
503
+ options[:primary_key] = row["primary_key"]
504
+ else
505
+ options[:column] = group.map { |row| unquote_identifier(row["column"]) }
506
+ options[:primary_key] = group.map { |row| row["primary_key"] }
507
+ end
421
508
 
422
- ForeignKeyDefinition.new(table_name, row["to_table"], options)
509
+ ForeignKeyDefinition.new(table_name, unquote_identifier(row["to_table"]), options)
423
510
  end
424
511
  end
425
512
 
@@ -427,7 +514,7 @@ module ActiveRecord
427
514
  if supports_check_constraints?
428
515
  scope = quoted_scope(table_name)
429
516
 
430
- chk_info = exec_query(<<~SQL, "SCHEMA")
517
+ sql = <<~SQL
431
518
  SELECT cc.constraint_name AS 'name',
432
519
  cc.check_clause AS 'expression'
433
520
  FROM information_schema.check_constraints cc
@@ -437,13 +524,24 @@ module ActiveRecord
437
524
  AND tc.table_name = #{scope[:name]}
438
525
  AND cc.constraint_schema = #{scope[:schema]}
439
526
  SQL
527
+ sql += " AND cc.table_name = #{scope[:name]}" if mariadb?
528
+
529
+ chk_info = internal_exec_query(sql, "SCHEMA")
440
530
 
441
531
  chk_info.map do |row|
442
532
  options = {
443
533
  name: row["name"]
444
534
  }
445
535
  expression = row["expression"]
446
- expression = expression[1..-2] unless mariadb? # remove parentheses added by mysql
536
+ expression = expression[1..-2] if expression.start_with?("(") && expression.end_with?(")")
537
+ expression = strip_whitespace_characters(expression)
538
+
539
+ unless mariadb?
540
+ # MySQL returns check constraints expression in an already escaped form.
541
+ # This leads to duplicate escaping later (e.g. when the expression is used in the SchemaDumper).
542
+ expression = expression.gsub("\\'", "'")
543
+ end
544
+
447
545
  CheckConstraintDefinition.new(table_name, expression, options)
448
546
  end
449
547
  else
@@ -541,82 +639,168 @@ module ActiveRecord
541
639
  end
542
640
 
543
641
  def build_insert_sql(insert) # :nodoc:
544
- sql = +"INSERT #{insert.into} #{insert.values_list}"
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}"
545
665
 
546
- if insert.skip_duplicates?
547
- no_op_column = quote_column_name(insert.keys.first)
548
- sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
549
- elsif insert.update_duplicates?
550
- sql << " ON DUPLICATE KEY UPDATE "
551
- sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
552
- sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
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
678
+ end
553
679
  end
554
680
 
681
+ sql << " RETURNING #{insert.returning}" if insert.returning
555
682
  sql
556
683
  end
557
684
 
558
685
  def check_version # :nodoc:
559
686
  if database_version < "5.5.8"
560
- 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."
561
688
  end
562
689
  end
563
690
 
564
- private
565
- def initialize_type_map(m = type_map)
566
- super
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)
699
+ end
700
+ end
567
701
 
568
- m.register_type(%r(char)i) do |sql_type|
569
- limit = extract_limit(sql_type)
570
- Type.lookup(:string, adapter: :mysql2, limit: limit)
702
+ class << self
703
+ def extended_type_map(default_timezone: nil, emulate_booleans:) # :nodoc:
704
+ super(default_timezone: default_timezone).tap do |m|
705
+ if emulate_booleans
706
+ m.register_type %r(^tinyint\(1\))i, Type::Boolean.new
707
+ end
571
708
  end
709
+ end
572
710
 
573
- m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
574
- m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
575
- m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
576
- m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
577
- m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
578
- m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
579
- m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
580
- m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
581
- m.register_type %r(^float)i, Type::Float.new(limit: 24)
582
- m.register_type %r(^double)i, Type::Float.new(limit: 53)
583
-
584
- register_integer_type m, %r(^bigint)i, limit: 8
585
- register_integer_type m, %r(^int)i, limit: 4
586
- register_integer_type m, %r(^mediumint)i, limit: 3
587
- register_integer_type m, %r(^smallint)i, limit: 2
588
- register_integer_type m, %r(^tinyint)i, limit: 1
589
-
590
- m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
591
- m.alias_type %r(year)i, "integer"
592
- m.alias_type %r(bit)i, "binary"
593
-
594
- m.register_type %r(^enum)i, Type.lookup(:string, adapter: :mysql2)
595
- m.register_type %r(^set)i, Type.lookup(:string, adapter: :mysql2)
596
- end
597
-
598
- def register_integer_type(mapping, key, **options)
599
- mapping.register_type(key) do |sql_type|
600
- if /\bunsigned\b/.match?(sql_type)
601
- Type::UnsignedInteger.new(**options)
711
+ private
712
+ def initialize_type_map(m)
713
+ super
714
+
715
+ m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
716
+ m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
717
+ m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
718
+ m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
719
+ m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
720
+ m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
721
+ m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
722
+ m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
723
+ m.register_type %r(^float)i, Type::Float.new(limit: 24)
724
+ m.register_type %r(^double)i, Type::Float.new(limit: 53)
725
+
726
+ register_integer_type m, %r(^bigint)i, limit: 8
727
+ register_integer_type m, %r(^int)i, limit: 4
728
+ register_integer_type m, %r(^mediumint)i, limit: 3
729
+ register_integer_type m, %r(^smallint)i, limit: 2
730
+ register_integer_type m, %r(^tinyint)i, limit: 1
731
+
732
+ m.alias_type %r(year)i, "integer"
733
+ m.alias_type %r(bit)i, "binary"
734
+ end
735
+
736
+ def register_integer_type(mapping, key, **options)
737
+ mapping.register_type(key) do |sql_type|
738
+ if /\bunsigned\b/.match?(sql_type)
739
+ Type::UnsignedInteger.new(**options)
740
+ else
741
+ Type::Integer.new(**options)
742
+ end
743
+ end
744
+ end
745
+
746
+ def extract_precision(sql_type)
747
+ if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
748
+ super || 0
602
749
  else
603
- Type::Integer.new(**options)
750
+ super
604
751
  end
605
752
  end
753
+ end
754
+
755
+ EXTENDED_TYPE_MAPS = Concurrent::Map.new
756
+ EMULATE_BOOLEANS_TRUE = { emulate_booleans: true }.freeze
757
+
758
+ private
759
+ def strip_whitespace_characters(expression)
760
+ expression = expression.gsub(/\\n|\\\\/, "")
761
+ expression = expression.gsub(/\s{2,}/, " ")
762
+ expression
606
763
  end
607
764
 
608
- def extract_precision(sql_type)
609
- if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
610
- super || 0
611
- else
612
- super
765
+ def extended_type_map_key
766
+ if @default_timezone
767
+ { default_timezone: @default_timezone, emulate_booleans: emulate_booleans }
768
+ elsif emulate_booleans
769
+ EMULATE_BOOLEANS_TRUE
770
+ end
771
+ end
772
+
773
+ def handle_warnings(sql)
774
+ return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
775
+
776
+ @affected_rows_before_warnings = @raw_connection.affected_rows
777
+ warning_count = @raw_connection.warning_count
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
782
+ result.each do |level, code, message|
783
+ warning = SQLWarning.new(message, code, level, sql, @pool)
784
+ next if warning_ignored?(warning)
785
+
786
+ ActiveRecord.db_warnings_action.call(warning)
613
787
  end
614
788
  end
615
789
 
790
+ def warning_ignored?(warning)
791
+ warning.level == "Note" || super
792
+ end
793
+
794
+ # Make sure we carry over any changes to ActiveRecord.default_timezone that have been
795
+ # made since we established the connection
796
+ def sync_timezone_changes(raw_connection)
797
+ end
798
+
616
799
  # See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html
617
800
  ER_DB_CREATE_EXISTS = 1007
618
801
  ER_FILSORT_ABORT = 1028
619
802
  ER_DUP_ENTRY = 1062
803
+ ER_SERVER_SHUTDOWN = 1053
620
804
  ER_NOT_NULL_VIOLATION = 1048
621
805
  ER_NO_REFERENCED_ROW = 1216
622
806
  ER_ROW_IS_REFERENCED = 1217
@@ -630,69 +814,59 @@ module ActiveRecord
630
814
  ER_CANNOT_CREATE_TABLE = 1005
631
815
  ER_LOCK_WAIT_TIMEOUT = 1205
632
816
  ER_QUERY_INTERRUPTED = 1317
817
+ ER_CONNECTION_KILLED = 1927
818
+ CR_SERVER_GONE_ERROR = 2006
819
+ CR_SERVER_LOST = 2013
633
820
  ER_QUERY_TIMEOUT = 3024
634
821
  ER_FK_INCOMPATIBLE_COLUMNS = 3780
822
+ ER_CLIENT_INTERACTION_TIMEOUT = 4031
635
823
 
636
824
  def translate_exception(exception, message:, sql:, binds:)
637
825
  case error_number(exception)
638
826
  when nil
639
827
  if exception.message.match?(/MySQL client is not connected/i)
640
- ConnectionNotEstablished.new(exception)
828
+ ConnectionNotEstablished.new(exception, connection_pool: @pool)
641
829
  else
642
830
  super
643
831
  end
832
+ when ER_CONNECTION_KILLED, ER_SERVER_SHUTDOWN, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
833
+ ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool)
644
834
  when ER_DB_CREATE_EXISTS
645
- DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
835
+ DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
646
836
  when ER_DUP_ENTRY
647
- RecordNotUnique.new(message, sql: sql, binds: binds)
837
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
648
838
  when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
649
- InvalidForeignKey.new(message, sql: sql, binds: binds)
839
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
650
840
  when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS
651
- mismatched_foreign_key(message, sql: sql, binds: binds)
841
+ mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
652
842
  when ER_CANNOT_CREATE_TABLE
653
843
  if message.include?("errno: 150")
654
- mismatched_foreign_key(message, sql: sql, binds: binds)
844
+ mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
655
845
  else
656
846
  super
657
847
  end
658
848
  when ER_DATA_TOO_LONG
659
- ValueTooLong.new(message, sql: sql, binds: binds)
849
+ ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
660
850
  when ER_OUT_OF_RANGE
661
- RangeError.new(message, sql: sql, binds: binds)
851
+ RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
662
852
  when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
663
- NotNullViolation.new(message, sql: sql, binds: binds)
853
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
664
854
  when ER_LOCK_DEADLOCK
665
- Deadlocked.new(message, sql: sql, binds: binds)
855
+ Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
666
856
  when ER_LOCK_WAIT_TIMEOUT
667
- LockWaitTimeout.new(message, sql: sql, binds: binds)
857
+ LockWaitTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
668
858
  when ER_QUERY_TIMEOUT, ER_FILSORT_ABORT
669
- StatementTimeout.new(message, sql: sql, binds: binds)
859
+ StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
670
860
  when ER_QUERY_INTERRUPTED
671
- QueryCanceled.new(message, sql: sql, binds: binds)
861
+ QueryCanceled.new(message, sql: sql, binds: binds, connection_pool: @pool)
672
862
  else
673
863
  super
674
864
  end
675
865
  end
676
866
 
677
867
  def change_column_for_alter(table_name, column_name, type, **options)
678
- column = column_for(table_name, column_name)
679
- type ||= column.sql_type
680
-
681
- unless options.key?(:default)
682
- options[:default] = column.default
683
- end
684
-
685
- unless options.key?(:null)
686
- options[:null] = column.null
687
- end
688
-
689
- unless options.key?(:comment)
690
- options[:comment] = column.comment
691
- end
692
-
693
- td = create_table_definition(table_name)
694
- cd = td.new_column_definition(column.name, type, **options)
695
- schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
868
+ cd = build_change_column_definition(table_name, column_name, type, **options)
869
+ schema_creation.accept(cd)
696
870
  end
697
871
 
698
872
  def rename_column_for_alter(table_name, column_name, new_column_name)
@@ -706,7 +880,7 @@ module ActiveRecord
706
880
  comment: column.comment
707
881
  }
708
882
 
709
- current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
883
+ current_type = internal_exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
710
884
  td = create_table_definition(table_name)
711
885
  cd = td.new_column_definition(new_column_name, current_type, **options)
712
886
  schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
@@ -724,6 +898,10 @@ module ActiveRecord
724
898
  "DROP INDEX #{quote_column_name(index_name)}"
725
899
  end
726
900
 
901
+ def supports_insert_raw_alias_syntax?
902
+ !mariadb? && database_version >= "8.0.19"
903
+ end
904
+
727
905
  def supports_rename_index?
728
906
  if mariadb?
729
907
  database_version >= "10.5.2"
@@ -741,11 +919,9 @@ module ActiveRecord
741
919
  end
742
920
 
743
921
  def configure_connection
922
+ super
744
923
  variables = @config.fetch(:variables, {}).stringify_keys
745
924
 
746
- # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
747
- variables["sql_auto_is_null"] = 0
748
-
749
925
  # Increase timeout so the server doesn't disconnect us.
750
926
  wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
751
927
  wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
@@ -780,17 +956,16 @@ module ActiveRecord
780
956
  end
781
957
 
782
958
  # Gather up all of the SET variables...
783
- variable_assignments = variables.map do |k, v|
959
+ variable_assignments = variables.filter_map do |k, v|
784
960
  if defaults.include?(v)
785
961
  "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
786
962
  elsif !v.nil?
787
963
  "@@SESSION.#{k} = #{quote(v)}"
788
964
  end
789
- # or else nil; compact to clear nils out
790
- end.compact.join(", ")
965
+ end.join(", ")
791
966
 
792
967
  # ...and send them all in one query
793
- execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
968
+ internal_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}")
794
969
  end
795
970
 
796
971
  def column_definitions(table_name) # :nodoc:
@@ -800,7 +975,7 @@ module ActiveRecord
800
975
  end
801
976
 
802
977
  def create_table_info(table_name) # :nodoc:
803
- exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
978
+ internal_exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
804
979
  end
805
980
 
806
981
  def arel_visitor
@@ -811,18 +986,17 @@ module ActiveRecord
811
986
  StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
812
987
  end
813
988
 
814
- def mismatched_foreign_key(message, sql:, binds:)
989
+ def mismatched_foreign_key_details(message:, sql:)
990
+ foreign_key_pat =
991
+ /Referencing column '(\w+)' and referenced/i =~ message ? $1 : '\w+'
992
+
815
993
  match = %r/
816
994
  (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
817
- FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
995
+ FOREIGN\s+KEY\s*\(`?(?<foreign_key>#{foreign_key_pat})`?\)\s*
818
996
  REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
819
997
  /xmi.match(sql)
820
998
 
821
- options = {
822
- message: message,
823
- sql: sql,
824
- binds: binds,
825
- }
999
+ options = {}
826
1000
 
827
1001
  if match
828
1002
  options[:table] = match[:table]
@@ -832,24 +1006,33 @@ module ActiveRecord
832
1006
  options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
833
1007
  end
834
1008
 
835
- MismatchedForeignKey.new(**options)
1009
+ options
836
1010
  end
837
1011
 
838
- def version_string(full_version_string)
839
- full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
840
- end
1012
+ def mismatched_foreign_key(message, sql:, binds:, connection_pool:)
1013
+ options = {
1014
+ message: message,
1015
+ sql: sql,
1016
+ binds: binds,
1017
+ connection_pool: connection_pool
1018
+ }
841
1019
 
842
- # Alias MysqlString to work Mashal.load(File.read("legacy_record.dump")).
843
- # TODO: Remove the constant alias once Rails 6.1 has released.
844
- MysqlString = Type::String # :nodoc:
1020
+ if sql
1021
+ options.update mismatched_foreign_key_details(message: message, sql: sql)
1022
+ else
1023
+ options[:query_parser] = ->(sql) { mismatched_foreign_key_details(message: message, sql: sql) }
1024
+ end
845
1025
 
846
- ActiveRecord::Type.register(:immutable_string, adapter: :mysql2) do |_, **args|
847
- Type::ImmutableString.new(true: "1", false: "0", **args)
1026
+ MismatchedForeignKey.new(**options)
848
1027
  end
849
- ActiveRecord::Type.register(:string, adapter: :mysql2) do |_, **args|
850
- Type::String.new(true: "1", false: "0", **args)
1028
+
1029
+ def version_string(full_version_string)
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
851
1035
  end
852
- ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
853
1036
  end
854
1037
  end
855
1038
  end