activerecord 7.0.8 → 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 (277) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +530 -2004
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +29 -29
  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 +26 -14
  20. data/lib/active_record/associations/collection_proxy.rb +29 -11
  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 +21 -14
  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 +5 -5
  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 +1 -3
  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 +328 -471
  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 +131 -32
  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 +148 -33
  47. data/lib/active_record/attributes.rb +58 -45
  48. data/lib/active_record/autosave_association.rb +69 -37
  49. data/lib/active_record/base.rb +9 -5
  50. data/lib/active_record/callbacks.rb +10 -24
  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 -42
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
  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 +317 -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 +188 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
  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 +137 -11
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +306 -128
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +274 -125
  69. data/lib/active_record/connection_adapters/column.rb +9 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  92. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +368 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +364 -198
  96. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  97. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  101. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
  102. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  103. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
  105. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  106. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  107. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  108. data/lib/active_record/connection_adapters.rb +124 -1
  109. data/lib/active_record/connection_handling.rb +96 -104
  110. data/lib/active_record/core.rb +217 -174
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  113. data/lib/active_record/database_configurations/database_config.rb +26 -5
  114. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  115. data/lib/active_record/database_configurations/url_config.rb +37 -12
  116. data/lib/active_record/database_configurations.rb +87 -34
  117. data/lib/active_record/delegated_type.rb +39 -10
  118. data/lib/active_record/deprecator.rb +7 -0
  119. data/lib/active_record/destroy_association_async_job.rb +3 -1
  120. data/lib/active_record/dynamic_matchers.rb +2 -2
  121. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  122. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  123. data/lib/active_record/encryption/config.rb +25 -1
  124. data/lib/active_record/encryption/configurable.rb +12 -19
  125. data/lib/active_record/encryption/context.rb +10 -3
  126. data/lib/active_record/encryption/contexts.rb +5 -1
  127. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  128. data/lib/active_record/encryption/encryptable_record.rb +44 -20
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +45 -10
  130. data/lib/active_record/encryption/encryptor.rb +17 -2
  131. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  132. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  133. data/lib/active_record/encryption/key_generator.rb +12 -1
  134. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  135. data/lib/active_record/encryption/message_serializer.rb +6 -0
  136. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  137. data/lib/active_record/encryption/properties.rb +3 -3
  138. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  139. data/lib/active_record/encryption/scheme.rb +22 -21
  140. data/lib/active_record/encryption.rb +1 -0
  141. data/lib/active_record/enum.rb +122 -29
  142. data/lib/active_record/errors.rb +151 -31
  143. data/lib/active_record/explain.rb +21 -12
  144. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  145. data/lib/active_record/fixture_set/render_context.rb +2 -0
  146. data/lib/active_record/fixture_set/table_row.rb +29 -8
  147. data/lib/active_record/fixtures.rb +167 -97
  148. data/lib/active_record/future_result.rb +47 -8
  149. data/lib/active_record/gem_version.rb +3 -3
  150. data/lib/active_record/inheritance.rb +34 -18
  151. data/lib/active_record/insert_all.rb +72 -22
  152. data/lib/active_record/integration.rb +11 -8
  153. data/lib/active_record/internal_metadata.rb +124 -20
  154. data/lib/active_record/locking/optimistic.rb +8 -7
  155. data/lib/active_record/locking/pessimistic.rb +5 -2
  156. data/lib/active_record/log_subscriber.rb +18 -22
  157. data/lib/active_record/marshalling.rb +56 -0
  158. data/lib/active_record/message_pack.rb +124 -0
  159. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  160. data/lib/active_record/middleware/database_selector.rb +6 -8
  161. data/lib/active_record/middleware/shard_selector.rb +3 -1
  162. data/lib/active_record/migration/command_recorder.rb +106 -8
  163. data/lib/active_record/migration/compatibility.rb +147 -5
  164. data/lib/active_record/migration/default_strategy.rb +22 -0
  165. data/lib/active_record/migration/execution_strategy.rb +19 -0
  166. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  167. data/lib/active_record/migration.rb +234 -117
  168. data/lib/active_record/model_schema.rb +88 -103
  169. data/lib/active_record/nested_attributes.rb +35 -9
  170. data/lib/active_record/normalization.rb +163 -0
  171. data/lib/active_record/persistence.rb +168 -339
  172. data/lib/active_record/promise.rb +84 -0
  173. data/lib/active_record/query_cache.rb +19 -25
  174. data/lib/active_record/query_logs.rb +92 -52
  175. data/lib/active_record/query_logs_formatter.rb +41 -0
  176. data/lib/active_record/querying.rb +33 -8
  177. data/lib/active_record/railtie.rb +135 -86
  178. data/lib/active_record/railties/controller_runtime.rb +22 -7
  179. data/lib/active_record/railties/databases.rake +145 -154
  180. data/lib/active_record/railties/job_runtime.rb +23 -0
  181. data/lib/active_record/readonly_attributes.rb +32 -5
  182. data/lib/active_record/reflection.rb +259 -68
  183. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  184. data/lib/active_record/relation/batches.rb +196 -61
  185. data/lib/active_record/relation/calculations.rb +249 -92
  186. data/lib/active_record/relation/delegation.rb +30 -19
  187. data/lib/active_record/relation/finder_methods.rb +93 -18
  188. data/lib/active_record/relation/merger.rb +6 -6
  189. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  190. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  191. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  192. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  193. data/lib/active_record/relation/predicate_builder.rb +28 -16
  194. data/lib/active_record/relation/query_attribute.rb +2 -1
  195. data/lib/active_record/relation/query_methods.rb +548 -94
  196. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  197. data/lib/active_record/relation/spawn_methods.rb +5 -4
  198. data/lib/active_record/relation/where_clause.rb +7 -19
  199. data/lib/active_record/relation.rb +580 -90
  200. data/lib/active_record/result.rb +49 -48
  201. data/lib/active_record/runtime_registry.rb +63 -1
  202. data/lib/active_record/sanitization.rb +70 -25
  203. data/lib/active_record/schema.rb +8 -7
  204. data/lib/active_record/schema_dumper.rb +63 -14
  205. data/lib/active_record/schema_migration.rb +75 -24
  206. data/lib/active_record/scoping/default.rb +15 -5
  207. data/lib/active_record/scoping/named.rb +2 -2
  208. data/lib/active_record/scoping.rb +2 -1
  209. data/lib/active_record/secure_password.rb +60 -0
  210. data/lib/active_record/secure_token.rb +21 -3
  211. data/lib/active_record/signed_id.rb +27 -6
  212. data/lib/active_record/statement_cache.rb +7 -7
  213. data/lib/active_record/store.rb +8 -8
  214. data/lib/active_record/suppressor.rb +3 -1
  215. data/lib/active_record/table_metadata.rb +1 -1
  216. data/lib/active_record/tasks/database_tasks.rb +180 -119
  217. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  218. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  219. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  220. data/lib/active_record/test_fixtures.rb +170 -155
  221. data/lib/active_record/testing/query_assertions.rb +121 -0
  222. data/lib/active_record/timestamp.rb +31 -17
  223. data/lib/active_record/token_for.rb +123 -0
  224. data/lib/active_record/touch_later.rb +12 -7
  225. data/lib/active_record/transaction.rb +132 -0
  226. data/lib/active_record/transactions.rb +106 -24
  227. data/lib/active_record/translation.rb +0 -2
  228. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  229. data/lib/active_record/type/internal/timezone.rb +7 -2
  230. data/lib/active_record/type/serialized.rb +1 -3
  231. data/lib/active_record/type/time.rb +4 -0
  232. data/lib/active_record/type_caster/connection.rb +4 -4
  233. data/lib/active_record/validations/absence.rb +1 -1
  234. data/lib/active_record/validations/associated.rb +9 -3
  235. data/lib/active_record/validations/numericality.rb +5 -4
  236. data/lib/active_record/validations/presence.rb +5 -28
  237. data/lib/active_record/validations/uniqueness.rb +60 -11
  238. data/lib/active_record/validations.rb +12 -5
  239. data/lib/active_record/version.rb +1 -1
  240. data/lib/active_record.rb +247 -33
  241. data/lib/arel/alias_predication.rb +1 -1
  242. data/lib/arel/collectors/bind.rb +2 -0
  243. data/lib/arel/collectors/composite.rb +7 -0
  244. data/lib/arel/collectors/sql_string.rb +1 -1
  245. data/lib/arel/collectors/substitute_binds.rb +1 -1
  246. data/lib/arel/errors.rb +10 -0
  247. data/lib/arel/factory_methods.rb +4 -0
  248. data/lib/arel/nodes/binary.rb +6 -7
  249. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  250. data/lib/arel/nodes/cte.rb +36 -0
  251. data/lib/arel/nodes/fragments.rb +35 -0
  252. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  253. data/lib/arel/nodes/leading_join.rb +8 -0
  254. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  255. data/lib/arel/nodes/node.rb +115 -5
  256. data/lib/arel/nodes/sql_literal.rb +13 -0
  257. data/lib/arel/nodes/table_alias.rb +4 -0
  258. data/lib/arel/nodes.rb +6 -2
  259. data/lib/arel/predications.rb +3 -1
  260. data/lib/arel/select_manager.rb +1 -1
  261. data/lib/arel/table.rb +9 -5
  262. data/lib/arel/tree_manager.rb +8 -3
  263. data/lib/arel/update_manager.rb +2 -1
  264. data/lib/arel/visitors/dot.rb +1 -0
  265. data/lib/arel/visitors/mysql.rb +17 -5
  266. data/lib/arel/visitors/postgresql.rb +1 -12
  267. data/lib/arel/visitors/to_sql.rb +112 -34
  268. data/lib/arel/visitors/visitor.rb +2 -2
  269. data/lib/arel.rb +21 -3
  270. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  271. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  272. data/lib/rails/generators/active_record/migration.rb +3 -1
  273. data/lib/rails/generators/active_record/model/USAGE +113 -0
  274. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  275. metadata +56 -14
  276. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  277. data/lib/active_record/null_relation.rb +0 -63
@@ -5,20 +5,24 @@ require "concurrent/map"
5
5
  module ActiveRecord
6
6
  module ConnectionAdapters # :nodoc:
7
7
  module QueryCache
8
+ DEFAULT_SIZE = 100 # :nodoc:
9
+
8
10
  class << self
9
11
  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
+ dirties_query_cache base, :exec_query, :execute, :create, :insert, :update, :delete, :truncate,
13
+ :truncate_tables, :rollback_to_savepoint, :rollback_db_transaction, :restart_db_transaction,
14
+ :exec_insert_all
12
15
 
13
- base.set_callback :checkout, :after, :configure_query_cache!
14
- base.set_callback :checkin, :after, :disable_query_cache!
16
+ base.set_callback :checkin, :after, :unset_query_cache!
15
17
  end
16
18
 
17
19
  def dirties_query_cache(base, *method_names)
18
20
  method_names.each do |method_name|
19
21
  base.class_eval <<-end_code, __FILE__, __LINE__ + 1
20
- def #{method_name}(*)
21
- ActiveRecord::Base.clear_query_caches_for_current_thread
22
+ def #{method_name}(...)
23
+ if pool.dirties_query_cache
24
+ ActiveRecord::Base.clear_query_caches_for_current_thread
25
+ end
22
26
  super
23
27
  end
24
28
  end_code
@@ -26,59 +30,168 @@ module ActiveRecord
26
30
  end
27
31
  end
28
32
 
29
- module ConnectionPoolConfiguration
30
- def initialize(*)
33
+ class Store # :nodoc:
34
+ attr_accessor :enabled, :dirties
35
+ alias_method :enabled?, :enabled
36
+ alias_method :dirties?, :dirties
37
+
38
+ def initialize(max_size)
39
+ @map = {}
40
+ @max_size = max_size
41
+ @enabled = false
42
+ @dirties = true
43
+ end
44
+
45
+ def size
46
+ @map.size
47
+ end
48
+
49
+ def empty?
50
+ @map.empty?
51
+ end
52
+
53
+ def [](key)
54
+ return unless @enabled
55
+
56
+ if entry = @map.delete(key)
57
+ @map[key] = entry
58
+ end
59
+ end
60
+
61
+ def compute_if_absent(key)
62
+ return yield unless @enabled
63
+
64
+ if entry = @map.delete(key)
65
+ return @map[key] = entry
66
+ end
67
+
68
+ if @max_size && @map.size >= @max_size
69
+ @map.shift # evict the oldest entry
70
+ end
71
+
72
+ @map[key] ||= yield
73
+ end
74
+
75
+ def clear
76
+ @map.clear
77
+ self
78
+ end
79
+ end
80
+
81
+ module ConnectionPoolConfiguration # :nodoc:
82
+ def initialize(...)
83
+ super
84
+ @thread_query_caches = Concurrent::Map.new(initial_capacity: @size)
85
+ @query_cache_max_size = \
86
+ case query_cache = db_config&.query_cache
87
+ when 0, false
88
+ nil
89
+ when Integer
90
+ query_cache
91
+ when nil
92
+ DEFAULT_SIZE
93
+ end
94
+ end
95
+
96
+ def checkout_and_verify(connection)
31
97
  super
32
- @query_cache_enabled = Concurrent::Map.new { false }
98
+ connection.query_cache ||= query_cache
99
+ connection
100
+ end
101
+
102
+ # Disable the query cache within the block.
103
+ def disable_query_cache(dirties: true)
104
+ cache = query_cache
105
+ old_enabled, cache.enabled, old_dirties, cache.dirties = cache.enabled, false, cache.dirties, dirties
106
+ begin
107
+ yield
108
+ ensure
109
+ cache.enabled, cache.dirties = old_enabled, old_dirties
110
+ end
111
+ end
112
+
113
+ def enable_query_cache
114
+ cache = query_cache
115
+ old_enabled, cache.enabled, old_dirties, cache.dirties = cache.enabled, true, cache.dirties, true
116
+ begin
117
+ yield
118
+ ensure
119
+ cache.enabled, cache.dirties = old_enabled, old_dirties
120
+ end
33
121
  end
34
122
 
35
123
  def enable_query_cache!
36
- @query_cache_enabled[connection_cache_key(current_thread)] = true
37
- connection.enable_query_cache! if active_connection?
124
+ query_cache.enabled, query_cache.dirties = true, true
38
125
  end
39
126
 
40
127
  def disable_query_cache!
41
- @query_cache_enabled.delete connection_cache_key(current_thread)
42
- connection.disable_query_cache! if active_connection?
128
+ query_cache.enabled, query_cache.dirties = false, true
43
129
  end
44
130
 
45
131
  def query_cache_enabled
46
- @query_cache_enabled[connection_cache_key(current_thread)]
132
+ query_cache.enabled
133
+ end
134
+
135
+ def dirties_query_cache
136
+ query_cache.dirties
47
137
  end
138
+
139
+ def clear_query_cache
140
+ if @pinned_connection
141
+ # With transactional fixtures, and especially systems test
142
+ # another thread may use the same connection, but with a different
143
+ # query cache. So we must clear them all.
144
+ @thread_query_caches.each_value(&:clear)
145
+ else
146
+ query_cache.clear
147
+ end
148
+ end
149
+
150
+ def query_cache
151
+ @thread_query_caches.compute_if_absent(ActiveSupport::IsolatedExecutionState.context) do
152
+ Store.new(@query_cache_max_size)
153
+ end
154
+ end
155
+
156
+ private
157
+ def prune_thread_cache
158
+ dead_threads = @thread_query_caches.keys.reject(&:alive?)
159
+ dead_threads.each do |dead_thread|
160
+ @thread_query_caches.delete(dead_thread)
161
+ end
162
+ end
48
163
  end
49
164
 
50
- attr_reader :query_cache, :query_cache_enabled
165
+ attr_accessor :query_cache
51
166
 
52
167
  def initialize(*)
53
168
  super
54
- @query_cache = Hash.new { |h, sql| h[sql] = {} }
55
- @query_cache_enabled = false
169
+ @query_cache = nil
170
+ end
171
+
172
+ def query_cache_enabled
173
+ @query_cache&.enabled?
56
174
  end
57
175
 
58
176
  # 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
177
+ def cache(&block)
178
+ pool.enable_query_cache(&block)
65
179
  end
66
180
 
67
181
  def enable_query_cache!
68
- @query_cache_enabled = true
182
+ pool.enable_query_cache!
69
183
  end
70
184
 
71
- def disable_query_cache!
72
- @query_cache_enabled = false
73
- clear_query_cache
185
+ # Disable the query cache within the block.
186
+ #
187
+ # Set <tt>dirties: false</tt> to prevent query caches on all connections from being cleared by write operations.
188
+ # (By default, write operations dirty all connections' query caches in case they are replicas whose cache would now be outdated.)
189
+ def uncached(dirties: true, &block)
190
+ pool.disable_query_cache(dirties: dirties, &block)
74
191
  end
75
192
 
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
193
+ def disable_query_cache!
194
+ pool.disable_query_cache!
82
195
  end
83
196
 
84
197
  # Clears the query cache.
@@ -88,23 +201,22 @@ module ActiveRecord
88
201
  # the same SQL query and repeatedly return the same result each time, silently
89
202
  # undermining the randomness you were expecting.
90
203
  def clear_query_cache
91
- @lock.synchronize do
92
- @query_cache.clear
93
- end
204
+ pool.clear_query_cache
94
205
  end
95
206
 
96
- def select_all(arel, name = nil, binds = [], preparable: nil, async: false)
207
+ def select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false) # :nodoc:
97
208
  arel = arel_from_relation(arel)
98
209
 
99
210
  # If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
100
211
  # 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)
212
+ if @query_cache&.enabled? && !(arel.respond_to?(:locked) && arel.locked)
213
+ sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable)
103
214
 
104
215
  if async
105
- lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async)
216
+ result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry)
217
+ FutureResult.wrap(result)
106
218
  else
107
- cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async) }
219
+ cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry) }
108
220
  end
109
221
  else
110
222
  super
@@ -112,32 +224,48 @@ module ActiveRecord
112
224
  end
113
225
 
114
226
  private
227
+ def unset_query_cache!
228
+ @query_cache = nil
229
+ end
230
+
115
231
  def lookup_sql_cache(sql, name, binds)
232
+ key = binds.empty? ? sql : [sql, binds]
233
+
234
+ result = nil
116
235
  @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
236
+ result = @query_cache[key]
237
+ end
238
+
239
+ if result
240
+ ActiveSupport::Notifications.instrument(
241
+ "sql.active_record",
242
+ cache_notification_info(sql, name, binds)
243
+ )
124
244
  end
245
+
246
+ result
125
247
  end
126
248
 
127
249
  def cache_sql(sql, name, binds)
250
+ key = binds.empty? ? sql : [sql, binds]
251
+ result = nil
252
+ hit = true
253
+
128
254
  @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
255
+ result = @query_cache.compute_if_absent(key) do
256
+ hit = false
257
+ yield
258
+ end
259
+ end
260
+
261
+ if hit
262
+ ActiveSupport::Notifications.instrument(
263
+ "sql.active_record",
264
+ cache_notification_info(sql, name, binds)
265
+ )
140
266
  end
267
+
268
+ result.dup
141
269
  end
142
270
 
143
271
  # Database adapters can override this method to
@@ -149,13 +277,10 @@ module ActiveRecord
149
277
  type_casted_binds: -> { type_casted_binds(binds) },
150
278
  name: name,
151
279
  connection: self,
280
+ transaction: current_transaction.user_transaction.presence,
152
281
  cached: true
153
282
  }
154
283
  end
155
-
156
- def configure_query_cache!
157
- enable_query_cache! if pool.query_cache_enabled
158
- end
159
284
  end
160
285
  end
161
286
  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,7 +183,7 @@ 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
@@ -158,59 +220,6 @@ module ActiveRecord
158
220
  comment
159
221
  end
160
222
 
161
- def column_name_matcher # :nodoc:
162
- COLUMN_NAME
163
- end
164
-
165
- def column_name_with_order_matcher # :nodoc:
166
- COLUMN_NAME_WITH_ORDER
167
- end
168
-
169
- # Regexp for column names (with or without a table name prefix).
170
- # Matches the following:
171
- #
172
- # "#{table_name}.#{column_name}"
173
- # "#{column_name}"
174
- COLUMN_NAME = /
175
- \A
176
- (
177
- (?:
178
- # table_name.column_name | function(one or no argument)
179
- ((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
180
- )
181
- (?:(?:\s+AS)?\s+\w+)?
182
- )
183
- (?:\s*,\s*\g<1>)*
184
- \z
185
- /ix
186
-
187
- # Regexp for column names with order (with or without a table name prefix,
188
- # with or without various order modifiers). Matches the following:
189
- #
190
- # "#{table_name}.#{column_name}"
191
- # "#{table_name}.#{column_name} #{direction}"
192
- # "#{table_name}.#{column_name} #{direction} NULLS FIRST"
193
- # "#{table_name}.#{column_name} NULLS LAST"
194
- # "#{column_name}"
195
- # "#{column_name} #{direction}"
196
- # "#{column_name} #{direction} NULLS FIRST"
197
- # "#{column_name} NULLS LAST"
198
- COLUMN_NAME_WITH_ORDER = /
199
- \A
200
- (
201
- (?:
202
- # table_name.column_name | function(one or no argument)
203
- ((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
204
- )
205
- (?:\s+ASC|\s+DESC)?
206
- (?:\s+NULLS\s+(?:FIRST|LAST))?
207
- )
208
- (?:\s*,\s*\g<1>)*
209
- \z
210
- /ix
211
-
212
- private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
213
-
214
223
  private
215
224
  def type_casted_binds(binds)
216
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(" ")