activerecord 7.0.0 → 7.2.1

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 (289) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +515 -1268
  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 +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +23 -8
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +28 -17
  20. data/lib/active_record/associations/collection_proxy.rb +36 -13
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +28 -18
  24. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  27. data/lib/active_record/associations/join_dependency.rb +18 -14
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +2 -4
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +378 -491
  36. data/lib/active_record/attribute_assignment.rb +1 -13
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +153 -70
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +153 -40
  47. data/lib/active_record/attributes.rb +63 -48
  48. data/lib/active_record/autosave_association.rb +70 -38
  49. data/lib/active_record/base.rb +12 -8
  50. data/lib/active_record/callbacks.rb +16 -32
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -34
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +124 -132
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +297 -88
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +215 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +83 -65
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +319 -135
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +512 -126
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +282 -119
  70. data/lib/active_record/connection_adapters/column.rb +9 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +27 -140
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +64 -52
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  78. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  80. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  81. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +101 -48
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +94 -61
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +379 -66
  98. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  99. data/lib/active_record/connection_adapters/postgresql_adapter.rb +370 -203
  100. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  101. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  102. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  103. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +61 -46
  104. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  106. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  107. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +64 -22
  108. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +321 -110
  109. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  110. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  111. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  112. data/lib/active_record/connection_adapters.rb +124 -1
  113. data/lib/active_record/connection_handling.rb +98 -106
  114. data/lib/active_record/core.rb +220 -177
  115. data/lib/active_record/counter_cache.rb +68 -34
  116. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -2
  117. data/lib/active_record/database_configurations/database_config.rb +26 -5
  118. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  119. data/lib/active_record/database_configurations/url_config.rb +37 -12
  120. data/lib/active_record/database_configurations.rb +88 -35
  121. data/lib/active_record/delegated_type.rb +40 -11
  122. data/lib/active_record/deprecator.rb +7 -0
  123. data/lib/active_record/destroy_association_async_job.rb +3 -1
  124. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  125. data/lib/active_record/dynamic_matchers.rb +2 -2
  126. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  127. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  128. data/lib/active_record/encryption/config.rb +25 -1
  129. data/lib/active_record/encryption/configurable.rb +13 -14
  130. data/lib/active_record/encryption/context.rb +10 -3
  131. data/lib/active_record/encryption/contexts.rb +8 -4
  132. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  133. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  134. data/lib/active_record/encryption/encryptable_record.rb +47 -25
  135. data/lib/active_record/encryption/encrypted_attribute_type.rb +49 -14
  136. data/lib/active_record/encryption/encryptor.rb +25 -10
  137. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  138. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  139. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  140. data/lib/active_record/encryption/key_generator.rb +12 -1
  141. data/lib/active_record/encryption/message.rb +1 -1
  142. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  143. data/lib/active_record/encryption/message_serializer.rb +6 -0
  144. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  145. data/lib/active_record/encryption/properties.rb +4 -4
  146. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  147. data/lib/active_record/encryption/scheme.rb +23 -22
  148. data/lib/active_record/encryption.rb +1 -0
  149. data/lib/active_record/enum.rb +131 -27
  150. data/lib/active_record/errors.rb +151 -31
  151. data/lib/active_record/explain.rb +21 -12
  152. data/lib/active_record/explain_subscriber.rb +1 -1
  153. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  154. data/lib/active_record/fixture_set/render_context.rb +2 -0
  155. data/lib/active_record/fixture_set/table_row.rb +29 -8
  156. data/lib/active_record/fixtures.rb +169 -99
  157. data/lib/active_record/future_result.rb +47 -8
  158. data/lib/active_record/gem_version.rb +3 -3
  159. data/lib/active_record/inheritance.rb +34 -18
  160. data/lib/active_record/insert_all.rb +72 -22
  161. data/lib/active_record/integration.rb +13 -10
  162. data/lib/active_record/internal_metadata.rb +124 -20
  163. data/lib/active_record/locking/optimistic.rb +39 -24
  164. data/lib/active_record/locking/pessimistic.rb +8 -5
  165. data/lib/active_record/log_subscriber.rb +28 -27
  166. data/lib/active_record/marshalling.rb +56 -0
  167. data/lib/active_record/message_pack.rb +124 -0
  168. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  169. data/lib/active_record/middleware/database_selector.rb +18 -13
  170. data/lib/active_record/middleware/shard_selector.rb +7 -5
  171. data/lib/active_record/migration/command_recorder.rb +110 -13
  172. data/lib/active_record/migration/compatibility.rb +174 -64
  173. data/lib/active_record/migration/default_strategy.rb +22 -0
  174. data/lib/active_record/migration/execution_strategy.rb +19 -0
  175. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  176. data/lib/active_record/migration.rb +292 -125
  177. data/lib/active_record/model_schema.rb +113 -112
  178. data/lib/active_record/nested_attributes.rb +35 -9
  179. data/lib/active_record/normalization.rb +163 -0
  180. data/lib/active_record/persistence.rb +177 -345
  181. data/lib/active_record/promise.rb +84 -0
  182. data/lib/active_record/query_cache.rb +19 -25
  183. data/lib/active_record/query_logs.rb +102 -51
  184. data/lib/active_record/query_logs_formatter.rb +41 -0
  185. data/lib/active_record/querying.rb +34 -9
  186. data/lib/active_record/railtie.rb +153 -100
  187. data/lib/active_record/railties/controller_runtime.rb +24 -10
  188. data/lib/active_record/railties/databases.rake +148 -152
  189. data/lib/active_record/railties/job_runtime.rb +23 -0
  190. data/lib/active_record/readonly_attributes.rb +32 -5
  191. data/lib/active_record/reflection.rb +278 -69
  192. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  193. data/lib/active_record/relation/batches.rb +198 -63
  194. data/lib/active_record/relation/calculations.rb +293 -108
  195. data/lib/active_record/relation/delegation.rb +31 -20
  196. data/lib/active_record/relation/finder_methods.rb +93 -18
  197. data/lib/active_record/relation/merger.rb +6 -6
  198. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  199. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  200. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  201. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  202. data/lib/active_record/relation/predicate_builder.rb +28 -16
  203. data/lib/active_record/relation/query_attribute.rb +25 -1
  204. data/lib/active_record/relation/query_methods.rb +625 -107
  205. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  206. data/lib/active_record/relation/spawn_methods.rb +5 -4
  207. data/lib/active_record/relation/where_clause.rb +7 -19
  208. data/lib/active_record/relation.rb +602 -96
  209. data/lib/active_record/result.rb +55 -52
  210. data/lib/active_record/runtime_registry.rb +63 -1
  211. data/lib/active_record/sanitization.rb +76 -30
  212. data/lib/active_record/schema.rb +39 -23
  213. data/lib/active_record/schema_dumper.rb +82 -30
  214. data/lib/active_record/schema_migration.rb +75 -24
  215. data/lib/active_record/scoping/default.rb +20 -12
  216. data/lib/active_record/scoping/named.rb +3 -2
  217. data/lib/active_record/scoping.rb +2 -1
  218. data/lib/active_record/secure_password.rb +60 -0
  219. data/lib/active_record/secure_token.rb +21 -3
  220. data/lib/active_record/serialization.rb +5 -0
  221. data/lib/active_record/signed_id.rb +29 -8
  222. data/lib/active_record/statement_cache.rb +7 -7
  223. data/lib/active_record/store.rb +16 -11
  224. data/lib/active_record/suppressor.rb +3 -1
  225. data/lib/active_record/table_metadata.rb +7 -3
  226. data/lib/active_record/tasks/database_tasks.rb +191 -121
  227. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  228. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  229. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  230. data/lib/active_record/test_fixtures.rb +174 -152
  231. data/lib/active_record/testing/query_assertions.rb +121 -0
  232. data/lib/active_record/timestamp.rb +31 -17
  233. data/lib/active_record/token_for.rb +123 -0
  234. data/lib/active_record/touch_later.rb +12 -7
  235. data/lib/active_record/transaction.rb +132 -0
  236. data/lib/active_record/transactions.rb +109 -27
  237. data/lib/active_record/translation.rb +1 -3
  238. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  239. data/lib/active_record/type/internal/timezone.rb +7 -2
  240. data/lib/active_record/type/serialized.rb +9 -7
  241. data/lib/active_record/type/time.rb +4 -0
  242. data/lib/active_record/type_caster/connection.rb +4 -4
  243. data/lib/active_record/validations/absence.rb +1 -1
  244. data/lib/active_record/validations/associated.rb +12 -6
  245. data/lib/active_record/validations/numericality.rb +5 -4
  246. data/lib/active_record/validations/presence.rb +5 -28
  247. data/lib/active_record/validations/uniqueness.rb +63 -14
  248. data/lib/active_record/validations.rb +12 -5
  249. data/lib/active_record/version.rb +1 -1
  250. data/lib/active_record.rb +266 -30
  251. data/lib/arel/alias_predication.rb +1 -1
  252. data/lib/arel/collectors/bind.rb +2 -0
  253. data/lib/arel/collectors/composite.rb +7 -0
  254. data/lib/arel/collectors/sql_string.rb +1 -1
  255. data/lib/arel/collectors/substitute_binds.rb +1 -1
  256. data/lib/arel/errors.rb +10 -0
  257. data/lib/arel/factory_methods.rb +4 -0
  258. data/lib/arel/filter_predications.rb +1 -1
  259. data/lib/arel/nodes/binary.rb +6 -7
  260. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  261. data/lib/arel/nodes/cte.rb +36 -0
  262. data/lib/arel/nodes/filter.rb +1 -1
  263. data/lib/arel/nodes/fragments.rb +35 -0
  264. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  265. data/lib/arel/nodes/leading_join.rb +8 -0
  266. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  267. data/lib/arel/nodes/node.rb +115 -5
  268. data/lib/arel/nodes/sql_literal.rb +13 -0
  269. data/lib/arel/nodes/table_alias.rb +4 -0
  270. data/lib/arel/nodes.rb +6 -2
  271. data/lib/arel/predications.rb +3 -1
  272. data/lib/arel/select_manager.rb +1 -1
  273. data/lib/arel/table.rb +9 -5
  274. data/lib/arel/tree_manager.rb +8 -3
  275. data/lib/arel/update_manager.rb +2 -1
  276. data/lib/arel/visitors/dot.rb +1 -0
  277. data/lib/arel/visitors/mysql.rb +17 -5
  278. data/lib/arel/visitors/postgresql.rb +1 -12
  279. data/lib/arel/visitors/to_sql.rb +112 -34
  280. data/lib/arel/visitors/visitor.rb +2 -2
  281. data/lib/arel.rb +21 -3
  282. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  283. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  284. data/lib/rails/generators/active_record/migration.rb +3 -1
  285. data/lib/rails/generators/active_record/model/USAGE +113 -0
  286. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  287. metadata +59 -17
  288. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  289. data/lib/active_record/null_relation.rb +0 -63
@@ -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
12
  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
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,194 @@ 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, query_cache.dirties = true, true
38
161
  end
39
162
 
40
163
  def disable_query_cache!
41
- @query_cache_enabled.delete connection_cache_key(current_thread)
42
- connection.disable_query_cache! if active_connection?
164
+ query_cache.enabled, query_cache.dirties = false, true
43
165
  end
44
166
 
45
167
  def query_cache_enabled
46
- @query_cache_enabled[connection_cache_key(current_thread)]
168
+ query_cache.enabled
169
+ end
170
+
171
+ def dirties_query_cache
172
+ query_cache.dirties
173
+ end
174
+
175
+ def clear_query_cache
176
+ if @pinned_connection
177
+ # With transactional fixtures, and especially systems test
178
+ # another thread may use the same connection, but with a different
179
+ # query cache. So we must clear them all.
180
+ @query_cache_version.increment
181
+ end
182
+ query_cache.clear
183
+ end
184
+
185
+ def query_cache
186
+ @thread_query_caches.compute_if_absent(ActiveSupport::IsolatedExecutionState.context) do
187
+ Store.new(@query_cache_version, @query_cache_max_size)
188
+ end
47
189
  end
48
190
  end
49
191
 
50
- attr_reader :query_cache, :query_cache_enabled
192
+ attr_accessor :query_cache
51
193
 
52
194
  def initialize(*)
53
195
  super
54
- @query_cache = Hash.new { |h, sql| h[sql] = {} }
55
- @query_cache_enabled = false
196
+ @query_cache = nil
197
+ end
198
+
199
+ def query_cache_enabled
200
+ @query_cache&.enabled?
56
201
  end
57
202
 
58
203
  # 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
204
+ def cache(&block)
205
+ pool.enable_query_cache(&block)
65
206
  end
66
207
 
67
208
  def enable_query_cache!
68
- @query_cache_enabled = true
209
+ pool.enable_query_cache!
69
210
  end
70
211
 
71
- def disable_query_cache!
72
- @query_cache_enabled = false
73
- clear_query_cache
212
+ # Disable the query cache within the block.
213
+ #
214
+ # Set <tt>dirties: false</tt> to prevent query caches on all connections from being cleared by write operations.
215
+ # (By default, write operations dirty all connections' query caches in case they are replicas whose cache would now be outdated.)
216
+ def uncached(dirties: true, &block)
217
+ pool.disable_query_cache(dirties: dirties, &block)
74
218
  end
75
219
 
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
220
+ def disable_query_cache!
221
+ pool.disable_query_cache!
82
222
  end
83
223
 
84
224
  # Clears the query cache.
@@ -88,23 +228,22 @@ module ActiveRecord
88
228
  # the same SQL query and repeatedly return the same result each time, silently
89
229
  # undermining the randomness you were expecting.
90
230
  def clear_query_cache
91
- @lock.synchronize do
92
- @query_cache.clear
93
- end
231
+ pool.clear_query_cache
94
232
  end
95
233
 
96
- def select_all(arel, name = nil, binds = [], preparable: nil, async: false)
234
+ def select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false) # :nodoc:
97
235
  arel = arel_from_relation(arel)
98
236
 
99
237
  # If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
100
238
  # Such queries should not be cached.
101
- if @query_cache_enabled && !(arel.respond_to?(:locked) && arel.locked)
102
- sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
239
+ if @query_cache&.enabled? && !(arel.respond_to?(:locked) && arel.locked)
240
+ sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable)
103
241
 
104
242
  if async
105
- lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async)
243
+ result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry)
244
+ FutureResult.wrap(result)
106
245
  else
107
- cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async) }
246
+ cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry) }
108
247
  end
109
248
  else
110
249
  super
@@ -112,32 +251,48 @@ module ActiveRecord
112
251
  end
113
252
 
114
253
  private
254
+ def unset_query_cache!
255
+ @query_cache = nil
256
+ end
257
+
115
258
  def lookup_sql_cache(sql, name, binds)
259
+ key = binds.empty? ? sql : [sql, binds]
260
+
261
+ result = nil
116
262
  @lock.synchronize do
117
- if @query_cache[sql].key?(binds)
118
- ActiveSupport::Notifications.instrument(
119
- "sql.active_record",
120
- cache_notification_info(sql, name, binds)
121
- )
122
- @query_cache[sql][binds]
123
- end
263
+ result = @query_cache[key]
264
+ end
265
+
266
+ if result
267
+ ActiveSupport::Notifications.instrument(
268
+ "sql.active_record",
269
+ cache_notification_info(sql, name, binds)
270
+ )
124
271
  end
272
+
273
+ result
125
274
  end
126
275
 
127
276
  def cache_sql(sql, name, binds)
277
+ key = binds.empty? ? sql : [sql, binds]
278
+ result = nil
279
+ hit = true
280
+
128
281
  @lock.synchronize do
129
- result =
130
- if @query_cache[sql].key?(binds)
131
- ActiveSupport::Notifications.instrument(
132
- "sql.active_record",
133
- cache_notification_info(sql, name, binds)
134
- )
135
- @query_cache[sql][binds]
136
- else
137
- @query_cache[sql][binds] = yield
138
- end
139
- result.dup
282
+ result = @query_cache.compute_if_absent(key) do
283
+ hit = false
284
+ yield
285
+ end
140
286
  end
287
+
288
+ if hit
289
+ ActiveSupport::Notifications.instrument(
290
+ "sql.active_record",
291
+ cache_notification_info(sql, name, binds)
292
+ )
293
+ end
294
+
295
+ result.dup
141
296
  end
142
297
 
143
298
  # Database adapters can override this method to
@@ -149,13 +304,10 @@ module ActiveRecord
149
304
  type_casted_binds: -> { type_casted_binds(binds) },
150
305
  name: name,
151
306
  connection: self,
307
+ transaction: current_transaction.user_transaction.presence,
152
308
  cached: true
153
309
  }
154
310
  end
155
-
156
- def configure_query_cache!
157
- enable_query_cache! if pool.query_cache_enabled
158
- end
159
311
  end
160
312
  end
161
313
  end
@@ -5,7 +5,69 @@ 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)
@@ -17,7 +79,7 @@ module ActiveRecord
17
79
  when nil then "NULL"
18
80
  # BigDecimals need to be put in a non-normalized form and quoted.
19
81
  when BigDecimal then value.to_s("F")
20
- when Numeric, ActiveSupport::Duration then value.to_s
82
+ when Numeric then value.to_s
21
83
  when Type::Binary::Data then quoted_binary(value)
22
84
  when Type::Time::Value then "'#{quoted_time(value)}'"
23
85
  when Date, Time then "'#{quoted_date(value)}'"
@@ -44,11 +106,11 @@ module ActiveRecord
44
106
  end
45
107
  end
46
108
 
47
- # Quote a value to be used as a bound parameter of unknown type. For example,
109
+ # Cast a value to be used as a bound parameter of unknown type. For example,
48
110
  # MySQL might perform dangerous castings when comparing a string to a number,
49
111
  # so this method will cast numbers to string.
50
- def quote_bound_value(value)
51
- quote(value)
112
+ def cast_bound_value(value) # :nodoc:
113
+ value
52
114
  end
53
115
 
54
116
  # If you are having to call this function, you are likely doing something
@@ -70,20 +132,20 @@ module ActiveRecord
70
132
  s.gsub("\\", '\&\&').gsub("'", "''") # ' (for ruby-mode)
71
133
  end
72
134
 
73
- # Quotes the column name. Defaults to no quoting.
135
+ # Quotes the column name.
74
136
  def quote_column_name(column_name)
75
- column_name.to_s
137
+ self.class.quote_column_name(column_name)
76
138
  end
77
139
 
78
- # Quotes the table name. Defaults to column name quoting.
140
+ # Quotes the table name.
79
141
  def quote_table_name(table_name)
80
- quote_column_name(table_name)
142
+ self.class.quote_table_name(table_name)
81
143
  end
82
144
 
83
145
  # Override to return the quoted table name for assignment. Defaults to
84
146
  # table quoting.
85
147
  #
86
- # This works for mysql2 where table.column can be used to
148
+ # This works for MySQL where table.column can be used to
87
149
  # resolve ambiguity.
88
150
  #
89
151
  # We override this in the sqlite3 and postgresql adapters to use only
@@ -121,14 +183,14 @@ module ActiveRecord
121
183
  # if the value is a Time responding to usec.
122
184
  def quoted_date(value)
123
185
  if value.acts_like?(:time)
124
- if ActiveRecord.default_timezone == :utc
186
+ if default_timezone == :utc
125
187
  value = value.getutc if !value.utc?
126
188
  else
127
189
  value = value.getlocal
128
190
  end
129
191
  end
130
192
 
131
- result = value.to_formatted_s(:db)
193
+ result = value.to_fs(:db)
132
194
  if value.respond_to?(:usec) && value.usec > 0
133
195
  result << "." << sprintf("%06d", value.usec)
134
196
  else
@@ -146,62 +208,18 @@ module ActiveRecord
146
208
  end
147
209
 
148
210
  def sanitize_as_sql_comment(value) # :nodoc:
149
- value.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "")
150
- end
151
-
152
- def column_name_matcher # :nodoc:
153
- COLUMN_NAME
154
- end
155
-
156
- def column_name_with_order_matcher # :nodoc:
157
- COLUMN_NAME_WITH_ORDER
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
158
221
  end
159
222
 
160
- # Regexp for column names (with or without a table name prefix).
161
- # Matches the following:
162
- #
163
- # "#{table_name}.#{column_name}"
164
- # "#{column_name}"
165
- COLUMN_NAME = /
166
- \A
167
- (
168
- (?:
169
- # table_name.column_name | function(one or no argument)
170
- ((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
171
- )
172
- (?:(?:\s+AS)?\s+\w+)?
173
- )
174
- (?:\s*,\s*\g<1>)*
175
- \z
176
- /ix
177
-
178
- # Regexp for column names with order (with or without a table name prefix,
179
- # with or without various order modifiers). Matches the following:
180
- #
181
- # "#{table_name}.#{column_name}"
182
- # "#{table_name}.#{column_name} #{direction}"
183
- # "#{table_name}.#{column_name} #{direction} NULLS FIRST"
184
- # "#{table_name}.#{column_name} NULLS LAST"
185
- # "#{column_name}"
186
- # "#{column_name} #{direction}"
187
- # "#{column_name} #{direction} NULLS FIRST"
188
- # "#{column_name} NULLS LAST"
189
- COLUMN_NAME_WITH_ORDER = /
190
- \A
191
- (
192
- (?:
193
- # table_name.column_name | function(one or no argument)
194
- ((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
195
- )
196
- (?:\s+ASC|\s+DESC)?
197
- (?:\s+NULLS\s+(?:FIRST|LAST))?
198
- )
199
- (?:\s*,\s*\g<1>)*
200
- \z
201
- /ix
202
-
203
- private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
204
-
205
223
  private
206
224
  def type_casted_binds(binds)
207
225
  binds.map do |value|
@@ -2,21 +2,22 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
+ # = Active Record Connection Adapters \Savepoints
5
6
  module Savepoints
6
7
  def current_savepoint_name
7
8
  current_transaction.savepoint_name
8
9
  end
9
10
 
10
11
  def create_savepoint(name = current_savepoint_name)
11
- execute("SAVEPOINT #{name}", "TRANSACTION")
12
+ internal_execute("SAVEPOINT #{name}", "TRANSACTION")
12
13
  end
13
14
 
14
15
  def exec_rollback_to_savepoint(name = current_savepoint_name)
15
- execute("ROLLBACK TO SAVEPOINT #{name}", "TRANSACTION")
16
+ internal_execute("ROLLBACK TO SAVEPOINT #{name}", "TRANSACTION")
16
17
  end
17
18
 
18
19
  def release_savepoint(name = current_savepoint_name)
19
- execute("RELEASE SAVEPOINT #{name}", "TRANSACTION")
20
+ internal_execute("RELEASE SAVEPOINT #{name}", "TRANSACTION")
20
21
  end
21
22
  end
22
23
  end
@@ -14,8 +14,10 @@ module ActiveRecord
14
14
  end
15
15
 
16
16
  delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql,
17
- :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys?,
17
+ :options_include_default?, :supports_indexes_in_create?, :use_foreign_keys?,
18
18
  :quoted_columns_for_index, :supports_partial_index?, :supports_check_constraints?,
19
+ :supports_index_include?, :supports_exclusion_constraints?, :supports_unique_constraints?,
20
+ :supports_nulls_not_distinct?,
19
21
  to: :@conn, private: true
20
22
 
21
23
  private
@@ -51,7 +53,7 @@ module ActiveRecord
51
53
  statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) })
52
54
  end
53
55
 
54
- if supports_foreign_keys?
56
+ if use_foreign_keys?
55
57
  statements.concat(o.foreign_keys.map { |fk| accept fk })
56
58
  end
57
59
 
@@ -59,6 +61,14 @@ module ActiveRecord
59
61
  statements.concat(o.check_constraints.map { |chk| accept chk })
60
62
  end
61
63
 
64
+ if supports_exclusion_constraints?
65
+ statements.concat(o.exclusion_constraints.map { |exc| accept exc })
66
+ end
67
+
68
+ if supports_unique_constraints?
69
+ statements.concat(o.unique_constraints.map { |exc| accept exc })
70
+ end
71
+
62
72
  create_sql << "(#{statements.join(', ')})" if statements.present?
63
73
  add_table_options!(create_sql, o)
64
74
  create_sql << " AS #{to_sql(o.as)}" if o.as
@@ -70,10 +80,12 @@ module ActiveRecord
70
80
  end
71
81
 
72
82
  def visit_ForeignKeyDefinition(o)
83
+ quoted_columns = Array(o.column).map { |c| quote_column_name(c) }
84
+ quoted_primary_keys = Array(o.primary_key).map { |c| quote_column_name(c) }
73
85
  sql = +<<~SQL
74
86
  CONSTRAINT #{quote_column_name(o.name)}
75
- FOREIGN KEY (#{quote_column_name(o.column)})
76
- REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)})
87
+ FOREIGN KEY (#{quoted_columns.join(", ")})
88
+ REFERENCES #{quote_table_name(o.to_table)} (#{quoted_primary_keys.join(", ")})
77
89
  SQL
78
90
  sql << " #{action_sql('DELETE', o.on_delete)}" if o.on_delete
79
91
  sql << " #{action_sql('UPDATE', o.on_update)}" if o.on_update
@@ -100,6 +112,8 @@ module ActiveRecord
100
112
  sql << "#{quote_column_name(index.name)} ON #{quote_table_name(index.table)}"
101
113
  sql << "USING #{index.using}" if supports_index_using? && index.using
102
114
  sql << "(#{quoted_columns(index)})"
115
+ sql << "INCLUDE (#{quoted_include_columns(index.include)})" if supports_index_include? && index.include
116
+ sql << "NULLS NOT DISTINCT" if supports_nulls_not_distinct? && index.nulls_not_distinct
103
117
  sql << "WHERE #{index.where}" if supports_partial_index? && index.where
104
118
 
105
119
  sql.join(" ")