activerecord 5.2.8 → 7.0.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (364) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1393 -587
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +7 -5
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +10 -9
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +19 -16
  9. data/lib/active_record/associations/association.rb +122 -47
  10. data/lib/active_record/associations/association_scope.rb +24 -24
  11. data/lib/active_record/associations/belongs_to_association.rb +67 -49
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -7
  13. data/lib/active_record/associations/builder/association.rb +52 -23
  14. data/lib/active_record/associations/builder/belongs_to.rb +44 -61
  15. data/lib/active_record/associations/builder/collection_association.rb +17 -19
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
  17. data/lib/active_record/associations/builder/has_many.rb +10 -3
  18. data/lib/active_record/associations/builder/has_one.rb +35 -3
  19. data/lib/active_record/associations/builder/singular_association.rb +5 -3
  20. data/lib/active_record/associations/collection_association.rb +59 -50
  21. data/lib/active_record/associations/collection_proxy.rb +32 -23
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/foreign_association.rb +20 -0
  24. data/lib/active_record/associations/has_many_association.rb +27 -14
  25. data/lib/active_record/associations/has_many_through_association.rb +26 -19
  26. data/lib/active_record/associations/has_one_association.rb +52 -37
  27. data/lib/active_record/associations/has_one_through_association.rb +6 -6
  28. data/lib/active_record/associations/join_dependency/join_association.rb +44 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
  30. data/lib/active_record/associations/join_dependency.rb +97 -62
  31. data/lib/active_record/associations/preloader/association.rb +220 -60
  32. data/lib/active_record/associations/preloader/batch.rb +48 -0
  33. data/lib/active_record/associations/preloader/branch.rb +147 -0
  34. data/lib/active_record/associations/preloader/through_association.rb +85 -40
  35. data/lib/active_record/associations/preloader.rb +44 -105
  36. data/lib/active_record/associations/singular_association.rb +9 -17
  37. data/lib/active_record/associations/through_association.rb +4 -4
  38. data/lib/active_record/associations.rb +207 -66
  39. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  40. data/lib/active_record/attribute_assignment.rb +17 -19
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +19 -8
  42. data/lib/active_record/attribute_methods/dirty.rb +141 -47
  43. data/lib/active_record/attribute_methods/primary_key.rb +22 -27
  44. data/lib/active_record/attribute_methods/query.rb +6 -10
  45. data/lib/active_record/attribute_methods/read.rb +15 -55
  46. data/lib/active_record/attribute_methods/serialization.rb +77 -18
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +16 -18
  48. data/lib/active_record/attribute_methods/write.rb +18 -37
  49. data/lib/active_record/attribute_methods.rb +90 -153
  50. data/lib/active_record/attributes.rb +38 -12
  51. data/lib/active_record/autosave_association.rb +50 -50
  52. data/lib/active_record/base.rb +23 -18
  53. data/lib/active_record/callbacks.rb +159 -44
  54. data/lib/active_record/coders/yaml_column.rb +12 -3
  55. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  58. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +92 -464
  59. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -51
  60. data/lib/active_record/connection_adapters/abstract/database_statements.rb +209 -164
  61. data/lib/active_record/connection_adapters/abstract/query_cache.rb +38 -22
  62. data/lib/active_record/connection_adapters/abstract/quoting.rb +103 -82
  63. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  64. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +140 -110
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -94
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +16 -5
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +456 -159
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +169 -78
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +367 -162
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +311 -327
  71. data/lib/active_record/connection_adapters/column.rb +33 -11
  72. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  73. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  74. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  75. data/lib/active_record/connection_adapters/mysql/database_statements.rb +113 -45
  76. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  77. data/lib/active_record/connection_adapters/mysql/quoting.rb +71 -5
  78. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
  79. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
  80. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +25 -8
  81. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +143 -19
  82. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
  83. data/lib/active_record/connection_adapters/mysql2_adapter.rb +63 -22
  84. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  85. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  86. data/lib/active_record/connection_adapters/postgresql/column.rb +53 -28
  87. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +56 -63
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +54 -16
  95. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
  97. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  102. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +26 -12
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
  106. data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
  107. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -52
  108. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -2
  109. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +39 -4
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +128 -91
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -1
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +149 -113
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +386 -182
  116. data/lib/active_record/connection_adapters/schema_cache.rb +161 -22
  117. data/lib/active_record/connection_adapters/sql_type_metadata.rb +17 -6
  118. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +152 -0
  119. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +65 -18
  120. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  121. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +92 -26
  122. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +251 -204
  123. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  124. data/lib/active_record/connection_adapters.rb +53 -0
  125. data/lib/active_record/connection_handling.rb +292 -38
  126. data/lib/active_record/core.rb +385 -158
  127. data/lib/active_record/counter_cache.rb +8 -30
  128. data/lib/active_record/database_configurations/connection_url_resolver.rb +100 -0
  129. data/lib/active_record/database_configurations/database_config.rb +83 -0
  130. data/lib/active_record/database_configurations/hash_config.rb +154 -0
  131. data/lib/active_record/database_configurations/url_config.rb +53 -0
  132. data/lib/active_record/database_configurations.rb +256 -0
  133. data/lib/active_record/delegated_type.rb +250 -0
  134. data/lib/active_record/destroy_association_async_job.rb +36 -0
  135. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  136. data/lib/active_record/dynamic_matchers.rb +4 -5
  137. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  138. data/lib/active_record/encryption/cipher.rb +53 -0
  139. data/lib/active_record/encryption/config.rb +44 -0
  140. data/lib/active_record/encryption/configurable.rb +61 -0
  141. data/lib/active_record/encryption/context.rb +35 -0
  142. data/lib/active_record/encryption/contexts.rb +72 -0
  143. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  144. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  145. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  146. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -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 +155 -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 +160 -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 +42 -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_serializer.rb +90 -0
  159. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  160. data/lib/active_record/encryption/properties.rb +76 -0
  161. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  162. data/lib/active_record/encryption/scheme.rb +99 -0
  163. data/lib/active_record/encryption.rb +55 -0
  164. data/lib/active_record/enum.rb +130 -51
  165. data/lib/active_record/errors.rb +129 -23
  166. data/lib/active_record/explain.rb +10 -6
  167. data/lib/active_record/explain_registry.rb +11 -6
  168. data/lib/active_record/explain_subscriber.rb +1 -1
  169. data/lib/active_record/fixture_set/file.rb +22 -15
  170. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  171. data/lib/active_record/fixture_set/render_context.rb +17 -0
  172. data/lib/active_record/fixture_set/table_row.rb +187 -0
  173. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  174. data/lib/active_record/fixtures.rb +206 -490
  175. data/lib/active_record/future_result.rb +139 -0
  176. data/lib/active_record/gem_version.rb +3 -3
  177. data/lib/active_record/inheritance.rb +104 -37
  178. data/lib/active_record/insert_all.rb +278 -0
  179. data/lib/active_record/integration.rb +69 -18
  180. data/lib/active_record/internal_metadata.rb +24 -9
  181. data/lib/active_record/legacy_yaml_adapter.rb +3 -36
  182. data/lib/active_record/locking/optimistic.rb +41 -26
  183. data/lib/active_record/locking/pessimistic.rb +18 -8
  184. data/lib/active_record/log_subscriber.rb +46 -35
  185. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  186. data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
  187. data/lib/active_record/middleware/database_selector.rb +82 -0
  188. data/lib/active_record/middleware/shard_selector.rb +60 -0
  189. data/lib/active_record/migration/command_recorder.rb +96 -44
  190. data/lib/active_record/migration/compatibility.rb +246 -64
  191. data/lib/active_record/migration/join_table.rb +1 -2
  192. data/lib/active_record/migration.rb +266 -187
  193. data/lib/active_record/model_schema.rb +165 -52
  194. data/lib/active_record/nested_attributes.rb +17 -19
  195. data/lib/active_record/no_touching.rb +11 -4
  196. data/lib/active_record/null_relation.rb +2 -7
  197. data/lib/active_record/persistence.rb +467 -92
  198. data/lib/active_record/query_cache.rb +21 -4
  199. data/lib/active_record/query_logs.rb +138 -0
  200. data/lib/active_record/querying.rb +51 -24
  201. data/lib/active_record/railtie.rb +224 -57
  202. data/lib/active_record/railties/console_sandbox.rb +2 -4
  203. data/lib/active_record/railties/controller_runtime.rb +31 -36
  204. data/lib/active_record/railties/databases.rake +369 -101
  205. data/lib/active_record/readonly_attributes.rb +15 -0
  206. data/lib/active_record/reflection.rb +170 -137
  207. data/lib/active_record/relation/batches/batch_enumerator.rb +44 -14
  208. data/lib/active_record/relation/batches.rb +46 -37
  209. data/lib/active_record/relation/calculations.rb +168 -96
  210. data/lib/active_record/relation/delegation.rb +37 -52
  211. data/lib/active_record/relation/finder_methods.rb +79 -58
  212. data/lib/active_record/relation/from_clause.rb +5 -1
  213. data/lib/active_record/relation/merger.rb +50 -51
  214. data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
  215. data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
  216. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  217. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +11 -10
  218. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  219. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  220. data/lib/active_record/relation/predicate_builder.rb +58 -46
  221. data/lib/active_record/relation/query_attribute.rb +9 -10
  222. data/lib/active_record/relation/query_methods.rb +685 -208
  223. data/lib/active_record/relation/record_fetch_warning.rb +9 -11
  224. data/lib/active_record/relation/spawn_methods.rb +10 -10
  225. data/lib/active_record/relation/where_clause.rb +108 -64
  226. data/lib/active_record/relation.rb +515 -151
  227. data/lib/active_record/result.rb +78 -42
  228. data/lib/active_record/runtime_registry.rb +9 -13
  229. data/lib/active_record/sanitization.rb +29 -44
  230. data/lib/active_record/schema.rb +37 -31
  231. data/lib/active_record/schema_dumper.rb +74 -23
  232. data/lib/active_record/schema_migration.rb +7 -9
  233. data/lib/active_record/scoping/default.rb +62 -17
  234. data/lib/active_record/scoping/named.rb +17 -32
  235. data/lib/active_record/scoping.rb +70 -41
  236. data/lib/active_record/secure_token.rb +16 -8
  237. data/lib/active_record/serialization.rb +6 -4
  238. data/lib/active_record/signed_id.rb +116 -0
  239. data/lib/active_record/statement_cache.rb +49 -6
  240. data/lib/active_record/store.rb +88 -9
  241. data/lib/active_record/suppressor.rb +13 -17
  242. data/lib/active_record/table_metadata.rb +42 -43
  243. data/lib/active_record/tasks/database_tasks.rb +352 -94
  244. data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
  245. data/lib/active_record/tasks/postgresql_database_tasks.rb +41 -39
  246. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
  247. data/lib/active_record/test_databases.rb +24 -0
  248. data/lib/active_record/test_fixtures.rb +287 -0
  249. data/lib/active_record/timestamp.rb +44 -34
  250. data/lib/active_record/touch_later.rb +23 -22
  251. data/lib/active_record/transactions.rb +67 -128
  252. data/lib/active_record/translation.rb +3 -3
  253. data/lib/active_record/type/adapter_specific_registry.rb +34 -19
  254. data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
  255. data/lib/active_record/type/internal/timezone.rb +2 -2
  256. data/lib/active_record/type/serialized.rb +7 -4
  257. data/lib/active_record/type/time.rb +10 -0
  258. data/lib/active_record/type/type_map.rb +17 -21
  259. data/lib/active_record/type/unsigned_integer.rb +0 -1
  260. data/lib/active_record/type.rb +9 -5
  261. data/lib/active_record/type_caster/connection.rb +15 -15
  262. data/lib/active_record/type_caster/map.rb +8 -8
  263. data/lib/active_record/validations/associated.rb +2 -3
  264. data/lib/active_record/validations/numericality.rb +35 -0
  265. data/lib/active_record/validations/uniqueness.rb +39 -31
  266. data/lib/active_record/validations.rb +4 -3
  267. data/lib/active_record.rb +209 -32
  268. data/lib/arel/alias_predication.rb +9 -0
  269. data/lib/arel/attributes/attribute.rb +33 -0
  270. data/lib/arel/collectors/bind.rb +29 -0
  271. data/lib/arel/collectors/composite.rb +39 -0
  272. data/lib/arel/collectors/plain_string.rb +20 -0
  273. data/lib/arel/collectors/sql_string.rb +27 -0
  274. data/lib/arel/collectors/substitute_binds.rb +35 -0
  275. data/lib/arel/crud.rb +48 -0
  276. data/lib/arel/delete_manager.rb +32 -0
  277. data/lib/arel/errors.rb +9 -0
  278. data/lib/arel/expressions.rb +29 -0
  279. data/lib/arel/factory_methods.rb +49 -0
  280. data/lib/arel/filter_predications.rb +9 -0
  281. data/lib/arel/insert_manager.rb +48 -0
  282. data/lib/arel/math.rb +45 -0
  283. data/lib/arel/nodes/and.rb +32 -0
  284. data/lib/arel/nodes/ascending.rb +23 -0
  285. data/lib/arel/nodes/binary.rb +126 -0
  286. data/lib/arel/nodes/bind_param.rb +44 -0
  287. data/lib/arel/nodes/case.rb +55 -0
  288. data/lib/arel/nodes/casted.rb +62 -0
  289. data/lib/arel/nodes/comment.rb +29 -0
  290. data/lib/arel/nodes/count.rb +12 -0
  291. data/lib/arel/nodes/delete_statement.rb +44 -0
  292. data/lib/arel/nodes/descending.rb +23 -0
  293. data/lib/arel/nodes/equality.rb +15 -0
  294. data/lib/arel/nodes/extract.rb +24 -0
  295. data/lib/arel/nodes/false.rb +16 -0
  296. data/lib/arel/nodes/filter.rb +10 -0
  297. data/lib/arel/nodes/full_outer_join.rb +8 -0
  298. data/lib/arel/nodes/function.rb +45 -0
  299. data/lib/arel/nodes/grouping.rb +11 -0
  300. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  301. data/lib/arel/nodes/in.rb +15 -0
  302. data/lib/arel/nodes/infix_operation.rb +92 -0
  303. data/lib/arel/nodes/inner_join.rb +8 -0
  304. data/lib/arel/nodes/insert_statement.rb +37 -0
  305. data/lib/arel/nodes/join_source.rb +20 -0
  306. data/lib/arel/nodes/matches.rb +18 -0
  307. data/lib/arel/nodes/named_function.rb +23 -0
  308. data/lib/arel/nodes/node.rb +51 -0
  309. data/lib/arel/nodes/node_expression.rb +13 -0
  310. data/lib/arel/nodes/ordering.rb +27 -0
  311. data/lib/arel/nodes/outer_join.rb +8 -0
  312. data/lib/arel/nodes/over.rb +15 -0
  313. data/lib/arel/nodes/regexp.rb +16 -0
  314. data/lib/arel/nodes/right_outer_join.rb +8 -0
  315. data/lib/arel/nodes/select_core.rb +67 -0
  316. data/lib/arel/nodes/select_statement.rb +41 -0
  317. data/lib/arel/nodes/sql_literal.rb +19 -0
  318. data/lib/arel/nodes/string_join.rb +11 -0
  319. data/lib/arel/nodes/table_alias.rb +31 -0
  320. data/lib/arel/nodes/terminal.rb +16 -0
  321. data/lib/arel/nodes/true.rb +16 -0
  322. data/lib/arel/nodes/unary.rb +44 -0
  323. data/lib/arel/nodes/unary_operation.rb +20 -0
  324. data/lib/arel/nodes/unqualified_column.rb +22 -0
  325. data/lib/arel/nodes/update_statement.rb +46 -0
  326. data/lib/arel/nodes/values_list.rb +9 -0
  327. data/lib/arel/nodes/window.rb +126 -0
  328. data/lib/arel/nodes/with.rb +11 -0
  329. data/lib/arel/nodes.rb +71 -0
  330. data/lib/arel/order_predications.rb +13 -0
  331. data/lib/arel/predications.rb +258 -0
  332. data/lib/arel/select_manager.rb +276 -0
  333. data/lib/arel/table.rb +117 -0
  334. data/lib/arel/tree_manager.rb +60 -0
  335. data/lib/arel/update_manager.rb +48 -0
  336. data/lib/arel/visitors/dot.rb +298 -0
  337. data/lib/arel/visitors/mysql.rb +99 -0
  338. data/lib/arel/visitors/postgresql.rb +110 -0
  339. data/lib/arel/visitors/sqlite.rb +38 -0
  340. data/lib/arel/visitors/to_sql.rb +955 -0
  341. data/lib/arel/visitors/visitor.rb +45 -0
  342. data/lib/arel/visitors.rb +13 -0
  343. data/lib/arel/window_predications.rb +9 -0
  344. data/lib/arel.rb +55 -0
  345. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  346. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  347. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
  348. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
  349. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
  350. data/lib/rails/generators/active_record/migration.rb +19 -2
  351. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  352. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  353. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  354. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  355. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  356. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  357. metadata +162 -32
  358. data/lib/active_record/attribute_decorators.rb +0 -90
  359. data/lib/active_record/collection_cache_key.rb +0 -53
  360. data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
  361. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
  362. data/lib/active_record/define_callbacks.rb +0 -22
  363. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
  364. data/lib/active_record/relation/where_clause_factory.rb +0 -34
@@ -11,8 +11,6 @@ require "active_record/connection_adapters/mysql/schema_dumper"
11
11
  require "active_record/connection_adapters/mysql/schema_statements"
12
12
  require "active_record/connection_adapters/mysql/type_metadata"
13
13
 
14
- require "active_support/core_ext/string/strip"
15
-
16
14
  module ActiveRecord
17
15
  module ConnectionAdapters
18
16
  class AbstractMysqlAdapter < AbstractAdapter
@@ -31,49 +29,52 @@ module ActiveRecord
31
29
  NATIVE_DATABASE_TYPES = {
32
30
  primary_key: "bigint auto_increment PRIMARY KEY",
33
31
  string: { name: "varchar", limit: 255 },
34
- text: { name: "text", limit: 65535 },
32
+ text: { name: "text" },
35
33
  integer: { name: "int", limit: 4 },
34
+ bigint: { name: "bigint" },
36
35
  float: { name: "float", limit: 24 },
37
36
  decimal: { name: "decimal" },
38
37
  datetime: { name: "datetime" },
39
38
  timestamp: { name: "timestamp" },
40
39
  time: { name: "time" },
41
40
  date: { name: "date" },
42
- binary: { name: "blob", limit: 65535 },
41
+ binary: { name: "blob" },
42
+ blob: { name: "blob" },
43
43
  boolean: { name: "tinyint", limit: 1 },
44
44
  json: { name: "json" },
45
45
  }
46
46
 
47
47
  class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
48
- private def dealloc(stmt)
49
- stmt[:stmt].close
50
- end
48
+ private
49
+ def dealloc(stmt)
50
+ stmt.close
51
+ end
51
52
  end
52
53
 
53
54
  def initialize(connection, logger, connection_options, config)
54
55
  super(connection, logger, config)
55
-
56
- @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
57
-
58
- if version < "5.1.10"
59
- raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.1.10."
60
- end
61
56
  end
62
57
 
63
- def version #:nodoc:
64
- @version ||= Version.new(version_string)
58
+ def get_database_version # :nodoc:
59
+ full_version_string = get_full_version
60
+ version_string = version_string(full_version_string)
61
+ Version.new(version_string, full_version_string)
65
62
  end
66
63
 
67
64
  def mariadb? # :nodoc:
68
65
  /mariadb/i.match?(full_version)
69
66
  end
70
67
 
71
- def supports_bulk_alter? #:nodoc:
68
+ def supports_bulk_alter?
72
69
  true
73
70
  end
74
71
 
75
72
  def supports_index_sort_order?
76
- !mariadb? && version >= "8.0.1"
73
+ !mariadb? && database_version >= "8.0.1"
74
+ end
75
+
76
+ def supports_expression_index?
77
+ !mariadb? && database_version >= "8.0.13"
77
78
  end
78
79
 
79
80
  def supports_transaction_isolation?
@@ -92,23 +93,36 @@ module ActiveRecord
92
93
  true
93
94
  end
94
95
 
96
+ def supports_check_constraints?
97
+ if mariadb?
98
+ database_version >= "10.2.1"
99
+ else
100
+ database_version >= "8.0.16"
101
+ end
102
+ end
103
+
95
104
  def supports_views?
96
105
  true
97
106
  end
98
107
 
99
108
  def supports_datetime_with_precision?
100
- if mariadb?
101
- version >= "5.3.0"
102
- else
103
- version >= "5.6.4"
104
- end
109
+ mariadb? || database_version >= "5.6.4"
105
110
  end
106
111
 
107
112
  def supports_virtual_columns?
113
+ mariadb? || database_version >= "5.7.5"
114
+ end
115
+
116
+ # See https://dev.mysql.com/doc/refman/en/optimizer-hints.html for more details.
117
+ def supports_optimizer_hints?
118
+ !mariadb? && database_version >= "5.7.7"
119
+ end
120
+
121
+ def supports_common_table_expressions?
108
122
  if mariadb?
109
- version >= "5.2.0"
123
+ database_version >= "10.2.1"
110
124
  else
111
- version >= "5.7.5"
125
+ database_version >= "8.0.1"
112
126
  end
113
127
  end
114
128
 
@@ -116,6 +130,19 @@ module ActiveRecord
116
130
  true
117
131
  end
118
132
 
133
+ def supports_insert_on_duplicate_skip?
134
+ true
135
+ end
136
+
137
+ def supports_insert_on_duplicate_update?
138
+ true
139
+ end
140
+
141
+ def field_ordered_value(column, values) # :nodoc:
142
+ field = Arel::Nodes::NamedFunction.new("FIELD", [column, values.reverse])
143
+ Arel::Nodes::Descending.new(field)
144
+ end
145
+
119
146
  def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
120
147
  query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
121
148
  end
@@ -129,7 +156,12 @@ module ActiveRecord
129
156
  end
130
157
 
131
158
  def index_algorithms
132
- { default: "ALGORITHM = DEFAULT".dup, copy: "ALGORITHM = COPY".dup, inplace: "ALGORITHM = INPLACE".dup }
159
+ {
160
+ default: "ALGORITHM = DEFAULT",
161
+ copy: "ALGORITHM = COPY",
162
+ inplace: "ALGORITHM = INPLACE",
163
+ instant: "ALGORITHM = INSTANT",
164
+ }
133
165
  end
134
166
 
135
167
  # HELPER METHODS ===========================================
@@ -148,7 +180,7 @@ module ActiveRecord
148
180
 
149
181
  # REFERENTIAL INTEGRITY ====================================
150
182
 
151
- def disable_referential_integrity #:nodoc:
183
+ def disable_referential_integrity # :nodoc:
152
184
  old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
153
185
 
154
186
  begin
@@ -159,73 +191,40 @@ module ActiveRecord
159
191
  end
160
192
  end
161
193
 
162
- # CONNECTION MANAGEMENT ====================================
163
-
164
- # Clears the prepared statements cache.
165
- def clear_cache!
166
- reload_type_map
167
- @statements.clear
168
- end
169
-
170
194
  #--
171
195
  # DATABASE STATEMENTS ======================================
172
196
  #++
173
197
 
174
- def explain(arel, binds = [])
175
- sql = "EXPLAIN #{to_sql(arel, binds)}"
176
- start = Time.now
177
- result = exec_query(sql, "EXPLAIN", binds)
178
- elapsed = Time.now - start
179
-
180
- MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
181
- end
182
-
183
198
  # Executes the SQL statement in the context of this connection.
184
- def execute(sql, name = nil)
185
- log(sql, name) do
186
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
187
- @connection.query(sql)
188
- end
189
- end
199
+ def execute(sql, name = nil, async: false)
200
+ raw_execute(sql, name, async: async)
190
201
  end
191
202
 
192
203
  # Mysql2Adapter doesn't have to free a result after using it, but we use this method
193
204
  # to write stuff in an abstract way without concerning ourselves about whether it
194
205
  # needs to be explicitly freed or not.
195
- def execute_and_free(sql, name = nil) # :nodoc:
196
- yield execute(sql, name)
206
+ def execute_and_free(sql, name = nil, async: false) # :nodoc:
207
+ yield execute(sql, name, async: async)
197
208
  end
198
209
 
199
- def begin_db_transaction
200
- execute "BEGIN"
210
+ def begin_db_transaction # :nodoc:
211
+ execute("BEGIN", "TRANSACTION")
201
212
  end
202
213
 
203
- def begin_isolated_db_transaction(isolation)
214
+ def begin_isolated_db_transaction(isolation) # :nodoc:
204
215
  execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
205
216
  begin_db_transaction
206
217
  end
207
218
 
208
- def commit_db_transaction #:nodoc:
209
- execute "COMMIT"
219
+ def commit_db_transaction # :nodoc:
220
+ execute("COMMIT", "TRANSACTION")
210
221
  end
211
222
 
212
- def exec_rollback_db_transaction #:nodoc:
213
- execute "ROLLBACK"
223
+ def exec_rollback_db_transaction # :nodoc:
224
+ execute("ROLLBACK", "TRANSACTION")
214
225
  end
215
226
 
216
- # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
217
- # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
218
- # these, we must use a subquery.
219
- def join_to_update(update, select, key) # :nodoc:
220
- if select.limit || select.offset || select.orders.any?
221
- super
222
- else
223
- update.table select.source
224
- update.wheres = select.constraints
225
- end
226
- end
227
-
228
- def empty_insert_statement_value
227
+ def empty_insert_statement_value(primary_key = nil) # :nodoc:
229
228
  "VALUES ()"
230
229
  end
231
230
 
@@ -241,7 +240,7 @@ module ActiveRecord
241
240
  end
242
241
 
243
242
  # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
244
- # Charset defaults to utf8.
243
+ # Charset defaults to utf8mb4.
245
244
  #
246
245
  # Example:
247
246
  # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
@@ -250,8 +249,12 @@ module ActiveRecord
250
249
  def create_database(name, options = {})
251
250
  if options[:collation]
252
251
  execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}"
252
+ elsif options[:charset]
253
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset])}"
254
+ elsif row_format_dynamic_by_default?
255
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET `utf8mb4`"
253
256
  else
254
- execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
257
+ raise "Configure a supported :charset and ensure innodb_large_prefix is enabled to support indexes on varchar(255) string columns."
255
258
  end
256
259
  end
257
260
 
@@ -259,7 +262,7 @@ module ActiveRecord
259
262
  #
260
263
  # Example:
261
264
  # drop_database('sebastian_development')
262
- def drop_database(name) #:nodoc:
265
+ def drop_database(name) # :nodoc:
263
266
  execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
264
267
  end
265
268
 
@@ -277,14 +280,10 @@ module ActiveRecord
277
280
  show_variable "collation_database"
278
281
  end
279
282
 
280
- def truncate(table_name, name = nil)
281
- execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
282
- end
283
-
284
283
  def table_comment(table_name) # :nodoc:
285
284
  scope = quoted_scope(table_name)
286
285
 
287
- query_value(<<-SQL.strip_heredoc, "SCHEMA").presence
286
+ query_value(<<~SQL, "SCHEMA").presence
288
287
  SELECT table_comment
289
288
  FROM information_schema.tables
290
289
  WHERE table_schema = #{scope[:schema]}
@@ -292,22 +291,8 @@ module ActiveRecord
292
291
  SQL
293
292
  end
294
293
 
295
- def bulk_change_table(table_name, operations) #:nodoc:
296
- sqls = operations.flat_map do |command, args|
297
- table, arguments = args.shift, args
298
- method = :"#{command}_for_alter"
299
-
300
- if respond_to?(method, true)
301
- send(method, table, *arguments)
302
- else
303
- raise "Unknown method called : #{method}(#{arguments.inspect})"
304
- end
305
- end.join(", ")
306
-
307
- execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
308
- end
309
-
310
- def change_table_comment(table_name, comment) #:nodoc:
294
+ def change_table_comment(table_name, comment_or_changes) # :nodoc:
295
+ comment = extract_new_comment_value(comment_or_changes)
311
296
  comment = "" if comment.nil?
312
297
  execute("ALTER TABLE #{quote_table_name(table_name)} COMMENT #{quote(comment)}")
313
298
  end
@@ -317,6 +302,8 @@ module ActiveRecord
317
302
  # Example:
318
303
  # rename_table('octopuses', 'octopi')
319
304
  def rename_table(table_name, new_name)
305
+ schema_cache.clear_data_source_cache!(table_name.to_s)
306
+ schema_cache.clear_data_source_cache!(new_name.to_s)
320
307
  execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
321
308
  rename_table_indexes(table_name, new_name)
322
309
  end
@@ -336,7 +323,8 @@ module ActiveRecord
336
323
  # Although this command ignores most +options+ and the block if one is given,
337
324
  # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
338
325
  # In that case, +options+ and the block will be used by create_table.
339
- def drop_table(table_name, options = {})
326
+ def drop_table(table_name, **options)
327
+ schema_cache.clear_data_source_cache!(table_name.to_s)
340
328
  execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
341
329
  end
342
330
 
@@ -350,12 +338,12 @@ module ActiveRecord
350
338
  end
351
339
  end
352
340
 
353
- def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
341
+ def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
354
342
  default = extract_new_default_value(default_or_changes)
355
343
  change_column table_name, column_name, nil, default: default
356
344
  end
357
345
 
358
- def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
346
+ def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
359
347
  unless null || default.nil?
360
348
  execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
361
349
  end
@@ -363,23 +351,27 @@ module ActiveRecord
363
351
  change_column table_name, column_name, nil, null: null
364
352
  end
365
353
 
366
- def change_column_comment(table_name, column_name, comment) #:nodoc:
354
+ def change_column_comment(table_name, column_name, comment_or_changes) # :nodoc:
355
+ comment = extract_new_comment_value(comment_or_changes)
367
356
  change_column table_name, column_name, nil, comment: comment
368
357
  end
369
358
 
370
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
371
- execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, options)}")
359
+ def change_column(table_name, column_name, type, **options) # :nodoc:
360
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, **options)}")
372
361
  end
373
362
 
374
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
363
+ def rename_column(table_name, column_name, new_column_name) # :nodoc:
375
364
  execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
376
365
  rename_column_indexes(table_name, column_name, new_column_name)
377
366
  end
378
367
 
379
- def add_index(table_name, column_name, options = {}) #:nodoc:
380
- index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
381
- sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}".dup
382
- execute add_sql_comment!(sql, comment)
368
+ def add_index(table_name, column_name, **options) # :nodoc:
369
+ index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
370
+
371
+ return if if_not_exists && index_exists?(table_name, column_name, name: index.name)
372
+
373
+ create_index = CreateIndexDefinition.new(index, algorithm)
374
+ execute schema_creation.accept(create_index)
383
375
  end
384
376
 
385
377
  def add_sql_comment!(sql, comment) # :nodoc:
@@ -392,7 +384,7 @@ module ActiveRecord
392
384
 
393
385
  scope = quoted_scope(table_name)
394
386
 
395
- fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA")
387
+ fk_info = exec_query(<<~SQL, "SCHEMA")
396
388
  SELECT fk.referenced_table_name AS 'to_table',
397
389
  fk.referenced_column_name AS 'primary_key',
398
390
  fk.column_name AS 'column',
@@ -423,51 +415,66 @@ module ActiveRecord
423
415
  end
424
416
  end
425
417
 
426
- def table_options(table_name) # :nodoc:
427
- table_options = {}
418
+ def check_constraints(table_name)
419
+ if supports_check_constraints?
420
+ scope = quoted_scope(table_name)
421
+
422
+ sql = <<~SQL
423
+ SELECT cc.constraint_name AS 'name',
424
+ cc.check_clause AS 'expression'
425
+ FROM information_schema.check_constraints cc
426
+ JOIN information_schema.table_constraints tc
427
+ USING (constraint_schema, constraint_name)
428
+ WHERE tc.table_schema = #{scope[:schema]}
429
+ AND tc.table_name = #{scope[:name]}
430
+ AND cc.constraint_schema = #{scope[:schema]}
431
+ SQL
432
+ sql += " AND cc.table_name = #{scope[:name]}" if mariadb?
433
+
434
+ chk_info = exec_query(sql, "SCHEMA")
435
+
436
+ chk_info.map do |row|
437
+ options = {
438
+ name: row["name"]
439
+ }
440
+ expression = row["expression"]
441
+ expression = expression[1..-2] unless mariadb? # remove parentheses added by mysql
442
+ CheckConstraintDefinition.new(table_name, expression, options)
443
+ end
444
+ else
445
+ raise NotImplementedError
446
+ end
447
+ end
428
448
 
449
+ def table_options(table_name) # :nodoc:
429
450
  create_table_info = create_table_info(table_name)
430
451
 
431
452
  # strip create_definitions and partition_options
432
- raw_table_options = create_table_info.sub(/\A.*\n\) /m, "").sub(/\n\/\*!.*\*\/\n\z/m, "").strip
453
+ # Be aware that `create_table_info` might not include any table options due to `NO_TABLE_OPTIONS` sql mode.
454
+ raw_table_options = create_table_info.sub(/\A.*\n\) ?/m, "").sub(/\n\/\*!.*\*\/\n\z/m, "").strip
455
+
456
+ return if raw_table_options.empty?
457
+
458
+ table_options = {}
459
+
460
+ if / DEFAULT CHARSET=(?<charset>\w+)(?: COLLATE=(?<collation>\w+))?/ =~ raw_table_options
461
+ raw_table_options = $` + $' # before part + after part
462
+ table_options[:charset] = charset
463
+ table_options[:collation] = collation if collation
464
+ end
433
465
 
434
466
  # strip AUTO_INCREMENT
435
467
  raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
436
468
 
437
- table_options[:options] = raw_table_options
438
-
439
469
  # strip COMMENT
440
470
  if raw_table_options.sub!(/ COMMENT='.+'/, "")
441
471
  table_options[:comment] = table_comment(table_name)
442
472
  end
443
473
 
474
+ table_options[:options] = raw_table_options unless raw_table_options == "ENGINE=InnoDB"
444
475
  table_options
445
476
  end
446
477
 
447
- # Maps logical Rails types to MySQL-specific data types.
448
- def type_to_sql(type, limit: nil, precision: nil, scale: nil, unsigned: nil, **) # :nodoc:
449
- sql = \
450
- case type.to_s
451
- when "integer"
452
- integer_to_sql(limit)
453
- when "text"
454
- text_to_sql(limit)
455
- when "blob"
456
- binary_to_sql(limit)
457
- when "binary"
458
- if (0..0xfff) === limit
459
- "varbinary(#{limit})"
460
- else
461
- binary_to_sql(limit)
462
- end
463
- else
464
- super
465
- end
466
-
467
- sql = "#{sql} unsigned" if unsigned && type != :primary_key
468
- sql
469
- end
470
-
471
478
  # SHOW VARIABLES LIKE 'name'
472
479
  def show_variable(name)
473
480
  query_value("SELECT @@#{name}", "SCHEMA")
@@ -480,19 +487,21 @@ module ActiveRecord
480
487
 
481
488
  scope = quoted_scope(table_name)
482
489
 
483
- query_values(<<-SQL.strip_heredoc, "SCHEMA")
490
+ query_values(<<~SQL, "SCHEMA")
484
491
  SELECT column_name
485
- FROM information_schema.key_column_usage
486
- WHERE constraint_name = 'PRIMARY'
492
+ FROM information_schema.statistics
493
+ WHERE index_name = 'PRIMARY'
487
494
  AND table_schema = #{scope[:schema]}
488
495
  AND table_name = #{scope[:name]}
489
- ORDER BY ordinal_position
496
+ ORDER BY seq_in_index
490
497
  SQL
491
498
  end
492
499
 
493
- def case_sensitive_comparison(table, attribute, column, value) # :nodoc:
500
+ def case_sensitive_comparison(attribute, value) # :nodoc:
501
+ column = column_for_attribute(attribute)
502
+
494
503
  if column.collation && !column.case_sensitive?
495
- table[attribute].eq(Arel::Nodes::Bin.new(value))
504
+ attribute.eq(Arel::Nodes::Bin.new(value))
496
505
  else
497
506
  super
498
507
  end
@@ -506,14 +515,14 @@ module ActiveRecord
506
515
  # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
507
516
  # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
508
517
  # distinct queries, and requires that the ORDER BY include the distinct column.
509
- # See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
518
+ # See https://dev.mysql.com/doc/refman/en/group-by-handling.html
510
519
  def columns_for_distinct(columns, orders) # :nodoc:
511
- order_columns = orders.reject(&:blank?).map { |s|
520
+ order_columns = orders.compact_blank.map { |s|
512
521
  # Convert Arel node to string
513
- s = s.to_sql unless s.is_a?(String)
522
+ s = visitor.compile(s) unless s.is_a?(String)
514
523
  # Remove any ASC/DESC modifiers
515
524
  s.gsub(/\s+(?:ASC|DESC)\b/i, "")
516
- }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
525
+ }.compact_blank.map.with_index { |column, i| "#{column} AS alias_#{i}" }
517
526
 
518
527
  (order_columns << super).join(", ")
519
528
  end
@@ -526,101 +535,114 @@ module ActiveRecord
526
535
  index.using == :btree || super
527
536
  end
528
537
 
529
- def insert_fixtures_set(fixture_set, tables_to_delete = [])
530
- with_multi_statements do
531
- super { discard_remaining_results }
532
- end
533
- end
538
+ def build_insert_sql(insert) # :nodoc:
539
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
534
540
 
535
- private
536
- def combine_multi_statements(total_sql)
537
- total_sql.each_with_object([]) do |sql, total_sql_chunks|
538
- previous_packet = total_sql_chunks.last
539
- sql << ";\n"
540
- if max_allowed_packet_reached?(sql, previous_packet) || total_sql_chunks.empty?
541
- total_sql_chunks << sql
542
- else
543
- previous_packet << sql
544
- end
545
- end
546
- end
547
-
548
- def max_allowed_packet_reached?(current_packet, previous_packet)
549
- if current_packet.bytesize > max_allowed_packet
550
- raise ActiveRecordError, "Fixtures set is too large #{current_packet.bytesize}. Consider increasing the max_allowed_packet variable."
551
- elsif previous_packet.nil?
552
- false
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
553
548
  else
554
- (current_packet.bytesize + previous_packet.bytesize) > max_allowed_packet
549
+ sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
550
+ sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
555
551
  end
556
552
  end
557
553
 
558
- def max_allowed_packet
559
- bytes_margin = 2
560
- @max_allowed_packet ||= (show_variable("max_allowed_packet") - bytes_margin)
554
+ sql
555
+ end
556
+
557
+ def check_version # :nodoc:
558
+ if database_version < "5.5.8"
559
+ raise "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
561
560
  end
561
+ end
562
562
 
563
- def initialize_type_map(m = type_map)
564
- super
563
+ class << self
564
+ private
565
+ def initialize_type_map(m)
566
+ super
565
567
 
566
- register_class_with_limit m, %r(char)i, MysqlString
567
-
568
- m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
569
- m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
570
- m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
571
- m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
572
- m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
573
- m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
574
- m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
575
- m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
576
- m.register_type %r(^float)i, Type::Float.new(limit: 24)
577
- m.register_type %r(^double)i, Type::Float.new(limit: 53)
578
-
579
- register_integer_type m, %r(^bigint)i, limit: 8
580
- register_integer_type m, %r(^int)i, limit: 4
581
- register_integer_type m, %r(^mediumint)i, limit: 3
582
- register_integer_type m, %r(^smallint)i, limit: 2
583
- register_integer_type m, %r(^tinyint)i, limit: 1
584
-
585
- m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
586
- m.alias_type %r(year)i, "integer"
587
- m.alias_type %r(bit)i, "binary"
588
-
589
- m.register_type(%r(enum)i) do |sql_type|
590
- limit = sql_type[/^enum\((.+)\)/i, 1]
591
- .split(",").map { |enum| enum.strip.length - 2 }.max
592
- MysqlString.new(limit: limit)
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
+ 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.alias_type %r(year)i, "integer"
591
+ 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)
593
595
  end
594
596
 
595
- m.register_type(%r(^set)i) do |sql_type|
596
- limit = sql_type[/^set\((.+)\)/i, 1]
597
- .split(",").map { |set| set.strip.length - 1 }.sum - 1
598
- MysqlString.new(limit: limit)
597
+ def register_integer_type(mapping, key, **options)
598
+ mapping.register_type(key) do |sql_type|
599
+ if /\bunsigned\b/.match?(sql_type)
600
+ Type::UnsignedInteger.new(**options)
601
+ else
602
+ Type::Integer.new(**options)
603
+ end
604
+ end
599
605
  end
600
- end
601
606
 
602
- def register_integer_type(mapping, key, options)
603
- mapping.register_type(key) do |sql_type|
604
- if /\bunsigned\b/.match?(sql_type)
605
- Type::UnsignedInteger.new(options)
607
+ def extract_precision(sql_type)
608
+ if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
609
+ super || 0
606
610
  else
607
- Type::Integer.new(options)
611
+ super
608
612
  end
609
613
  end
614
+ end
615
+
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
620
+
621
+ private
622
+ def type_map
623
+ emulate_booleans ? TYPE_MAP_WITH_BOOLEAN : TYPE_MAP
610
624
  end
611
625
 
612
- def extract_precision(sql_type)
613
- if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
614
- super || 0
615
- else
616
- super
626
+ def raw_execute(sql, name, async: false)
627
+ materialize_transactions
628
+ mark_transaction_written_if_write(sql)
629
+
630
+ log(sql, name, async: async) do
631
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
632
+ @connection.query(sql)
633
+ end
617
634
  end
618
635
  end
619
636
 
620
- # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html
637
+ # See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html
638
+ ER_DB_CREATE_EXISTS = 1007
639
+ ER_FILSORT_ABORT = 1028
621
640
  ER_DUP_ENTRY = 1062
622
641
  ER_NOT_NULL_VIOLATION = 1048
642
+ ER_NO_REFERENCED_ROW = 1216
643
+ ER_ROW_IS_REFERENCED = 1217
623
644
  ER_DO_NOT_HAVE_DEFAULT = 1364
645
+ ER_ROW_IS_REFERENCED_2 = 1451
624
646
  ER_NO_REFERENCED_ROW_2 = 1452
625
647
  ER_DATA_TOO_LONG = 1406
626
648
  ER_OUT_OF_RANGE = 1264
@@ -630,41 +652,50 @@ module ActiveRecord
630
652
  ER_LOCK_WAIT_TIMEOUT = 1205
631
653
  ER_QUERY_INTERRUPTED = 1317
632
654
  ER_QUERY_TIMEOUT = 3024
655
+ ER_FK_INCOMPATIBLE_COLUMNS = 3780
633
656
 
634
- def translate_exception(exception, message)
657
+ def translate_exception(exception, message:, sql:, binds:)
635
658
  case error_number(exception)
659
+ when nil
660
+ if exception.message.match?(/MySQL client is not connected/i)
661
+ ConnectionNotEstablished.new(exception)
662
+ else
663
+ super
664
+ end
665
+ when ER_DB_CREATE_EXISTS
666
+ DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
636
667
  when ER_DUP_ENTRY
637
- RecordNotUnique.new(message)
638
- when ER_NO_REFERENCED_ROW_2
639
- InvalidForeignKey.new(message)
640
- when ER_CANNOT_ADD_FOREIGN
641
- mismatched_foreign_key(message)
668
+ RecordNotUnique.new(message, sql: sql, binds: binds)
669
+ 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)
671
+ when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS
672
+ mismatched_foreign_key(message, sql: sql, binds: binds)
642
673
  when ER_CANNOT_CREATE_TABLE
643
674
  if message.include?("errno: 150")
644
- mismatched_foreign_key(message)
675
+ mismatched_foreign_key(message, sql: sql, binds: binds)
645
676
  else
646
677
  super
647
678
  end
648
679
  when ER_DATA_TOO_LONG
649
- ValueTooLong.new(message)
680
+ ValueTooLong.new(message, sql: sql, binds: binds)
650
681
  when ER_OUT_OF_RANGE
651
- RangeError.new(message)
682
+ RangeError.new(message, sql: sql, binds: binds)
652
683
  when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
653
- NotNullViolation.new(message)
684
+ NotNullViolation.new(message, sql: sql, binds: binds)
654
685
  when ER_LOCK_DEADLOCK
655
- Deadlocked.new(message)
686
+ Deadlocked.new(message, sql: sql, binds: binds)
656
687
  when ER_LOCK_WAIT_TIMEOUT
657
- LockWaitTimeout.new(message)
658
- when ER_QUERY_TIMEOUT
659
- StatementTimeout.new(message)
688
+ LockWaitTimeout.new(message, sql: sql, binds: binds)
689
+ when ER_QUERY_TIMEOUT, ER_FILSORT_ABORT
690
+ StatementTimeout.new(message, sql: sql, binds: binds)
660
691
  when ER_QUERY_INTERRUPTED
661
- QueryCanceled.new(message)
692
+ QueryCanceled.new(message, sql: sql, binds: binds)
662
693
  else
663
694
  super
664
695
  end
665
696
  end
666
697
 
667
- def change_column_for_alter(table_name, column_name, type, options = {})
698
+ def change_column_for_alter(table_name, column_name, type, **options)
668
699
  column = column_for(table_name, column_name)
669
700
  type ||= column.sql_type
670
701
 
@@ -681,59 +712,53 @@ module ActiveRecord
681
712
  end
682
713
 
683
714
  td = create_table_definition(table_name)
684
- cd = td.new_column_definition(column.name, type, options)
715
+ cd = td.new_column_definition(column.name, type, **options)
685
716
  schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
686
717
  end
687
718
 
688
719
  def rename_column_for_alter(table_name, column_name, new_column_name)
720
+ return rename_column_sql(table_name, column_name, new_column_name) if supports_rename_column?
721
+
689
722
  column = column_for(table_name, column_name)
690
723
  options = {
691
724
  default: column.default,
692
725
  null: column.null,
693
- auto_increment: column.auto_increment?
726
+ auto_increment: column.auto_increment?,
727
+ comment: column.comment
694
728
  }
695
729
 
696
730
  current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
697
731
  td = create_table_definition(table_name)
698
- cd = td.new_column_definition(new_column_name, current_type, options)
732
+ cd = td.new_column_definition(new_column_name, current_type, **options)
699
733
  schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
700
734
  end
701
735
 
702
- def add_index_for_alter(table_name, column_name, options = {})
703
- index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
704
- index_algorithm[0, 0] = ", " if index_algorithm.present?
705
- "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
706
- end
736
+ def add_index_for_alter(table_name, column_name, **options)
737
+ index, algorithm, _ = add_index_options(table_name, column_name, **options)
738
+ algorithm = ", #{algorithm}" if algorithm
707
739
 
708
- def remove_index_for_alter(table_name, options = {})
709
- index_name = index_name_for_remove(table_name, options)
710
- "DROP INDEX #{quote_column_name(index_name)}"
711
- end
712
-
713
- def add_timestamps_for_alter(table_name, options = {})
714
- [add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)]
740
+ "ADD #{schema_creation.accept(index)}#{algorithm}"
715
741
  end
716
742
 
717
- def remove_timestamps_for_alter(table_name, options = {})
718
- [remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)]
743
+ def remove_index_for_alter(table_name, column_name = nil, **options)
744
+ index_name = index_name_for_remove(table_name, column_name, options)
745
+ "DROP INDEX #{quote_column_name(index_name)}"
719
746
  end
720
747
 
721
- # MySQL is too stupid to create a temporary table for use subquery, so we have
722
- # to give it some prompting in the form of a subsubquery. Ugh!
723
- def subquery_for(key, select)
724
- subselect = select.clone
725
- subselect.projections = [key]
726
-
727
- # Materialize subquery by adding distinct
728
- # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
729
- subselect.distinct unless select.limit || select.offset || select.orders.any?
730
-
731
- key_name = quote_column_name(key.name)
732
- Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name))
748
+ def supports_rename_index?
749
+ if mariadb?
750
+ database_version >= "10.5.2"
751
+ else
752
+ database_version >= "5.7.6"
753
+ end
733
754
  end
734
755
 
735
- def supports_rename_index?
736
- mariadb? ? false : version >= "5.7.6"
756
+ def supports_rename_column?
757
+ if mariadb?
758
+ database_version >= "10.5.2"
759
+ else
760
+ database_version >= "8.0.3"
761
+ end
737
762
  end
738
763
 
739
764
  def configure_connection
@@ -750,7 +775,7 @@ module ActiveRecord
750
775
  defaults = [":default", :default].to_set
751
776
 
752
777
  # Make MySQL reject illegal values rather than truncating or blanking them, see
753
- # https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
778
+ # https://dev.mysql.com/doc/refman/en/sql-mode.html#sqlmode_strict_all_tables
754
779
  # If the user has provided another value for sql_mode, don't replace it.
755
780
  if sql_mode = variables.delete("sql_mode")
756
781
  sql_mode = quote(sql_mode)
@@ -767,26 +792,25 @@ module ActiveRecord
767
792
  sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
768
793
 
769
794
  # NAMES does not have an equals sign, see
770
- # https://dev.mysql.com/doc/refman/5.7/en/set-names.html
795
+ # https://dev.mysql.com/doc/refman/en/set-names.html
771
796
  # (trailing comma because variable_assignments will always have content)
772
797
  if @config[:encoding]
773
- encoding = "NAMES #{@config[:encoding]}".dup
798
+ encoding = +"NAMES #{@config[:encoding]}"
774
799
  encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
775
800
  encoding << ", "
776
801
  end
777
802
 
778
803
  # Gather up all of the SET variables...
779
- variable_assignments = variables.map do |k, v|
804
+ variable_assignments = variables.filter_map do |k, v|
780
805
  if defaults.include?(v)
781
806
  "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
782
807
  elsif !v.nil?
783
808
  "@@SESSION.#{k} = #{quote(v)}"
784
809
  end
785
- # or else nil; compact to clear nils out
786
- end.compact.join(", ")
810
+ end.join(", ")
787
811
 
788
812
  # ...and send them all in one query
789
- execute "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
813
+ execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
790
814
  end
791
815
 
792
816
  def column_definitions(table_name) # :nodoc:
@@ -803,15 +827,21 @@ module ActiveRecord
803
827
  Arel::Visitors::MySQL.new(self)
804
828
  end
805
829
 
806
- def mismatched_foreign_key(message)
830
+ def build_statement_pool
831
+ StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
832
+ end
833
+
834
+ def mismatched_foreign_key(message, sql:, binds:)
807
835
  match = %r/
808
836
  (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
809
837
  FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
810
838
  REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
811
- /xmi.match(message)
839
+ /xmi.match(sql)
812
840
 
813
841
  options = {
814
842
  message: message,
843
+ sql: sql,
844
+ binds: binds,
815
845
  }
816
846
 
817
847
  if match
@@ -822,65 +852,19 @@ module ActiveRecord
822
852
  options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
823
853
  end
824
854
 
825
- MismatchedForeignKey.new(options)
855
+ MismatchedForeignKey.new(**options)
826
856
  end
827
857
 
828
- def integer_to_sql(limit) # :nodoc:
829
- case limit
830
- when 1; "tinyint"
831
- when 2; "smallint"
832
- when 3; "mediumint"
833
- when nil, 4; "int"
834
- when 5..8; "bigint"
835
- else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a decimal with scale 0 instead.")
836
- end
858
+ def version_string(full_version_string)
859
+ full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
837
860
  end
838
861
 
839
- def text_to_sql(limit) # :nodoc:
840
- case limit
841
- when 0..0xff; "tinytext"
842
- when nil, 0x100..0xffff; "text"
843
- when 0x10000..0xffffff; "mediumtext"
844
- when 0x1000000..0xffffffff; "longtext"
845
- else raise(ActiveRecordError, "No text type has byte length #{limit}")
846
- end
862
+ ActiveRecord::Type.register(:immutable_string, adapter: :mysql2) do |_, **args|
863
+ Type::ImmutableString.new(true: "1", false: "0", **args)
847
864
  end
848
-
849
- def binary_to_sql(limit) # :nodoc:
850
- case limit
851
- when 0..0xff; "tinyblob"
852
- when nil, 0x100..0xffff; "blob"
853
- when 0x10000..0xffffff; "mediumblob"
854
- when 0x1000000..0xffffffff; "longblob"
855
- else raise(ActiveRecordError, "No binary type has byte length #{limit}")
856
- end
857
- end
858
-
859
- def version_string
860
- full_version.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
865
+ ActiveRecord::Type.register(:string, adapter: :mysql2) do |_, **args|
866
+ Type::String.new(true: "1", false: "0", **args)
861
867
  end
862
-
863
- class MysqlString < Type::String # :nodoc:
864
- def serialize(value)
865
- case value
866
- when true then "1"
867
- when false then "0"
868
- else super
869
- end
870
- end
871
-
872
- private
873
-
874
- def cast_value(value)
875
- case value
876
- when true then "1"
877
- when false then "0"
878
- else super
879
- end
880
- end
881
- end
882
-
883
- ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
884
868
  ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
885
869
  end
886
870
  end