activerecord 8.0.0 → 8.1.4
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1381 -241
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/association.rb +35 -17
- data/lib/active_record/associations/association_scope.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +20 -4
- data/lib/active_record/associations/builder/association.rb +16 -5
- data/lib/active_record/associations/builder/belongs_to.rb +36 -8
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +9 -5
- data/lib/active_record/associations/collection_proxy.rb +26 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/foreign_association.rb +3 -1
- data/lib/active_record/associations/has_one_association.rb +1 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
- data/lib/active_record/associations/join_dependency.rb +5 -4
- data/lib/active_record/associations/preloader/association.rb +1 -1
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations.rb +159 -21
- data/lib/active_record/attribute_methods/primary_key.rb +2 -1
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +19 -6
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
- data/lib/active_record/attribute_methods.rb +24 -17
- data/lib/active_record/attributes.rb +40 -26
- data/lib/active_record/autosave_association.rb +22 -12
- data/lib/active_record/base.rb +3 -4
- data/lib/active_record/callbacks.rb +1 -2
- data/lib/active_record/coders/column_serializer.rb +1 -1
- data/lib/active_record/coders/json.rb +16 -6
- data/lib/active_record/coders/yaml_column.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +19 -18
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +474 -112
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +68 -41
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -9
- data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +31 -35
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +94 -28
- data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +166 -64
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +133 -40
- data/lib/active_record/connection_adapters/column.rb +14 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -1
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +40 -25
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +66 -15
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -3
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +29 -33
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +8 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -33
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +70 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +99 -54
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +40 -12
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -23
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +15 -3
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +7 -14
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +68 -37
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +8 -3
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +16 -2
- data/lib/active_record/connection_adapters.rb +1 -0
- data/lib/active_record/connection_handling.rb +15 -10
- data/lib/active_record/core.rb +44 -12
- data/lib/active_record/counter_cache.rb +36 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- data/lib/active_record/database_configurations/database_config.rb +5 -1
- data/lib/active_record/database_configurations/hash_config.rb +66 -9
- data/lib/active_record/database_configurations/url_config.rb +15 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +19 -19
- data/lib/active_record/disable_joins_association_relation.rb +5 -1
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/encryptable_record.rb +5 -5
- data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
- data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
- data/lib/active_record/encryption/encryptor.rb +39 -25
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +37 -20
- data/lib/active_record/errors.rb +23 -7
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +76 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/future_result.rb +5 -3
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +9 -4
- data/lib/active_record/insert_all.rb +14 -9
- data/lib/active_record/locking/optimistic.rb +7 -0
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +30 -22
- data/lib/active_record/message_pack.rb +13 -0
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +32 -6
- data/lib/active_record/migration/compatibility.rb +34 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +31 -21
- data/lib/active_record/model_schema.rb +38 -12
- data/lib/active_record/nested_attributes.rb +10 -6
- data/lib/active_record/persistence.rb +35 -3
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +7 -7
- data/lib/active_record/querying.rb +17 -5
- data/lib/active_record/railtie.rb +35 -6
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +24 -20
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +36 -1
- data/lib/active_record/relation/batches.rb +25 -11
- data/lib/active_record/relation/calculations.rb +62 -41
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +76 -45
- data/lib/active_record/relation/merger.rb +4 -2
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
- data/lib/active_record/relation/predicate_builder.rb +10 -8
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +106 -45
- data/lib/active_record/relation/spawn_methods.rb +6 -6
- data/lib/active_record/relation/where_clause.rb +10 -11
- data/lib/active_record/relation.rb +63 -31
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/runtime_registry.rb +42 -58
- data/lib/active_record/sanitization.rb +2 -0
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +41 -21
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +48 -19
- data/lib/active_record/statement_cache.rb +16 -12
- data/lib/active_record/store.rb +46 -19
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +5 -20
- data/lib/active_record/tasks/abstract_tasks.rb +83 -0
- data/lib/active_record/tasks/database_tasks.rb +44 -45
- data/lib/active_record/tasks/mysql_database_tasks.rb +4 -56
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
- data/lib/active_record/test_databases.rb +18 -4
- data/lib/active_record/test_fixtures.rb +40 -6
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +58 -32
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +13 -2
- data/lib/active_record/type/serialized.rb +16 -4
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record.rb +72 -7
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/collectors/bind.rb +1 -1
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/select_manager.rb +13 -4
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +7 -23
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +16 -15
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
class FilterAttributeHandler # :nodoc:
|
|
5
|
+
class << self
|
|
6
|
+
def on_sensitive_attribute_declared(&block)
|
|
7
|
+
@sensitive_attribute_declaration_listeners ||= Concurrent::Array.new
|
|
8
|
+
@sensitive_attribute_declaration_listeners << block
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def sensitive_attribute_was_declared(klass, list)
|
|
12
|
+
@sensitive_attribute_declaration_listeners&.each do |block|
|
|
13
|
+
block.call(klass, list)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(app)
|
|
19
|
+
@app = app
|
|
20
|
+
@attributes_by_class = Concurrent::Map.new
|
|
21
|
+
@collecting = true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def enable
|
|
25
|
+
install_collecting_hook
|
|
26
|
+
|
|
27
|
+
apply_collected_attributes
|
|
28
|
+
@collecting = false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
attr_reader :app
|
|
33
|
+
|
|
34
|
+
def install_collecting_hook
|
|
35
|
+
self.class.on_sensitive_attribute_declared do |klass, list|
|
|
36
|
+
attribute_was_declared(klass, list)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def attribute_was_declared(klass, list)
|
|
41
|
+
if collecting?
|
|
42
|
+
collect_for_later(klass, list)
|
|
43
|
+
else
|
|
44
|
+
apply_filter(klass, list)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def apply_collected_attributes
|
|
49
|
+
@attributes_by_class.each do |klass, list|
|
|
50
|
+
apply_filter(klass, list)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def collecting?
|
|
55
|
+
@collecting
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def collect_for_later(klass, list)
|
|
59
|
+
@attributes_by_class[klass] ||= Concurrent::Array.new
|
|
60
|
+
@attributes_by_class[klass] += list
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def apply_filter(klass, list)
|
|
64
|
+
list.each do |attribute|
|
|
65
|
+
next if klass.abstract_class? || klass == Base
|
|
66
|
+
|
|
67
|
+
case attribute
|
|
68
|
+
when Symbol, String
|
|
69
|
+
klass_name = klass.name ? klass.model_name.element : nil
|
|
70
|
+
filter = [klass_name, attribute.to_s].compact.join(".")
|
|
71
|
+
app.config.filter_parameters << filter unless app.config.filter_parameters.include?(filter)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -193,8 +193,25 @@ module ActiveRecord
|
|
|
193
193
|
|
|
194
194
|
targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
|
|
195
195
|
joins = targets.map do |target|
|
|
196
|
-
join = {
|
|
197
|
-
|
|
196
|
+
join = {}
|
|
197
|
+
|
|
198
|
+
if rhs_key.is_a?(Array)
|
|
199
|
+
composite_key = ActiveRecord::FixtureSet.composite_identify(target, rhs_key)
|
|
200
|
+
composite_key.each do |column, value|
|
|
201
|
+
join[column] = value
|
|
202
|
+
end
|
|
203
|
+
else
|
|
204
|
+
join[rhs_key] = ActiveRecord::FixtureSet.identify(target, column_type)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
if lhs_key.is_a?(Array)
|
|
208
|
+
lhs_key.zip(model_metadata.primary_key_name).each do |fkey, pkey|
|
|
209
|
+
join[fkey] = @row[pkey]
|
|
210
|
+
end
|
|
211
|
+
else
|
|
212
|
+
join[lhs_key] = @row[model_metadata.primary_key_name]
|
|
213
|
+
end
|
|
214
|
+
|
|
198
215
|
association.timestamp_column_names.each do |col|
|
|
199
216
|
join[col] = @now
|
|
200
217
|
end
|
|
@@ -242,10 +242,10 @@ module ActiveRecord
|
|
|
242
242
|
# and one for the humans. Why don't we generate the primary key instead?
|
|
243
243
|
# Hashing each fixture's label yields a consistent ID:
|
|
244
244
|
#
|
|
245
|
-
# george: # generated id:
|
|
245
|
+
# george: # generated id: 380982691
|
|
246
246
|
# name: George the Monkey
|
|
247
247
|
#
|
|
248
|
-
# reginald: # generated id:
|
|
248
|
+
# reginald: # generated id: 41001176
|
|
249
249
|
# name: Reginald the Pirate
|
|
250
250
|
#
|
|
251
251
|
# Active Record looks at the fixture's model class, discovers the correct
|
|
@@ -105,14 +105,16 @@ module ActiveRecord
|
|
|
105
105
|
|
|
106
106
|
@pool.with_connection do |connection|
|
|
107
107
|
return unless @mutex.try_lock
|
|
108
|
+
previous_instrumenter = ActiveSupport::IsolatedExecutionState[:active_record_instrumenter]
|
|
108
109
|
begin
|
|
109
110
|
if pending?
|
|
110
111
|
@event_buffer = EventBuffer.new(self, @instrumenter)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = @event_buffer
|
|
113
|
+
|
|
114
|
+
execute_query(connection, async: true)
|
|
114
115
|
end
|
|
115
116
|
ensure
|
|
117
|
+
ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = previous_instrumenter
|
|
116
118
|
@mutex.unlock
|
|
117
119
|
end
|
|
118
120
|
end
|
|
@@ -32,9 +32,9 @@ module ActiveRecord
|
|
|
32
32
|
# be triggered. In that case, it'll work just like normal subclasses with no special magic
|
|
33
33
|
# for differentiating between them or reloading the right type with find.
|
|
34
34
|
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
35
|
+
# All subclasses share the same database table. See the
|
|
36
|
+
# {Single Table Inheritance pattern}[https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html]
|
|
37
|
+
# for more background.
|
|
38
38
|
#
|
|
39
39
|
module Inheritance
|
|
40
40
|
extend ActiveSupport::Concern
|
|
@@ -97,7 +97,7 @@ module ActiveRecord
|
|
|
97
97
|
# Returns the first class in the inheritance hierarchy that descends from either an
|
|
98
98
|
# abstract class or from <tt>ActiveRecord::Base</tt>.
|
|
99
99
|
#
|
|
100
|
-
# Consider the following
|
|
100
|
+
# Consider the following behavior:
|
|
101
101
|
#
|
|
102
102
|
# class ApplicationRecord < ActiveRecord::Base
|
|
103
103
|
# self.abstract_class = true
|
|
@@ -237,6 +237,11 @@ module ActiveRecord
|
|
|
237
237
|
end
|
|
238
238
|
|
|
239
239
|
protected
|
|
240
|
+
def reload_schema_from_cache(*) # :nodoc:
|
|
241
|
+
@finder_needs_type_condition = nil
|
|
242
|
+
super
|
|
243
|
+
end
|
|
244
|
+
|
|
240
245
|
# Returns the class type of the record using the current module as a prefix. So descendants of
|
|
241
246
|
# MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
|
|
242
247
|
def compute_type(type_name)
|
|
@@ -11,7 +11,7 @@ module ActiveRecord
|
|
|
11
11
|
def execute(relation, ...)
|
|
12
12
|
relation.model.with_connection do |c|
|
|
13
13
|
new(relation, c, ...).execute
|
|
14
|
-
end
|
|
14
|
+
end.tap { relation.reset }
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
|
|
@@ -39,7 +39,7 @@ module ActiveRecord
|
|
|
39
39
|
@returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
|
|
40
40
|
@returning = false if @returning == []
|
|
41
41
|
|
|
42
|
-
@unique_by = find_unique_index_for(@unique_by)
|
|
42
|
+
@unique_by = find_unique_index_for(@unique_by) if @on_duplicate != :raise
|
|
43
43
|
|
|
44
44
|
configure_on_duplicate_update_logic
|
|
45
45
|
ensure_valid_options_for_connection!
|
|
@@ -48,7 +48,7 @@ module ActiveRecord
|
|
|
48
48
|
def execute
|
|
49
49
|
return ActiveRecord::Result.empty if inserts.empty?
|
|
50
50
|
|
|
51
|
-
message = +"#{model} "
|
|
51
|
+
message = +"#{model.name} "
|
|
52
52
|
message << "Bulk " if inserts.many?
|
|
53
53
|
message << (on_duplicate == :update ? "Upsert" : "Insert")
|
|
54
54
|
connection.exec_insert_all to_sql, message
|
|
@@ -225,7 +225,7 @@ module ActiveRecord
|
|
|
225
225
|
class Builder # :nodoc:
|
|
226
226
|
attr_reader :model
|
|
227
227
|
|
|
228
|
-
delegate :skip_duplicates?, :update_duplicates?, :keys, :keys_including_timestamps, :record_timestamps?, to: :insert_all
|
|
228
|
+
delegate :skip_duplicates?, :update_duplicates?, :keys, :keys_including_timestamps, :record_timestamps?, :primary_keys, to: :insert_all
|
|
229
229
|
|
|
230
230
|
def initialize(insert_all)
|
|
231
231
|
@insert_all, @model, @connection = insert_all, insert_all.model, insert_all.connection
|
|
@@ -236,11 +236,16 @@ module ActiveRecord
|
|
|
236
236
|
end
|
|
237
237
|
|
|
238
238
|
def values_list
|
|
239
|
-
types =
|
|
239
|
+
types = extract_types_for(keys_including_timestamps)
|
|
240
240
|
|
|
241
241
|
values_list = insert_all.map_key_with_value do |key, value|
|
|
242
|
-
|
|
243
|
-
|
|
242
|
+
if Arel::Nodes::SqlLiteral === value
|
|
243
|
+
value
|
|
244
|
+
elsif primary_keys.include?(key) && value.nil?
|
|
245
|
+
connection.default_insert_value(model.columns_hash[key])
|
|
246
|
+
else
|
|
247
|
+
ActiveModel::Type::SerializeCastValue.serialize(type = types[key], type.cast(value))
|
|
248
|
+
end
|
|
244
249
|
end
|
|
245
250
|
|
|
246
251
|
connection.visitor.compile(Arel::Nodes::ValuesList.new(values_list))
|
|
@@ -303,8 +308,8 @@ module ActiveRecord
|
|
|
303
308
|
format_columns(insert_all.keys_including_timestamps)
|
|
304
309
|
end
|
|
305
310
|
|
|
306
|
-
def
|
|
307
|
-
columns = @model.
|
|
311
|
+
def extract_types_for(keys)
|
|
312
|
+
columns = @model.columns_hash
|
|
308
313
|
|
|
309
314
|
unknown_column = (keys - columns.keys).first
|
|
310
315
|
raise UnknownAttributeError.new(model.new, unknown_column) if unknown_column
|
|
@@ -101,6 +101,13 @@ module ActiveRecord
|
|
|
101
101
|
attribute_names = attribute_names.dup if attribute_names.frozen?
|
|
102
102
|
attribute_names << locking_column
|
|
103
103
|
|
|
104
|
+
if self[locking_column].nil?
|
|
105
|
+
raise(<<-MSG.squish)
|
|
106
|
+
For optimistic locking, locking_column ('#{locking_column}') can't be nil.
|
|
107
|
+
Are you missing a default value or validation on '#{locking_column}'?
|
|
108
|
+
MSG
|
|
109
|
+
end
|
|
110
|
+
|
|
104
111
|
self[locking_column] += 1
|
|
105
112
|
|
|
106
113
|
affected_rows = self.class._update_record(
|
|
@@ -67,6 +67,10 @@ module ActiveRecord
|
|
|
67
67
|
# or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
|
|
68
68
|
# the locked record.
|
|
69
69
|
def lock!(lock = true)
|
|
70
|
+
if self.class.current_preventing_writes
|
|
71
|
+
raise ActiveRecord::ReadOnlyError, "Lock query attempted while in readonly mode"
|
|
72
|
+
end
|
|
73
|
+
|
|
70
74
|
if persisted?
|
|
71
75
|
if has_changes_to_save?
|
|
72
76
|
raise(<<-MSG.squish)
|
|
@@ -79,6 +83,7 @@ module ActiveRecord
|
|
|
79
83
|
|
|
80
84
|
reload(lock: lock)
|
|
81
85
|
end
|
|
86
|
+
|
|
82
87
|
self
|
|
83
88
|
end
|
|
84
89
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ActiveRecord
|
|
4
|
-
class LogSubscriber < ActiveSupport::LogSubscriber
|
|
4
|
+
class LogSubscriber < ActiveSupport::LogSubscriber # :nodoc:
|
|
5
5
|
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
|
|
6
6
|
|
|
7
7
|
class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
|
|
@@ -86,23 +86,35 @@ module ActiveRecord
|
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
def sql_color(sql)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
89
|
+
color = if (match = sql.match(/\A\s*(\w+)(?:\s|\Z)/))
|
|
90
|
+
case match[1].downcase
|
|
91
|
+
when "rollback"
|
|
92
|
+
RED
|
|
93
|
+
when "lock"
|
|
94
|
+
WHITE
|
|
95
|
+
when "select"
|
|
96
|
+
if sql.match?(/for update/mi)
|
|
97
|
+
WHITE
|
|
98
|
+
else
|
|
99
|
+
BLUE
|
|
100
|
+
end
|
|
101
|
+
when "insert"
|
|
102
|
+
GREEN
|
|
103
|
+
when "update"
|
|
104
|
+
YELLOW
|
|
105
|
+
when "delete"
|
|
106
|
+
RED
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
if color
|
|
111
|
+
color
|
|
104
112
|
else
|
|
105
|
-
|
|
113
|
+
if sql.match?(/transaction\s*\Z/i)
|
|
114
|
+
CYAN
|
|
115
|
+
else
|
|
116
|
+
MAGENTA
|
|
117
|
+
end
|
|
106
118
|
end
|
|
107
119
|
end
|
|
108
120
|
|
|
@@ -127,11 +139,7 @@ module ActiveRecord
|
|
|
127
139
|
end
|
|
128
140
|
|
|
129
141
|
def query_source_location
|
|
130
|
-
|
|
131
|
-
frame = backtrace_cleaner.clean_frame(location)
|
|
132
|
-
return frame if frame
|
|
133
|
-
end
|
|
134
|
-
nil
|
|
142
|
+
backtrace_cleaner.first_clean_frame
|
|
135
143
|
end
|
|
136
144
|
|
|
137
145
|
def filter(name, value)
|
|
@@ -23,6 +23,11 @@ module ActiveRecord
|
|
|
23
23
|
extend self
|
|
24
24
|
|
|
25
25
|
def install(registry)
|
|
26
|
+
registry.register_type 118, ActiveRecord::Type::Time::Value,
|
|
27
|
+
packer: method(:write_time_value),
|
|
28
|
+
unpacker: method(:read_time_value),
|
|
29
|
+
recursive: true
|
|
30
|
+
|
|
26
31
|
registry.register_type 119, ActiveModel::Type::Binary::Data,
|
|
27
32
|
packer: :to_s,
|
|
28
33
|
unpacker: :new
|
|
@@ -33,6 +38,14 @@ module ActiveRecord
|
|
|
33
38
|
recursive: true
|
|
34
39
|
end
|
|
35
40
|
|
|
41
|
+
def write_time_value(value, packer)
|
|
42
|
+
packer.write(value.__getobj__)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def read_time_value(unpacker)
|
|
46
|
+
ActiveRecord::Type::Time::Value.new(unpacker.read)
|
|
47
|
+
end
|
|
48
|
+
|
|
36
49
|
def write_record(record, packer)
|
|
37
50
|
packer.write(ActiveRecord::MessagePack.dump(record))
|
|
38
51
|
end
|
|
@@ -9,25 +9,40 @@ module ActiveRecord
|
|
|
9
9
|
# shard to switch to and allows for applications to write custom strategies
|
|
10
10
|
# for swapping if needed.
|
|
11
11
|
#
|
|
12
|
-
#
|
|
13
|
-
# that can be used by the middleware to alter behavior. +lock+ is
|
|
14
|
-
# true by default and will prohibit the request from switching shards once
|
|
15
|
-
# inside the block. If +lock+ is false, then shard swapping will be allowed.
|
|
16
|
-
# For tenant based sharding, +lock+ should always be true to prevent application
|
|
17
|
-
# code from mistakenly switching between tenants.
|
|
12
|
+
# == Setup
|
|
18
13
|
#
|
|
19
|
-
#
|
|
14
|
+
# Applications must provide a resolver that will provide application-specific logic for
|
|
15
|
+
# selecting the appropriate shard. Setting +config.active_record.shard_resolver+ will cause
|
|
16
|
+
# Rails to add ShardSelector to the default middleware stack.
|
|
20
17
|
#
|
|
21
|
-
#
|
|
18
|
+
# The resolver, along with any configuration options, can be set in the application
|
|
19
|
+
# configuration using an initializer like so:
|
|
22
20
|
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
21
|
+
# Rails.application.configure do
|
|
22
|
+
# config.active_record.shard_selector = { lock: false, class_name: "AnimalsRecord" }
|
|
23
|
+
# config.active_record.shard_resolver = ->(request) {
|
|
24
|
+
# subdomain = request.subdomain
|
|
25
|
+
# tenant = Tenant.find_by_subdomain!(subdomain)
|
|
26
|
+
# tenant.shard
|
|
27
|
+
# }
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# == Configuration
|
|
31
|
+
#
|
|
32
|
+
# The behavior of ShardSelector can be altered through some configuration options.
|
|
33
|
+
#
|
|
34
|
+
# [+lock:+]
|
|
35
|
+
# +lock+ is true by default and will prohibit the request from switching shards once inside
|
|
36
|
+
# the block. If +lock+ is false, then shard switching will be allowed. For tenant based
|
|
37
|
+
# sharding, +lock+ should always be true to prevent application code from mistakenly switching
|
|
38
|
+
# between tenants.
|
|
39
|
+
#
|
|
40
|
+
# [+class_name:+]
|
|
41
|
+
# +class_name+ is the name of the abstract connection class to switch. By
|
|
42
|
+
# default, the ShardSelector will use ActiveRecord::Base, but if the
|
|
43
|
+
# application has multiple databases, then this option should be set to
|
|
44
|
+
# the name of the sharded database's abstract connection class.
|
|
25
45
|
#
|
|
26
|
-
# config.active_record.shard_resolver = ->(request) {
|
|
27
|
-
# subdomain = request.subdomain
|
|
28
|
-
# tenant = Tenant.find_by_subdomain!(subdomain)
|
|
29
|
-
# tenant.shard
|
|
30
|
-
# }
|
|
31
46
|
class ShardSelector
|
|
32
47
|
def initialize(app, resolver, options = {})
|
|
33
48
|
@app = app
|
|
@@ -53,8 +68,10 @@ module ActiveRecord
|
|
|
53
68
|
end
|
|
54
69
|
|
|
55
70
|
def set_shard(shard, &block)
|
|
56
|
-
ActiveRecord::Base
|
|
57
|
-
|
|
71
|
+
klass = options[:class_name]&.constantize || ActiveRecord::Base
|
|
72
|
+
|
|
73
|
+
klass.connected_to(shard: shard.to_sym) do
|
|
74
|
+
klass.prohibit_shard_swapping(options.fetch(:lock, true), &block)
|
|
58
75
|
end
|
|
59
76
|
end
|
|
60
77
|
end
|
|
@@ -44,6 +44,8 @@ module ActiveRecord
|
|
|
44
44
|
# * rename_enum_value (must supply a +:from+ and +:to+ option)
|
|
45
45
|
# * rename_index
|
|
46
46
|
# * rename_table
|
|
47
|
+
# * enable_index
|
|
48
|
+
# * disable_index
|
|
47
49
|
class CommandRecorder
|
|
48
50
|
ReversibleAndIrreversibleMethods = [
|
|
49
51
|
:create_table, :create_join_table, :rename_table, :add_column, :remove_column,
|
|
@@ -58,7 +60,8 @@ module ActiveRecord
|
|
|
58
60
|
:add_unique_constraint, :remove_unique_constraint,
|
|
59
61
|
:create_enum, :drop_enum, :rename_enum, :add_enum_value, :rename_enum_value,
|
|
60
62
|
:create_schema, :drop_schema,
|
|
61
|
-
:create_virtual_table, :drop_virtual_table
|
|
63
|
+
:create_virtual_table, :drop_virtual_table,
|
|
64
|
+
:enable_index, :disable_index
|
|
62
65
|
]
|
|
63
66
|
include JoinTable
|
|
64
67
|
|
|
@@ -139,7 +142,7 @@ module ActiveRecord
|
|
|
139
142
|
recorder.reverting = @reverting
|
|
140
143
|
yield recorder.delegate.update_table_definition(table_name, recorder)
|
|
141
144
|
commands = recorder.commands
|
|
142
|
-
@commands << [:change_table, [table_name], -> t { bulk_change_table(
|
|
145
|
+
@commands << [:change_table, [table_name], -> t { bulk_change_table(t.name, commands.reverse) }]
|
|
143
146
|
else
|
|
144
147
|
yield delegate.update_table_definition(table_name, self)
|
|
145
148
|
end
|
|
@@ -183,6 +186,16 @@ module ActiveRecord
|
|
|
183
186
|
|
|
184
187
|
include StraightReversions
|
|
185
188
|
|
|
189
|
+
def invert_enable_index(args)
|
|
190
|
+
table_name, index_name = args
|
|
191
|
+
[:disable_index, [table_name, index_name]]
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def invert_disable_index(args)
|
|
195
|
+
table_name, index_name = args
|
|
196
|
+
[:enable_index, [table_name, index_name]]
|
|
197
|
+
end
|
|
198
|
+
|
|
186
199
|
def invert_transaction(args, &block)
|
|
187
200
|
sub_recorder = CommandRecorder.new(delegate)
|
|
188
201
|
sub_recorder.revert(&block)
|
|
@@ -213,7 +226,9 @@ module ActiveRecord
|
|
|
213
226
|
raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)."
|
|
214
227
|
end
|
|
215
228
|
|
|
216
|
-
|
|
229
|
+
args << options unless options.empty?
|
|
230
|
+
|
|
231
|
+
super(args, &block)
|
|
217
232
|
end
|
|
218
233
|
|
|
219
234
|
def invert_rename_table(args)
|
|
@@ -224,7 +239,9 @@ module ActiveRecord
|
|
|
224
239
|
end
|
|
225
240
|
|
|
226
241
|
def invert_remove_column(args)
|
|
227
|
-
|
|
242
|
+
if args.size <= 2 || args[2].is_a?(Hash)
|
|
243
|
+
raise ActiveRecord::IrreversibleMigration, "remove_column is only reversible if given a type."
|
|
244
|
+
end
|
|
228
245
|
super
|
|
229
246
|
end
|
|
230
247
|
|
|
@@ -282,6 +299,13 @@ module ActiveRecord
|
|
|
282
299
|
[:change_column_null, args]
|
|
283
300
|
end
|
|
284
301
|
|
|
302
|
+
def invert_add_column(args)
|
|
303
|
+
if (options = args.last).is_a?(Hash)
|
|
304
|
+
options[:if_exists] = options.delete(:if_not_exists) if options.key?(:if_not_exists)
|
|
305
|
+
end
|
|
306
|
+
super
|
|
307
|
+
end
|
|
308
|
+
|
|
285
309
|
def invert_add_foreign_key(args)
|
|
286
310
|
args.last.delete(:validate) if args.last.is_a?(Hash)
|
|
287
311
|
super
|
|
@@ -292,6 +316,7 @@ module ActiveRecord
|
|
|
292
316
|
from_table, to_table = args
|
|
293
317
|
|
|
294
318
|
to_table ||= options.delete(:to_table)
|
|
319
|
+
options[:if_not_exists] = options.delete(:if_exists) if options.key?(:if_exists)
|
|
295
320
|
|
|
296
321
|
raise ActiveRecord::IrreversibleMigration, "remove_foreign_key is only reversible if given a second table" if to_table.nil?
|
|
297
322
|
|
|
@@ -380,11 +405,12 @@ module ActiveRecord
|
|
|
380
405
|
raise ActiveRecord::IrreversibleMigration, "rename_enum_value is only reversible if given a :from and :to option."
|
|
381
406
|
end
|
|
382
407
|
|
|
383
|
-
[:
|
|
408
|
+
options[:to], options[:from] = options[:from], options[:to]
|
|
409
|
+
[:rename_enum_value, [type_name, options]]
|
|
384
410
|
end
|
|
385
411
|
|
|
386
412
|
def invert_drop_virtual_table(args)
|
|
387
|
-
|
|
413
|
+
_table_name, _module_name, values = args.dup.tap(&:extract_options!)
|
|
388
414
|
raise ActiveRecord::IrreversibleMigration, "drop_virtual_table is only reversible if given options." unless values
|
|
389
415
|
super
|
|
390
416
|
end
|
|
@@ -21,7 +21,7 @@ module ActiveRecord
|
|
|
21
21
|
# New migration functionality that will never be backward compatible should be added directly to `ActiveRecord::Migration`.
|
|
22
22
|
#
|
|
23
23
|
# There are classes for each prior Rails version. Each class descends from the *next* Rails version, so:
|
|
24
|
-
# 5.2 < 6.0 < 6.1 < 7.0 < 7.1 < 7.2 < 8.0
|
|
24
|
+
# 5.2 < 6.0 < 6.1 < 7.0 < 7.1 < 7.2 < 8.0 < 8.1
|
|
25
25
|
#
|
|
26
26
|
# If you are introducing new migration functionality that should only apply from Rails 7 onward, then you should
|
|
27
27
|
# find the class that immediately precedes it (6.1), and override the relevant migration methods to undo your changes.
|
|
@@ -29,7 +29,31 @@ module ActiveRecord
|
|
|
29
29
|
# For example, Rails 6 added a default value for the `precision` option on datetime columns. So in this file, the `V5_2`
|
|
30
30
|
# class sets the value of `precision` to `nil` if it's not explicitly provided. This way, the default value will not apply
|
|
31
31
|
# for migrations written for 5.2, but will for migrations written for 6.0.
|
|
32
|
-
|
|
32
|
+
V8_1 = Current
|
|
33
|
+
|
|
34
|
+
class V8_0 < V8_1
|
|
35
|
+
module RemoveForeignKeyColumnMatch
|
|
36
|
+
def remove_foreign_key(*args, **options)
|
|
37
|
+
options[:_skip_column_match] = true
|
|
38
|
+
super
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
module TableDefinition
|
|
43
|
+
def remove_foreign_key(to_table = nil, **options)
|
|
44
|
+
options[:_skip_column_match] = true
|
|
45
|
+
super
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
include RemoveForeignKeyColumnMatch
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
def compatible_table_definition(t)
|
|
53
|
+
t.singleton_class.prepend(TableDefinition)
|
|
54
|
+
super
|
|
55
|
+
end
|
|
56
|
+
end
|
|
33
57
|
|
|
34
58
|
class V7_2 < V8_0
|
|
35
59
|
end
|
|
@@ -154,9 +178,7 @@ module ActiveRecord
|
|
|
154
178
|
|
|
155
179
|
private
|
|
156
180
|
def compatible_table_definition(t)
|
|
157
|
-
|
|
158
|
-
prepend TableDefinition
|
|
159
|
-
end
|
|
181
|
+
t.singleton_class.prepend(TableDefinition)
|
|
160
182
|
super
|
|
161
183
|
end
|
|
162
184
|
end
|
|
@@ -217,9 +239,7 @@ module ActiveRecord
|
|
|
217
239
|
|
|
218
240
|
private
|
|
219
241
|
def compatible_table_definition(t)
|
|
220
|
-
|
|
221
|
-
prepend TableDefinition
|
|
222
|
-
end
|
|
242
|
+
t.singleton_class.prepend(TableDefinition)
|
|
223
243
|
super
|
|
224
244
|
end
|
|
225
245
|
end
|
|
@@ -260,9 +280,7 @@ module ActiveRecord
|
|
|
260
280
|
|
|
261
281
|
private
|
|
262
282
|
def compatible_table_definition(t)
|
|
263
|
-
|
|
264
|
-
prepend TableDefinition
|
|
265
|
-
end
|
|
283
|
+
t.singleton_class.prepend(TableDefinition)
|
|
266
284
|
super
|
|
267
285
|
end
|
|
268
286
|
end
|
|
@@ -308,17 +326,13 @@ module ActiveRecord
|
|
|
308
326
|
|
|
309
327
|
private
|
|
310
328
|
def compatible_table_definition(t)
|
|
311
|
-
|
|
312
|
-
prepend TableDefinition
|
|
313
|
-
end
|
|
329
|
+
t.singleton_class.prepend(TableDefinition)
|
|
314
330
|
super
|
|
315
331
|
end
|
|
316
332
|
|
|
317
333
|
def command_recorder
|
|
318
334
|
recorder = super
|
|
319
|
-
|
|
320
|
-
prepend CommandRecorder
|
|
321
|
-
end
|
|
335
|
+
recorder.singleton_class.prepend(CommandRecorder)
|
|
322
336
|
recorder
|
|
323
337
|
end
|
|
324
338
|
end
|
|
@@ -406,9 +420,7 @@ module ActiveRecord
|
|
|
406
420
|
|
|
407
421
|
private
|
|
408
422
|
def compatible_table_definition(t)
|
|
409
|
-
|
|
410
|
-
prepend TableDefinition
|
|
411
|
-
end
|
|
423
|
+
t.singleton_class.prepend(TableDefinition)
|
|
412
424
|
super
|
|
413
425
|
end
|
|
414
426
|
end
|
|
@@ -442,7 +454,7 @@ module ActiveRecord
|
|
|
442
454
|
super
|
|
443
455
|
end
|
|
444
456
|
|
|
445
|
-
def index_exists?(table_name, column_name, **options)
|
|
457
|
+
def index_exists?(table_name, column_name = nil, **options)
|
|
446
458
|
column_names = Array(column_name).map(&:to_s)
|
|
447
459
|
options[:name] =
|
|
448
460
|
if options[:name].present?
|
|
@@ -460,9 +472,7 @@ module ActiveRecord
|
|
|
460
472
|
|
|
461
473
|
private
|
|
462
474
|
def compatible_table_definition(t)
|
|
463
|
-
|
|
464
|
-
prepend TableDefinition
|
|
465
|
-
end
|
|
475
|
+
t.singleton_class.prepend(TableDefinition)
|
|
466
476
|
super
|
|
467
477
|
end
|
|
468
478
|
|