activerecord 7.0.8.7 → 7.1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1795 -1424
  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 +19 -13
  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/join_association.rb +3 -2
  21. data/lib/active_record/associations/join_dependency.rb +10 -10
  22. data/lib/active_record/associations/preloader/association.rb +31 -7
  23. data/lib/active_record/associations/preloader.rb +13 -10
  24. data/lib/active_record/associations/singular_association.rb +1 -1
  25. data/lib/active_record/associations/through_association.rb +22 -11
  26. data/lib/active_record/associations.rb +319 -217
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  30. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  31. data/lib/active_record/attribute_methods/query.rb +28 -16
  32. data/lib/active_record/attribute_methods/read.rb +21 -8
  33. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  34. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
  35. data/lib/active_record/attribute_methods/write.rb +6 -6
  36. data/lib/active_record/attribute_methods.rb +145 -21
  37. data/lib/active_record/attributes.rb +3 -3
  38. data/lib/active_record/autosave_association.rb +59 -10
  39. data/lib/active_record/base.rb +7 -2
  40. data/lib/active_record/callbacks.rb +10 -24
  41. data/lib/active_record/coders/column_serializer.rb +61 -0
  42. data/lib/active_record/coders/json.rb +1 -1
  43. data/lib/active_record/coders/yaml_column.rb +70 -42
  44. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  45. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  46. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  47. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +80 -50
  48. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  49. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  50. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  51. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  52. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  53. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  54. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +296 -127
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -92
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +244 -121
  59. data/lib/active_record/connection_adapters/column.rb +9 -0
  60. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  61. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
  62. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
  63. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  64. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  65. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  66. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +19 -13
  67. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
  69. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  70. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
  73. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  75. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
  78. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  79. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  80. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  81. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +364 -61
  83. data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
  84. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  85. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  86. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  87. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  88. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  89. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  90. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +211 -81
  91. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  92. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  93. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  94. data/lib/active_record/connection_adapters.rb +3 -1
  95. data/lib/active_record/connection_handling.rb +72 -95
  96. data/lib/active_record/core.rb +181 -154
  97. data/lib/active_record/counter_cache.rb +52 -27
  98. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  99. data/lib/active_record/database_configurations/database_config.rb +9 -3
  100. data/lib/active_record/database_configurations/hash_config.rb +28 -14
  101. data/lib/active_record/database_configurations/url_config.rb +17 -11
  102. data/lib/active_record/database_configurations.rb +86 -33
  103. data/lib/active_record/delegated_type.rb +15 -10
  104. data/lib/active_record/deprecator.rb +7 -0
  105. data/lib/active_record/destroy_association_async_job.rb +3 -1
  106. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  107. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  108. data/lib/active_record/encryption/config.rb +25 -1
  109. data/lib/active_record/encryption/configurable.rb +12 -19
  110. data/lib/active_record/encryption/context.rb +10 -3
  111. data/lib/active_record/encryption/contexts.rb +5 -1
  112. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  113. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  114. data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
  115. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  116. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  117. data/lib/active_record/encryption/key_generator.rb +12 -1
  118. data/lib/active_record/encryption/message_serializer.rb +2 -0
  119. data/lib/active_record/encryption/properties.rb +3 -3
  120. data/lib/active_record/encryption/scheme.rb +22 -21
  121. data/lib/active_record/encryption.rb +3 -0
  122. data/lib/active_record/enum.rb +112 -28
  123. data/lib/active_record/errors.rb +112 -18
  124. data/lib/active_record/explain.rb +23 -3
  125. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  126. data/lib/active_record/fixture_set/render_context.rb +2 -0
  127. data/lib/active_record/fixture_set/table_row.rb +29 -8
  128. data/lib/active_record/fixtures.rb +135 -71
  129. data/lib/active_record/future_result.rb +40 -5
  130. data/lib/active_record/gem_version.rb +4 -4
  131. data/lib/active_record/inheritance.rb +30 -16
  132. data/lib/active_record/insert_all.rb +57 -10
  133. data/lib/active_record/integration.rb +8 -8
  134. data/lib/active_record/internal_metadata.rb +120 -30
  135. data/lib/active_record/locking/optimistic.rb +1 -1
  136. data/lib/active_record/locking/pessimistic.rb +5 -2
  137. data/lib/active_record/log_subscriber.rb +29 -12
  138. data/lib/active_record/marshalling.rb +59 -0
  139. data/lib/active_record/message_pack.rb +124 -0
  140. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  141. data/lib/active_record/middleware/database_selector.rb +6 -8
  142. data/lib/active_record/middleware/shard_selector.rb +3 -1
  143. data/lib/active_record/migration/command_recorder.rb +104 -5
  144. data/lib/active_record/migration/compatibility.rb +145 -5
  145. data/lib/active_record/migration/default_strategy.rb +23 -0
  146. data/lib/active_record/migration/execution_strategy.rb +19 -0
  147. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  148. data/lib/active_record/migration.rb +219 -111
  149. data/lib/active_record/model_schema.rb +69 -44
  150. data/lib/active_record/nested_attributes.rb +37 -8
  151. data/lib/active_record/normalization.rb +167 -0
  152. data/lib/active_record/persistence.rb +188 -37
  153. data/lib/active_record/promise.rb +84 -0
  154. data/lib/active_record/query_cache.rb +4 -22
  155. data/lib/active_record/query_logs.rb +77 -52
  156. data/lib/active_record/query_logs_formatter.rb +41 -0
  157. data/lib/active_record/querying.rb +15 -2
  158. data/lib/active_record/railtie.rb +107 -45
  159. data/lib/active_record/railties/controller_runtime.rb +12 -6
  160. data/lib/active_record/railties/databases.rake +144 -150
  161. data/lib/active_record/railties/job_runtime.rb +23 -0
  162. data/lib/active_record/readonly_attributes.rb +32 -5
  163. data/lib/active_record/reflection.rb +181 -45
  164. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  165. data/lib/active_record/relation/batches.rb +190 -61
  166. data/lib/active_record/relation/calculations.rb +187 -63
  167. data/lib/active_record/relation/delegation.rb +23 -9
  168. data/lib/active_record/relation/finder_methods.rb +77 -16
  169. data/lib/active_record/relation/merger.rb +2 -0
  170. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  171. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  172. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  173. data/lib/active_record/relation/predicate_builder.rb +26 -14
  174. data/lib/active_record/relation/query_attribute.rb +2 -1
  175. data/lib/active_record/relation/query_methods.rb +371 -68
  176. data/lib/active_record/relation/spawn_methods.rb +18 -1
  177. data/lib/active_record/relation.rb +103 -37
  178. data/lib/active_record/result.rb +19 -5
  179. data/lib/active_record/runtime_registry.rb +24 -1
  180. data/lib/active_record/sanitization.rb +51 -11
  181. data/lib/active_record/schema.rb +2 -3
  182. data/lib/active_record/schema_dumper.rb +46 -7
  183. data/lib/active_record/schema_migration.rb +68 -33
  184. data/lib/active_record/scoping/default.rb +15 -5
  185. data/lib/active_record/scoping/named.rb +2 -2
  186. data/lib/active_record/scoping.rb +2 -1
  187. data/lib/active_record/secure_password.rb +60 -0
  188. data/lib/active_record/secure_token.rb +21 -3
  189. data/lib/active_record/signed_id.rb +7 -5
  190. data/lib/active_record/store.rb +8 -8
  191. data/lib/active_record/suppressor.rb +3 -1
  192. data/lib/active_record/table_metadata.rb +10 -1
  193. data/lib/active_record/tasks/database_tasks.rb +152 -108
  194. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  195. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  196. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  197. data/lib/active_record/test_fixtures.rb +114 -96
  198. data/lib/active_record/timestamp.rb +30 -16
  199. data/lib/active_record/token_for.rb +113 -0
  200. data/lib/active_record/touch_later.rb +11 -6
  201. data/lib/active_record/transactions.rb +36 -10
  202. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  203. data/lib/active_record/type/internal/timezone.rb +7 -2
  204. data/lib/active_record/type/time.rb +4 -0
  205. data/lib/active_record/validations/absence.rb +1 -1
  206. data/lib/active_record/validations/numericality.rb +5 -4
  207. data/lib/active_record/validations/presence.rb +5 -28
  208. data/lib/active_record/validations/uniqueness.rb +47 -2
  209. data/lib/active_record/validations.rb +8 -4
  210. data/lib/active_record/version.rb +1 -1
  211. data/lib/active_record.rb +122 -17
  212. data/lib/arel/errors.rb +10 -0
  213. data/lib/arel/factory_methods.rb +4 -0
  214. data/lib/arel/nodes/binary.rb +6 -1
  215. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  216. data/lib/arel/nodes/cte.rb +36 -0
  217. data/lib/arel/nodes/fragments.rb +35 -0
  218. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  219. data/lib/arel/nodes/leading_join.rb +8 -0
  220. data/lib/arel/nodes/node.rb +111 -2
  221. data/lib/arel/nodes/sql_literal.rb +6 -0
  222. data/lib/arel/nodes/table_alias.rb +4 -0
  223. data/lib/arel/nodes.rb +4 -0
  224. data/lib/arel/predications.rb +2 -0
  225. data/lib/arel/table.rb +9 -5
  226. data/lib/arel/tree_manager.rb +5 -1
  227. data/lib/arel/visitors/mysql.rb +8 -1
  228. data/lib/arel/visitors/to_sql.rb +83 -18
  229. data/lib/arel/visitors/visitor.rb +2 -2
  230. data/lib/arel.rb +16 -2
  231. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  232. data/lib/rails/generators/active_record/migration.rb +3 -1
  233. data/lib/rails/generators/active_record/model/USAGE +113 -0
  234. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  235. metadata +46 -10
  236. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  237. 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.wrap(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.wrap(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(" ")