activerecord 6.0.0 → 7.2.3

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 (376) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +996 -594
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +34 -34
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +22 -20
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +41 -30
  9. data/lib/active_record/associations/association.rb +106 -41
  10. data/lib/active_record/associations/association_scope.rb +30 -21
  11. data/lib/active_record/associations/belongs_to_association.rb +69 -14
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +20 -6
  13. data/lib/active_record/associations/builder/association.rb +39 -6
  14. data/lib/active_record/associations/builder/belongs_to.rb +47 -17
  15. data/lib/active_record/associations/builder/collection_association.rb +14 -6
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -10
  17. data/lib/active_record/associations/builder/has_many.rb +7 -3
  18. data/lib/active_record/associations/builder/has_one.rb +13 -16
  19. data/lib/active_record/associations/builder/singular_association.rb +7 -3
  20. data/lib/active_record/associations/collection_association.rb +90 -53
  21. data/lib/active_record/associations/collection_proxy.rb +54 -19
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/errors.rb +265 -0
  24. data/lib/active_record/associations/foreign_association.rb +21 -1
  25. data/lib/active_record/associations/has_many_association.rb +41 -10
  26. data/lib/active_record/associations/has_many_through_association.rb +29 -12
  27. data/lib/active_record/associations/has_one_association.rb +33 -9
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +41 -17
  30. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  31. data/lib/active_record/associations/join_dependency.rb +97 -54
  32. data/lib/active_record/associations/nested_error.rb +47 -0
  33. data/lib/active_record/associations/preloader/association.rb +237 -54
  34. data/lib/active_record/associations/preloader/batch.rb +48 -0
  35. data/lib/active_record/associations/preloader/branch.rb +153 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +51 -17
  37. data/lib/active_record/associations/preloader.rb +55 -121
  38. data/lib/active_record/associations/singular_association.rb +16 -4
  39. data/lib/active_record/associations/through_association.rb +26 -15
  40. data/lib/active_record/associations.rb +454 -440
  41. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  42. data/lib/active_record/attribute_assignment.rb +11 -14
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +36 -11
  44. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  45. data/lib/active_record/attribute_methods/dirty.rb +75 -34
  46. data/lib/active_record/attribute_methods/primary_key.rb +53 -31
  47. data/lib/active_record/attribute_methods/query.rb +31 -22
  48. data/lib/active_record/attribute_methods/read.rb +16 -17
  49. data/lib/active_record/attribute_methods/serialization.rb +177 -35
  50. data/lib/active_record/attribute_methods/time_zone_conversion.rb +18 -15
  51. data/lib/active_record/attribute_methods/write.rb +16 -28
  52. data/lib/active_record/attribute_methods.rb +227 -100
  53. data/lib/active_record/attributes.rb +94 -56
  54. data/lib/active_record/autosave_association.rb +119 -73
  55. data/lib/active_record/base.rb +31 -21
  56. data/lib/active_record/callbacks.rb +168 -55
  57. data/lib/active_record/coders/column_serializer.rb +61 -0
  58. data/lib/active_record/coders/json.rb +1 -1
  59. data/lib/active_record/coders/yaml_column.rb +70 -25
  60. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +367 -565
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -57
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +277 -89
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +241 -69
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +122 -134
  68. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  69. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  70. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +324 -72
  71. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +17 -4
  72. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +611 -211
  73. data/lib/active_record/connection_adapters/abstract/transaction.rb +425 -82
  74. data/lib/active_record/connection_adapters/abstract_adapter.rb +698 -211
  75. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +464 -239
  76. data/lib/active_record/connection_adapters/column.rb +28 -1
  77. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  78. data/lib/active_record/connection_adapters/mysql/column.rb +2 -1
  79. data/lib/active_record/connection_adapters/mysql/database_statements.rb +32 -137
  80. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  81. data/lib/active_record/connection_adapters/mysql/quoting.rb +90 -43
  82. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +41 -7
  83. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +18 -1
  84. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +13 -4
  85. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +53 -15
  86. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  87. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  88. data/lib/active_record/connection_adapters/mysql2_adapter.rb +127 -63
  89. data/lib/active_record/connection_adapters/pool_config.rb +83 -0
  90. data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
  91. data/lib/active_record/connection_adapters/postgresql/column.rb +54 -2
  92. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +127 -100
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +9 -5
  95. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
  97. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  98. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -15
  99. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
  101. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +5 -4
  103. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +35 -8
  106. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  110. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -4
  111. data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
  112. data/lib/active_record/connection_adapters/postgresql/quoting.rb +139 -106
  113. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -2
  114. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +98 -4
  115. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +176 -4
  116. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -1
  117. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -118
  118. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  119. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -11
  120. data/lib/active_record/connection_adapters/postgresql_adapter.rb +585 -295
  121. data/lib/active_record/connection_adapters/schema_cache.rb +399 -60
  122. data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
  123. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  124. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +99 -48
  125. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +80 -54
  126. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +27 -1
  127. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  128. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  129. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +102 -24
  130. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +425 -174
  131. data/lib/active_record/connection_adapters/statement_pool.rb +7 -1
  132. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  133. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  134. data/lib/active_record/connection_adapters.rb +176 -0
  135. data/lib/active_record/connection_handling.rb +243 -115
  136. data/lib/active_record/core.rb +481 -199
  137. data/lib/active_record/counter_cache.rb +69 -32
  138. data/lib/active_record/database_configurations/connection_url_resolver.rb +107 -0
  139. data/lib/active_record/database_configurations/database_config.rb +77 -10
  140. data/lib/active_record/database_configurations/hash_config.rb +148 -26
  141. data/lib/active_record/database_configurations/url_config.rb +44 -45
  142. data/lib/active_record/database_configurations.rb +190 -114
  143. data/lib/active_record/delegated_type.rb +279 -0
  144. data/lib/active_record/deprecator.rb +7 -0
  145. data/lib/active_record/destroy_association_async_job.rb +38 -0
  146. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  147. data/lib/active_record/dynamic_matchers.rb +5 -6
  148. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  149. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  150. data/lib/active_record/encryption/cipher.rb +53 -0
  151. data/lib/active_record/encryption/config.rb +68 -0
  152. data/lib/active_record/encryption/configurable.rb +60 -0
  153. data/lib/active_record/encryption/context.rb +42 -0
  154. data/lib/active_record/encryption/contexts.rb +76 -0
  155. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  156. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  157. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  158. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  159. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  160. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  161. data/lib/active_record/encryption/encryptor.rb +171 -0
  162. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  163. data/lib/active_record/encryption/errors.rb +15 -0
  164. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  165. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  166. data/lib/active_record/encryption/key.rb +28 -0
  167. data/lib/active_record/encryption/key_generator.rb +53 -0
  168. data/lib/active_record/encryption/key_provider.rb +46 -0
  169. data/lib/active_record/encryption/message.rb +33 -0
  170. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  171. data/lib/active_record/encryption/message_serializer.rb +96 -0
  172. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  173. data/lib/active_record/encryption/properties.rb +76 -0
  174. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  175. data/lib/active_record/encryption/scheme.rb +100 -0
  176. data/lib/active_record/encryption.rb +58 -0
  177. data/lib/active_record/enum.rb +224 -73
  178. data/lib/active_record/errors.rb +254 -36
  179. data/lib/active_record/explain.rb +30 -17
  180. data/lib/active_record/explain_registry.rb +11 -6
  181. data/lib/active_record/explain_subscriber.rb +2 -2
  182. data/lib/active_record/fixture_set/file.rb +22 -15
  183. data/lib/active_record/fixture_set/model_metadata.rb +15 -6
  184. data/lib/active_record/fixture_set/render_context.rb +3 -1
  185. data/lib/active_record/fixture_set/table_row.rb +88 -16
  186. data/lib/active_record/fixture_set/table_rows.rb +4 -5
  187. data/lib/active_record/fixtures.rb +229 -116
  188. data/lib/active_record/future_result.rb +178 -0
  189. data/lib/active_record/gem_version.rb +4 -4
  190. data/lib/active_record/inheritance.rb +121 -48
  191. data/lib/active_record/insert_all.rb +178 -29
  192. data/lib/active_record/integration.rb +16 -14
  193. data/lib/active_record/internal_metadata.rb +132 -21
  194. data/lib/active_record/legacy_yaml_adapter.rb +3 -36
  195. data/lib/active_record/locking/optimistic.rb +64 -33
  196. data/lib/active_record/locking/pessimistic.rb +21 -8
  197. data/lib/active_record/log_subscriber.rb +61 -30
  198. data/lib/active_record/marshalling.rb +59 -0
  199. data/lib/active_record/message_pack.rb +124 -0
  200. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  201. data/lib/active_record/middleware/database_selector/resolver.rb +19 -19
  202. data/lib/active_record/middleware/database_selector.rb +25 -13
  203. data/lib/active_record/middleware/shard_selector.rb +62 -0
  204. data/lib/active_record/migration/command_recorder.rb +160 -55
  205. data/lib/active_record/migration/compatibility.rb +286 -43
  206. data/lib/active_record/migration/default_strategy.rb +22 -0
  207. data/lib/active_record/migration/execution_strategy.rb +19 -0
  208. data/lib/active_record/migration/join_table.rb +1 -2
  209. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  210. data/lib/active_record/migration.rb +421 -193
  211. data/lib/active_record/model_schema.rb +217 -125
  212. data/lib/active_record/nested_attributes.rb +62 -27
  213. data/lib/active_record/no_touching.rb +4 -4
  214. data/lib/active_record/normalization.rb +163 -0
  215. data/lib/active_record/persistence.rb +322 -319
  216. data/lib/active_record/promise.rb +84 -0
  217. data/lib/active_record/query_cache.rb +18 -15
  218. data/lib/active_record/query_logs.rb +193 -0
  219. data/lib/active_record/query_logs_formatter.rb +41 -0
  220. data/lib/active_record/querying.rb +54 -14
  221. data/lib/active_record/railtie.rb +250 -72
  222. data/lib/active_record/railties/console_sandbox.rb +2 -4
  223. data/lib/active_record/railties/controller_runtime.rb +25 -11
  224. data/lib/active_record/railties/databases.rake +312 -197
  225. data/lib/active_record/railties/job_runtime.rb +23 -0
  226. data/lib/active_record/readonly_attributes.rb +45 -3
  227. data/lib/active_record/reflection.rb +389 -146
  228. data/lib/active_record/relation/batches/batch_enumerator.rb +61 -16
  229. data/lib/active_record/relation/batches.rb +214 -73
  230. data/lib/active_record/relation/calculations.rb +379 -124
  231. data/lib/active_record/relation/delegation.rb +36 -23
  232. data/lib/active_record/relation/finder_methods.rb +159 -49
  233. data/lib/active_record/relation/from_clause.rb +5 -1
  234. data/lib/active_record/relation/merger.rb +41 -33
  235. data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -11
  236. data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -7
  237. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +20 -13
  238. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  239. data/lib/active_record/relation/predicate_builder.rb +79 -53
  240. data/lib/active_record/relation/query_attribute.rb +30 -12
  241. data/lib/active_record/relation/query_methods.rb +1156 -279
  242. data/lib/active_record/relation/record_fetch_warning.rb +12 -11
  243. data/lib/active_record/relation/spawn_methods.rb +10 -9
  244. data/lib/active_record/relation/where_clause.rb +100 -66
  245. data/lib/active_record/relation.rb +829 -194
  246. data/lib/active_record/result.rb +76 -56
  247. data/lib/active_record/runtime_registry.rb +71 -13
  248. data/lib/active_record/sanitization.rb +86 -47
  249. data/lib/active_record/schema.rb +39 -23
  250. data/lib/active_record/schema_dumper.rb +140 -33
  251. data/lib/active_record/schema_migration.rb +74 -29
  252. data/lib/active_record/scoping/default.rb +73 -19
  253. data/lib/active_record/scoping/named.rb +10 -28
  254. data/lib/active_record/scoping.rb +65 -35
  255. data/lib/active_record/secure_password.rb +60 -0
  256. data/lib/active_record/secure_token.rb +34 -8
  257. data/lib/active_record/serialization.rb +11 -4
  258. data/lib/active_record/signed_id.rb +138 -0
  259. data/lib/active_record/statement_cache.rb +26 -10
  260. data/lib/active_record/store.rb +19 -14
  261. data/lib/active_record/suppressor.rb +15 -17
  262. data/lib/active_record/table_metadata.rb +46 -36
  263. data/lib/active_record/tasks/database_tasks.rb +371 -205
  264. data/lib/active_record/tasks/mysql_database_tasks.rb +43 -36
  265. data/lib/active_record/tasks/postgresql_database_tasks.rb +54 -41
  266. data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -13
  267. data/lib/active_record/test_databases.rb +5 -4
  268. data/lib/active_record/test_fixtures.rb +189 -104
  269. data/lib/active_record/testing/query_assertions.rb +121 -0
  270. data/lib/active_record/timestamp.rb +35 -25
  271. data/lib/active_record/token_for.rb +123 -0
  272. data/lib/active_record/touch_later.rb +31 -27
  273. data/lib/active_record/transaction.rb +132 -0
  274. data/lib/active_record/transactions.rb +131 -99
  275. data/lib/active_record/translation.rb +3 -5
  276. data/lib/active_record/type/adapter_specific_registry.rb +33 -18
  277. data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
  278. data/lib/active_record/type/internal/timezone.rb +7 -2
  279. data/lib/active_record/type/serialized.rb +11 -6
  280. data/lib/active_record/type/time.rb +14 -0
  281. data/lib/active_record/type/type_map.rb +17 -21
  282. data/lib/active_record/type/unsigned_integer.rb +0 -1
  283. data/lib/active_record/type.rb +7 -2
  284. data/lib/active_record/type_caster/connection.rb +4 -5
  285. data/lib/active_record/type_caster/map.rb +8 -5
  286. data/lib/active_record/validations/absence.rb +1 -1
  287. data/lib/active_record/validations/associated.rb +13 -8
  288. data/lib/active_record/validations/numericality.rb +36 -0
  289. data/lib/active_record/validations/presence.rb +5 -28
  290. data/lib/active_record/validations/uniqueness.rb +88 -18
  291. data/lib/active_record/validations.rb +15 -8
  292. data/lib/active_record/version.rb +1 -1
  293. data/lib/active_record.rb +446 -40
  294. data/lib/arel/alias_predication.rb +1 -1
  295. data/lib/arel/attributes/attribute.rb +4 -8
  296. data/lib/arel/collectors/bind.rb +8 -1
  297. data/lib/arel/collectors/composite.rb +15 -0
  298. data/lib/arel/collectors/sql_string.rb +7 -0
  299. data/lib/arel/collectors/substitute_binds.rb +7 -0
  300. data/lib/arel/crud.rb +30 -22
  301. data/lib/arel/delete_manager.rb +23 -4
  302. data/lib/arel/errors.rb +10 -0
  303. data/lib/arel/factory_methods.rb +4 -0
  304. data/lib/arel/filter_predications.rb +9 -0
  305. data/lib/arel/insert_manager.rb +2 -3
  306. data/lib/arel/nodes/binary.rb +82 -9
  307. data/lib/arel/nodes/bind_param.rb +8 -0
  308. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  309. data/lib/arel/nodes/casted.rb +22 -10
  310. data/lib/arel/nodes/cte.rb +36 -0
  311. data/lib/arel/nodes/delete_statement.rb +14 -13
  312. data/lib/arel/nodes/equality.rb +6 -9
  313. data/lib/arel/nodes/filter.rb +10 -0
  314. data/lib/arel/nodes/fragments.rb +35 -0
  315. data/lib/arel/nodes/function.rb +1 -0
  316. data/lib/arel/nodes/grouping.rb +3 -0
  317. data/lib/arel/nodes/homogeneous_in.rb +68 -0
  318. data/lib/arel/nodes/in.rb +8 -1
  319. data/lib/arel/nodes/infix_operation.rb +13 -1
  320. data/lib/arel/nodes/insert_statement.rb +2 -2
  321. data/lib/arel/nodes/join_source.rb +1 -1
  322. data/lib/arel/nodes/leading_join.rb +8 -0
  323. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  324. data/lib/arel/nodes/node.rb +122 -11
  325. data/lib/arel/nodes/ordering.rb +27 -0
  326. data/lib/arel/nodes/select_core.rb +2 -2
  327. data/lib/arel/nodes/select_statement.rb +2 -2
  328. data/lib/arel/nodes/sql_literal.rb +16 -0
  329. data/lib/arel/nodes/table_alias.rb +11 -3
  330. data/lib/arel/nodes/unary.rb +0 -1
  331. data/lib/arel/nodes/update_statement.rb +11 -4
  332. data/lib/arel/nodes.rb +10 -3
  333. data/lib/arel/predications.rb +31 -28
  334. data/lib/arel/select_manager.rb +18 -9
  335. data/lib/arel/table.rb +21 -10
  336. data/lib/arel/tree_manager.rb +8 -15
  337. data/lib/arel/update_manager.rb +25 -5
  338. data/lib/arel/visitors/dot.rb +94 -90
  339. data/lib/arel/visitors/mysql.rb +34 -6
  340. data/lib/arel/visitors/postgresql.rb +5 -16
  341. data/lib/arel/visitors/sqlite.rb +25 -1
  342. data/lib/arel/visitors/to_sql.rb +227 -81
  343. data/lib/arel/visitors/visitor.rb +2 -3
  344. data/lib/arel/visitors.rb +0 -7
  345. data/lib/arel.rb +37 -15
  346. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  347. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  348. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  349. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  350. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +6 -1
  351. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
  352. data/lib/rails/generators/active_record/migration.rb +9 -3
  353. data/lib/rails/generators/active_record/model/USAGE +113 -0
  354. data/lib/rails/generators/active_record/model/model_generator.rb +49 -4
  355. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  356. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  357. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  358. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  359. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  360. metadata +117 -30
  361. data/lib/active_record/attribute_decorators.rb +0 -90
  362. data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
  363. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  364. data/lib/active_record/define_callbacks.rb +0 -22
  365. data/lib/active_record/null_relation.rb +0 -68
  366. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  367. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  368. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  369. data/lib/arel/attributes.rb +0 -22
  370. data/lib/arel/visitors/depth_first.rb +0 -204
  371. data/lib/arel/visitors/ibm_db.rb +0 -34
  372. data/lib/arel/visitors/informix.rb +0 -62
  373. data/lib/arel/visitors/mssql.rb +0 -157
  374. data/lib/arel/visitors/oracle.rb +0 -159
  375. data/lib/arel/visitors/oracle12.rb +0 -66
  376. data/lib/arel/visitors/where_sql.rb +0 -23
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
+ # = Active Record Connection Handling
4
5
  module ConnectionHandling
5
6
  RAILS_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"].presence || ENV["RACK_ENV"].presence }
6
7
  DEFAULT_ENV = -> { RAILS_ENV.call || "default_env" }
@@ -38,25 +39,25 @@ module ActiveRecord
38
39
  # )
39
40
  #
40
41
  # In case {ActiveRecord::Base.configurations}[rdoc-ref:Core.configurations]
41
- # is set (Rails automatically loads the contents of config/database.yml into it),
42
+ # is set (\Rails automatically loads the contents of config/database.yml into it),
42
43
  # a symbol can also be given as argument, representing a key in the
43
44
  # configuration hash:
44
45
  #
45
46
  # ActiveRecord::Base.establish_connection(:production)
46
47
  #
47
- # The exceptions AdapterNotSpecified, AdapterNotFound and +ArgumentError+
48
+ # The exceptions AdapterNotSpecified, AdapterNotFound, and +ArgumentError+
48
49
  # may be returned on an error.
49
50
  def establish_connection(config_or_env = nil)
50
- config_hash = resolve_config_for_connection(config_or_env)
51
- connection_handler.establish_connection(config_hash)
51
+ config_or_env ||= DEFAULT_ENV.call.to_sym
52
+ db_config = resolve_config_for_connection(config_or_env)
53
+ connection_handler.establish_connection(db_config, owner_name: self, role: current_role, shard: current_shard)
52
54
  end
53
55
 
54
56
  # Connects a model to the databases specified. The +database+ keyword
55
57
  # takes a hash consisting of a +role+ and a +database_key+.
56
58
  #
57
- # This will create a connection handler for switching between connections,
58
- # look up the config hash using the +database_key+ and finally
59
- # establishes a connection to that config.
59
+ # This will look up the database config using the +database_key+ and
60
+ # establish a connection to that config.
60
61
  #
61
62
  # class AnimalsModel < ApplicationRecord
62
63
  # self.abstract_class = true
@@ -64,25 +65,53 @@ module ActiveRecord
64
65
  # connects_to database: { writing: :primary, reading: :primary_replica }
65
66
  # end
66
67
  #
67
- # Returns an array of established connections.
68
- def connects_to(database: {})
68
+ # +connects_to+ also supports horizontal sharding. The horizontal sharding API
69
+ # supports read replicas as well. You can connect a model to a list of shards like this:
70
+ #
71
+ # class AnimalsModel < ApplicationRecord
72
+ # self.abstract_class = true
73
+ #
74
+ # connects_to shards: {
75
+ # default: { writing: :primary, reading: :primary_replica },
76
+ # shard_two: { writing: :primary_shard_two, reading: :primary_shard_replica_two }
77
+ # }
78
+ # end
79
+ #
80
+ # Returns an array of database connections.
81
+ def connects_to(database: {}, shards: {})
82
+ raise NotImplementedError, "`connects_to` can only be called on ActiveRecord::Base or abstract classes" unless self == Base || abstract_class?
83
+
84
+ if database.present? && shards.present?
85
+ raise ArgumentError, "`connects_to` can only accept a `database` or `shards` argument, but not both arguments."
86
+ end
87
+
69
88
  connections = []
70
89
 
71
- database.each do |role, database_key|
72
- config_hash = resolve_config_for_connection(database_key)
73
- handler = lookup_connection_handler(role.to_sym)
90
+ if shards.empty?
91
+ shards[:default] = database
92
+ end
74
93
 
75
- connections << handler.establish_connection(config_hash)
94
+ self.default_shard = shards.keys.first
95
+
96
+ shards.each do |shard, database_keys|
97
+ database_keys.each do |role, database_key|
98
+ db_config = resolve_config_for_connection(database_key)
99
+
100
+ self.connection_class = true
101
+ connections << connection_handler.establish_connection(db_config, owner_name: self, role: role, shard: shard.to_sym)
102
+ end
76
103
  end
77
104
 
78
105
  connections
79
106
  end
80
107
 
81
- # Connects to a database or role (ex writing, reading, or another
82
- # custom role) for the duration of the block.
108
+ # Connects to a role (e.g. writing, reading, or a custom role) and/or
109
+ # shard for the duration of the block. At the end of the block the
110
+ # connection will be returned to the original role / shard.
83
111
  #
84
- # If a role is passed, Active Record will look up the connection
85
- # based on the requested role:
112
+ # If only a role is passed, Active Record will look up the connection
113
+ # based on the requested role. If a non-established role is requested
114
+ # an +ActiveRecord::ConnectionNotEstablished+ error will be raised:
86
115
  #
87
116
  # ActiveRecord::Base.connected_to(role: :writing) do
88
117
  # Dog.create! # creates dog using dog writing connection
@@ -92,176 +121,275 @@ module ActiveRecord
92
121
  # Dog.create! # throws exception because we're on a replica
93
122
  # end
94
123
  #
95
- # ActiveRecord::Base.connected_to(role: :unknown_role) do
96
- # # raises exception due to non-existent role
97
- # end
124
+ # When swapping to a shard, the role must be passed as well. If a non-existent
125
+ # shard is passed, an +ActiveRecord::ConnectionNotEstablished+ error will be
126
+ # raised.
98
127
  #
99
- # For cases where you may want to connect to a database outside of the model,
100
- # you can use +connected_to+ with a +database+ argument. The +database+ argument
101
- # expects a symbol that corresponds to the database key in your config.
128
+ # When a shard and role is passed, Active Record will first lookup the role,
129
+ # and then look up the connection by shard key.
102
130
  #
103
- # ActiveRecord::Base.connected_to(database: :animals_slow_replica) do
104
- # Dog.run_a_long_query # runs a long query while connected to the +animals_slow_replica+
131
+ # ActiveRecord::Base.connected_to(role: :reading, shard: :shard_one_replica) do
132
+ # Dog.first # finds first Dog record stored on the shard one replica
105
133
  # end
134
+ def connected_to(role: nil, shard: nil, prevent_writes: false, &blk)
135
+ if self != Base && !abstract_class
136
+ raise NotImplementedError, "calling `connected_to` is only allowed on ActiveRecord::Base or abstract classes."
137
+ end
138
+
139
+ if !connection_class? && !primary_class?
140
+ raise NotImplementedError, "calling `connected_to` is only allowed on the abstract class that established the connection."
141
+ end
142
+
143
+ unless role || shard
144
+ raise ArgumentError, "must provide a `shard` and/or `role`."
145
+ end
146
+
147
+ with_role_and_shard(role, shard, prevent_writes, &blk)
148
+ end
149
+
150
+ # Connects a role and/or shard to the provided connection names. Optionally +prevent_writes+
151
+ # can be passed to block writes on a connection. +reading+ will automatically set
152
+ # +prevent_writes+ to true.
106
153
  #
107
- # This will connect to a new database for the queries inside the block. By
108
- # default the `:writing` role will be used since all connections must be assigned
109
- # a role. If you would like to use a different role you can pass a hash to database:
154
+ # +connected_to_many+ is an alternative to deeply nested +connected_to+ blocks.
110
155
  #
111
- # ActiveRecord::Base.connected_to(database: { readonly_slow: :animals_slow_replica }) do
112
- # # runs a long query while connected to the +animals_slow_replica+ using the readonly_slow role.
113
- # Dog.run_a_long_query
114
- # end
156
+ # Usage:
115
157
  #
116
- # When using the database key a new connection will be established every time.
117
- def connected_to(database: nil, role: nil, &blk)
118
- if database && role
119
- raise ArgumentError, "connected_to can only accept a `database` or a `role` argument, but not both arguments."
120
- elsif database
121
- if database.is_a?(Hash)
122
- role, database = database.first
123
- role = role.to_sym
124
- end
158
+ # ActiveRecord::Base.connected_to_many(AnimalsRecord, MealsRecord, role: :reading) do
159
+ # Dog.first # Read from animals replica
160
+ # Dinner.first # Read from meals replica
161
+ # Person.first # Read from primary writer
162
+ # end
163
+ def connected_to_many(*classes, role:, shard: nil, prevent_writes: false)
164
+ classes = classes.flatten
125
165
 
126
- config_hash = resolve_config_for_connection(database)
127
- handler = lookup_connection_handler(role)
166
+ if self != Base || classes.include?(Base)
167
+ raise NotImplementedError, "connected_to_many can only be called on ActiveRecord::Base."
168
+ end
128
169
 
129
- handler.establish_connection(config_hash)
170
+ prevent_writes = true if role == ActiveRecord.reading_role
130
171
 
131
- with_handler(role, &blk)
132
- elsif role
133
- with_handler(role.to_sym, &blk)
134
- else
135
- raise ArgumentError, "must provide a `database` or a `role`."
172
+ append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: classes)
173
+ begin
174
+ yield
175
+ ensure
176
+ connected_to_stack.pop
136
177
  end
137
178
  end
138
179
 
139
- # Returns true if role is the current connected role.
180
+ # Use a specified connection.
140
181
  #
141
- # ActiveRecord::Base.connected_to(role: :writing) do
142
- # ActiveRecord::Base.connected_to?(role: :writing) #=> true
143
- # ActiveRecord::Base.connected_to?(role: :reading) #=> false
144
- # end
145
- def connected_to?(role:)
146
- current_role == role.to_sym
182
+ # This method is useful for ensuring that a specific connection is
183
+ # being used. For example, when booting a console in readonly mode.
184
+ #
185
+ # It is not recommended to use this method in a request since it
186
+ # does not yield to a block like +connected_to+.
187
+ def connecting_to(role: default_role, shard: default_shard, prevent_writes: false)
188
+ prevent_writes = true if role == ActiveRecord.reading_role
189
+
190
+ append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self])
147
191
  end
148
192
 
149
- # Returns the symbol representing the current connected role.
193
+ # Prohibit swapping shards while inside of the passed block.
150
194
  #
151
- # ActiveRecord::Base.connected_to(role: :writing) do
152
- # ActiveRecord::Base.current_role #=> :writing
153
- # end
154
- #
155
- # ActiveRecord::Base.connected_to(role: :reading) do
156
- # ActiveRecord::Base.current_role #=> :reading
157
- # end
158
- def current_role
159
- connection_handlers.key(connection_handler)
195
+ # In some cases you may want to be able to swap shards but not allow a
196
+ # nested call to connected_to or connected_to_many to swap again. This
197
+ # is useful in cases you're using sharding to provide per-request
198
+ # database isolation.
199
+ def prohibit_shard_swapping(enabled = true)
200
+ prev_value = ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping]
201
+ ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping] = enabled
202
+ yield
203
+ ensure
204
+ ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping] = prev_value
160
205
  end
161
206
 
162
- def lookup_connection_handler(handler_key) # :nodoc:
163
- handler_key ||= ActiveRecord::Base.writing_role
164
- connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new
207
+ # Determine whether or not shard swapping is currently prohibited
208
+ def shard_swapping_prohibited?
209
+ ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping]
165
210
  end
166
211
 
167
- def with_handler(handler_key, &blk) # :nodoc:
168
- handler = lookup_connection_handler(handler_key)
169
- swap_connection_handler(handler, &blk)
212
+ # Prevent writing to the database regardless of role.
213
+ #
214
+ # In some cases you may want to prevent writes to the database
215
+ # even if you are on a database that can write. +while_preventing_writes+
216
+ # will prevent writes to the database for the duration of the block.
217
+ #
218
+ # This method does not provide the same protection as a readonly
219
+ # user and is meant to be a safeguard against accidental writes.
220
+ #
221
+ # See +READ_QUERY+ for the queries that are blocked by this
222
+ # method.
223
+ def while_preventing_writes(enabled = true, &block)
224
+ connected_to(role: current_role, prevent_writes: enabled, &block)
170
225
  end
171
226
 
172
- def resolve_config_for_connection(config_or_env) # :nodoc:
173
- raise "Anonymous class is not allowed." unless name
174
-
175
- config_or_env ||= DEFAULT_ENV.call.to_sym
176
- pool_name = primary_class? ? "primary" : name
177
- self.connection_specification_name = pool_name
178
-
179
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
180
- config_hash = resolver.resolve(config_or_env, pool_name).symbolize_keys
181
- config_hash[:name] = pool_name
182
-
183
- config_hash
227
+ # Returns true if role is the current connected role and/or
228
+ # current connected shard. If no shard is passed, the default will be
229
+ # used.
230
+ #
231
+ # ActiveRecord::Base.connected_to(role: :writing) do
232
+ # ActiveRecord::Base.connected_to?(role: :writing) #=> true
233
+ # ActiveRecord::Base.connected_to?(role: :reading) #=> false
234
+ # end
235
+ #
236
+ # ActiveRecord::Base.connected_to(role: :reading, shard: :shard_one) do
237
+ # ActiveRecord::Base.connected_to?(role: :reading, shard: :shard_one) #=> true
238
+ # ActiveRecord::Base.connected_to?(role: :reading, shard: :default) #=> false
239
+ # ActiveRecord::Base.connected_to?(role: :writing, shard: :shard_one) #=> true
240
+ # end
241
+ def connected_to?(role:, shard: ActiveRecord::Base.default_shard)
242
+ current_role == role.to_sym && current_shard == shard.to_sym
184
243
  end
185
244
 
186
245
  # Clears the query cache for all connections associated with the current thread.
187
246
  def clear_query_caches_for_current_thread
188
- ActiveRecord::Base.connection_handlers.each_value do |handler|
189
- handler.connection_pool_list.each do |pool|
190
- pool.connection.clear_query_cache if pool.active_connection?
191
- end
247
+ connection_handler.each_connection_pool do |pool|
248
+ pool.clear_query_cache
192
249
  end
193
250
  end
194
251
 
195
252
  # Returns the connection currently associated with the class. This can
196
253
  # also be used to "borrow" the connection to do database work unrelated
197
254
  # to any of the specific Active Records.
255
+ # The connection will remain leased for the entire duration of the request
256
+ # or job, or until +#release_connection+ is called.
257
+ def lease_connection
258
+ connection_pool.lease_connection
259
+ end
260
+
261
+ # Soft deprecated. Use +#with_connection+ or +#lease_connection+ instead.
198
262
  def connection
199
- retrieve_connection
263
+ pool = connection_pool
264
+ if pool.permanent_lease?
265
+ case ActiveRecord.permanent_connection_checkout
266
+ when :deprecated
267
+ ActiveRecord.deprecator.warn <<~MESSAGE
268
+ Called deprecated `ActiveRecord::Base.connection` method.
269
+
270
+ Either use `with_connection` or `lease_connection`.
271
+ MESSAGE
272
+ when :disallowed
273
+ raise ActiveRecordError, <<~MESSAGE
274
+ Called deprecated `ActiveRecord::Base.connection` method.
275
+
276
+ Either use `with_connection` or `lease_connection`.
277
+ MESSAGE
278
+ end
279
+ pool.lease_connection
280
+ else
281
+ pool.active_connection
282
+ end
283
+ end
284
+
285
+ # Return the currently leased connection into the pool
286
+ def release_connection
287
+ connection_pool.release_connection
288
+ end
289
+
290
+ # Checkouts a connection from the pool, yield it and then check it back in.
291
+ # If a connection was already leased via #lease_connection or a parent call to
292
+ # #with_connection, that same connection is yieled.
293
+ # If #lease_connection is called inside the block, the connection won't be checked
294
+ # back in.
295
+ # If #connection is called inside the block, the connection won't be checked back in
296
+ # unless the +prevent_permanent_checkout+ argument is set to +true+.
297
+ def with_connection(prevent_permanent_checkout: false, &block)
298
+ connection_pool.with_connection(prevent_permanent_checkout: prevent_permanent_checkout, &block)
200
299
  end
201
300
 
202
301
  attr_writer :connection_specification_name
203
302
 
204
- # Return the specification name from the current class or its parent.
303
+ # Returns the connection specification name from the current class or its parent.
205
304
  def connection_specification_name
206
- if !defined?(@connection_specification_name) || @connection_specification_name.nil?
207
- return primary_class? ? "primary" : superclass.connection_specification_name
305
+ if @connection_specification_name.nil?
306
+ return self == Base ? Base.name : superclass.connection_specification_name
208
307
  end
209
308
  @connection_specification_name
210
309
  end
211
310
 
212
311
  def primary_class? # :nodoc:
213
- self == Base || defined?(ApplicationRecord) && self == ApplicationRecord
312
+ self == Base || application_record_class?
214
313
  end
215
314
 
216
- # Returns the configuration of the associated connection as a hash:
315
+ # Returns the db_config object from the associated connection:
217
316
  #
218
- # ActiveRecord::Base.connection_config
219
- # # => {pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"}
317
+ # ActiveRecord::Base.connection_db_config
318
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
319
+ # @name="primary", @config={pool: 5, timeout: 5000, database: "storage/development.sqlite3", adapter: "sqlite3"}>
220
320
  #
221
- # Please use only for reading.
222
- def connection_config
223
- connection_pool.spec.config
321
+ # Use only for reading.
322
+ def connection_db_config
323
+ connection_pool.db_config
324
+ end
325
+
326
+ def adapter_class # :nodoc:
327
+ connection_pool.db_config.adapter_class
224
328
  end
225
329
 
226
330
  def connection_pool
227
- connection_handler.retrieve_connection_pool(connection_specification_name) || raise(ConnectionNotEstablished)
331
+ connection_handler.retrieve_connection_pool(connection_specification_name, role: current_role, shard: current_shard, strict: true)
228
332
  end
229
333
 
230
334
  def retrieve_connection
231
- connection_handler.retrieve_connection(connection_specification_name)
335
+ connection_handler.retrieve_connection(connection_specification_name, role: current_role, shard: current_shard)
232
336
  end
233
337
 
234
338
  # Returns +true+ if Active Record is connected.
235
339
  def connected?
236
- connection_handler.connected?(connection_specification_name)
340
+ connection_handler.connected?(connection_specification_name, role: current_role, shard: current_shard)
237
341
  end
238
342
 
239
- def remove_connection(name = nil)
240
- name ||= @connection_specification_name if defined?(@connection_specification_name)
343
+ def remove_connection
344
+ name = @connection_specification_name if defined?(@connection_specification_name)
345
+
241
346
  # if removing a connection that has a pool, we reset the
242
347
  # connection_specification_name so it will use the parent
243
348
  # pool.
244
- if connection_handler.retrieve_connection_pool(name)
349
+ if connection_handler.retrieve_connection_pool(name, role: current_role, shard: current_shard)
245
350
  self.connection_specification_name = nil
246
351
  end
247
352
 
248
- connection_handler.remove_connection(name)
353
+ connection_handler.remove_connection_pool(name, role: current_role, shard: current_shard)
249
354
  end
250
355
 
251
- def clear_cache! # :nodoc:
252
- connection.schema_cache.clear!
356
+ def schema_cache # :nodoc:
357
+ connection_pool.schema_cache
253
358
  end
254
359
 
255
- delegate :clear_active_connections!, :clear_reloadable_connections!,
256
- :clear_all_connections!, :flush_idle_connections!, to: :connection_handler
360
+ def clear_cache! # :nodoc:
361
+ connection_pool.schema_cache.clear!
362
+ end
257
363
 
258
364
  private
365
+ def resolve_config_for_connection(config_or_env)
366
+ raise "Anonymous class is not allowed." unless name
259
367
 
260
- def swap_connection_handler(handler, &blk) # :nodoc:
261
- old_handler, ActiveRecord::Base.connection_handler = ActiveRecord::Base.connection_handler, handler
262
- yield
263
- ensure
264
- ActiveRecord::Base.connection_handler = old_handler
368
+ connection_name = primary_class? ? Base.name : name
369
+ self.connection_specification_name = connection_name
370
+
371
+ Base.configurations.resolve(config_or_env)
372
+ end
373
+
374
+ def with_role_and_shard(role, shard, prevent_writes)
375
+ prevent_writes = true if role == ActiveRecord.reading_role
376
+
377
+ append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self])
378
+ begin
379
+ return_value = yield
380
+ return_value.load if return_value.is_a? ActiveRecord::Relation
381
+ return_value
382
+ ensure
383
+ self.connected_to_stack.pop
384
+ end
385
+ end
386
+
387
+ def append_to_connected_to_stack(entry)
388
+ if shard_swapping_prohibited? && entry[:shard].present?
389
+ raise ArgumentError, "cannot swap `shard` while shard swapping is prohibited."
390
+ end
391
+
392
+ connected_to_stack << entry
265
393
  end
266
394
  end
267
395
  end