omg-activerecord 8.0.0.alpha1

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 (412) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +355 -0
  3. data/MIT-LICENSE +22 -0
  4. data/README.rdoc +219 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record/aggregations.rb +287 -0
  8. data/lib/active_record/association_relation.rb +50 -0
  9. data/lib/active_record/associations/alias_tracker.rb +90 -0
  10. data/lib/active_record/associations/association.rb +417 -0
  11. data/lib/active_record/associations/association_scope.rb +175 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +163 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  14. data/lib/active_record/associations/builder/association.rb +170 -0
  15. data/lib/active_record/associations/builder/belongs_to.rb +160 -0
  16. data/lib/active_record/associations/builder/collection_association.rb +80 -0
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -0
  18. data/lib/active_record/associations/builder/has_many.rb +23 -0
  19. data/lib/active_record/associations/builder/has_one.rb +61 -0
  20. data/lib/active_record/associations/builder/singular_association.rb +48 -0
  21. data/lib/active_record/associations/collection_association.rb +535 -0
  22. data/lib/active_record/associations/collection_proxy.rb +1163 -0
  23. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  24. data/lib/active_record/associations/errors.rb +265 -0
  25. data/lib/active_record/associations/foreign_association.rb +40 -0
  26. data/lib/active_record/associations/has_many_association.rb +167 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +232 -0
  28. data/lib/active_record/associations/has_one_association.rb +142 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +106 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  33. data/lib/active_record/associations/join_dependency.rb +301 -0
  34. data/lib/active_record/associations/nested_error.rb +47 -0
  35. data/lib/active_record/associations/preloader/association.rb +316 -0
  36. data/lib/active_record/associations/preloader/batch.rb +48 -0
  37. data/lib/active_record/associations/preloader/branch.rb +153 -0
  38. data/lib/active_record/associations/preloader/through_association.rb +150 -0
  39. data/lib/active_record/associations/preloader.rb +135 -0
  40. data/lib/active_record/associations/singular_association.rb +76 -0
  41. data/lib/active_record/associations/through_association.rb +132 -0
  42. data/lib/active_record/associations.rb +1897 -0
  43. data/lib/active_record/asynchronous_queries_tracker.rb +64 -0
  44. data/lib/active_record/attribute_assignment.rb +82 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +106 -0
  46. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  47. data/lib/active_record/attribute_methods/dirty.rb +262 -0
  48. data/lib/active_record/attribute_methods/primary_key.rb +158 -0
  49. data/lib/active_record/attribute_methods/query.rb +50 -0
  50. data/lib/active_record/attribute_methods/read.rb +46 -0
  51. data/lib/active_record/attribute_methods/serialization.rb +232 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +94 -0
  53. data/lib/active_record/attribute_methods/write.rb +49 -0
  54. data/lib/active_record/attribute_methods.rb +542 -0
  55. data/lib/active_record/attributes.rb +307 -0
  56. data/lib/active_record/autosave_association.rb +586 -0
  57. data/lib/active_record/base.rb +338 -0
  58. data/lib/active_record/callbacks.rb +452 -0
  59. data/lib/active_record/coders/column_serializer.rb +61 -0
  60. data/lib/active_record/coders/json.rb +15 -0
  61. data/lib/active_record/coders/yaml_column.rb +95 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +290 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +210 -0
  64. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +923 -0
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +31 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +747 -0
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +319 -0
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +239 -0
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +24 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +190 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +961 -0
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +106 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1883 -0
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +676 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +1218 -0
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1016 -0
  78. data/lib/active_record/connection_adapters/column.rb +122 -0
  79. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  80. data/lib/active_record/connection_adapters/mysql/column.rb +28 -0
  81. data/lib/active_record/connection_adapters/mysql/database_statements.rb +95 -0
  82. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +71 -0
  83. data/lib/active_record/connection_adapters/mysql/quoting.rb +114 -0
  84. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +106 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +106 -0
  86. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +97 -0
  87. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +300 -0
  88. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
  89. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +96 -0
  90. data/lib/active_record/connection_adapters/mysql2_adapter.rb +196 -0
  91. data/lib/active_record/connection_adapters/pool_config.rb +83 -0
  92. data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
  93. data/lib/active_record/connection_adapters/postgresql/column.rb +82 -0
  94. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +231 -0
  95. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +91 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +53 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +54 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +31 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +20 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +109 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +42 -0
  112. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  113. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +74 -0
  114. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +124 -0
  115. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  116. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  117. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  118. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +125 -0
  119. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +45 -0
  120. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  121. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  122. data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -0
  123. data/lib/active_record/connection_adapters/postgresql/quoting.rb +238 -0
  124. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +71 -0
  125. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +169 -0
  126. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +392 -0
  127. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +127 -0
  128. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +1162 -0
  129. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +44 -0
  130. data/lib/active_record/connection_adapters/postgresql/utils.rb +79 -0
  131. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1182 -0
  132. data/lib/active_record/connection_adapters/schema_cache.rb +478 -0
  133. data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
  134. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  135. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +145 -0
  136. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  137. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +116 -0
  138. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +37 -0
  139. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +39 -0
  140. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +47 -0
  141. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +221 -0
  142. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +843 -0
  143. data/lib/active_record/connection_adapters/statement_pool.rb +67 -0
  144. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +69 -0
  145. data/lib/active_record/connection_adapters/trilogy_adapter.rb +212 -0
  146. data/lib/active_record/connection_adapters.rb +176 -0
  147. data/lib/active_record/connection_handling.rb +413 -0
  148. data/lib/active_record/core.rb +836 -0
  149. data/lib/active_record/counter_cache.rb +230 -0
  150. data/lib/active_record/database_configurations/connection_url_resolver.rb +105 -0
  151. data/lib/active_record/database_configurations/database_config.rb +104 -0
  152. data/lib/active_record/database_configurations/hash_config.rb +172 -0
  153. data/lib/active_record/database_configurations/url_config.rb +78 -0
  154. data/lib/active_record/database_configurations.rb +309 -0
  155. data/lib/active_record/delegated_type.rb +289 -0
  156. data/lib/active_record/deprecator.rb +7 -0
  157. data/lib/active_record/destroy_association_async_job.rb +38 -0
  158. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  159. data/lib/active_record/dynamic_matchers.rb +121 -0
  160. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  161. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  162. data/lib/active_record/encryption/cipher.rb +53 -0
  163. data/lib/active_record/encryption/config.rb +70 -0
  164. data/lib/active_record/encryption/configurable.rb +60 -0
  165. data/lib/active_record/encryption/context.rb +42 -0
  166. data/lib/active_record/encryption/contexts.rb +76 -0
  167. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  168. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  169. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  170. data/lib/active_record/encryption/encrypted_attribute_type.rb +184 -0
  171. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  172. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  173. data/lib/active_record/encryption/encryptor.rb +177 -0
  174. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  175. data/lib/active_record/encryption/errors.rb +15 -0
  176. data/lib/active_record/encryption/extended_deterministic_queries.rb +159 -0
  177. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  178. data/lib/active_record/encryption/key.rb +28 -0
  179. data/lib/active_record/encryption/key_generator.rb +53 -0
  180. data/lib/active_record/encryption/key_provider.rb +46 -0
  181. data/lib/active_record/encryption/message.rb +33 -0
  182. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  183. data/lib/active_record/encryption/message_serializer.rb +96 -0
  184. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  185. data/lib/active_record/encryption/properties.rb +76 -0
  186. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  187. data/lib/active_record/encryption/scheme.rb +107 -0
  188. data/lib/active_record/encryption.rb +58 -0
  189. data/lib/active_record/enum.rb +424 -0
  190. data/lib/active_record/errors.rb +614 -0
  191. data/lib/active_record/explain.rb +63 -0
  192. data/lib/active_record/explain_registry.rb +37 -0
  193. data/lib/active_record/explain_subscriber.rb +34 -0
  194. data/lib/active_record/fixture_set/file.rb +89 -0
  195. data/lib/active_record/fixture_set/model_metadata.rb +42 -0
  196. data/lib/active_record/fixture_set/render_context.rb +19 -0
  197. data/lib/active_record/fixture_set/table_row.rb +208 -0
  198. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  199. data/lib/active_record/fixtures.rb +850 -0
  200. data/lib/active_record/future_result.rb +182 -0
  201. data/lib/active_record/gem_version.rb +17 -0
  202. data/lib/active_record/inheritance.rb +366 -0
  203. data/lib/active_record/insert_all.rb +328 -0
  204. data/lib/active_record/integration.rb +209 -0
  205. data/lib/active_record/internal_metadata.rb +164 -0
  206. data/lib/active_record/legacy_yaml_adapter.rb +15 -0
  207. data/lib/active_record/locale/en.yml +48 -0
  208. data/lib/active_record/locking/optimistic.rb +228 -0
  209. data/lib/active_record/locking/pessimistic.rb +102 -0
  210. data/lib/active_record/log_subscriber.rb +149 -0
  211. data/lib/active_record/marshalling.rb +56 -0
  212. data/lib/active_record/message_pack.rb +124 -0
  213. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  214. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  215. data/lib/active_record/middleware/database_selector.rb +87 -0
  216. data/lib/active_record/middleware/shard_selector.rb +62 -0
  217. data/lib/active_record/migration/command_recorder.rb +406 -0
  218. data/lib/active_record/migration/compatibility.rb +490 -0
  219. data/lib/active_record/migration/default_strategy.rb +22 -0
  220. data/lib/active_record/migration/execution_strategy.rb +19 -0
  221. data/lib/active_record/migration/join_table.rb +16 -0
  222. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  223. data/lib/active_record/migration.rb +1626 -0
  224. data/lib/active_record/model_schema.rb +635 -0
  225. data/lib/active_record/nested_attributes.rb +633 -0
  226. data/lib/active_record/no_touching.rb +65 -0
  227. data/lib/active_record/normalization.rb +163 -0
  228. data/lib/active_record/persistence.rb +968 -0
  229. data/lib/active_record/promise.rb +84 -0
  230. data/lib/active_record/query_cache.rb +56 -0
  231. data/lib/active_record/query_logs.rb +247 -0
  232. data/lib/active_record/query_logs_formatter.rb +30 -0
  233. data/lib/active_record/querying.rb +122 -0
  234. data/lib/active_record/railtie.rb +440 -0
  235. data/lib/active_record/railties/console_sandbox.rb +5 -0
  236. data/lib/active_record/railties/controller_runtime.rb +65 -0
  237. data/lib/active_record/railties/databases.rake +641 -0
  238. data/lib/active_record/railties/job_runtime.rb +23 -0
  239. data/lib/active_record/readonly_attributes.rb +66 -0
  240. data/lib/active_record/reflection.rb +1287 -0
  241. data/lib/active_record/relation/batches/batch_enumerator.rb +115 -0
  242. data/lib/active_record/relation/batches.rb +491 -0
  243. data/lib/active_record/relation/calculations.rb +679 -0
  244. data/lib/active_record/relation/delegation.rb +154 -0
  245. data/lib/active_record/relation/finder_methods.rb +661 -0
  246. data/lib/active_record/relation/from_clause.rb +30 -0
  247. data/lib/active_record/relation/merger.rb +192 -0
  248. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  249. data/lib/active_record/relation/predicate_builder/association_query_value.rb +76 -0
  250. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  251. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +60 -0
  252. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  253. data/lib/active_record/relation/predicate_builder/relation_handler.rb +24 -0
  254. data/lib/active_record/relation/predicate_builder.rb +181 -0
  255. data/lib/active_record/relation/query_attribute.rb +68 -0
  256. data/lib/active_record/relation/query_methods.rb +2235 -0
  257. data/lib/active_record/relation/record_fetch_warning.rb +52 -0
  258. data/lib/active_record/relation/spawn_methods.rb +78 -0
  259. data/lib/active_record/relation/where_clause.rb +218 -0
  260. data/lib/active_record/relation.rb +1495 -0
  261. data/lib/active_record/result.rb +249 -0
  262. data/lib/active_record/runtime_registry.rb +82 -0
  263. data/lib/active_record/sanitization.rb +254 -0
  264. data/lib/active_record/schema.rb +77 -0
  265. data/lib/active_record/schema_dumper.rb +364 -0
  266. data/lib/active_record/schema_migration.rb +106 -0
  267. data/lib/active_record/scoping/default.rb +205 -0
  268. data/lib/active_record/scoping/named.rb +202 -0
  269. data/lib/active_record/scoping.rb +136 -0
  270. data/lib/active_record/secure_password.rb +60 -0
  271. data/lib/active_record/secure_token.rb +66 -0
  272. data/lib/active_record/serialization.rb +29 -0
  273. data/lib/active_record/signed_id.rb +137 -0
  274. data/lib/active_record/statement_cache.rb +164 -0
  275. data/lib/active_record/store.rb +299 -0
  276. data/lib/active_record/suppressor.rb +59 -0
  277. data/lib/active_record/table_metadata.rb +85 -0
  278. data/lib/active_record/tasks/database_tasks.rb +681 -0
  279. data/lib/active_record/tasks/mysql_database_tasks.rb +120 -0
  280. data/lib/active_record/tasks/postgresql_database_tasks.rb +147 -0
  281. data/lib/active_record/tasks/sqlite_database_tasks.rb +89 -0
  282. data/lib/active_record/test_databases.rb +24 -0
  283. data/lib/active_record/test_fixtures.rb +321 -0
  284. data/lib/active_record/testing/query_assertions.rb +121 -0
  285. data/lib/active_record/timestamp.rb +177 -0
  286. data/lib/active_record/token_for.rb +123 -0
  287. data/lib/active_record/touch_later.rb +70 -0
  288. data/lib/active_record/transaction.rb +132 -0
  289. data/lib/active_record/transactions.rb +523 -0
  290. data/lib/active_record/translation.rb +22 -0
  291. data/lib/active_record/type/adapter_specific_registry.rb +144 -0
  292. data/lib/active_record/type/date.rb +9 -0
  293. data/lib/active_record/type/date_time.rb +9 -0
  294. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  295. data/lib/active_record/type/hash_lookup_type_map.rb +57 -0
  296. data/lib/active_record/type/internal/timezone.rb +22 -0
  297. data/lib/active_record/type/json.rb +30 -0
  298. data/lib/active_record/type/serialized.rb +76 -0
  299. data/lib/active_record/type/text.rb +11 -0
  300. data/lib/active_record/type/time.rb +35 -0
  301. data/lib/active_record/type/type_map.rb +58 -0
  302. data/lib/active_record/type/unsigned_integer.rb +16 -0
  303. data/lib/active_record/type.rb +83 -0
  304. data/lib/active_record/type_caster/connection.rb +33 -0
  305. data/lib/active_record/type_caster/map.rb +23 -0
  306. data/lib/active_record/type_caster.rb +9 -0
  307. data/lib/active_record/validations/absence.rb +25 -0
  308. data/lib/active_record/validations/associated.rb +65 -0
  309. data/lib/active_record/validations/length.rb +26 -0
  310. data/lib/active_record/validations/numericality.rb +36 -0
  311. data/lib/active_record/validations/presence.rb +45 -0
  312. data/lib/active_record/validations/uniqueness.rb +295 -0
  313. data/lib/active_record/validations.rb +101 -0
  314. data/lib/active_record/version.rb +10 -0
  315. data/lib/active_record.rb +616 -0
  316. data/lib/arel/alias_predication.rb +9 -0
  317. data/lib/arel/attributes/attribute.rb +33 -0
  318. data/lib/arel/collectors/bind.rb +31 -0
  319. data/lib/arel/collectors/composite.rb +46 -0
  320. data/lib/arel/collectors/plain_string.rb +20 -0
  321. data/lib/arel/collectors/sql_string.rb +27 -0
  322. data/lib/arel/collectors/substitute_binds.rb +35 -0
  323. data/lib/arel/crud.rb +48 -0
  324. data/lib/arel/delete_manager.rb +32 -0
  325. data/lib/arel/errors.rb +19 -0
  326. data/lib/arel/expressions.rb +29 -0
  327. data/lib/arel/factory_methods.rb +53 -0
  328. data/lib/arel/filter_predications.rb +9 -0
  329. data/lib/arel/insert_manager.rb +48 -0
  330. data/lib/arel/math.rb +45 -0
  331. data/lib/arel/nodes/ascending.rb +23 -0
  332. data/lib/arel/nodes/binary.rb +125 -0
  333. data/lib/arel/nodes/bind_param.rb +44 -0
  334. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  335. data/lib/arel/nodes/case.rb +55 -0
  336. data/lib/arel/nodes/casted.rb +62 -0
  337. data/lib/arel/nodes/comment.rb +29 -0
  338. data/lib/arel/nodes/count.rb +12 -0
  339. data/lib/arel/nodes/cte.rb +36 -0
  340. data/lib/arel/nodes/delete_statement.rb +44 -0
  341. data/lib/arel/nodes/descending.rb +23 -0
  342. data/lib/arel/nodes/equality.rb +15 -0
  343. data/lib/arel/nodes/extract.rb +24 -0
  344. data/lib/arel/nodes/false.rb +16 -0
  345. data/lib/arel/nodes/filter.rb +10 -0
  346. data/lib/arel/nodes/fragments.rb +35 -0
  347. data/lib/arel/nodes/full_outer_join.rb +8 -0
  348. data/lib/arel/nodes/function.rb +45 -0
  349. data/lib/arel/nodes/grouping.rb +11 -0
  350. data/lib/arel/nodes/homogeneous_in.rb +68 -0
  351. data/lib/arel/nodes/in.rb +15 -0
  352. data/lib/arel/nodes/infix_operation.rb +92 -0
  353. data/lib/arel/nodes/inner_join.rb +8 -0
  354. data/lib/arel/nodes/insert_statement.rb +37 -0
  355. data/lib/arel/nodes/join_source.rb +20 -0
  356. data/lib/arel/nodes/leading_join.rb +8 -0
  357. data/lib/arel/nodes/matches.rb +18 -0
  358. data/lib/arel/nodes/named_function.rb +23 -0
  359. data/lib/arel/nodes/nary.rb +39 -0
  360. data/lib/arel/nodes/node.rb +161 -0
  361. data/lib/arel/nodes/node_expression.rb +13 -0
  362. data/lib/arel/nodes/ordering.rb +27 -0
  363. data/lib/arel/nodes/outer_join.rb +8 -0
  364. data/lib/arel/nodes/over.rb +15 -0
  365. data/lib/arel/nodes/regexp.rb +16 -0
  366. data/lib/arel/nodes/right_outer_join.rb +8 -0
  367. data/lib/arel/nodes/select_core.rb +67 -0
  368. data/lib/arel/nodes/select_statement.rb +41 -0
  369. data/lib/arel/nodes/sql_literal.rb +32 -0
  370. data/lib/arel/nodes/string_join.rb +11 -0
  371. data/lib/arel/nodes/table_alias.rb +35 -0
  372. data/lib/arel/nodes/terminal.rb +16 -0
  373. data/lib/arel/nodes/true.rb +16 -0
  374. data/lib/arel/nodes/unary.rb +44 -0
  375. data/lib/arel/nodes/unary_operation.rb +20 -0
  376. data/lib/arel/nodes/unqualified_column.rb +22 -0
  377. data/lib/arel/nodes/update_statement.rb +46 -0
  378. data/lib/arel/nodes/values_list.rb +9 -0
  379. data/lib/arel/nodes/window.rb +126 -0
  380. data/lib/arel/nodes/with.rb +11 -0
  381. data/lib/arel/nodes.rb +75 -0
  382. data/lib/arel/order_predications.rb +13 -0
  383. data/lib/arel/predications.rb +260 -0
  384. data/lib/arel/select_manager.rb +276 -0
  385. data/lib/arel/table.rb +121 -0
  386. data/lib/arel/tree_manager.rb +65 -0
  387. data/lib/arel/update_manager.rb +49 -0
  388. data/lib/arel/visitors/dot.rb +299 -0
  389. data/lib/arel/visitors/mysql.rb +111 -0
  390. data/lib/arel/visitors/postgresql.rb +99 -0
  391. data/lib/arel/visitors/sqlite.rb +38 -0
  392. data/lib/arel/visitors/to_sql.rb +1033 -0
  393. data/lib/arel/visitors/visitor.rb +45 -0
  394. data/lib/arel/visitors.rb +13 -0
  395. data/lib/arel/window_predications.rb +9 -0
  396. data/lib/arel.rb +73 -0
  397. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  398. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
  399. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  400. data/lib/rails/generators/active_record/migration/migration_generator.rb +76 -0
  401. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +29 -0
  402. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +48 -0
  403. data/lib/rails/generators/active_record/migration.rb +54 -0
  404. data/lib/rails/generators/active_record/model/USAGE +113 -0
  405. data/lib/rails/generators/active_record/model/model_generator.rb +94 -0
  406. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  407. data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
  408. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  409. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  410. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  411. data/lib/rails/generators/active_record.rb +19 -0
  412. metadata +505 -0
@@ -0,0 +1,1016 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/connection_adapters/abstract_adapter"
4
+ require "active_record/connection_adapters/statement_pool"
5
+ require "active_record/connection_adapters/mysql/column"
6
+ require "active_record/connection_adapters/mysql/database_statements"
7
+ require "active_record/connection_adapters/mysql/explain_pretty_printer"
8
+ require "active_record/connection_adapters/mysql/quoting"
9
+ require "active_record/connection_adapters/mysql/schema_creation"
10
+ require "active_record/connection_adapters/mysql/schema_definitions"
11
+ require "active_record/connection_adapters/mysql/schema_dumper"
12
+ require "active_record/connection_adapters/mysql/schema_statements"
13
+ require "active_record/connection_adapters/mysql/type_metadata"
14
+
15
+ module ActiveRecord
16
+ module ConnectionAdapters
17
+ class AbstractMysqlAdapter < AbstractAdapter
18
+ include MySQL::DatabaseStatements
19
+ include MySQL::Quoting
20
+ include MySQL::SchemaStatements
21
+
22
+ ##
23
+ # :singleton-method:
24
+ # By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
25
+ # as boolean. If you wish to disable this emulation you can add the following line
26
+ # to your application.rb file:
27
+ #
28
+ # ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
29
+ class_attribute :emulate_booleans, default: true
30
+
31
+ NATIVE_DATABASE_TYPES = {
32
+ primary_key: "bigint auto_increment PRIMARY KEY",
33
+ string: { name: "varchar", limit: 255 },
34
+ text: { name: "text" },
35
+ integer: { name: "int", limit: 4 },
36
+ bigint: { name: "bigint" },
37
+ float: { name: "float", limit: 24 },
38
+ decimal: { name: "decimal" },
39
+ datetime: { name: "datetime" },
40
+ timestamp: { name: "timestamp" },
41
+ time: { name: "time" },
42
+ date: { name: "date" },
43
+ binary: { name: "blob" },
44
+ blob: { name: "blob" },
45
+ boolean: { name: "tinyint", limit: 1 },
46
+ json: { name: "json" },
47
+ }
48
+
49
+ class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
50
+ private
51
+ def dealloc(stmt)
52
+ stmt.close
53
+ end
54
+ end
55
+
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(ActiveRecord.database_cli[:mysql], *args)
83
+ end
84
+ end
85
+
86
+ def get_database_version # :nodoc:
87
+ full_version_string = get_full_version
88
+ version_string = version_string(full_version_string)
89
+ Version.new(version_string, full_version_string)
90
+ end
91
+
92
+ def mariadb? # :nodoc:
93
+ /mariadb/i.match?(full_version)
94
+ end
95
+
96
+ def supports_bulk_alter?
97
+ true
98
+ end
99
+
100
+ def supports_index_sort_order?
101
+ !mariadb? && database_version >= "8.0.1"
102
+ end
103
+
104
+ def supports_expression_index?
105
+ !mariadb? && database_version >= "8.0.13"
106
+ end
107
+
108
+ def supports_transaction_isolation?
109
+ true
110
+ end
111
+
112
+ def supports_restart_db_transaction?
113
+ true
114
+ end
115
+
116
+ def supports_explain?
117
+ true
118
+ end
119
+
120
+ def supports_indexes_in_create?
121
+ true
122
+ end
123
+
124
+ def supports_foreign_keys?
125
+ true
126
+ end
127
+
128
+ def supports_check_constraints?
129
+ if mariadb?
130
+ database_version >= "10.3.10" || (database_version < "10.3" && database_version >= "10.2.22")
131
+ else
132
+ database_version >= "8.0.16"
133
+ end
134
+ end
135
+
136
+ def supports_views?
137
+ true
138
+ end
139
+
140
+ def supports_datetime_with_precision?
141
+ true
142
+ end
143
+
144
+ def supports_virtual_columns?
145
+ mariadb? || database_version >= "5.7.5"
146
+ end
147
+
148
+ # See https://dev.mysql.com/doc/refman/en/optimizer-hints.html for more details.
149
+ def supports_optimizer_hints?
150
+ !mariadb? && database_version >= "5.7.7"
151
+ end
152
+
153
+ def supports_common_table_expressions?
154
+ if mariadb?
155
+ database_version >= "10.2.1"
156
+ else
157
+ database_version >= "8.0.1"
158
+ end
159
+ end
160
+
161
+ def supports_advisory_locks?
162
+ true
163
+ end
164
+
165
+ def supports_insert_on_duplicate_skip?
166
+ true
167
+ end
168
+
169
+ def supports_insert_on_duplicate_update?
170
+ true
171
+ end
172
+
173
+ def supports_insert_returning?
174
+ mariadb? && database_version >= "10.5.0"
175
+ end
176
+
177
+ def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
178
+ query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
179
+ end
180
+
181
+ def release_advisory_lock(lock_name) # :nodoc:
182
+ query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
183
+ end
184
+
185
+ def native_database_types
186
+ NATIVE_DATABASE_TYPES
187
+ end
188
+
189
+ def index_algorithms
190
+ {
191
+ default: "ALGORITHM = DEFAULT",
192
+ copy: "ALGORITHM = COPY",
193
+ inplace: "ALGORITHM = INPLACE",
194
+ instant: "ALGORITHM = INSTANT",
195
+ }
196
+ end
197
+
198
+ # HELPER METHODS ===========================================
199
+
200
+ # Must return the MySQL error number from the exception, if the exception has an
201
+ # error number.
202
+ def error_number(exception) # :nodoc:
203
+ raise NotImplementedError
204
+ end
205
+
206
+ # REFERENTIAL INTEGRITY ====================================
207
+
208
+ def disable_referential_integrity # :nodoc:
209
+ old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
210
+
211
+ begin
212
+ update("SET FOREIGN_KEY_CHECKS = 0")
213
+ yield
214
+ ensure
215
+ update("SET FOREIGN_KEY_CHECKS = #{old}") if active?
216
+ end
217
+ end
218
+
219
+ #--
220
+ # DATABASE STATEMENTS ======================================
221
+ #++
222
+
223
+ def begin_db_transaction # :nodoc:
224
+ internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
225
+ end
226
+
227
+ def begin_isolated_db_transaction(isolation) # :nodoc:
228
+ # From MySQL manual: The [SET TRANSACTION] statement applies only to the next single transaction performed within the session.
229
+ # So we don't need to implement #reset_isolation_level
230
+ execute_batch(
231
+ ["SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "BEGIN"],
232
+ "TRANSACTION",
233
+ allow_retry: true,
234
+ materialize_transactions: false,
235
+ )
236
+ end
237
+
238
+ def commit_db_transaction # :nodoc:
239
+ internal_execute("COMMIT", "TRANSACTION", allow_retry: false, materialize_transactions: true)
240
+ end
241
+
242
+ def exec_rollback_db_transaction # :nodoc:
243
+ internal_execute("ROLLBACK", "TRANSACTION", allow_retry: false, materialize_transactions: true)
244
+ end
245
+
246
+ def exec_restart_db_transaction # :nodoc:
247
+ internal_execute("ROLLBACK AND CHAIN", "TRANSACTION", allow_retry: false, materialize_transactions: true)
248
+ end
249
+
250
+ def empty_insert_statement_value(primary_key = nil) # :nodoc:
251
+ "VALUES ()"
252
+ end
253
+
254
+ # SCHEMA STATEMENTS ========================================
255
+
256
+ # Drops the database specified on the +name+ attribute
257
+ # and creates it again using the provided +options+.
258
+ def recreate_database(name, options = {})
259
+ drop_database(name)
260
+ sql = create_database(name, options)
261
+ reconnect!
262
+ sql
263
+ end
264
+
265
+ # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
266
+ # Charset defaults to utf8mb4.
267
+ #
268
+ # Example:
269
+ # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
270
+ # create_database 'matt_development'
271
+ # create_database 'matt_development', charset: :big5
272
+ def create_database(name, options = {})
273
+ if options[:collation]
274
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}"
275
+ elsif options[:charset]
276
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset])}"
277
+ elsif row_format_dynamic_by_default?
278
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET `utf8mb4`"
279
+ else
280
+ raise "Configure a supported :charset and ensure innodb_large_prefix is enabled to support indexes on varchar(255) string columns."
281
+ end
282
+ end
283
+
284
+ # Drops a MySQL database.
285
+ #
286
+ # Example:
287
+ # drop_database('sebastian_development')
288
+ def drop_database(name) # :nodoc:
289
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
290
+ end
291
+
292
+ def current_database
293
+ query_value("SELECT database()", "SCHEMA")
294
+ end
295
+
296
+ # Returns the database character set.
297
+ def charset
298
+ show_variable "character_set_database"
299
+ end
300
+
301
+ # Returns the database collation strategy.
302
+ def collation
303
+ show_variable "collation_database"
304
+ end
305
+
306
+ def table_comment(table_name) # :nodoc:
307
+ scope = quoted_scope(table_name)
308
+
309
+ query_value(<<~SQL, "SCHEMA").presence
310
+ SELECT table_comment
311
+ FROM information_schema.tables
312
+ WHERE table_schema = #{scope[:schema]}
313
+ AND table_name = #{scope[:name]}
314
+ SQL
315
+ end
316
+
317
+ def change_table_comment(table_name, comment_or_changes) # :nodoc:
318
+ comment = extract_new_comment_value(comment_or_changes)
319
+ comment = "" if comment.nil?
320
+ execute("ALTER TABLE #{quote_table_name(table_name)} COMMENT #{quote(comment)}")
321
+ end
322
+
323
+ # Renames a table.
324
+ #
325
+ # Example:
326
+ # rename_table('octopuses', 'octopi')
327
+ def rename_table(table_name, new_name, **options)
328
+ validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
329
+ schema_cache.clear_data_source_cache!(table_name.to_s)
330
+ schema_cache.clear_data_source_cache!(new_name.to_s)
331
+ execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
332
+ rename_table_indexes(table_name, new_name, **options)
333
+ end
334
+
335
+ # Drops a table or tables from the database.
336
+ #
337
+ # [<tt>:force</tt>]
338
+ # Set to +:cascade+ to drop dependent objects as well.
339
+ # Defaults to false.
340
+ # [<tt>:if_exists</tt>]
341
+ # Set to +true+ to only drop the table if it exists.
342
+ # Defaults to false.
343
+ # [<tt>:temporary</tt>]
344
+ # Set to +true+ to drop temporary table.
345
+ # Defaults to false.
346
+ #
347
+ # Although this command ignores most +options+ and the block if one is given,
348
+ # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
349
+ # In that case, +options+ and the block will be used by #create_table except if you provide more than one table which is not supported.
350
+ def drop_table(*table_names, **options)
351
+ table_names.each { |table_name| schema_cache.clear_data_source_cache!(table_name.to_s) }
352
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{table_names.map { |table_name| quote_table_name(table_name) }.join(', ')}#{' CASCADE' if options[:force] == :cascade}"
353
+ end
354
+
355
+ def rename_index(table_name, old_name, new_name)
356
+ if supports_rename_index?
357
+ validate_index_length!(table_name, new_name)
358
+
359
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
360
+ else
361
+ super
362
+ end
363
+ end
364
+
365
+ def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
366
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
367
+ end
368
+
369
+ def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc:
370
+ column = column_for(table_name, column_name)
371
+ return unless column
372
+
373
+ default = extract_new_default_value(default_or_changes)
374
+ ChangeColumnDefaultDefinition.new(column, default)
375
+ end
376
+
377
+ def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
378
+ validate_change_column_null_argument!(null)
379
+
380
+ unless null || default.nil?
381
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
382
+ end
383
+
384
+ change_column table_name, column_name, nil, null: null
385
+ end
386
+
387
+ def change_column_comment(table_name, column_name, comment_or_changes) # :nodoc:
388
+ comment = extract_new_comment_value(comment_or_changes)
389
+ change_column table_name, column_name, nil, comment: comment
390
+ end
391
+
392
+ def change_column(table_name, column_name, type, **options) # :nodoc:
393
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, **options)}")
394
+ end
395
+
396
+ # Builds a ChangeColumnDefinition object.
397
+ #
398
+ # This definition object contains information about the column change that would occur
399
+ # if the same arguments were passed to #change_column. See #change_column for information about
400
+ # passing a +table_name+, +column_name+, +type+ and other options that can be passed.
401
+ def build_change_column_definition(table_name, column_name, type, **options) # :nodoc:
402
+ column = column_for(table_name, column_name)
403
+ type ||= column.sql_type
404
+
405
+ unless options.key?(:default)
406
+ options[:default] = column.default
407
+ end
408
+
409
+ unless options.key?(:null)
410
+ options[:null] = column.null
411
+ end
412
+
413
+ unless options.key?(:comment)
414
+ options[:comment] = column.comment
415
+ end
416
+
417
+ if options[:collation] == :no_collation
418
+ options.delete(:collation)
419
+ else
420
+ options[:collation] ||= column.collation if text_type?(type)
421
+ end
422
+
423
+ unless options.key?(:auto_increment)
424
+ options[:auto_increment] = column.auto_increment?
425
+ end
426
+
427
+ td = create_table_definition(table_name)
428
+ cd = td.new_column_definition(column.name, type, **options)
429
+ ChangeColumnDefinition.new(cd, column.name)
430
+ end
431
+
432
+ def rename_column(table_name, column_name, new_column_name) # :nodoc:
433
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
434
+ rename_column_indexes(table_name, column_name, new_column_name)
435
+ end
436
+
437
+ def add_index(table_name, column_name, **options) # :nodoc:
438
+ create_index = build_create_index_definition(table_name, column_name, **options)
439
+ return unless create_index
440
+
441
+ execute schema_creation.accept(create_index)
442
+ end
443
+
444
+ def build_create_index_definition(table_name, column_name, **options) # :nodoc:
445
+ index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
446
+
447
+ return if if_not_exists && index_exists?(table_name, column_name, name: index.name)
448
+
449
+ CreateIndexDefinition.new(index, algorithm)
450
+ end
451
+
452
+ def add_sql_comment!(sql, comment) # :nodoc:
453
+ sql << " COMMENT #{quote(comment)}" if comment.present?
454
+ sql
455
+ end
456
+
457
+ def foreign_keys(table_name)
458
+ raise ArgumentError unless table_name.present?
459
+
460
+ scope = quoted_scope(table_name)
461
+
462
+ # MySQL returns 1 row for each column of composite foreign keys.
463
+ fk_info = internal_exec_query(<<~SQL, "SCHEMA")
464
+ SELECT fk.referenced_table_name AS 'to_table',
465
+ fk.referenced_column_name AS 'primary_key',
466
+ fk.column_name AS 'column',
467
+ fk.constraint_name AS 'name',
468
+ fk.ordinal_position AS 'position',
469
+ rc.update_rule AS 'on_update',
470
+ rc.delete_rule AS 'on_delete'
471
+ FROM information_schema.referential_constraints rc
472
+ JOIN information_schema.key_column_usage fk
473
+ USING (constraint_schema, constraint_name)
474
+ WHERE fk.referenced_column_name IS NOT NULL
475
+ AND fk.table_schema = #{scope[:schema]}
476
+ AND fk.table_name = #{scope[:name]}
477
+ AND rc.constraint_schema = #{scope[:schema]}
478
+ AND rc.table_name = #{scope[:name]}
479
+ SQL
480
+
481
+ grouped_fk = fk_info.group_by { |row| row["name"] }.values.each { |group| group.sort_by! { |row| row["position"] } }
482
+ grouped_fk.map do |group|
483
+ row = group.first
484
+ options = {
485
+ name: row["name"],
486
+ on_update: extract_foreign_key_action(row["on_update"]),
487
+ on_delete: extract_foreign_key_action(row["on_delete"])
488
+ }
489
+
490
+ if group.one?
491
+ options[:column] = unquote_identifier(row["column"])
492
+ options[:primary_key] = row["primary_key"]
493
+ else
494
+ options[:column] = group.map { |row| unquote_identifier(row["column"]) }
495
+ options[:primary_key] = group.map { |row| row["primary_key"] }
496
+ end
497
+
498
+ ForeignKeyDefinition.new(table_name, unquote_identifier(row["to_table"]), options)
499
+ end
500
+ end
501
+
502
+ def check_constraints(table_name)
503
+ if supports_check_constraints?
504
+ scope = quoted_scope(table_name)
505
+
506
+ sql = <<~SQL
507
+ SELECT cc.constraint_name AS 'name',
508
+ cc.check_clause AS 'expression'
509
+ FROM information_schema.check_constraints cc
510
+ JOIN information_schema.table_constraints tc
511
+ USING (constraint_schema, constraint_name)
512
+ WHERE tc.table_schema = #{scope[:schema]}
513
+ AND tc.table_name = #{scope[:name]}
514
+ AND cc.constraint_schema = #{scope[:schema]}
515
+ SQL
516
+ sql += " AND cc.table_name = #{scope[:name]}" if mariadb?
517
+
518
+ chk_info = internal_exec_query(sql, "SCHEMA")
519
+
520
+ chk_info.map do |row|
521
+ options = {
522
+ name: row["name"]
523
+ }
524
+ expression = row["expression"]
525
+ expression = expression[1..-2] if expression.start_with?("(") && expression.end_with?(")")
526
+ expression = strip_whitespace_characters(expression)
527
+
528
+ unless mariadb?
529
+ # MySQL returns check constraints expression in an already escaped form.
530
+ # This leads to duplicate escaping later (e.g. when the expression is used in the SchemaDumper).
531
+ expression = expression.gsub("\\'", "'")
532
+ end
533
+
534
+ CheckConstraintDefinition.new(table_name, expression, options)
535
+ end
536
+ else
537
+ raise NotImplementedError
538
+ end
539
+ end
540
+
541
+ def table_options(table_name) # :nodoc:
542
+ create_table_info = create_table_info(table_name)
543
+
544
+ # strip create_definitions and partition_options
545
+ # Be aware that `create_table_info` might not include any table options due to `NO_TABLE_OPTIONS` sql mode.
546
+ raw_table_options = create_table_info.sub(/\A.*\n\) ?/m, "").sub(/\n\/\*!.*\*\/\n\z/m, "").strip
547
+
548
+ return if raw_table_options.empty?
549
+
550
+ table_options = {}
551
+
552
+ if / DEFAULT CHARSET=(?<charset>\w+)(?: COLLATE=(?<collation>\w+))?/ =~ raw_table_options
553
+ raw_table_options = $` + $' # before part + after part
554
+ table_options[:charset] = charset
555
+ table_options[:collation] = collation if collation
556
+ end
557
+
558
+ # strip AUTO_INCREMENT
559
+ raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
560
+
561
+ # strip COMMENT
562
+ if raw_table_options.sub!(/ COMMENT='.+'/, "")
563
+ table_options[:comment] = table_comment(table_name)
564
+ end
565
+
566
+ table_options[:options] = raw_table_options unless raw_table_options == "ENGINE=InnoDB"
567
+ table_options
568
+ end
569
+
570
+ # SHOW VARIABLES LIKE 'name'
571
+ def show_variable(name)
572
+ query_value("SELECT @@#{name}", "SCHEMA", materialize_transactions: false, allow_retry: true)
573
+ rescue ActiveRecord::StatementInvalid
574
+ nil
575
+ end
576
+
577
+ def primary_keys(table_name) # :nodoc:
578
+ raise ArgumentError unless table_name.present?
579
+
580
+ scope = quoted_scope(table_name)
581
+
582
+ query_values(<<~SQL, "SCHEMA")
583
+ SELECT column_name
584
+ FROM information_schema.statistics
585
+ WHERE index_name = 'PRIMARY'
586
+ AND table_schema = #{scope[:schema]}
587
+ AND table_name = #{scope[:name]}
588
+ ORDER BY seq_in_index
589
+ SQL
590
+ end
591
+
592
+ def case_sensitive_comparison(attribute, value) # :nodoc:
593
+ column = column_for_attribute(attribute)
594
+
595
+ if column.collation && !column.case_sensitive?
596
+ attribute.eq(Arel::Nodes::Bin.new(value))
597
+ else
598
+ super
599
+ end
600
+ end
601
+
602
+ def can_perform_case_insensitive_comparison_for?(column)
603
+ column.case_sensitive?
604
+ end
605
+ private :can_perform_case_insensitive_comparison_for?
606
+
607
+ # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
608
+ # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
609
+ # distinct queries, and requires that the ORDER BY include the distinct column.
610
+ # See https://dev.mysql.com/doc/refman/en/group-by-handling.html
611
+ def columns_for_distinct(columns, orders) # :nodoc:
612
+ order_columns = orders.compact_blank.map { |s|
613
+ # Convert Arel node to string
614
+ s = visitor.compile(s) unless s.is_a?(String)
615
+ # Remove any ASC/DESC modifiers
616
+ s.gsub(/\s+(?:ASC|DESC)\b/i, "")
617
+ }.compact_blank.map.with_index { |column, i| "#{column} AS alias_#{i}" }
618
+
619
+ (order_columns << super).join(", ")
620
+ end
621
+
622
+ def strict_mode?
623
+ self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
624
+ end
625
+
626
+ def default_index_type?(index) # :nodoc:
627
+ index.using == :btree || super
628
+ end
629
+
630
+ def build_insert_sql(insert) # :nodoc:
631
+ no_op_column = quote_column_name(insert.keys.first)
632
+
633
+ # MySQL 8.0.19 replaces `VALUES(<expression>)` clauses with row and column alias names, see https://dev.mysql.com/worklog/task/?id=6312 .
634
+ # then MySQL 8.0.20 deprecates the `VALUES(<expression>)` see https://dev.mysql.com/worklog/task/?id=13325 .
635
+ if supports_insert_raw_alias_syntax?
636
+ values_alias = quote_table_name("#{insert.model.table_name.parameterize}_values")
637
+ sql = +"INSERT #{insert.into} #{insert.values_list} AS #{values_alias}"
638
+
639
+ if insert.skip_duplicates?
640
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{values_alias}.#{no_op_column}"
641
+ elsif insert.update_duplicates?
642
+ if insert.raw_update_sql?
643
+ sql = +"INSERT #{insert.into} #{insert.values_list} ON DUPLICATE KEY UPDATE #{insert.raw_update_sql}"
644
+ else
645
+ sql << " ON DUPLICATE KEY UPDATE "
646
+ sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column}<=>#{values_alias}.#{column}" }
647
+ sql << insert.updatable_columns.map { |column| "#{column}=#{values_alias}.#{column}" }.join(",")
648
+ end
649
+ end
650
+ else
651
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
652
+
653
+ if insert.skip_duplicates?
654
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
655
+ elsif insert.update_duplicates?
656
+ sql << " ON DUPLICATE KEY UPDATE "
657
+ if insert.raw_update_sql?
658
+ sql << insert.raw_update_sql
659
+ else
660
+ sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
661
+ sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
662
+ end
663
+ end
664
+ end
665
+
666
+ sql << " RETURNING #{insert.returning}" if insert.returning
667
+ sql
668
+ end
669
+
670
+ def check_version # :nodoc:
671
+ if database_version < "5.6.4"
672
+ raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.6.4."
673
+ end
674
+ end
675
+
676
+ #--
677
+ # QUOTING ==================================================
678
+ #++
679
+
680
+ # Quotes strings for use in SQL input.
681
+ def quote_string(string)
682
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
683
+ connection.escape(string)
684
+ end
685
+ end
686
+
687
+ class << self
688
+ def extended_type_map(default_timezone: nil, emulate_booleans:) # :nodoc:
689
+ super(default_timezone: default_timezone).tap do |m|
690
+ if emulate_booleans
691
+ m.register_type %r(^tinyint\(1\))i, Type::Boolean.new
692
+ end
693
+ end
694
+ end
695
+
696
+ private
697
+ def initialize_type_map(m)
698
+ super
699
+
700
+ m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
701
+ m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
702
+ m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
703
+ m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
704
+ m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
705
+ m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
706
+ m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
707
+ m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
708
+ m.register_type %r(^float)i, Type::Float.new(limit: 24)
709
+ m.register_type %r(^double)i, Type::Float.new(limit: 53)
710
+
711
+ register_integer_type m, %r(^bigint)i, limit: 8
712
+ register_integer_type m, %r(^int)i, limit: 4
713
+ register_integer_type m, %r(^mediumint)i, limit: 3
714
+ register_integer_type m, %r(^smallint)i, limit: 2
715
+ register_integer_type m, %r(^tinyint)i, limit: 1
716
+
717
+ m.alias_type %r(year)i, "integer"
718
+ m.alias_type %r(bit)i, "binary"
719
+ end
720
+
721
+ def register_integer_type(mapping, key, **options)
722
+ mapping.register_type(key) do |sql_type|
723
+ if /\bunsigned\b/.match?(sql_type)
724
+ Type::UnsignedInteger.new(**options)
725
+ else
726
+ Type::Integer.new(**options)
727
+ end
728
+ end
729
+ end
730
+
731
+ def extract_precision(sql_type)
732
+ if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
733
+ super || 0
734
+ else
735
+ super
736
+ end
737
+ end
738
+ end
739
+
740
+ EXTENDED_TYPE_MAPS = Concurrent::Map.new
741
+ EMULATE_BOOLEANS_TRUE = { emulate_booleans: true }.freeze
742
+
743
+ private
744
+ def strip_whitespace_characters(expression)
745
+ expression = expression.gsub(/\\n|\\\\/, "")
746
+ expression = expression.gsub(/\s{2,}/, " ")
747
+ expression
748
+ end
749
+
750
+ def extended_type_map_key
751
+ if @default_timezone
752
+ { default_timezone: @default_timezone, emulate_booleans: emulate_booleans }
753
+ elsif emulate_booleans
754
+ EMULATE_BOOLEANS_TRUE
755
+ end
756
+ end
757
+
758
+ def handle_warnings(sql)
759
+ return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
760
+
761
+ @affected_rows_before_warnings = @raw_connection.affected_rows
762
+ warning_count = @raw_connection.warning_count
763
+ result = @raw_connection.query("SHOW WARNINGS")
764
+ result = [
765
+ ["Warning", nil, "Query had warning_count=#{warning_count} but ‘SHOW WARNINGS’ did not return the warnings. Check MySQL logs or database configuration."],
766
+ ] if result.count == 0
767
+ result.each do |level, code, message|
768
+ warning = SQLWarning.new(message, code, level, sql, @pool)
769
+ next if warning_ignored?(warning)
770
+
771
+ ActiveRecord.db_warnings_action.call(warning)
772
+ end
773
+ end
774
+
775
+ def warning_ignored?(warning)
776
+ warning.level == "Note" || super
777
+ end
778
+
779
+ # See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html
780
+ ER_DB_CREATE_EXISTS = 1007
781
+ ER_FILSORT_ABORT = 1028
782
+ ER_DUP_ENTRY = 1062
783
+ ER_SERVER_SHUTDOWN = 1053
784
+ ER_NOT_NULL_VIOLATION = 1048
785
+ ER_NO_REFERENCED_ROW = 1216
786
+ ER_ROW_IS_REFERENCED = 1217
787
+ ER_DO_NOT_HAVE_DEFAULT = 1364
788
+ ER_ROW_IS_REFERENCED_2 = 1451
789
+ ER_NO_REFERENCED_ROW_2 = 1452
790
+ ER_DATA_TOO_LONG = 1406
791
+ ER_OUT_OF_RANGE = 1264
792
+ ER_LOCK_DEADLOCK = 1213
793
+ ER_CANNOT_ADD_FOREIGN = 1215
794
+ ER_CANNOT_CREATE_TABLE = 1005
795
+ ER_LOCK_WAIT_TIMEOUT = 1205
796
+ ER_QUERY_INTERRUPTED = 1317
797
+ ER_CONNECTION_KILLED = 1927
798
+ CR_SERVER_GONE_ERROR = 2006
799
+ CR_SERVER_LOST = 2013
800
+ ER_QUERY_TIMEOUT = 3024
801
+ ER_FK_INCOMPATIBLE_COLUMNS = 3780
802
+ ER_CLIENT_INTERACTION_TIMEOUT = 4031
803
+
804
+ def translate_exception(exception, message:, sql:, binds:)
805
+ case error_number(exception)
806
+ when nil
807
+ if exception.message.match?(/MySQL client is not connected/i)
808
+ ConnectionNotEstablished.new(exception, connection_pool: @pool)
809
+ else
810
+ super
811
+ end
812
+ when ER_CONNECTION_KILLED, ER_SERVER_SHUTDOWN, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
813
+ ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool)
814
+ when ER_DB_CREATE_EXISTS
815
+ DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
816
+ when ER_DUP_ENTRY
817
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
818
+ when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
819
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
820
+ when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS
821
+ mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
822
+ when ER_CANNOT_CREATE_TABLE
823
+ if message.include?("errno: 150")
824
+ mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
825
+ else
826
+ super
827
+ end
828
+ when ER_DATA_TOO_LONG
829
+ ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
830
+ when ER_OUT_OF_RANGE
831
+ RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
832
+ when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
833
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
834
+ when ER_LOCK_DEADLOCK
835
+ Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
836
+ when ER_LOCK_WAIT_TIMEOUT
837
+ LockWaitTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
838
+ when ER_QUERY_TIMEOUT, ER_FILSORT_ABORT
839
+ StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
840
+ when ER_QUERY_INTERRUPTED
841
+ QueryCanceled.new(message, sql: sql, binds: binds, connection_pool: @pool)
842
+ else
843
+ super
844
+ end
845
+ end
846
+
847
+ def change_column_for_alter(table_name, column_name, type, **options)
848
+ cd = build_change_column_definition(table_name, column_name, type, **options)
849
+ schema_creation.accept(cd)
850
+ end
851
+
852
+ def rename_column_for_alter(table_name, column_name, new_column_name)
853
+ return rename_column_sql(table_name, column_name, new_column_name) if supports_rename_column?
854
+
855
+ column = column_for(table_name, column_name)
856
+ options = {
857
+ default: column.default,
858
+ null: column.null,
859
+ auto_increment: column.auto_increment?,
860
+ comment: column.comment
861
+ }
862
+
863
+ current_type = internal_exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
864
+ td = create_table_definition(table_name)
865
+ cd = td.new_column_definition(new_column_name, current_type, **options)
866
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
867
+ end
868
+
869
+ def add_index_for_alter(table_name, column_name, **options)
870
+ index, algorithm, _ = add_index_options(table_name, column_name, **options)
871
+ algorithm = ", #{algorithm}" if algorithm
872
+
873
+ "ADD #{schema_creation.accept(index)}#{algorithm}"
874
+ end
875
+
876
+ def remove_index_for_alter(table_name, column_name = nil, **options)
877
+ index_name = index_name_for_remove(table_name, column_name, options)
878
+ "DROP INDEX #{quote_column_name(index_name)}"
879
+ end
880
+
881
+ def supports_insert_raw_alias_syntax?
882
+ !mariadb? && database_version >= "8.0.19"
883
+ end
884
+
885
+ def supports_rename_index?
886
+ if mariadb?
887
+ database_version >= "10.5.2"
888
+ else
889
+ database_version >= "5.7.6"
890
+ end
891
+ end
892
+
893
+ def supports_rename_column?
894
+ if mariadb?
895
+ database_version >= "10.5.2"
896
+ else
897
+ database_version >= "8.0.3"
898
+ end
899
+ end
900
+
901
+ def configure_connection
902
+ super
903
+ variables = @config.fetch(:variables, {}).stringify_keys
904
+
905
+ # Increase timeout so the server doesn't disconnect us.
906
+ wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
907
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
908
+ variables["wait_timeout"] = wait_timeout
909
+
910
+ defaults = [":default", :default].to_set
911
+
912
+ # Make MySQL reject illegal values rather than truncating or blanking them, see
913
+ # https://dev.mysql.com/doc/refman/en/sql-mode.html#sqlmode_strict_all_tables
914
+ # If the user has provided another value for sql_mode, don't replace it.
915
+ if sql_mode = variables.delete("sql_mode")
916
+ sql_mode = quote(sql_mode)
917
+ elsif !defaults.include?(strict_mode?)
918
+ if strict_mode?
919
+ sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')"
920
+ else
921
+ sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')"
922
+ sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')"
923
+ sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')"
924
+ end
925
+ sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
926
+ end
927
+ sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
928
+
929
+ # NAMES does not have an equals sign, see
930
+ # https://dev.mysql.com/doc/refman/en/set-names.html
931
+ # (trailing comma because variable_assignments will always have content)
932
+ if @config[:encoding]
933
+ encoding = +"NAMES #{@config[:encoding]}"
934
+ encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
935
+ encoding << ", "
936
+ end
937
+
938
+ # Gather up all of the SET variables...
939
+ variable_assignments = variables.filter_map do |k, v|
940
+ if defaults.include?(v)
941
+ "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
942
+ elsif !v.nil?
943
+ "@@SESSION.#{k} = #{quote(v)}"
944
+ end
945
+ end.join(", ")
946
+
947
+ # ...and send them all in one query
948
+ raw_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
949
+ end
950
+
951
+ def column_definitions(table_name) # :nodoc:
952
+ internal_exec_query("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA")
953
+ end
954
+
955
+ def create_table_info(table_name) # :nodoc:
956
+ internal_exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
957
+ end
958
+
959
+ def arel_visitor
960
+ Arel::Visitors::MySQL.new(self)
961
+ end
962
+
963
+ def build_statement_pool
964
+ StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
965
+ end
966
+
967
+ def mismatched_foreign_key_details(message:, sql:)
968
+ foreign_key_pat =
969
+ /Referencing column '(\w+)' and referenced/i =~ message ? $1 : '\w+'
970
+
971
+ match = %r/
972
+ (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
973
+ FOREIGN\s+KEY\s*\(`?(?<foreign_key>#{foreign_key_pat})`?\)\s*
974
+ REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
975
+ /xmi.match(sql)
976
+
977
+ options = {}
978
+
979
+ if match
980
+ options[:table] = match[:table]
981
+ options[:foreign_key] = match[:foreign_key]
982
+ options[:target_table] = match[:target_table]
983
+ options[:primary_key] = match[:primary_key]
984
+ options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
985
+ end
986
+
987
+ options
988
+ end
989
+
990
+ def mismatched_foreign_key(message, sql:, binds:, connection_pool:)
991
+ options = {
992
+ message: message,
993
+ sql: sql,
994
+ binds: binds,
995
+ connection_pool: connection_pool
996
+ }
997
+
998
+ if sql
999
+ options.update mismatched_foreign_key_details(message: message, sql: sql)
1000
+ else
1001
+ options[:query_parser] = ->(sql) { mismatched_foreign_key_details(message: message, sql: sql) }
1002
+ end
1003
+
1004
+ MismatchedForeignKey.new(**options)
1005
+ end
1006
+
1007
+ def version_string(full_version_string)
1008
+ if full_version_string && matches = full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)
1009
+ matches[1]
1010
+ else
1011
+ raise DatabaseVersionError, "Unable to parse MySQL version from #{full_version_string.inspect}"
1012
+ end
1013
+ end
1014
+ end
1015
+ end
1016
+ end