activerecord 7.0.8.7 → 7.1.0.beta1

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 (227) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1339 -1572
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +15 -16
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +18 -3
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +17 -9
  15. data/lib/active_record/associations/collection_proxy.rb +16 -11
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency.rb +10 -8
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader.rb +12 -9
  23. data/lib/active_record/associations/singular_association.rb +1 -1
  24. data/lib/active_record/associations/through_association.rb +22 -11
  25. data/lib/active_record/associations.rb +193 -97
  26. data/lib/active_record/attribute_assignment.rb +0 -2
  27. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +40 -26
  29. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  30. data/lib/active_record/attribute_methods/query.rb +28 -16
  31. data/lib/active_record/attribute_methods/read.rb +18 -5
  32. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +105 -21
  35. data/lib/active_record/attributes.rb +3 -3
  36. data/lib/active_record/autosave_association.rb +55 -9
  37. data/lib/active_record/base.rb +7 -2
  38. data/lib/active_record/callbacks.rb +10 -24
  39. data/lib/active_record/coders/column_serializer.rb +61 -0
  40. data/lib/active_record/coders/json.rb +1 -1
  41. data/lib/active_record/coders/yaml_column.rb +70 -42
  42. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  45. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +109 -32
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  49. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  50. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  51. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  52. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  53. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -122
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +280 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +502 -91
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +200 -108
  57. data/lib/active_record/connection_adapters/column.rb +9 -0
  58. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  59. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
  61. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  62. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  63. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -12
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  66. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  67. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  68. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  69. data/lib/active_record/connection_adapters/postgresql/column.rb +1 -2
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -29
  71. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  72. data/lib/active_record/connection_adapters/postgresql/quoting.rb +9 -6
  73. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  74. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  75. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  76. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +42 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +351 -54
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +336 -168
  79. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  80. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  81. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +42 -36
  82. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  83. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  84. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  85. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +162 -77
  86. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  87. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  88. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  89. data/lib/active_record/connection_adapters.rb +3 -1
  90. data/lib/active_record/connection_handling.rb +71 -94
  91. data/lib/active_record/core.rb +128 -138
  92. data/lib/active_record/counter_cache.rb +46 -25
  93. data/lib/active_record/database_configurations/database_config.rb +9 -3
  94. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  95. data/lib/active_record/database_configurations/url_config.rb +17 -11
  96. data/lib/active_record/database_configurations.rb +86 -33
  97. data/lib/active_record/delegated_type.rb +8 -3
  98. data/lib/active_record/deprecator.rb +7 -0
  99. data/lib/active_record/destroy_association_async_job.rb +2 -0
  100. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  101. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  102. data/lib/active_record/encryption/config.rb +25 -1
  103. data/lib/active_record/encryption/configurable.rb +12 -19
  104. data/lib/active_record/encryption/context.rb +10 -3
  105. data/lib/active_record/encryption/contexts.rb +5 -1
  106. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  107. data/lib/active_record/encryption/encryptable_record.rb +36 -18
  108. data/lib/active_record/encryption/encrypted_attribute_type.rb +17 -6
  109. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -54
  110. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +2 -2
  111. data/lib/active_record/encryption/key_generator.rb +12 -1
  112. data/lib/active_record/encryption/message_serializer.rb +2 -0
  113. data/lib/active_record/encryption/properties.rb +3 -3
  114. data/lib/active_record/encryption/scheme.rb +19 -22
  115. data/lib/active_record/encryption.rb +1 -0
  116. data/lib/active_record/enum.rb +113 -26
  117. data/lib/active_record/errors.rb +89 -15
  118. data/lib/active_record/explain.rb +23 -3
  119. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  120. data/lib/active_record/fixture_set/render_context.rb +2 -0
  121. data/lib/active_record/fixture_set/table_row.rb +29 -8
  122. data/lib/active_record/fixtures.rb +119 -71
  123. data/lib/active_record/future_result.rb +30 -5
  124. data/lib/active_record/gem_version.rb +4 -4
  125. data/lib/active_record/inheritance.rb +30 -16
  126. data/lib/active_record/insert_all.rb +55 -8
  127. data/lib/active_record/integration.rb +8 -8
  128. data/lib/active_record/internal_metadata.rb +118 -30
  129. data/lib/active_record/locking/pessimistic.rb +5 -2
  130. data/lib/active_record/log_subscriber.rb +29 -12
  131. data/lib/active_record/marshalling.rb +56 -0
  132. data/lib/active_record/message_pack.rb +124 -0
  133. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  134. data/lib/active_record/middleware/database_selector.rb +5 -7
  135. data/lib/active_record/middleware/shard_selector.rb +3 -1
  136. data/lib/active_record/migration/command_recorder.rb +100 -4
  137. data/lib/active_record/migration/compatibility.rb +131 -5
  138. data/lib/active_record/migration/default_strategy.rb +23 -0
  139. data/lib/active_record/migration/execution_strategy.rb +19 -0
  140. data/lib/active_record/migration.rb +213 -109
  141. data/lib/active_record/model_schema.rb +47 -27
  142. data/lib/active_record/nested_attributes.rb +28 -3
  143. data/lib/active_record/normalization.rb +158 -0
  144. data/lib/active_record/persistence.rb +183 -33
  145. data/lib/active_record/promise.rb +84 -0
  146. data/lib/active_record/query_cache.rb +3 -21
  147. data/lib/active_record/query_logs.rb +77 -52
  148. data/lib/active_record/query_logs_formatter.rb +41 -0
  149. data/lib/active_record/querying.rb +15 -2
  150. data/lib/active_record/railtie.rb +107 -45
  151. data/lib/active_record/railties/controller_runtime.rb +10 -5
  152. data/lib/active_record/railties/databases.rake +139 -145
  153. data/lib/active_record/railties/job_runtime.rb +23 -0
  154. data/lib/active_record/readonly_attributes.rb +32 -5
  155. data/lib/active_record/reflection.rb +169 -45
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  157. data/lib/active_record/relation/batches.rb +190 -61
  158. data/lib/active_record/relation/calculations.rb +152 -63
  159. data/lib/active_record/relation/delegation.rb +22 -8
  160. data/lib/active_record/relation/finder_methods.rb +85 -15
  161. data/lib/active_record/relation/merger.rb +2 -0
  162. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  163. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  164. data/lib/active_record/relation/predicate_builder.rb +26 -14
  165. data/lib/active_record/relation/query_attribute.rb +2 -1
  166. data/lib/active_record/relation/query_methods.rb +351 -62
  167. data/lib/active_record/relation/spawn_methods.rb +18 -1
  168. data/lib/active_record/relation.rb +76 -35
  169. data/lib/active_record/result.rb +19 -5
  170. data/lib/active_record/runtime_registry.rb +10 -1
  171. data/lib/active_record/sanitization.rb +51 -11
  172. data/lib/active_record/schema.rb +2 -3
  173. data/lib/active_record/schema_dumper.rb +41 -7
  174. data/lib/active_record/schema_migration.rb +68 -33
  175. data/lib/active_record/scoping/default.rb +15 -5
  176. data/lib/active_record/scoping/named.rb +2 -2
  177. data/lib/active_record/scoping.rb +2 -1
  178. data/lib/active_record/secure_password.rb +60 -0
  179. data/lib/active_record/secure_token.rb +21 -3
  180. data/lib/active_record/signed_id.rb +7 -5
  181. data/lib/active_record/store.rb +8 -8
  182. data/lib/active_record/suppressor.rb +3 -1
  183. data/lib/active_record/table_metadata.rb +10 -1
  184. data/lib/active_record/tasks/database_tasks.rb +127 -105
  185. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  186. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  187. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -7
  188. data/lib/active_record/test_fixtures.rb +113 -96
  189. data/lib/active_record/timestamp.rb +26 -14
  190. data/lib/active_record/token_for.rb +113 -0
  191. data/lib/active_record/touch_later.rb +11 -6
  192. data/lib/active_record/transactions.rb +36 -10
  193. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  194. data/lib/active_record/type/internal/timezone.rb +7 -2
  195. data/lib/active_record/type/time.rb +4 -0
  196. data/lib/active_record/validations/absence.rb +1 -1
  197. data/lib/active_record/validations/numericality.rb +5 -4
  198. data/lib/active_record/validations/presence.rb +5 -28
  199. data/lib/active_record/validations/uniqueness.rb +47 -2
  200. data/lib/active_record/validations.rb +8 -4
  201. data/lib/active_record/version.rb +1 -1
  202. data/lib/active_record.rb +121 -16
  203. data/lib/arel/errors.rb +10 -0
  204. data/lib/arel/factory_methods.rb +4 -0
  205. data/lib/arel/nodes/binary.rb +6 -1
  206. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  207. data/lib/arel/nodes/cte.rb +36 -0
  208. data/lib/arel/nodes/fragments.rb +35 -0
  209. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  210. data/lib/arel/nodes/leading_join.rb +8 -0
  211. data/lib/arel/nodes/node.rb +111 -2
  212. data/lib/arel/nodes/sql_literal.rb +6 -0
  213. data/lib/arel/nodes/table_alias.rb +4 -0
  214. data/lib/arel/nodes.rb +4 -0
  215. data/lib/arel/predications.rb +2 -0
  216. data/lib/arel/table.rb +9 -5
  217. data/lib/arel/visitors/mysql.rb +8 -1
  218. data/lib/arel/visitors/to_sql.rb +81 -17
  219. data/lib/arel/visitors/visitor.rb +2 -2
  220. data/lib/arel.rb +16 -2
  221. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  222. data/lib/rails/generators/active_record/migration.rb +3 -1
  223. data/lib/rails/generators/active_record/model/USAGE +113 -0
  224. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  225. metadata +52 -17
  226. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  227. data/lib/active_record/null_relation.rb +0 -63
@@ -15,7 +15,12 @@ module ActiveRecord
15
15
  end
16
16
 
17
17
  def to_sql_and_binds(arel_or_sql_string, binds = [], preparable = nil) # :nodoc:
18
+ # Arel::TreeManager -> Arel::Node
18
19
  if arel_or_sql_string.respond_to?(:ast)
20
+ arel_or_sql_string = arel_or_sql_string.ast
21
+ end
22
+
23
+ if Arel.arel_node?(arel_or_sql_string) && !(String === arel_or_sql_string)
19
24
  unless binds.empty?
20
25
  raise "Passing bind parameters with an arel AST is forbidden. " \
21
26
  "The values must be stored on the AST directly"
@@ -25,7 +30,7 @@ module ActiveRecord
25
30
 
26
31
  if prepared_statements
27
32
  collector.preparable = true
28
- sql, binds = visitor.compile(arel_or_sql_string.ast, collector)
33
+ sql, binds = visitor.compile(arel_or_sql_string, collector)
29
34
 
30
35
  if binds.length > bind_params_length
31
36
  unprepared_statement do
@@ -34,7 +39,7 @@ module ActiveRecord
34
39
  end
35
40
  preparable = collector.preparable
36
41
  else
37
- sql = visitor.compile(arel_or_sql_string.ast, collector)
42
+ sql = visitor.compile(arel_or_sql_string, collector)
38
43
  end
39
44
  [sql.freeze, binds, preparable]
40
45
  else
@@ -65,18 +70,18 @@ module ActiveRecord
65
70
 
66
71
  select(sql, name, binds, prepare: prepared_statements && preparable, async: async && FutureResult::SelectAll)
67
72
  rescue ::RangeError
68
- ActiveRecord::Result.empty
73
+ ActiveRecord::Result.empty(async: async)
69
74
  end
70
75
 
71
76
  # Returns a record hash with the column names as keys and column values
72
77
  # as values.
73
- def select_one(arel, name = nil, binds = [])
74
- select_all(arel, name, binds).first
78
+ def select_one(arel, name = nil, binds = [], async: false)
79
+ select_all(arel, name, binds, async: async).then(&:first)
75
80
  end
76
81
 
77
82
  # Returns a single value from a record
78
- def select_value(arel, name = nil, binds = [])
79
- single_value_from_rows(select_rows(arel, name, binds))
83
+ def select_value(arel, name = nil, binds = [], async: false)
84
+ select_rows(arel, name, binds, async: async).then { |rows| single_value_from_rows(rows) }
80
85
  end
81
86
 
82
87
  # Returns an array of the values of the first column in a select:
@@ -87,8 +92,8 @@ module ActiveRecord
87
92
 
88
93
  # Returns an array of arrays containing the field values.
89
94
  # Order is the same as that returned by +columns+.
90
- def select_rows(arel, name = nil, binds = [])
91
- select_all(arel, name, binds).rows
95
+ def select_rows(arel, name = nil, binds = [], async: false)
96
+ select_all(arel, name, binds, async: async).then(&:rows)
92
97
  end
93
98
 
94
99
  def query_value(sql, name = nil) # :nodoc:
@@ -100,7 +105,7 @@ module ActiveRecord
100
105
  end
101
106
 
102
107
  def query(sql, name = nil) # :nodoc:
103
- exec_query(sql, name).rows
108
+ internal_exec_query(sql, name).rows
104
109
  end
105
110
 
106
111
  # Determines whether the SQL statement is a write query.
@@ -110,47 +115,63 @@ module ActiveRecord
110
115
 
111
116
  # Executes the SQL statement in the context of this connection and returns
112
117
  # the raw result from the connection adapter.
118
+ #
119
+ # Setting +allow_retry+ to true causes the db to reconnect and retry
120
+ # executing the SQL statement in case of a connection-related exception.
121
+ # This option should only be enabled for known idempotent queries.
122
+ #
123
+ # Note: the query is assumed to have side effects and the query cache
124
+ # will be cleared. If the query is read-only, consider using #select_all
125
+ # instead.
126
+ #
113
127
  # Note: depending on your database connector, the result returned by this
114
- # method may be manually memory managed. Consider using the exec_query
128
+ # method may be manually memory managed. Consider using #exec_query
115
129
  # wrapper instead.
116
- def execute(sql, name = nil)
117
- raise NotImplementedError
130
+ def execute(sql, name = nil, allow_retry: false)
131
+ internal_execute(sql, name, allow_retry: allow_retry)
118
132
  end
119
133
 
120
134
  # Executes +sql+ statement in the context of this connection using
121
135
  # +binds+ as the bind substitutes. +name+ is logged along with
122
136
  # the executed +sql+ statement.
137
+ #
138
+ # Note: the query is assumed to have side effects and the query cache
139
+ # will be cleared. If the query is read-only, consider using #select_all
140
+ # instead.
123
141
  def exec_query(sql, name = "SQL", binds = [], prepare: false)
124
- raise NotImplementedError
142
+ internal_exec_query(sql, name, binds, prepare: prepare)
125
143
  end
126
144
 
127
145
  # Executes insert +sql+ statement in the context of this connection using
128
146
  # +binds+ as the bind substitutes. +name+ is logged along with
129
147
  # the executed +sql+ statement.
130
- def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
131
- sql, binds = sql_for_insert(sql, pk, binds)
132
- exec_query(sql, name, binds)
148
+ # Some adapters support the `returning` keyword argument which allows to control the result of the query:
149
+ # `nil` is the default value and maintains default behavior. If an array of column names is passed -
150
+ # the result will contain values of the specified columns from the inserted row.
151
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil)
152
+ sql, binds = sql_for_insert(sql, pk, binds, returning)
153
+ internal_exec_query(sql, name, binds)
133
154
  end
134
155
 
135
156
  # Executes delete +sql+ statement in the context of this connection using
136
157
  # +binds+ as the bind substitutes. +name+ is logged along with
137
158
  # the executed +sql+ statement.
138
159
  def exec_delete(sql, name = nil, binds = [])
139
- exec_query(sql, name, binds)
160
+ internal_exec_query(sql, name, binds)
140
161
  end
141
162
 
142
163
  # Executes update +sql+ statement in the context of this connection using
143
164
  # +binds+ as the bind substitutes. +name+ is logged along with
144
165
  # the executed +sql+ statement.
145
166
  def exec_update(sql, name = nil, binds = [])
146
- exec_query(sql, name, binds)
167
+ internal_exec_query(sql, name, binds)
147
168
  end
148
169
 
149
170
  def exec_insert_all(sql, name) # :nodoc:
150
- exec_query(sql, name)
171
+ internal_exec_query(sql, name)
151
172
  end
152
173
 
153
- def explain(arel, binds = []) # :nodoc:
174
+ def explain(arel, binds = [], options = []) # :nodoc:
154
175
  raise NotImplementedError
155
176
  end
156
177
 
@@ -162,10 +183,14 @@ module ActiveRecord
162
183
  #
163
184
  # If the next id was calculated in advance (as in Oracle), it should be
164
185
  # passed in as +id_value+.
165
- def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
186
+ # Some adapters support the `returning` keyword argument which allows defining the return value of the method:
187
+ # `nil` is the default value and maintains default behavior. If an array of column names is passed -
188
+ # an array of is returned from the method representing values of the specified columns from the inserted row.
189
+ def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [], returning: nil)
166
190
  sql, binds = to_sql_and_binds(arel, binds)
167
- value = exec_insert(sql, name, binds, pk, sequence_name)
168
- id_value || last_inserted_id(value)
191
+ value = exec_insert(sql, name, binds, pk, sequence_name, returning: returning)
192
+ return id_value if id_value
193
+ returning.nil? ? last_inserted_id(value) : returning_column_values(value)
169
194
  end
170
195
  alias create insert
171
196
 
@@ -187,7 +212,7 @@ module ActiveRecord
187
212
  end
188
213
 
189
214
  def truncate_tables(*table_names) # :nodoc:
190
- table_names -= [schema_migration.table_name, InternalMetadata.table_name]
215
+ table_names -= [schema_migration.table_name, internal_metadata.table_name]
191
216
 
192
217
  return if table_names.empty?
193
218
 
@@ -304,8 +329,9 @@ module ActiveRecord
304
329
  # * You are joining an existing open transaction
305
330
  # * You are creating a nested (savepoint) transaction
306
331
  #
307
- # The mysql2 and postgresql adapters support setting the transaction
332
+ # The mysql2, trilogy, and postgresql adapters support setting the transaction
308
333
  # isolation level.
334
+ # :args: (requires_new: nil, isolation: nil, &block)
309
335
  def transaction(requires_new: nil, isolation: nil, joinable: true, &block)
310
336
  if !requires_new && current_transaction.joinable?
311
337
  if isolation
@@ -323,7 +349,8 @@ module ActiveRecord
323
349
 
324
350
  delegate :within_new_transaction, :open_transactions, :current_transaction, :begin_transaction,
325
351
  :commit_transaction, :rollback_transaction, :materialize_transactions,
326
- :disable_lazy_transactions!, :enable_lazy_transactions!, to: :transaction_manager
352
+ :disable_lazy_transactions!, :enable_lazy_transactions!, :dirty_current_transaction,
353
+ to: :transaction_manager
327
354
 
328
355
  def mark_transaction_written_if_write(sql) # :nodoc:
329
356
  transaction = current_transaction
@@ -336,8 +363,24 @@ module ActiveRecord
336
363
  current_transaction.open?
337
364
  end
338
365
 
339
- def reset_transaction # :nodoc:
366
+ def reset_transaction(restore: false) # :nodoc:
367
+ # Store the existing transaction state to the side
368
+ old_state = @transaction_manager if restore && @transaction_manager&.restorable?
369
+
340
370
  @transaction_manager = ConnectionAdapters::TransactionManager.new(self)
371
+
372
+ if block_given?
373
+ # Reconfigure the connection without any transaction state in the way
374
+ result = yield
375
+
376
+ # Now the connection's fully established, we can swap back
377
+ if old_state
378
+ @transaction_manager = old_state
379
+ @transaction_manager.restore_transactions
380
+ end
381
+
382
+ result
383
+ end
341
384
  end
342
385
 
343
386
  # Register a record with the current transaction so that its after_commit and after_rollback callbacks
@@ -372,10 +415,18 @@ module ActiveRecord
372
415
  # done if the transaction block raises an exception or returns false.
373
416
  def rollback_db_transaction
374
417
  exec_rollback_db_transaction
418
+ rescue ActiveRecord::ConnectionNotEstablished, ActiveRecord::ConnectionFailed
419
+ # Connection's gone; that counts as a rollback
375
420
  end
376
421
 
377
422
  def exec_rollback_db_transaction() end # :nodoc:
378
423
 
424
+ def restart_db_transaction
425
+ exec_restart_db_transaction
426
+ end
427
+
428
+ def exec_restart_db_transaction() end # :nodoc:
429
+
379
430
  def rollback_to_savepoint(name = nil)
380
431
  exec_rollback_to_savepoint(name)
381
432
  end
@@ -393,7 +444,7 @@ module ActiveRecord
393
444
  # something beyond a simple insert (e.g. Oracle).
394
445
  # Most of adapters should implement +insert_fixtures_set+ that leverages bulk SQL insert.
395
446
  # We keep this method to provide fallback
396
- # for databases like sqlite that do not support bulk inserts.
447
+ # for databases like SQLite that do not support bulk inserts.
397
448
  def insert_fixture(fixture, table_name)
398
449
  execute(build_fixture_sql(Array.wrap(fixture), table_name), "Fixture Insert")
399
450
  end
@@ -454,13 +505,30 @@ module ActiveRecord
454
505
  HIGH_PRECISION_CURRENT_TIMESTAMP
455
506
  end
456
507
 
508
+ def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
509
+ raise NotImplementedError
510
+ end
511
+
457
512
  private
513
+ def internal_execute(sql, name = "SCHEMA", allow_retry: false, materialize_transactions: true)
514
+ sql = transform_query(sql)
515
+ check_if_write_query(sql)
516
+
517
+ mark_transaction_written_if_write(sql)
518
+
519
+ raw_execute(sql, name, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
520
+ end
521
+
458
522
  def execute_batch(statements, name = nil)
459
523
  statements.each do |statement|
460
- execute(statement, name)
524
+ internal_execute(statement, name)
461
525
  end
462
526
  end
463
527
 
528
+ def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
529
+ raise NotImplementedError
530
+ end
531
+
464
532
  DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
465
533
  private_constant :DEFAULT_INSERT_VALUE
466
534
 
@@ -557,10 +625,15 @@ module ActiveRecord
557
625
  return future_result
558
626
  end
559
627
 
560
- exec_query(sql, name, binds, prepare: prepare)
628
+ result = internal_exec_query(sql, name, binds, prepare: prepare)
629
+ if async
630
+ FutureResult::Complete.new(result)
631
+ else
632
+ result
633
+ end
561
634
  end
562
635
 
563
- def sql_for_insert(sql, pk, binds)
636
+ def sql_for_insert(sql, _pk, binds, _returning)
564
637
  [sql, binds]
565
638
  end
566
639
 
@@ -568,6 +641,10 @@ module ActiveRecord
568
641
  single_value_from_rows(result.rows)
569
642
  end
570
643
 
644
+ def returning_column_values(result)
645
+ [last_inserted_id(result)]
646
+ end
647
+
571
648
  def single_value_from_rows(rows)
572
649
  row = rows.first
573
650
  row && row.first
@@ -5,10 +5,13 @@ 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
16
  base.set_callback :checkout, :after, :configure_query_cache!
14
17
  base.set_callback :checkin, :after, :disable_query_cache!
@@ -17,7 +20,7 @@ module ActiveRecord
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}(*)
23
+ def #{method_name}(...)
21
24
  ActiveRecord::Base.clear_query_caches_for_current_thread
22
25
  super
23
26
  end
@@ -51,8 +54,9 @@ module ActiveRecord
51
54
 
52
55
  def initialize(*)
53
56
  super
54
- @query_cache = Hash.new { |h, sql| h[sql] = {} }
57
+ @query_cache = {}
55
58
  @query_cache_enabled = false
59
+ @query_cache_max_size = nil
56
60
  end
57
61
 
58
62
  # Enable the query cache within the block.
@@ -93,7 +97,7 @@ module ActiveRecord
93
97
  end
94
98
  end
95
99
 
96
- def select_all(arel, name = nil, binds = [], preparable: nil, async: false)
100
+ def select_all(arel, name = nil, binds = [], preparable: nil, async: false) # :nodoc:
97
101
  arel = arel_from_relation(arel)
98
102
 
99
103
  # If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
@@ -113,31 +117,52 @@ module ActiveRecord
113
117
 
114
118
  private
115
119
  def lookup_sql_cache(sql, name, binds)
120
+ key = binds.empty? ? sql : [sql, binds]
121
+ hit = false
122
+ result = nil
123
+
116
124
  @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]
125
+ if (result = @query_cache.delete(key))
126
+ hit = true
127
+ @query_cache[key] = result
123
128
  end
124
129
  end
130
+
131
+ if hit
132
+ ActiveSupport::Notifications.instrument(
133
+ "sql.active_record",
134
+ cache_notification_info(sql, name, binds)
135
+ )
136
+
137
+ result
138
+ end
125
139
  end
126
140
 
127
141
  def cache_sql(sql, name, binds)
142
+ key = binds.empty? ? sql : [sql, binds]
143
+ result = nil
144
+ hit = false
145
+
128
146
  @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
147
+ if (result = @query_cache.delete(key))
148
+ hit = true
149
+ @query_cache[key] = result
150
+ else
151
+ result = @query_cache[key] = yield
152
+ if @query_cache_max_size && @query_cache.size > @query_cache_max_size
153
+ @query_cache.shift
138
154
  end
139
- result.dup
155
+ end
140
156
  end
157
+
158
+ if hit
159
+ ActiveSupport::Notifications.instrument(
160
+ "sql.active_record",
161
+ cache_notification_info(sql, name, binds)
162
+ )
163
+ end
164
+
165
+ result.dup
141
166
  end
142
167
 
143
168
  # Database adapters can override this method to
@@ -154,7 +179,20 @@ module ActiveRecord
154
179
  end
155
180
 
156
181
  def configure_query_cache!
157
- enable_query_cache! if pool.query_cache_enabled
182
+ case query_cache = pool.db_config.query_cache
183
+ when 0, false
184
+ return
185
+ when Integer
186
+ @query_cache_max_size = query_cache
187
+ when nil
188
+ @query_cache_max_size = DEFAULT_SIZE
189
+ else
190
+ @query_cache_max_size = nil # no limit
191
+ end
192
+
193
+ if pool.query_cache_enabled
194
+ enable_query_cache!
195
+ end
158
196
  end
159
197
  end
160
198
  end
@@ -5,6 +5,7 @@ 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
9
10
  # Quotes the column value to help prevent
10
11
  # {SQL injection attacks}[https://en.wikipedia.org/wiki/SQL_injection].
@@ -17,11 +18,14 @@ module ActiveRecord
17
18
  when nil then "NULL"
18
19
  # BigDecimals need to be put in a non-normalized form and quoted.
19
20
  when BigDecimal then value.to_s("F")
20
- when Numeric, ActiveSupport::Duration then value.to_s
21
+ when Numeric then value.to_s
21
22
  when Type::Binary::Data then quoted_binary(value)
22
23
  when Type::Time::Value then "'#{quoted_time(value)}'"
23
24
  when Date, Time then "'#{quoted_date(value)}'"
24
25
  when Class then "'#{value}'"
26
+ when ActiveSupport::Duration
27
+ warn_quote_duration_deprecated
28
+ value.to_s
25
29
  else raise TypeError, "can't quote #{value.class.name}"
26
30
  end
27
31
  end
@@ -47,8 +51,23 @@ module ActiveRecord
47
51
  # Quote a value to be used as a bound parameter of unknown type. For example,
48
52
  # MySQL might perform dangerous castings when comparing a string to a number,
49
53
  # so this method will cast numbers to string.
54
+ #
55
+ # Deprecated: Consider `Arel.sql("... ? ...", value)` or
56
+ # +sanitize_sql+ instead.
50
57
  def quote_bound_value(value)
51
- quote(value)
58
+ ActiveRecord.deprecator.warn(<<~MSG.squish)
59
+ #quote_bound_value is deprecated and will be removed in Rails 7.2.
60
+ Consider Arel.sql(".. ? ..", value) or #sanitize_sql instead.
61
+ MSG
62
+
63
+ quote(cast_bound_value(value))
64
+ end
65
+
66
+ # Cast a value to be used as a bound parameter of unknown type. For example,
67
+ # MySQL might perform dangerous castings when comparing a string to a number,
68
+ # so this method will cast numbers to string.
69
+ def cast_bound_value(value) # :nodoc:
70
+ value
52
71
  end
53
72
 
54
73
  # If you are having to call this function, you are likely doing something
@@ -83,7 +102,7 @@ module ActiveRecord
83
102
  # Override to return the quoted table name for assignment. Defaults to
84
103
  # table quoting.
85
104
  #
86
- # This works for mysql2 where table.column can be used to
105
+ # This works for MySQL where table.column can be used to
87
106
  # resolve ambiguity.
88
107
  #
89
108
  # We override this in the sqlite3 and postgresql adapters to use only
@@ -121,7 +140,7 @@ module ActiveRecord
121
140
  # if the value is a Time responding to usec.
122
141
  def quoted_date(value)
123
142
  if value.acts_like?(:time)
124
- if ActiveRecord.default_timezone == :utc
143
+ if default_timezone == :utc
125
144
  value = value.getutc if !value.utc?
126
145
  else
127
146
  value = value.getlocal
@@ -176,7 +195,7 @@ module ActiveRecord
176
195
  (
177
196
  (?:
178
197
  # table_name.column_name | function(one or no argument)
179
- ((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
198
+ ((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
180
199
  )
181
200
  (?:(?:\s+AS)?\s+\w+)?
182
201
  )
@@ -200,7 +219,7 @@ module ActiveRecord
200
219
  (
201
220
  (?:
202
221
  # table_name.column_name | function(one or no argument)
203
- ((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
222
+ ((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
204
223
  )
205
224
  (?:\s+ASC|\s+DESC)?
206
225
  (?:\s+NULLS\s+(?:FIRST|LAST))?
@@ -225,6 +244,22 @@ module ActiveRecord
225
244
  def lookup_cast_type(sql_type)
226
245
  type_map.lookup(sql_type)
227
246
  end
247
+
248
+ def warn_quote_duration_deprecated
249
+ ActiveRecord.deprecator.warn(<<~MSG)
250
+ Using ActiveSupport::Duration as an interpolated bind parameter in a SQL
251
+ string template is deprecated. To avoid this warning, you should explicitly
252
+ convert the duration to a more specific database type. For example, if you
253
+ want to use a duration as an integer number of seconds:
254
+ ```
255
+ Record.where("duration = ?", 1.hour.to_i)
256
+ ```
257
+ If you want to use a duration as an ISO 8601 string:
258
+ ```
259
+ Record.where("duration = ?", 1.hour.iso8601)
260
+ ```
261
+ MSG
262
+ end
228
263
  end
229
264
  end
230
265
  end
@@ -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_keys?,
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_keys?
69
+ statements.concat(o.unique_keys.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(" ")