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
@@ -5,11 +5,27 @@ module ActiveRecord
5
5
  class BatchEnumerator
6
6
  include Enumerable
7
7
 
8
- def initialize(of: 1000, start: nil, finish: nil, relation:) #:nodoc:
8
+ def initialize(of: 1000, start: nil, finish: nil, relation:, order: :asc, use_ranges: nil) # :nodoc:
9
9
  @of = of
10
10
  @relation = relation
11
11
  @start = start
12
12
  @finish = finish
13
+ @order = order
14
+ @use_ranges = use_ranges
15
+ end
16
+
17
+ # The primary key value from which the BatchEnumerator starts, inclusive of the value.
18
+ attr_reader :start
19
+
20
+ # The primary key value at which the BatchEnumerator ends, inclusive of the value.
21
+ attr_reader :finish
22
+
23
+ # The relation from which the BatchEnumerator yields batches.
24
+ attr_reader :relation
25
+
26
+ # The size of the batches yielded by the BatchEnumerator.
27
+ def batch_size
28
+ @of
13
29
  end
14
30
 
15
31
  # Looping through a collection of records from the database (using the
@@ -33,24 +49,53 @@ module ActiveRecord
33
49
  # Person.in_batches.each_record.with_index do |person, index|
34
50
  # person.award_trophy(index + 1)
35
51
  # end
36
- def each_record
52
+ def each_record(&block)
37
53
  return to_enum(:each_record) unless block_given?
38
54
 
39
- @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation|
40
- relation.records.each { |record| yield record }
55
+ @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true, order: @order).each do |relation|
56
+ relation.records.each(&block)
57
+ end
58
+ end
59
+
60
+ # Deletes records in batches. Returns the total number of rows affected.
61
+ #
62
+ # Person.in_batches.delete_all
63
+ #
64
+ # See Relation#delete_all for details of how each batch is deleted.
65
+ def delete_all
66
+ sum(&:delete_all)
67
+ end
68
+
69
+ # Updates records in batches. Returns the total number of rows affected.
70
+ #
71
+ # Person.in_batches.update_all("age = age + 1")
72
+ #
73
+ # See Relation#update_all for details of how each batch is updated.
74
+ def update_all(updates)
75
+ sum do |relation|
76
+ relation.update_all(updates)
77
+ end
78
+ end
79
+
80
+ # Touches records in batches. Returns the total number of rows affected.
81
+ #
82
+ # Person.in_batches.touch_all
83
+ #
84
+ # See Relation#touch_all for details of how each batch is touched.
85
+ def touch_all(...)
86
+ sum do |relation|
87
+ relation.touch_all(...)
41
88
  end
42
89
  end
43
90
 
44
- # Delegates #delete_all, #update_all, #destroy_all methods to each batch.
91
+ # Destroys records in batches. Returns the total number of rows affected.
92
+ #
93
+ # Person.where("age < 10").in_batches.destroy_all
45
94
  #
46
- # People.in_batches.delete_all
47
- # People.where('age < 10').in_batches.destroy_all
48
- # People.in_batches.update_all('age = age + 1')
49
- [:delete_all, :update_all, :destroy_all].each do |method|
50
- define_method(method) do |*args, &block|
51
- @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false).each do |relation|
52
- relation.send(method, *args, &block)
53
- end
95
+ # See Relation#destroy_all for details of how each batch is destroyed.
96
+ def destroy_all
97
+ sum do |relation|
98
+ relation.destroy_all.count(&:destroyed?)
54
99
  end
55
100
  end
56
101
 
@@ -59,9 +104,9 @@ module ActiveRecord
59
104
  # Person.in_batches.each do |relation|
60
105
  # relation.update_all(awesome: true)
61
106
  # end
62
- def each
63
- enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false)
64
- return enum.each { |relation| yield relation } if block_given?
107
+ def each(&block)
108
+ enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false, order: @order, use_ranges: @use_ranges)
109
+ return enum.each(&block) if block_given?
65
110
  enum
66
111
  end
67
112
  end
@@ -3,8 +3,10 @@
3
3
  require "active_record/relation/batches/batch_enumerator"
4
4
 
5
5
  module ActiveRecord
6
+ # = Active Record \Batches
6
7
  module Batches
7
8
  ORDER_IGNORE_MESSAGE = "Scoped order is ignored, it's forced to be batch order."
9
+ DEFAULT_ORDER = :asc
8
10
 
9
11
  # Looping through a collection of records from the database
10
12
  # (using the Scoping::Named::ClassMethods.all method, for example)
@@ -37,6 +39,16 @@ module ActiveRecord
37
39
  # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
38
40
  # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
39
41
  # an order is present in the relation.
42
+ # * <tt>:order</tt> - Specifies the primary key order (can be +:asc+ or +:desc+ or an array consisting
43
+ # of :asc or :desc). Defaults to +:asc+.
44
+ #
45
+ # class Order < ActiveRecord::Base
46
+ # self.primary_key = [:id_1, :id_2]
47
+ # end
48
+ #
49
+ # Order.find_each(order: [:asc, :desc])
50
+ #
51
+ # In the above code, +id_1+ is sorted in ascending order and +id_2+ in descending order.
40
52
  #
41
53
  # Limits are honored, and if present there is no requirement for the batch
42
54
  # size: it can be less than, equal to, or greater than the limit.
@@ -57,22 +69,22 @@ module ActiveRecord
57
69
  # person.party_all_night!
58
70
  # end
59
71
  #
60
- # NOTE: It's not possible to set the order. That is automatically set to
61
- # ascending on the primary key ("id ASC") to make the batch ordering
62
- # work. This also means that this method only works when the primary key is
72
+ # NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
73
+ # ascending on the primary key ("id ASC").
74
+ # This also means that this method only works when the primary key is
63
75
  # orderable (e.g. an integer or string).
64
76
  #
65
77
  # NOTE: By its nature, batch processing is subject to race conditions if
66
78
  # other processes are modifying the database.
67
- def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
79
+ def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: DEFAULT_ORDER, &block)
68
80
  if block_given?
69
- find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
70
- records.each { |record| yield record }
81
+ find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do |records|
82
+ records.each(&block)
71
83
  end
72
84
  else
73
- enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
85
+ enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do
74
86
  relation = self
75
- apply_limits(relation, start, finish).size
87
+ apply_limits(relation, start, finish, build_batch_orders(order)).size
76
88
  end
77
89
  end
78
90
  end
@@ -101,6 +113,16 @@ module ActiveRecord
101
113
  # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
102
114
  # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
103
115
  # an order is present in the relation.
116
+ # * <tt>:order</tt> - Specifies the primary key order (can be +:asc+ or +:desc+ or an array consisting
117
+ # of :asc or :desc). Defaults to +:asc+.
118
+ #
119
+ # class Order < ActiveRecord::Base
120
+ # self.primary_key = [:id_1, :id_2]
121
+ # end
122
+ #
123
+ # Order.find_in_batches(order: [:asc, :desc])
124
+ #
125
+ # In the above code, +id_1+ is sorted in ascending order and +id_2+ in descending order.
104
126
  #
105
127
  # Limits are honored, and if present there is no requirement for the batch
106
128
  # size: it can be less than, equal to, or greater than the limit.
@@ -116,23 +138,23 @@ module ActiveRecord
116
138
  # group.each { |person| person.party_all_night! }
117
139
  # end
118
140
  #
119
- # NOTE: It's not possible to set the order. That is automatically set to
120
- # ascending on the primary key ("id ASC") to make the batch ordering
121
- # work. This also means that this method only works when the primary key is
141
+ # NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
142
+ # ascending on the primary key ("id ASC").
143
+ # This also means that this method only works when the primary key is
122
144
  # orderable (e.g. an integer or string).
123
145
  #
124
146
  # NOTE: By its nature, batch processing is subject to race conditions if
125
147
  # other processes are modifying the database.
126
- def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
148
+ def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: DEFAULT_ORDER)
127
149
  relation = self
128
150
  unless block_given?
129
- return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
130
- total = apply_limits(relation, start, finish).size
151
+ return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do
152
+ total = apply_limits(relation, start, finish, build_batch_orders(order)).size
131
153
  (total - 1).div(batch_size) + 1
132
154
  end
133
155
  end
134
156
 
135
- in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore) do |batch|
157
+ in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore, order: order) do |batch|
136
158
  yield batch.to_a
137
159
  end
138
160
  end
@@ -165,6 +187,22 @@ module ActiveRecord
165
187
  # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
166
188
  # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
167
189
  # an order is present in the relation.
190
+ # * <tt>:order</tt> - Specifies the primary key order (can be +:asc+ or +:desc+ or an array consisting
191
+ # of :asc or :desc). Defaults to +:asc+.
192
+ #
193
+ # class Order < ActiveRecord::Base
194
+ # self.primary_key = [:id_1, :id_2]
195
+ # end
196
+ #
197
+ # Order.in_batches(order: [:asc, :desc])
198
+ #
199
+ # In the above code, +id_1+ is sorted in ascending order and +id_2+ in descending order.
200
+ #
201
+ # * <tt>:use_ranges</tt> - Specifies whether to use range iteration (id >= x AND id <= y).
202
+ # It can make iterating over the whole or almost whole tables several times faster.
203
+ # Only whole table iterations use this style of iteration by default. You can disable this behavior by passing +false+.
204
+ # If you iterate over the table and the only condition is, e.g., <tt>archived_at: nil</tt> (and only a tiny fraction
205
+ # of the records are archived), it makes sense to opt in to this approach.
168
206
  #
169
207
  # Limits are honored, and if present there is no requirement for the batch
170
208
  # size, it can be less than, equal, or greater than the limit.
@@ -191,94 +229,101 @@ module ActiveRecord
191
229
  #
192
230
  # Person.in_batches.each_record(&:party_all_night!)
193
231
  #
194
- # NOTE: It's not possible to set the order. That is automatically set to
195
- # ascending on the primary key ("id ASC") to make the batch ordering
196
- # consistent. Therefore the primary key must be orderable, e.g. an integer
197
- # or a string.
232
+ # NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
233
+ # ascending on the primary key ("id ASC").
234
+ # This also means that this method only works when the primary key is
235
+ # orderable (e.g. an integer or string).
198
236
  #
199
237
  # NOTE: By its nature, batch processing is subject to race conditions if
200
238
  # other processes are modifying the database.
201
- def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
202
- relation = self
203
- unless block_given?
204
- return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
239
+ def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, order: DEFAULT_ORDER, use_ranges: nil, &block)
240
+ unless Array(order).all? { |ord| [:asc, :desc].include?(ord) }
241
+ raise ArgumentError, ":order must be :asc or :desc or an array consisting of :asc or :desc, got #{order.inspect}"
205
242
  end
206
243
 
207
244
  if arel.orders.present?
208
245
  act_on_ignored_order(error_on_ignore)
209
246
  end
210
247
 
248
+ unless block
249
+ return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self, order: order, use_ranges: use_ranges)
250
+ end
251
+
211
252
  batch_limit = of
253
+
212
254
  if limit_value
213
255
  remaining = limit_value
214
256
  batch_limit = remaining if remaining < batch_limit
215
257
  end
216
258
 
217
- relation = relation.reorder(batch_order).limit(batch_limit)
218
- relation = apply_limits(relation, start, finish)
219
- relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
220
- batch_relation = relation
221
-
222
- loop do
223
- if load
224
- records = batch_relation.records
225
- ids = records.map(&:id)
226
- yielded_relation = where(primary_key => ids)
227
- yielded_relation.load_records(records)
228
- else
229
- ids = batch_relation.pluck(primary_key)
230
- yielded_relation = where(primary_key => ids)
231
- end
232
-
233
- break if ids.empty?
234
-
235
- primary_key_offset = ids.last
236
- raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset
237
-
238
- yield yielded_relation
239
-
240
- break if ids.length < batch_limit
241
-
242
- if limit_value
243
- remaining -= ids.length
244
-
245
- if remaining == 0
246
- # Saves a useless iteration when the limit is a multiple of the
247
- # batch size.
248
- break
249
- elsif remaining < batch_limit
250
- relation = relation.limit(remaining)
251
- end
252
- end
253
-
254
- batch_relation = relation.where(
255
- bind_attribute(primary_key, primary_key_offset) { |attr, bind| attr.gt(bind) }
259
+ if self.loaded?
260
+ batch_on_loaded_relation(
261
+ relation: self,
262
+ start: start,
263
+ finish: finish,
264
+ order: order,
265
+ batch_limit: batch_limit,
266
+ &block
267
+ )
268
+ else
269
+ batch_on_unloaded_relation(
270
+ relation: self,
271
+ start: start,
272
+ finish: finish,
273
+ load: load,
274
+ order: order,
275
+ use_ranges: use_ranges,
276
+ remaining: remaining,
277
+ batch_limit: batch_limit,
278
+ &block
256
279
  )
257
280
  end
258
281
  end
259
282
 
260
283
  private
261
-
262
- def apply_limits(relation, start, finish)
263
- relation = apply_start_limit(relation, start) if start
264
- relation = apply_finish_limit(relation, finish) if finish
284
+ def apply_limits(relation, start, finish, batch_orders)
285
+ relation = apply_start_limit(relation, start, batch_orders) if start
286
+ relation = apply_finish_limit(relation, finish, batch_orders) if finish
265
287
  relation
266
288
  end
267
289
 
268
- def apply_start_limit(relation, start)
269
- relation.where(bind_attribute(primary_key, start) { |attr, bind| attr.gteq(bind) })
290
+ def apply_start_limit(relation, start, batch_orders)
291
+ operators = batch_orders.map do |_column, order|
292
+ order == :desc ? :lteq : :gteq
293
+ end
294
+ batch_condition(relation, primary_key, start, operators)
270
295
  end
271
296
 
272
- def apply_finish_limit(relation, finish)
273
- relation.where(bind_attribute(primary_key, finish) { |attr, bind| attr.lteq(bind) })
297
+ def apply_finish_limit(relation, finish, batch_orders)
298
+ operators = batch_orders.map do |_column, order|
299
+ order == :desc ? :gteq : :lteq
300
+ end
301
+ batch_condition(relation, primary_key, finish, operators)
274
302
  end
275
303
 
276
- def batch_order
277
- arel_attribute(primary_key).asc
304
+ def batch_condition(relation, columns, values, operators)
305
+ cursor_positions = Array(columns).zip(Array(values), operators)
306
+
307
+ first_clause_column, first_clause_value, operator = cursor_positions.pop
308
+ where_clause = predicate_builder[first_clause_column, first_clause_value, operator]
309
+
310
+ cursor_positions.reverse_each do |column_name, value, operator|
311
+ where_clause = predicate_builder[column_name, value, operator == :lteq ? :lt : :gt].or(
312
+ predicate_builder[column_name, value, :eq].and(where_clause)
313
+ )
314
+ end
315
+
316
+ relation.where(where_clause)
317
+ end
318
+
319
+ def build_batch_orders(order)
320
+ get_the_order_of_primary_key(order).map do |column, ord|
321
+ [column, ord || DEFAULT_ORDER]
322
+ end
278
323
  end
279
324
 
280
325
  def act_on_ignored_order(error_on_ignore)
281
- raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore)
326
+ raise_error = (error_on_ignore.nil? ? ActiveRecord.error_on_ignored_order : error_on_ignore)
282
327
 
283
328
  if raise_error
284
329
  raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
@@ -286,5 +331,101 @@ module ActiveRecord
286
331
  logger.warn(ORDER_IGNORE_MESSAGE)
287
332
  end
288
333
  end
334
+
335
+ def get_the_order_of_primary_key(order)
336
+ Array(primary_key).zip(Array(order))
337
+ end
338
+
339
+ def batch_on_loaded_relation(relation:, start:, finish:, order:, batch_limit:)
340
+ records = relation.to_a
341
+
342
+ if start || finish
343
+ records = records.filter do |record|
344
+ id = record.id
345
+
346
+ if order == :asc
347
+ (start.nil? || id >= start) && (finish.nil? || id <= finish)
348
+ else
349
+ (start.nil? || id <= start) && (finish.nil? || id >= finish)
350
+ end
351
+ end
352
+ end
353
+
354
+ records.sort_by!(&:id)
355
+
356
+ if order == :desc
357
+ records.reverse!
358
+ end
359
+
360
+ records.each_slice(batch_limit) do |subrecords|
361
+ subrelation = relation.spawn
362
+ subrelation.load_records(subrecords)
363
+
364
+ yield subrelation
365
+ end
366
+
367
+ nil
368
+ end
369
+
370
+ def batch_on_unloaded_relation(relation:, start:, finish:, load:, order:, use_ranges:, remaining:, batch_limit:)
371
+ batch_orders = build_batch_orders(order)
372
+ relation = relation.reorder(batch_orders.to_h).limit(batch_limit)
373
+ relation = apply_limits(relation, start, finish, batch_orders)
374
+ relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
375
+ batch_relation = relation
376
+ empty_scope = to_sql == klass.unscoped.all.to_sql
377
+
378
+ loop do
379
+ if load
380
+ records = batch_relation.records
381
+ ids = records.map(&:id)
382
+ yielded_relation = where(primary_key => ids)
383
+ yielded_relation.load_records(records)
384
+ elsif (empty_scope && use_ranges != false) || use_ranges
385
+ ids = batch_relation.ids
386
+ finish = ids.last
387
+ if finish
388
+ yielded_relation = apply_finish_limit(batch_relation, finish, batch_orders)
389
+ yielded_relation = yielded_relation.except(:limit, :order)
390
+ yielded_relation.skip_query_cache!(false)
391
+ end
392
+ else
393
+ ids = batch_relation.ids
394
+ yielded_relation = where(primary_key => ids)
395
+ end
396
+
397
+ break if ids.empty?
398
+
399
+ primary_key_offset = ids.last
400
+ raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset
401
+
402
+ yield yielded_relation
403
+
404
+ break if ids.length < batch_limit
405
+
406
+ if limit_value
407
+ remaining -= ids.length
408
+
409
+ if remaining == 0
410
+ # Saves a useless iteration when the limit is a multiple of the
411
+ # batch size.
412
+ break
413
+ elsif remaining < batch_limit
414
+ relation = relation.limit(remaining)
415
+ end
416
+ end
417
+
418
+ batch_orders_copy = batch_orders.dup
419
+ _last_column, last_order = batch_orders_copy.pop
420
+ operators = batch_orders_copy.map do |_column, order|
421
+ order == :desc ? :lteq : :gteq
422
+ end
423
+ operators << (last_order == :desc ? :lt : :gt)
424
+
425
+ batch_relation = batch_condition(relation, primary_key, primary_key_offset, operators)
426
+ end
427
+
428
+ nil
429
+ end
289
430
  end
290
431
  end