omg-activerecord 8.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (412) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +355 -0
  3. data/MIT-LICENSE +22 -0
  4. data/README.rdoc +219 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record/aggregations.rb +287 -0
  8. data/lib/active_record/association_relation.rb +50 -0
  9. data/lib/active_record/associations/alias_tracker.rb +90 -0
  10. data/lib/active_record/associations/association.rb +417 -0
  11. data/lib/active_record/associations/association_scope.rb +175 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +163 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  14. data/lib/active_record/associations/builder/association.rb +170 -0
  15. data/lib/active_record/associations/builder/belongs_to.rb +160 -0
  16. data/lib/active_record/associations/builder/collection_association.rb +80 -0
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -0
  18. data/lib/active_record/associations/builder/has_many.rb +23 -0
  19. data/lib/active_record/associations/builder/has_one.rb +61 -0
  20. data/lib/active_record/associations/builder/singular_association.rb +48 -0
  21. data/lib/active_record/associations/collection_association.rb +535 -0
  22. data/lib/active_record/associations/collection_proxy.rb +1163 -0
  23. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  24. data/lib/active_record/associations/errors.rb +265 -0
  25. data/lib/active_record/associations/foreign_association.rb +40 -0
  26. data/lib/active_record/associations/has_many_association.rb +167 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +232 -0
  28. data/lib/active_record/associations/has_one_association.rb +142 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +106 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  33. data/lib/active_record/associations/join_dependency.rb +301 -0
  34. data/lib/active_record/associations/nested_error.rb +47 -0
  35. data/lib/active_record/associations/preloader/association.rb +316 -0
  36. data/lib/active_record/associations/preloader/batch.rb +48 -0
  37. data/lib/active_record/associations/preloader/branch.rb +153 -0
  38. data/lib/active_record/associations/preloader/through_association.rb +150 -0
  39. data/lib/active_record/associations/preloader.rb +135 -0
  40. data/lib/active_record/associations/singular_association.rb +76 -0
  41. data/lib/active_record/associations/through_association.rb +132 -0
  42. data/lib/active_record/associations.rb +1897 -0
  43. data/lib/active_record/asynchronous_queries_tracker.rb +64 -0
  44. data/lib/active_record/attribute_assignment.rb +82 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +106 -0
  46. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  47. data/lib/active_record/attribute_methods/dirty.rb +262 -0
  48. data/lib/active_record/attribute_methods/primary_key.rb +158 -0
  49. data/lib/active_record/attribute_methods/query.rb +50 -0
  50. data/lib/active_record/attribute_methods/read.rb +46 -0
  51. data/lib/active_record/attribute_methods/serialization.rb +232 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +94 -0
  53. data/lib/active_record/attribute_methods/write.rb +49 -0
  54. data/lib/active_record/attribute_methods.rb +542 -0
  55. data/lib/active_record/attributes.rb +307 -0
  56. data/lib/active_record/autosave_association.rb +586 -0
  57. data/lib/active_record/base.rb +338 -0
  58. data/lib/active_record/callbacks.rb +452 -0
  59. data/lib/active_record/coders/column_serializer.rb +61 -0
  60. data/lib/active_record/coders/json.rb +15 -0
  61. data/lib/active_record/coders/yaml_column.rb +95 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +290 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +210 -0
  64. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +923 -0
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +31 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +747 -0
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +319 -0
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +239 -0
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +24 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +190 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +961 -0
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +106 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1883 -0
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +676 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +1218 -0
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1016 -0
  78. data/lib/active_record/connection_adapters/column.rb +122 -0
  79. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  80. data/lib/active_record/connection_adapters/mysql/column.rb +28 -0
  81. data/lib/active_record/connection_adapters/mysql/database_statements.rb +95 -0
  82. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +71 -0
  83. data/lib/active_record/connection_adapters/mysql/quoting.rb +114 -0
  84. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +106 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +106 -0
  86. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +97 -0
  87. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +300 -0
  88. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
  89. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +96 -0
  90. data/lib/active_record/connection_adapters/mysql2_adapter.rb +196 -0
  91. data/lib/active_record/connection_adapters/pool_config.rb +83 -0
  92. data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
  93. data/lib/active_record/connection_adapters/postgresql/column.rb +82 -0
  94. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +231 -0
  95. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +91 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +53 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +54 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +31 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +20 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +109 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +42 -0
  112. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  113. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +74 -0
  114. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +124 -0
  115. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  116. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  117. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  118. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +125 -0
  119. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +45 -0
  120. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  121. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  122. data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -0
  123. data/lib/active_record/connection_adapters/postgresql/quoting.rb +238 -0
  124. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +71 -0
  125. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +169 -0
  126. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +392 -0
  127. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +127 -0
  128. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +1162 -0
  129. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +44 -0
  130. data/lib/active_record/connection_adapters/postgresql/utils.rb +79 -0
  131. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1182 -0
  132. data/lib/active_record/connection_adapters/schema_cache.rb +478 -0
  133. data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
  134. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  135. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +145 -0
  136. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  137. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +116 -0
  138. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +37 -0
  139. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +39 -0
  140. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +47 -0
  141. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +221 -0
  142. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +843 -0
  143. data/lib/active_record/connection_adapters/statement_pool.rb +67 -0
  144. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +69 -0
  145. data/lib/active_record/connection_adapters/trilogy_adapter.rb +212 -0
  146. data/lib/active_record/connection_adapters.rb +176 -0
  147. data/lib/active_record/connection_handling.rb +413 -0
  148. data/lib/active_record/core.rb +836 -0
  149. data/lib/active_record/counter_cache.rb +230 -0
  150. data/lib/active_record/database_configurations/connection_url_resolver.rb +105 -0
  151. data/lib/active_record/database_configurations/database_config.rb +104 -0
  152. data/lib/active_record/database_configurations/hash_config.rb +172 -0
  153. data/lib/active_record/database_configurations/url_config.rb +78 -0
  154. data/lib/active_record/database_configurations.rb +309 -0
  155. data/lib/active_record/delegated_type.rb +289 -0
  156. data/lib/active_record/deprecator.rb +7 -0
  157. data/lib/active_record/destroy_association_async_job.rb +38 -0
  158. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  159. data/lib/active_record/dynamic_matchers.rb +121 -0
  160. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  161. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  162. data/lib/active_record/encryption/cipher.rb +53 -0
  163. data/lib/active_record/encryption/config.rb +70 -0
  164. data/lib/active_record/encryption/configurable.rb +60 -0
  165. data/lib/active_record/encryption/context.rb +42 -0
  166. data/lib/active_record/encryption/contexts.rb +76 -0
  167. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  168. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  169. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  170. data/lib/active_record/encryption/encrypted_attribute_type.rb +184 -0
  171. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  172. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  173. data/lib/active_record/encryption/encryptor.rb +177 -0
  174. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  175. data/lib/active_record/encryption/errors.rb +15 -0
  176. data/lib/active_record/encryption/extended_deterministic_queries.rb +159 -0
  177. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  178. data/lib/active_record/encryption/key.rb +28 -0
  179. data/lib/active_record/encryption/key_generator.rb +53 -0
  180. data/lib/active_record/encryption/key_provider.rb +46 -0
  181. data/lib/active_record/encryption/message.rb +33 -0
  182. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  183. data/lib/active_record/encryption/message_serializer.rb +96 -0
  184. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  185. data/lib/active_record/encryption/properties.rb +76 -0
  186. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  187. data/lib/active_record/encryption/scheme.rb +107 -0
  188. data/lib/active_record/encryption.rb +58 -0
  189. data/lib/active_record/enum.rb +424 -0
  190. data/lib/active_record/errors.rb +614 -0
  191. data/lib/active_record/explain.rb +63 -0
  192. data/lib/active_record/explain_registry.rb +37 -0
  193. data/lib/active_record/explain_subscriber.rb +34 -0
  194. data/lib/active_record/fixture_set/file.rb +89 -0
  195. data/lib/active_record/fixture_set/model_metadata.rb +42 -0
  196. data/lib/active_record/fixture_set/render_context.rb +19 -0
  197. data/lib/active_record/fixture_set/table_row.rb +208 -0
  198. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  199. data/lib/active_record/fixtures.rb +850 -0
  200. data/lib/active_record/future_result.rb +182 -0
  201. data/lib/active_record/gem_version.rb +17 -0
  202. data/lib/active_record/inheritance.rb +366 -0
  203. data/lib/active_record/insert_all.rb +328 -0
  204. data/lib/active_record/integration.rb +209 -0
  205. data/lib/active_record/internal_metadata.rb +164 -0
  206. data/lib/active_record/legacy_yaml_adapter.rb +15 -0
  207. data/lib/active_record/locale/en.yml +48 -0
  208. data/lib/active_record/locking/optimistic.rb +228 -0
  209. data/lib/active_record/locking/pessimistic.rb +102 -0
  210. data/lib/active_record/log_subscriber.rb +149 -0
  211. data/lib/active_record/marshalling.rb +56 -0
  212. data/lib/active_record/message_pack.rb +124 -0
  213. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  214. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  215. data/lib/active_record/middleware/database_selector.rb +87 -0
  216. data/lib/active_record/middleware/shard_selector.rb +62 -0
  217. data/lib/active_record/migration/command_recorder.rb +406 -0
  218. data/lib/active_record/migration/compatibility.rb +490 -0
  219. data/lib/active_record/migration/default_strategy.rb +22 -0
  220. data/lib/active_record/migration/execution_strategy.rb +19 -0
  221. data/lib/active_record/migration/join_table.rb +16 -0
  222. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  223. data/lib/active_record/migration.rb +1626 -0
  224. data/lib/active_record/model_schema.rb +635 -0
  225. data/lib/active_record/nested_attributes.rb +633 -0
  226. data/lib/active_record/no_touching.rb +65 -0
  227. data/lib/active_record/normalization.rb +163 -0
  228. data/lib/active_record/persistence.rb +968 -0
  229. data/lib/active_record/promise.rb +84 -0
  230. data/lib/active_record/query_cache.rb +56 -0
  231. data/lib/active_record/query_logs.rb +247 -0
  232. data/lib/active_record/query_logs_formatter.rb +30 -0
  233. data/lib/active_record/querying.rb +122 -0
  234. data/lib/active_record/railtie.rb +440 -0
  235. data/lib/active_record/railties/console_sandbox.rb +5 -0
  236. data/lib/active_record/railties/controller_runtime.rb +65 -0
  237. data/lib/active_record/railties/databases.rake +641 -0
  238. data/lib/active_record/railties/job_runtime.rb +23 -0
  239. data/lib/active_record/readonly_attributes.rb +66 -0
  240. data/lib/active_record/reflection.rb +1287 -0
  241. data/lib/active_record/relation/batches/batch_enumerator.rb +115 -0
  242. data/lib/active_record/relation/batches.rb +491 -0
  243. data/lib/active_record/relation/calculations.rb +679 -0
  244. data/lib/active_record/relation/delegation.rb +154 -0
  245. data/lib/active_record/relation/finder_methods.rb +661 -0
  246. data/lib/active_record/relation/from_clause.rb +30 -0
  247. data/lib/active_record/relation/merger.rb +192 -0
  248. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  249. data/lib/active_record/relation/predicate_builder/association_query_value.rb +76 -0
  250. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  251. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +60 -0
  252. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  253. data/lib/active_record/relation/predicate_builder/relation_handler.rb +24 -0
  254. data/lib/active_record/relation/predicate_builder.rb +181 -0
  255. data/lib/active_record/relation/query_attribute.rb +68 -0
  256. data/lib/active_record/relation/query_methods.rb +2235 -0
  257. data/lib/active_record/relation/record_fetch_warning.rb +52 -0
  258. data/lib/active_record/relation/spawn_methods.rb +78 -0
  259. data/lib/active_record/relation/where_clause.rb +218 -0
  260. data/lib/active_record/relation.rb +1495 -0
  261. data/lib/active_record/result.rb +249 -0
  262. data/lib/active_record/runtime_registry.rb +82 -0
  263. data/lib/active_record/sanitization.rb +254 -0
  264. data/lib/active_record/schema.rb +77 -0
  265. data/lib/active_record/schema_dumper.rb +364 -0
  266. data/lib/active_record/schema_migration.rb +106 -0
  267. data/lib/active_record/scoping/default.rb +205 -0
  268. data/lib/active_record/scoping/named.rb +202 -0
  269. data/lib/active_record/scoping.rb +136 -0
  270. data/lib/active_record/secure_password.rb +60 -0
  271. data/lib/active_record/secure_token.rb +66 -0
  272. data/lib/active_record/serialization.rb +29 -0
  273. data/lib/active_record/signed_id.rb +137 -0
  274. data/lib/active_record/statement_cache.rb +164 -0
  275. data/lib/active_record/store.rb +299 -0
  276. data/lib/active_record/suppressor.rb +59 -0
  277. data/lib/active_record/table_metadata.rb +85 -0
  278. data/lib/active_record/tasks/database_tasks.rb +681 -0
  279. data/lib/active_record/tasks/mysql_database_tasks.rb +120 -0
  280. data/lib/active_record/tasks/postgresql_database_tasks.rb +147 -0
  281. data/lib/active_record/tasks/sqlite_database_tasks.rb +89 -0
  282. data/lib/active_record/test_databases.rb +24 -0
  283. data/lib/active_record/test_fixtures.rb +321 -0
  284. data/lib/active_record/testing/query_assertions.rb +121 -0
  285. data/lib/active_record/timestamp.rb +177 -0
  286. data/lib/active_record/token_for.rb +123 -0
  287. data/lib/active_record/touch_later.rb +70 -0
  288. data/lib/active_record/transaction.rb +132 -0
  289. data/lib/active_record/transactions.rb +523 -0
  290. data/lib/active_record/translation.rb +22 -0
  291. data/lib/active_record/type/adapter_specific_registry.rb +144 -0
  292. data/lib/active_record/type/date.rb +9 -0
  293. data/lib/active_record/type/date_time.rb +9 -0
  294. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  295. data/lib/active_record/type/hash_lookup_type_map.rb +57 -0
  296. data/lib/active_record/type/internal/timezone.rb +22 -0
  297. data/lib/active_record/type/json.rb +30 -0
  298. data/lib/active_record/type/serialized.rb +76 -0
  299. data/lib/active_record/type/text.rb +11 -0
  300. data/lib/active_record/type/time.rb +35 -0
  301. data/lib/active_record/type/type_map.rb +58 -0
  302. data/lib/active_record/type/unsigned_integer.rb +16 -0
  303. data/lib/active_record/type.rb +83 -0
  304. data/lib/active_record/type_caster/connection.rb +33 -0
  305. data/lib/active_record/type_caster/map.rb +23 -0
  306. data/lib/active_record/type_caster.rb +9 -0
  307. data/lib/active_record/validations/absence.rb +25 -0
  308. data/lib/active_record/validations/associated.rb +65 -0
  309. data/lib/active_record/validations/length.rb +26 -0
  310. data/lib/active_record/validations/numericality.rb +36 -0
  311. data/lib/active_record/validations/presence.rb +45 -0
  312. data/lib/active_record/validations/uniqueness.rb +295 -0
  313. data/lib/active_record/validations.rb +101 -0
  314. data/lib/active_record/version.rb +10 -0
  315. data/lib/active_record.rb +616 -0
  316. data/lib/arel/alias_predication.rb +9 -0
  317. data/lib/arel/attributes/attribute.rb +33 -0
  318. data/lib/arel/collectors/bind.rb +31 -0
  319. data/lib/arel/collectors/composite.rb +46 -0
  320. data/lib/arel/collectors/plain_string.rb +20 -0
  321. data/lib/arel/collectors/sql_string.rb +27 -0
  322. data/lib/arel/collectors/substitute_binds.rb +35 -0
  323. data/lib/arel/crud.rb +48 -0
  324. data/lib/arel/delete_manager.rb +32 -0
  325. data/lib/arel/errors.rb +19 -0
  326. data/lib/arel/expressions.rb +29 -0
  327. data/lib/arel/factory_methods.rb +53 -0
  328. data/lib/arel/filter_predications.rb +9 -0
  329. data/lib/arel/insert_manager.rb +48 -0
  330. data/lib/arel/math.rb +45 -0
  331. data/lib/arel/nodes/ascending.rb +23 -0
  332. data/lib/arel/nodes/binary.rb +125 -0
  333. data/lib/arel/nodes/bind_param.rb +44 -0
  334. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  335. data/lib/arel/nodes/case.rb +55 -0
  336. data/lib/arel/nodes/casted.rb +62 -0
  337. data/lib/arel/nodes/comment.rb +29 -0
  338. data/lib/arel/nodes/count.rb +12 -0
  339. data/lib/arel/nodes/cte.rb +36 -0
  340. data/lib/arel/nodes/delete_statement.rb +44 -0
  341. data/lib/arel/nodes/descending.rb +23 -0
  342. data/lib/arel/nodes/equality.rb +15 -0
  343. data/lib/arel/nodes/extract.rb +24 -0
  344. data/lib/arel/nodes/false.rb +16 -0
  345. data/lib/arel/nodes/filter.rb +10 -0
  346. data/lib/arel/nodes/fragments.rb +35 -0
  347. data/lib/arel/nodes/full_outer_join.rb +8 -0
  348. data/lib/arel/nodes/function.rb +45 -0
  349. data/lib/arel/nodes/grouping.rb +11 -0
  350. data/lib/arel/nodes/homogeneous_in.rb +68 -0
  351. data/lib/arel/nodes/in.rb +15 -0
  352. data/lib/arel/nodes/infix_operation.rb +92 -0
  353. data/lib/arel/nodes/inner_join.rb +8 -0
  354. data/lib/arel/nodes/insert_statement.rb +37 -0
  355. data/lib/arel/nodes/join_source.rb +20 -0
  356. data/lib/arel/nodes/leading_join.rb +8 -0
  357. data/lib/arel/nodes/matches.rb +18 -0
  358. data/lib/arel/nodes/named_function.rb +23 -0
  359. data/lib/arel/nodes/nary.rb +39 -0
  360. data/lib/arel/nodes/node.rb +161 -0
  361. data/lib/arel/nodes/node_expression.rb +13 -0
  362. data/lib/arel/nodes/ordering.rb +27 -0
  363. data/lib/arel/nodes/outer_join.rb +8 -0
  364. data/lib/arel/nodes/over.rb +15 -0
  365. data/lib/arel/nodes/regexp.rb +16 -0
  366. data/lib/arel/nodes/right_outer_join.rb +8 -0
  367. data/lib/arel/nodes/select_core.rb +67 -0
  368. data/lib/arel/nodes/select_statement.rb +41 -0
  369. data/lib/arel/nodes/sql_literal.rb +32 -0
  370. data/lib/arel/nodes/string_join.rb +11 -0
  371. data/lib/arel/nodes/table_alias.rb +35 -0
  372. data/lib/arel/nodes/terminal.rb +16 -0
  373. data/lib/arel/nodes/true.rb +16 -0
  374. data/lib/arel/nodes/unary.rb +44 -0
  375. data/lib/arel/nodes/unary_operation.rb +20 -0
  376. data/lib/arel/nodes/unqualified_column.rb +22 -0
  377. data/lib/arel/nodes/update_statement.rb +46 -0
  378. data/lib/arel/nodes/values_list.rb +9 -0
  379. data/lib/arel/nodes/window.rb +126 -0
  380. data/lib/arel/nodes/with.rb +11 -0
  381. data/lib/arel/nodes.rb +75 -0
  382. data/lib/arel/order_predications.rb +13 -0
  383. data/lib/arel/predications.rb +260 -0
  384. data/lib/arel/select_manager.rb +276 -0
  385. data/lib/arel/table.rb +121 -0
  386. data/lib/arel/tree_manager.rb +65 -0
  387. data/lib/arel/update_manager.rb +49 -0
  388. data/lib/arel/visitors/dot.rb +299 -0
  389. data/lib/arel/visitors/mysql.rb +111 -0
  390. data/lib/arel/visitors/postgresql.rb +99 -0
  391. data/lib/arel/visitors/sqlite.rb +38 -0
  392. data/lib/arel/visitors/to_sql.rb +1033 -0
  393. data/lib/arel/visitors/visitor.rb +45 -0
  394. data/lib/arel/visitors.rb +13 -0
  395. data/lib/arel/window_predications.rb +9 -0
  396. data/lib/arel.rb +73 -0
  397. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  398. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
  399. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  400. data/lib/rails/generators/active_record/migration/migration_generator.rb +76 -0
  401. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +29 -0
  402. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +48 -0
  403. data/lib/rails/generators/active_record/migration.rb +54 -0
  404. data/lib/rails/generators/active_record/model/USAGE +113 -0
  405. data/lib/rails/generators/active_record/model/model_generator.rb +94 -0
  406. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  407. data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
  408. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  409. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  410. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  411. data/lib/rails/generators/active_record.rb +19 -0
  412. metadata +505 -0
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Batches
5
+ class BatchEnumerator
6
+ include Enumerable
7
+
8
+ def initialize(of: 1000, start: nil, finish: nil, relation:, cursor:, order: :asc, use_ranges: nil) # :nodoc:
9
+ @of = of
10
+ @relation = relation
11
+ @start = start
12
+ @finish = finish
13
+ @cursor = cursor
14
+ @order = order
15
+ @use_ranges = use_ranges
16
+ end
17
+
18
+ # The primary key value from which the BatchEnumerator starts, inclusive of the value.
19
+ attr_reader :start
20
+
21
+ # The primary key value at which the BatchEnumerator ends, inclusive of the value.
22
+ attr_reader :finish
23
+
24
+ # The relation from which the BatchEnumerator yields batches.
25
+ attr_reader :relation
26
+
27
+ # The size of the batches yielded by the BatchEnumerator.
28
+ def batch_size
29
+ @of
30
+ end
31
+
32
+ # Looping through a collection of records from the database (using the
33
+ # +all+ method, for example) is very inefficient since it will try to
34
+ # instantiate all the objects at once.
35
+ #
36
+ # In that case, batch processing methods allow you to work with the
37
+ # records in batches, thereby greatly reducing memory consumption.
38
+ #
39
+ # Person.in_batches.each_record do |person|
40
+ # person.do_awesome_stuff
41
+ # end
42
+ #
43
+ # Person.where("age > 21").in_batches(of: 10).each_record do |person|
44
+ # person.party_all_night!
45
+ # end
46
+ #
47
+ # If you do not provide a block to #each_record, it will return an Enumerator
48
+ # for chaining with other methods:
49
+ #
50
+ # Person.in_batches.each_record.with_index do |person, index|
51
+ # person.award_trophy(index + 1)
52
+ # end
53
+ def each_record(&block)
54
+ return to_enum(:each_record) unless block_given?
55
+
56
+ @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true, cursor: @cursor, order: @order).each do |relation|
57
+ relation.records.each(&block)
58
+ end
59
+ end
60
+
61
+ # Deletes records in batches. Returns the total number of rows affected.
62
+ #
63
+ # Person.in_batches.delete_all
64
+ #
65
+ # See Relation#delete_all for details of how each batch is deleted.
66
+ def delete_all
67
+ sum(&:delete_all)
68
+ end
69
+
70
+ # Updates records in batches. Returns the total number of rows affected.
71
+ #
72
+ # Person.in_batches.update_all("age = age + 1")
73
+ #
74
+ # See Relation#update_all for details of how each batch is updated.
75
+ def update_all(updates)
76
+ sum do |relation|
77
+ relation.update_all(updates)
78
+ end
79
+ end
80
+
81
+ # Touches records in batches. Returns the total number of rows affected.
82
+ #
83
+ # Person.in_batches.touch_all
84
+ #
85
+ # See Relation#touch_all for details of how each batch is touched.
86
+ def touch_all(...)
87
+ sum do |relation|
88
+ relation.touch_all(...)
89
+ end
90
+ end
91
+
92
+ # Destroys records in batches. Returns the total number of rows affected.
93
+ #
94
+ # Person.where("age < 10").in_batches.destroy_all
95
+ #
96
+ # See Relation#destroy_all for details of how each batch is destroyed.
97
+ def destroy_all
98
+ sum do |relation|
99
+ relation.destroy_all.count(&:destroyed?)
100
+ end
101
+ end
102
+
103
+ # Yields an ActiveRecord::Relation object for each batch of records.
104
+ #
105
+ # Person.in_batches.each do |relation|
106
+ # relation.update_all(awesome: true)
107
+ # end
108
+ def each(&block)
109
+ enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false, cursor: @cursor, order: @order, use_ranges: @use_ranges)
110
+ return enum.each(&block) if block_given?
111
+ enum
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,491 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/relation/batches/batch_enumerator"
4
+
5
+ module ActiveRecord
6
+ # = Active Record \Batches
7
+ module Batches
8
+ ORDER_IGNORE_MESSAGE = "Scoped order is ignored, use :cursor with :order to configure custom order."
9
+ DEFAULT_ORDER = :asc
10
+
11
+ # Looping through a collection of records from the database
12
+ # (using the Scoping::Named::ClassMethods.all method, for example)
13
+ # is very inefficient since it will try to instantiate all the objects at once.
14
+ #
15
+ # In that case, batch processing methods allow you to work
16
+ # with the records in batches, thereby greatly reducing memory consumption.
17
+ #
18
+ # The #find_each method uses #find_in_batches with a batch size of 1000 (or as
19
+ # specified by the +:batch_size+ option).
20
+ #
21
+ # Person.find_each do |person|
22
+ # person.do_awesome_stuff
23
+ # end
24
+ #
25
+ # Person.where("age > 21").find_each do |person|
26
+ # person.party_all_night!
27
+ # end
28
+ #
29
+ # If you do not provide a block to #find_each, it will return an Enumerator
30
+ # for chaining with other methods:
31
+ #
32
+ # Person.find_each.with_index do |person, index|
33
+ # person.award_trophy(index + 1)
34
+ # end
35
+ #
36
+ # ==== Options
37
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
38
+ # * <tt>:start</tt> - Specifies the cursor column value to start from, inclusive of the value.
39
+ # * <tt>:finish</tt> - Specifies the cursor column value to end at, inclusive of the value.
40
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
41
+ # an order is present in the relation.
42
+ # * <tt>:cursor</tt> - Specifies the column to use for batching (can be a column name or an array
43
+ # of column names). Defaults to primary key.
44
+ # * <tt>:order</tt> - Specifies the cursor column order (can be +:asc+ or +:desc+ or an array consisting
45
+ # of :asc or :desc). Defaults to +:asc+.
46
+ #
47
+ # class Order < ActiveRecord::Base
48
+ # self.primary_key = [:id_1, :id_2]
49
+ # end
50
+ #
51
+ # Order.find_each(order: [:asc, :desc])
52
+ #
53
+ # In the above code, +id_1+ is sorted in ascending order and +id_2+ in descending order.
54
+ #
55
+ # Limits are honored, and if present there is no requirement for the batch
56
+ # size: it can be less than, equal to, or greater than the limit.
57
+ #
58
+ # The options +start+ and +finish+ are especially useful if you want
59
+ # multiple workers dealing with the same processing queue. You can make
60
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
61
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
62
+ # option on each worker.
63
+ #
64
+ # # In worker 1, let's process until 9999 records.
65
+ # Person.find_each(finish: 9_999) do |person|
66
+ # person.party_all_night!
67
+ # end
68
+ #
69
+ # # In worker 2, let's process from record 10_000 and onwards.
70
+ # Person.find_each(start: 10_000) do |person|
71
+ # person.party_all_night!
72
+ # end
73
+ #
74
+ # NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
75
+ # ascending on the primary key ("id ASC").
76
+ # This also means that this method only works when the cursor column is
77
+ # orderable (e.g. an integer or string).
78
+ #
79
+ # NOTE: When using custom columns for batching, they should include at least one unique column
80
+ # (e.g. primary key) as a tiebreaker. Also, to reduce the likelihood of race conditions,
81
+ # all columns should be static (unchangeable after it was set).
82
+ #
83
+ # NOTE: By its nature, batch processing is subject to race conditions if
84
+ # other processes are modifying the database.
85
+ def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER, &block)
86
+ if block_given?
87
+ find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, cursor: cursor, order: order) do |records|
88
+ records.each(&block)
89
+ end
90
+ else
91
+ enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, cursor: cursor, order: order) do
92
+ relation = self
93
+ cursor = Array(cursor)
94
+ apply_limits(relation, cursor, start, finish, build_batch_orders(cursor, order)).size
95
+ end
96
+ end
97
+ end
98
+
99
+ # Yields each batch of records that was found by the find options as
100
+ # an array.
101
+ #
102
+ # Person.where("age > 21").find_in_batches do |group|
103
+ # sleep(50) # Make sure it doesn't get too crowded in there!
104
+ # group.each { |person| person.party_all_night! }
105
+ # end
106
+ #
107
+ # If you do not provide a block to #find_in_batches, it will return an Enumerator
108
+ # for chaining with other methods:
109
+ #
110
+ # Person.find_in_batches.with_index do |group, batch|
111
+ # puts "Processing group ##{batch}"
112
+ # group.each(&:recover_from_last_night!)
113
+ # end
114
+ #
115
+ # To be yielded each record one by one, use #find_each instead.
116
+ #
117
+ # ==== Options
118
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
119
+ # * <tt>:start</tt> - Specifies the cursor column value to start from, inclusive of the value.
120
+ # * <tt>:finish</tt> - Specifies the cursor column value to end at, inclusive of the value.
121
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
122
+ # an order is present in the relation.
123
+ # * <tt>:cursor</tt> - Specifies the column to use for batching (can be a column name or an array
124
+ # of column names). Defaults to primary key.
125
+ # * <tt>:order</tt> - Specifies the cursor column order (can be +:asc+ or +:desc+ or an array consisting
126
+ # of :asc or :desc). Defaults to +:asc+.
127
+ #
128
+ # class Order < ActiveRecord::Base
129
+ # self.primary_key = [:id_1, :id_2]
130
+ # end
131
+ #
132
+ # Order.find_in_batches(order: [:asc, :desc])
133
+ #
134
+ # In the above code, +id_1+ is sorted in ascending order and +id_2+ in descending order.
135
+ #
136
+ # Limits are honored, and if present there is no requirement for the batch
137
+ # size: it can be less than, equal to, or greater than the limit.
138
+ #
139
+ # The options +start+ and +finish+ are especially useful if you want
140
+ # multiple workers dealing with the same processing queue. You can make
141
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
142
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
143
+ # option on each worker.
144
+ #
145
+ # # Let's process from record 10_000 on.
146
+ # Person.find_in_batches(start: 10_000) do |group|
147
+ # group.each { |person| person.party_all_night! }
148
+ # end
149
+ #
150
+ # NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
151
+ # ascending on the primary key ("id ASC").
152
+ # This also means that this method only works when the cursor column is
153
+ # orderable (e.g. an integer or string).
154
+ #
155
+ # NOTE: When using custom columns for batching, they should include at least one unique column
156
+ # (e.g. primary key) as a tiebreaker. Also, to reduce the likelihood of race conditions,
157
+ # all columns should be static (unchangeable after it was set).
158
+ #
159
+ # NOTE: By its nature, batch processing is subject to race conditions if
160
+ # other processes are modifying the database.
161
+ def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER)
162
+ relation = self
163
+ unless block_given?
164
+ return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, cursor: cursor, order: order) do
165
+ cursor = Array(cursor)
166
+ total = apply_limits(relation, cursor, start, finish, build_batch_orders(cursor, order)).size
167
+ (total - 1).div(batch_size) + 1
168
+ end
169
+ end
170
+
171
+ in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore, cursor: cursor, order: order) do |batch|
172
+ yield batch.to_a
173
+ end
174
+ end
175
+
176
+ # Yields ActiveRecord::Relation objects to work with a batch of records.
177
+ #
178
+ # Person.where("age > 21").in_batches do |relation|
179
+ # relation.delete_all
180
+ # sleep(10) # Throttle the delete queries
181
+ # end
182
+ #
183
+ # If you do not provide a block to #in_batches, it will return a
184
+ # BatchEnumerator which is enumerable.
185
+ #
186
+ # Person.in_batches.each_with_index do |relation, batch_index|
187
+ # puts "Processing relation ##{batch_index}"
188
+ # relation.delete_all
189
+ # end
190
+ #
191
+ # Examples of calling methods on the returned BatchEnumerator object:
192
+ #
193
+ # Person.in_batches.delete_all
194
+ # Person.in_batches.update_all(awesome: true)
195
+ # Person.in_batches.each_record(&:party_all_night!)
196
+ #
197
+ # ==== Options
198
+ # * <tt>:of</tt> - Specifies the size of the batch. Defaults to 1000.
199
+ # * <tt>:load</tt> - Specifies if the relation should be loaded. Defaults to false.
200
+ # * <tt>:start</tt> - Specifies the cursor column value to start from, inclusive of the value.
201
+ # * <tt>:finish</tt> - Specifies the cursor column value to end at, inclusive of the value.
202
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
203
+ # an order is present in the relation.
204
+ # * <tt>:cursor</tt> - Specifies the column to use for batching (can be a column name or an array
205
+ # of column names). Defaults to primary key.
206
+ # * <tt>:order</tt> - Specifies the cursor column order (can be +:asc+ or +:desc+ or an array consisting
207
+ # of :asc or :desc). Defaults to +:asc+.
208
+ #
209
+ # class Order < ActiveRecord::Base
210
+ # self.primary_key = [:id_1, :id_2]
211
+ # end
212
+ #
213
+ # Order.in_batches(order: [:asc, :desc])
214
+ #
215
+ # In the above code, +id_1+ is sorted in ascending order and +id_2+ in descending order.
216
+ #
217
+ # * <tt>:use_ranges</tt> - Specifies whether to use range iteration (id >= x AND id <= y).
218
+ # It can make iterating over the whole or almost whole tables several times faster.
219
+ # Only whole table iterations use this style of iteration by default. You can disable this behavior by passing +false+.
220
+ # If you iterate over the table and the only condition is, e.g., <tt>archived_at: nil</tt> (and only a tiny fraction
221
+ # of the records are archived), it makes sense to opt in to this approach.
222
+ #
223
+ # Limits are honored, and if present there is no requirement for the batch
224
+ # size, it can be less than, equal, or greater than the limit.
225
+ #
226
+ # The options +start+ and +finish+ are especially useful if you want
227
+ # multiple workers dealing with the same processing queue. You can make
228
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
229
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
230
+ # option on each worker.
231
+ #
232
+ # # Let's process from record 10_000 on.
233
+ # Person.in_batches(start: 10_000).update_all(awesome: true)
234
+ #
235
+ # An example of calling where query method on the relation:
236
+ #
237
+ # Person.in_batches.each do |relation|
238
+ # relation.update_all('age = age + 1')
239
+ # relation.where('age > 21').update_all(should_party: true)
240
+ # relation.where('age <= 21').delete_all
241
+ # end
242
+ #
243
+ # NOTE: If you are going to iterate through each record, you should call
244
+ # #each_record on the yielded BatchEnumerator:
245
+ #
246
+ # Person.in_batches.each_record(&:party_all_night!)
247
+ #
248
+ # NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
249
+ # ascending on the primary key ("id ASC").
250
+ # This also means that this method only works when the cursor column is
251
+ # orderable (e.g. an integer or string).
252
+ #
253
+ # NOTE: When using custom columns for batching, they should include at least one unique column
254
+ # (e.g. primary key) as a tiebreaker. Also, to reduce the likelihood of race conditions,
255
+ # all columns should be static (unchangeable after it was set).
256
+ #
257
+ # NOTE: By its nature, batch processing is subject to race conditions if
258
+ # other processes are modifying the database.
259
+ def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER, use_ranges: nil, &block)
260
+ cursor = Array(cursor).map(&:to_s)
261
+ ensure_valid_options_for_batching!(cursor, start, finish, order)
262
+
263
+ if arel.orders.present?
264
+ act_on_ignored_order(error_on_ignore)
265
+ end
266
+
267
+ unless block
268
+ return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self, cursor: cursor, order: order, use_ranges: use_ranges)
269
+ end
270
+
271
+ batch_limit = of
272
+
273
+ if limit_value
274
+ remaining = limit_value
275
+ batch_limit = remaining if remaining < batch_limit
276
+ end
277
+
278
+ if self.loaded?
279
+ batch_on_loaded_relation(
280
+ relation: self,
281
+ start: start,
282
+ finish: finish,
283
+ cursor: cursor,
284
+ order: order,
285
+ batch_limit: batch_limit,
286
+ &block
287
+ )
288
+ else
289
+ batch_on_unloaded_relation(
290
+ relation: self,
291
+ start: start,
292
+ finish: finish,
293
+ load: load,
294
+ cursor: cursor,
295
+ order: order,
296
+ use_ranges: use_ranges,
297
+ remaining: remaining,
298
+ batch_limit: batch_limit,
299
+ &block
300
+ )
301
+ end
302
+ end
303
+
304
+ private
305
+ def ensure_valid_options_for_batching!(cursor, start, finish, order)
306
+ if start && Array(start).size != cursor.size
307
+ raise ArgumentError, ":start must contain one value per cursor column"
308
+ end
309
+
310
+ if finish && Array(finish).size != cursor.size
311
+ raise ArgumentError, ":finish must contain one value per cursor column"
312
+ end
313
+
314
+ if (Array(primary_key) - cursor).any?
315
+ indexes = model.schema_cache.indexes(table_name)
316
+ unique_index = indexes.find { |index| index.unique && index.where.nil? && (Array(index.columns) - cursor).empty? }
317
+
318
+ unless unique_index
319
+ raise ArgumentError, ":cursor must include a primary key or other unique column(s)"
320
+ end
321
+ end
322
+
323
+ if (Array(order) - [:asc, :desc]).any?
324
+ raise ArgumentError, ":order must be :asc or :desc or an array consisting of :asc or :desc, got #{order.inspect}"
325
+ end
326
+ end
327
+
328
+ def apply_limits(relation, cursor, start, finish, batch_orders)
329
+ relation = apply_start_limit(relation, cursor, start, batch_orders) if start
330
+ relation = apply_finish_limit(relation, cursor, finish, batch_orders) if finish
331
+ relation
332
+ end
333
+
334
+ def apply_start_limit(relation, cursor, start, batch_orders)
335
+ operators = batch_orders.map do |_column, order|
336
+ order == :desc ? :lteq : :gteq
337
+ end
338
+ batch_condition(relation, cursor, start, operators)
339
+ end
340
+
341
+ def apply_finish_limit(relation, cursor, finish, batch_orders)
342
+ operators = batch_orders.map do |_column, order|
343
+ order == :desc ? :gteq : :lteq
344
+ end
345
+ batch_condition(relation, cursor, finish, operators)
346
+ end
347
+
348
+ def batch_condition(relation, cursor, values, operators)
349
+ cursor_positions = cursor.zip(Array(values), operators)
350
+
351
+ first_clause_column, first_clause_value, operator = cursor_positions.pop
352
+ where_clause = predicate_builder[first_clause_column, first_clause_value, operator]
353
+
354
+ cursor_positions.reverse_each do |column_name, value, operator|
355
+ where_clause = predicate_builder[column_name, value, operator == :lteq ? :lt : :gt].or(
356
+ predicate_builder[column_name, value, :eq].and(where_clause)
357
+ )
358
+ end
359
+
360
+ relation.where(where_clause)
361
+ end
362
+
363
+ def build_batch_orders(cursor, order)
364
+ cursor.zip(Array(order)).map do |column, order_|
365
+ [column, order_ || DEFAULT_ORDER]
366
+ end
367
+ end
368
+
369
+ def act_on_ignored_order(error_on_ignore)
370
+ raise_error = (error_on_ignore.nil? ? ActiveRecord.error_on_ignored_order : error_on_ignore)
371
+
372
+ if raise_error
373
+ raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
374
+ elsif model.logger
375
+ model.logger.warn(ORDER_IGNORE_MESSAGE)
376
+ end
377
+ end
378
+
379
+ def batch_on_loaded_relation(relation:, start:, finish:, cursor:, order:, batch_limit:)
380
+ records = relation.to_a
381
+ order = build_batch_orders(cursor, order).map(&:second)
382
+
383
+ if start || finish
384
+ records = records.filter do |record|
385
+ values = record_cursor_values(record, cursor)
386
+
387
+ (start.nil? || compare_values_for_order(values, Array(start), order) >= 0) &&
388
+ (finish.nil? || compare_values_for_order(values, Array(finish), order) <= 0)
389
+ end
390
+ end
391
+
392
+ records.sort! do |record1, record2|
393
+ values1 = record_cursor_values(record1, cursor)
394
+ values2 = record_cursor_values(record2, cursor)
395
+ compare_values_for_order(values1, values2, order)
396
+ end
397
+
398
+ records.each_slice(batch_limit) do |subrecords|
399
+ subrelation = relation.spawn
400
+ subrelation.load_records(subrecords)
401
+
402
+ yield subrelation
403
+ end
404
+
405
+ nil
406
+ end
407
+
408
+ def record_cursor_values(record, cursor)
409
+ record.attributes.slice(*cursor).values
410
+ end
411
+
412
+ # This is a custom implementation of `<=>` operator,
413
+ # which also takes into account how the collection will be ordered.
414
+ def compare_values_for_order(values1, values2, order)
415
+ values1.each_with_index do |element1, index|
416
+ element2 = values2[index]
417
+ direction = order[index]
418
+ comparison = element1 <=> element2
419
+ comparison = -comparison if direction == :desc
420
+ return comparison if comparison != 0
421
+ end
422
+
423
+ 0
424
+ end
425
+
426
+ def batch_on_unloaded_relation(relation:, start:, finish:, load:, cursor:, order:, use_ranges:, remaining:, batch_limit:)
427
+ batch_orders = build_batch_orders(cursor, order)
428
+ relation = relation.reorder(batch_orders.to_h).limit(batch_limit)
429
+ relation = apply_limits(relation, cursor, start, finish, batch_orders)
430
+ relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
431
+ batch_relation = relation
432
+ empty_scope = to_sql == model.unscoped.all.to_sql
433
+
434
+ loop do
435
+ if load
436
+ records = batch_relation.records
437
+ values = records.pluck(*cursor)
438
+ yielded_relation = where(cursor => values)
439
+ yielded_relation.load_records(records)
440
+ elsif (empty_scope && use_ranges != false) || use_ranges
441
+ values = batch_relation.pluck(*cursor)
442
+
443
+ finish = values.last
444
+ if finish
445
+ yielded_relation = apply_finish_limit(batch_relation, cursor, finish, batch_orders)
446
+ yielded_relation = yielded_relation.except(:limit, :order)
447
+ yielded_relation.skip_query_cache!(false)
448
+ end
449
+ else
450
+ values = batch_relation.pluck(*cursor)
451
+ yielded_relation = where(cursor => values)
452
+ end
453
+
454
+ break if values.empty?
455
+
456
+ if values.flatten.any?(nil)
457
+ raise ArgumentError, "Not all of the batch cursor columns were included in the custom select clause "\
458
+ "or some columns contain nil."
459
+ end
460
+
461
+ yield yielded_relation
462
+
463
+ break if values.length < batch_limit
464
+
465
+ if limit_value
466
+ remaining -= values.length
467
+
468
+ if remaining == 0
469
+ # Saves a useless iteration when the limit is a multiple of the
470
+ # batch size.
471
+ break
472
+ elsif remaining < batch_limit
473
+ relation = relation.limit(remaining)
474
+ end
475
+ end
476
+
477
+ batch_orders_copy = batch_orders.dup
478
+ _last_column, last_order = batch_orders_copy.pop
479
+ operators = batch_orders_copy.map do |_column, order|
480
+ order == :desc ? :lteq : :gteq
481
+ end
482
+ operators << (last_order == :desc ? :lt : :gt)
483
+
484
+ cursor_value = values.last
485
+ batch_relation = batch_condition(relation, cursor, cursor_value, operators)
486
+ end
487
+
488
+ nil
489
+ end
490
+ end
491
+ end