activerecord 6.1.7 → 7.2.0

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 (332) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +520 -1385
  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 +12 -7
  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 +27 -25
  30. data/lib/active_record/associations/join_dependency.rb +23 -15
  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 +404 -509
  40. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  41. data/lib/active_record/attribute_assignment.rb +2 -14
  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 +11 -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 +51 -49
  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 +18 -34
  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 +327 -612
  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 +201 -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 +377 -142
  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 +345 -166
  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 +401 -77
  108. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  109. data/lib/active_record/connection_adapters/postgresql_adapter.rb +518 -251
  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 +276 -251
  125. data/lib/active_record/counter_cache.rb +68 -34
  126. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -3
  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 +56 -0
  165. data/lib/active_record/enum.rb +163 -63
  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 +56 -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 +143 -159
  199. data/lib/active_record/nested_attributes.rb +48 -21
  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 +19 -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 +234 -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 +325 -103
  214. data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
  215. data/lib/active_record/relation/batches.rb +198 -63
  216. data/lib/active_record/relation/calculations.rb +300 -111
  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 +842 -150
  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 +5 -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 +277 -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 +64 -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/to_sql.rb +170 -36
  317. data/lib/arel/visitors/visitor.rb +2 -2
  318. data/lib/arel.rb +23 -4
  319. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  320. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  321. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  322. data/lib/rails/generators/active_record/migration.rb +3 -1
  323. data/lib/rails/generators/active_record/model/USAGE +113 -0
  324. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  325. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  326. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  327. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  328. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  329. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  330. metadata +100 -14
  331. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  332. data/lib/active_record/null_relation.rb +0 -67
@@ -5,30 +5,49 @@ require "active_support/core_ext/enumerable"
5
5
  module ActiveRecord
6
6
  class InsertAll # :nodoc:
7
7
  attr_reader :model, :connection, :inserts, :keys
8
- attr_reader :on_duplicate, :returning, :unique_by
8
+ attr_reader :on_duplicate, :update_only, :returning, :unique_by, :update_sql
9
9
 
10
- def initialize(model, inserts, on_duplicate:, returning: nil, unique_by: nil)
11
- raise ArgumentError, "Empty list of attributes passed" if inserts.blank?
12
-
13
- @model, @connection, @inserts, @keys = model, model.connection, inserts, inserts.first.keys.map(&:to_s)
14
- @on_duplicate, @returning, @unique_by = on_duplicate, returning, unique_by
10
+ class << self
11
+ def execute(relation, ...)
12
+ relation.model.with_connection do |c|
13
+ new(relation, c, ...).execute
14
+ end
15
+ end
16
+ end
15
17
 
16
- if model.scope_attributes?
17
- @scope_attributes = model.scope_attributes
18
- @keys |= @scope_attributes.keys
18
+ def initialize(relation, connection, inserts, on_duplicate:, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
19
+ @relation = relation
20
+ @model, @connection, @inserts = relation.model, connection, inserts.map(&:stringify_keys)
21
+ @on_duplicate, @update_only, @returning, @unique_by = on_duplicate, update_only, returning, unique_by
22
+ @record_timestamps = record_timestamps.nil? ? model.record_timestamps : record_timestamps
23
+
24
+ disallow_raw_sql!(on_duplicate)
25
+ disallow_raw_sql!(returning)
26
+
27
+ if @inserts.empty?
28
+ @keys = []
29
+ else
30
+ resolve_sti
31
+ resolve_attribute_aliases
32
+ @keys = @inserts.first.keys
19
33
  end
34
+
35
+ @scope_attributes = relation.scope_for_create.except(@model.inheritance_column)
36
+ @keys |= @scope_attributes.keys
20
37
  @keys = @keys.to_set
21
38
 
22
39
  @returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
23
40
  @returning = false if @returning == []
24
41
 
25
- @unique_by = find_unique_index_for(unique_by)
26
- @on_duplicate = :skip if @on_duplicate == :update && updatable_columns.empty?
42
+ @unique_by = find_unique_index_for(@unique_by)
27
43
 
44
+ configure_on_duplicate_update_logic
28
45
  ensure_valid_options_for_connection!
29
46
  end
30
47
 
31
48
  def execute
49
+ return ActiveRecord::Result.empty if inserts.empty?
50
+
32
51
  message = +"#{model} "
33
52
  message << "Bulk " if inserts.many?
34
53
  message << (on_duplicate == :update ? "Upsert" : "Insert")
@@ -36,14 +55,13 @@ module ActiveRecord
36
55
  end
37
56
 
38
57
  def updatable_columns
39
- keys - readonly_columns - unique_by_columns
58
+ @updatable_columns ||= keys - readonly_columns - unique_by_columns
40
59
  end
41
60
 
42
61
  def primary_keys
43
- Array(connection.schema_cache.primary_keys(model.table_name))
62
+ Array(@model.schema_cache.primary_keys(model.table_name))
44
63
  end
45
64
 
46
-
47
65
  def skip_duplicates?
48
66
  on_duplicate == :skip
49
67
  end
@@ -55,18 +73,78 @@ module ActiveRecord
55
73
  def map_key_with_value
56
74
  inserts.map do |attributes|
57
75
  attributes = attributes.stringify_keys
58
- attributes.merge!(scope_attributes) if scope_attributes
76
+ attributes.merge!(@scope_attributes)
77
+ attributes.reverse_merge!(timestamps_for_create) if record_timestamps?
59
78
 
60
79
  verify_attributes(attributes)
61
80
 
62
- keys.map do |key|
81
+ keys_including_timestamps.map do |key|
63
82
  yield key, attributes[key]
64
83
  end
65
84
  end
66
85
  end
67
86
 
87
+ def record_timestamps?
88
+ @record_timestamps
89
+ end
90
+
91
+ # TODO: Consider renaming this method, as it only conditionally extends keys, not always
92
+ def keys_including_timestamps
93
+ @keys_including_timestamps ||= if record_timestamps?
94
+ keys + model.all_timestamp_attributes_in_model
95
+ else
96
+ keys
97
+ end
98
+ end
99
+
68
100
  private
69
- attr_reader :scope_attributes
101
+ def has_attribute_aliases?(attributes)
102
+ attributes.keys.any? { |attribute| model.attribute_alias?(attribute) }
103
+ end
104
+
105
+ def resolve_sti
106
+ return if model.descends_from_active_record?
107
+
108
+ sti_type = model.sti_name
109
+ @inserts = @inserts.map do |insert|
110
+ insert.reverse_merge(model.inheritance_column.to_s => sti_type)
111
+ end
112
+ end
113
+
114
+ def resolve_attribute_aliases
115
+ return unless has_attribute_aliases?(@inserts.first)
116
+
117
+ @inserts = @inserts.map do |insert|
118
+ insert.transform_keys { |attribute| resolve_attribute_alias(attribute) }
119
+ end
120
+
121
+ @update_only = Array(@update_only).map { |attribute| resolve_attribute_alias(attribute) } if @update_only
122
+ @unique_by = Array(@unique_by).map { |attribute| resolve_attribute_alias(attribute) } if @unique_by
123
+ end
124
+
125
+ def resolve_attribute_alias(attribute)
126
+ model.attribute_alias(attribute) || attribute
127
+ end
128
+
129
+ def configure_on_duplicate_update_logic
130
+ if custom_update_sql_provided? && update_only.present?
131
+ raise ArgumentError, "You can't set :update_only and provide custom update SQL via :on_duplicate at the same time"
132
+ end
133
+
134
+ if update_only.present?
135
+ @updatable_columns = Array(update_only)
136
+ @on_duplicate = :update
137
+ elsif custom_update_sql_provided?
138
+ @update_sql = on_duplicate
139
+ @on_duplicate = :update
140
+ elsif @on_duplicate == :update && updatable_columns.empty?
141
+ @on_duplicate = :skip
142
+ end
143
+ end
144
+
145
+ def custom_update_sql_provided?
146
+ @custom_update_sql_provided ||= Arel.arel_node?(on_duplicate)
147
+ end
70
148
 
71
149
  def find_unique_index_for(unique_by)
72
150
  if !connection.supports_insert_conflict_target?
@@ -77,8 +155,9 @@ module ActiveRecord
77
155
 
78
156
  name_or_columns = unique_by || model.primary_key
79
157
  match = Array(name_or_columns).map(&:to_s)
158
+ sorted_match = match.sort
80
159
 
81
- if index = unique_indexes.find { |i| match.include?(i.name) || i.columns == match }
160
+ if index = unique_indexes.find { |i| match.include?(i.name) || Array(i.columns).sort == sorted_match }
82
161
  index
83
162
  elsif match == primary_keys
84
163
  unique_by.nil? ? nil : ActiveRecord::ConnectionAdapters::IndexDefinition.new(model.table_name, "#{model.table_name}_primary_key", true, match)
@@ -88,10 +167,9 @@ module ActiveRecord
88
167
  end
89
168
 
90
169
  def unique_indexes
91
- connection.schema_cache.indexes(model.table_name).select(&:unique)
170
+ @model.schema_cache.indexes(model.table_name).select(&:unique)
92
171
  end
93
172
 
94
-
95
173
  def ensure_valid_options_for_connection!
96
174
  if returning && !connection.supports_insert_returning?
97
175
  raise ArgumentError, "#{connection.class} does not support :returning"
@@ -117,7 +195,7 @@ module ActiveRecord
117
195
 
118
196
 
119
197
  def readonly_columns
120
- primary_keys + model.readonly_attributes.to_a
198
+ primary_keys + model.readonly_attributes
121
199
  end
122
200
 
123
201
  def unique_by_columns
@@ -126,15 +204,28 @@ module ActiveRecord
126
204
 
127
205
 
128
206
  def verify_attributes(attributes)
129
- if keys != attributes.keys.to_set
207
+ if keys_including_timestamps != attributes.keys.to_set
130
208
  raise ArgumentError, "All objects being inserted must have the same keys"
131
209
  end
132
210
  end
133
211
 
212
+ def disallow_raw_sql!(value)
213
+ return if !value.is_a?(String) || Arel.arel_node?(value)
214
+
215
+ raise ArgumentError, "Dangerous query method (method whose arguments are used as raw " \
216
+ "SQL) called: #{value}. " \
217
+ "Known-safe values can be passed " \
218
+ "by wrapping them in Arel.sql()."
219
+ end
220
+
221
+ def timestamps_for_create
222
+ model.all_timestamp_attributes_in_model.index_with(connection.high_precision_current_timestamp)
223
+ end
224
+
134
225
  class Builder # :nodoc:
135
226
  attr_reader :model
136
227
 
137
- delegate :skip_duplicates?, :update_duplicates?, :keys, to: :insert_all
228
+ delegate :skip_duplicates?, :update_duplicates?, :keys, :keys_including_timestamps, :record_timestamps?, to: :insert_all
138
229
 
139
230
  def initialize(insert_all)
140
231
  @insert_all, @model, @connection = insert_all, insert_all.model, insert_all.connection
@@ -145,9 +236,10 @@ module ActiveRecord
145
236
  end
146
237
 
147
238
  def values_list
148
- types = extract_types_from_columns_on(model.table_name, keys: keys)
239
+ types = extract_types_from_columns_on(model.table_name, keys: keys_including_timestamps)
149
240
 
150
241
  values_list = insert_all.map_key_with_value do |key, value|
242
+ next value if Arel::Nodes::SqlLiteral === value
151
243
  connection.with_yaml_fallback(types[key].serialize(value))
152
244
  end
153
245
 
@@ -155,7 +247,19 @@ module ActiveRecord
155
247
  end
156
248
 
157
249
  def returning
158
- format_columns(insert_all.returning) if insert_all.returning
250
+ return unless insert_all.returning
251
+
252
+ if insert_all.returning.is_a?(String)
253
+ insert_all.returning
254
+ else
255
+ Array(insert_all.returning).map do |attribute|
256
+ if model.attribute_alias?(attribute)
257
+ "#{quote_column(model.attribute_alias(attribute))} AS #{quote_column(attribute)}"
258
+ else
259
+ quote_column(attribute)
260
+ end
261
+ end.join(",")
262
+ end
159
263
  end
160
264
 
161
265
  def conflict_target
@@ -173,26 +277,34 @@ module ActiveRecord
173
277
  end
174
278
 
175
279
  def touch_model_timestamps_unless(&block)
176
- model.send(:timestamp_attributes_for_update_in_model).map do |column_name|
280
+ return "" unless update_duplicates? && record_timestamps?
281
+
282
+ model.timestamp_attributes_for_update_in_model.filter_map do |column_name|
177
283
  if touch_timestamp_attribute?(column_name)
178
- "#{column_name}=(CASE WHEN (#{updatable_columns.map(&block).join(" AND ")}) THEN #{model.quoted_table_name}.#{column_name} ELSE CURRENT_TIMESTAMP END),"
284
+ "#{column_name}=(CASE WHEN (#{updatable_columns.map(&block).join(" AND ")}) THEN #{model.quoted_table_name}.#{column_name} ELSE #{connection.high_precision_current_timestamp} END),"
179
285
  end
180
- end.compact.join
286
+ end.join
181
287
  end
182
288
 
289
+ def raw_update_sql
290
+ insert_all.update_sql
291
+ end
292
+
293
+ alias raw_update_sql? raw_update_sql
294
+
183
295
  private
184
296
  attr_reader :connection, :insert_all
185
297
 
186
298
  def touch_timestamp_attribute?(column_name)
187
- update_duplicates? && !insert_all.updatable_columns.include?(column_name)
299
+ insert_all.updatable_columns.exclude?(column_name)
188
300
  end
189
301
 
190
302
  def columns_list
191
- format_columns(insert_all.keys)
303
+ format_columns(insert_all.keys_including_timestamps)
192
304
  end
193
305
 
194
306
  def extract_types_from_columns_on(table_name, keys:)
195
- columns = connection.schema_cache.columns_hash(table_name)
307
+ columns = @model.schema_cache.columns_hash(table_name)
196
308
 
197
309
  unknown_column = (keys - columns.keys).first
198
310
  raise UnknownAttributeError.new(model.new, unknown_column) if unknown_column
@@ -205,7 +317,11 @@ module ActiveRecord
205
317
  end
206
318
 
207
319
  def quote_columns(columns)
208
- columns.map(&connection.method(:quote_column_name))
320
+ columns.map { |column| quote_column(column) }
321
+ end
322
+
323
+ def quote_column(column)
324
+ connection.quote_column_name(column)
209
325
  end
210
326
  end
211
327
  end
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  ##
11
11
  # :singleton-method:
12
12
  # Indicates the format used to generate the timestamp in the cache key, if
13
- # versioning is off. Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>.
13
+ # versioning is off. Accepts any of the symbols in +Time::DATE_FORMATS+.
14
14
  #
15
15
  # This is +:usec+, by default.
16
16
  class_attribute :cache_timestamp_format, instance_writer: false, default: :usec
@@ -20,7 +20,7 @@ module ActiveRecord
20
20
  # Indicates whether to use a stable #cache_key method that is accompanied
21
21
  # by a changing version in the #cache_version method.
22
22
  #
23
- # This is +true+, by default on Rails 5.2 and above.
23
+ # This is +true+, by default on \Rails 5.2 and above.
24
24
  class_attribute :cache_versioning, instance_writer: false, default: false
25
25
 
26
26
  ##
@@ -28,7 +28,7 @@ module ActiveRecord
28
28
  # Indicates whether to use a stable #cache_key method that is accompanied
29
29
  # by a changing version in the #cache_version method on collections.
30
30
  #
31
- # This is +false+, by default until Rails 6.1.
31
+ # This is +false+, by default until \Rails 6.1.
32
32
  class_attribute :collection_cache_versioning, instance_writer: false, default: false
33
33
  end
34
34
 
@@ -55,8 +55,8 @@ module ActiveRecord
55
55
  # user = User.find_by(name: 'Phusion')
56
56
  # user_path(user) # => "/users/Phusion"
57
57
  def to_param
58
- # We can't use alias_method here, because method 'id' optimizes itself on the fly.
59
- id && id.to_s # Be sure to stringify the id for routes
58
+ return unless id
59
+ Array(id).join(self.class.param_delimiter)
60
60
  end
61
61
 
62
62
  # Returns a stable cache key that can be used to identify this record.
@@ -64,7 +64,7 @@ module ActiveRecord
64
64
  # Product.new.cache_key # => "products/new"
65
65
  # Product.find(5).cache_key # => "products/5"
66
66
  #
67
- # If ActiveRecord::Base.cache_versioning is turned off, as it was in Rails 5.1 and earlier,
67
+ # If ActiveRecord::Base.cache_versioning is turned off, as it was in \Rails 5.1 and earlier,
68
68
  # the cache key will also include a version.
69
69
  #
70
70
  # Product.cache_versioning = false
@@ -79,7 +79,7 @@ module ActiveRecord
79
79
  timestamp = max_updated_column_timestamp
80
80
 
81
81
  if timestamp
82
- timestamp = timestamp.utc.to_s(cache_timestamp_format)
82
+ timestamp = timestamp.utc.to_fs(cache_timestamp_format)
83
83
  "#{model_name.cache_key}/#{id}-#{timestamp}"
84
84
  else
85
85
  "#{model_name.cache_key}/#{id}"
@@ -101,11 +101,12 @@ module ActiveRecord
101
101
  timestamp = updated_at_before_type_cast
102
102
  if can_use_fast_cache_version?(timestamp)
103
103
  raw_timestamp_to_cache_version(timestamp)
104
+
104
105
  elsif timestamp = updated_at
105
- timestamp.utc.to_s(cache_timestamp_format)
106
+ timestamp.utc.to_fs(cache_timestamp_format)
106
107
  end
107
108
  elsif self.class.has_attribute?("updated_at")
108
- raise ActiveModel::MissingAttributeError, "missing attribute: updated_at"
109
+ raise ActiveModel::MissingAttributeError, "missing attribute 'updated_at' for #{self.class}"
109
110
  end
110
111
  end
111
112
 
@@ -177,7 +178,10 @@ module ActiveRecord
177
178
  def can_use_fast_cache_version?(timestamp)
178
179
  timestamp.is_a?(String) &&
179
180
  cache_timestamp_format == :usec &&
180
- default_timezone == :utc &&
181
+ # FIXME: checking out a connection for this is wasteful
182
+ # we should store/cache this information in the schema cache
183
+ # or similar.
184
+ self.class.with_connection(&:default_timezone) == :utc &&
181
185
  !updated_at_came_from_user?
182
186
  end
183
187
 
@@ -9,42 +9,83 @@ module ActiveRecord
9
9
  #
10
10
  # This is enabled by default. To disable this functionality set
11
11
  # `use_metadata_table` to false in your database configuration.
12
- class InternalMetadata < ActiveRecord::Base # :nodoc:
13
- self.record_timestamps = true
12
+ class InternalMetadata # :nodoc:
13
+ class NullInternalMetadata # :nodoc:
14
+ end
14
15
 
15
- class << self
16
- def enabled?
17
- ActiveRecord::Base.connection.use_metadata_table?
18
- end
16
+ attr_reader :arel_table
17
+
18
+ def initialize(pool)
19
+ @pool = pool
20
+ @arel_table = Arel::Table.new(table_name)
21
+ end
22
+
23
+ def primary_key
24
+ "key"
25
+ end
19
26
 
20
- def _internal?
21
- true
27
+ def value_key
28
+ "value"
29
+ end
30
+
31
+ def table_name
32
+ "#{ActiveRecord::Base.table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{ActiveRecord::Base.table_name_suffix}"
33
+ end
34
+
35
+ def enabled?
36
+ @pool.db_config.use_metadata_table?
37
+ end
38
+
39
+ def []=(key, value)
40
+ return unless enabled?
41
+
42
+ @pool.with_connection do |connection|
43
+ update_or_create_entry(connection, key, value)
22
44
  end
45
+ end
23
46
 
24
- def primary_key
25
- "key"
47
+ def [](key)
48
+ return unless enabled?
49
+
50
+ @pool.with_connection do |connection|
51
+ if entry = select_entry(connection, key)
52
+ entry[value_key]
53
+ end
26
54
  end
55
+ end
56
+
57
+ def delete_all_entries
58
+ dm = Arel::DeleteManager.new(arel_table)
27
59
 
28
- def table_name
29
- "#{table_name_prefix}#{internal_metadata_table_name}#{table_name_suffix}"
60
+ @pool.with_connection do |connection|
61
+ connection.delete(dm, "#{self.class} Destroy")
30
62
  end
63
+ end
31
64
 
32
- def []=(key, value)
33
- return unless enabled?
65
+ def count
66
+ sm = Arel::SelectManager.new(arel_table)
67
+ sm.project(*Arel::Nodes::Count.new([Arel.star]))
34
68
 
35
- find_or_initialize_by(key: key).update!(value: value)
69
+ @pool.with_connection do |connection|
70
+ connection.select_values(sm, "#{self.class} Count").first
36
71
  end
72
+ end
37
73
 
38
- def [](key)
39
- return unless enabled?
74
+ def create_table_and_set_flags(environment, schema_sha1 = nil)
75
+ return unless enabled?
40
76
 
41
- where(key: key).pluck(:value).first
77
+ @pool.with_connection do |connection|
78
+ create_table
79
+ update_or_create_entry(connection, :environment, environment)
80
+ update_or_create_entry(connection, :schema_sha1, schema_sha1) if schema_sha1
42
81
  end
82
+ end
43
83
 
44
- # Creates an internal metadata table with columns +key+ and +value+
45
- def create_table
46
- return unless enabled?
84
+ # Creates an internal metadata table with columns +key+ and +value+
85
+ def create_table
86
+ return unless enabled?
47
87
 
88
+ @pool.with_connection do |connection|
48
89
  unless connection.table_exists?(table_name)
49
90
  connection.create_table(table_name, id: false) do |t|
50
91
  t.string :key, **connection.internal_string_options_for_primary_key
@@ -53,12 +94,71 @@ module ActiveRecord
53
94
  end
54
95
  end
55
96
  end
97
+ end
56
98
 
57
- def drop_table
58
- return unless enabled?
99
+ def drop_table
100
+ return unless enabled?
59
101
 
102
+ @pool.with_connection do |connection|
60
103
  connection.drop_table table_name, if_exists: true
61
104
  end
62
105
  end
106
+
107
+ def table_exists?
108
+ @pool.schema_cache.data_source_exists?(table_name)
109
+ end
110
+
111
+ private
112
+ def update_or_create_entry(connection, key, value)
113
+ entry = select_entry(connection, key)
114
+
115
+ if entry
116
+ if entry[value_key] != value
117
+ update_entry(connection, key, value)
118
+ else
119
+ entry[value_key]
120
+ end
121
+ else
122
+ create_entry(connection, key, value)
123
+ end
124
+ end
125
+
126
+ def current_time(connection)
127
+ connection.default_timezone == :utc ? Time.now.utc : Time.now
128
+ end
129
+
130
+ def create_entry(connection, key, value)
131
+ im = Arel::InsertManager.new(arel_table)
132
+ im.insert [
133
+ [arel_table[primary_key], key],
134
+ [arel_table[value_key], value],
135
+ [arel_table[:created_at], current_time(connection)],
136
+ [arel_table[:updated_at], current_time(connection)]
137
+ ]
138
+
139
+ connection.insert(im, "#{self.class} Create", primary_key, key)
140
+ end
141
+
142
+ def update_entry(connection, key, new_value)
143
+ um = Arel::UpdateManager.new(arel_table)
144
+ um.set [
145
+ [arel_table[value_key], new_value],
146
+ [arel_table[:updated_at], current_time(connection)]
147
+ ]
148
+
149
+ um.where(arel_table[primary_key].eq(key))
150
+
151
+ connection.update(um, "#{self.class} Update")
152
+ end
153
+
154
+ def select_entry(connection, key)
155
+ sm = Arel::SelectManager.new(arel_table)
156
+ sm.project(Arel::Nodes::SqlLiteral.new("*", retryable: true))
157
+ sm.where(arel_table[primary_key].eq(Arel::Nodes::BindParam.new(key)))
158
+ sm.order(arel_table[primary_key].asc)
159
+ sm.limit = 1
160
+
161
+ connection.select_all(sm, "#{self.class} Load").first
162
+ end
63
163
  end
64
164
  end
@@ -2,50 +2,13 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module LegacyYamlAdapter # :nodoc:
5
- def self.convert(klass, coder)
5
+ def self.convert(coder)
6
6
  return coder unless coder.is_a?(Psych::Coder)
7
7
 
8
8
  case coder["active_record_yaml_version"]
9
9
  when 1, 2 then coder
10
10
  else
11
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
12
- YAML loading from legacy format older than Rails 5.0 is deprecated
13
- and will be removed in Rails 7.0.
14
- MSG
15
- if coder["attributes"].is_a?(ActiveModel::AttributeSet)
16
- Rails420.convert(klass, coder)
17
- else
18
- Rails41.convert(klass, coder)
19
- end
20
- end
21
- end
22
-
23
- module Rails420 # :nodoc:
24
- def self.convert(klass, coder)
25
- attribute_set = coder["attributes"]
26
-
27
- klass.attribute_names.each do |attr_name|
28
- attribute = attribute_set[attr_name]
29
- if attribute.type.is_a?(Delegator)
30
- type_from_klass = klass.type_for_attribute(attr_name)
31
- attribute_set[attr_name] = attribute.with_type(type_from_klass)
32
- end
33
- end
34
-
35
- coder
36
- end
37
- end
38
-
39
- module Rails41 # :nodoc:
40
- def self.convert(klass, coder)
41
- attributes = klass.attributes_builder
42
- .build_from_database(coder["attributes"])
43
- new_record = coder["attributes"][klass.primary_key].blank?
44
-
45
- {
46
- "attributes" => attributes,
47
- "new_record" => new_record,
48
- }
11
+ raise("Active Record doesn't know how to load YAML with this format.")
49
12
  end
50
13
  end
51
14
  end