activerecord 7.0.0 → 7.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 +515 -1268
- data/MIT-LICENSE +1 -1
- data/README.rdoc +31 -31
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +2 -2
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +35 -12
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +23 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +22 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
- 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 +4 -0
- data/lib/active_record/associations/collection_association.rb +28 -17
- data/lib/active_record/associations/collection_proxy.rb +36 -13
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +28 -18
- 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 +27 -25
- data/lib/active_record/associations/join_dependency.rb +18 -14
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +33 -8
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +2 -4
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +7 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +378 -491
- data/lib/active_record/attribute_assignment.rb +1 -13
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +53 -35
- data/lib/active_record/attribute_methods/primary_key.rb +45 -25
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +8 -7
- data/lib/active_record/attribute_methods/serialization.rb +153 -70
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +153 -40
- data/lib/active_record/attributes.rb +63 -48
- data/lib/active_record/autosave_association.rb +70 -38
- data/lib/active_record/base.rb +12 -8
- data/lib/active_record/callbacks.rb +16 -32
- 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 -34
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +124 -132
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +297 -88
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +215 -63
- data/lib/active_record/connection_adapters/abstract/quoting.rb +83 -65
- 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 +163 -29
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +319 -135
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +512 -126
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +282 -119
- 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 +27 -140
- data/lib/active_record/connection_adapters/mysql/quoting.rb +64 -52
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
- data/lib/active_record/connection_adapters/pool_config.rb +20 -10
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +101 -48
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- 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 +4 -2
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +94 -61
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +379 -66
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +370 -203
- data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +61 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +64 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +321 -110
- 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 +229 -0
- data/lib/active_record/connection_adapters.rb +124 -1
- data/lib/active_record/connection_handling.rb +98 -106
- data/lib/active_record/core.rb +220 -177
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -2
- data/lib/active_record/database_configurations/database_config.rb +26 -5
- data/lib/active_record/database_configurations/hash_config.rb +52 -34
- data/lib/active_record/database_configurations/url_config.rb +37 -12
- data/lib/active_record/database_configurations.rb +88 -35
- data/lib/active_record/delegated_type.rb +40 -11
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/disable_joins_association_relation.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- 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 +13 -14
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +8 -4
- data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
- data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
- data/lib/active_record/encryption/encryptable_record.rb +47 -25
- data/lib/active_record/encryption/encrypted_attribute_type.rb +49 -14
- data/lib/active_record/encryption/encryptor.rb +25 -10
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
- 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.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +6 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/properties.rb +4 -4
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +23 -22
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +131 -27
- data/lib/active_record/errors.rb +151 -31
- data/lib/active_record/explain.rb +21 -12
- data/lib/active_record/explain_subscriber.rb +1 -1
- 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 +169 -99
- data/lib/active_record/future_result.rb +47 -8
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +34 -18
- data/lib/active_record/insert_all.rb +72 -22
- data/lib/active_record/integration.rb +13 -10
- data/lib/active_record/internal_metadata.rb +124 -20
- data/lib/active_record/locking/optimistic.rb +39 -24
- data/lib/active_record/locking/pessimistic.rb +8 -5
- data/lib/active_record/log_subscriber.rb +28 -27
- data/lib/active_record/marshalling.rb +56 -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 +18 -13
- data/lib/active_record/middleware/shard_selector.rb +7 -5
- data/lib/active_record/migration/command_recorder.rb +110 -13
- data/lib/active_record/migration/compatibility.rb +174 -64
- data/lib/active_record/migration/default_strategy.rb +22 -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 +292 -125
- data/lib/active_record/model_schema.rb +113 -112
- data/lib/active_record/nested_attributes.rb +35 -9
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +177 -345
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +19 -25
- data/lib/active_record/query_logs.rb +102 -51
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +34 -9
- data/lib/active_record/railtie.rb +153 -100
- data/lib/active_record/railties/controller_runtime.rb +24 -10
- data/lib/active_record/railties/databases.rake +148 -152
- 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 +278 -69
- data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
- data/lib/active_record/relation/batches.rb +198 -63
- data/lib/active_record/relation/calculations.rb +293 -108
- data/lib/active_record/relation/delegation.rb +31 -20
- data/lib/active_record/relation/finder_methods.rb +93 -18
- data/lib/active_record/relation/merger.rb +6 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
- 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 +28 -16
- data/lib/active_record/relation/query_attribute.rb +25 -1
- data/lib/active_record/relation/query_methods.rb +625 -107
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +5 -4
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +602 -96
- data/lib/active_record/result.rb +55 -52
- data/lib/active_record/runtime_registry.rb +63 -1
- data/lib/active_record/sanitization.rb +76 -30
- data/lib/active_record/schema.rb +39 -23
- data/lib/active_record/schema_dumper.rb +82 -30
- data/lib/active_record/schema_migration.rb +75 -24
- data/lib/active_record/scoping/default.rb +20 -12
- data/lib/active_record/scoping/named.rb +3 -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/serialization.rb +5 -0
- data/lib/active_record/signed_id.rb +29 -8
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +16 -11
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +7 -3
- data/lib/active_record/tasks/database_tasks.rb +191 -121
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
- data/lib/active_record/test_fixtures.rb +174 -152
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +31 -17
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +12 -7
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +109 -27
- data/lib/active_record/translation.rb +1 -3
- 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/serialized.rb +9 -7
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +12 -6
- 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 +63 -14
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +266 -30
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +1 -1
- data/lib/arel/nodes/binary.rb +6 -7
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/filter.rb +1 -1
- 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/{and.rb → nary.rb} +9 -2
- data/lib/arel/nodes/node.rb +115 -5
- data/lib/arel/nodes/sql_literal.rb +13 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +6 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +17 -5
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/to_sql.rb +112 -34
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +21 -3
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- 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 +59 -17
- 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 { |c| Utils.unquote_identifier(c.strip.gsub('""', '"')) } : []
|
|
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
|
|
@@ -207,8 +209,16 @@ module ActiveRecord
|
|
|
207
209
|
end
|
|
208
210
|
|
|
209
211
|
# Creates a schema for the given schema name.
|
|
210
|
-
def create_schema(schema_name)
|
|
211
|
-
|
|
212
|
+
def create_schema(schema_name, force: nil, if_not_exists: nil)
|
|
213
|
+
if force && if_not_exists
|
|
214
|
+
raise ArgumentError, "Options `:force` and `:if_not_exists` cannot be used simultaneously."
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
if force
|
|
218
|
+
drop_schema(schema_name, if_exists: true)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
execute("CREATE SCHEMA#{' IF NOT EXISTS' if if_not_exists} #{quote_schema_name(schema_name)}")
|
|
212
222
|
end
|
|
213
223
|
|
|
214
224
|
# Drops the schema for the given schema name.
|
|
@@ -223,7 +233,7 @@ module ActiveRecord
|
|
|
223
233
|
# This should be not be called manually but set in database.yml.
|
|
224
234
|
def schema_search_path=(schema_csv)
|
|
225
235
|
if schema_csv
|
|
226
|
-
|
|
236
|
+
internal_execute("SET search_path TO #{schema_csv}")
|
|
227
237
|
@schema_search_path = schema_csv
|
|
228
238
|
end
|
|
229
239
|
end
|
|
@@ -240,7 +250,7 @@ module ActiveRecord
|
|
|
240
250
|
|
|
241
251
|
# Set the client message level.
|
|
242
252
|
def client_min_messages=(level)
|
|
243
|
-
|
|
253
|
+
internal_execute("SET client_min_messages TO '#{level}'")
|
|
244
254
|
end
|
|
245
255
|
|
|
246
256
|
# Returns the sequence name for a table's primary key or some other specified key.
|
|
@@ -288,14 +298,14 @@ module ActiveRecord
|
|
|
288
298
|
quoted_sequence = quote_table_name(sequence)
|
|
289
299
|
max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
|
|
290
300
|
if max_pk.nil?
|
|
291
|
-
if database_version >=
|
|
301
|
+
if database_version >= 10_00_00
|
|
292
302
|
minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
|
|
293
303
|
else
|
|
294
304
|
minvalue = query_value("SELECT min_value FROM #{quoted_sequence}", "SCHEMA")
|
|
295
305
|
end
|
|
296
306
|
end
|
|
297
307
|
|
|
298
|
-
query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk
|
|
308
|
+
query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk || minvalue}, #{max_pk ? true : false})", "SCHEMA")
|
|
299
309
|
end
|
|
300
310
|
end
|
|
301
311
|
|
|
@@ -339,7 +349,7 @@ module ActiveRecord
|
|
|
339
349
|
JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
|
|
340
350
|
WHERE t.oid = #{quote(quote_table_name(table))}::regclass
|
|
341
351
|
AND cons.contype = 'p'
|
|
342
|
-
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
|
|
352
|
+
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate|gen_random_uuid'
|
|
343
353
|
SQL
|
|
344
354
|
end
|
|
345
355
|
|
|
@@ -375,22 +385,30 @@ module ActiveRecord
|
|
|
375
385
|
#
|
|
376
386
|
# Example:
|
|
377
387
|
# rename_table('octopuses', 'octopi')
|
|
378
|
-
def rename_table(table_name, new_name)
|
|
388
|
+
def rename_table(table_name, new_name, **options)
|
|
389
|
+
validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
|
|
379
390
|
clear_cache!
|
|
380
391
|
schema_cache.clear_data_source_cache!(table_name.to_s)
|
|
381
392
|
schema_cache.clear_data_source_cache!(new_name.to_s)
|
|
382
393
|
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
|
383
394
|
pk, seq = pk_and_sequence_for(new_name)
|
|
384
395
|
if pk
|
|
385
|
-
|
|
386
|
-
|
|
396
|
+
# PostgreSQL automatically creates an index for PRIMARY KEY with name consisting of
|
|
397
|
+
# truncated table name and "_pkey" suffix fitting into max_identifier_length number of characters.
|
|
398
|
+
max_pkey_prefix = max_identifier_length - "_pkey".size
|
|
399
|
+
idx = "#{table_name[0, max_pkey_prefix]}_pkey"
|
|
400
|
+
new_idx = "#{new_name[0, max_pkey_prefix]}_pkey"
|
|
387
401
|
execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
|
|
388
|
-
|
|
389
|
-
|
|
402
|
+
|
|
403
|
+
# PostgreSQL automatically creates a sequence for PRIMARY KEY with name consisting of
|
|
404
|
+
# truncated table name and "#{primary_key}_seq" suffix fitting into max_identifier_length number of characters.
|
|
405
|
+
max_seq_prefix = max_identifier_length - "_#{pk}_seq".size
|
|
406
|
+
if seq && seq.identifier == "#{table_name[0, max_seq_prefix]}_#{pk}_seq"
|
|
407
|
+
new_seq = "#{new_name[0, max_seq_prefix]}_#{pk}_seq"
|
|
390
408
|
execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
|
|
391
409
|
end
|
|
392
410
|
end
|
|
393
|
-
rename_table_indexes(table_name, new_name)
|
|
411
|
+
rename_table_indexes(table_name, new_name, **options)
|
|
394
412
|
end
|
|
395
413
|
|
|
396
414
|
def add_column(table_name, column_name, type, **options) # :nodoc:
|
|
@@ -406,18 +424,39 @@ module ActiveRecord
|
|
|
406
424
|
procs.each(&:call)
|
|
407
425
|
end
|
|
408
426
|
|
|
427
|
+
# Builds a ChangeColumnDefinition object.
|
|
428
|
+
#
|
|
429
|
+
# This definition object contains information about the column change that would occur
|
|
430
|
+
# if the same arguments were passed to #change_column. See #change_column for information about
|
|
431
|
+
# passing a +table_name+, +column_name+, +type+ and other options that can be passed.
|
|
432
|
+
def build_change_column_definition(table_name, column_name, type, **options) # :nodoc:
|
|
433
|
+
td = create_table_definition(table_name)
|
|
434
|
+
cd = td.new_column_definition(column_name, type, **options)
|
|
435
|
+
ChangeColumnDefinition.new(cd, column_name)
|
|
436
|
+
end
|
|
437
|
+
|
|
409
438
|
# Changes the default value of a table column.
|
|
410
439
|
def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
|
|
411
440
|
execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
|
|
412
441
|
end
|
|
413
442
|
|
|
443
|
+
def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc:
|
|
444
|
+
column = column_for(table_name, column_name)
|
|
445
|
+
return unless column
|
|
446
|
+
|
|
447
|
+
default = extract_new_default_value(default_or_changes)
|
|
448
|
+
ChangeColumnDefaultDefinition.new(column, default)
|
|
449
|
+
end
|
|
450
|
+
|
|
414
451
|
def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
|
|
452
|
+
validate_change_column_null_argument!(null)
|
|
453
|
+
|
|
415
454
|
clear_cache!
|
|
416
455
|
unless null || default.nil?
|
|
417
456
|
column = column_for(table_name, column_name)
|
|
418
457
|
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
458
|
end
|
|
420
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} #{
|
|
459
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
|
421
460
|
end
|
|
422
461
|
|
|
423
462
|
# Adds comment for given table column or drops it if +comment+ is a +nil+
|
|
@@ -442,15 +481,19 @@ module ActiveRecord
|
|
|
442
481
|
end
|
|
443
482
|
|
|
444
483
|
def add_index(table_name, column_name, **options) # :nodoc:
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists)
|
|
484
|
+
create_index = build_create_index_definition(table_name, column_name, **options)
|
|
448
485
|
result = execute schema_creation.accept(create_index)
|
|
449
486
|
|
|
487
|
+
index = create_index.index
|
|
450
488
|
execute "COMMENT ON INDEX #{quote_column_name(index.name)} IS #{quote(index.comment)}" if index.comment
|
|
451
489
|
result
|
|
452
490
|
end
|
|
453
491
|
|
|
492
|
+
def build_create_index_definition(table_name, column_name, **options) # :nodoc:
|
|
493
|
+
index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
|
|
494
|
+
CreateIndexDefinition.new(index, algorithm, if_not_exists)
|
|
495
|
+
end
|
|
496
|
+
|
|
454
497
|
def remove_index(table_name, column_name = nil, **options) # :nodoc:
|
|
455
498
|
table = Utils.extract_schema_qualified_name(table_name.to_s)
|
|
456
499
|
|
|
@@ -477,13 +520,25 @@ module ActiveRecord
|
|
|
477
520
|
def rename_index(table_name, old_name, new_name)
|
|
478
521
|
validate_index_length!(table_name, new_name)
|
|
479
522
|
|
|
480
|
-
|
|
523
|
+
schema, = extract_schema_qualified_name(table_name)
|
|
524
|
+
execute "ALTER INDEX #{quote_table_name(schema) + '.' if schema}#{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
def index_name(table_name, options) # :nodoc:
|
|
528
|
+
_schema, table_name = extract_schema_qualified_name(table_name.to_s)
|
|
529
|
+
super
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
def add_foreign_key(from_table, to_table, **options)
|
|
533
|
+
assert_valid_deferrable(options[:deferrable])
|
|
534
|
+
|
|
535
|
+
super
|
|
481
536
|
end
|
|
482
537
|
|
|
483
538
|
def foreign_keys(table_name)
|
|
484
539
|
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
|
|
540
|
+
fk_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
|
541
|
+
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
542
|
FROM pg_constraint c
|
|
488
543
|
JOIN pg_class t1 ON c.conrelid = t1.oid
|
|
489
544
|
JOIN pg_class t2 ON c.confrelid = t2.oid
|
|
@@ -497,19 +552,31 @@ module ActiveRecord
|
|
|
497
552
|
SQL
|
|
498
553
|
|
|
499
554
|
fk_info.map do |row|
|
|
555
|
+
to_table = Utils.unquote_identifier(row["to_table"])
|
|
556
|
+
conkey = row["conkey"].scan(/\d+/).map(&:to_i)
|
|
557
|
+
confkey = row["confkey"].scan(/\d+/).map(&:to_i)
|
|
558
|
+
|
|
559
|
+
if conkey.size > 1
|
|
560
|
+
column = column_names_from_column_numbers(row["conrelid"], conkey)
|
|
561
|
+
primary_key = column_names_from_column_numbers(row["confrelid"], confkey)
|
|
562
|
+
else
|
|
563
|
+
column = Utils.unquote_identifier(row["column"])
|
|
564
|
+
primary_key = row["primary_key"]
|
|
565
|
+
end
|
|
566
|
+
|
|
500
567
|
options = {
|
|
501
|
-
column:
|
|
568
|
+
column: column,
|
|
502
569
|
name: row["name"],
|
|
503
|
-
primary_key:
|
|
570
|
+
primary_key: primary_key
|
|
504
571
|
}
|
|
505
572
|
|
|
506
573
|
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
|
|
507
574
|
options[:on_update] = extract_foreign_key_action(row["on_update"])
|
|
508
|
-
options[:deferrable] =
|
|
575
|
+
options[:deferrable] = extract_constraint_deferrable(row["deferrable"], row["deferred"])
|
|
509
576
|
|
|
510
577
|
options[:validate] = row["valid"]
|
|
511
578
|
|
|
512
|
-
ForeignKeyDefinition.new(table_name,
|
|
579
|
+
ForeignKeyDefinition.new(table_name, to_table, options)
|
|
513
580
|
end
|
|
514
581
|
end
|
|
515
582
|
|
|
@@ -524,12 +591,14 @@ module ActiveRecord
|
|
|
524
591
|
def check_constraints(table_name) # :nodoc:
|
|
525
592
|
scope = quoted_scope(table_name)
|
|
526
593
|
|
|
527
|
-
check_info =
|
|
528
|
-
SELECT conname, pg_get_constraintdef(c.oid) AS constraintdef, c.convalidated AS valid
|
|
594
|
+
check_info = internal_exec_query(<<-SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
|
595
|
+
SELECT conname, pg_get_constraintdef(c.oid, true) AS constraintdef, c.convalidated AS valid
|
|
529
596
|
FROM pg_constraint c
|
|
530
597
|
JOIN pg_class t ON c.conrelid = t.oid
|
|
598
|
+
JOIN pg_namespace n ON n.oid = c.connamespace
|
|
531
599
|
WHERE c.contype = 'c'
|
|
532
600
|
AND t.relname = #{scope[:name]}
|
|
601
|
+
AND n.nspname = #{scope[:schema]}
|
|
533
602
|
SQL
|
|
534
603
|
|
|
535
604
|
check_info.map do |row|
|
|
@@ -537,12 +606,181 @@ module ActiveRecord
|
|
|
537
606
|
name: row["conname"],
|
|
538
607
|
validate: row["valid"]
|
|
539
608
|
}
|
|
540
|
-
expression = row["constraintdef"][/CHECK \(
|
|
609
|
+
expression = row["constraintdef"][/CHECK \((.+)\)/m, 1]
|
|
541
610
|
|
|
542
611
|
CheckConstraintDefinition.new(table_name, expression, options)
|
|
543
612
|
end
|
|
544
613
|
end
|
|
545
614
|
|
|
615
|
+
# Returns an array of exclusion constraints for the given table.
|
|
616
|
+
# The exclusion constraints are represented as ExclusionConstraintDefinition objects.
|
|
617
|
+
def exclusion_constraints(table_name)
|
|
618
|
+
scope = quoted_scope(table_name)
|
|
619
|
+
|
|
620
|
+
exclusion_info = internal_exec_query(<<-SQL, "SCHEMA")
|
|
621
|
+
SELECT conname, pg_get_constraintdef(c.oid) AS constraintdef, c.condeferrable, c.condeferred
|
|
622
|
+
FROM pg_constraint c
|
|
623
|
+
JOIN pg_class t ON c.conrelid = t.oid
|
|
624
|
+
JOIN pg_namespace n ON n.oid = c.connamespace
|
|
625
|
+
WHERE c.contype = 'x'
|
|
626
|
+
AND t.relname = #{scope[:name]}
|
|
627
|
+
AND n.nspname = #{scope[:schema]}
|
|
628
|
+
SQL
|
|
629
|
+
|
|
630
|
+
exclusion_info.map do |row|
|
|
631
|
+
method_and_elements, predicate = row["constraintdef"].split(" WHERE ")
|
|
632
|
+
method_and_elements_parts = method_and_elements.match(/EXCLUDE(?: USING (?<using>\S+))? \((?<expression>.+)\)/)
|
|
633
|
+
predicate.remove!(/ DEFERRABLE(?: INITIALLY (?:IMMEDIATE|DEFERRED))?/) if predicate
|
|
634
|
+
predicate = predicate.from(2).to(-3) if predicate # strip 2 opening and closing parentheses
|
|
635
|
+
|
|
636
|
+
deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
|
|
637
|
+
|
|
638
|
+
options = {
|
|
639
|
+
name: row["conname"],
|
|
640
|
+
using: method_and_elements_parts["using"].to_sym,
|
|
641
|
+
where: predicate,
|
|
642
|
+
deferrable: deferrable
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
ExclusionConstraintDefinition.new(table_name, method_and_elements_parts["expression"], options)
|
|
646
|
+
end
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
# Returns an array of unique constraints for the given table.
|
|
650
|
+
# The unique constraints are represented as UniqueConstraintDefinition objects.
|
|
651
|
+
def unique_constraints(table_name)
|
|
652
|
+
scope = quoted_scope(table_name)
|
|
653
|
+
|
|
654
|
+
unique_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
|
655
|
+
SELECT c.conname, c.conrelid, c.conkey, c.condeferrable, c.condeferred
|
|
656
|
+
FROM pg_constraint c
|
|
657
|
+
JOIN pg_class t ON c.conrelid = t.oid
|
|
658
|
+
JOIN pg_namespace n ON n.oid = c.connamespace
|
|
659
|
+
WHERE c.contype = 'u'
|
|
660
|
+
AND t.relname = #{scope[:name]}
|
|
661
|
+
AND n.nspname = #{scope[:schema]}
|
|
662
|
+
SQL
|
|
663
|
+
|
|
664
|
+
unique_info.map do |row|
|
|
665
|
+
conkey = row["conkey"].delete("{}").split(",").map(&:to_i)
|
|
666
|
+
columns = column_names_from_column_numbers(row["conrelid"], conkey)
|
|
667
|
+
|
|
668
|
+
deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
|
|
669
|
+
|
|
670
|
+
options = {
|
|
671
|
+
name: row["conname"],
|
|
672
|
+
deferrable: deferrable
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
UniqueConstraintDefinition.new(table_name, columns, options)
|
|
676
|
+
end
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
# Adds a new exclusion constraint to the table. +expression+ is a String
|
|
680
|
+
# representation of a list of exclusion elements and operators.
|
|
681
|
+
#
|
|
682
|
+
# add_exclusion_constraint :products, "price WITH =, availability_range WITH &&", using: :gist, name: "price_check"
|
|
683
|
+
#
|
|
684
|
+
# generates:
|
|
685
|
+
#
|
|
686
|
+
# ALTER TABLE "products" ADD CONSTRAINT price_check EXCLUDE USING gist (price WITH =, availability_range WITH &&)
|
|
687
|
+
#
|
|
688
|
+
# The +options+ hash can include the following keys:
|
|
689
|
+
# [<tt>:name</tt>]
|
|
690
|
+
# The constraint name. Defaults to <tt>excl_rails_<identifier></tt>.
|
|
691
|
+
# [<tt>:deferrable</tt>]
|
|
692
|
+
# 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+.
|
|
693
|
+
# [<tt>:using</tt>]
|
|
694
|
+
# Specify which index method to use when creating this exclusion constraint (e.g. +:btree+, +:gist+ etc).
|
|
695
|
+
# [<tt>:where</tt>]
|
|
696
|
+
# Specify an exclusion constraint on a subset of the table (internally PostgreSQL creates a partial index for this).
|
|
697
|
+
def add_exclusion_constraint(table_name, expression, **options)
|
|
698
|
+
options = exclusion_constraint_options(table_name, expression, options)
|
|
699
|
+
at = create_alter_table(table_name)
|
|
700
|
+
at.add_exclusion_constraint(expression, options)
|
|
701
|
+
|
|
702
|
+
execute schema_creation.accept(at)
|
|
703
|
+
end
|
|
704
|
+
|
|
705
|
+
def exclusion_constraint_options(table_name, expression, options) # :nodoc:
|
|
706
|
+
assert_valid_deferrable(options[:deferrable])
|
|
707
|
+
|
|
708
|
+
options = options.dup
|
|
709
|
+
options[:name] ||= exclusion_constraint_name(table_name, expression: expression, **options)
|
|
710
|
+
options
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
# Removes the given exclusion constraint from the table.
|
|
714
|
+
#
|
|
715
|
+
# remove_exclusion_constraint :products, name: "price_check"
|
|
716
|
+
#
|
|
717
|
+
# The +expression+ parameter will be ignored if present. It can be helpful
|
|
718
|
+
# to provide this in a migration's +change+ method so it can be reverted.
|
|
719
|
+
# In that case, +expression+ will be used by #add_exclusion_constraint.
|
|
720
|
+
def remove_exclusion_constraint(table_name, expression = nil, **options)
|
|
721
|
+
excl_name_to_delete = exclusion_constraint_for!(table_name, expression: expression, **options).name
|
|
722
|
+
|
|
723
|
+
at = create_alter_table(table_name)
|
|
724
|
+
at.drop_exclusion_constraint(excl_name_to_delete)
|
|
725
|
+
|
|
726
|
+
execute schema_creation.accept(at)
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
# Adds a new unique constraint to the table.
|
|
730
|
+
#
|
|
731
|
+
# add_unique_constraint :sections, [:position], deferrable: :deferred, name: "unique_position"
|
|
732
|
+
#
|
|
733
|
+
# generates:
|
|
734
|
+
#
|
|
735
|
+
# ALTER TABLE "sections" ADD CONSTRAINT unique_position UNIQUE (position) DEFERRABLE INITIALLY DEFERRED
|
|
736
|
+
#
|
|
737
|
+
# If you want to change an existing unique index to deferrable, you can use :using_index to create deferrable unique constraints.
|
|
738
|
+
#
|
|
739
|
+
# add_unique_constraint :sections, deferrable: :deferred, name: "unique_position", using_index: "index_sections_on_position"
|
|
740
|
+
#
|
|
741
|
+
# The +options+ hash can include the following keys:
|
|
742
|
+
# [<tt>:name</tt>]
|
|
743
|
+
# The constraint name. Defaults to <tt>uniq_rails_<identifier></tt>.
|
|
744
|
+
# [<tt>:deferrable</tt>]
|
|
745
|
+
# 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+.
|
|
746
|
+
# [<tt>:using_index</tt>]
|
|
747
|
+
# To specify an existing unique index name. Defaults to +nil+.
|
|
748
|
+
def add_unique_constraint(table_name, column_name = nil, **options)
|
|
749
|
+
options = unique_constraint_options(table_name, column_name, options)
|
|
750
|
+
at = create_alter_table(table_name)
|
|
751
|
+
at.add_unique_constraint(column_name, options)
|
|
752
|
+
|
|
753
|
+
execute schema_creation.accept(at)
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
def unique_constraint_options(table_name, column_name, options) # :nodoc:
|
|
757
|
+
assert_valid_deferrable(options[:deferrable])
|
|
758
|
+
|
|
759
|
+
if column_name && options[:using_index]
|
|
760
|
+
raise ArgumentError, "Cannot specify both column_name and :using_index options."
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
options = options.dup
|
|
764
|
+
options[:name] ||= unique_constraint_name(table_name, column: column_name, **options)
|
|
765
|
+
options
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
# Removes the given unique constraint from the table.
|
|
769
|
+
#
|
|
770
|
+
# remove_unique_constraint :sections, name: "unique_position"
|
|
771
|
+
#
|
|
772
|
+
# The +column_name+ parameter will be ignored if present. It can be helpful
|
|
773
|
+
# to provide this in a migration's +change+ method so it can be reverted.
|
|
774
|
+
# In that case, +column_name+ will be used by #add_unique_constraint.
|
|
775
|
+
def remove_unique_constraint(table_name, column_name = nil, **options)
|
|
776
|
+
unique_name_to_delete = unique_constraint_for!(table_name, column: column_name, **options).name
|
|
777
|
+
|
|
778
|
+
at = create_alter_table(table_name)
|
|
779
|
+
at.drop_unique_constraint(unique_name_to_delete)
|
|
780
|
+
|
|
781
|
+
execute schema_creation.accept(at)
|
|
782
|
+
end
|
|
783
|
+
|
|
546
784
|
# Maps logical Rails types to PostgreSQL-specific data types.
|
|
547
785
|
def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, enum_type: nil, **) # :nodoc:
|
|
548
786
|
sql = \
|
|
@@ -646,11 +884,32 @@ module ActiveRecord
|
|
|
646
884
|
validate_constraint table_name, chk_name_to_validate
|
|
647
885
|
end
|
|
648
886
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
887
|
+
def foreign_key_column_for(table_name, column_name) # :nodoc:
|
|
888
|
+
_schema, table_name = extract_schema_qualified_name(table_name)
|
|
889
|
+
super
|
|
890
|
+
end
|
|
891
|
+
|
|
892
|
+
def add_index_options(table_name, column_name, **options) # :nodoc:
|
|
893
|
+
if (where = options[:where]) && table_exists?(table_name) && column_exists?(table_name, where)
|
|
894
|
+
options[:where] = quote_column_name(where)
|
|
895
|
+
end
|
|
896
|
+
super
|
|
897
|
+
end
|
|
898
|
+
|
|
899
|
+
def quoted_include_columns_for_index(column_names) # :nodoc:
|
|
900
|
+
return quote_column_name(column_names) if column_names.is_a?(Symbol)
|
|
901
|
+
|
|
902
|
+
quoted_columns = column_names.each_with_object({}) do |name, result|
|
|
903
|
+
result[name.to_sym] = quote_column_name(name).dup
|
|
652
904
|
end
|
|
905
|
+
add_options_for_index_columns(quoted_columns).values.join(", ")
|
|
906
|
+
end
|
|
653
907
|
|
|
908
|
+
def schema_creation # :nodoc:
|
|
909
|
+
PostgreSQL::SchemaCreation.new(self)
|
|
910
|
+
end
|
|
911
|
+
|
|
912
|
+
private
|
|
654
913
|
def create_table_definition(name, **options)
|
|
655
914
|
PostgreSQL::TableDefinition.new(self, name, **options)
|
|
656
915
|
end
|
|
@@ -659,11 +918,16 @@ module ActiveRecord
|
|
|
659
918
|
PostgreSQL::AlterTable.new create_table_definition(name)
|
|
660
919
|
end
|
|
661
920
|
|
|
662
|
-
def new_column_from_field(table_name, field)
|
|
663
|
-
column_name, type, default, notnull, oid, fmod, collation, comment, attgenerated = field
|
|
921
|
+
def new_column_from_field(table_name, field, _definitions)
|
|
922
|
+
column_name, type, default, notnull, oid, fmod, collation, comment, identity, attgenerated = field
|
|
664
923
|
type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
|
|
665
924
|
default_value = extract_value_from_default(default)
|
|
666
|
-
|
|
925
|
+
|
|
926
|
+
if attgenerated.present?
|
|
927
|
+
default_function = default
|
|
928
|
+
else
|
|
929
|
+
default_function = extract_default_function(default_value, default)
|
|
930
|
+
end
|
|
667
931
|
|
|
668
932
|
if match = default_function&.match(/\Anextval\('"?(?<sequence_name>.+_(?<suffix>seq\d*))"?'::regclass\)\z/)
|
|
669
933
|
serial = sequence_name_from_parts(table_name, column_name, match[:suffix]) == match[:sequence_name]
|
|
@@ -678,6 +942,7 @@ module ActiveRecord
|
|
|
678
942
|
collation: collation,
|
|
679
943
|
comment: comment.presence,
|
|
680
944
|
serial: serial,
|
|
945
|
+
identity: identity.presence,
|
|
681
946
|
generated: attgenerated
|
|
682
947
|
)
|
|
683
948
|
end
|
|
@@ -718,8 +983,19 @@ module ActiveRecord
|
|
|
718
983
|
end
|
|
719
984
|
end
|
|
720
985
|
|
|
721
|
-
def
|
|
722
|
-
deferrable
|
|
986
|
+
def assert_valid_deferrable(deferrable)
|
|
987
|
+
return if !deferrable || %i(immediate deferred).include?(deferrable)
|
|
988
|
+
|
|
989
|
+
raise ArgumentError, "deferrable must be `:immediate` or `:deferred`, got: `#{deferrable.inspect}`"
|
|
990
|
+
end
|
|
991
|
+
|
|
992
|
+
def extract_constraint_deferrable(deferrable, deferred)
|
|
993
|
+
deferrable && (deferred ? :deferred : :immediate)
|
|
994
|
+
end
|
|
995
|
+
|
|
996
|
+
def reference_name_for_table(table_name)
|
|
997
|
+
_schema, table_name = extract_schema_qualified_name(table_name.to_s)
|
|
998
|
+
table_name.singularize
|
|
723
999
|
end
|
|
724
1000
|
|
|
725
1001
|
def add_column_for_alter(table_name, column_name, type, **options)
|
|
@@ -728,32 +1004,20 @@ module ActiveRecord
|
|
|
728
1004
|
end
|
|
729
1005
|
|
|
730
1006
|
def change_column_for_alter(table_name, column_name, type, **options)
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))]
|
|
1007
|
+
change_col_def = build_change_column_definition(table_name, column_name, type, **options)
|
|
1008
|
+
sqls = [schema_creation.accept(change_col_def)]
|
|
734
1009
|
sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
|
|
735
1010
|
sqls
|
|
736
1011
|
end
|
|
737
1012
|
|
|
738
|
-
def
|
|
739
|
-
column = column_for(table_name, column_name)
|
|
740
|
-
return unless column
|
|
741
|
-
|
|
742
|
-
default = extract_new_default_value(default_or_changes)
|
|
743
|
-
alter_column_query = "ALTER COLUMN #{quote_column_name(column_name)} %s"
|
|
1013
|
+
def change_column_null_for_alter(table_name, column_name, null, default = nil)
|
|
744
1014
|
if default.nil?
|
|
745
|
-
|
|
746
|
-
# cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
|
|
747
|
-
alter_column_query % "DROP DEFAULT"
|
|
1015
|
+
"ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
|
748
1016
|
else
|
|
749
|
-
|
|
1017
|
+
Proc.new { change_column_null(table_name, column_name, null, default) }
|
|
750
1018
|
end
|
|
751
1019
|
end
|
|
752
1020
|
|
|
753
|
-
def change_column_null_for_alter(table_name, column_name, null, default = nil)
|
|
754
|
-
"ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
|
755
|
-
end
|
|
756
|
-
|
|
757
1021
|
def add_index_opclass(quoted_columns, **options)
|
|
758
1022
|
opclasses = options_for_index_columns(options[:opclass])
|
|
759
1023
|
quoted_columns.each do |name, column|
|
|
@@ -766,6 +1030,46 @@ module ActiveRecord
|
|
|
766
1030
|
super
|
|
767
1031
|
end
|
|
768
1032
|
|
|
1033
|
+
def exclusion_constraint_name(table_name, **options)
|
|
1034
|
+
options.fetch(:name) do
|
|
1035
|
+
expression = options.fetch(:expression)
|
|
1036
|
+
identifier = "#{table_name}_#{expression}_excl"
|
|
1037
|
+
hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
|
|
1038
|
+
|
|
1039
|
+
"excl_rails_#{hashed_identifier}"
|
|
1040
|
+
end
|
|
1041
|
+
end
|
|
1042
|
+
|
|
1043
|
+
def exclusion_constraint_for(table_name, **options)
|
|
1044
|
+
excl_name = exclusion_constraint_name(table_name, **options)
|
|
1045
|
+
exclusion_constraints(table_name).detect { |excl| excl.name == excl_name }
|
|
1046
|
+
end
|
|
1047
|
+
|
|
1048
|
+
def exclusion_constraint_for!(table_name, expression: nil, **options)
|
|
1049
|
+
exclusion_constraint_for(table_name, expression: expression, **options) ||
|
|
1050
|
+
raise(ArgumentError, "Table '#{table_name}' has no exclusion constraint for #{expression || options}")
|
|
1051
|
+
end
|
|
1052
|
+
|
|
1053
|
+
def unique_constraint_name(table_name, **options)
|
|
1054
|
+
options.fetch(:name) do
|
|
1055
|
+
column_or_index = Array(options[:column] || options[:using_index]).map(&:to_s)
|
|
1056
|
+
identifier = "#{table_name}_#{column_or_index * '_and_'}_unique"
|
|
1057
|
+
hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
|
|
1058
|
+
|
|
1059
|
+
"uniq_rails_#{hashed_identifier}"
|
|
1060
|
+
end
|
|
1061
|
+
end
|
|
1062
|
+
|
|
1063
|
+
def unique_constraint_for(table_name, **options)
|
|
1064
|
+
name = unique_constraint_name(table_name, **options) unless options.key?(:column)
|
|
1065
|
+
unique_constraints(table_name).detect { |unique_constraint| unique_constraint.defined_for?(name: name, **options) }
|
|
1066
|
+
end
|
|
1067
|
+
|
|
1068
|
+
def unique_constraint_for!(table_name, column: nil, **options)
|
|
1069
|
+
unique_constraint_for(table_name, column: column, **options) ||
|
|
1070
|
+
raise(ArgumentError, "Table '#{table_name}' has no unique constraint for #{column || options}")
|
|
1071
|
+
end
|
|
1072
|
+
|
|
769
1073
|
def data_source_sql(name = nil, type: nil)
|
|
770
1074
|
scope = quoted_scope(name, type: type)
|
|
771
1075
|
scope[:type] ||= "'r','v','m','p','f'" # (r)elation/table, (v)iew, (m)aterialized view, (p)artitioned table, (f)oreign table
|
|
@@ -799,6 +1103,15 @@ module ActiveRecord
|
|
|
799
1103
|
name = Utils.extract_schema_qualified_name(string.to_s)
|
|
800
1104
|
[name.schema, name.identifier]
|
|
801
1105
|
end
|
|
1106
|
+
|
|
1107
|
+
def column_names_from_column_numbers(table_oid, column_numbers)
|
|
1108
|
+
Hash[query(<<~SQL, "SCHEMA")].values_at(*column_numbers).compact
|
|
1109
|
+
SELECT a.attnum, a.attname
|
|
1110
|
+
FROM pg_attribute a
|
|
1111
|
+
WHERE a.attrelid = #{table_oid}
|
|
1112
|
+
AND a.attnum IN (#{column_numbers.join(", ")})
|
|
1113
|
+
SQL
|
|
1114
|
+
end
|
|
802
1115
|
end
|
|
803
1116
|
end
|
|
804
1117
|
end
|