activerecord 7.2.4 → 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 +1013 -878
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/association.rb +69 -27
- data/lib/active_record/associations/association_scope.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +2 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- 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 +7 -3
- data/lib/active_record/associations/collection_proxy.rb +26 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- 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_many_through_association.rb +3 -2
- data/lib/active_record/associations/has_one_association.rb +1 -0
- data/lib/active_record/associations/join_dependency.rb +3 -2
- data/lib/active_record/associations/preloader/association.rb +3 -3
- 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/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +192 -24
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +18 -5
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/base.rb +1 -2
- 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 +35 -28
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +429 -93
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +148 -74
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +124 -34
- data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +129 -63
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +132 -65
- 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 +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -66
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
- 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 +79 -61
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- 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 +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +156 -68
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +15 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +104 -37
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +41 -66
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +15 -18
- data/lib/active_record/connection_adapters.rb +1 -56
- data/lib/active_record/connection_handling.rb +25 -2
- data/lib/active_record/core.rb +33 -17
- data/lib/active_record/counter_cache.rb +35 -8
- data/lib/active_record/database_configurations/database_config.rb +9 -1
- data/lib/active_record/database_configurations/hash_config.rb +74 -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 +1 -1
- 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/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +8 -8
- data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
- data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
- data/lib/active_record/encryption/encryptor.rb +28 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +33 -30
- data/lib/active_record/errors.rb +33 -9
- 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/fixtures.rb +2 -4
- data/lib/active_record/future_result.rb +15 -9
- 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 +8 -1
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +31 -29
- 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 +56 -13
- data/lib/active_record/migration/compatibility.rb +37 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +48 -42
- data/lib/active_record/model_schema.rb +40 -15
- data/lib/active_record/nested_attributes.rb +14 -12
- data/lib/active_record/persistence.rb +163 -133
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +100 -52
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +35 -30
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +26 -38
- 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 +54 -22
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +147 -73
- data/lib/active_record/relation/calculations.rb +60 -43
- data/lib/active_record/relation/delegation.rb +25 -15
- data/lib/active_record/relation/finder_methods.rb +73 -43
- data/lib/active_record/relation/merger.rb +12 -10
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +23 -8
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +200 -96
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +2 -9
- data/lib/active_record/relation.rb +127 -87
- data/lib/active_record/result.rb +109 -24
- data/lib/active_record/runtime_registry.rb +42 -58
- data/lib/active_record/sanitization.rb +9 -6
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +17 -10
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +44 -16
- data/lib/active_record/statement_cache.rb +24 -20
- data/lib/active_record/store.rb +53 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +6 -23
- data/lib/active_record/tasks/abstract_tasks.rb +83 -0
- data/lib/active_record/tasks/database_tasks.rb +85 -85
- data/lib/active_record/tasks/mysql_database_tasks.rb +4 -58
- data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
- data/lib/active_record/test_databases.rb +18 -4
- data/lib/active_record/test_fixtures.rb +52 -6
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +56 -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/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +85 -50
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/crud.rb +6 -11
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/count.rb +2 -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.rb +0 -2
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/table.rb +3 -7
- data/lib/arel/visitors/dot.rb +0 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +4 -22
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +15 -12
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "active_support/core_ext/module/delegation"
|
|
4
3
|
|
|
5
4
|
module ActiveRecord
|
|
6
5
|
# This is a thread locals registry for EXPLAIN. For example
|
|
@@ -9,8 +8,53 @@ module ActiveRecord
|
|
|
9
8
|
#
|
|
10
9
|
# returns the collected queries local to the current thread.
|
|
11
10
|
class ExplainRegistry # :nodoc:
|
|
11
|
+
class Subscriber
|
|
12
|
+
MUTEX = Mutex.new
|
|
13
|
+
@subscribed = false
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
def ensure_subscribed
|
|
17
|
+
return if @subscribed
|
|
18
|
+
MUTEX.synchronize do
|
|
19
|
+
return if @subscribed
|
|
20
|
+
|
|
21
|
+
ActiveSupport::Notifications.subscribe("sql.active_record", new)
|
|
22
|
+
@subscribed = true
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def start(name, id, payload)
|
|
28
|
+
# unused
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def finish(name, id, payload)
|
|
32
|
+
if ExplainRegistry.collect? && !ignore_payload?(payload)
|
|
33
|
+
ExplainRegistry.queries << payload.values_at(:sql, :binds)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def silenced?(_name)
|
|
38
|
+
!ExplainRegistry.collect?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on
|
|
42
|
+
# our own EXPLAINs no matter how loopingly beautiful that would be.
|
|
43
|
+
#
|
|
44
|
+
# On the other hand, we want to monitor the performance of our real database
|
|
45
|
+
# queries, not the performance of the access to the query cache.
|
|
46
|
+
IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN)
|
|
47
|
+
EXPLAINED_SQLS = /\A\s*(\/\*.*\*\/)?\s*(with|select|update|delete|insert)\b/i
|
|
48
|
+
def ignore_payload?(payload)
|
|
49
|
+
payload[:exception] ||
|
|
50
|
+
payload[:cached] ||
|
|
51
|
+
IGNORED_PAYLOADS.include?(payload[:name]) ||
|
|
52
|
+
!payload[:sql].match?(EXPLAINED_SQLS)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
12
56
|
class << self
|
|
13
|
-
delegate :reset, :collect, :collect=, :collect?, :queries, to: :instance
|
|
57
|
+
delegate :start, :reset, :collect, :collect=, :collect?, :queries, to: :instance
|
|
14
58
|
|
|
15
59
|
private
|
|
16
60
|
def instance
|
|
@@ -25,6 +69,11 @@ module ActiveRecord
|
|
|
25
69
|
reset
|
|
26
70
|
end
|
|
27
71
|
|
|
72
|
+
def start
|
|
73
|
+
Subscriber.ensure_subscribed
|
|
74
|
+
@collect = true
|
|
75
|
+
end
|
|
76
|
+
|
|
28
77
|
def collect?
|
|
29
78
|
@collect
|
|
30
79
|
end
|
|
@@ -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
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require "erb"
|
|
4
4
|
require "yaml"
|
|
5
|
-
require "zlib"
|
|
6
|
-
require "set"
|
|
7
5
|
require "active_support/dependencies"
|
|
8
6
|
require "active_support/core_ext/digest/uuid"
|
|
9
7
|
require "active_record/test_fixtures"
|
|
@@ -244,10 +242,10 @@ module ActiveRecord
|
|
|
244
242
|
# and one for the humans. Why don't we generate the primary key instead?
|
|
245
243
|
# Hashing each fixture's label yields a consistent ID:
|
|
246
244
|
#
|
|
247
|
-
# george: # generated id:
|
|
245
|
+
# george: # generated id: 380982691
|
|
248
246
|
# name: George the Monkey
|
|
249
247
|
#
|
|
250
|
-
# reginald: # generated id:
|
|
248
|
+
# reginald: # generated id: 41001176
|
|
251
249
|
# name: Reginald the Pirate
|
|
252
250
|
#
|
|
253
251
|
# Active Record looks at the fixture's model class, discovers the correct
|
|
@@ -100,17 +100,23 @@ module ActiveRecord
|
|
|
100
100
|
def execute_or_skip
|
|
101
101
|
return unless pending?
|
|
102
102
|
|
|
103
|
-
@
|
|
104
|
-
return unless
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
103
|
+
@session.synchronize do
|
|
104
|
+
return unless pending?
|
|
105
|
+
|
|
106
|
+
@pool.with_connection do |connection|
|
|
107
|
+
return unless @mutex.try_lock
|
|
108
|
+
previous_instrumenter = ActiveSupport::IsolatedExecutionState[:active_record_instrumenter]
|
|
109
|
+
begin
|
|
110
|
+
if pending?
|
|
111
|
+
@event_buffer = EventBuffer.new(self, @instrumenter)
|
|
112
|
+
ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = @event_buffer
|
|
113
|
+
|
|
109
114
|
execute_query(connection, async: true)
|
|
110
115
|
end
|
|
116
|
+
ensure
|
|
117
|
+
ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = previous_instrumenter
|
|
118
|
+
@mutex.unlock
|
|
111
119
|
end
|
|
112
|
-
ensure
|
|
113
|
-
@mutex.unlock
|
|
114
120
|
end
|
|
115
121
|
end
|
|
116
122
|
end
|
|
@@ -163,7 +169,7 @@ module ActiveRecord
|
|
|
163
169
|
end
|
|
164
170
|
|
|
165
171
|
def exec_query(connection, *args, **kwargs)
|
|
166
|
-
connection.
|
|
172
|
+
connection.raw_exec_query(*args, **kwargs)
|
|
167
173
|
end
|
|
168
174
|
|
|
169
175
|
class SelectAll < FutureResult # :nodoc:
|
|
@@ -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
|
|
@@ -9,7 +9,7 @@ module ActiveRecord
|
|
|
9
9
|
# it was opened, an ActiveRecord::StaleObjectError exception is thrown if that has occurred
|
|
10
10
|
# and the update is ignored.
|
|
11
11
|
#
|
|
12
|
-
# Check out
|
|
12
|
+
# Check out ActiveRecord::Locking::Pessimistic for an alternative.
|
|
13
13
|
#
|
|
14
14
|
# == Usage
|
|
15
15
|
#
|
|
@@ -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
|
|
|
@@ -126,18 +138,8 @@ module ActiveRecord
|
|
|
126
138
|
end
|
|
127
139
|
end
|
|
128
140
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
Thread.each_caller_location do |location|
|
|
132
|
-
frame = backtrace_cleaner.clean_frame(location)
|
|
133
|
-
return frame if frame
|
|
134
|
-
end
|
|
135
|
-
nil
|
|
136
|
-
end
|
|
137
|
-
else
|
|
138
|
-
def query_source_location
|
|
139
|
-
backtrace_cleaner.clean(caller(1).lazy).first
|
|
140
|
-
end
|
|
141
|
+
def query_source_location
|
|
142
|
+
backtrace_cleaner.first_clean_frame
|
|
141
143
|
end
|
|
142
144
|
|
|
143
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
|