activerecord 5.2.8 → 7.0.2

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

Potentially problematic release.


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

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