activerecord 7.0.8.7 → 7.1.5.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 +1795 -1424
- data/MIT-LICENSE +1 -1
- data/README.rdoc +16 -16
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +20 -4
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +19 -13
- data/lib/active_record/associations/collection_proxy.rb +15 -10
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +20 -13
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +10 -10
- data/lib/active_record/associations/preloader/association.rb +31 -7
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +319 -217
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +53 -35
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +21 -8
- data/lib/active_record/attribute_methods/serialization.rb +150 -31
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +145 -21
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +59 -10
- data/lib/active_record/base.rb +7 -2
- data/lib/active_record/callbacks.rb +10 -24
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +80 -50
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
- data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +296 -127
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -92
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +244 -121
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
- data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +19 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +364 -61
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +211 -81
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +72 -95
- data/lib/active_record/core.rb +181 -154
- data/lib/active_record/counter_cache.rb +52 -27
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +28 -14
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +86 -33
- data/lib/active_record/delegated_type.rb +15 -10
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +42 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/scheme.rb +22 -21
- data/lib/active_record/encryption.rb +3 -0
- data/lib/active_record/enum.rb +112 -28
- data/lib/active_record/errors.rb +112 -18
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +135 -71
- data/lib/active_record/future_result.rb +40 -5
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +57 -10
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/internal_metadata.rb +120 -30
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- data/lib/active_record/marshalling.rb +59 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +6 -8
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +104 -5
- data/lib/active_record/migration/compatibility.rb +145 -5
- data/lib/active_record/migration/default_strategy.rb +23 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +219 -111
- data/lib/active_record/model_schema.rb +69 -44
- data/lib/active_record/nested_attributes.rb +37 -8
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +188 -37
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +4 -22
- data/lib/active_record/query_logs.rb +77 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +15 -2
- data/lib/active_record/railtie.rb +107 -45
- data/lib/active_record/railties/controller_runtime.rb +12 -6
- data/lib/active_record/railties/databases.rake +144 -150
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +181 -45
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +187 -63
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +26 -14
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +371 -68
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +103 -37
- data/lib/active_record/result.rb +19 -5
- data/lib/active_record/runtime_registry.rb +24 -1
- data/lib/active_record/sanitization.rb +51 -11
- data/lib/active_record/schema.rb +2 -3
- data/lib/active_record/schema_dumper.rb +46 -7
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/signed_id.rb +7 -5
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +10 -1
- data/lib/active_record/tasks/database_tasks.rb +152 -108
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +114 -96
- data/lib/active_record/timestamp.rb +30 -16
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +36 -10
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +47 -2
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +122 -17
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +5 -1
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +83 -18
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +46 -10
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -74,11 +74,11 @@ module ActiveRecord
|
|
74
74
|
FROM pg_class t
|
75
75
|
INNER JOIN pg_index d ON t.oid = d.indrelid
|
76
76
|
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
77
|
-
LEFT JOIN pg_namespace n ON n.oid =
|
77
|
+
LEFT JOIN pg_namespace n ON n.oid = t.relnamespace
|
78
78
|
WHERE i.relkind IN ('i', 'I')
|
79
79
|
AND i.relname = #{index[:name]}
|
80
80
|
AND t.relname = #{table[:name]}
|
81
|
-
AND n.nspname = #{
|
81
|
+
AND n.nspname = #{table[:schema]}
|
82
82
|
SQL
|
83
83
|
end
|
84
84
|
|
@@ -88,11 +88,11 @@ module ActiveRecord
|
|
88
88
|
|
89
89
|
result = query(<<~SQL, "SCHEMA")
|
90
90
|
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
|
91
|
-
pg_catalog.obj_description(i.oid, 'pg_class') AS comment
|
91
|
+
pg_catalog.obj_description(i.oid, 'pg_class') AS comment, d.indisvalid
|
92
92
|
FROM pg_class t
|
93
93
|
INNER JOIN pg_index d ON t.oid = d.indrelid
|
94
94
|
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
95
|
-
LEFT JOIN pg_namespace n ON n.oid =
|
95
|
+
LEFT JOIN pg_namespace n ON n.oid = t.relnamespace
|
96
96
|
WHERE i.relkind IN ('i', 'I')
|
97
97
|
AND d.indisprimary = 'f'
|
98
98
|
AND t.relname = #{scope[:name]}
|
@@ -107,25 +107,24 @@ module ActiveRecord
|
|
107
107
|
inddef = row[3]
|
108
108
|
oid = row[4]
|
109
109
|
comment = row[5]
|
110
|
-
|
111
|
-
using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/m).flatten
|
110
|
+
valid = row[6]
|
111
|
+
using, expressions, include, nulls_not_distinct, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: INCLUDE \((.+?)\))?( NULLS NOT DISTINCT)?(?: WHERE (.+))?\z/m).flatten
|
112
112
|
|
113
113
|
orders = {}
|
114
114
|
opclasses = {}
|
115
|
+
include_columns = include ? include.split(",").map(&:strip) : []
|
115
116
|
|
116
117
|
if indkey.include?(0)
|
117
118
|
columns = expressions
|
118
119
|
else
|
119
|
-
columns =
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
AND a.attnum IN (#{indkey.join(",")})
|
124
|
-
SQL
|
120
|
+
columns = column_names_from_column_numbers(oid, indkey)
|
121
|
+
|
122
|
+
# prevent INCLUDE columns from being matched
|
123
|
+
columns.reject! { |c| include_columns.include?(c) }
|
125
124
|
|
126
125
|
# add info on sort order (only desc order is explicitly specified, asc is the default)
|
127
126
|
# and non-default opclasses
|
128
|
-
expressions.scan(/(?<column>\w+)"?\s?(?<opclass>\w+_ops)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
|
127
|
+
expressions.scan(/(?<column>\w+)"?\s?(?<opclass>\w+_ops(_\w+)?)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
|
129
128
|
opclasses[column] = opclass.to_sym if opclass
|
130
129
|
if nulls
|
131
130
|
orders[column] = [desc, nulls].compact.join(" ")
|
@@ -144,7 +143,10 @@ module ActiveRecord
|
|
144
143
|
opclasses: opclasses,
|
145
144
|
where: where,
|
146
145
|
using: using.to_sym,
|
147
|
-
|
146
|
+
include: include_columns.presence,
|
147
|
+
nulls_not_distinct: nulls_not_distinct.present?,
|
148
|
+
comment: comment.presence,
|
149
|
+
valid: valid
|
148
150
|
)
|
149
151
|
end
|
150
152
|
end
|
@@ -223,7 +225,7 @@ module ActiveRecord
|
|
223
225
|
# This should be not be called manually but set in database.yml.
|
224
226
|
def schema_search_path=(schema_csv)
|
225
227
|
if schema_csv
|
226
|
-
|
228
|
+
internal_execute("SET search_path TO #{schema_csv}")
|
227
229
|
@schema_search_path = schema_csv
|
228
230
|
end
|
229
231
|
end
|
@@ -240,11 +242,13 @@ module ActiveRecord
|
|
240
242
|
|
241
243
|
# Set the client message level.
|
242
244
|
def client_min_messages=(level)
|
243
|
-
|
245
|
+
internal_execute("SET client_min_messages TO '#{level}'")
|
244
246
|
end
|
245
247
|
|
246
248
|
# Returns the sequence name for a table's primary key or some other specified key.
|
247
249
|
def default_sequence_name(table_name, pk = "id") # :nodoc:
|
250
|
+
return nil if pk.is_a?(Array)
|
251
|
+
|
248
252
|
result = serial_sequence(table_name, pk)
|
249
253
|
return nil unless result
|
250
254
|
Utils.extract_schema_qualified_name(result).to_s
|
@@ -288,14 +292,14 @@ module ActiveRecord
|
|
288
292
|
quoted_sequence = quote_table_name(sequence)
|
289
293
|
max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
|
290
294
|
if max_pk.nil?
|
291
|
-
if database_version >=
|
295
|
+
if database_version >= 10_00_00
|
292
296
|
minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
|
293
297
|
else
|
294
298
|
minvalue = query_value("SELECT min_value FROM #{quoted_sequence}", "SCHEMA")
|
295
299
|
end
|
296
300
|
end
|
297
301
|
|
298
|
-
query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk
|
302
|
+
query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk || minvalue}, #{max_pk ? true : false})", "SCHEMA")
|
299
303
|
end
|
300
304
|
end
|
301
305
|
|
@@ -339,7 +343,7 @@ module ActiveRecord
|
|
339
343
|
JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
|
340
344
|
WHERE t.oid = #{quote(quote_table_name(table))}::regclass
|
341
345
|
AND cons.contype = 'p'
|
342
|
-
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
|
346
|
+
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate|gen_random_uuid'
|
343
347
|
SQL
|
344
348
|
end
|
345
349
|
|
@@ -375,22 +379,30 @@ module ActiveRecord
|
|
375
379
|
#
|
376
380
|
# Example:
|
377
381
|
# rename_table('octopuses', 'octopi')
|
378
|
-
def rename_table(table_name, new_name)
|
382
|
+
def rename_table(table_name, new_name, **options)
|
383
|
+
validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
|
379
384
|
clear_cache!
|
380
385
|
schema_cache.clear_data_source_cache!(table_name.to_s)
|
381
386
|
schema_cache.clear_data_source_cache!(new_name.to_s)
|
382
387
|
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
383
388
|
pk, seq = pk_and_sequence_for(new_name)
|
384
389
|
if pk
|
385
|
-
|
386
|
-
|
390
|
+
# PostgreSQL automatically creates an index for PRIMARY KEY with name consisting of
|
391
|
+
# truncated table name and "_pkey" suffix fitting into max_identifier_length number of characters.
|
392
|
+
max_pkey_prefix = max_identifier_length - "_pkey".size
|
393
|
+
idx = "#{table_name[0, max_pkey_prefix]}_pkey"
|
394
|
+
new_idx = "#{new_name[0, max_pkey_prefix]}_pkey"
|
387
395
|
execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
|
388
|
-
|
389
|
-
|
396
|
+
|
397
|
+
# PostgreSQL automatically creates a sequence for PRIMARY KEY with name consisting of
|
398
|
+
# truncated table name and "#{primary_key}_seq" suffix fitting into max_identifier_length number of characters.
|
399
|
+
max_seq_prefix = max_identifier_length - "_#{pk}_seq".size
|
400
|
+
if seq && seq.identifier == "#{table_name[0, max_seq_prefix]}_#{pk}_seq"
|
401
|
+
new_seq = "#{new_name[0, max_seq_prefix]}_#{pk}_seq"
|
390
402
|
execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
|
391
403
|
end
|
392
404
|
end
|
393
|
-
rename_table_indexes(table_name, new_name)
|
405
|
+
rename_table_indexes(table_name, new_name, **options)
|
394
406
|
end
|
395
407
|
|
396
408
|
def add_column(table_name, column_name, type, **options) # :nodoc:
|
@@ -406,18 +418,39 @@ module ActiveRecord
|
|
406
418
|
procs.each(&:call)
|
407
419
|
end
|
408
420
|
|
421
|
+
# Builds a ChangeColumnDefinition object.
|
422
|
+
#
|
423
|
+
# This definition object contains information about the column change that would occur
|
424
|
+
# if the same arguments were passed to #change_column. See #change_column for information about
|
425
|
+
# passing a +table_name+, +column_name+, +type+ and other options that can be passed.
|
426
|
+
def build_change_column_definition(table_name, column_name, type, **options) # :nodoc:
|
427
|
+
td = create_table_definition(table_name)
|
428
|
+
cd = td.new_column_definition(column_name, type, **options)
|
429
|
+
ChangeColumnDefinition.new(cd, column_name)
|
430
|
+
end
|
431
|
+
|
409
432
|
# Changes the default value of a table column.
|
410
433
|
def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
|
411
434
|
execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
|
412
435
|
end
|
413
436
|
|
437
|
+
def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc:
|
438
|
+
column = column_for(table_name, column_name)
|
439
|
+
return unless column
|
440
|
+
|
441
|
+
default = extract_new_default_value(default_or_changes)
|
442
|
+
ChangeColumnDefaultDefinition.new(column, default)
|
443
|
+
end
|
444
|
+
|
414
445
|
def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
|
446
|
+
validate_change_column_null_argument!(null)
|
447
|
+
|
415
448
|
clear_cache!
|
416
449
|
unless null || default.nil?
|
417
450
|
column = column_for(table_name, column_name)
|
418
451
|
execute "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL" if column
|
419
452
|
end
|
420
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} #{
|
453
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
421
454
|
end
|
422
455
|
|
423
456
|
# Adds comment for given table column or drops it if +comment+ is a +nil+
|
@@ -442,15 +475,19 @@ module ActiveRecord
|
|
442
475
|
end
|
443
476
|
|
444
477
|
def add_index(table_name, column_name, **options) # :nodoc:
|
445
|
-
|
446
|
-
|
447
|
-
create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists)
|
478
|
+
create_index = build_create_index_definition(table_name, column_name, **options)
|
448
479
|
result = execute schema_creation.accept(create_index)
|
449
480
|
|
481
|
+
index = create_index.index
|
450
482
|
execute "COMMENT ON INDEX #{quote_column_name(index.name)} IS #{quote(index.comment)}" if index.comment
|
451
483
|
result
|
452
484
|
end
|
453
485
|
|
486
|
+
def build_create_index_definition(table_name, column_name, **options) # :nodoc:
|
487
|
+
index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
|
488
|
+
CreateIndexDefinition.new(index, algorithm, if_not_exists)
|
489
|
+
end
|
490
|
+
|
454
491
|
def remove_index(table_name, column_name = nil, **options) # :nodoc:
|
455
492
|
table = Utils.extract_schema_qualified_name(table_name.to_s)
|
456
493
|
|
@@ -477,13 +514,33 @@ module ActiveRecord
|
|
477
514
|
def rename_index(table_name, old_name, new_name)
|
478
515
|
validate_index_length!(table_name, new_name)
|
479
516
|
|
480
|
-
|
517
|
+
schema, = extract_schema_qualified_name(table_name)
|
518
|
+
execute "ALTER INDEX #{quote_table_name(schema) + '.' if schema}#{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
|
519
|
+
end
|
520
|
+
|
521
|
+
def index_name(table_name, options) # :nodoc:
|
522
|
+
_schema, table_name = extract_schema_qualified_name(table_name.to_s)
|
523
|
+
super
|
524
|
+
end
|
525
|
+
|
526
|
+
def add_foreign_key(from_table, to_table, **options)
|
527
|
+
if options[:deferrable] == true
|
528
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
529
|
+
`deferrable: true` is deprecated in favor of `deferrable: :immediate`, and will be removed in Rails 7.2.
|
530
|
+
MSG
|
531
|
+
|
532
|
+
options[:deferrable] = :immediate
|
533
|
+
end
|
534
|
+
|
535
|
+
assert_valid_deferrable(options[:deferrable])
|
536
|
+
|
537
|
+
super
|
481
538
|
end
|
482
539
|
|
483
540
|
def foreign_keys(table_name)
|
484
541
|
scope = quoted_scope(table_name)
|
485
|
-
fk_info =
|
486
|
-
SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid, c.condeferrable AS deferrable, c.condeferred AS deferred
|
542
|
+
fk_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
543
|
+
SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid, c.condeferrable AS deferrable, c.condeferred AS deferred, c.conkey, c.confkey, c.conrelid, c.confrelid
|
487
544
|
FROM pg_constraint c
|
488
545
|
JOIN pg_class t1 ON c.conrelid = t1.oid
|
489
546
|
JOIN pg_class t2 ON c.confrelid = t2.oid
|
@@ -497,18 +554,29 @@ module ActiveRecord
|
|
497
554
|
SQL
|
498
555
|
|
499
556
|
fk_info.map do |row|
|
557
|
+
to_table = Utils.unquote_identifier(row["to_table"])
|
558
|
+
conkey = row["conkey"].scan(/\d+/).map(&:to_i)
|
559
|
+
confkey = row["confkey"].scan(/\d+/).map(&:to_i)
|
560
|
+
|
561
|
+
if conkey.size > 1
|
562
|
+
column = column_names_from_column_numbers(row["conrelid"], conkey)
|
563
|
+
primary_key = column_names_from_column_numbers(row["confrelid"], confkey)
|
564
|
+
else
|
565
|
+
column = Utils.unquote_identifier(row["column"])
|
566
|
+
primary_key = row["primary_key"]
|
567
|
+
end
|
568
|
+
|
500
569
|
options = {
|
501
|
-
column:
|
570
|
+
column: column,
|
502
571
|
name: row["name"],
|
503
|
-
primary_key:
|
572
|
+
primary_key: primary_key
|
504
573
|
}
|
505
574
|
|
506
575
|
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
|
507
576
|
options[:on_update] = extract_foreign_key_action(row["on_update"])
|
508
|
-
options[:deferrable] =
|
577
|
+
options[:deferrable] = extract_constraint_deferrable(row["deferrable"], row["deferred"])
|
509
578
|
|
510
579
|
options[:validate] = row["valid"]
|
511
|
-
to_table = Utils.unquote_identifier(row["to_table"])
|
512
580
|
|
513
581
|
ForeignKeyDefinition.new(table_name, to_table, options)
|
514
582
|
end
|
@@ -525,7 +593,7 @@ module ActiveRecord
|
|
525
593
|
def check_constraints(table_name) # :nodoc:
|
526
594
|
scope = quoted_scope(table_name)
|
527
595
|
|
528
|
-
check_info =
|
596
|
+
check_info = internal_exec_query(<<-SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
529
597
|
SELECT conname, pg_get_constraintdef(c.oid, true) AS constraintdef, c.convalidated AS valid
|
530
598
|
FROM pg_constraint c
|
531
599
|
JOIN pg_class t ON c.conrelid = t.oid
|
@@ -546,6 +614,171 @@ module ActiveRecord
|
|
546
614
|
end
|
547
615
|
end
|
548
616
|
|
617
|
+
# Returns an array of exclusion constraints for the given table.
|
618
|
+
# The exclusion constraints are represented as ExclusionConstraintDefinition objects.
|
619
|
+
def exclusion_constraints(table_name)
|
620
|
+
scope = quoted_scope(table_name)
|
621
|
+
|
622
|
+
exclusion_info = internal_exec_query(<<-SQL, "SCHEMA")
|
623
|
+
SELECT conname, pg_get_constraintdef(c.oid) AS constraintdef, c.condeferrable, c.condeferred
|
624
|
+
FROM pg_constraint c
|
625
|
+
JOIN pg_class t ON c.conrelid = t.oid
|
626
|
+
JOIN pg_namespace n ON n.oid = c.connamespace
|
627
|
+
WHERE c.contype = 'x'
|
628
|
+
AND t.relname = #{scope[:name]}
|
629
|
+
AND n.nspname = #{scope[:schema]}
|
630
|
+
SQL
|
631
|
+
|
632
|
+
exclusion_info.map do |row|
|
633
|
+
method_and_elements, predicate = row["constraintdef"].split(" WHERE ")
|
634
|
+
method_and_elements_parts = method_and_elements.match(/EXCLUDE(?: USING (?<using>\S+))? \((?<expression>.+)\)/)
|
635
|
+
predicate.remove!(/ DEFERRABLE(?: INITIALLY (?:IMMEDIATE|DEFERRED))?/) if predicate
|
636
|
+
predicate = predicate.from(2).to(-3) if predicate # strip 2 opening and closing parentheses
|
637
|
+
|
638
|
+
deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
|
639
|
+
|
640
|
+
options = {
|
641
|
+
name: row["conname"],
|
642
|
+
using: method_and_elements_parts["using"].to_sym,
|
643
|
+
where: predicate,
|
644
|
+
deferrable: deferrable
|
645
|
+
}
|
646
|
+
|
647
|
+
ExclusionConstraintDefinition.new(table_name, method_and_elements_parts["expression"], options)
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
# Returns an array of unique constraints for the given table.
|
652
|
+
# The unique constraints are represented as UniqueConstraintDefinition objects.
|
653
|
+
def unique_constraints(table_name)
|
654
|
+
scope = quoted_scope(table_name)
|
655
|
+
|
656
|
+
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
|
658
|
+
FROM pg_constraint c
|
659
|
+
JOIN pg_class t ON c.conrelid = t.oid
|
660
|
+
JOIN pg_namespace n ON n.oid = c.connamespace
|
661
|
+
WHERE c.contype = 'u'
|
662
|
+
AND t.relname = #{scope[:name]}
|
663
|
+
AND n.nspname = #{scope[:schema]}
|
664
|
+
SQL
|
665
|
+
|
666
|
+
unique_info.map do |row|
|
667
|
+
conkey = row["conkey"].delete("{}").split(",").map(&:to_i)
|
668
|
+
columns = column_names_from_column_numbers(row["conrelid"], conkey)
|
669
|
+
|
670
|
+
deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
|
671
|
+
|
672
|
+
options = {
|
673
|
+
name: row["conname"],
|
674
|
+
deferrable: deferrable
|
675
|
+
}
|
676
|
+
|
677
|
+
UniqueConstraintDefinition.new(table_name, columns, options)
|
678
|
+
end
|
679
|
+
end
|
680
|
+
|
681
|
+
# Adds a new exclusion constraint to the table. +expression+ is a String
|
682
|
+
# representation of a list of exclusion elements and operators.
|
683
|
+
#
|
684
|
+
# add_exclusion_constraint :products, "price WITH =, availability_range WITH &&", using: :gist, name: "price_check"
|
685
|
+
#
|
686
|
+
# generates:
|
687
|
+
#
|
688
|
+
# ALTER TABLE "products" ADD CONSTRAINT price_check EXCLUDE USING gist (price WITH =, availability_range WITH &&)
|
689
|
+
#
|
690
|
+
# The +options+ hash can include the following keys:
|
691
|
+
# [<tt>:name</tt>]
|
692
|
+
# The constraint name. Defaults to <tt>excl_rails_<identifier></tt>.
|
693
|
+
# [<tt>:deferrable</tt>]
|
694
|
+
# Specify whether or not the exclusion constraint should be deferrable. Valid values are +false+ or +:immediate+ or +:deferred+ to specify the default behavior. Defaults to +false+.
|
695
|
+
def add_exclusion_constraint(table_name, expression, **options)
|
696
|
+
options = exclusion_constraint_options(table_name, expression, options)
|
697
|
+
at = create_alter_table(table_name)
|
698
|
+
at.add_exclusion_constraint(expression, options)
|
699
|
+
|
700
|
+
execute schema_creation.accept(at)
|
701
|
+
end
|
702
|
+
|
703
|
+
def exclusion_constraint_options(table_name, expression, options) # :nodoc:
|
704
|
+
assert_valid_deferrable(options[:deferrable])
|
705
|
+
|
706
|
+
options = options.dup
|
707
|
+
options[:name] ||= exclusion_constraint_name(table_name, expression: expression, **options)
|
708
|
+
options
|
709
|
+
end
|
710
|
+
|
711
|
+
# Removes the given exclusion constraint from the table.
|
712
|
+
#
|
713
|
+
# remove_exclusion_constraint :products, name: "price_check"
|
714
|
+
#
|
715
|
+
# The +expression+ parameter will be ignored if present. It can be helpful
|
716
|
+
# to provide this in a migration's +change+ method so it can be reverted.
|
717
|
+
# In that case, +expression+ will be used by #add_exclusion_constraint.
|
718
|
+
def remove_exclusion_constraint(table_name, expression = nil, **options)
|
719
|
+
excl_name_to_delete = exclusion_constraint_for!(table_name, expression: expression, **options).name
|
720
|
+
|
721
|
+
at = create_alter_table(table_name)
|
722
|
+
at.drop_exclusion_constraint(excl_name_to_delete)
|
723
|
+
|
724
|
+
execute schema_creation.accept(at)
|
725
|
+
end
|
726
|
+
|
727
|
+
# Adds a new unique constraint to the table.
|
728
|
+
#
|
729
|
+
# add_unique_constraint :sections, [:position], deferrable: :deferred, name: "unique_position"
|
730
|
+
#
|
731
|
+
# generates:
|
732
|
+
#
|
733
|
+
# ALTER TABLE "sections" ADD CONSTRAINT unique_position UNIQUE (position) DEFERRABLE INITIALLY DEFERRED
|
734
|
+
#
|
735
|
+
# If you want to change an existing unique index to deferrable, you can use :using_index to create deferrable unique constraints.
|
736
|
+
#
|
737
|
+
# add_unique_constraint :sections, deferrable: :deferred, name: "unique_position", using_index: "index_sections_on_position"
|
738
|
+
#
|
739
|
+
# The +options+ hash can include the following keys:
|
740
|
+
# [<tt>:name</tt>]
|
741
|
+
# The constraint name. Defaults to <tt>uniq_rails_<identifier></tt>.
|
742
|
+
# [<tt>:deferrable</tt>]
|
743
|
+
# 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+.
|
744
|
+
# [<tt>:using_index</tt>]
|
745
|
+
# To specify an existing unique index name. Defaults to +nil+.
|
746
|
+
def add_unique_constraint(table_name, column_name = nil, **options)
|
747
|
+
options = unique_constraint_options(table_name, column_name, options)
|
748
|
+
at = create_alter_table(table_name)
|
749
|
+
at.add_unique_constraint(column_name, options)
|
750
|
+
|
751
|
+
execute schema_creation.accept(at)
|
752
|
+
end
|
753
|
+
|
754
|
+
def unique_constraint_options(table_name, column_name, options) # :nodoc:
|
755
|
+
assert_valid_deferrable(options[:deferrable])
|
756
|
+
|
757
|
+
if column_name && options[:using_index]
|
758
|
+
raise ArgumentError, "Cannot specify both column_name and :using_index options."
|
759
|
+
end
|
760
|
+
|
761
|
+
options = options.dup
|
762
|
+
options[:name] ||= unique_constraint_name(table_name, column: column_name, **options)
|
763
|
+
options
|
764
|
+
end
|
765
|
+
|
766
|
+
# Removes the given unique constraint from the table.
|
767
|
+
#
|
768
|
+
# remove_unique_constraint :sections, name: "unique_position"
|
769
|
+
#
|
770
|
+
# The +column_name+ parameter will be ignored if present. It can be helpful
|
771
|
+
# to provide this in a migration's +change+ method so it can be reverted.
|
772
|
+
# In that case, +column_name+ will be used by #add_unique_constraint.
|
773
|
+
def remove_unique_constraint(table_name, column_name = nil, **options)
|
774
|
+
unique_name_to_delete = unique_constraint_for!(table_name, column: column_name, **options).name
|
775
|
+
|
776
|
+
at = create_alter_table(table_name)
|
777
|
+
at.drop_unique_constraint(unique_name_to_delete)
|
778
|
+
|
779
|
+
execute schema_creation.accept(at)
|
780
|
+
end
|
781
|
+
|
549
782
|
# Maps logical Rails types to PostgreSQL-specific data types.
|
550
783
|
def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, enum_type: nil, **) # :nodoc:
|
551
784
|
sql = \
|
@@ -649,11 +882,32 @@ module ActiveRecord
|
|
649
882
|
validate_constraint table_name, chk_name_to_validate
|
650
883
|
end
|
651
884
|
|
652
|
-
|
653
|
-
|
654
|
-
|
885
|
+
def foreign_key_column_for(table_name, column_name) # :nodoc:
|
886
|
+
_schema, table_name = extract_schema_qualified_name(table_name)
|
887
|
+
super
|
888
|
+
end
|
889
|
+
|
890
|
+
def add_index_options(table_name, column_name, **options) # :nodoc:
|
891
|
+
if (where = options[:where]) && table_exists?(table_name) && column_exists?(table_name, where)
|
892
|
+
options[:where] = quote_column_name(where)
|
893
|
+
end
|
894
|
+
super
|
895
|
+
end
|
896
|
+
|
897
|
+
def quoted_include_columns_for_index(column_names) # :nodoc:
|
898
|
+
return quote_column_name(column_names) if column_names.is_a?(Symbol)
|
899
|
+
|
900
|
+
quoted_columns = column_names.each_with_object({}) do |name, result|
|
901
|
+
result[name.to_sym] = quote_column_name(name).dup
|
655
902
|
end
|
903
|
+
add_options_for_index_columns(quoted_columns).values.join(", ")
|
904
|
+
end
|
656
905
|
|
906
|
+
def schema_creation # :nodoc:
|
907
|
+
PostgreSQL::SchemaCreation.new(self)
|
908
|
+
end
|
909
|
+
|
910
|
+
private
|
657
911
|
def create_table_definition(name, **options)
|
658
912
|
PostgreSQL::TableDefinition.new(self, name, **options)
|
659
913
|
end
|
@@ -662,8 +916,8 @@ module ActiveRecord
|
|
662
916
|
PostgreSQL::AlterTable.new create_table_definition(name)
|
663
917
|
end
|
664
918
|
|
665
|
-
def new_column_from_field(table_name, field)
|
666
|
-
column_name, type, default, notnull, oid, fmod, collation, comment, attgenerated = field
|
919
|
+
def new_column_from_field(table_name, field, _definitions)
|
920
|
+
column_name, type, default, notnull, oid, fmod, collation, comment, identity, attgenerated = field
|
667
921
|
type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
|
668
922
|
default_value = extract_value_from_default(default)
|
669
923
|
|
@@ -686,6 +940,7 @@ module ActiveRecord
|
|
686
940
|
collation: collation,
|
687
941
|
comment: comment.presence,
|
688
942
|
serial: serial,
|
943
|
+
identity: identity.presence,
|
689
944
|
generated: attgenerated
|
690
945
|
)
|
691
946
|
end
|
@@ -726,8 +981,19 @@ module ActiveRecord
|
|
726
981
|
end
|
727
982
|
end
|
728
983
|
|
729
|
-
def
|
730
|
-
deferrable
|
984
|
+
def assert_valid_deferrable(deferrable)
|
985
|
+
return if !deferrable || %i(immediate deferred).include?(deferrable)
|
986
|
+
|
987
|
+
raise ArgumentError, "deferrable must be `:immediate` or `:deferred`, got: `#{deferrable.inspect}`"
|
988
|
+
end
|
989
|
+
|
990
|
+
def extract_constraint_deferrable(deferrable, deferred)
|
991
|
+
deferrable && (deferred ? :deferred : :immediate)
|
992
|
+
end
|
993
|
+
|
994
|
+
def reference_name_for_table(table_name)
|
995
|
+
_schema, table_name = extract_schema_qualified_name(table_name.to_s)
|
996
|
+
table_name.singularize
|
731
997
|
end
|
732
998
|
|
733
999
|
def add_column_for_alter(table_name, column_name, type, **options)
|
@@ -736,32 +1002,20 @@ module ActiveRecord
|
|
736
1002
|
end
|
737
1003
|
|
738
1004
|
def change_column_for_alter(table_name, column_name, type, **options)
|
739
|
-
|
740
|
-
|
741
|
-
sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))]
|
1005
|
+
change_col_def = build_change_column_definition(table_name, column_name, type, **options)
|
1006
|
+
sqls = [schema_creation.accept(change_col_def)]
|
742
1007
|
sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
|
743
1008
|
sqls
|
744
1009
|
end
|
745
1010
|
|
746
|
-
def
|
747
|
-
column = column_for(table_name, column_name)
|
748
|
-
return unless column
|
749
|
-
|
750
|
-
default = extract_new_default_value(default_or_changes)
|
751
|
-
alter_column_query = "ALTER COLUMN #{quote_column_name(column_name)} %s"
|
1011
|
+
def change_column_null_for_alter(table_name, column_name, null, default = nil)
|
752
1012
|
if default.nil?
|
753
|
-
|
754
|
-
# cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
|
755
|
-
alter_column_query % "DROP DEFAULT"
|
1013
|
+
"ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
756
1014
|
else
|
757
|
-
|
1015
|
+
Proc.new { change_column_null(table_name, column_name, null, default) }
|
758
1016
|
end
|
759
1017
|
end
|
760
1018
|
|
761
|
-
def change_column_null_for_alter(table_name, column_name, null, default = nil)
|
762
|
-
"ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
763
|
-
end
|
764
|
-
|
765
1019
|
def add_index_opclass(quoted_columns, **options)
|
766
1020
|
opclasses = options_for_index_columns(options[:opclass])
|
767
1021
|
quoted_columns.each do |name, column|
|
@@ -774,6 +1028,46 @@ module ActiveRecord
|
|
774
1028
|
super
|
775
1029
|
end
|
776
1030
|
|
1031
|
+
def exclusion_constraint_name(table_name, **options)
|
1032
|
+
options.fetch(:name) do
|
1033
|
+
expression = options.fetch(:expression)
|
1034
|
+
identifier = "#{table_name}_#{expression}_excl"
|
1035
|
+
hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
|
1036
|
+
|
1037
|
+
"excl_rails_#{hashed_identifier}"
|
1038
|
+
end
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
def exclusion_constraint_for(table_name, **options)
|
1042
|
+
excl_name = exclusion_constraint_name(table_name, **options)
|
1043
|
+
exclusion_constraints(table_name).detect { |excl| excl.name == excl_name }
|
1044
|
+
end
|
1045
|
+
|
1046
|
+
def exclusion_constraint_for!(table_name, expression: nil, **options)
|
1047
|
+
exclusion_constraint_for(table_name, expression: expression, **options) ||
|
1048
|
+
raise(ArgumentError, "Table '#{table_name}' has no exclusion constraint for #{expression || options}")
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
def unique_constraint_name(table_name, **options)
|
1052
|
+
options.fetch(:name) do
|
1053
|
+
column_or_index = Array(options[:column] || options[:using_index]).map(&:to_s)
|
1054
|
+
identifier = "#{table_name}_#{column_or_index * '_and_'}_unique"
|
1055
|
+
hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
|
1056
|
+
|
1057
|
+
"uniq_rails_#{hashed_identifier}"
|
1058
|
+
end
|
1059
|
+
end
|
1060
|
+
|
1061
|
+
def unique_constraint_for(table_name, **options)
|
1062
|
+
name = unique_constraint_name(table_name, **options) unless options.key?(:column)
|
1063
|
+
unique_constraints(table_name).detect { |unique_constraint| unique_constraint.defined_for?(name: name, **options) }
|
1064
|
+
end
|
1065
|
+
|
1066
|
+
def unique_constraint_for!(table_name, column: nil, **options)
|
1067
|
+
unique_constraint_for(table_name, column: column, **options) ||
|
1068
|
+
raise(ArgumentError, "Table '#{table_name}' has no unique constraint for #{column || options}")
|
1069
|
+
end
|
1070
|
+
|
777
1071
|
def data_source_sql(name = nil, type: nil)
|
778
1072
|
scope = quoted_scope(name, type: type)
|
779
1073
|
scope[:type] ||= "'r','v','m','p','f'" # (r)elation/table, (v)iew, (m)aterialized view, (p)artitioned table, (f)oreign table
|
@@ -807,6 +1101,15 @@ module ActiveRecord
|
|
807
1101
|
name = Utils.extract_schema_qualified_name(string.to_s)
|
808
1102
|
[name.schema, name.identifier]
|
809
1103
|
end
|
1104
|
+
|
1105
|
+
def column_names_from_column_numbers(table_oid, column_numbers)
|
1106
|
+
Hash[query(<<~SQL, "SCHEMA")].values_at(*column_numbers).compact
|
1107
|
+
SELECT a.attnum, a.attname
|
1108
|
+
FROM pg_attribute a
|
1109
|
+
WHERE a.attrelid = #{table_oid}
|
1110
|
+
AND a.attnum IN (#{column_numbers.join(", ")})
|
1111
|
+
SQL
|
1112
|
+
end
|
810
1113
|
end
|
811
1114
|
end
|
812
1115
|
end
|