activerecord 6.1.7 → 7.2.2
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 +616 -1290
- data/MIT-LICENSE +1 -1
- data/README.rdoc +31 -31
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +17 -14
- data/lib/active_record/association_relation.rb +2 -12
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +60 -21
- data/lib/active_record/associations/association_scope.rb +17 -12
- data/lib/active_record/associations/belongs_to_association.rb +37 -11
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -4
- data/lib/active_record/associations/builder/association.rb +11 -5
- data/lib/active_record/associations/builder/belongs_to.rb +41 -14
- data/lib/active_record/associations/builder/collection_association.rb +10 -3
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +4 -4
- data/lib/active_record/associations/builder/singular_association.rb +6 -2
- data/lib/active_record/associations/collection_association.rb +46 -36
- data/lib/active_record/associations/collection_proxy.rb +44 -16
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- 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 +29 -19
- data/lib/active_record/associations/has_many_through_association.rb +19 -8
- 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/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +28 -20
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +212 -53
- data/lib/active_record/associations/preloader/batch.rb +48 -0
- data/lib/active_record/associations/preloader/branch.rb +153 -0
- data/lib/active_record/associations/preloader/through_association.rb +50 -16
- data/lib/active_record/associations/preloader.rb +50 -121
- data/lib/active_record/associations/singular_association.rb +15 -3
- data/lib/active_record/associations/through_association.rb +25 -14
- data/lib/active_record/associations.rb +429 -522
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +1 -5
- data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +73 -22
- data/lib/active_record/attribute_methods/primary_key.rb +47 -27
- data/lib/active_record/attribute_methods/query.rb +31 -19
- data/lib/active_record/attribute_methods/read.rb +14 -11
- data/lib/active_record/attribute_methods/serialization.rb +174 -37
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +15 -9
- data/lib/active_record/attribute_methods/write.rb +12 -15
- data/lib/active_record/attribute_methods.rb +164 -52
- data/lib/active_record/attributes.rb +57 -54
- data/lib/active_record/autosave_association.rb +74 -57
- data/lib/active_record/base.rb +27 -5
- data/lib/active_record/callbacks.rb +19 -35
- 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 +284 -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 +79 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +325 -604
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -60
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +230 -64
- data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -131
- 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 +378 -143
- data/lib/active_record/connection_adapters/abstract/transaction.rb +361 -76
- data/lib/active_record/connection_adapters/abstract_adapter.rb +624 -163
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +348 -165
- 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 +29 -130
- data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -55
- 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 +107 -68
- data/lib/active_record/connection_adapters/pool_config.rb +26 -16
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +114 -54
- 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/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/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 +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/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +137 -104
- 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 +173 -3
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +403 -77
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +520 -253
- data/lib/active_record/connection_adapters/schema_cache.rb +326 -102
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +78 -55
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +68 -54
- 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 +66 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +372 -130
- 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 +130 -6
- data/lib/active_record/connection_handling.rb +132 -146
- data/lib/active_record/core.rb +310 -253
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -4
- data/lib/active_record/database_configurations/database_config.rb +34 -10
- data/lib/active_record/database_configurations/hash_config.rb +107 -31
- data/lib/active_record/database_configurations/url_config.rb +38 -13
- data/lib/active_record/database_configurations.rb +96 -60
- data/lib/active_record/delegated_type.rb +90 -20
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +4 -2
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +3 -3
- 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 +230 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -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 +170 -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 +157 -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_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +96 -0
- data/lib/active_record/encryption/null_encryptor.rb +25 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
- data/lib/active_record/encryption/scheme.rb +100 -0
- data/lib/active_record/encryption.rb +58 -0
- data/lib/active_record/enum.rb +170 -62
- data/lib/active_record/errors.rb +210 -27
- data/lib/active_record/explain.rb +21 -12
- 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 +179 -112
- data/lib/active_record/future_result.rb +178 -0
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +85 -31
- data/lib/active_record/insert_all.rb +148 -32
- data/lib/active_record/integration.rb +14 -10
- data/lib/active_record/internal_metadata.rb +123 -23
- data/lib/active_record/legacy_yaml_adapter.rb +2 -39
- data/lib/active_record/locking/optimistic.rb +43 -27
- data/lib/active_record/locking/pessimistic.rb +15 -6
- data/lib/active_record/log_subscriber.rb +41 -29
- data/lib/active_record/marshalling.rb +59 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +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 +113 -16
- data/lib/active_record/migration/compatibility.rb +235 -46
- 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/join_table.rb +1 -1
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +374 -177
- data/lib/active_record/model_schema.rb +145 -158
- data/lib/active_record/nested_attributes.rb +61 -23
- data/lib/active_record/no_touching.rb +3 -3
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +282 -283
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +18 -25
- data/lib/active_record/query_logs.rb +189 -0
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +44 -9
- data/lib/active_record/railtie.rb +229 -71
- data/lib/active_record/railties/controller_runtime.rb +25 -11
- data/lib/active_record/railties/databases.rake +189 -256
- 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 +332 -103
- data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
- data/lib/active_record/relation/batches.rb +200 -65
- data/lib/active_record/relation/calculations.rb +301 -112
- data/lib/active_record/relation/delegation.rb +33 -22
- data/lib/active_record/relation/finder_methods.rb +123 -52
- data/lib/active_record/relation/merger.rb +26 -19
- 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 +29 -22
- data/lib/active_record/relation/query_attribute.rb +30 -12
- data/lib/active_record/relation/query_methods.rb +870 -163
- data/lib/active_record/relation/record_fetch_warning.rb +10 -9
- data/lib/active_record/relation/spawn_methods.rb +7 -6
- data/lib/active_record/relation/where_clause.rb +15 -36
- data/lib/active_record/relation.rb +736 -145
- data/lib/active_record/result.rb +67 -54
- data/lib/active_record/runtime_registry.rb +71 -13
- data/lib/active_record/sanitization.rb +84 -34
- data/lib/active_record/schema.rb +39 -23
- data/lib/active_record/schema_dumper.rb +90 -31
- data/lib/active_record/schema_migration.rb +74 -23
- data/lib/active_record/scoping/default.rb +72 -15
- data/lib/active_record/scoping/named.rb +6 -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 +30 -9
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +10 -10
- data/lib/active_record/suppressor.rb +13 -15
- data/lib/active_record/table_metadata.rb +7 -3
- data/lib/active_record/tasks/database_tasks.rb +288 -149
- 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 +16 -7
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +173 -155
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +32 -19
- 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 +118 -41
- data/lib/active_record/translation.rb +3 -5
- 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 -7
- 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/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +13 -7
- 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 +65 -15
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +444 -32
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/attributes/attribute.rb +0 -8
- 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/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/binary.rb +6 -7
- data/lib/arel/nodes/bound_sql_literal.rb +65 -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 +1 -9
- data/lib/arel/nodes/insert_statement.rb +2 -2
- 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/select_core.rb +2 -2
- data/lib/arel/nodes/select_statement.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +13 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes/update_statement.rb +8 -3
- data/lib/arel/nodes.rb +7 -2
- data/lib/arel/predications.rb +14 -4
- data/lib/arel/select_manager.rb +11 -5
- data/lib/arel/table.rb +9 -6
- data/lib/arel/tree_manager.rb +8 -15
- data/lib/arel/update_manager.rb +20 -5
- data/lib/arel/visitors/dot.rb +81 -90
- data/lib/arel/visitors/mysql.rb +23 -5
- data/lib/arel/visitors/postgresql.rb +1 -22
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +170 -36
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +23 -4
- 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/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
- 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 +103 -17
- 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 { |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,11 +250,13 @@ 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.
|
247
|
-
def default_sequence_name(table_name, pk = "id")
|
257
|
+
def default_sequence_name(table_name, pk = "id") # :nodoc:
|
258
|
+
return nil if pk.is_a?(Array)
|
259
|
+
|
248
260
|
result = serial_sequence(table_name, pk)
|
249
261
|
return nil unless result
|
250
262
|
Utils.extract_schema_qualified_name(result).to_s
|
@@ -257,7 +269,7 @@ module ActiveRecord
|
|
257
269
|
end
|
258
270
|
|
259
271
|
# Sets the sequence of a table's primary key to the specified value.
|
260
|
-
def set_pk_sequence!(table, value)
|
272
|
+
def set_pk_sequence!(table, value) # :nodoc:
|
261
273
|
pk, sequence = pk_and_sequence_for(table)
|
262
274
|
|
263
275
|
if pk
|
@@ -272,7 +284,7 @@ module ActiveRecord
|
|
272
284
|
end
|
273
285
|
|
274
286
|
# Resets the sequence of a table's primary key to the maximum value.
|
275
|
-
def reset_pk_sequence!(table, pk = nil, sequence = nil)
|
287
|
+
def reset_pk_sequence!(table, pk = nil, sequence = nil) # :nodoc:
|
276
288
|
unless pk && sequence
|
277
289
|
default_pk, default_sequence = pk_and_sequence_for(table)
|
278
290
|
|
@@ -288,19 +300,19 @@ module ActiveRecord
|
|
288
300
|
quoted_sequence = quote_table_name(sequence)
|
289
301
|
max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
|
290
302
|
if max_pk.nil?
|
291
|
-
if database_version >=
|
303
|
+
if database_version >= 10_00_00
|
292
304
|
minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
|
293
305
|
else
|
294
306
|
minvalue = query_value("SELECT min_value FROM #{quoted_sequence}", "SCHEMA")
|
295
307
|
end
|
296
308
|
end
|
297
309
|
|
298
|
-
query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk
|
310
|
+
query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk || minvalue}, #{max_pk ? true : false})", "SCHEMA")
|
299
311
|
end
|
300
312
|
end
|
301
313
|
|
302
314
|
# Returns a table's primary key and belonging sequence.
|
303
|
-
def pk_and_sequence_for(table)
|
315
|
+
def pk_and_sequence_for(table) # :nodoc:
|
304
316
|
# First try looking for a sequence with a dependency on the
|
305
317
|
# given table's primary key.
|
306
318
|
result = query(<<~SQL, "SCHEMA")[0]
|
@@ -339,7 +351,7 @@ module ActiveRecord
|
|
339
351
|
JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
|
340
352
|
WHERE t.oid = #{quote(quote_table_name(table))}::regclass
|
341
353
|
AND cons.contype = 'p'
|
342
|
-
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
|
354
|
+
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate|gen_random_uuid'
|
343
355
|
SQL
|
344
356
|
end
|
345
357
|
|
@@ -375,49 +387,78 @@ module ActiveRecord
|
|
375
387
|
#
|
376
388
|
# Example:
|
377
389
|
# rename_table('octopuses', 'octopi')
|
378
|
-
def rename_table(table_name, new_name)
|
390
|
+
def rename_table(table_name, new_name, **options)
|
391
|
+
validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
|
379
392
|
clear_cache!
|
380
393
|
schema_cache.clear_data_source_cache!(table_name.to_s)
|
381
394
|
schema_cache.clear_data_source_cache!(new_name.to_s)
|
382
395
|
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
383
396
|
pk, seq = pk_and_sequence_for(new_name)
|
384
397
|
if pk
|
385
|
-
|
386
|
-
|
398
|
+
# PostgreSQL automatically creates an index for PRIMARY KEY with name consisting of
|
399
|
+
# truncated table name and "_pkey" suffix fitting into max_identifier_length number of characters.
|
400
|
+
max_pkey_prefix = max_identifier_length - "_pkey".size
|
401
|
+
idx = "#{table_name[0, max_pkey_prefix]}_pkey"
|
402
|
+
new_idx = "#{new_name[0, max_pkey_prefix]}_pkey"
|
387
403
|
execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
|
388
|
-
|
389
|
-
|
404
|
+
|
405
|
+
# PostgreSQL automatically creates a sequence for PRIMARY KEY with name consisting of
|
406
|
+
# truncated table name and "#{primary_key}_seq" suffix fitting into max_identifier_length number of characters.
|
407
|
+
max_seq_prefix = max_identifier_length - "_#{pk}_seq".size
|
408
|
+
if seq && seq.identifier == "#{table_name[0, max_seq_prefix]}_#{pk}_seq"
|
409
|
+
new_seq = "#{new_name[0, max_seq_prefix]}_#{pk}_seq"
|
390
410
|
execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
|
391
411
|
end
|
392
412
|
end
|
393
|
-
rename_table_indexes(table_name, new_name)
|
413
|
+
rename_table_indexes(table_name, new_name, **options)
|
394
414
|
end
|
395
415
|
|
396
|
-
def add_column(table_name, column_name, type, **options)
|
416
|
+
def add_column(table_name, column_name, type, **options) # :nodoc:
|
397
417
|
clear_cache!
|
398
418
|
super
|
399
419
|
change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
|
400
420
|
end
|
401
421
|
|
402
|
-
def change_column(table_name, column_name, type, **options)
|
422
|
+
def change_column(table_name, column_name, type, **options) # :nodoc:
|
403
423
|
clear_cache!
|
404
424
|
sqls, procs = Array(change_column_for_alter(table_name, column_name, type, **options)).partition { |v| v.is_a?(String) }
|
405
425
|
execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}"
|
406
426
|
procs.each(&:call)
|
407
427
|
end
|
408
428
|
|
429
|
+
# Builds a ChangeColumnDefinition object.
|
430
|
+
#
|
431
|
+
# This definition object contains information about the column change that would occur
|
432
|
+
# if the same arguments were passed to #change_column. See #change_column for information about
|
433
|
+
# passing a +table_name+, +column_name+, +type+ and other options that can be passed.
|
434
|
+
def build_change_column_definition(table_name, column_name, type, **options) # :nodoc:
|
435
|
+
td = create_table_definition(table_name)
|
436
|
+
cd = td.new_column_definition(column_name, type, **options)
|
437
|
+
ChangeColumnDefinition.new(cd, column_name)
|
438
|
+
end
|
439
|
+
|
409
440
|
# Changes the default value of a table column.
|
410
441
|
def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
|
411
442
|
execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
|
412
443
|
end
|
413
444
|
|
414
|
-
def
|
445
|
+
def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc:
|
446
|
+
column = column_for(table_name, column_name)
|
447
|
+
return unless column
|
448
|
+
|
449
|
+
default = extract_new_default_value(default_or_changes)
|
450
|
+
ChangeColumnDefaultDefinition.new(column, default)
|
451
|
+
end
|
452
|
+
|
453
|
+
def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
|
454
|
+
validate_change_column_null_argument!(null)
|
455
|
+
|
415
456
|
clear_cache!
|
416
457
|
unless null || default.nil?
|
417
458
|
column = column_for(table_name, column_name)
|
418
459
|
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
460
|
end
|
420
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} #{
|
461
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
421
462
|
end
|
422
463
|
|
423
464
|
# Adds comment for given table column or drops it if +comment+ is a +nil+
|
@@ -435,22 +476,26 @@ module ActiveRecord
|
|
435
476
|
end
|
436
477
|
|
437
478
|
# Renames a column in a table.
|
438
|
-
def rename_column(table_name, column_name, new_column_name)
|
479
|
+
def rename_column(table_name, column_name, new_column_name) # :nodoc:
|
439
480
|
clear_cache!
|
440
481
|
execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
|
441
482
|
rename_column_indexes(table_name, column_name, new_column_name)
|
442
483
|
end
|
443
484
|
|
444
|
-
def add_index(table_name, column_name, **options)
|
445
|
-
|
446
|
-
|
447
|
-
create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists)
|
485
|
+
def add_index(table_name, column_name, **options) # :nodoc:
|
486
|
+
create_index = build_create_index_definition(table_name, column_name, **options)
|
448
487
|
result = execute schema_creation.accept(create_index)
|
449
488
|
|
489
|
+
index = create_index.index
|
450
490
|
execute "COMMENT ON INDEX #{quote_column_name(index.name)} IS #{quote(index.comment)}" if index.comment
|
451
491
|
result
|
452
492
|
end
|
453
493
|
|
494
|
+
def build_create_index_definition(table_name, column_name, **options) # :nodoc:
|
495
|
+
index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
|
496
|
+
CreateIndexDefinition.new(index, algorithm, if_not_exists)
|
497
|
+
end
|
498
|
+
|
454
499
|
def remove_index(table_name, column_name = nil, **options) # :nodoc:
|
455
500
|
table = Utils.extract_schema_qualified_name(table_name.to_s)
|
456
501
|
|
@@ -477,13 +522,25 @@ module ActiveRecord
|
|
477
522
|
def rename_index(table_name, old_name, new_name)
|
478
523
|
validate_index_length!(table_name, new_name)
|
479
524
|
|
480
|
-
|
525
|
+
schema, = extract_schema_qualified_name(table_name)
|
526
|
+
execute "ALTER INDEX #{quote_table_name(schema) + '.' if schema}#{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
|
527
|
+
end
|
528
|
+
|
529
|
+
def index_name(table_name, options) # :nodoc:
|
530
|
+
_schema, table_name = extract_schema_qualified_name(table_name.to_s)
|
531
|
+
super
|
532
|
+
end
|
533
|
+
|
534
|
+
def add_foreign_key(from_table, to_table, **options)
|
535
|
+
assert_valid_deferrable(options[:deferrable])
|
536
|
+
|
537
|
+
super
|
481
538
|
end
|
482
539
|
|
483
540
|
def foreign_keys(table_name)
|
484
541
|
scope = quoted_scope(table_name)
|
485
|
-
fk_info =
|
486
|
-
SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid
|
542
|
+
fk_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
543
|
+
SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid, c.condeferrable AS deferrable, c.condeferred AS deferred, c.conkey, c.confkey, c.conrelid, c.confrelid
|
487
544
|
FROM pg_constraint c
|
488
545
|
JOIN pg_class t1 ON c.conrelid = t1.oid
|
489
546
|
JOIN pg_class t2 ON c.confrelid = t2.oid
|
@@ -497,17 +554,31 @@ module ActiveRecord
|
|
497
554
|
SQL
|
498
555
|
|
499
556
|
fk_info.map do |row|
|
557
|
+
to_table = Utils.unquote_identifier(row["to_table"])
|
558
|
+
conkey = row["conkey"].scan(/\d+/).map(&:to_i)
|
559
|
+
confkey = row["confkey"].scan(/\d+/).map(&:to_i)
|
560
|
+
|
561
|
+
if conkey.size > 1
|
562
|
+
column = column_names_from_column_numbers(row["conrelid"], conkey)
|
563
|
+
primary_key = column_names_from_column_numbers(row["confrelid"], confkey)
|
564
|
+
else
|
565
|
+
column = Utils.unquote_identifier(row["column"])
|
566
|
+
primary_key = row["primary_key"]
|
567
|
+
end
|
568
|
+
|
500
569
|
options = {
|
501
|
-
column:
|
570
|
+
column: column,
|
502
571
|
name: row["name"],
|
503
|
-
primary_key:
|
572
|
+
primary_key: primary_key
|
504
573
|
}
|
505
574
|
|
506
575
|
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
|
507
576
|
options[:on_update] = extract_foreign_key_action(row["on_update"])
|
577
|
+
options[:deferrable] = extract_constraint_deferrable(row["deferrable"], row["deferred"])
|
578
|
+
|
508
579
|
options[:validate] = row["valid"]
|
509
580
|
|
510
|
-
ForeignKeyDefinition.new(table_name,
|
581
|
+
ForeignKeyDefinition.new(table_name, to_table, options)
|
511
582
|
end
|
512
583
|
end
|
513
584
|
|
@@ -522,12 +593,14 @@ module ActiveRecord
|
|
522
593
|
def check_constraints(table_name) # :nodoc:
|
523
594
|
scope = quoted_scope(table_name)
|
524
595
|
|
525
|
-
check_info =
|
526
|
-
SELECT conname, pg_get_constraintdef(c.oid) AS constraintdef, c.convalidated AS valid
|
596
|
+
check_info = internal_exec_query(<<-SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
597
|
+
SELECT conname, pg_get_constraintdef(c.oid, true) AS constraintdef, c.convalidated AS valid
|
527
598
|
FROM pg_constraint c
|
528
599
|
JOIN pg_class t ON c.conrelid = t.oid
|
600
|
+
JOIN pg_namespace n ON n.oid = c.connamespace
|
529
601
|
WHERE c.contype = 'c'
|
530
602
|
AND t.relname = #{scope[:name]}
|
603
|
+
AND n.nspname = #{scope[:schema]}
|
531
604
|
SQL
|
532
605
|
|
533
606
|
check_info.map do |row|
|
@@ -535,14 +608,183 @@ module ActiveRecord
|
|
535
608
|
name: row["conname"],
|
536
609
|
validate: row["valid"]
|
537
610
|
}
|
538
|
-
expression = row["constraintdef"][/CHECK \(
|
611
|
+
expression = row["constraintdef"][/CHECK \((.+)\)/m, 1]
|
539
612
|
|
540
613
|
CheckConstraintDefinition.new(table_name, expression, options)
|
541
614
|
end
|
542
615
|
end
|
543
616
|
|
617
|
+
# Returns an array of exclusion constraints for the given table.
|
618
|
+
# The exclusion constraints are represented as ExclusionConstraintDefinition objects.
|
619
|
+
def exclusion_constraints(table_name)
|
620
|
+
scope = quoted_scope(table_name)
|
621
|
+
|
622
|
+
exclusion_info = internal_exec_query(<<-SQL, "SCHEMA")
|
623
|
+
SELECT conname, pg_get_constraintdef(c.oid) AS constraintdef, c.condeferrable, c.condeferred
|
624
|
+
FROM pg_constraint c
|
625
|
+
JOIN pg_class t ON c.conrelid = t.oid
|
626
|
+
JOIN pg_namespace n ON n.oid = c.connamespace
|
627
|
+
WHERE c.contype = 'x'
|
628
|
+
AND t.relname = #{scope[:name]}
|
629
|
+
AND n.nspname = #{scope[:schema]}
|
630
|
+
SQL
|
631
|
+
|
632
|
+
exclusion_info.map do |row|
|
633
|
+
method_and_elements, predicate = row["constraintdef"].split(" WHERE ")
|
634
|
+
method_and_elements_parts = method_and_elements.match(/EXCLUDE(?: USING (?<using>\S+))? \((?<expression>.+)\)/)
|
635
|
+
predicate.remove!(/ DEFERRABLE(?: INITIALLY (?:IMMEDIATE|DEFERRED))?/) if predicate
|
636
|
+
predicate = predicate.from(2).to(-3) if predicate # strip 2 opening and closing parentheses
|
637
|
+
|
638
|
+
deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
|
639
|
+
|
640
|
+
options = {
|
641
|
+
name: row["conname"],
|
642
|
+
using: method_and_elements_parts["using"].to_sym,
|
643
|
+
where: predicate,
|
644
|
+
deferrable: deferrable
|
645
|
+
}
|
646
|
+
|
647
|
+
ExclusionConstraintDefinition.new(table_name, method_and_elements_parts["expression"], options)
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
# Returns an array of unique constraints for the given table.
|
652
|
+
# The unique constraints are represented as UniqueConstraintDefinition objects.
|
653
|
+
def unique_constraints(table_name)
|
654
|
+
scope = quoted_scope(table_name)
|
655
|
+
|
656
|
+
unique_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
657
|
+
SELECT c.conname, c.conrelid, c.conkey, c.condeferrable, c.condeferred
|
658
|
+
FROM pg_constraint c
|
659
|
+
JOIN pg_class t ON c.conrelid = t.oid
|
660
|
+
JOIN pg_namespace n ON n.oid = c.connamespace
|
661
|
+
WHERE c.contype = 'u'
|
662
|
+
AND t.relname = #{scope[:name]}
|
663
|
+
AND n.nspname = #{scope[:schema]}
|
664
|
+
SQL
|
665
|
+
|
666
|
+
unique_info.map do |row|
|
667
|
+
conkey = row["conkey"].delete("{}").split(",").map(&:to_i)
|
668
|
+
columns = column_names_from_column_numbers(row["conrelid"], conkey)
|
669
|
+
|
670
|
+
deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
|
671
|
+
|
672
|
+
options = {
|
673
|
+
name: row["conname"],
|
674
|
+
deferrable: deferrable
|
675
|
+
}
|
676
|
+
|
677
|
+
UniqueConstraintDefinition.new(table_name, columns, options)
|
678
|
+
end
|
679
|
+
end
|
680
|
+
|
681
|
+
# Adds a new exclusion constraint to the table. +expression+ is a String
|
682
|
+
# representation of a list of exclusion elements and operators.
|
683
|
+
#
|
684
|
+
# add_exclusion_constraint :products, "price WITH =, availability_range WITH &&", using: :gist, name: "price_check"
|
685
|
+
#
|
686
|
+
# generates:
|
687
|
+
#
|
688
|
+
# ALTER TABLE "products" ADD CONSTRAINT price_check EXCLUDE USING gist (price WITH =, availability_range WITH &&)
|
689
|
+
#
|
690
|
+
# The +options+ hash can include the following keys:
|
691
|
+
# [<tt>:name</tt>]
|
692
|
+
# The constraint name. Defaults to <tt>excl_rails_<identifier></tt>.
|
693
|
+
# [<tt>:deferrable</tt>]
|
694
|
+
# Specify whether or not the exclusion constraint should be deferrable. Valid values are +false+ or +:immediate+ or +:deferred+ to specify the default behavior. Defaults to +false+.
|
695
|
+
# [<tt>:using</tt>]
|
696
|
+
# Specify which index method to use when creating this exclusion constraint (e.g. +:btree+, +:gist+ etc).
|
697
|
+
# [<tt>:where</tt>]
|
698
|
+
# Specify an exclusion constraint on a subset of the table (internally PostgreSQL creates a partial index for this).
|
699
|
+
def add_exclusion_constraint(table_name, expression, **options)
|
700
|
+
options = exclusion_constraint_options(table_name, expression, options)
|
701
|
+
at = create_alter_table(table_name)
|
702
|
+
at.add_exclusion_constraint(expression, options)
|
703
|
+
|
704
|
+
execute schema_creation.accept(at)
|
705
|
+
end
|
706
|
+
|
707
|
+
def exclusion_constraint_options(table_name, expression, options) # :nodoc:
|
708
|
+
assert_valid_deferrable(options[:deferrable])
|
709
|
+
|
710
|
+
options = options.dup
|
711
|
+
options[:name] ||= exclusion_constraint_name(table_name, expression: expression, **options)
|
712
|
+
options
|
713
|
+
end
|
714
|
+
|
715
|
+
# Removes the given exclusion constraint from the table.
|
716
|
+
#
|
717
|
+
# remove_exclusion_constraint :products, name: "price_check"
|
718
|
+
#
|
719
|
+
# The +expression+ parameter will be ignored if present. It can be helpful
|
720
|
+
# to provide this in a migration's +change+ method so it can be reverted.
|
721
|
+
# In that case, +expression+ will be used by #add_exclusion_constraint.
|
722
|
+
def remove_exclusion_constraint(table_name, expression = nil, **options)
|
723
|
+
excl_name_to_delete = exclusion_constraint_for!(table_name, expression: expression, **options).name
|
724
|
+
|
725
|
+
at = create_alter_table(table_name)
|
726
|
+
at.drop_exclusion_constraint(excl_name_to_delete)
|
727
|
+
|
728
|
+
execute schema_creation.accept(at)
|
729
|
+
end
|
730
|
+
|
731
|
+
# Adds a new unique constraint to the table.
|
732
|
+
#
|
733
|
+
# add_unique_constraint :sections, [:position], deferrable: :deferred, name: "unique_position"
|
734
|
+
#
|
735
|
+
# generates:
|
736
|
+
#
|
737
|
+
# ALTER TABLE "sections" ADD CONSTRAINT unique_position UNIQUE (position) DEFERRABLE INITIALLY DEFERRED
|
738
|
+
#
|
739
|
+
# If you want to change an existing unique index to deferrable, you can use :using_index to create deferrable unique constraints.
|
740
|
+
#
|
741
|
+
# add_unique_constraint :sections, deferrable: :deferred, name: "unique_position", using_index: "index_sections_on_position"
|
742
|
+
#
|
743
|
+
# The +options+ hash can include the following keys:
|
744
|
+
# [<tt>:name</tt>]
|
745
|
+
# The constraint name. Defaults to <tt>uniq_rails_<identifier></tt>.
|
746
|
+
# [<tt>:deferrable</tt>]
|
747
|
+
# Specify whether or not the unique constraint should be deferrable. Valid values are +false+ or +:immediate+ or +:deferred+ to specify the default behavior. Defaults to +false+.
|
748
|
+
# [<tt>:using_index</tt>]
|
749
|
+
# To specify an existing unique index name. Defaults to +nil+.
|
750
|
+
def add_unique_constraint(table_name, column_name = nil, **options)
|
751
|
+
options = unique_constraint_options(table_name, column_name, options)
|
752
|
+
at = create_alter_table(table_name)
|
753
|
+
at.add_unique_constraint(column_name, options)
|
754
|
+
|
755
|
+
execute schema_creation.accept(at)
|
756
|
+
end
|
757
|
+
|
758
|
+
def unique_constraint_options(table_name, column_name, options) # :nodoc:
|
759
|
+
assert_valid_deferrable(options[:deferrable])
|
760
|
+
|
761
|
+
if column_name && options[:using_index]
|
762
|
+
raise ArgumentError, "Cannot specify both column_name and :using_index options."
|
763
|
+
end
|
764
|
+
|
765
|
+
options = options.dup
|
766
|
+
options[:name] ||= unique_constraint_name(table_name, column: column_name, **options)
|
767
|
+
options
|
768
|
+
end
|
769
|
+
|
770
|
+
# Removes the given unique constraint from the table.
|
771
|
+
#
|
772
|
+
# remove_unique_constraint :sections, name: "unique_position"
|
773
|
+
#
|
774
|
+
# The +column_name+ parameter will be ignored if present. It can be helpful
|
775
|
+
# to provide this in a migration's +change+ method so it can be reverted.
|
776
|
+
# In that case, +column_name+ will be used by #add_unique_constraint.
|
777
|
+
def remove_unique_constraint(table_name, column_name = nil, **options)
|
778
|
+
unique_name_to_delete = unique_constraint_for!(table_name, column: column_name, **options).name
|
779
|
+
|
780
|
+
at = create_alter_table(table_name)
|
781
|
+
at.drop_unique_constraint(unique_name_to_delete)
|
782
|
+
|
783
|
+
execute schema_creation.accept(at)
|
784
|
+
end
|
785
|
+
|
544
786
|
# Maps logical Rails types to PostgreSQL-specific data types.
|
545
|
-
def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
|
787
|
+
def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, enum_type: nil, **) # :nodoc:
|
546
788
|
sql = \
|
547
789
|
case type.to_s
|
548
790
|
when "binary"
|
@@ -566,6 +808,10 @@ module ActiveRecord
|
|
566
808
|
when 5..8; "bigint"
|
567
809
|
else raise ArgumentError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead."
|
568
810
|
end
|
811
|
+
when "enum"
|
812
|
+
raise ArgumentError, "enum_type is required for enums" if enum_type.nil?
|
813
|
+
|
814
|
+
enum_type
|
569
815
|
else
|
570
816
|
super
|
571
817
|
end
|
@@ -576,7 +822,7 @@ module ActiveRecord
|
|
576
822
|
|
577
823
|
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
|
578
824
|
# requires that the ORDER BY include the distinct column.
|
579
|
-
def columns_for_distinct(columns, orders)
|
825
|
+
def columns_for_distinct(columns, orders) # :nodoc:
|
580
826
|
order_columns = orders.compact_blank.map { |s|
|
581
827
|
# Convert Arel node to string
|
582
828
|
s = visitor.compile(s) unless s.is_a?(String)
|
@@ -640,11 +886,32 @@ module ActiveRecord
|
|
640
886
|
validate_constraint table_name, chk_name_to_validate
|
641
887
|
end
|
642
888
|
|
643
|
-
|
644
|
-
|
645
|
-
|
889
|
+
def foreign_key_column_for(table_name, column_name) # :nodoc:
|
890
|
+
_schema, table_name = extract_schema_qualified_name(table_name)
|
891
|
+
super
|
892
|
+
end
|
893
|
+
|
894
|
+
def add_index_options(table_name, column_name, **options) # :nodoc:
|
895
|
+
if (where = options[:where]) && table_exists?(table_name) && column_exists?(table_name, where)
|
896
|
+
options[:where] = quote_column_name(where)
|
897
|
+
end
|
898
|
+
super
|
899
|
+
end
|
900
|
+
|
901
|
+
def quoted_include_columns_for_index(column_names) # :nodoc:
|
902
|
+
return quote_column_name(column_names) if column_names.is_a?(Symbol)
|
903
|
+
|
904
|
+
quoted_columns = column_names.each_with_object({}) do |name, result|
|
905
|
+
result[name.to_sym] = quote_column_name(name).dup
|
646
906
|
end
|
907
|
+
add_options_for_index_columns(quoted_columns).values.join(", ")
|
908
|
+
end
|
909
|
+
|
910
|
+
def schema_creation # :nodoc:
|
911
|
+
PostgreSQL::SchemaCreation.new(self)
|
912
|
+
end
|
647
913
|
|
914
|
+
private
|
648
915
|
def create_table_definition(name, **options)
|
649
916
|
PostgreSQL::TableDefinition.new(self, name, **options)
|
650
917
|
end
|
@@ -653,11 +920,16 @@ module ActiveRecord
|
|
653
920
|
PostgreSQL::AlterTable.new create_table_definition(name)
|
654
921
|
end
|
655
922
|
|
656
|
-
def new_column_from_field(table_name, field)
|
657
|
-
column_name, type, default, notnull, oid, fmod, collation, comment = field
|
923
|
+
def new_column_from_field(table_name, field, _definitions)
|
924
|
+
column_name, type, default, notnull, oid, fmod, collation, comment, identity, attgenerated = field
|
658
925
|
type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
|
659
926
|
default_value = extract_value_from_default(default)
|
660
|
-
|
927
|
+
|
928
|
+
if attgenerated.present?
|
929
|
+
default_function = default
|
930
|
+
else
|
931
|
+
default_function = extract_default_function(default_value, default)
|
932
|
+
end
|
661
933
|
|
662
934
|
if match = default_function&.match(/\Anextval\('"?(?<sequence_name>.+_(?<suffix>seq\d*))"?'::regclass\)\z/)
|
663
935
|
serial = sequence_name_from_parts(table_name, column_name, match[:suffix]) == match[:sequence_name]
|
@@ -671,7 +943,9 @@ module ActiveRecord
|
|
671
943
|
default_function,
|
672
944
|
collation: collation,
|
673
945
|
comment: comment.presence,
|
674
|
-
serial: serial
|
946
|
+
serial: serial,
|
947
|
+
identity: identity.presence,
|
948
|
+
generated: attgenerated
|
675
949
|
)
|
676
950
|
end
|
677
951
|
|
@@ -711,38 +985,41 @@ module ActiveRecord
|
|
711
985
|
end
|
712
986
|
end
|
713
987
|
|
988
|
+
def assert_valid_deferrable(deferrable)
|
989
|
+
return if !deferrable || %i(immediate deferred).include?(deferrable)
|
990
|
+
|
991
|
+
raise ArgumentError, "deferrable must be `:immediate` or `:deferred`, got: `#{deferrable.inspect}`"
|
992
|
+
end
|
993
|
+
|
994
|
+
def extract_constraint_deferrable(deferrable, deferred)
|
995
|
+
deferrable && (deferred ? :deferred : :immediate)
|
996
|
+
end
|
997
|
+
|
998
|
+
def reference_name_for_table(table_name)
|
999
|
+
_schema, table_name = extract_schema_qualified_name(table_name.to_s)
|
1000
|
+
table_name.singularize
|
1001
|
+
end
|
1002
|
+
|
714
1003
|
def add_column_for_alter(table_name, column_name, type, **options)
|
715
1004
|
return super unless options.key?(:comment)
|
716
1005
|
[super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
|
717
1006
|
end
|
718
1007
|
|
719
1008
|
def change_column_for_alter(table_name, column_name, type, **options)
|
720
|
-
|
721
|
-
|
722
|
-
sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))]
|
1009
|
+
change_col_def = build_change_column_definition(table_name, column_name, type, **options)
|
1010
|
+
sqls = [schema_creation.accept(change_col_def)]
|
723
1011
|
sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
|
724
1012
|
sqls
|
725
1013
|
end
|
726
1014
|
|
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"
|
1015
|
+
def change_column_null_for_alter(table_name, column_name, null, default = nil)
|
733
1016
|
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"
|
1017
|
+
"ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
737
1018
|
else
|
738
|
-
|
1019
|
+
Proc.new { change_column_null(table_name, column_name, null, default) }
|
739
1020
|
end
|
740
1021
|
end
|
741
1022
|
|
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
1023
|
def add_index_opclass(quoted_columns, **options)
|
747
1024
|
opclasses = options_for_index_columns(options[:opclass])
|
748
1025
|
quoted_columns.each do |name, column|
|
@@ -755,6 +1032,46 @@ module ActiveRecord
|
|
755
1032
|
super
|
756
1033
|
end
|
757
1034
|
|
1035
|
+
def exclusion_constraint_name(table_name, **options)
|
1036
|
+
options.fetch(:name) do
|
1037
|
+
expression = options.fetch(:expression)
|
1038
|
+
identifier = "#{table_name}_#{expression}_excl"
|
1039
|
+
hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
|
1040
|
+
|
1041
|
+
"excl_rails_#{hashed_identifier}"
|
1042
|
+
end
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
def exclusion_constraint_for(table_name, **options)
|
1046
|
+
excl_name = exclusion_constraint_name(table_name, **options)
|
1047
|
+
exclusion_constraints(table_name).detect { |excl| excl.name == excl_name }
|
1048
|
+
end
|
1049
|
+
|
1050
|
+
def exclusion_constraint_for!(table_name, expression: nil, **options)
|
1051
|
+
exclusion_constraint_for(table_name, expression: expression, **options) ||
|
1052
|
+
raise(ArgumentError, "Table '#{table_name}' has no exclusion constraint for #{expression || options}")
|
1053
|
+
end
|
1054
|
+
|
1055
|
+
def unique_constraint_name(table_name, **options)
|
1056
|
+
options.fetch(:name) do
|
1057
|
+
column_or_index = Array(options[:column] || options[:using_index]).map(&:to_s)
|
1058
|
+
identifier = "#{table_name}_#{column_or_index * '_and_'}_unique"
|
1059
|
+
hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
|
1060
|
+
|
1061
|
+
"uniq_rails_#{hashed_identifier}"
|
1062
|
+
end
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
def unique_constraint_for(table_name, **options)
|
1066
|
+
name = unique_constraint_name(table_name, **options) unless options.key?(:column)
|
1067
|
+
unique_constraints(table_name).detect { |unique_constraint| unique_constraint.defined_for?(name: name, **options) }
|
1068
|
+
end
|
1069
|
+
|
1070
|
+
def unique_constraint_for!(table_name, column: nil, **options)
|
1071
|
+
unique_constraint_for(table_name, column: column, **options) ||
|
1072
|
+
raise(ArgumentError, "Table '#{table_name}' has no unique constraint for #{column || options}")
|
1073
|
+
end
|
1074
|
+
|
758
1075
|
def data_source_sql(name = nil, type: nil)
|
759
1076
|
scope = quoted_scope(name, type: type)
|
760
1077
|
scope[:type] ||= "'r','v','m','p','f'" # (r)elation/table, (v)iew, (m)aterialized view, (p)artitioned table, (f)oreign table
|
@@ -788,6 +1105,15 @@ module ActiveRecord
|
|
788
1105
|
name = Utils.extract_schema_qualified_name(string.to_s)
|
789
1106
|
[name.schema, name.identifier]
|
790
1107
|
end
|
1108
|
+
|
1109
|
+
def column_names_from_column_numbers(table_oid, column_numbers)
|
1110
|
+
Hash[query(<<~SQL, "SCHEMA")].values_at(*column_numbers).compact
|
1111
|
+
SELECT a.attnum, a.attname
|
1112
|
+
FROM pg_attribute a
|
1113
|
+
WHERE a.attrelid = #{table_oid}
|
1114
|
+
AND a.attnum IN (#{column_numbers.join(", ")})
|
1115
|
+
SQL
|
1116
|
+
end
|
791
1117
|
end
|
792
1118
|
end
|
793
1119
|
end
|