activerecord 7.0.8.4 → 7.1.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1540 -1458
- data/MIT-LICENSE +1 -1
- data/README.rdoc +16 -16
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +20 -4
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +15 -9
- data/lib/active_record/associations/collection_proxy.rb +15 -10
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +20 -13
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency.rb +10 -8
- data/lib/active_record/associations/preloader/association.rb +31 -7
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +313 -217
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +52 -34
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +18 -5
- data/lib/active_record/attribute_methods/serialization.rb +150 -31
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +105 -21
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +55 -9
- data/lib/active_record/base.rb +7 -2
- data/lib/active_record/callbacks.rb +10 -24
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +74 -51
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
- data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -124
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -91
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -108
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
- data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +18 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +361 -60
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +209 -79
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +262 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +72 -95
- data/lib/active_record/core.rb +175 -153
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +22 -12
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +86 -33
- data/lib/active_record/delegated_type.rb +9 -4
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +42 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +21 -6
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/scheme.rb +19 -22
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +112 -28
- data/lib/active_record/errors.rb +112 -18
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +135 -71
- data/lib/active_record/future_result.rb +31 -5
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +57 -10
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/internal_metadata.rb +120 -30
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +6 -8
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +104 -5
- data/lib/active_record/migration/compatibility.rb +139 -5
- data/lib/active_record/migration/default_strategy.rb +23 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +219 -111
- data/lib/active_record/model_schema.rb +64 -44
- data/lib/active_record/nested_attributes.rb +24 -6
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +188 -37
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +77 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +15 -2
- data/lib/active_record/railtie.rb +109 -47
- data/lib/active_record/railties/controller_runtime.rb +12 -6
- data/lib/active_record/railties/databases.rake +142 -148
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +174 -44
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +187 -63
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +26 -14
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +352 -63
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +91 -35
- data/lib/active_record/result.rb +19 -5
- data/lib/active_record/runtime_registry.rb +24 -1
- data/lib/active_record/sanitization.rb +51 -11
- data/lib/active_record/schema.rb +2 -3
- data/lib/active_record/schema_dumper.rb +46 -7
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/signed_id.rb +7 -5
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +10 -1
- data/lib/active_record/tasks/database_tasks.rb +127 -105
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +113 -96
- data/lib/active_record/timestamp.rb +27 -15
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +36 -10
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +47 -2
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +121 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +81 -17
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +46 -10
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- 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
|
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
|
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
|
-
|
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
|
-
|
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
|
128
|
+
# method may be manually memory managed. Consider using #exec_query
|
115
129
|
# wrapper instead.
|
116
|
-
def execute(sql, name = nil)
|
117
|
-
|
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
|
-
|
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
|
-
|
131
|
-
|
132
|
-
|
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
|
-
|
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
|
-
|
167
|
+
internal_exec_query(sql, name, binds)
|
147
168
|
end
|
148
169
|
|
149
170
|
def exec_insert_all(sql, name) # :nodoc:
|
150
|
-
|
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
|
-
|
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,
|
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!,
|
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
|
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
|
-
|
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
|
-
|
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,
|
11
|
-
:rollback_to_savepoint, :rollback_db_transaction, :
|
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 =
|
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
|
118
|
-
|
119
|
-
|
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
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
@query_cache
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
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
|
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+
|
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+
|
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
|
-
|
12
|
+
internal_execute("SAVEPOINT #{name}", "TRANSACTION")
|
12
13
|
end
|
13
14
|
|
14
15
|
def exec_rollback_to_savepoint(name = current_savepoint_name)
|
15
|
-
|
16
|
+
internal_execute("ROLLBACK TO SAVEPOINT #{name}", "TRANSACTION")
|
16
17
|
end
|
17
18
|
|
18
19
|
def release_savepoint(name = current_savepoint_name)
|
19
|
-
|
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?, :
|
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
|
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 (#{
|
76
|
-
REFERENCES #{quote_table_name(o.to_table)} (#{
|
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(" ")
|