activerecord 7.0.8 → 7.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (231) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1536 -1454
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +16 -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 +20 -4
  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 +15 -9
  15. data/lib/active_record/associations/collection_proxy.rb +15 -10
  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 +31 -7
  22. data/lib/active_record/associations/preloader.rb +13 -10
  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 +313 -217
  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 +52 -34
  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 +74 -51
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  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 -124
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -91
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -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 +18 -13
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -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 +14 -3
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
  71. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  72. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  73. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
  75. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  76. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  77. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  78. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  79. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +361 -60
  80. data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
  81. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  82. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  83. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  84. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  85. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  86. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  87. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +209 -79
  88. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  89. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  90. data/lib/active_record/connection_adapters/trilogy_adapter.rb +262 -0
  91. data/lib/active_record/connection_adapters.rb +3 -1
  92. data/lib/active_record/connection_handling.rb +72 -95
  93. data/lib/active_record/core.rb +175 -153
  94. data/lib/active_record/counter_cache.rb +46 -25
  95. data/lib/active_record/database_configurations/database_config.rb +9 -3
  96. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  97. data/lib/active_record/database_configurations/url_config.rb +17 -11
  98. data/lib/active_record/database_configurations.rb +86 -33
  99. data/lib/active_record/delegated_type.rb +9 -4
  100. data/lib/active_record/deprecator.rb +7 -0
  101. data/lib/active_record/destroy_association_async_job.rb +2 -0
  102. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  103. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  104. data/lib/active_record/encryption/config.rb +25 -1
  105. data/lib/active_record/encryption/configurable.rb +12 -19
  106. data/lib/active_record/encryption/context.rb +10 -3
  107. data/lib/active_record/encryption/contexts.rb +5 -1
  108. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  109. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +21 -6
  111. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  112. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  113. data/lib/active_record/encryption/key_generator.rb +12 -1
  114. data/lib/active_record/encryption/message_serializer.rb +2 -0
  115. data/lib/active_record/encryption/properties.rb +3 -3
  116. data/lib/active_record/encryption/scheme.rb +19 -22
  117. data/lib/active_record/encryption.rb +1 -0
  118. data/lib/active_record/enum.rb +112 -28
  119. data/lib/active_record/errors.rb +112 -18
  120. data/lib/active_record/explain.rb +23 -3
  121. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  122. data/lib/active_record/fixture_set/render_context.rb +2 -0
  123. data/lib/active_record/fixture_set/table_row.rb +29 -8
  124. data/lib/active_record/fixtures.rb +135 -71
  125. data/lib/active_record/future_result.rb +31 -5
  126. data/lib/active_record/gem_version.rb +3 -3
  127. data/lib/active_record/inheritance.rb +30 -16
  128. data/lib/active_record/insert_all.rb +57 -10
  129. data/lib/active_record/integration.rb +8 -8
  130. data/lib/active_record/internal_metadata.rb +120 -30
  131. data/lib/active_record/locking/pessimistic.rb +5 -2
  132. data/lib/active_record/log_subscriber.rb +29 -12
  133. data/lib/active_record/marshalling.rb +56 -0
  134. data/lib/active_record/message_pack.rb +124 -0
  135. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  136. data/lib/active_record/middleware/database_selector.rb +6 -8
  137. data/lib/active_record/middleware/shard_selector.rb +3 -1
  138. data/lib/active_record/migration/command_recorder.rb +104 -5
  139. data/lib/active_record/migration/compatibility.rb +139 -5
  140. data/lib/active_record/migration/default_strategy.rb +23 -0
  141. data/lib/active_record/migration/execution_strategy.rb +19 -0
  142. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  143. data/lib/active_record/migration.rb +219 -111
  144. data/lib/active_record/model_schema.rb +64 -44
  145. data/lib/active_record/nested_attributes.rb +24 -6
  146. data/lib/active_record/normalization.rb +167 -0
  147. data/lib/active_record/persistence.rb +188 -37
  148. data/lib/active_record/promise.rb +84 -0
  149. data/lib/active_record/query_cache.rb +3 -21
  150. data/lib/active_record/query_logs.rb +77 -52
  151. data/lib/active_record/query_logs_formatter.rb +41 -0
  152. data/lib/active_record/querying.rb +15 -2
  153. data/lib/active_record/railtie.rb +109 -47
  154. data/lib/active_record/railties/controller_runtime.rb +12 -6
  155. data/lib/active_record/railties/databases.rake +142 -148
  156. data/lib/active_record/railties/job_runtime.rb +23 -0
  157. data/lib/active_record/readonly_attributes.rb +32 -5
  158. data/lib/active_record/reflection.rb +174 -44
  159. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  160. data/lib/active_record/relation/batches.rb +190 -61
  161. data/lib/active_record/relation/calculations.rb +187 -63
  162. data/lib/active_record/relation/delegation.rb +23 -9
  163. data/lib/active_record/relation/finder_methods.rb +77 -16
  164. data/lib/active_record/relation/merger.rb +2 -0
  165. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  166. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  167. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  168. data/lib/active_record/relation/predicate_builder.rb +26 -14
  169. data/lib/active_record/relation/query_attribute.rb +2 -1
  170. data/lib/active_record/relation/query_methods.rb +352 -63
  171. data/lib/active_record/relation/spawn_methods.rb +18 -1
  172. data/lib/active_record/relation.rb +91 -35
  173. data/lib/active_record/result.rb +19 -5
  174. data/lib/active_record/runtime_registry.rb +24 -1
  175. data/lib/active_record/sanitization.rb +51 -11
  176. data/lib/active_record/schema.rb +2 -3
  177. data/lib/active_record/schema_dumper.rb +46 -7
  178. data/lib/active_record/schema_migration.rb +68 -33
  179. data/lib/active_record/scoping/default.rb +15 -5
  180. data/lib/active_record/scoping/named.rb +2 -2
  181. data/lib/active_record/scoping.rb +2 -1
  182. data/lib/active_record/secure_password.rb +60 -0
  183. data/lib/active_record/secure_token.rb +21 -3
  184. data/lib/active_record/signed_id.rb +7 -5
  185. data/lib/active_record/store.rb +8 -8
  186. data/lib/active_record/suppressor.rb +3 -1
  187. data/lib/active_record/table_metadata.rb +10 -1
  188. data/lib/active_record/tasks/database_tasks.rb +127 -105
  189. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  190. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  191. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  192. data/lib/active_record/test_fixtures.rb +113 -96
  193. data/lib/active_record/timestamp.rb +27 -15
  194. data/lib/active_record/token_for.rb +113 -0
  195. data/lib/active_record/touch_later.rb +11 -6
  196. data/lib/active_record/transactions.rb +36 -10
  197. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  198. data/lib/active_record/type/internal/timezone.rb +7 -2
  199. data/lib/active_record/type/time.rb +4 -0
  200. data/lib/active_record/validations/absence.rb +1 -1
  201. data/lib/active_record/validations/numericality.rb +5 -4
  202. data/lib/active_record/validations/presence.rb +5 -28
  203. data/lib/active_record/validations/uniqueness.rb +47 -2
  204. data/lib/active_record/validations.rb +8 -4
  205. data/lib/active_record/version.rb +1 -1
  206. data/lib/active_record.rb +121 -16
  207. data/lib/arel/errors.rb +10 -0
  208. data/lib/arel/factory_methods.rb +4 -0
  209. data/lib/arel/nodes/binary.rb +6 -1
  210. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  211. data/lib/arel/nodes/cte.rb +36 -0
  212. data/lib/arel/nodes/fragments.rb +35 -0
  213. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  214. data/lib/arel/nodes/leading_join.rb +8 -0
  215. data/lib/arel/nodes/node.rb +111 -2
  216. data/lib/arel/nodes/sql_literal.rb +6 -0
  217. data/lib/arel/nodes/table_alias.rb +4 -0
  218. data/lib/arel/nodes.rb +4 -0
  219. data/lib/arel/predications.rb +2 -0
  220. data/lib/arel/table.rb +9 -5
  221. data/lib/arel/visitors/mysql.rb +8 -1
  222. data/lib/arel/visitors/to_sql.rb +81 -17
  223. data/lib/arel/visitors/visitor.rb +2 -2
  224. data/lib/arel.rb +16 -2
  225. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  226. data/lib/rails/generators/active_record/migration.rb +3 -1
  227. data/lib/rails/generators/active_record/model/USAGE +113 -0
  228. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  229. metadata +47 -11
  230. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  231. data/lib/active_record/null_relation.rb +0 -63
@@ -7,6 +7,11 @@ module ActiveRecord
7
7
  64
8
8
  end
9
9
 
10
+ # Returns the maximum length of a table name.
11
+ def table_name_length
12
+ max_identifier_length
13
+ end
14
+
10
15
  # Returns the maximum length of a table alias.
11
16
  def table_alias_length
12
17
  max_identifier_length
@@ -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,9 +183,15 @@ 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)
191
+ value = exec_insert(sql, name, binds, pk, sequence_name, returning: returning)
192
+
193
+ return returning_column_values(value) unless returning.nil?
194
+
168
195
  id_value || last_inserted_id(value)
169
196
  end
170
197
  alias create insert
@@ -187,7 +214,7 @@ module ActiveRecord
187
214
  end
188
215
 
189
216
  def truncate_tables(*table_names) # :nodoc:
190
- table_names -= [schema_migration.table_name, InternalMetadata.table_name]
217
+ table_names -= [schema_migration.table_name, internal_metadata.table_name]
191
218
 
192
219
  return if table_names.empty?
193
220
 
@@ -304,8 +331,9 @@ module ActiveRecord
304
331
  # * You are joining an existing open transaction
305
332
  # * You are creating a nested (savepoint) transaction
306
333
  #
307
- # The mysql2 and postgresql adapters support setting the transaction
334
+ # The mysql2, trilogy, and postgresql adapters support setting the transaction
308
335
  # isolation level.
336
+ # :args: (requires_new: nil, isolation: nil, &block)
309
337
  def transaction(requires_new: nil, isolation: nil, joinable: true, &block)
310
338
  if !requires_new && current_transaction.joinable?
311
339
  if isolation
@@ -323,7 +351,8 @@ module ActiveRecord
323
351
 
324
352
  delegate :within_new_transaction, :open_transactions, :current_transaction, :begin_transaction,
325
353
  :commit_transaction, :rollback_transaction, :materialize_transactions,
326
- :disable_lazy_transactions!, :enable_lazy_transactions!, to: :transaction_manager
354
+ :disable_lazy_transactions!, :enable_lazy_transactions!, :dirty_current_transaction,
355
+ to: :transaction_manager
327
356
 
328
357
  def mark_transaction_written_if_write(sql) # :nodoc:
329
358
  transaction = current_transaction
@@ -336,8 +365,24 @@ module ActiveRecord
336
365
  current_transaction.open?
337
366
  end
338
367
 
339
- def reset_transaction # :nodoc:
368
+ def reset_transaction(restore: false) # :nodoc:
369
+ # Store the existing transaction state to the side
370
+ old_state = @transaction_manager if restore && @transaction_manager&.restorable?
371
+
340
372
  @transaction_manager = ConnectionAdapters::TransactionManager.new(self)
373
+
374
+ if block_given?
375
+ # Reconfigure the connection without any transaction state in the way
376
+ result = yield
377
+
378
+ # Now the connection's fully established, we can swap back
379
+ if old_state
380
+ @transaction_manager = old_state
381
+ @transaction_manager.restore_transactions
382
+ end
383
+
384
+ result
385
+ end
341
386
  end
342
387
 
343
388
  # Register a record with the current transaction so that its after_commit and after_rollback callbacks
@@ -372,10 +417,18 @@ module ActiveRecord
372
417
  # done if the transaction block raises an exception or returns false.
373
418
  def rollback_db_transaction
374
419
  exec_rollback_db_transaction
420
+ rescue ActiveRecord::ConnectionNotEstablished, ActiveRecord::ConnectionFailed
421
+ # Connection's gone; that counts as a rollback
375
422
  end
376
423
 
377
424
  def exec_rollback_db_transaction() end # :nodoc:
378
425
 
426
+ def restart_db_transaction
427
+ exec_restart_db_transaction
428
+ end
429
+
430
+ def exec_restart_db_transaction() end # :nodoc:
431
+
379
432
  def rollback_to_savepoint(name = nil)
380
433
  exec_rollback_to_savepoint(name)
381
434
  end
@@ -393,7 +446,7 @@ module ActiveRecord
393
446
  # something beyond a simple insert (e.g. Oracle).
394
447
  # Most of adapters should implement +insert_fixtures_set+ that leverages bulk SQL insert.
395
448
  # We keep this method to provide fallback
396
- # for databases like sqlite that do not support bulk inserts.
449
+ # for databases like SQLite that do not support bulk inserts.
397
450
  def insert_fixture(fixture, table_name)
398
451
  execute(build_fixture_sql(Array.wrap(fixture), table_name), "Fixture Insert")
399
452
  end
@@ -454,13 +507,30 @@ module ActiveRecord
454
507
  HIGH_PRECISION_CURRENT_TIMESTAMP
455
508
  end
456
509
 
510
+ def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
511
+ raise NotImplementedError
512
+ end
513
+
457
514
  private
515
+ def internal_execute(sql, name = "SCHEMA", allow_retry: false, materialize_transactions: true)
516
+ sql = transform_query(sql)
517
+ check_if_write_query(sql)
518
+
519
+ mark_transaction_written_if_write(sql)
520
+
521
+ raw_execute(sql, name, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
522
+ end
523
+
458
524
  def execute_batch(statements, name = nil)
459
525
  statements.each do |statement|
460
- execute(statement, name)
526
+ internal_execute(statement, name)
461
527
  end
462
528
  end
463
529
 
530
+ def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
531
+ raise NotImplementedError
532
+ end
533
+
464
534
  DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
465
535
  private_constant :DEFAULT_INSERT_VALUE
466
536
 
@@ -557,10 +627,28 @@ module ActiveRecord
557
627
  return future_result
558
628
  end
559
629
 
560
- exec_query(sql, name, binds, prepare: prepare)
630
+ result = internal_exec_query(sql, name, binds, prepare: prepare)
631
+ if async
632
+ FutureResult::Complete.new(result)
633
+ else
634
+ result
635
+ end
561
636
  end
562
637
 
563
- def sql_for_insert(sql, pk, binds)
638
+ def sql_for_insert(sql, pk, binds, returning) # :nodoc:
639
+ if supports_insert_returning?
640
+ if pk.nil?
641
+ # Extract the table from the insert sql. Yuck.
642
+ table_ref = extract_table_ref_from_insert_sql(sql)
643
+ pk = primary_key(table_ref) if table_ref
644
+ end
645
+
646
+ returning_columns = returning || Array(pk)
647
+
648
+ returning_columns_statement = returning_columns.map { |c| quote_column_name(c) }.join(", ")
649
+ sql = "#{sql} RETURNING #{returning_columns_statement}" if returning_columns.any?
650
+ end
651
+
564
652
  [sql, binds]
565
653
  end
566
654
 
@@ -568,6 +656,10 @@ module ActiveRecord
568
656
  single_value_from_rows(result.rows)
569
657
  end
570
658
 
659
+ def returning_column_values(result)
660
+ [last_inserted_id(result)]
661
+ end
662
+
571
663
  def single_value_from_rows(rows)
572
664
  row = rows.first
573
665
  row && row.first
@@ -580,6 +672,12 @@ module ActiveRecord
580
672
  relation
581
673
  end
582
674
  end
675
+
676
+ def extract_table_ref_from_insert_sql(sql)
677
+ if sql =~ /into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im
678
+ $1.delete('"').strip
679
+ end
680
+ end
583
681
  end
584
682
  end
585
683
  end
@@ -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.
@@ -102,7 +106,8 @@ module ActiveRecord
102
106
  sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
103
107
 
104
108
  if async
105
- lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async)
109
+ result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async)
110
+ FutureResult::Complete.new(result)
106
111
  else
107
112
  cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async) }
108
113
  end
@@ -113,31 +118,52 @@ module ActiveRecord
113
118
 
114
119
  private
115
120
  def lookup_sql_cache(sql, name, binds)
121
+ key = binds.empty? ? sql : [sql, binds]
122
+ hit = false
123
+ result = nil
124
+
116
125
  @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]
126
+ if (result = @query_cache.delete(key))
127
+ hit = true
128
+ @query_cache[key] = result
123
129
  end
124
130
  end
131
+
132
+ if hit
133
+ ActiveSupport::Notifications.instrument(
134
+ "sql.active_record",
135
+ cache_notification_info(sql, name, binds)
136
+ )
137
+
138
+ result
139
+ end
125
140
  end
126
141
 
127
142
  def cache_sql(sql, name, binds)
143
+ key = binds.empty? ? sql : [sql, binds]
144
+ result = nil
145
+ hit = false
146
+
128
147
  @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
148
+ if (result = @query_cache.delete(key))
149
+ hit = true
150
+ @query_cache[key] = result
151
+ else
152
+ result = @query_cache[key] = yield
153
+ if @query_cache_max_size && @query_cache.size > @query_cache_max_size
154
+ @query_cache.shift
138
155
  end
139
- result.dup
156
+ end
140
157
  end
158
+
159
+ if hit
160
+ ActiveSupport::Notifications.instrument(
161
+ "sql.active_record",
162
+ cache_notification_info(sql, name, binds)
163
+ )
164
+ end
165
+
166
+ result.dup
141
167
  end
142
168
 
143
169
  # Database adapters can override this method to
@@ -154,7 +180,20 @@ module ActiveRecord
154
180
  end
155
181
 
156
182
  def configure_query_cache!
157
- enable_query_cache! if pool.query_cache_enabled
183
+ case query_cache = pool.db_config.query_cache
184
+ when 0, false
185
+ return
186
+ when Integer
187
+ @query_cache_max_size = query_cache
188
+ when nil
189
+ @query_cache_max_size = DEFAULT_SIZE
190
+ else
191
+ @query_cache_max_size = nil # no limit
192
+ end
193
+
194
+ if pool.query_cache_enabled
195
+ enable_query_cache!
196
+ end
158
197
  end
159
198
  end
160
199
  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_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(" ")