activerecord 6.1.7 → 7.1.0
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 +1516 -1019
- data/MIT-LICENSE +1 -1
- data/README.rdoc +17 -18
- data/lib/active_record/aggregations.rb +17 -14
- data/lib/active_record/association_relation.rb +1 -11
- data/lib/active_record/associations/association.rb +50 -19
- data/lib/active_record/associations/association_scope.rb +17 -12
- data/lib/active_record/associations/belongs_to_association.rb +28 -9
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
- data/lib/active_record/associations/builder/association.rb +11 -5
- data/lib/active_record/associations/builder/belongs_to.rb +40 -14
- data/lib/active_record/associations/builder/collection_association.rb +10 -3
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/has_many.rb +3 -2
- data/lib/active_record/associations/builder/has_one.rb +2 -1
- data/lib/active_record/associations/builder/singular_association.rb +6 -2
- data/lib/active_record/associations/collection_association.rb +35 -31
- data/lib/active_record/associations/collection_proxy.rb +30 -15
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -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 +12 -7
- data/lib/active_record/associations/has_one_association.rb +20 -10
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +26 -16
- data/lib/active_record/associations/preloader/association.rb +207 -52
- data/lib/active_record/associations/preloader/batch.rb +48 -0
- data/lib/active_record/associations/preloader/branch.rb +147 -0
- data/lib/active_record/associations/preloader/through_association.rb +50 -14
- data/lib/active_record/associations/preloader.rb +50 -121
- data/lib/active_record/associations/singular_association.rb +9 -3
- data/lib/active_record/associations/through_association.rb +25 -14
- data/lib/active_record/associations.rb +423 -289
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +1 -3
- data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
- data/lib/active_record/attribute_methods/dirty.rb +61 -14
- data/lib/active_record/attribute_methods/primary_key.rb +78 -26
- data/lib/active_record/attribute_methods/query.rb +31 -19
- data/lib/active_record/attribute_methods/read.rb +25 -10
- data/lib/active_record/attribute_methods/serialization.rb +194 -37
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
- data/lib/active_record/attribute_methods/write.rb +10 -13
- data/lib/active_record/attribute_methods.rb +121 -40
- data/lib/active_record/attributes.rb +27 -38
- data/lib/active_record/autosave_association.rb +61 -30
- data/lib/active_record/base.rb +25 -2
- data/lib/active_record/callbacks.rb +18 -34
- 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 -46
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +367 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +96 -590
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +171 -51
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +77 -27
- data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +360 -136
- data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
- data/lib/active_record/connection_adapters/abstract_adapter.rb +622 -149
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +285 -156
- data/lib/active_record/connection_adapters/column.rb +13 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
- data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
- 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 +38 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +104 -53
- data/lib/active_record/connection_adapters/pool_config.rb +20 -11
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +18 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -52
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +381 -69
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +492 -230
- data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +65 -53
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +294 -102
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
- data/lib/active_record/connection_adapters.rb +9 -6
- data/lib/active_record/connection_handling.rb +107 -136
- data/lib/active_record/core.rb +194 -224
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
- data/lib/active_record/database_configurations/database_config.rb +21 -12
- data/lib/active_record/database_configurations/hash_config.rb +84 -16
- data/lib/active_record/database_configurations/url_config.rb +18 -12
- data/lib/active_record/database_configurations.rb +95 -59
- data/lib/active_record/delegated_type.rb +61 -15
- 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 +39 -0
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
- data/lib/active_record/encryption/cipher.rb +53 -0
- data/lib/active_record/encryption/config.rb +68 -0
- data/lib/active_record/encryption/configurable.rb +60 -0
- data/lib/active_record/encryption/context.rb +42 -0
- data/lib/active_record/encryption/contexts.rb +76 -0
- data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
- data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
- data/lib/active_record/encryption/encryptable_record.rb +224 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +151 -0
- data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
- data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
- data/lib/active_record/encryption/encryptor.rb +155 -0
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
- data/lib/active_record/encryption/errors.rb +15 -0
- data/lib/active_record/encryption/extended_deterministic_queries.rb +172 -0
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
- data/lib/active_record/encryption/key.rb +28 -0
- data/lib/active_record/encryption/key_generator.rb +53 -0
- data/lib/active_record/encryption/key_provider.rb +46 -0
- data/lib/active_record/encryption/message.rb +33 -0
- data/lib/active_record/encryption/message_serializer.rb +92 -0
- data/lib/active_record/encryption/null_encryptor.rb +21 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
- data/lib/active_record/encryption/scheme.rb +96 -0
- data/lib/active_record/encryption.rb +56 -0
- data/lib/active_record/enum.rb +156 -62
- data/lib/active_record/errors.rb +171 -15
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +15 -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 +70 -14
- data/lib/active_record/fixture_set/table_rows.rb +4 -4
- data/lib/active_record/fixtures.rb +131 -86
- data/lib/active_record/future_result.rb +164 -0
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +81 -29
- data/lib/active_record/insert_all.rb +133 -20
- data/lib/active_record/integration.rb +11 -10
- data/lib/active_record/internal_metadata.rb +117 -33
- data/lib/active_record/legacy_yaml_adapter.rb +2 -39
- data/lib/active_record/locking/optimistic.rb +36 -21
- data/lib/active_record/locking/pessimistic.rb +15 -6
- data/lib/active_record/log_subscriber.rb +52 -19
- 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 +10 -10
- data/lib/active_record/middleware/database_selector.rb +23 -13
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +108 -13
- data/lib/active_record/migration/compatibility.rb +221 -48
- 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/join_table.rb +1 -1
- data/lib/active_record/migration.rb +355 -171
- data/lib/active_record/model_schema.rb +116 -97
- data/lib/active_record/nested_attributes.rb +36 -15
- data/lib/active_record/no_touching.rb +3 -3
- data/lib/active_record/normalization.rb +159 -0
- data/lib/active_record/persistence.rb +405 -85
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +174 -0
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +29 -6
- data/lib/active_record/railtie.rb +219 -43
- data/lib/active_record/railties/controller_runtime.rb +13 -9
- data/lib/active_record/railties/databases.rake +185 -249
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +41 -3
- data/lib/active_record/reflection.rb +229 -80
- data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
- data/lib/active_record/relation/batches.rb +192 -63
- data/lib/active_record/relation/calculations.rb +211 -90
- data/lib/active_record/relation/delegation.rb +27 -13
- data/lib/active_record/relation/finder_methods.rb +108 -51
- data/lib/active_record/relation/merger.rb +22 -13
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +27 -20
- data/lib/active_record/relation/query_attribute.rb +30 -12
- data/lib/active_record/relation/query_methods.rb +654 -127
- data/lib/active_record/relation/record_fetch_warning.rb +7 -9
- data/lib/active_record/relation/spawn_methods.rb +20 -3
- data/lib/active_record/relation/where_clause.rb +10 -19
- data/lib/active_record/relation.rb +262 -120
- data/lib/active_record/result.rb +37 -11
- data/lib/active_record/runtime_registry.rb +18 -13
- data/lib/active_record/sanitization.rb +65 -20
- data/lib/active_record/schema.rb +36 -22
- data/lib/active_record/schema_dumper.rb +73 -24
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +72 -15
- data/lib/active_record/scoping/named.rb +5 -13
- data/lib/active_record/scoping.rb +65 -34
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/serialization.rb +6 -1
- data/lib/active_record/signed_id.rb +10 -8
- data/lib/active_record/store.rb +10 -10
- data/lib/active_record/suppressor.rb +13 -15
- data/lib/active_record/table_metadata.rb +16 -3
- data/lib/active_record/tasks/database_tasks.rb +225 -136
- data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
- data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +116 -96
- data/lib/active_record/timestamp.rb +28 -17
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +48 -27
- data/lib/active_record/translation.rb +3 -3
- data/lib/active_record/type/adapter_specific_registry.rb +32 -14
- data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +9 -5
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type/type_map.rb +17 -20
- data/lib/active_record/type.rb +1 -2
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +4 -4
- 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 +51 -6
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +335 -32
- data/lib/arel/attributes/attribute.rb +0 -8
- data/lib/arel/crud.rb +28 -22
- data/lib/arel/delete_manager.rb +18 -4
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +2 -3
- data/lib/arel/nodes/and.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/casted.rb +1 -1
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +12 -13
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/function.rb +1 -0
- data/lib/arel/nodes/homogeneous_in.rb +0 -8
- data/lib/arel/nodes/insert_statement.rb +2 -2
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/select_core.rb +2 -2
- data/lib/arel/nodes/select_statement.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes/update_statement.rb +8 -3
- data/lib/arel/nodes.rb +5 -0
- data/lib/arel/predications.rb +13 -3
- data/lib/arel/select_manager.rb +10 -4
- data/lib/arel/table.rb +9 -6
- data/lib/arel/tree_manager.rb +0 -12
- data/lib/arel/update_manager.rb +18 -4
- data/lib/arel/visitors/dot.rb +80 -90
- data/lib/arel/visitors/mysql.rb +16 -3
- data/lib/arel/visitors/postgresql.rb +0 -10
- data/lib/arel/visitors/to_sql.rb +139 -19
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +18 -3
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -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
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
- data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
- data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
- metadata +92 -13
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -67
@@ -6,7 +6,7 @@ module ActiveRecord
|
|
6
6
|
module SchemaStatements
|
7
7
|
# Drops the database specified on the +name+ attribute
|
8
8
|
# and creates it again using the provided +options+.
|
9
|
-
def recreate_database(name, options = {})
|
9
|
+
def recreate_database(name, options = {}) # :nodoc:
|
10
10
|
drop_database(name)
|
11
11
|
create_database(name, options)
|
12
12
|
end
|
@@ -50,7 +50,7 @@ module ActiveRecord
|
|
50
50
|
#
|
51
51
|
# Example:
|
52
52
|
# drop_database 'matt_development'
|
53
|
-
def drop_database(name)
|
53
|
+
def drop_database(name) # :nodoc:
|
54
54
|
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
55
55
|
end
|
56
56
|
|
@@ -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,11 @@ 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
|
-
def default_sequence_name(table_name, pk = "id")
|
249
|
+
def default_sequence_name(table_name, pk = "id") # :nodoc:
|
248
250
|
result = serial_sequence(table_name, pk)
|
249
251
|
return nil unless result
|
250
252
|
Utils.extract_schema_qualified_name(result).to_s
|
@@ -257,7 +259,7 @@ module ActiveRecord
|
|
257
259
|
end
|
258
260
|
|
259
261
|
# Sets the sequence of a table's primary key to the specified value.
|
260
|
-
def set_pk_sequence!(table, value)
|
262
|
+
def set_pk_sequence!(table, value) # :nodoc:
|
261
263
|
pk, sequence = pk_and_sequence_for(table)
|
262
264
|
|
263
265
|
if pk
|
@@ -272,7 +274,7 @@ module ActiveRecord
|
|
272
274
|
end
|
273
275
|
|
274
276
|
# Resets the sequence of a table's primary key to the maximum value.
|
275
|
-
def reset_pk_sequence!(table, pk = nil, sequence = nil)
|
277
|
+
def reset_pk_sequence!(table, pk = nil, sequence = nil) # :nodoc:
|
276
278
|
unless pk && sequence
|
277
279
|
default_pk, default_sequence = pk_and_sequence_for(table)
|
278
280
|
|
@@ -288,19 +290,19 @@ module ActiveRecord
|
|
288
290
|
quoted_sequence = quote_table_name(sequence)
|
289
291
|
max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
|
290
292
|
if max_pk.nil?
|
291
|
-
if database_version >=
|
293
|
+
if database_version >= 10_00_00
|
292
294
|
minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
|
293
295
|
else
|
294
296
|
minvalue = query_value("SELECT min_value FROM #{quoted_sequence}", "SCHEMA")
|
295
297
|
end
|
296
298
|
end
|
297
299
|
|
298
|
-
query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk
|
300
|
+
query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk || minvalue}, #{max_pk ? true : false})", "SCHEMA")
|
299
301
|
end
|
300
302
|
end
|
301
303
|
|
302
304
|
# Returns a table's primary key and belonging sequence.
|
303
|
-
def pk_and_sequence_for(table)
|
305
|
+
def pk_and_sequence_for(table) # :nodoc:
|
304
306
|
# First try looking for a sequence with a dependency on the
|
305
307
|
# given table's primary key.
|
306
308
|
result = query(<<~SQL, "SCHEMA")[0]
|
@@ -375,7 +377,8 @@ module ActiveRecord
|
|
375
377
|
#
|
376
378
|
# Example:
|
377
379
|
# rename_table('octopuses', 'octopi')
|
378
|
-
def rename_table(table_name, new_name)
|
380
|
+
def rename_table(table_name, new_name, **options)
|
381
|
+
validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
|
379
382
|
clear_cache!
|
380
383
|
schema_cache.clear_data_source_cache!(table_name.to_s)
|
381
384
|
schema_cache.clear_data_source_cache!(new_name.to_s)
|
@@ -393,31 +396,52 @@ module ActiveRecord
|
|
393
396
|
rename_table_indexes(table_name, new_name)
|
394
397
|
end
|
395
398
|
|
396
|
-
def add_column(table_name, column_name, type, **options)
|
399
|
+
def add_column(table_name, column_name, type, **options) # :nodoc:
|
397
400
|
clear_cache!
|
398
401
|
super
|
399
402
|
change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
|
400
403
|
end
|
401
404
|
|
402
|
-
def change_column(table_name, column_name, type, **options)
|
405
|
+
def change_column(table_name, column_name, type, **options) # :nodoc:
|
403
406
|
clear_cache!
|
404
407
|
sqls, procs = Array(change_column_for_alter(table_name, column_name, type, **options)).partition { |v| v.is_a?(String) }
|
405
408
|
execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}"
|
406
409
|
procs.each(&:call)
|
407
410
|
end
|
408
411
|
|
412
|
+
# Builds a ChangeColumnDefinition object.
|
413
|
+
#
|
414
|
+
# This definition object contains information about the column change that would occur
|
415
|
+
# if the same arguments were passed to #change_column. See #change_column for information about
|
416
|
+
# passing a +table_name+, +column_name+, +type+ and other options that can be passed.
|
417
|
+
def build_change_column_definition(table_name, column_name, type, **options) # :nodoc:
|
418
|
+
td = create_table_definition(table_name)
|
419
|
+
cd = td.new_column_definition(column_name, type, **options)
|
420
|
+
ChangeColumnDefinition.new(cd, column_name)
|
421
|
+
end
|
422
|
+
|
409
423
|
# Changes the default value of a table column.
|
410
424
|
def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
|
411
425
|
execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
|
412
426
|
end
|
413
427
|
|
414
|
-
def
|
428
|
+
def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc:
|
429
|
+
column = column_for(table_name, column_name)
|
430
|
+
return unless column
|
431
|
+
|
432
|
+
default = extract_new_default_value(default_or_changes)
|
433
|
+
ChangeColumnDefaultDefinition.new(column, default)
|
434
|
+
end
|
435
|
+
|
436
|
+
def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
|
437
|
+
validate_change_column_null_argument!(null)
|
438
|
+
|
415
439
|
clear_cache!
|
416
440
|
unless null || default.nil?
|
417
441
|
column = column_for(table_name, column_name)
|
418
442
|
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
443
|
end
|
420
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} #{
|
444
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
421
445
|
end
|
422
446
|
|
423
447
|
# Adds comment for given table column or drops it if +comment+ is a +nil+
|
@@ -435,22 +459,26 @@ module ActiveRecord
|
|
435
459
|
end
|
436
460
|
|
437
461
|
# Renames a column in a table.
|
438
|
-
def rename_column(table_name, column_name, new_column_name)
|
462
|
+
def rename_column(table_name, column_name, new_column_name) # :nodoc:
|
439
463
|
clear_cache!
|
440
464
|
execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
|
441
465
|
rename_column_indexes(table_name, column_name, new_column_name)
|
442
466
|
end
|
443
467
|
|
444
|
-
def add_index(table_name, column_name, **options)
|
445
|
-
|
446
|
-
|
447
|
-
create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists)
|
468
|
+
def add_index(table_name, column_name, **options) # :nodoc:
|
469
|
+
create_index = build_create_index_definition(table_name, column_name, **options)
|
448
470
|
result = execute schema_creation.accept(create_index)
|
449
471
|
|
472
|
+
index = create_index.index
|
450
473
|
execute "COMMENT ON INDEX #{quote_column_name(index.name)} IS #{quote(index.comment)}" if index.comment
|
451
474
|
result
|
452
475
|
end
|
453
476
|
|
477
|
+
def build_create_index_definition(table_name, column_name, **options) # :nodoc:
|
478
|
+
index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
|
479
|
+
CreateIndexDefinition.new(index, algorithm, if_not_exists)
|
480
|
+
end
|
481
|
+
|
454
482
|
def remove_index(table_name, column_name = nil, **options) # :nodoc:
|
455
483
|
table = Utils.extract_schema_qualified_name(table_name.to_s)
|
456
484
|
|
@@ -477,13 +505,33 @@ module ActiveRecord
|
|
477
505
|
def rename_index(table_name, old_name, new_name)
|
478
506
|
validate_index_length!(table_name, new_name)
|
479
507
|
|
480
|
-
|
508
|
+
schema, = extract_schema_qualified_name(table_name)
|
509
|
+
execute "ALTER INDEX #{quote_table_name(schema) + '.' if schema}#{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
|
510
|
+
end
|
511
|
+
|
512
|
+
def index_name(table_name, options) # :nodoc:
|
513
|
+
_schema, table_name = extract_schema_qualified_name(table_name.to_s)
|
514
|
+
super
|
515
|
+
end
|
516
|
+
|
517
|
+
def add_foreign_key(from_table, to_table, **options)
|
518
|
+
if options[:deferrable] == true
|
519
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
520
|
+
`deferrable: true` is deprecated in favor of `deferrable: :immediate`, and will be removed in Rails 7.2.
|
521
|
+
MSG
|
522
|
+
|
523
|
+
options[:deferrable] = :immediate
|
524
|
+
end
|
525
|
+
|
526
|
+
assert_valid_deferrable(options[:deferrable])
|
527
|
+
|
528
|
+
super
|
481
529
|
end
|
482
530
|
|
483
531
|
def foreign_keys(table_name)
|
484
532
|
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
|
533
|
+
fk_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
534
|
+
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
535
|
FROM pg_constraint c
|
488
536
|
JOIN pg_class t1 ON c.conrelid = t1.oid
|
489
537
|
JOIN pg_class t2 ON c.confrelid = t2.oid
|
@@ -497,17 +545,31 @@ module ActiveRecord
|
|
497
545
|
SQL
|
498
546
|
|
499
547
|
fk_info.map do |row|
|
548
|
+
to_table = Utils.unquote_identifier(row["to_table"])
|
549
|
+
conkey = row["conkey"].scan(/\d+/).map(&:to_i)
|
550
|
+
confkey = row["confkey"].scan(/\d+/).map(&:to_i)
|
551
|
+
|
552
|
+
if conkey.size > 1
|
553
|
+
column = column_names_from_column_numbers(row["conrelid"], conkey)
|
554
|
+
primary_key = column_names_from_column_numbers(row["confrelid"], confkey)
|
555
|
+
else
|
556
|
+
column = Utils.unquote_identifier(row["column"])
|
557
|
+
primary_key = row["primary_key"]
|
558
|
+
end
|
559
|
+
|
500
560
|
options = {
|
501
|
-
column:
|
561
|
+
column: column,
|
502
562
|
name: row["name"],
|
503
|
-
primary_key:
|
563
|
+
primary_key: primary_key
|
504
564
|
}
|
505
565
|
|
506
566
|
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
|
507
567
|
options[:on_update] = extract_foreign_key_action(row["on_update"])
|
568
|
+
options[:deferrable] = extract_constraint_deferrable(row["deferrable"], row["deferred"])
|
569
|
+
|
508
570
|
options[:validate] = row["valid"]
|
509
571
|
|
510
|
-
ForeignKeyDefinition.new(table_name,
|
572
|
+
ForeignKeyDefinition.new(table_name, to_table, options)
|
511
573
|
end
|
512
574
|
end
|
513
575
|
|
@@ -522,12 +584,14 @@ module ActiveRecord
|
|
522
584
|
def check_constraints(table_name) # :nodoc:
|
523
585
|
scope = quoted_scope(table_name)
|
524
586
|
|
525
|
-
check_info =
|
526
|
-
SELECT conname, pg_get_constraintdef(c.oid) AS constraintdef, c.convalidated AS valid
|
587
|
+
check_info = internal_exec_query(<<-SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
588
|
+
SELECT conname, pg_get_constraintdef(c.oid, true) AS constraintdef, c.convalidated AS valid
|
527
589
|
FROM pg_constraint c
|
528
590
|
JOIN pg_class t ON c.conrelid = t.oid
|
591
|
+
JOIN pg_namespace n ON n.oid = c.connamespace
|
529
592
|
WHERE c.contype = 'c'
|
530
593
|
AND t.relname = #{scope[:name]}
|
594
|
+
AND n.nspname = #{scope[:schema]}
|
531
595
|
SQL
|
532
596
|
|
533
597
|
check_info.map do |row|
|
@@ -535,14 +599,179 @@ module ActiveRecord
|
|
535
599
|
name: row["conname"],
|
536
600
|
validate: row["valid"]
|
537
601
|
}
|
538
|
-
expression = row["constraintdef"][/CHECK \(
|
602
|
+
expression = row["constraintdef"][/CHECK \((.+)\)/m, 1]
|
539
603
|
|
540
604
|
CheckConstraintDefinition.new(table_name, expression, options)
|
541
605
|
end
|
542
606
|
end
|
543
607
|
|
608
|
+
# Returns an array of exclusion constraints for the given table.
|
609
|
+
# The exclusion constraints are represented as ExclusionConstraintDefinition objects.
|
610
|
+
def exclusion_constraints(table_name)
|
611
|
+
scope = quoted_scope(table_name)
|
612
|
+
|
613
|
+
exclusion_info = internal_exec_query(<<-SQL, "SCHEMA")
|
614
|
+
SELECT conname, pg_get_constraintdef(c.oid) AS constraintdef, c.condeferrable, c.condeferred
|
615
|
+
FROM pg_constraint c
|
616
|
+
JOIN pg_class t ON c.conrelid = t.oid
|
617
|
+
JOIN pg_namespace n ON n.oid = c.connamespace
|
618
|
+
WHERE c.contype = 'x'
|
619
|
+
AND t.relname = #{scope[:name]}
|
620
|
+
AND n.nspname = #{scope[:schema]}
|
621
|
+
SQL
|
622
|
+
|
623
|
+
exclusion_info.map do |row|
|
624
|
+
method_and_elements, predicate = row["constraintdef"].split(" WHERE ")
|
625
|
+
method_and_elements_parts = method_and_elements.match(/EXCLUDE(?: USING (?<using>\S+))? \((?<expression>.+)\)/)
|
626
|
+
predicate.remove!(/ DEFERRABLE(?: INITIALLY (?:IMMEDIATE|DEFERRED))?/) if predicate
|
627
|
+
predicate = predicate.from(2).to(-3) if predicate # strip 2 opening and closing parentheses
|
628
|
+
|
629
|
+
deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
|
630
|
+
|
631
|
+
options = {
|
632
|
+
name: row["conname"],
|
633
|
+
using: method_and_elements_parts["using"].to_sym,
|
634
|
+
where: predicate,
|
635
|
+
deferrable: deferrable
|
636
|
+
}
|
637
|
+
|
638
|
+
ExclusionConstraintDefinition.new(table_name, method_and_elements_parts["expression"], options)
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
# Returns an array of unique constraints for the given table.
|
643
|
+
# The unique constraints are represented as UniqueConstraintDefinition objects.
|
644
|
+
def unique_constraints(table_name)
|
645
|
+
scope = quoted_scope(table_name)
|
646
|
+
|
647
|
+
unique_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
648
|
+
SELECT c.conname, c.conrelid, c.conkey, c.condeferrable, c.condeferred
|
649
|
+
FROM pg_constraint c
|
650
|
+
JOIN pg_class t ON c.conrelid = t.oid
|
651
|
+
JOIN pg_namespace n ON n.oid = c.connamespace
|
652
|
+
WHERE c.contype = 'u'
|
653
|
+
AND t.relname = #{scope[:name]}
|
654
|
+
AND n.nspname = #{scope[:schema]}
|
655
|
+
SQL
|
656
|
+
|
657
|
+
unique_info.map do |row|
|
658
|
+
conkey = row["conkey"].delete("{}").split(",").map(&:to_i)
|
659
|
+
columns = column_names_from_column_numbers(row["conrelid"], conkey)
|
660
|
+
|
661
|
+
deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
|
662
|
+
|
663
|
+
options = {
|
664
|
+
name: row["conname"],
|
665
|
+
deferrable: deferrable
|
666
|
+
}
|
667
|
+
|
668
|
+
UniqueConstraintDefinition.new(table_name, columns, options)
|
669
|
+
end
|
670
|
+
end
|
671
|
+
|
672
|
+
# Adds a new exclusion constraint to the table. +expression+ is a String
|
673
|
+
# representation of a list of exclusion elements and operators.
|
674
|
+
#
|
675
|
+
# add_exclusion_constraint :products, "price WITH =, availability_range WITH &&", using: :gist, name: "price_check"
|
676
|
+
#
|
677
|
+
# generates:
|
678
|
+
#
|
679
|
+
# ALTER TABLE "products" ADD CONSTRAINT price_check EXCLUDE USING gist (price WITH =, availability_range WITH &&)
|
680
|
+
#
|
681
|
+
# The +options+ hash can include the following keys:
|
682
|
+
# [<tt>:name</tt>]
|
683
|
+
# The constraint name. Defaults to <tt>excl_rails_<identifier></tt>.
|
684
|
+
# [<tt>:deferrable</tt>]
|
685
|
+
# 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+.
|
686
|
+
def add_exclusion_constraint(table_name, expression, **options)
|
687
|
+
options = exclusion_constraint_options(table_name, expression, options)
|
688
|
+
at = create_alter_table(table_name)
|
689
|
+
at.add_exclusion_constraint(expression, options)
|
690
|
+
|
691
|
+
execute schema_creation.accept(at)
|
692
|
+
end
|
693
|
+
|
694
|
+
def exclusion_constraint_options(table_name, expression, options) # :nodoc:
|
695
|
+
assert_valid_deferrable(options[:deferrable])
|
696
|
+
|
697
|
+
options = options.dup
|
698
|
+
options[:name] ||= exclusion_constraint_name(table_name, expression: expression, **options)
|
699
|
+
options
|
700
|
+
end
|
701
|
+
|
702
|
+
# Removes the given exclusion constraint from the table.
|
703
|
+
#
|
704
|
+
# remove_exclusion_constraint :products, name: "price_check"
|
705
|
+
#
|
706
|
+
# The +expression+ parameter will be ignored if present. It can be helpful
|
707
|
+
# to provide this in a migration's +change+ method so it can be reverted.
|
708
|
+
# In that case, +expression+ will be used by #add_exclusion_constraint.
|
709
|
+
def remove_exclusion_constraint(table_name, expression = nil, **options)
|
710
|
+
excl_name_to_delete = exclusion_constraint_for!(table_name, expression: expression, **options).name
|
711
|
+
|
712
|
+
at = create_alter_table(table_name)
|
713
|
+
at.drop_exclusion_constraint(excl_name_to_delete)
|
714
|
+
|
715
|
+
execute schema_creation.accept(at)
|
716
|
+
end
|
717
|
+
|
718
|
+
# Adds a new unique constraint to the table.
|
719
|
+
#
|
720
|
+
# add_unique_constraint :sections, [:position], deferrable: :deferred, name: "unique_position"
|
721
|
+
#
|
722
|
+
# generates:
|
723
|
+
#
|
724
|
+
# ALTER TABLE "sections" ADD CONSTRAINT unique_position UNIQUE (position) DEFERRABLE INITIALLY DEFERRED
|
725
|
+
#
|
726
|
+
# If you want to change an existing unique index to deferrable, you can use :using_index to create deferrable unique constraints.
|
727
|
+
#
|
728
|
+
# add_unique_constraint :sections, deferrable: :deferred, name: "unique_position", using_index: "index_sections_on_position"
|
729
|
+
#
|
730
|
+
# The +options+ hash can include the following keys:
|
731
|
+
# [<tt>:name</tt>]
|
732
|
+
# The constraint name. Defaults to <tt>uniq_rails_<identifier></tt>.
|
733
|
+
# [<tt>:deferrable</tt>]
|
734
|
+
# 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+.
|
735
|
+
# [<tt>:using_index</tt>]
|
736
|
+
# To specify an existing unique index name. Defaults to +nil+.
|
737
|
+
def add_unique_constraint(table_name, column_name = nil, **options)
|
738
|
+
options = unique_constraint_options(table_name, column_name, options)
|
739
|
+
at = create_alter_table(table_name)
|
740
|
+
at.add_unique_constraint(column_name, options)
|
741
|
+
|
742
|
+
execute schema_creation.accept(at)
|
743
|
+
end
|
744
|
+
|
745
|
+
def unique_constraint_options(table_name, column_name, options) # :nodoc:
|
746
|
+
assert_valid_deferrable(options[:deferrable])
|
747
|
+
|
748
|
+
if column_name && options[:using_index]
|
749
|
+
raise ArgumentError, "Cannot specify both column_name and :using_index options."
|
750
|
+
end
|
751
|
+
|
752
|
+
options = options.dup
|
753
|
+
options[:name] ||= unique_constraint_name(table_name, column: column_name, **options)
|
754
|
+
options
|
755
|
+
end
|
756
|
+
|
757
|
+
# Removes the given unique constraint from the table.
|
758
|
+
#
|
759
|
+
# remove_unique_constraint :sections, name: "unique_position"
|
760
|
+
#
|
761
|
+
# The +column_name+ parameter will be ignored if present. It can be helpful
|
762
|
+
# to provide this in a migration's +change+ method so it can be reverted.
|
763
|
+
# In that case, +column_name+ will be used by #add_unique_constraint.
|
764
|
+
def remove_unique_constraint(table_name, column_name = nil, **options)
|
765
|
+
unique_name_to_delete = unique_constraint_for!(table_name, column: column_name, **options).name
|
766
|
+
|
767
|
+
at = create_alter_table(table_name)
|
768
|
+
at.drop_unique_constraint(unique_name_to_delete)
|
769
|
+
|
770
|
+
execute schema_creation.accept(at)
|
771
|
+
end
|
772
|
+
|
544
773
|
# Maps logical Rails types to PostgreSQL-specific data types.
|
545
|
-
def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
|
774
|
+
def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, enum_type: nil, **) # :nodoc:
|
546
775
|
sql = \
|
547
776
|
case type.to_s
|
548
777
|
when "binary"
|
@@ -566,6 +795,10 @@ module ActiveRecord
|
|
566
795
|
when 5..8; "bigint"
|
567
796
|
else raise ArgumentError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead."
|
568
797
|
end
|
798
|
+
when "enum"
|
799
|
+
raise ArgumentError, "enum_type is required for enums" if enum_type.nil?
|
800
|
+
|
801
|
+
enum_type
|
569
802
|
else
|
570
803
|
super
|
571
804
|
end
|
@@ -576,7 +809,7 @@ module ActiveRecord
|
|
576
809
|
|
577
810
|
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
|
578
811
|
# requires that the ORDER BY include the distinct column.
|
579
|
-
def columns_for_distinct(columns, orders)
|
812
|
+
def columns_for_distinct(columns, orders) # :nodoc:
|
580
813
|
order_columns = orders.compact_blank.map { |s|
|
581
814
|
# Convert Arel node to string
|
582
815
|
s = visitor.compile(s) unless s.is_a?(String)
|
@@ -640,11 +873,32 @@ module ActiveRecord
|
|
640
873
|
validate_constraint table_name, chk_name_to_validate
|
641
874
|
end
|
642
875
|
|
643
|
-
|
644
|
-
|
645
|
-
|
876
|
+
def foreign_key_column_for(table_name, column_name) # :nodoc:
|
877
|
+
_schema, table_name = extract_schema_qualified_name(table_name)
|
878
|
+
super
|
879
|
+
end
|
880
|
+
|
881
|
+
def add_index_options(table_name, column_name, **options) # :nodoc:
|
882
|
+
if (where = options[:where]) && table_exists?(table_name) && column_exists?(table_name, where)
|
883
|
+
options[:where] = quote_column_name(where)
|
646
884
|
end
|
885
|
+
super
|
886
|
+
end
|
887
|
+
|
888
|
+
def quoted_include_columns_for_index(column_names) # :nodoc:
|
889
|
+
return quote_column_name(column_names) if column_names.is_a?(Symbol)
|
890
|
+
|
891
|
+
quoted_columns = column_names.each_with_object({}) do |name, result|
|
892
|
+
result[name.to_sym] = quote_column_name(name).dup
|
893
|
+
end
|
894
|
+
add_options_for_index_columns(quoted_columns).values.join(", ")
|
895
|
+
end
|
647
896
|
|
897
|
+
def schema_creation # :nodoc:
|
898
|
+
PostgreSQL::SchemaCreation.new(self)
|
899
|
+
end
|
900
|
+
|
901
|
+
private
|
648
902
|
def create_table_definition(name, **options)
|
649
903
|
PostgreSQL::TableDefinition.new(self, name, **options)
|
650
904
|
end
|
@@ -653,11 +907,16 @@ module ActiveRecord
|
|
653
907
|
PostgreSQL::AlterTable.new create_table_definition(name)
|
654
908
|
end
|
655
909
|
|
656
|
-
def new_column_from_field(table_name, field)
|
657
|
-
column_name, type, default, notnull, oid, fmod, collation, comment = field
|
910
|
+
def new_column_from_field(table_name, field, _definitions)
|
911
|
+
column_name, type, default, notnull, oid, fmod, collation, comment, attgenerated = field
|
658
912
|
type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
|
659
913
|
default_value = extract_value_from_default(default)
|
660
|
-
|
914
|
+
|
915
|
+
if attgenerated.present?
|
916
|
+
default_function = default
|
917
|
+
else
|
918
|
+
default_function = extract_default_function(default_value, default)
|
919
|
+
end
|
661
920
|
|
662
921
|
if match = default_function&.match(/\Anextval\('"?(?<sequence_name>.+_(?<suffix>seq\d*))"?'::regclass\)\z/)
|
663
922
|
serial = sequence_name_from_parts(table_name, column_name, match[:suffix]) == match[:sequence_name]
|
@@ -671,7 +930,8 @@ module ActiveRecord
|
|
671
930
|
default_function,
|
672
931
|
collation: collation,
|
673
932
|
comment: comment.presence,
|
674
|
-
serial: serial
|
933
|
+
serial: serial,
|
934
|
+
generated: attgenerated
|
675
935
|
)
|
676
936
|
end
|
677
937
|
|
@@ -711,38 +971,41 @@ module ActiveRecord
|
|
711
971
|
end
|
712
972
|
end
|
713
973
|
|
974
|
+
def assert_valid_deferrable(deferrable)
|
975
|
+
return if !deferrable || %i(immediate deferred).include?(deferrable)
|
976
|
+
|
977
|
+
raise ArgumentError, "deferrable must be `:immediate` or `:deferred`, got: `#{deferrable.inspect}`"
|
978
|
+
end
|
979
|
+
|
980
|
+
def extract_constraint_deferrable(deferrable, deferred)
|
981
|
+
deferrable && (deferred ? :deferred : :immediate)
|
982
|
+
end
|
983
|
+
|
984
|
+
def reference_name_for_table(table_name)
|
985
|
+
_schema, table_name = extract_schema_qualified_name(table_name.to_s)
|
986
|
+
table_name.singularize
|
987
|
+
end
|
988
|
+
|
714
989
|
def add_column_for_alter(table_name, column_name, type, **options)
|
715
990
|
return super unless options.key?(:comment)
|
716
991
|
[super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
|
717
992
|
end
|
718
993
|
|
719
994
|
def change_column_for_alter(table_name, column_name, type, **options)
|
720
|
-
|
721
|
-
|
722
|
-
sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))]
|
995
|
+
change_col_def = build_change_column_definition(table_name, column_name, type, **options)
|
996
|
+
sqls = [schema_creation.accept(change_col_def)]
|
723
997
|
sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
|
724
998
|
sqls
|
725
999
|
end
|
726
1000
|
|
727
|
-
def
|
728
|
-
column = column_for(table_name, column_name)
|
729
|
-
return unless column
|
730
|
-
|
731
|
-
default = extract_new_default_value(default_or_changes)
|
732
|
-
alter_column_query = "ALTER COLUMN #{quote_column_name(column_name)} %s"
|
1001
|
+
def change_column_null_for_alter(table_name, column_name, null, default = nil)
|
733
1002
|
if default.nil?
|
734
|
-
|
735
|
-
# cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
|
736
|
-
alter_column_query % "DROP DEFAULT"
|
1003
|
+
"ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
737
1004
|
else
|
738
|
-
|
1005
|
+
Proc.new { change_column_null(table_name, column_name, null, default) }
|
739
1006
|
end
|
740
1007
|
end
|
741
1008
|
|
742
|
-
def change_column_null_for_alter(table_name, column_name, null, default = nil)
|
743
|
-
"ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
744
|
-
end
|
745
|
-
|
746
1009
|
def add_index_opclass(quoted_columns, **options)
|
747
1010
|
opclasses = options_for_index_columns(options[:opclass])
|
748
1011
|
quoted_columns.each do |name, column|
|
@@ -755,6 +1018,46 @@ module ActiveRecord
|
|
755
1018
|
super
|
756
1019
|
end
|
757
1020
|
|
1021
|
+
def exclusion_constraint_name(table_name, **options)
|
1022
|
+
options.fetch(:name) do
|
1023
|
+
expression = options.fetch(:expression)
|
1024
|
+
identifier = "#{table_name}_#{expression}_excl"
|
1025
|
+
hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
|
1026
|
+
|
1027
|
+
"excl_rails_#{hashed_identifier}"
|
1028
|
+
end
|
1029
|
+
end
|
1030
|
+
|
1031
|
+
def exclusion_constraint_for(table_name, **options)
|
1032
|
+
excl_name = exclusion_constraint_name(table_name, **options)
|
1033
|
+
exclusion_constraints(table_name).detect { |excl| excl.name == excl_name }
|
1034
|
+
end
|
1035
|
+
|
1036
|
+
def exclusion_constraint_for!(table_name, expression: nil, **options)
|
1037
|
+
exclusion_constraint_for(table_name, expression: expression, **options) ||
|
1038
|
+
raise(ArgumentError, "Table '#{table_name}' has no exclusion constraint for #{expression || options}")
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
def unique_constraint_name(table_name, **options)
|
1042
|
+
options.fetch(:name) do
|
1043
|
+
column_or_index = Array(options[:column] || options[:using_index]).map(&:to_s)
|
1044
|
+
identifier = "#{table_name}_#{column_or_index * '_and_'}_unique"
|
1045
|
+
hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
|
1046
|
+
|
1047
|
+
"uniq_rails_#{hashed_identifier}"
|
1048
|
+
end
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
def unique_constraint_for(table_name, **options)
|
1052
|
+
name = unique_constraint_name(table_name, **options) unless options.key?(:column)
|
1053
|
+
unique_constraints(table_name).detect { |unique_constraint| unique_constraint.defined_for?(name: name, **options) }
|
1054
|
+
end
|
1055
|
+
|
1056
|
+
def unique_constraint_for!(table_name, column: nil, **options)
|
1057
|
+
unique_constraint_for(table_name, column: column, **options) ||
|
1058
|
+
raise(ArgumentError, "Table '#{table_name}' has no unique constraint for #{column || options}")
|
1059
|
+
end
|
1060
|
+
|
758
1061
|
def data_source_sql(name = nil, type: nil)
|
759
1062
|
scope = quoted_scope(name, type: type)
|
760
1063
|
scope[:type] ||= "'r','v','m','p','f'" # (r)elation/table, (v)iew, (m)aterialized view, (p)artitioned table, (f)oreign table
|
@@ -788,6 +1091,15 @@ module ActiveRecord
|
|
788
1091
|
name = Utils.extract_schema_qualified_name(string.to_s)
|
789
1092
|
[name.schema, name.identifier]
|
790
1093
|
end
|
1094
|
+
|
1095
|
+
def column_names_from_column_numbers(table_oid, column_numbers)
|
1096
|
+
Hash[query(<<~SQL, "SCHEMA")].values_at(*column_numbers).compact
|
1097
|
+
SELECT a.attnum, a.attname
|
1098
|
+
FROM pg_attribute a
|
1099
|
+
WHERE a.attrelid = #{table_oid}
|
1100
|
+
AND a.attnum IN (#{column_numbers.join(", ")})
|
1101
|
+
SQL
|
1102
|
+
end
|
791
1103
|
end
|
792
1104
|
end
|
793
1105
|
end
|