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
@@ -1,24 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "concurrent/map"
4
+ require "concurrent/atomic/atomic_fixnum"
4
5
 
5
6
  module ActiveRecord
6
7
  module ConnectionAdapters # :nodoc:
7
8
  module QueryCache
9
+ DEFAULT_SIZE = 100 # :nodoc:
10
+
8
11
  class << self
9
- def included(base) #:nodoc:
10
- dirties_query_cache base, :create, :insert, :update, :delete, :truncate, :truncate_tables,
11
- :rollback_to_savepoint, :rollback_db_transaction, :exec_insert_all
12
+ def included(base) # :nodoc:
13
+ dirties_query_cache base, :exec_query, :execute, :create, :insert, :update, :delete, :truncate,
14
+ :truncate_tables, :rollback_to_savepoint, :rollback_db_transaction, :restart_db_transaction,
15
+ :exec_insert_all
12
16
 
13
- base.set_callback :checkout, :after, :configure_query_cache!
14
- base.set_callback :checkin, :after, :disable_query_cache!
17
+ base.set_callback :checkin, :after, :unset_query_cache!
15
18
  end
16
19
 
17
20
  def dirties_query_cache(base, *method_names)
18
21
  method_names.each do |method_name|
19
22
  base.class_eval <<-end_code, __FILE__, __LINE__ + 1
20
- def #{method_name}(*)
21
- ActiveRecord::Base.clear_query_caches_for_current_thread
23
+ def #{method_name}(...)
24
+ if pool.dirties_query_cache
25
+ ActiveRecord::Base.clear_query_caches_for_current_thread
26
+ end
22
27
  super
23
28
  end
24
29
  end_code
@@ -26,59 +31,196 @@ module ActiveRecord
26
31
  end
27
32
  end
28
33
 
29
- module ConnectionPoolConfiguration
30
- def initialize(*)
34
+ class Store # :nodoc:
35
+ attr_accessor :enabled, :dirties
36
+ alias_method :enabled?, :enabled
37
+ alias_method :dirties?, :dirties
38
+
39
+ def initialize(version, max_size)
40
+ @version = version
41
+ @current_version = version.value
42
+ @map = {}
43
+ @max_size = max_size
44
+ @enabled = false
45
+ @dirties = true
46
+ end
47
+
48
+ def size
49
+ check_version
50
+ @map.size
51
+ end
52
+
53
+ def empty?
54
+ check_version
55
+ @map.empty?
56
+ end
57
+
58
+ def [](key)
59
+ check_version
60
+ return unless @enabled
61
+
62
+ if entry = @map.delete(key)
63
+ @map[key] = entry
64
+ end
65
+ end
66
+
67
+ def compute_if_absent(key)
68
+ check_version
69
+
70
+ return yield unless @enabled
71
+
72
+ if entry = @map.delete(key)
73
+ return @map[key] = entry
74
+ end
75
+
76
+ if @max_size && @map.size >= @max_size
77
+ @map.shift # evict the oldest entry
78
+ end
79
+
80
+ @map[key] ||= yield
81
+ end
82
+
83
+ def clear
84
+ @map.clear
85
+ self
86
+ end
87
+
88
+ private
89
+ def check_version
90
+ if @current_version != @version.value
91
+ @map.clear
92
+ @current_version = @version.value
93
+ end
94
+ end
95
+ end
96
+
97
+ class QueryCacheRegistry # :nodoc:
98
+ def initialize
99
+ @mutex = Mutex.new
100
+ @map = ConnectionPool::WeakThreadKeyMap.new
101
+ end
102
+
103
+ def compute_if_absent(context)
104
+ @map[context] || @mutex.synchronize do
105
+ @map[context] ||= yield
106
+ end
107
+ end
108
+
109
+ def clear
110
+ @map.synchronize do
111
+ @map.clear
112
+ end
113
+ end
114
+ end
115
+
116
+ module ConnectionPoolConfiguration # :nodoc:
117
+ def initialize(...)
118
+ super
119
+ @query_cache_version = Concurrent::AtomicFixnum.new
120
+ @thread_query_caches = QueryCacheRegistry.new
121
+ @query_cache_max_size = \
122
+ case query_cache = db_config&.query_cache
123
+ when 0, false
124
+ nil
125
+ when Integer
126
+ query_cache
127
+ when nil
128
+ DEFAULT_SIZE
129
+ end
130
+ end
131
+
132
+ def checkout_and_verify(connection)
31
133
  super
32
- @query_cache_enabled = Concurrent::Map.new { false }
134
+ connection.query_cache ||= query_cache
135
+ connection
136
+ end
137
+
138
+ # Disable the query cache within the block.
139
+ def disable_query_cache(dirties: true)
140
+ cache = query_cache
141
+ old_enabled, cache.enabled, old_dirties, cache.dirties = cache.enabled, false, cache.dirties, dirties
142
+ begin
143
+ yield
144
+ ensure
145
+ cache.enabled, cache.dirties = old_enabled, old_dirties
146
+ end
147
+ end
148
+
149
+ def enable_query_cache
150
+ cache = query_cache
151
+ old_enabled, cache.enabled, old_dirties, cache.dirties = cache.enabled, true, cache.dirties, true
152
+ begin
153
+ yield
154
+ ensure
155
+ cache.enabled, cache.dirties = old_enabled, old_dirties
156
+ end
33
157
  end
34
158
 
35
159
  def enable_query_cache!
36
- @query_cache_enabled[connection_cache_key(current_thread)] = true
37
- connection.enable_query_cache! if active_connection?
160
+ query_cache.enabled = true
161
+ query_cache.dirties = true
38
162
  end
39
163
 
40
164
  def disable_query_cache!
41
- @query_cache_enabled.delete connection_cache_key(current_thread)
42
- connection.disable_query_cache! if active_connection?
165
+ query_cache.enabled = false
166
+ query_cache.dirties = true
43
167
  end
44
168
 
45
169
  def query_cache_enabled
46
- @query_cache_enabled[connection_cache_key(current_thread)]
170
+ query_cache.enabled
171
+ end
172
+
173
+ def dirties_query_cache
174
+ query_cache.dirties
175
+ end
176
+
177
+ def clear_query_cache
178
+ if @pinned_connection
179
+ # With transactional fixtures, and especially systems test
180
+ # another thread may use the same connection, but with a different
181
+ # query cache. So we must clear them all.
182
+ @query_cache_version.increment
183
+ end
184
+ query_cache.clear
185
+ end
186
+
187
+ def query_cache
188
+ @thread_query_caches.compute_if_absent(ActiveSupport::IsolatedExecutionState.context) do
189
+ Store.new(@query_cache_version, @query_cache_max_size)
190
+ end
47
191
  end
48
192
  end
49
193
 
50
- attr_reader :query_cache, :query_cache_enabled
194
+ attr_accessor :query_cache
51
195
 
52
196
  def initialize(*)
53
197
  super
54
- @query_cache = Hash.new { |h, sql| h[sql] = {} }
55
- @query_cache_enabled = false
198
+ @query_cache = nil
199
+ end
200
+
201
+ def query_cache_enabled
202
+ @query_cache&.enabled?
56
203
  end
57
204
 
58
205
  # Enable the query cache within the block.
59
- def cache
60
- old, @query_cache_enabled = @query_cache_enabled, true
61
- yield
62
- ensure
63
- @query_cache_enabled = old
64
- clear_query_cache unless @query_cache_enabled
206
+ def cache(&block)
207
+ pool.enable_query_cache(&block)
65
208
  end
66
209
 
67
210
  def enable_query_cache!
68
- @query_cache_enabled = true
211
+ pool.enable_query_cache!
69
212
  end
70
213
 
71
- def disable_query_cache!
72
- @query_cache_enabled = false
73
- clear_query_cache
214
+ # Disable the query cache within the block.
215
+ #
216
+ # Set <tt>dirties: false</tt> to prevent query caches on all connections from being cleared by write operations.
217
+ # (By default, write operations dirty all connections' query caches in case they are replicas whose cache would now be outdated.)
218
+ def uncached(dirties: true, &block)
219
+ pool.disable_query_cache(dirties: dirties, &block)
74
220
  end
75
221
 
76
- # Disable the query cache within the block.
77
- def uncached
78
- old, @query_cache_enabled = @query_cache_enabled, false
79
- yield
80
- ensure
81
- @query_cache_enabled = old
222
+ def disable_query_cache!
223
+ pool.disable_query_cache!
82
224
  end
83
225
 
84
226
  # Clears the query cache.
@@ -88,37 +230,71 @@ module ActiveRecord
88
230
  # the same SQL query and repeatedly return the same result each time, silently
89
231
  # undermining the randomness you were expecting.
90
232
  def clear_query_cache
91
- @lock.synchronize do
92
- @query_cache.clear
93
- end
233
+ pool.clear_query_cache
94
234
  end
95
235
 
96
- def select_all(arel, name = nil, binds = [], preparable: nil)
97
- if @query_cache_enabled && !locked?(arel)
98
- arel = arel_from_relation(arel)
99
- sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
236
+ def select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false) # :nodoc:
237
+ arel = arel_from_relation(arel)
238
+
239
+ # If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
240
+ # Such queries should not be cached.
241
+ if @query_cache&.enabled? && !(arel.respond_to?(:locked) && arel.locked)
242
+ sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable)
100
243
 
101
- cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable) }
244
+ if async
245
+ result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry)
246
+ FutureResult.wrap(result)
247
+ else
248
+ cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry) }
249
+ end
102
250
  else
103
251
  super
104
252
  end
105
253
  end
106
254
 
107
255
  private
256
+ def unset_query_cache!
257
+ @query_cache = nil
258
+ end
259
+
260
+ def lookup_sql_cache(sql, name, binds)
261
+ key = binds.empty? ? sql : [sql, binds]
262
+
263
+ result = nil
264
+ @lock.synchronize do
265
+ result = @query_cache[key]
266
+ end
267
+
268
+ if result
269
+ ActiveSupport::Notifications.instrument(
270
+ "sql.active_record",
271
+ cache_notification_info(sql, name, binds)
272
+ )
273
+ end
274
+
275
+ result
276
+ end
277
+
108
278
  def cache_sql(sql, name, binds)
279
+ key = binds.empty? ? sql : [sql, binds]
280
+ result = nil
281
+ hit = true
282
+
109
283
  @lock.synchronize do
110
- result =
111
- if @query_cache[sql].key?(binds)
112
- ActiveSupport::Notifications.instrument(
113
- "sql.active_record",
114
- cache_notification_info(sql, name, binds)
115
- )
116
- @query_cache[sql][binds]
117
- else
118
- @query_cache[sql][binds] = yield
119
- end
120
- result.dup
284
+ result = @query_cache.compute_if_absent(key) do
285
+ hit = false
286
+ yield
287
+ end
288
+ end
289
+
290
+ if hit
291
+ ActiveSupport::Notifications.instrument(
292
+ "sql.active_record",
293
+ cache_notification_info(sql, name, binds)
294
+ )
121
295
  end
296
+
297
+ result.dup
122
298
  end
123
299
 
124
300
  # Database adapters can override this method to
@@ -130,20 +306,10 @@ module ActiveRecord
130
306
  type_casted_binds: -> { type_casted_binds(binds) },
131
307
  name: name,
132
308
  connection: self,
309
+ transaction: current_transaction.user_transaction.presence,
133
310
  cached: true
134
311
  }
135
312
  end
136
-
137
- # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such
138
- # queries should not be cached.
139
- def locked?(arel)
140
- arel = arel.arel if arel.is_a?(Relation)
141
- arel.respond_to?(:locked) && arel.locked
142
- end
143
-
144
- def configure_query_cache!
145
- enable_query_cache! if pool.query_cache_enabled
146
- end
147
313
  end
148
314
  end
149
315
  end
@@ -5,42 +5,112 @@ require "active_support/multibyte/chars"
5
5
 
6
6
  module ActiveRecord
7
7
  module ConnectionAdapters # :nodoc:
8
+ # = Active Record Connection Adapters \Quoting
8
9
  module Quoting
10
+ extend ActiveSupport::Concern
11
+
12
+ module ClassMethods # :nodoc:
13
+ # Regexp for column names (with or without a table name prefix).
14
+ # Matches the following:
15
+ #
16
+ # "#{table_name}.#{column_name}"
17
+ # "#{column_name}"
18
+ def column_name_matcher
19
+ /
20
+ \A
21
+ (
22
+ (?:
23
+ # table_name.column_name | function(one or no argument)
24
+ ((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
25
+ )
26
+ (?:(?:\s+AS)?\s+\w+)?
27
+ )
28
+ (?:\s*,\s*\g<1>)*
29
+ \z
30
+ /ix
31
+ end
32
+
33
+ # Regexp for column names with order (with or without a table name prefix,
34
+ # with or without various order modifiers). Matches the following:
35
+ #
36
+ # "#{table_name}.#{column_name}"
37
+ # "#{table_name}.#{column_name} #{direction}"
38
+ # "#{table_name}.#{column_name} #{direction} NULLS FIRST"
39
+ # "#{table_name}.#{column_name} NULLS LAST"
40
+ # "#{column_name}"
41
+ # "#{column_name} #{direction}"
42
+ # "#{column_name} #{direction} NULLS FIRST"
43
+ # "#{column_name} NULLS LAST"
44
+ def column_name_with_order_matcher
45
+ /
46
+ \A
47
+ (
48
+ (?:
49
+ # table_name.column_name | function(one or no argument)
50
+ ((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
51
+ )
52
+ (?:\s+ASC|\s+DESC)?
53
+ (?:\s+NULLS\s+(?:FIRST|LAST))?
54
+ )
55
+ (?:\s*,\s*\g<1>)*
56
+ \z
57
+ /ix
58
+ end
59
+
60
+ # Quotes the column name. Must be implemented by subclasses
61
+ def quote_column_name(column_name)
62
+ raise NotImplementedError
63
+ end
64
+
65
+ # Quotes the table name. Defaults to column name quoting.
66
+ def quote_table_name(table_name)
67
+ quote_column_name(table_name)
68
+ end
69
+ end
70
+
9
71
  # Quotes the column value to help prevent
10
72
  # {SQL injection attacks}[https://en.wikipedia.org/wiki/SQL_injection].
11
73
  def quote(value)
12
- if value.is_a?(Base)
13
- ActiveSupport::Deprecation.warn(<<~MSG)
14
- Passing an Active Record object to `quote` directly is deprecated
15
- and will be no longer quoted as id value in Rails 7.0.
16
- MSG
17
- value = value.id_for_database
74
+ case value
75
+ when String, Symbol, ActiveSupport::Multibyte::Chars
76
+ "'#{quote_string(value.to_s)}'"
77
+ when true then quoted_true
78
+ when false then quoted_false
79
+ when nil then "NULL"
80
+ # BigDecimals need to be put in a non-normalized form and quoted.
81
+ when BigDecimal then value.to_s("F")
82
+ when Numeric then value.to_s
83
+ when Type::Binary::Data then quoted_binary(value)
84
+ when Type::Time::Value then "'#{quoted_time(value)}'"
85
+ when Date, Time then "'#{quoted_date(value)}'"
86
+ when Class then "'#{value}'"
87
+ else raise TypeError, "can't quote #{value.class.name}"
18
88
  end
19
-
20
- _quote(value)
21
89
  end
22
90
 
23
91
  # Cast a +value+ to a type that the database understands. For example,
24
92
  # SQLite does not understand dates, so this method will convert a Date
25
93
  # to a String.
26
- def type_cast(value, column = nil)
27
- if value.is_a?(Base)
28
- ActiveSupport::Deprecation.warn(<<~MSG)
29
- Passing an Active Record object to `type_cast` directly is deprecated
30
- and will be no longer type casted as id value in Rails 7.0.
31
- MSG
32
- value = value.id_for_database
33
- end
34
-
35
- if column
36
- ActiveSupport::Deprecation.warn(<<~MSG)
37
- Passing a column to `type_cast` is deprecated and will be removed in Rails 7.0.
38
- MSG
39
- type = lookup_cast_type_from_column(column)
40
- value = type.serialize(value)
94
+ def type_cast(value)
95
+ case value
96
+ when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data
97
+ value.to_s
98
+ when true then unquoted_true
99
+ when false then unquoted_false
100
+ # BigDecimals need to be put in a non-normalized form and quoted.
101
+ when BigDecimal then value.to_s("F")
102
+ when nil, Numeric, String then value
103
+ when Type::Time::Value then quoted_time(value)
104
+ when Date, Time then quoted_date(value)
105
+ else raise TypeError, "can't cast #{value.class.name}"
41
106
  end
107
+ end
42
108
 
43
- _type_cast(value)
109
+ # Cast a value to be used as a bound parameter of unknown type. For example,
110
+ # MySQL might perform dangerous castings when comparing a string to a number,
111
+ # so this method will cast numbers to string.
112
+ def cast_bound_value(value) # :nodoc:
113
+ value
44
114
  end
45
115
 
46
116
  # If you are having to call this function, you are likely doing something
@@ -59,23 +129,23 @@ module ActiveRecord
59
129
  # Quotes a string, escaping any ' (single quote) and \ (backslash)
60
130
  # characters.
61
131
  def quote_string(s)
62
- s.gsub('\\', '\&\&').gsub("'", "''") # ' (for ruby-mode)
132
+ s.gsub("\\", '\&\&').gsub("'", "''") # ' (for ruby-mode)
63
133
  end
64
134
 
65
- # Quotes the column name. Defaults to no quoting.
135
+ # Quotes the column name.
66
136
  def quote_column_name(column_name)
67
- column_name.to_s
137
+ self.class.quote_column_name(column_name)
68
138
  end
69
139
 
70
- # Quotes the table name. Defaults to column name quoting.
140
+ # Quotes the table name.
71
141
  def quote_table_name(table_name)
72
- quote_column_name(table_name)
142
+ self.class.quote_table_name(table_name)
73
143
  end
74
144
 
75
145
  # Override to return the quoted table name for assignment. Defaults to
76
146
  # table quoting.
77
147
  #
78
- # This works for mysql2 where table.column can be used to
148
+ # This works for MySQL where table.column can be used to
79
149
  # resolve ambiguity.
80
150
  #
81
151
  # We override this in the sqlite3 and postgresql adapters to use only
@@ -113,14 +183,14 @@ module ActiveRecord
113
183
  # if the value is a Time responding to usec.
114
184
  def quoted_date(value)
115
185
  if value.acts_like?(:time)
116
- if ActiveRecord::Base.default_timezone == :utc
117
- value = value.getutc if value.respond_to?(:getutc) && !value.utc?
186
+ if default_timezone == :utc
187
+ value = value.getutc if !value.utc?
118
188
  else
119
- value = value.getlocal if value.respond_to?(:getlocal)
189
+ value = value.getlocal
120
190
  end
121
191
  end
122
192
 
123
- result = value.to_s(:db)
193
+ result = value.to_fs(:db)
124
194
  if value.respond_to?(:usec) && value.usec > 0
125
195
  result << "." << sprintf("%06d", value.usec)
126
196
  else
@@ -138,74 +208,25 @@ module ActiveRecord
138
208
  end
139
209
 
140
210
  def sanitize_as_sql_comment(value) # :nodoc:
141
- value.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "")
211
+ # Sanitize a string to appear within a SQL comment
212
+ # For compatibility, this also surrounding "/*+", "/*", and "*/"
213
+ # charcacters, possibly with single surrounding space.
214
+ # Then follows that by replacing any internal "*/" or "/ *" with
215
+ # "* /" or "/ *"
216
+ comment = value.to_s.dup
217
+ comment.gsub!(%r{\A\s*/\*\+?\s?|\s?\*/\s*\Z}, "")
218
+ comment.gsub!("*/", "* /")
219
+ comment.gsub!("/*", "/ *")
220
+ comment
142
221
  end
143
222
 
144
- def column_name_matcher # :nodoc:
145
- COLUMN_NAME
146
- end
147
-
148
- def column_name_with_order_matcher # :nodoc:
149
- COLUMN_NAME_WITH_ORDER
150
- end
151
-
152
- # Regexp for column names (with or without a table name prefix).
153
- # Matches the following:
154
- #
155
- # "#{table_name}.#{column_name}"
156
- # "#{column_name}"
157
- COLUMN_NAME = /
158
- \A
159
- (
160
- (?:
161
- # table_name.column_name | function(one or no argument)
162
- ((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
163
- )
164
- (?:(?:\s+AS)?\s+\w+)?
165
- )
166
- (?:\s*,\s*\g<1>)*
167
- \z
168
- /ix
169
-
170
- # Regexp for column names with order (with or without a table name prefix,
171
- # with or without various order modifiers). Matches the following:
172
- #
173
- # "#{table_name}.#{column_name}"
174
- # "#{table_name}.#{column_name} #{direction}"
175
- # "#{table_name}.#{column_name} #{direction} NULLS FIRST"
176
- # "#{table_name}.#{column_name} NULLS LAST"
177
- # "#{column_name}"
178
- # "#{column_name} #{direction}"
179
- # "#{column_name} #{direction} NULLS FIRST"
180
- # "#{column_name} NULLS LAST"
181
- COLUMN_NAME_WITH_ORDER = /
182
- \A
183
- (
184
- (?:
185
- # table_name.column_name | function(one or no argument)
186
- ((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
187
- )
188
- (?:\s+ASC|\s+DESC)?
189
- (?:\s+NULLS\s+(?:FIRST|LAST))?
190
- )
191
- (?:\s*,\s*\g<1>)*
192
- \z
193
- /ix
194
-
195
- private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
196
-
197
223
  private
198
224
  def type_casted_binds(binds)
199
- case binds.first
200
- when Array
201
- binds.map { |column, value| type_cast(value, column) }
202
- else
203
- binds.map do |value|
204
- if ActiveModel::Attribute === value
205
- type_cast(value.value_for_database)
206
- else
207
- type_cast(value)
208
- end
225
+ binds.map do |value|
226
+ if ActiveModel::Attribute === value
227
+ type_cast(value.value_for_database)
228
+ else
229
+ type_cast(value)
209
230
  end
210
231
  end
211
232
  end
@@ -213,39 +234,6 @@ module ActiveRecord
213
234
  def lookup_cast_type(sql_type)
214
235
  type_map.lookup(sql_type)
215
236
  end
216
-
217
- def _quote(value)
218
- case value
219
- when String, Symbol, ActiveSupport::Multibyte::Chars
220
- "'#{quote_string(value.to_s)}'"
221
- when true then quoted_true
222
- when false then quoted_false
223
- when nil then "NULL"
224
- # BigDecimals need to be put in a non-normalized form and quoted.
225
- when BigDecimal then value.to_s("F")
226
- when Numeric, ActiveSupport::Duration then value.to_s
227
- when Type::Binary::Data then quoted_binary(value)
228
- when Type::Time::Value then "'#{quoted_time(value)}'"
229
- when Date, Time then "'#{quoted_date(value)}'"
230
- when Class then "'#{value}'"
231
- else raise TypeError, "can't quote #{value.class.name}"
232
- end
233
- end
234
-
235
- def _type_cast(value)
236
- case value
237
- when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data
238
- value.to_s
239
- when true then unquoted_true
240
- when false then unquoted_false
241
- # BigDecimals need to be put in a non-normalized form and quoted.
242
- when BigDecimal then value.to_s("F")
243
- when nil, Numeric, String then value
244
- when Type::Time::Value then quoted_time(value)
245
- when Date, Time then quoted_date(value)
246
- else raise TypeError, "can't cast #{value.class.name}"
247
- end
248
- end
249
237
  end
250
238
  end
251
239
  end