activerecord 7.2.2.1 → 8.0.5
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 +523 -677
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +1 -0
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/association.rb +34 -10
- data/lib/active_record/associations/belongs_to_association.rb +18 -2
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/collection_association.rb +10 -8
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
- data/lib/active_record/associations/join_dependency.rb +2 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +34 -4
- 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 +1 -1
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
- data/lib/active_record/attribute_methods.rb +24 -19
- data/lib/active_record/attributes.rb +37 -26
- data/lib/active_record/autosave_association.rb +91 -39
- data/lib/active_record/base.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +34 -25
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +55 -47
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +91 -44
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -8
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +40 -10
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +92 -70
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +35 -46
- data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +50 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +84 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -9
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -43
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -24
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +60 -17
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +61 -99
- 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 +80 -100
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
- 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 +10 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +63 -17
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
- data/lib/active_record/connection_adapters.rb +0 -56
- data/lib/active_record/connection_handling.rb +35 -9
- data/lib/active_record/core.rb +57 -22
- data/lib/active_record/counter_cache.rb +1 -1
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- data/lib/active_record/database_configurations/database_config.rb +4 -0
- data/lib/active_record/database_configurations/hash_config.rb +16 -2
- data/lib/active_record/delegated_type.rb +18 -18
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +5 -5
- data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
- data/lib/active_record/encryption/encryptor.rb +37 -28
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/enum.rb +22 -34
- data/lib/active_record/errors.rb +16 -8
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +0 -2
- data/lib/active_record/future_result.rb +15 -9
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/insert_all.rb +3 -3
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +5 -11
- data/lib/active_record/migration/command_recorder.rb +32 -12
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +40 -43
- data/lib/active_record/model_schema.rb +3 -4
- data/lib/active_record/nested_attributes.rb +4 -6
- data/lib/active_record/persistence.rb +128 -130
- data/lib/active_record/query_logs.rb +106 -50
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +12 -12
- data/lib/active_record/railtie.rb +4 -28
- data/lib/active_record/railties/databases.rake +11 -34
- data/lib/active_record/reflection.rb +18 -21
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +132 -72
- data/lib/active_record/relation/calculations.rb +71 -65
- data/lib/active_record/relation/delegation.rb +25 -14
- data/lib/active_record/relation/finder_methods.rb +32 -31
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +13 -0
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +121 -70
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +9 -3
- data/lib/active_record/relation.rb +95 -67
- data/lib/active_record/result.rb +66 -4
- data/lib/active_record/sanitization.rb +7 -6
- data/lib/active_record/schema_dumper.rb +34 -11
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +7 -6
- data/lib/active_record/statement_cache.rb +14 -14
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/table_metadata.rb +1 -3
- data/lib/active_record/tasks/database_tasks.rb +69 -60
- data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
- data/lib/active_record/tasks/postgresql_database_tasks.rb +9 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +12 -0
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transactions.rb +8 -7
- data/lib/active_record/type/serialized.rb +5 -0
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +22 -49
- 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 +2 -0
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/select_manager.rb +6 -2
- data/lib/arel/table.rb +3 -7
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -0
- data/lib/arel/visitors/to_sql.rb +3 -1
- metadata +11 -15
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
|
@@ -5,9 +5,8 @@ module ActiveRecord
|
|
|
5
5
|
class PoolConfig # :nodoc:
|
|
6
6
|
include MonitorMixin
|
|
7
7
|
|
|
8
|
-
attr_reader :db_config, :role, :shard
|
|
8
|
+
attr_reader :db_config, :role, :shard, :connection_descriptor
|
|
9
9
|
attr_writer :schema_reflection, :server_version
|
|
10
|
-
attr_accessor :connection_class
|
|
11
10
|
|
|
12
11
|
def schema_reflection
|
|
13
12
|
@schema_reflection ||= SchemaReflection.new(db_config.lazy_schema_cache_path)
|
|
@@ -29,7 +28,7 @@ module ActiveRecord
|
|
|
29
28
|
def initialize(connection_class, db_config, role, shard)
|
|
30
29
|
super()
|
|
31
30
|
@server_version = nil
|
|
32
|
-
|
|
31
|
+
self.connection_descriptor = connection_class
|
|
33
32
|
@db_config = db_config
|
|
34
33
|
@role = role
|
|
35
34
|
@shard = shard
|
|
@@ -41,11 +40,12 @@ module ActiveRecord
|
|
|
41
40
|
@server_version || synchronize { @server_version ||= connection.get_database_version }
|
|
42
41
|
end
|
|
43
42
|
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
def connection_descriptor=(connection_descriptor)
|
|
44
|
+
case connection_descriptor
|
|
45
|
+
when ConnectionHandler::ConnectionDescriptor
|
|
46
|
+
@connection_descriptor = connection_descriptor
|
|
47
47
|
else
|
|
48
|
-
|
|
48
|
+
@connection_descriptor = ConnectionHandler::ConnectionDescriptor.new(connection_descriptor.name, connection_descriptor.primary_class?)
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
|
|
@@ -65,7 +65,8 @@ module ActiveRecord
|
|
|
65
65
|
other.is_a?(Column) &&
|
|
66
66
|
super &&
|
|
67
67
|
identity? == other.identity? &&
|
|
68
|
-
serial? == other.serial?
|
|
68
|
+
serial? == other.serial? &&
|
|
69
|
+
virtual? == other.virtual?
|
|
69
70
|
end
|
|
70
71
|
alias :eql? :==
|
|
71
72
|
|
|
@@ -73,7 +74,8 @@ module ActiveRecord
|
|
|
73
74
|
Column.hash ^
|
|
74
75
|
super.hash ^
|
|
75
76
|
identity?.hash ^
|
|
76
|
-
serial?.hash
|
|
77
|
+
serial?.hash ^
|
|
78
|
+
virtual?.hash
|
|
77
79
|
end
|
|
78
80
|
end
|
|
79
81
|
end
|
|
@@ -12,16 +12,8 @@ module ActiveRecord
|
|
|
12
12
|
|
|
13
13
|
# Queries the database and returns the results in an Array-like object
|
|
14
14
|
def query(sql, name = nil) # :nodoc:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
log(sql, name) do |notification_payload|
|
|
18
|
-
with_raw_connection do |conn|
|
|
19
|
-
result = conn.async_exec(sql).map_types!(@type_map_for_results).values
|
|
20
|
-
verified!
|
|
21
|
-
notification_payload[:row_count] = result.count
|
|
22
|
-
result
|
|
23
|
-
end
|
|
24
|
-
end
|
|
15
|
+
result = internal_execute(sql, name)
|
|
16
|
+
result.map_types!(@type_map_for_results).values
|
|
25
17
|
end
|
|
26
18
|
|
|
27
19
|
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
|
|
@@ -50,36 +42,6 @@ module ActiveRecord
|
|
|
50
42
|
@notice_receiver_sql_warnings = []
|
|
51
43
|
end
|
|
52
44
|
|
|
53
|
-
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
|
54
|
-
log(sql, name, async: async) do |notification_payload|
|
|
55
|
-
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
|
56
|
-
result = conn.async_exec(sql)
|
|
57
|
-
verified!
|
|
58
|
-
handle_warnings(result)
|
|
59
|
-
notification_payload[:row_count] = result.count
|
|
60
|
-
result
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true) # :nodoc:
|
|
66
|
-
execute_and_clear(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |result|
|
|
67
|
-
types = {}
|
|
68
|
-
fields = result.fields
|
|
69
|
-
fields.each_with_index do |fname, i|
|
|
70
|
-
ftype = result.ftype i
|
|
71
|
-
fmod = result.fmod i
|
|
72
|
-
types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
|
|
73
|
-
end
|
|
74
|
-
build_result(columns: fields, rows: result.values, column_types: types.freeze)
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def exec_delete(sql, name = nil, binds = []) # :nodoc:
|
|
79
|
-
execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
|
|
80
|
-
end
|
|
81
|
-
alias :exec_update :exec_delete
|
|
82
|
-
|
|
83
45
|
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc:
|
|
84
46
|
if use_insert_returning? || pk == false
|
|
85
47
|
super
|
|
@@ -165,13 +127,80 @@ module ActiveRecord
|
|
|
165
127
|
def cancel_any_running_query
|
|
166
128
|
return if @raw_connection.nil? || IDLE_TRANSACTION_STATUSES.include?(@raw_connection.transaction_status)
|
|
167
129
|
|
|
168
|
-
@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
|
|
169
138
|
@raw_connection.block
|
|
170
139
|
rescue PG::Error
|
|
171
140
|
end
|
|
172
141
|
|
|
173
|
-
def
|
|
174
|
-
|
|
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
|
+
handle_warnings(result)
|
|
174
|
+
notification_payload[:row_count] = result.ntuples
|
|
175
|
+
result
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def cast_result(result)
|
|
179
|
+
if result.fields.empty?
|
|
180
|
+
result.clear
|
|
181
|
+
return ActiveRecord::Result.empty
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
types = {}
|
|
185
|
+
fields = result.fields
|
|
186
|
+
fields.each_with_index do |fname, i|
|
|
187
|
+
ftype = result.ftype i
|
|
188
|
+
fmod = result.fmod i
|
|
189
|
+
types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
|
|
190
|
+
end
|
|
191
|
+
ar_result = ActiveRecord::Result.new(fields, result.values, types.freeze)
|
|
192
|
+
result.clear
|
|
193
|
+
ar_result
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def affected_rows(result)
|
|
197
|
+
affected_rows = result.cmd_tuples
|
|
198
|
+
result.clear
|
|
199
|
+
affected_rows
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def execute_batch(statements, name = nil, **kwargs)
|
|
203
|
+
raw_execute(combine_multi_statements(statements), name, batch: true, **kwargs)
|
|
175
204
|
end
|
|
176
205
|
|
|
177
206
|
def build_truncate_statements(table_names)
|
|
@@ -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
|
|
@@ -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
|
)
|
|
@@ -11,14 +11,11 @@ module ActiveRecord
|
|
|
11
11
|
sql = super
|
|
12
12
|
sql << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
|
|
13
13
|
sql << o.exclusion_constraint_adds.map { |con| visit_AddExclusionConstraint con }.join(" ")
|
|
14
|
-
sql << o.exclusion_constraint_drops.map { |con| visit_DropExclusionConstraint con }.join(" ")
|
|
15
14
|
sql << o.unique_constraint_adds.map { |con| visit_AddUniqueConstraint con }.join(" ")
|
|
16
|
-
sql << o.unique_constraint_drops.map { |con| visit_DropUniqueConstraint con }.join(" ")
|
|
17
15
|
end
|
|
18
16
|
|
|
19
17
|
def visit_AddForeignKey(o)
|
|
20
18
|
super.dup.tap do |sql|
|
|
21
|
-
sql << " DEFERRABLE INITIALLY #{o.options[:deferrable].to_s.upcase}" if o.deferrable
|
|
22
19
|
sql << " NOT VALID" unless o.validate?
|
|
23
20
|
end
|
|
24
21
|
end
|
|
@@ -55,6 +52,7 @@ module ActiveRecord
|
|
|
55
52
|
sql = ["CONSTRAINT"]
|
|
56
53
|
sql << quote_column_name(o.name)
|
|
57
54
|
sql << "UNIQUE"
|
|
55
|
+
sql << "NULLS NOT DISTINCT" if supports_nulls_not_distinct? && o.nulls_not_distinct
|
|
58
56
|
|
|
59
57
|
if o.using_index
|
|
60
58
|
sql << "USING INDEX #{quote_column_name(o.using_index)}"
|
|
@@ -73,18 +71,10 @@ module ActiveRecord
|
|
|
73
71
|
"ADD #{accept(o)}"
|
|
74
72
|
end
|
|
75
73
|
|
|
76
|
-
def visit_DropExclusionConstraint(name)
|
|
77
|
-
"DROP CONSTRAINT #{quote_column_name(name)}"
|
|
78
|
-
end
|
|
79
|
-
|
|
80
74
|
def visit_AddUniqueConstraint(o)
|
|
81
75
|
"ADD #{accept(o)}"
|
|
82
76
|
end
|
|
83
77
|
|
|
84
|
-
def visit_DropUniqueConstraint(name)
|
|
85
|
-
"DROP CONSTRAINT #{quote_column_name(name)}"
|
|
86
|
-
end
|
|
87
|
-
|
|
88
78
|
def visit_ChangeColumnDefinition(o)
|
|
89
79
|
column = o.column
|
|
90
80
|
column.sql_type = type_to_sql(column.type, **column.options)
|
|
@@ -224,11 +224,17 @@ module ActiveRecord
|
|
|
224
224
|
options[:using_index]
|
|
225
225
|
end
|
|
226
226
|
|
|
227
|
+
def nulls_not_distinct
|
|
228
|
+
options[:nulls_not_distinct]
|
|
229
|
+
end
|
|
230
|
+
|
|
227
231
|
def export_name_on_schema_dump?
|
|
228
232
|
!ActiveRecord::SchemaDumper.unique_ignore_pattern.match?(name) if name
|
|
229
233
|
end
|
|
230
234
|
|
|
231
235
|
def defined_for?(name: nil, column: nil, **options)
|
|
236
|
+
options = options.slice(*self.options.keys)
|
|
237
|
+
|
|
232
238
|
(name.nil? || self.name == name.to_s) &&
|
|
233
239
|
(column.nil? || Array(self.column) == Array(column).map(&:to_s)) &&
|
|
234
240
|
options.all? { |k, v| self.options[k].to_s == v.to_s }
|
|
@@ -302,8 +308,8 @@ module ActiveRecord
|
|
|
302
308
|
# t.exclusion_constraint("price WITH =, availability_range WITH &&", using: :gist, name: "price_check")
|
|
303
309
|
#
|
|
304
310
|
# See {connection.add_exclusion_constraint}[rdoc-ref:SchemaStatements#add_exclusion_constraint]
|
|
305
|
-
def exclusion_constraint(
|
|
306
|
-
@base.add_exclusion_constraint(name,
|
|
311
|
+
def exclusion_constraint(...)
|
|
312
|
+
@base.add_exclusion_constraint(name, ...)
|
|
307
313
|
end
|
|
308
314
|
|
|
309
315
|
# Removes the given exclusion constraint from the table.
|
|
@@ -311,17 +317,17 @@ module ActiveRecord
|
|
|
311
317
|
# t.remove_exclusion_constraint(name: "price_check")
|
|
312
318
|
#
|
|
313
319
|
# See {connection.remove_exclusion_constraint}[rdoc-ref:SchemaStatements#remove_exclusion_constraint]
|
|
314
|
-
def remove_exclusion_constraint(
|
|
315
|
-
@base.remove_exclusion_constraint(name,
|
|
320
|
+
def remove_exclusion_constraint(...)
|
|
321
|
+
@base.remove_exclusion_constraint(name, ...)
|
|
316
322
|
end
|
|
317
323
|
|
|
318
324
|
# Adds a unique constraint.
|
|
319
325
|
#
|
|
320
|
-
# t.unique_constraint(:position, name: 'unique_position', deferrable: :deferred)
|
|
326
|
+
# t.unique_constraint(:position, name: 'unique_position', deferrable: :deferred, nulls_not_distinct: true)
|
|
321
327
|
#
|
|
322
328
|
# See {connection.add_unique_constraint}[rdoc-ref:SchemaStatements#add_unique_constraint]
|
|
323
|
-
def unique_constraint(
|
|
324
|
-
@base.add_unique_constraint(name,
|
|
329
|
+
def unique_constraint(...)
|
|
330
|
+
@base.add_unique_constraint(name, ...)
|
|
325
331
|
end
|
|
326
332
|
|
|
327
333
|
# Removes the given unique constraint from the table.
|
|
@@ -329,8 +335,8 @@ module ActiveRecord
|
|
|
329
335
|
# t.remove_unique_constraint(name: "unique_position")
|
|
330
336
|
#
|
|
331
337
|
# See {connection.remove_unique_constraint}[rdoc-ref:SchemaStatements#remove_unique_constraint]
|
|
332
|
-
def remove_unique_constraint(
|
|
333
|
-
@base.remove_unique_constraint(name,
|
|
338
|
+
def remove_unique_constraint(...)
|
|
339
|
+
@base.remove_unique_constraint(name, ...)
|
|
334
340
|
end
|
|
335
341
|
|
|
336
342
|
# Validates the given constraint on the table.
|
|
@@ -339,8 +345,8 @@ module ActiveRecord
|
|
|
339
345
|
# t.validate_constraint "price_check"
|
|
340
346
|
#
|
|
341
347
|
# See {connection.validate_constraint}[rdoc-ref:SchemaStatements#validate_constraint]
|
|
342
|
-
def validate_constraint(
|
|
343
|
-
@base.validate_constraint(name,
|
|
348
|
+
def validate_constraint(...)
|
|
349
|
+
@base.validate_constraint(name, ...)
|
|
344
350
|
end
|
|
345
351
|
|
|
346
352
|
# Validates the given check constraint on the table
|
|
@@ -349,22 +355,20 @@ module ActiveRecord
|
|
|
349
355
|
# t.validate_check_constraint name: "price_check"
|
|
350
356
|
#
|
|
351
357
|
# See {connection.validate_check_constraint}[rdoc-ref:SchemaStatements#validate_check_constraint]
|
|
352
|
-
def validate_check_constraint(
|
|
353
|
-
@base.validate_check_constraint(name,
|
|
358
|
+
def validate_check_constraint(...)
|
|
359
|
+
@base.validate_check_constraint(name, ...)
|
|
354
360
|
end
|
|
355
361
|
end
|
|
356
362
|
|
|
357
363
|
# = Active Record PostgreSQL Adapter Alter \Table
|
|
358
364
|
class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
|
|
359
|
-
attr_reader :constraint_validations, :exclusion_constraint_adds, :
|
|
365
|
+
attr_reader :constraint_validations, :exclusion_constraint_adds, :unique_constraint_adds
|
|
360
366
|
|
|
361
367
|
def initialize(td)
|
|
362
368
|
super
|
|
363
369
|
@constraint_validations = []
|
|
364
370
|
@exclusion_constraint_adds = []
|
|
365
|
-
@exclusion_constraint_drops = []
|
|
366
371
|
@unique_constraint_adds = []
|
|
367
|
-
@unique_constraint_drops = []
|
|
368
372
|
end
|
|
369
373
|
|
|
370
374
|
def validate_constraint(name)
|
|
@@ -375,17 +379,9 @@ module ActiveRecord
|
|
|
375
379
|
@exclusion_constraint_adds << @td.new_exclusion_constraint_definition(expression, options)
|
|
376
380
|
end
|
|
377
381
|
|
|
378
|
-
def drop_exclusion_constraint(constraint_name)
|
|
379
|
-
@exclusion_constraint_drops << constraint_name
|
|
380
|
-
end
|
|
381
|
-
|
|
382
382
|
def add_unique_constraint(column_name, options)
|
|
383
383
|
@unique_constraint_adds << @td.new_unique_constraint_definition(column_name, options)
|
|
384
384
|
end
|
|
385
|
-
|
|
386
|
-
def drop_unique_constraint(unique_constraint_name)
|
|
387
|
-
@unique_constraint_drops << unique_constraint_name
|
|
388
|
-
end
|
|
389
385
|
end
|
|
390
386
|
end
|
|
391
387
|
end
|
|
@@ -22,7 +22,7 @@ module ActiveRecord
|
|
|
22
22
|
stream.puts " # Custom types defined in this database."
|
|
23
23
|
stream.puts " # Note that some types may not work with other database engines. Be careful if changing database."
|
|
24
24
|
types.sort.each do |name, values|
|
|
25
|
-
stream.puts " create_enum #{name.inspect}, #{values.
|
|
25
|
+
stream.puts " create_enum #{name.inspect}, #{values.inspect}"
|
|
26
26
|
end
|
|
27
27
|
stream.puts
|
|
28
28
|
end
|
|
@@ -68,6 +68,7 @@ module ActiveRecord
|
|
|
68
68
|
"t.unique_constraint #{unique_constraint.column.inspect}"
|
|
69
69
|
]
|
|
70
70
|
|
|
71
|
+
parts << "nulls_not_distinct: #{unique_constraint.nulls_not_distinct.inspect}" if unique_constraint.nulls_not_distinct
|
|
71
72
|
parts << "deferrable: #{unique_constraint.deferrable.inspect}" if unique_constraint.deferrable
|
|
72
73
|
|
|
73
74
|
if unique_constraint.export_name_on_schema_dump?
|
|
@@ -91,7 +92,7 @@ module ActiveRecord
|
|
|
91
92
|
spec = { type: schema_type(column).inspect }.merge!(spec)
|
|
92
93
|
end
|
|
93
94
|
|
|
94
|
-
spec[:enum_type] =
|
|
95
|
+
spec[:enum_type] = column.sql_type.inspect if column.enum?
|
|
95
96
|
|
|
96
97
|
spec
|
|
97
98
|
end
|
|
@@ -54,9 +54,9 @@ module ActiveRecord
|
|
|
54
54
|
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
-
def drop_table(
|
|
58
|
-
schema_cache.clear_data_source_cache!(table_name.to_s)
|
|
59
|
-
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
|
57
|
+
def drop_table(*table_names, **options) # :nodoc:
|
|
58
|
+
table_names.each { |table_name| schema_cache.clear_data_source_cache!(table_name.to_s) }
|
|
59
|
+
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{table_names.map { |table_name| quote_table_name(table_name) }.join(', ')}#{' CASCADE' if options[:force] == :cascade}"
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
# Returns true if schema exists.
|
|
@@ -152,9 +152,23 @@ module ActiveRecord
|
|
|
152
152
|
end
|
|
153
153
|
|
|
154
154
|
def table_options(table_name) # :nodoc:
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
options = {}
|
|
156
|
+
|
|
157
|
+
comment = table_comment(table_name)
|
|
158
|
+
|
|
159
|
+
options[:comment] = comment if comment
|
|
160
|
+
|
|
161
|
+
inherited_table_names = inherited_table_names(table_name).presence
|
|
162
|
+
|
|
163
|
+
options[:options] = "INHERITS (#{inherited_table_names.join(", ")})" if inherited_table_names
|
|
164
|
+
|
|
165
|
+
if !options[:options] && supports_native_partitioning?
|
|
166
|
+
partition_definition = table_partition_definition(table_name)
|
|
167
|
+
|
|
168
|
+
options[:options] = "PARTITION BY #{partition_definition}" if partition_definition
|
|
157
169
|
end
|
|
170
|
+
|
|
171
|
+
options
|
|
158
172
|
end
|
|
159
173
|
|
|
160
174
|
# Returns a comment stored in database for given table
|
|
@@ -172,6 +186,36 @@ module ActiveRecord
|
|
|
172
186
|
end
|
|
173
187
|
end
|
|
174
188
|
|
|
189
|
+
# Returns the partition definition of a given table
|
|
190
|
+
def table_partition_definition(table_name) # :nodoc:
|
|
191
|
+
scope = quoted_scope(table_name, type: "BASE TABLE")
|
|
192
|
+
|
|
193
|
+
query_value(<<~SQL, "SCHEMA")
|
|
194
|
+
SELECT pg_catalog.pg_get_partkeydef(c.oid)
|
|
195
|
+
FROM pg_catalog.pg_class c
|
|
196
|
+
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
197
|
+
WHERE c.relname = #{scope[:name]}
|
|
198
|
+
AND c.relkind IN (#{scope[:type]})
|
|
199
|
+
AND n.nspname = #{scope[:schema]}
|
|
200
|
+
SQL
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Returns the inherited table name of a given table
|
|
204
|
+
def inherited_table_names(table_name) # :nodoc:
|
|
205
|
+
scope = quoted_scope(table_name, type: "BASE TABLE")
|
|
206
|
+
|
|
207
|
+
query_values(<<~SQL, "SCHEMA")
|
|
208
|
+
SELECT parent.relname
|
|
209
|
+
FROM pg_catalog.pg_inherits i
|
|
210
|
+
JOIN pg_catalog.pg_class child ON i.inhrelid = child.oid
|
|
211
|
+
JOIN pg_catalog.pg_class parent ON i.inhparent = parent.oid
|
|
212
|
+
LEFT JOIN pg_namespace n ON n.oid = child.relnamespace
|
|
213
|
+
WHERE child.relname = #{scope[:name]}
|
|
214
|
+
AND child.relkind IN (#{scope[:type]})
|
|
215
|
+
AND n.nspname = #{scope[:schema]}
|
|
216
|
+
SQL
|
|
217
|
+
end
|
|
218
|
+
|
|
175
219
|
# Returns the current database name.
|
|
176
220
|
def current_database
|
|
177
221
|
query_value("SELECT current_database()", "SCHEMA")
|
|
@@ -250,7 +294,7 @@ module ActiveRecord
|
|
|
250
294
|
|
|
251
295
|
# Set the client message level.
|
|
252
296
|
def client_min_messages=(level)
|
|
253
|
-
internal_execute("SET client_min_messages TO '#{level}'")
|
|
297
|
+
internal_execute("SET client_min_messages TO '#{level}'", "SCHEMA")
|
|
254
298
|
end
|
|
255
299
|
|
|
256
300
|
# Returns the sequence name for a table's primary key or some other specified key.
|
|
@@ -654,7 +698,7 @@ module ActiveRecord
|
|
|
654
698
|
scope = quoted_scope(table_name)
|
|
655
699
|
|
|
656
700
|
unique_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
|
657
|
-
SELECT c.conname, c.conrelid, c.conkey, c.condeferrable, c.condeferred
|
|
701
|
+
SELECT c.conname, c.conrelid, c.conkey, c.condeferrable, c.condeferred, pg_get_constraintdef(c.oid) AS constraintdef
|
|
658
702
|
FROM pg_constraint c
|
|
659
703
|
JOIN pg_class t ON c.conrelid = t.oid
|
|
660
704
|
JOIN pg_namespace n ON n.oid = c.connamespace
|
|
@@ -667,10 +711,12 @@ module ActiveRecord
|
|
|
667
711
|
conkey = row["conkey"].delete("{}").split(",").map(&:to_i)
|
|
668
712
|
columns = column_names_from_column_numbers(row["conrelid"], conkey)
|
|
669
713
|
|
|
714
|
+
nulls_not_distinct = row["constraintdef"].start_with?("UNIQUE NULLS NOT DISTINCT")
|
|
670
715
|
deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
|
|
671
716
|
|
|
672
717
|
options = {
|
|
673
718
|
name: row["conname"],
|
|
719
|
+
nulls_not_distinct: nulls_not_distinct,
|
|
674
720
|
deferrable: deferrable
|
|
675
721
|
}
|
|
676
722
|
|
|
@@ -722,15 +768,12 @@ module ActiveRecord
|
|
|
722
768
|
def remove_exclusion_constraint(table_name, expression = nil, **options)
|
|
723
769
|
excl_name_to_delete = exclusion_constraint_for!(table_name, expression: expression, **options).name
|
|
724
770
|
|
|
725
|
-
|
|
726
|
-
at.drop_exclusion_constraint(excl_name_to_delete)
|
|
727
|
-
|
|
728
|
-
execute schema_creation.accept(at)
|
|
771
|
+
remove_constraint(table_name, excl_name_to_delete)
|
|
729
772
|
end
|
|
730
773
|
|
|
731
774
|
# Adds a new unique constraint to the table.
|
|
732
775
|
#
|
|
733
|
-
# add_unique_constraint :sections, [:position], deferrable: :deferred, name: "unique_position"
|
|
776
|
+
# add_unique_constraint :sections, [:position], deferrable: :deferred, name: "unique_position", nulls_not_distinct: true
|
|
734
777
|
#
|
|
735
778
|
# generates:
|
|
736
779
|
#
|
|
@@ -747,6 +790,9 @@ module ActiveRecord
|
|
|
747
790
|
# Specify whether or not the unique constraint should be deferrable. Valid values are +false+ or +:immediate+ or +:deferred+ to specify the default behavior. Defaults to +false+.
|
|
748
791
|
# [<tt>:using_index</tt>]
|
|
749
792
|
# To specify an existing unique index name. Defaults to +nil+.
|
|
793
|
+
# [<tt>:nulls_not_distinct</tt>]
|
|
794
|
+
# Create a unique constraint where NULLs are treated equally.
|
|
795
|
+
# Note: only supported by PostgreSQL version 15.0.0 and greater.
|
|
750
796
|
def add_unique_constraint(table_name, column_name = nil, **options)
|
|
751
797
|
options = unique_constraint_options(table_name, column_name, options)
|
|
752
798
|
at = create_alter_table(table_name)
|
|
@@ -777,10 +823,7 @@ module ActiveRecord
|
|
|
777
823
|
def remove_unique_constraint(table_name, column_name = nil, **options)
|
|
778
824
|
unique_name_to_delete = unique_constraint_for!(table_name, column: column_name, **options).name
|
|
779
825
|
|
|
780
|
-
|
|
781
|
-
at.drop_unique_constraint(unique_name_to_delete)
|
|
782
|
-
|
|
783
|
-
execute schema_creation.accept(at)
|
|
826
|
+
remove_constraint(table_name, unique_name_to_delete)
|
|
784
827
|
end
|
|
785
828
|
|
|
786
829
|
# Maps logical Rails types to PostgreSQL-specific data types.
|
|
@@ -879,7 +922,7 @@ module ActiveRecord
|
|
|
879
922
|
#
|
|
880
923
|
# validate_check_constraint :products, name: "price_check"
|
|
881
924
|
#
|
|
882
|
-
# The +options+ hash accepts the same keys as add_check_constraint[rdoc-ref:ConnectionAdapters::SchemaStatements#add_check_constraint].
|
|
925
|
+
# The +options+ hash accepts the same keys as {add_check_constraint}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_check_constraint].
|
|
883
926
|
def validate_check_constraint(table_name, **options)
|
|
884
927
|
chk_name_to_validate = check_constraint_for!(table_name, **options).name
|
|
885
928
|
|