activerecord 6.1.7 → 7.2.2

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 (333) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +616 -1290
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +31 -31
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +17 -14
  7. data/lib/active_record/association_relation.rb +2 -12
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +60 -21
  10. data/lib/active_record/associations/association_scope.rb +17 -12
  11. data/lib/active_record/associations/belongs_to_association.rb +37 -11
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -4
  13. data/lib/active_record/associations/builder/association.rb +11 -5
  14. data/lib/active_record/associations/builder/belongs_to.rb +41 -14
  15. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  17. data/lib/active_record/associations/builder/has_many.rb +4 -4
  18. data/lib/active_record/associations/builder/has_one.rb +4 -4
  19. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  20. data/lib/active_record/associations/collection_association.rb +46 -36
  21. data/lib/active_record/associations/collection_proxy.rb +44 -16
  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 +10 -3
  25. data/lib/active_record/associations/has_many_association.rb +29 -19
  26. data/lib/active_record/associations/has_many_through_association.rb +19 -8
  27. data/lib/active_record/associations/has_one_association.rb +20 -10
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  30. data/lib/active_record/associations/join_dependency.rb +28 -20
  31. data/lib/active_record/associations/nested_error.rb +47 -0
  32. data/lib/active_record/associations/preloader/association.rb +212 -53
  33. data/lib/active_record/associations/preloader/batch.rb +48 -0
  34. data/lib/active_record/associations/preloader/branch.rb +153 -0
  35. data/lib/active_record/associations/preloader/through_association.rb +50 -16
  36. data/lib/active_record/associations/preloader.rb +50 -121
  37. data/lib/active_record/associations/singular_association.rb +15 -3
  38. data/lib/active_record/associations/through_association.rb +25 -14
  39. data/lib/active_record/associations.rb +429 -522
  40. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  41. data/lib/active_record/attribute_assignment.rb +1 -5
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  43. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  44. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  45. data/lib/active_record/attribute_methods/primary_key.rb +47 -27
  46. data/lib/active_record/attribute_methods/query.rb +31 -19
  47. data/lib/active_record/attribute_methods/read.rb +14 -11
  48. data/lib/active_record/attribute_methods/serialization.rb +174 -37
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +15 -9
  50. data/lib/active_record/attribute_methods/write.rb +12 -15
  51. data/lib/active_record/attribute_methods.rb +164 -52
  52. data/lib/active_record/attributes.rb +57 -54
  53. data/lib/active_record/autosave_association.rb +74 -57
  54. data/lib/active_record/base.rb +27 -5
  55. data/lib/active_record/callbacks.rb +19 -35
  56. data/lib/active_record/coders/column_serializer.rb +61 -0
  57. data/lib/active_record/coders/json.rb +1 -1
  58. data/lib/active_record/coders/yaml_column.rb +70 -46
  59. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +325 -604
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -60
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +230 -64
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -131
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +378 -143
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +361 -76
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +624 -163
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +348 -165
  75. data/lib/active_record/connection_adapters/column.rb +13 -0
  76. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  77. data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -130
  78. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -55
  79. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  81. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  82. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  83. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  84. data/lib/active_record/connection_adapters/mysql2_adapter.rb +107 -68
  85. data/lib/active_record/connection_adapters/pool_config.rb +26 -16
  86. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  87. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  88. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +114 -54
  89. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  94. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  97. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  100. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  102. data/lib/active_record/connection_adapters/postgresql/quoting.rb +137 -104
  103. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  104. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  105. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +173 -3
  106. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  107. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +403 -77
  108. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  109. data/lib/active_record/connection_adapters/postgresql_adapter.rb +520 -253
  110. data/lib/active_record/connection_adapters/schema_cache.rb +326 -102
  111. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  112. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +78 -55
  113. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +68 -54
  114. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  115. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  116. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  117. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +66 -22
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +372 -130
  119. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  120. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  121. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  122. data/lib/active_record/connection_adapters.rb +130 -6
  123. data/lib/active_record/connection_handling.rb +132 -146
  124. data/lib/active_record/core.rb +310 -253
  125. data/lib/active_record/counter_cache.rb +68 -34
  126. data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -4
  127. data/lib/active_record/database_configurations/database_config.rb +34 -10
  128. data/lib/active_record/database_configurations/hash_config.rb +107 -31
  129. data/lib/active_record/database_configurations/url_config.rb +38 -13
  130. data/lib/active_record/database_configurations.rb +96 -60
  131. data/lib/active_record/delegated_type.rb +90 -20
  132. data/lib/active_record/deprecator.rb +7 -0
  133. data/lib/active_record/destroy_association_async_job.rb +4 -2
  134. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  135. data/lib/active_record/dynamic_matchers.rb +3 -3
  136. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  137. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  138. data/lib/active_record/encryption/cipher.rb +53 -0
  139. data/lib/active_record/encryption/config.rb +68 -0
  140. data/lib/active_record/encryption/configurable.rb +60 -0
  141. data/lib/active_record/encryption/context.rb +42 -0
  142. data/lib/active_record/encryption/contexts.rb +76 -0
  143. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  144. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  145. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  146. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  147. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  148. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  149. data/lib/active_record/encryption/encryptor.rb +170 -0
  150. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  151. data/lib/active_record/encryption/errors.rb +15 -0
  152. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  153. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  154. data/lib/active_record/encryption/key.rb +28 -0
  155. data/lib/active_record/encryption/key_generator.rb +53 -0
  156. data/lib/active_record/encryption/key_provider.rb +46 -0
  157. data/lib/active_record/encryption/message.rb +33 -0
  158. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  159. data/lib/active_record/encryption/message_serializer.rb +96 -0
  160. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  161. data/lib/active_record/encryption/properties.rb +76 -0
  162. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  163. data/lib/active_record/encryption/scheme.rb +100 -0
  164. data/lib/active_record/encryption.rb +58 -0
  165. data/lib/active_record/enum.rb +170 -62
  166. data/lib/active_record/errors.rb +210 -27
  167. data/lib/active_record/explain.rb +21 -12
  168. data/lib/active_record/explain_registry.rb +11 -6
  169. data/lib/active_record/explain_subscriber.rb +1 -1
  170. data/lib/active_record/fixture_set/file.rb +15 -1
  171. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  172. data/lib/active_record/fixture_set/render_context.rb +2 -0
  173. data/lib/active_record/fixture_set/table_row.rb +70 -14
  174. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  175. data/lib/active_record/fixtures.rb +179 -112
  176. data/lib/active_record/future_result.rb +178 -0
  177. data/lib/active_record/gem_version.rb +4 -4
  178. data/lib/active_record/inheritance.rb +85 -31
  179. data/lib/active_record/insert_all.rb +148 -32
  180. data/lib/active_record/integration.rb +14 -10
  181. data/lib/active_record/internal_metadata.rb +123 -23
  182. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  183. data/lib/active_record/locking/optimistic.rb +43 -27
  184. data/lib/active_record/locking/pessimistic.rb +15 -6
  185. data/lib/active_record/log_subscriber.rb +41 -29
  186. data/lib/active_record/marshalling.rb +59 -0
  187. data/lib/active_record/message_pack.rb +124 -0
  188. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  189. data/lib/active_record/middleware/database_selector.rb +23 -13
  190. data/lib/active_record/middleware/shard_selector.rb +62 -0
  191. data/lib/active_record/migration/command_recorder.rb +113 -16
  192. data/lib/active_record/migration/compatibility.rb +235 -46
  193. data/lib/active_record/migration/default_strategy.rb +22 -0
  194. data/lib/active_record/migration/execution_strategy.rb +19 -0
  195. data/lib/active_record/migration/join_table.rb +1 -1
  196. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  197. data/lib/active_record/migration.rb +374 -177
  198. data/lib/active_record/model_schema.rb +145 -158
  199. data/lib/active_record/nested_attributes.rb +61 -23
  200. data/lib/active_record/no_touching.rb +3 -3
  201. data/lib/active_record/normalization.rb +163 -0
  202. data/lib/active_record/persistence.rb +282 -283
  203. data/lib/active_record/promise.rb +84 -0
  204. data/lib/active_record/query_cache.rb +18 -25
  205. data/lib/active_record/query_logs.rb +189 -0
  206. data/lib/active_record/query_logs_formatter.rb +41 -0
  207. data/lib/active_record/querying.rb +44 -9
  208. data/lib/active_record/railtie.rb +229 -71
  209. data/lib/active_record/railties/controller_runtime.rb +25 -11
  210. data/lib/active_record/railties/databases.rake +189 -256
  211. data/lib/active_record/railties/job_runtime.rb +23 -0
  212. data/lib/active_record/readonly_attributes.rb +41 -3
  213. data/lib/active_record/reflection.rb +332 -103
  214. data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
  215. data/lib/active_record/relation/batches.rb +200 -65
  216. data/lib/active_record/relation/calculations.rb +301 -112
  217. data/lib/active_record/relation/delegation.rb +33 -22
  218. data/lib/active_record/relation/finder_methods.rb +123 -52
  219. data/lib/active_record/relation/merger.rb +26 -19
  220. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  221. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  222. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  223. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  224. data/lib/active_record/relation/predicate_builder.rb +29 -22
  225. data/lib/active_record/relation/query_attribute.rb +30 -12
  226. data/lib/active_record/relation/query_methods.rb +870 -163
  227. data/lib/active_record/relation/record_fetch_warning.rb +10 -9
  228. data/lib/active_record/relation/spawn_methods.rb +7 -6
  229. data/lib/active_record/relation/where_clause.rb +15 -36
  230. data/lib/active_record/relation.rb +736 -145
  231. data/lib/active_record/result.rb +67 -54
  232. data/lib/active_record/runtime_registry.rb +71 -13
  233. data/lib/active_record/sanitization.rb +84 -34
  234. data/lib/active_record/schema.rb +39 -23
  235. data/lib/active_record/schema_dumper.rb +90 -31
  236. data/lib/active_record/schema_migration.rb +74 -23
  237. data/lib/active_record/scoping/default.rb +72 -15
  238. data/lib/active_record/scoping/named.rb +6 -13
  239. data/lib/active_record/scoping.rb +65 -34
  240. data/lib/active_record/secure_password.rb +60 -0
  241. data/lib/active_record/secure_token.rb +21 -3
  242. data/lib/active_record/serialization.rb +6 -1
  243. data/lib/active_record/signed_id.rb +30 -9
  244. data/lib/active_record/statement_cache.rb +7 -7
  245. data/lib/active_record/store.rb +10 -10
  246. data/lib/active_record/suppressor.rb +13 -15
  247. data/lib/active_record/table_metadata.rb +7 -3
  248. data/lib/active_record/tasks/database_tasks.rb +288 -149
  249. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  250. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  251. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  252. data/lib/active_record/test_databases.rb +1 -1
  253. data/lib/active_record/test_fixtures.rb +173 -155
  254. data/lib/active_record/testing/query_assertions.rb +121 -0
  255. data/lib/active_record/timestamp.rb +32 -19
  256. data/lib/active_record/token_for.rb +123 -0
  257. data/lib/active_record/touch_later.rb +12 -7
  258. data/lib/active_record/transaction.rb +132 -0
  259. data/lib/active_record/transactions.rb +118 -41
  260. data/lib/active_record/translation.rb +3 -5
  261. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  262. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  263. data/lib/active_record/type/internal/timezone.rb +7 -2
  264. data/lib/active_record/type/serialized.rb +9 -7
  265. data/lib/active_record/type/time.rb +4 -0
  266. data/lib/active_record/type/type_map.rb +17 -20
  267. data/lib/active_record/type.rb +1 -2
  268. data/lib/active_record/type_caster/connection.rb +4 -4
  269. data/lib/active_record/validations/absence.rb +1 -1
  270. data/lib/active_record/validations/associated.rb +13 -7
  271. data/lib/active_record/validations/numericality.rb +5 -4
  272. data/lib/active_record/validations/presence.rb +5 -28
  273. data/lib/active_record/validations/uniqueness.rb +65 -15
  274. data/lib/active_record/validations.rb +12 -5
  275. data/lib/active_record/version.rb +1 -1
  276. data/lib/active_record.rb +444 -32
  277. data/lib/arel/alias_predication.rb +1 -1
  278. data/lib/arel/attributes/attribute.rb +0 -8
  279. data/lib/arel/collectors/bind.rb +2 -0
  280. data/lib/arel/collectors/composite.rb +7 -0
  281. data/lib/arel/collectors/sql_string.rb +1 -1
  282. data/lib/arel/collectors/substitute_binds.rb +1 -1
  283. data/lib/arel/crud.rb +28 -22
  284. data/lib/arel/delete_manager.rb +18 -4
  285. data/lib/arel/errors.rb +10 -0
  286. data/lib/arel/factory_methods.rb +4 -0
  287. data/lib/arel/filter_predications.rb +9 -0
  288. data/lib/arel/insert_manager.rb +2 -3
  289. data/lib/arel/nodes/binary.rb +6 -7
  290. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  291. data/lib/arel/nodes/casted.rb +1 -1
  292. data/lib/arel/nodes/cte.rb +36 -0
  293. data/lib/arel/nodes/delete_statement.rb +12 -13
  294. data/lib/arel/nodes/filter.rb +10 -0
  295. data/lib/arel/nodes/fragments.rb +35 -0
  296. data/lib/arel/nodes/function.rb +1 -0
  297. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  298. data/lib/arel/nodes/insert_statement.rb +2 -2
  299. data/lib/arel/nodes/leading_join.rb +8 -0
  300. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  301. data/lib/arel/nodes/node.rb +115 -5
  302. data/lib/arel/nodes/select_core.rb +2 -2
  303. data/lib/arel/nodes/select_statement.rb +2 -2
  304. data/lib/arel/nodes/sql_literal.rb +13 -0
  305. data/lib/arel/nodes/table_alias.rb +4 -0
  306. data/lib/arel/nodes/update_statement.rb +8 -3
  307. data/lib/arel/nodes.rb +7 -2
  308. data/lib/arel/predications.rb +14 -4
  309. data/lib/arel/select_manager.rb +11 -5
  310. data/lib/arel/table.rb +9 -6
  311. data/lib/arel/tree_manager.rb +8 -15
  312. data/lib/arel/update_manager.rb +20 -5
  313. data/lib/arel/visitors/dot.rb +81 -90
  314. data/lib/arel/visitors/mysql.rb +23 -5
  315. data/lib/arel/visitors/postgresql.rb +1 -22
  316. data/lib/arel/visitors/sqlite.rb +25 -0
  317. data/lib/arel/visitors/to_sql.rb +170 -36
  318. data/lib/arel/visitors/visitor.rb +2 -2
  319. data/lib/arel.rb +23 -4
  320. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  321. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  322. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  323. data/lib/rails/generators/active_record/migration.rb +3 -1
  324. data/lib/rails/generators/active_record/model/USAGE +113 -0
  325. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  326. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  327. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  328. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  329. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  330. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  331. metadata +103 -17
  332. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  333. data/lib/active_record/null_relation.rb +0 -67
data/lib/arel/crud.rb CHANGED
@@ -4,23 +4,6 @@ module Arel # :nodoc: all
4
4
  ###
5
5
  # FIXME hopefully we can remove this
6
6
  module Crud
7
- def compile_update(values, pk)
8
- um = UpdateManager.new
9
-
10
- if Nodes::SqlLiteral === values
11
- relation = @ctx.from
12
- else
13
- relation = values.first.first.relation
14
- end
15
- um.key = pk
16
- um.table relation
17
- um.set values
18
- um.take @ast.limit.expr if @ast.limit
19
- um.order(*@ast.orders)
20
- um.wheres = @ctx.wheres
21
- um
22
- end
23
-
24
7
  def compile_insert(values)
25
8
  im = create_insert
26
9
  im.insert values
@@ -31,11 +14,34 @@ module Arel # :nodoc: all
31
14
  InsertManager.new
32
15
  end
33
16
 
34
- def compile_delete
35
- dm = DeleteManager.new
36
- dm.take @ast.limit.expr if @ast.limit
37
- dm.wheres = @ctx.wheres
38
- dm.from @ctx.froms
17
+ def compile_update(
18
+ values,
19
+ key = nil,
20
+ having_clause = nil,
21
+ group_values_columns = []
22
+ )
23
+ um = UpdateManager.new(source)
24
+ um.set(values)
25
+ um.take(limit)
26
+ um.offset(offset)
27
+ um.order(*orders)
28
+ um.wheres = constraints
29
+ um.key = key
30
+
31
+ um.group(group_values_columns) unless group_values_columns.empty?
32
+ um.having(having_clause) unless having_clause.nil?
33
+ um
34
+ end
35
+
36
+ def compile_delete(key = nil, having_clause = nil, group_values_columns = [])
37
+ dm = DeleteManager.new(source)
38
+ dm.take(limit)
39
+ dm.offset(offset)
40
+ dm.order(*orders)
41
+ dm.wheres = constraints
42
+ dm.key = key
43
+ dm.group(group_values_columns) unless group_values_columns.empty?
44
+ dm.having(having_clause) unless having_clause.nil?
39
45
  dm
40
46
  end
41
47
  end
@@ -4,15 +4,29 @@ module Arel # :nodoc: all
4
4
  class DeleteManager < Arel::TreeManager
5
5
  include TreeManager::StatementMethods
6
6
 
7
- def initialize
8
- super
9
- @ast = Nodes::DeleteStatement.new
10
- @ctx = @ast
7
+ def initialize(table = nil)
8
+ @ast = Nodes::DeleteStatement.new(table)
11
9
  end
12
10
 
13
11
  def from(relation)
14
12
  @ast.relation = relation
15
13
  self
16
14
  end
15
+
16
+ def group(columns)
17
+ columns.each do |column|
18
+ column = Nodes::SqlLiteral.new(column) if String === column
19
+ column = Nodes::SqlLiteral.new(column.to_s) if Symbol === column
20
+
21
+ @ast.groups.push Nodes::Group.new column
22
+ end
23
+
24
+ self
25
+ end
26
+
27
+ def having(expr)
28
+ @ast.havings << expr
29
+ self
30
+ end
17
31
  end
18
32
  end
data/lib/arel/errors.rb CHANGED
@@ -6,4 +6,14 @@ module Arel # :nodoc: all
6
6
 
7
7
  class EmptyJoinError < ArelError
8
8
  end
9
+
10
+ class BindError < ArelError
11
+ def initialize(message, sql = nil)
12
+ if sql
13
+ super("#{message} in: #{sql.inspect}")
14
+ else
15
+ super(message)
16
+ end
17
+ end
18
+ end
9
19
  end
@@ -45,5 +45,9 @@ module Arel # :nodoc: all
45
45
  def coalesce(*exprs)
46
46
  Nodes::NamedFunction.new "COALESCE", exprs
47
47
  end
48
+
49
+ def cast(name, type)
50
+ Nodes::NamedFunction.new "CAST", [name.as(type)]
51
+ end
48
52
  end
49
53
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel # :nodoc: all
4
+ module FilterPredications
5
+ def filter(expr)
6
+ Nodes::Filter.new(self, expr)
7
+ end
8
+ end
9
+ end
@@ -2,9 +2,8 @@
2
2
 
3
3
  module Arel # :nodoc: all
4
4
  class InsertManager < Arel::TreeManager
5
- def initialize
6
- super
7
- @ast = Nodes::InsertStatement.new
5
+ def initialize(table = nil)
6
+ @ast = Nodes::InsertStatement.new(table)
8
7
  end
9
8
 
10
9
  def into(table)
@@ -39,6 +39,12 @@ module Arel # :nodoc: all
39
39
  end
40
40
  end
41
41
 
42
+ class As < Binary
43
+ def to_cte
44
+ Arel::Nodes::Cte.new(left.name, right)
45
+ end
46
+ end
47
+
42
48
  class Between < Binary; include FetchAttribute; end
43
49
 
44
50
  class GreaterThan < Binary
@@ -105,14 +111,7 @@ module Arel # :nodoc: all
105
111
  end
106
112
  end
107
113
 
108
- class Or < Binary
109
- def fetch_attribute(&block)
110
- left.fetch_attribute(&block) && right.fetch_attribute(&block)
111
- end
112
- end
113
-
114
114
  %w{
115
- As
116
115
  Assignment
117
116
  Join
118
117
  Union
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel # :nodoc: all
4
+ module Nodes
5
+ class BoundSqlLiteral < NodeExpression
6
+ attr_reader :sql_with_placeholders, :positional_binds, :named_binds
7
+
8
+ def initialize(sql_with_placeholders, positional_binds, named_binds)
9
+ has_positional = !(positional_binds.nil? || positional_binds.empty?)
10
+ has_named = !(named_binds.nil? || named_binds.empty?)
11
+
12
+ if has_positional
13
+ if has_named
14
+ raise BindError.new("cannot mix positional and named binds", sql_with_placeholders)
15
+ end
16
+ if positional_binds.size != (expected = sql_with_placeholders.count("?"))
17
+ raise BindError.new("wrong number of bind variables (#{positional_binds.size} for #{expected})", sql_with_placeholders)
18
+ end
19
+ elsif has_named
20
+ tokens_in_string = sql_with_placeholders.scan(/:(?<!::)([a-zA-Z]\w*)/).flatten.map(&:to_sym).uniq
21
+ tokens_in_hash = named_binds.keys.map(&:to_sym).uniq
22
+
23
+ if !(missing = (tokens_in_string - tokens_in_hash)).empty?
24
+ if missing.size == 1
25
+ raise BindError.new("missing value for #{missing.first.inspect}", sql_with_placeholders)
26
+ else
27
+ raise BindError.new("missing values for #{missing.inspect}", sql_with_placeholders)
28
+ end
29
+ end
30
+ end
31
+
32
+ @sql_with_placeholders = sql_with_placeholders
33
+ if has_positional
34
+ @positional_binds = positional_binds
35
+ @named_binds = nil
36
+ else
37
+ @positional_binds = nil
38
+ @named_binds = named_binds
39
+ end
40
+ end
41
+
42
+ def hash
43
+ [self.class, sql_with_placeholders, positional_binds, named_binds].hash
44
+ end
45
+
46
+ def eql?(other)
47
+ self.class == other.class &&
48
+ sql_with_placeholders == other.sql_with_placeholders &&
49
+ positional_binds == other.positional_binds &&
50
+ named_binds == other.named_binds
51
+ end
52
+ alias :== :eql?
53
+
54
+ def +(other)
55
+ raise ArgumentError, "Expected Arel node" unless Arel.arel_node?(other)
56
+
57
+ Fragments.new([self, other])
58
+ end
59
+
60
+ def inspect
61
+ "#<#{self.class.name} #{sql_with_placeholders.inspect} #{(named_binds || positional_binds).inspect}>"
62
+ end
63
+ end
64
+ end
65
+ end
@@ -47,7 +47,7 @@ module Arel # :nodoc: all
47
47
 
48
48
  def self.build_quoted(other, attribute = nil)
49
49
  case other
50
- when Arel::Nodes::Node, Arel::Attributes::Attribute, Arel::Table, Arel::Nodes::BindParam, Arel::SelectManager, Arel::Nodes::Quoted, Arel::Nodes::SqlLiteral
50
+ when Arel::Nodes::Node, Arel::Attributes::Attribute, Arel::Table, Arel::SelectManager, Arel::Nodes::SqlLiteral, ActiveModel::Attribute
51
51
  other
52
52
  else
53
53
  case attribute
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel # :nodoc: all
4
+ module Nodes
5
+ class Cte < Arel::Nodes::Binary
6
+ alias :name :left
7
+ alias :relation :right
8
+ attr_reader :materialized
9
+
10
+ def initialize(name, relation, materialized: nil)
11
+ super(name, relation)
12
+ @materialized = materialized
13
+ end
14
+
15
+ def hash
16
+ [name, relation, materialized].hash
17
+ end
18
+
19
+ def eql?(other)
20
+ self.class == other.class &&
21
+ self.name == other.name &&
22
+ self.relation == other.relation &&
23
+ self.materialized == other.materialized
24
+ end
25
+ alias :== :eql?
26
+
27
+ def to_cte
28
+ self
29
+ end
30
+
31
+ def to_table
32
+ Arel::Table.new(name)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -3,17 +3,14 @@
3
3
  module Arel # :nodoc: all
4
4
  module Nodes
5
5
  class DeleteStatement < Arel::Nodes::Node
6
- attr_accessor :left, :right, :orders, :limit, :offset, :key
7
-
8
- alias :relation :left
9
- alias :relation= :left=
10
- alias :wheres :right
11
- alias :wheres= :right=
6
+ attr_accessor :relation, :wheres, :groups, :havings, :orders, :limit, :offset, :key
12
7
 
13
8
  def initialize(relation = nil, wheres = [])
14
9
  super()
15
- @left = relation
16
- @right = wheres
10
+ @relation = relation
11
+ @wheres = wheres
12
+ @groups = []
13
+ @havings = []
17
14
  @orders = []
18
15
  @limit = nil
19
16
  @offset = nil
@@ -22,19 +19,21 @@ module Arel # :nodoc: all
22
19
 
23
20
  def initialize_copy(other)
24
21
  super
25
- @left = @left.clone if @left
26
- @right = @right.clone if @right
22
+ @relation = @relation.clone if @relation
23
+ @wheres = @wheres.clone if @wheres
27
24
  end
28
25
 
29
26
  def hash
30
- [self.class, @left, @right, @orders, @limit, @offset, @key].hash
27
+ [self.class, @relation, @wheres, @orders, @limit, @offset, @key].hash
31
28
  end
32
29
 
33
30
  def eql?(other)
34
31
  self.class == other.class &&
35
- self.left == other.left &&
36
- self.right == other.right &&
32
+ self.relation == other.relation &&
33
+ self.wheres == other.wheres &&
37
34
  self.orders == other.orders &&
35
+ self.groups == other.groups &&
36
+ self.havings == other.havings &&
38
37
  self.limit == other.limit &&
39
38
  self.offset == other.offset &&
40
39
  self.key == other.key
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel # :nodoc: all
4
+ module Nodes
5
+ class Filter < Binary
6
+ include Arel::WindowPredications
7
+ include Arel::AliasPredication
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel # :nodoc: all
4
+ module Nodes
5
+ class Fragments < Arel::Nodes::Node
6
+ attr_reader :values
7
+
8
+ def initialize(values = [])
9
+ super()
10
+ @values = values
11
+ end
12
+
13
+ def initialize_copy(other)
14
+ super
15
+ @values = @values.clone
16
+ end
17
+
18
+ def hash
19
+ [@values].hash
20
+ end
21
+
22
+ def +(other)
23
+ raise ArgumentError, "Expected Arel node" unless Arel.arel_node?(other)
24
+
25
+ self.class.new([*@values, other])
26
+ end
27
+
28
+ def eql?(other)
29
+ self.class == other.class &&
30
+ self.values == other.values
31
+ end
32
+ alias :== :eql?
33
+ end
34
+ end
35
+ end
@@ -4,6 +4,7 @@ module Arel # :nodoc: all
4
4
  module Nodes
5
5
  class Function < Arel::Nodes::NodeExpression
6
6
  include Arel::WindowPredications
7
+ include Arel::FilterPredications
7
8
  attr_accessor :expressions, :alias, :distinct
8
9
 
9
10
  def initialize(expr, aliaz = nil)
@@ -36,14 +36,6 @@ module Arel # :nodoc: all
36
36
  attribute.quoted_array(values)
37
37
  end
38
38
 
39
- def table_name
40
- attribute.relation.table_alias || attribute.relation.name
41
- end
42
-
43
- def column_name
44
- attribute.name
45
- end
46
-
47
39
  def casted_values
48
40
  type = attribute.type_caster
49
41
 
@@ -56,7 +48,7 @@ module Arel # :nodoc: all
56
48
  end
57
49
 
58
50
  def proc_for_binds
59
- -> value { ActiveModel::Attribute.with_cast_value(attribute.name, value, attribute.type_caster) }
51
+ -> value { ActiveModel::Attribute.with_cast_value(attribute.name, value, ActiveModel::Type.default_value) }
60
52
  end
61
53
 
62
54
  def fetch_attribute(&block)
@@ -5,9 +5,9 @@ module Arel # :nodoc: all
5
5
  class InsertStatement < Arel::Nodes::Node
6
6
  attr_accessor :relation, :columns, :values, :select
7
7
 
8
- def initialize
8
+ def initialize(relation = nil)
9
9
  super()
10
- @relation = nil
10
+ @relation = relation
11
11
  @columns = []
12
12
  @values = nil
13
13
  @select = nil
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel # :nodoc: all
4
+ module Nodes
5
+ class LeadingJoin < Arel::Nodes::InnerJoin
6
+ end
7
+ end
8
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Arel # :nodoc: all
4
4
  module Nodes
5
- class And < Arel::Nodes::NodeExpression
5
+ class Nary < Arel::Nodes::NodeExpression
6
6
  attr_reader :children
7
7
 
8
8
  def initialize(children)
@@ -18,8 +18,12 @@ module Arel # :nodoc: all
18
18
  children[1]
19
19
  end
20
20
 
21
+ def fetch_attribute(&block)
22
+ children.any? && children.all? { |child| child.fetch_attribute(&block) }
23
+ end
24
+
21
25
  def hash
22
- children.hash
26
+ [self.class, children].hash
23
27
  end
24
28
 
25
29
  def eql?(other)
@@ -28,5 +32,8 @@ module Arel # :nodoc: all
28
32
  end
29
33
  alias :== :eql?
30
34
  end
35
+
36
+ And = Class.new(Nary)
37
+ Or = Class.new(Nary)
31
38
  end
32
39
  end
@@ -2,8 +2,117 @@
2
2
 
3
3
  module Arel # :nodoc: all
4
4
  module Nodes
5
- ###
6
- # Abstract base class for all AST nodes
5
+ # = Using +Arel::Nodes::Node+
6
+ #
7
+ # Active Record uses Arel to compose SQL statements. Instead of building SQL strings directly, it's building an
8
+ # abstract syntax tree (AST) of the statement using various types of Arel::Nodes::Node. Each node represents a
9
+ # fragment of a SQL statement.
10
+ #
11
+ # The intermediate representation allows Arel to compile the statement into the database's specific SQL dialect
12
+ # only before sending it without having to care about the nuances of each database when building the statement.
13
+ # It also allows easier composition of statements without having to resort to (brittle and unsafe) string manipulation.
14
+ #
15
+ # == Building constraints
16
+ #
17
+ # One of the most common use cases of Arel is generating constraints for +SELECT+ statements. To help with that,
18
+ # most nodes include a couple of useful factory methods to create subtree structures for common constraints. For
19
+ # a full list of those, please refer to Arel::Predications.
20
+ #
21
+ # The following example creates an equality constraint where the value of the name column on the users table
22
+ # matches the value DHH.
23
+ #
24
+ # users = Arel::Table.new(:users)
25
+ # constraint = users[:name].eq("DHH")
26
+ #
27
+ # # => Arel::Nodes::Equality.new(
28
+ # # Arel::Attributes::Attribute.new(users, "name"),
29
+ # # Arel::Nodes::Casted.new(
30
+ # # "DHH",
31
+ # # Arel::Attributes::Attribute.new(users, "name")
32
+ # # )
33
+ # # )
34
+ #
35
+ # The resulting SQL fragment will look like this:
36
+ #
37
+ # "users"."name" = 'DHH'
38
+ #
39
+ # The constraint fragments can be used with regular ActiveRecord::Relation objects instead of a Hash. The
40
+ # following two examples show two ways of creating the same query.
41
+ #
42
+ # User.where(name: 'DHH')
43
+ #
44
+ # # SELECT "users".* FROM "users" WHERE "users"."name" = 'DHH'
45
+ #
46
+ # users = User.arel_table
47
+ #
48
+ # User.where(users[:name].eq('DHH'))
49
+ #
50
+ # # SELECT "users".* FROM "users" WHERE "users"."name" = 'DHH'
51
+ #
52
+ # == Functions
53
+ #
54
+ # Arel comes with built-in support for SQL functions like +COUNT+, +SUM+, +MIN+, +MAX+, and +AVG+. The
55
+ # Arel::Expressions module includes factory methods for the default functions.
56
+ #
57
+ # employees = Employee.arel_table
58
+ #
59
+ # Employee.select(employees[:department_id], employees[:salary].average).group(employees[:department_id])
60
+ #
61
+ # # SELECT "employees"."department_id", AVG("employees"."salary")
62
+ # # FROM "employees" GROUP BY "employees"."department_id"
63
+ #
64
+ # It’s also possible to use custom functions by using the Arel::Nodes::NamedFunction node type. It accepts a
65
+ # function name and an array of parameters.
66
+ #
67
+ # Arel::Nodes::NamedFunction.new('date_trunc', [Arel::Nodes.build_quoted('day'), User.arel_table[:created_at]])
68
+ #
69
+ # # date_trunc('day', "users"."created_at")
70
+ #
71
+ # == Quoting & bind params
72
+ #
73
+ # Values that you pass to Arel nodes need to be quoted or wrapped in bind params. This ensures they are properly
74
+ # converted into the correct format without introducing a possible SQL injection vulnerability. Most factory
75
+ # methods (like +eq+, +gt+, +lteq+, …) quote passed values automatically. When not using a factory method, it’s
76
+ # possible to convert a value and wrap it in an Arel::Nodes::Quoted node (if necessary) by calling +Arel::Nodes.
77
+ # build_quoted+.
78
+ #
79
+ # Arel::Nodes.build_quoted("foo") # 'foo'
80
+ # Arel::Nodes.build_quoted(12.3) # 12.3
81
+ #
82
+ # Instead of quoting values and embedding them directly in the SQL statement, it’s also possible to create bind
83
+ # params. This keeps the actual values outside of the statement and allows using the prepared statement feature
84
+ # of some databases.
85
+ #
86
+ # attribute = ActiveRecord::Relation::QueryAttribute.new(:name, "DHH", ActiveRecord::Type::String.new)
87
+ # Arel::Nodes::BindParam.new(attribute)
88
+ #
89
+ # When ActiveRecord runs the query, bind params are replaced by placeholders (like +$1+) and the values are passed
90
+ # separately.
91
+ #
92
+ # == SQL Literals
93
+ #
94
+ # For cases where there is no way to represent a particular SQL fragment using Arel nodes, you can use an SQL
95
+ # literal. SQL literals are strings that Arel will treat “as is”.
96
+ #
97
+ # Arel.sql('LOWER("users"."name")').eq('dhh')
98
+ #
99
+ # # LOWER("users"."name") = 'dhh'
100
+ #
101
+ # Please keep in mind that passing data as raw SQL literals might introduce a possible SQL injection. However,
102
+ # `Arel.sql` supports binding parameters which will ensure proper quoting. This can be useful when you need to
103
+ # control the exact SQL you run, but you still have potentially user-supplied values.
104
+ #
105
+ # Arel.sql('LOWER("users"."name") = ?', 'dhh')
106
+ #
107
+ # # LOWER("users"."name") = 'dhh'
108
+ #
109
+ # You can also combine SQL literals.
110
+ #
111
+ # sql = Arel.sql('SELECT * FROM "users" WHERE ')
112
+ # sql += Arel.sql('LOWER("users"."name") = :name', name: 'dhh')
113
+ # sql += Arel.sql('AND "users"."age" > :age', age: 35)
114
+ #
115
+ # # SELECT * FROM "users" WHERE LOWER("users"."name") = 'dhh' AND "users"."age" > '35'
7
116
  class Node
8
117
  include Arel::FactoryMethods
9
118
 
@@ -18,7 +127,7 @@ module Arel # :nodoc: all
18
127
  # Factory method to create a Nodes::Grouping node that has an Nodes::Or
19
128
  # node as a child.
20
129
  def or(right)
21
- Nodes::Grouping.new Nodes::Or.new(self, right)
130
+ Nodes::Grouping.new Nodes::Or.new([self, right])
22
131
  end
23
132
 
24
133
  ###
@@ -38,8 +147,9 @@ module Arel # :nodoc: all
38
147
  # Maybe we should just use `Table.engine`? :'(
39
148
  def to_sql(engine = Table.engine)
40
149
  collector = Arel::Collectors::SQLString.new
41
- collector = engine.connection.visitor.accept self, collector
42
- collector.value
150
+ engine.with_connection do |connection|
151
+ connection.visitor.accept(self, collector).value
152
+ end
43
153
  end
44
154
 
45
155
  def fetch_attribute
@@ -6,9 +6,9 @@ module Arel # :nodoc: all
6
6
  attr_accessor :projections, :wheres, :groups, :windows, :comment
7
7
  attr_accessor :havings, :source, :set_quantifier, :optimizer_hints
8
8
 
9
- def initialize
9
+ def initialize(relation = nil)
10
10
  super()
11
- @source = JoinSource.new nil
11
+ @source = JoinSource.new(relation)
12
12
 
13
13
  # https://ronsavage.github.io/SQL/sql-92.bnf.html#set%20quantifier
14
14
  @set_quantifier = nil
@@ -6,9 +6,9 @@ module Arel # :nodoc: all
6
6
  attr_reader :cores
7
7
  attr_accessor :limit, :orders, :lock, :offset, :with
8
8
 
9
- def initialize(cores = [SelectCore.new])
9
+ def initialize(relation = nil)
10
10
  super()
11
- @cores = cores
11
+ @cores = [SelectCore.new(relation)]
12
12
  @orders = []
13
13
  @limit = nil
14
14
  @lock = nil
@@ -8,12 +8,25 @@ module Arel # :nodoc: all
8
8
  include Arel::AliasPredication
9
9
  include Arel::OrderPredications
10
10
 
11
+ attr_reader :retryable
12
+
13
+ def initialize(string, retryable: false)
14
+ @retryable = retryable
15
+ super(string)
16
+ end
17
+
11
18
  def encode_with(coder)
12
19
  coder.scalar = self.to_s
13
20
  end
14
21
 
15
22
  def fetch_attribute
16
23
  end
24
+
25
+ def +(other)
26
+ raise ArgumentError, "Expected Arel node" unless Arel.arel_node?(other)
27
+
28
+ Fragments.new([self, other])
29
+ end
17
30
  end
18
31
  end
19
32
  end
@@ -26,6 +26,10 @@ module Arel # :nodoc: all
26
26
  def able_to_type_cast?
27
27
  relation.respond_to?(:able_to_type_cast?) && relation.able_to_type_cast?
28
28
  end
29
+
30
+ def to_cte
31
+ Arel::Nodes::Cte.new(name, relation)
32
+ end
29
33
  end
30
34
  end
31
35
  end