omg-activerecord 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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