activerecord 7.1.5.2 → 8.1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +567 -2443
- data/README.rdoc +16 -16
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +3 -2
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +43 -12
- data/lib/active_record/associations/belongs_to_association.rb +31 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +18 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +17 -9
- data/lib/active_record/associations/collection_proxy.rb +36 -5
- 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 +268 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +4 -3
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +8 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +14 -3
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +250 -315
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +25 -61
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +20 -27
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
- data/lib/active_record/attribute_methods.rb +71 -75
- data/lib/active_record/attributes.rb +96 -68
- data/lib/active_record/autosave_association.rb +93 -58
- data/lib/active_record/base.rb +5 -7
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
- data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
- data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
- 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 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
- data/lib/active_record/connection_adapters/pool_config.rb +14 -13
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
- 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 +44 -41
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
- data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
- data/lib/active_record/connection_adapters.rb +66 -0
- data/lib/active_record/connection_handling.rb +88 -46
- data/lib/active_record/core.rb +142 -58
- data/lib/active_record/counter_cache.rb +52 -18
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
- data/lib/active_record/database_configurations/database_config.rb +28 -5
- data/lib/active_record/database_configurations/hash_config.rb +103 -41
- data/lib/active_record/database_configurations/url_config.rb +30 -1
- data/lib/active_record/database_configurations.rb +8 -4
- data/lib/active_record/delegated_type.rb +43 -19
- data/lib/active_record/dynamic_matchers.rb +56 -71
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +12 -12
- data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
- data/lib/active_record/encryption/encryptor.rb +58 -22
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +57 -36
- data/lib/active_record/errors.rb +76 -26
- data/lib/active_record/explain.rb +14 -25
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +39 -35
- data/lib/active_record/future_result.rb +21 -13
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +5 -3
- data/lib/active_record/insert_all.rb +28 -20
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +15 -7
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -34
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +46 -14
- data/lib/active_record/migration/compatibility.rb +40 -25
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +119 -103
- data/lib/active_record/model_schema.rb +66 -78
- data/lib/active_record/nested_attributes.rb +17 -9
- data/lib/active_record/persistence.rb +161 -454
- data/lib/active_record/query_cache.rb +33 -15
- data/lib/active_record/query_logs.rb +106 -39
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +28 -16
- data/lib/active_record/railtie.rb +61 -74
- data/lib/active_record/railties/controller_runtime.rb +19 -5
- data/lib/active_record/railties/databases.rake +64 -78
- 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 +147 -53
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +153 -73
- data/lib/active_record/relation/calculations.rb +146 -96
- data/lib/active_record/relation/delegation.rb +30 -23
- data/lib/active_record/relation/finder_methods.rb +64 -34
- data/lib/active_record/relation/merger.rb +12 -14
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
- 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 +25 -10
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +351 -124
- data/lib/active_record/relation/spawn_methods.rb +9 -25
- data/lib/active_record/relation/where_clause.rb +17 -30
- data/lib/active_record/relation.rb +590 -124
- data/lib/active_record/result.rb +131 -59
- data/lib/active_record/runtime_registry.rb +44 -21
- data/lib/active_record/sanitization.rb +33 -25
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +66 -31
- data/lib/active_record/schema_migration.rb +31 -14
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +68 -20
- data/lib/active_record/statement_cache.rb +29 -25
- data/lib/active_record/store.rb +51 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +7 -33
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +118 -90
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +123 -89
- data/lib/active_record/testing/query_assertions.rb +127 -0
- data/lib/active_record/timestamp.rb +6 -4
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +129 -0
- data/lib/active_record/transactions.rb +97 -18
- data/lib/active_record/translation.rb +0 -2
- 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 +15 -2
- data/lib/active_record/type/serialized.rb +12 -7
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +5 -4
- data/lib/active_record/validations/associated.rb +10 -4
- data/lib/active_record/validations/uniqueness.rb +23 -18
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +203 -59
- data/lib/arel/alias_predication.rb +3 -1
- data/lib/arel/collectors/bind.rb +4 -2
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +2 -2
- data/lib/arel/collectors/substitute_binds.rb +3 -3
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -7
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- 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/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +6 -5
- data/lib/arel/nodes/sql_literal.rb +8 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +2 -4
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +14 -5
- data/lib/arel/table.rb +3 -7
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +7 -1
- data/lib/arel/visitors/dot.rb +3 -3
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +56 -12
- data/lib/arel/visitors/sqlite.rb +80 -8
- data/lib/arel/visitors/to_sql.rb +34 -37
- data/lib/arel.rb +10 -4
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +23 -14
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -167
- data/lib/active_record/relation/record_fetch_warning.rb +0 -49
|
@@ -11,16 +11,9 @@ module ActiveRecord
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
# Queries the database and returns the results in an Array-like object
|
|
14
|
-
def query(sql, name = nil) # :nodoc:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
log(sql, name) do
|
|
18
|
-
with_raw_connection do |conn|
|
|
19
|
-
result = conn.async_exec(sql).map_types!(@type_map_for_results).values
|
|
20
|
-
verified!
|
|
21
|
-
result
|
|
22
|
-
end
|
|
23
|
-
end
|
|
14
|
+
def query(sql, name = nil, allow_retry: true, materialize_transactions: true) # :nodoc:
|
|
15
|
+
result = internal_execute(sql, name, allow_retry:, materialize_transactions:)
|
|
16
|
+
result.map_types!(@type_map_for_results).values
|
|
24
17
|
end
|
|
25
18
|
|
|
26
19
|
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
|
|
@@ -49,35 +42,6 @@ module ActiveRecord
|
|
|
49
42
|
@notice_receiver_sql_warnings = []
|
|
50
43
|
end
|
|
51
44
|
|
|
52
|
-
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
|
53
|
-
log(sql, name, async: async) do
|
|
54
|
-
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
|
55
|
-
result = conn.async_exec(sql)
|
|
56
|
-
verified!
|
|
57
|
-
handle_warnings(result)
|
|
58
|
-
result
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true) # :nodoc:
|
|
64
|
-
execute_and_clear(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |result|
|
|
65
|
-
types = {}
|
|
66
|
-
fields = result.fields
|
|
67
|
-
fields.each_with_index do |fname, i|
|
|
68
|
-
ftype = result.ftype i
|
|
69
|
-
fmod = result.fmod i
|
|
70
|
-
types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
|
|
71
|
-
end
|
|
72
|
-
build_result(columns: fields, rows: result.values, column_types: types)
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def exec_delete(sql, name = nil, binds = []) # :nodoc:
|
|
77
|
-
execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
|
|
78
|
-
end
|
|
79
|
-
alias :exec_update :exec_delete
|
|
80
|
-
|
|
81
45
|
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc:
|
|
82
46
|
if use_insert_returning? || pk == false
|
|
83
47
|
super
|
|
@@ -122,7 +86,7 @@ module ActiveRecord
|
|
|
122
86
|
end
|
|
123
87
|
|
|
124
88
|
# From https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
|
|
125
|
-
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
|
|
89
|
+
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP", retryable: true).freeze # :nodoc:
|
|
126
90
|
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
|
|
127
91
|
|
|
128
92
|
def high_precision_current_timestamp
|
|
@@ -135,6 +99,27 @@ module ActiveRecord
|
|
|
135
99
|
"EXPLAIN (#{options.join(", ").upcase})"
|
|
136
100
|
end
|
|
137
101
|
|
|
102
|
+
# Set when constraints will be checked for the current transaction.
|
|
103
|
+
#
|
|
104
|
+
# Not passing any specific constraint names will set the value for all deferrable constraints.
|
|
105
|
+
#
|
|
106
|
+
# [<tt>deferred</tt>]
|
|
107
|
+
# Valid values are +:deferred+ or +:immediate+.
|
|
108
|
+
#
|
|
109
|
+
# See https://www.postgresql.org/docs/current/sql-set-constraints.html
|
|
110
|
+
def set_constraints(deferred, *constraints)
|
|
111
|
+
unless %i[deferred immediate].include?(deferred)
|
|
112
|
+
raise ArgumentError, "deferred must be :deferred or :immediate"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
constraints = if constraints.empty?
|
|
116
|
+
"ALL"
|
|
117
|
+
else
|
|
118
|
+
constraints.map { |c| quote_table_name(c) }.join(", ")
|
|
119
|
+
end
|
|
120
|
+
execute("SET CONSTRAINTS #{constraints} #{deferred.to_s.upcase}")
|
|
121
|
+
end
|
|
122
|
+
|
|
138
123
|
private
|
|
139
124
|
IDLE_TRANSACTION_STATUSES = [PG::PQTRANS_IDLE, PG::PQTRANS_INTRANS, PG::PQTRANS_INERROR]
|
|
140
125
|
private_constant :IDLE_TRANSACTION_STATUSES
|
|
@@ -142,13 +127,82 @@ module ActiveRecord
|
|
|
142
127
|
def cancel_any_running_query
|
|
143
128
|
return if @raw_connection.nil? || IDLE_TRANSACTION_STATUSES.include?(@raw_connection.transaction_status)
|
|
144
129
|
|
|
145
|
-
@raw_connection.cancel
|
|
130
|
+
# Skip @raw_connection.cancel (PG::Connection#cancel) when using libpq >= 18 with pg < 1.6.0,
|
|
131
|
+
# because the pg gem cannot obtain the backend_key in that case.
|
|
132
|
+
# This method is only called from exec_rollback_db_transaction and exec_restart_db_transaction.
|
|
133
|
+
# Even without cancel, rollback will still run. However, since any running
|
|
134
|
+
# query must finish first, the rollback may take longer.
|
|
135
|
+
if !(PG.library_version >= 18_00_00 && Gem::Version.new(PG::VERSION) < Gem::Version.new("1.6.0"))
|
|
136
|
+
@raw_connection.cancel
|
|
137
|
+
end
|
|
146
138
|
@raw_connection.block
|
|
147
139
|
rescue PG::Error
|
|
148
140
|
end
|
|
149
141
|
|
|
150
|
-
def
|
|
151
|
-
|
|
142
|
+
def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
|
|
143
|
+
update_typemap_for_default_timezone
|
|
144
|
+
result = if prepare
|
|
145
|
+
begin
|
|
146
|
+
stmt_key = prepare_statement(sql, binds, raw_connection)
|
|
147
|
+
notification_payload[:statement_name] = stmt_key
|
|
148
|
+
raw_connection.exec_prepared(stmt_key, type_casted_binds)
|
|
149
|
+
rescue PG::FeatureNotSupported => error
|
|
150
|
+
if is_cached_plan_failure?(error)
|
|
151
|
+
# Nothing we can do if we are in a transaction because all commands
|
|
152
|
+
# will raise InFailedSQLTransaction
|
|
153
|
+
if in_transaction?
|
|
154
|
+
raise PreparedStatementCacheExpired.new(error.message, connection_pool: @pool)
|
|
155
|
+
else
|
|
156
|
+
@lock.synchronize do
|
|
157
|
+
# outside of transactions we can simply flush this query and retry
|
|
158
|
+
@statements.delete sql_key(sql)
|
|
159
|
+
end
|
|
160
|
+
retry
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
raise
|
|
165
|
+
end
|
|
166
|
+
elsif binds.nil? || binds.empty?
|
|
167
|
+
raw_connection.async_exec(sql)
|
|
168
|
+
else
|
|
169
|
+
raw_connection.exec_params(sql, type_casted_binds)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
verified!
|
|
173
|
+
|
|
174
|
+
notification_payload[:affected_rows] = result.cmd_tuples
|
|
175
|
+
notification_payload[:row_count] = result.ntuples
|
|
176
|
+
result
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def cast_result(result)
|
|
180
|
+
ar_result = if result.fields.empty?
|
|
181
|
+
ActiveRecord::Result.empty(affected_rows: result.cmd_tuples)
|
|
182
|
+
else
|
|
183
|
+
fields = result.fields
|
|
184
|
+
types = Array.new(fields.size)
|
|
185
|
+
fields.size.times do |index|
|
|
186
|
+
ftype = result.ftype(index)
|
|
187
|
+
fmod = result.fmod(index)
|
|
188
|
+
types[index] = get_oid_type(ftype, fmod, fields[index])
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
ActiveRecord::Result.new(fields, result.values, types.freeze, affected_rows: result.cmd_tuples)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
result.clear
|
|
195
|
+
ar_result
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def affected_rows(result)
|
|
199
|
+
affected_rows = result.cmd_tuples
|
|
200
|
+
result.clear
|
|
201
|
+
affected_rows
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def execute_batch(statements, name = nil, **kwargs)
|
|
205
|
+
raw_execute(combine_multi_statements(statements), name, batch: true, **kwargs)
|
|
152
206
|
end
|
|
153
207
|
|
|
154
208
|
def build_truncate_statements(table_names)
|
|
@@ -168,7 +222,7 @@ module ActiveRecord
|
|
|
168
222
|
pk unless pk.is_a?(Array)
|
|
169
223
|
end
|
|
170
224
|
|
|
171
|
-
def handle_warnings(sql)
|
|
225
|
+
def handle_warnings(result, sql)
|
|
172
226
|
@notice_receiver_sql_warnings.each do |warning|
|
|
173
227
|
next if warning_ignored?(warning)
|
|
174
228
|
|
|
@@ -16,8 +16,8 @@ module ActiveRecord
|
|
|
16
16
|
@subtype = subtype
|
|
17
17
|
@delimiter = delimiter
|
|
18
18
|
|
|
19
|
-
@pg_encoder = PG::TextEncoder::Array.new
|
|
20
|
-
@pg_decoder = PG::TextDecoder::Array.new
|
|
19
|
+
@pg_encoder = PG::TextEncoder::Array.new(name: "#{type}[]".freeze, delimiter: delimiter).freeze
|
|
20
|
+
@pg_decoder = PG::TextDecoder::Array.new(name: "#{type}[]".freeze, delimiter: delimiter).freeze
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def deserialize(value)
|
|
@@ -65,7 +65,7 @@ module ActiveRecord
|
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def map(value, &block)
|
|
68
|
-
value.map
|
|
68
|
+
value.is_a?(::Array) ? value.map(&block) : subtype.map(value, &block)
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
def changed_in_place?(raw_old_value, new_value)
|
|
@@ -33,7 +33,7 @@ module ActiveRecord
|
|
|
33
33
|
when ::Numeric
|
|
34
34
|
# Sometimes operations on Times returns just float number of seconds so we need to handle that.
|
|
35
35
|
# Example: Time.current - (Time.current + 1.hour) # => -3600.000001776 (Float)
|
|
36
|
-
value.
|
|
36
|
+
ActiveSupport::Duration.build(value).iso8601(precision: self.precision)
|
|
37
37
|
else
|
|
38
38
|
super
|
|
39
39
|
end
|
|
@@ -25,6 +25,10 @@ module ActiveRecord
|
|
|
25
25
|
build_point(x, y)
|
|
26
26
|
when ::Array
|
|
27
27
|
build_point(*value)
|
|
28
|
+
when ::Hash
|
|
29
|
+
return if value.blank?
|
|
30
|
+
|
|
31
|
+
build_point(*values_array_from_hash(value))
|
|
28
32
|
else
|
|
29
33
|
value
|
|
30
34
|
end
|
|
@@ -36,6 +40,8 @@ module ActiveRecord
|
|
|
36
40
|
"(#{number_for_point(value.x)},#{number_for_point(value.y)})"
|
|
37
41
|
when ::Array
|
|
38
42
|
serialize(build_point(*value))
|
|
43
|
+
when ::Hash
|
|
44
|
+
serialize(build_point(*values_array_from_hash(value)))
|
|
39
45
|
else
|
|
40
46
|
super
|
|
41
47
|
end
|
|
@@ -57,6 +63,10 @@ module ActiveRecord
|
|
|
57
63
|
def build_point(x, y)
|
|
58
64
|
ActiveRecord::Point.new(Float(x), Float(y))
|
|
59
65
|
end
|
|
66
|
+
|
|
67
|
+
def values_array_from_hash(value)
|
|
68
|
+
[value.values_at(:x, "x").compact.first, value.values_at(:y, "y").compact.first]
|
|
69
|
+
end
|
|
60
70
|
end
|
|
61
71
|
end
|
|
62
72
|
end
|
|
@@ -6,6 +6,7 @@ module ActiveRecord
|
|
|
6
6
|
module OID # :nodoc:
|
|
7
7
|
class Uuid < Type::Value # :nodoc:
|
|
8
8
|
ACCEPTABLE_UUID = %r{\A(\{)?([a-fA-F0-9]{4}-?){8}(?(1)\}|)\z}
|
|
9
|
+
CANONICAL_UUID = %r{\A[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}\z}
|
|
9
10
|
|
|
10
11
|
alias :serialize :deserialize
|
|
11
12
|
|
|
@@ -15,18 +16,27 @@ module ActiveRecord
|
|
|
15
16
|
|
|
16
17
|
def changed?(old_value, new_value, _new_value_before_type_cast)
|
|
17
18
|
old_value.class != new_value.class ||
|
|
18
|
-
new_value
|
|
19
|
+
new_value != old_value
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def changed_in_place?(raw_old_value, new_value)
|
|
22
23
|
raw_old_value.class != new_value.class ||
|
|
23
|
-
new_value
|
|
24
|
+
new_value != raw_old_value
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
private
|
|
27
28
|
def cast_value(value)
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
value = value.to_s
|
|
30
|
+
format_uuid(value) if value.match?(ACCEPTABLE_UUID)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def format_uuid(uuid)
|
|
34
|
+
if uuid.match?(CANONICAL_UUID)
|
|
35
|
+
uuid
|
|
36
|
+
else
|
|
37
|
+
uuid = uuid.delete("{}-").downcase
|
|
38
|
+
"#{uuid[..7]}-#{uuid[8..11]}-#{uuid[12..15]}-#{uuid[16..19]}-#{uuid[20..]}"
|
|
39
|
+
end
|
|
30
40
|
end
|
|
31
41
|
end
|
|
32
42
|
end
|
|
@@ -4,9 +4,62 @@ module ActiveRecord
|
|
|
4
4
|
module ConnectionAdapters
|
|
5
5
|
module PostgreSQL
|
|
6
6
|
module Quoting
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
7
9
|
QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
|
|
8
10
|
QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
|
|
9
11
|
|
|
12
|
+
module ClassMethods # :nodoc:
|
|
13
|
+
def column_name_matcher
|
|
14
|
+
/
|
|
15
|
+
\A
|
|
16
|
+
(
|
|
17
|
+
(?:
|
|
18
|
+
# "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
|
|
19
|
+
((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
|
|
20
|
+
)
|
|
21
|
+
(?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
|
|
22
|
+
)
|
|
23
|
+
(?:\s*,\s*\g<1>)*
|
|
24
|
+
\z
|
|
25
|
+
/ix
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def column_name_with_order_matcher
|
|
29
|
+
/
|
|
30
|
+
\A
|
|
31
|
+
(
|
|
32
|
+
(?:
|
|
33
|
+
# "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
|
|
34
|
+
((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
|
|
35
|
+
)
|
|
36
|
+
(?:\s+COLLATE\s+"\w+")?
|
|
37
|
+
(?:\s+ASC|\s+DESC)?
|
|
38
|
+
(?:\s+NULLS\s+(?:FIRST|LAST))?
|
|
39
|
+
)
|
|
40
|
+
(?:\s*,\s*\g<1>)*
|
|
41
|
+
\z
|
|
42
|
+
/ix
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Quotes column names for use in SQL queries.
|
|
46
|
+
def quote_column_name(name) # :nodoc:
|
|
47
|
+
QUOTED_COLUMN_NAMES[name] ||= PG::Connection.quote_ident(name.to_s).freeze
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Checks the following cases:
|
|
51
|
+
#
|
|
52
|
+
# - table_name
|
|
53
|
+
# - "table.name"
|
|
54
|
+
# - schema_name.table_name
|
|
55
|
+
# - schema_name."table.name"
|
|
56
|
+
# - "schema.name".table_name
|
|
57
|
+
# - "schema.name"."table.name"
|
|
58
|
+
def quote_table_name(name) # :nodoc:
|
|
59
|
+
QUOTED_TABLE_NAMES[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
10
63
|
class IntegerOutOf64BitRange < StandardError
|
|
11
64
|
def initialize(msg)
|
|
12
65
|
super(msg)
|
|
@@ -77,30 +130,13 @@ module ActiveRecord
|
|
|
77
130
|
end
|
|
78
131
|
end
|
|
79
132
|
|
|
80
|
-
# Checks the following cases:
|
|
81
|
-
#
|
|
82
|
-
# - table_name
|
|
83
|
-
# - "table.name"
|
|
84
|
-
# - schema_name.table_name
|
|
85
|
-
# - schema_name."table.name"
|
|
86
|
-
# - "schema.name".table_name
|
|
87
|
-
# - "schema.name"."table.name"
|
|
88
|
-
def quote_table_name(name) # :nodoc:
|
|
89
|
-
QUOTED_TABLE_NAMES[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# Quotes schema names for use in SQL queries.
|
|
93
|
-
def quote_schema_name(name)
|
|
94
|
-
PG::Connection.quote_ident(name)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
133
|
def quote_table_name_for_assignment(table, attr)
|
|
98
134
|
quote_column_name(attr)
|
|
99
135
|
end
|
|
100
136
|
|
|
101
|
-
# Quotes
|
|
102
|
-
def
|
|
103
|
-
|
|
137
|
+
# Quotes schema names for use in SQL queries.
|
|
138
|
+
def quote_schema_name(schema_name)
|
|
139
|
+
quote_column_name(schema_name)
|
|
104
140
|
end
|
|
105
141
|
|
|
106
142
|
# Quote date/time values for use in SQL input.
|
|
@@ -117,14 +153,15 @@ module ActiveRecord
|
|
|
117
153
|
"'#{escape_bytea(value.to_s)}'"
|
|
118
154
|
end
|
|
119
155
|
|
|
156
|
+
# `column` may be either an instance of Column or ColumnDefinition.
|
|
120
157
|
def quote_default_expression(value, column) # :nodoc:
|
|
121
158
|
if value.is_a?(Proc)
|
|
122
159
|
value.call
|
|
123
160
|
elsif column.type == :uuid && value.is_a?(String) && value.include?("()")
|
|
124
161
|
value # Does not quote function default values for UUID columns
|
|
125
162
|
elsif column.respond_to?(:array?)
|
|
126
|
-
|
|
127
|
-
quote(
|
|
163
|
+
# TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
|
|
164
|
+
quote(column.fetch_cast_type(self).serialize(value))
|
|
128
165
|
else
|
|
129
166
|
super
|
|
130
167
|
end
|
|
@@ -143,59 +180,19 @@ module ActiveRecord
|
|
|
143
180
|
encode_array(value)
|
|
144
181
|
when Range
|
|
145
182
|
encode_range(value)
|
|
183
|
+
when Rational
|
|
184
|
+
value.to_f
|
|
146
185
|
else
|
|
147
186
|
super
|
|
148
187
|
end
|
|
149
188
|
end
|
|
150
189
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
def column_name_matcher
|
|
157
|
-
COLUMN_NAME
|
|
190
|
+
# TODO: Make this method private after we release 8.1.
|
|
191
|
+
def lookup_cast_type(sql_type) # :nodoc:
|
|
192
|
+
super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
|
|
158
193
|
end
|
|
159
194
|
|
|
160
|
-
def column_name_with_order_matcher
|
|
161
|
-
COLUMN_NAME_WITH_ORDER
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
COLUMN_NAME = /
|
|
165
|
-
\A
|
|
166
|
-
(
|
|
167
|
-
(?:
|
|
168
|
-
# "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
|
|
169
|
-
((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
|
|
170
|
-
)
|
|
171
|
-
(?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
|
|
172
|
-
)
|
|
173
|
-
(?:\s*,\s*\g<1>)*
|
|
174
|
-
\z
|
|
175
|
-
/ix
|
|
176
|
-
|
|
177
|
-
COLUMN_NAME_WITH_ORDER = /
|
|
178
|
-
\A
|
|
179
|
-
(
|
|
180
|
-
(?:
|
|
181
|
-
# "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
|
|
182
|
-
((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
|
|
183
|
-
)
|
|
184
|
-
(?:\s+COLLATE\s+"\w+")?
|
|
185
|
-
(?:\s+ASC|\s+DESC)?
|
|
186
|
-
(?:\s+NULLS\s+(?:FIRST|LAST))?
|
|
187
|
-
)
|
|
188
|
-
(?:\s*,\s*\g<1>)*
|
|
189
|
-
\z
|
|
190
|
-
/ix
|
|
191
|
-
|
|
192
|
-
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
|
|
193
|
-
|
|
194
195
|
private
|
|
195
|
-
def lookup_cast_type(sql_type)
|
|
196
|
-
super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
|
|
197
|
-
end
|
|
198
|
-
|
|
199
196
|
def encode_array(array_data)
|
|
200
197
|
encoder = array_data.encoder
|
|
201
198
|
values = type_cast_array(array_data.values)
|
|
@@ -208,7 +205,17 @@ module ActiveRecord
|
|
|
208
205
|
end
|
|
209
206
|
|
|
210
207
|
def encode_range(range)
|
|
211
|
-
|
|
208
|
+
lower_bound = type_cast_range_value(range.begin)
|
|
209
|
+
upper_bound = if date_or_time_range?(range)
|
|
210
|
+
# Postgres will convert `[today,]` to `[today,)`, making it exclusive.
|
|
211
|
+
# We can use the special timestamp value `infinity` to force inclusion.
|
|
212
|
+
# https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-INFINITE
|
|
213
|
+
range.end.nil? ? "infinity" : type_cast(range.end)
|
|
214
|
+
else
|
|
215
|
+
type_cast_range_value(range.end)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
"[#{lower_bound},#{upper_bound}#{range.exclude_end? ? ')' : ']'}"
|
|
212
219
|
end
|
|
213
220
|
|
|
214
221
|
def determine_encoding_of_strings_in_array(value)
|
|
@@ -232,6 +239,10 @@ module ActiveRecord
|
|
|
232
239
|
def infinity?(value)
|
|
233
240
|
value.respond_to?(:infinite?) && value.infinite?
|
|
234
241
|
end
|
|
242
|
+
|
|
243
|
+
def date_or_time_range?(range)
|
|
244
|
+
[range.begin.class, range.end.class].intersect?([Date, DateTime, Time])
|
|
245
|
+
end
|
|
235
246
|
end
|
|
236
247
|
end
|
|
237
248
|
end
|
|
@@ -45,12 +45,10 @@ Rails needs superuser privileges to disable referential integrity.
|
|
|
45
45
|
BEGIN
|
|
46
46
|
FOR r IN (
|
|
47
47
|
SELECT FORMAT(
|
|
48
|
-
'UPDATE pg_constraint SET convalidated=false WHERE conname = ''%I'' AND connamespace::regnamespace = ''%I''::regnamespace; ALTER TABLE %I.%I VALIDATE CONSTRAINT %I;',
|
|
48
|
+
'UPDATE pg_catalog.pg_constraint SET convalidated=false WHERE conname = ''%1$I'' AND connamespace::regnamespace = ''%2$I''::regnamespace; ALTER TABLE %2$I.%3$I VALIDATE CONSTRAINT %1$I;',
|
|
49
49
|
constraint_name,
|
|
50
50
|
table_schema,
|
|
51
|
-
|
|
52
|
-
table_name,
|
|
53
|
-
constraint_name
|
|
51
|
+
table_name
|
|
54
52
|
) AS constraint_check
|
|
55
53
|
FROM information_schema.table_constraints WHERE constraint_type = 'FOREIGN KEY'
|
|
56
54
|
)
|
|
@@ -6,19 +6,17 @@ module ActiveRecord
|
|
|
6
6
|
class SchemaCreation < SchemaCreation # :nodoc:
|
|
7
7
|
private
|
|
8
8
|
delegate :quoted_include_columns_for_index, to: :@conn
|
|
9
|
+
delegate :database_version, to: :@conn
|
|
9
10
|
|
|
10
11
|
def visit_AlterTable(o)
|
|
11
12
|
sql = super
|
|
12
13
|
sql << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
|
|
13
14
|
sql << o.exclusion_constraint_adds.map { |con| visit_AddExclusionConstraint con }.join(" ")
|
|
14
|
-
sql << o.exclusion_constraint_drops.map { |con| visit_DropExclusionConstraint con }.join(" ")
|
|
15
15
|
sql << o.unique_constraint_adds.map { |con| visit_AddUniqueConstraint con }.join(" ")
|
|
16
|
-
sql << o.unique_constraint_drops.map { |con| visit_DropUniqueConstraint con }.join(" ")
|
|
17
16
|
end
|
|
18
17
|
|
|
19
18
|
def visit_AddForeignKey(o)
|
|
20
19
|
super.dup.tap do |sql|
|
|
21
|
-
sql << " DEFERRABLE INITIALLY #{o.options[:deferrable].to_s.upcase}" if o.deferrable
|
|
22
20
|
sql << " NOT VALID" unless o.validate?
|
|
23
21
|
end
|
|
24
22
|
end
|
|
@@ -55,6 +53,7 @@ module ActiveRecord
|
|
|
55
53
|
sql = ["CONSTRAINT"]
|
|
56
54
|
sql << quote_column_name(o.name)
|
|
57
55
|
sql << "UNIQUE"
|
|
56
|
+
sql << "NULLS NOT DISTINCT" if supports_nulls_not_distinct? && o.nulls_not_distinct
|
|
58
57
|
|
|
59
58
|
if o.using_index
|
|
60
59
|
sql << "USING INDEX #{quote_column_name(o.using_index)}"
|
|
@@ -73,18 +72,10 @@ module ActiveRecord
|
|
|
73
72
|
"ADD #{accept(o)}"
|
|
74
73
|
end
|
|
75
74
|
|
|
76
|
-
def visit_DropExclusionConstraint(name)
|
|
77
|
-
"DROP CONSTRAINT #{quote_column_name(name)}"
|
|
78
|
-
end
|
|
79
|
-
|
|
80
75
|
def visit_AddUniqueConstraint(o)
|
|
81
76
|
"ADD #{accept(o)}"
|
|
82
77
|
end
|
|
83
78
|
|
|
84
|
-
def visit_DropUniqueConstraint(name)
|
|
85
|
-
"DROP CONSTRAINT #{quote_column_name(name)}"
|
|
86
|
-
end
|
|
87
|
-
|
|
88
79
|
def visit_ChangeColumnDefinition(o)
|
|
89
80
|
column = o.column
|
|
90
81
|
column.sql_type = type_to_sql(column.type, **column.options)
|
|
@@ -109,7 +100,7 @@ module ActiveRecord
|
|
|
109
100
|
if options[:default].nil?
|
|
110
101
|
change_column_sql << ", ALTER COLUMN #{quoted_column_name} DROP DEFAULT"
|
|
111
102
|
else
|
|
112
|
-
quoted_default =
|
|
103
|
+
quoted_default = quote_default_expression_for_column_definition(options[:default], column)
|
|
113
104
|
change_column_sql << ", ALTER COLUMN #{quoted_column_name} SET DEFAULT #{quoted_default}"
|
|
114
105
|
end
|
|
115
106
|
end
|
|
@@ -136,16 +127,17 @@ module ActiveRecord
|
|
|
136
127
|
end
|
|
137
128
|
|
|
138
129
|
if as = options[:as]
|
|
139
|
-
|
|
130
|
+
stored = options[:stored]
|
|
140
131
|
|
|
141
|
-
if
|
|
142
|
-
sql << " STORED"
|
|
143
|
-
else
|
|
132
|
+
if stored != true && database_version < 18_00_00
|
|
144
133
|
raise ArgumentError, <<~MSG
|
|
145
|
-
PostgreSQL
|
|
134
|
+
PostgreSQL versions before 18 do not support VIRTUAL (not persisted) generated columns.
|
|
146
135
|
Specify 'stored: true' option for '#{options[:column].name}'
|
|
147
136
|
MSG
|
|
148
137
|
end
|
|
138
|
+
|
|
139
|
+
sql << " GENERATED ALWAYS AS (#{as})"
|
|
140
|
+
sql << (stored ? " STORED" : " VIRTUAL")
|
|
149
141
|
end
|
|
150
142
|
super
|
|
151
143
|
end
|