commit d0f11aa4f8efe8a18ed3ffa4368c7245cc3c8deb Author: David G. Johnston Date: Wed Jun 17 22:24:08 2020 +0000 Document DROP relation and DROP type IF EXISTS namespace behavior Bug# 16492 takes offense to the fact that DROP VIEW IF EXISTS name errors if a table with the same name exists but a view with that name does not. While I believe the bug has merit there is no consensus to actually fix the behavior to work as it is documented. Therefore, fix the documentation to match the behavior and encode that behavior in the regression tests. Due to the behavior of CREATE TABLE producing a composite type the behaviors for DROP TYPE and DROP DOMAIN are relevant here so add tests and documentation for those as well. In particular document that DROP TYPE drops domains. One shortcoming tested here is that DROP IF EXISTS doesn't only look at the first search_path entry while CREATE IF NOT EXISTS will only consider the first search_path entry. As this patch addresses undocumented fundamental behavior for these commands backpatch to all supported versions. I (DGJ) have not tried to address the IMO confusing statement that "composite types are relations". That material is better done against head only anyway as it should incorporate the newly created glossary. diff --git a/doc/src/sgml/ref/drop_domain.sgml b/doc/src/sgml/ref/drop_domain.sgml index b18faf3917..cba665fcc7 100644 --- a/doc/src/sgml/ref/drop_domain.sgml +++ b/doc/src/sgml/ref/drop_domain.sgml @@ -30,7 +30,9 @@ DROP DOMAIN [ IF EXISTS ] name [, . DROP DOMAIN removes a domain. Only the owner of - a domain can remove it. + a domain can remove it. This duplicates the functionality provided by + but restricts the object type to domain. + @@ -42,8 +44,11 @@ DROP DOMAIN [ IF EXISTS ] name [, . IF EXISTS - Do not throw an error if the domain does not exist. A notice is issued - in this case. + This parameter instructs PostgreSQL to search + for the first instance of any type definition with the provided name. + If no type definition is found a notice is issued and the command ends. + If a type definition is found then one of two things will happen: + if the type definition is a domain it is dropped, otherwise the command fails. @@ -108,6 +113,7 @@ DROP DOMAIN box; + diff --git a/doc/src/sgml/ref/drop_foreign_table.sgml b/doc/src/sgml/ref/drop_foreign_table.sgml index 07b3fd4251..0288fb2062 100644 --- a/doc/src/sgml/ref/drop_foreign_table.sgml +++ b/doc/src/sgml/ref/drop_foreign_table.sgml @@ -42,8 +42,11 @@ DROP FOREIGN TABLE [ IF EXISTS ] nameIF EXISTS - Do not throw an error if the foreign table does not exist. - A notice is issued in this case. + This parameter instructs PostgreSQL to search + for the first instance of any relation with the provided name. + If no relations are found a notice is issued and the command ends. + If a relation is found then one of two things will happen: + if the relation is a foreign table it is dropped, otherwise the command fails. diff --git a/doc/src/sgml/ref/drop_index.sgml b/doc/src/sgml/ref/drop_index.sgml index 0aedd71bd6..dff437cf9b 100644 --- a/doc/src/sgml/ref/drop_index.sgml +++ b/doc/src/sgml/ref/drop_index.sgml @@ -70,8 +70,11 @@ DROP INDEX [ CONCURRENTLY ] [ IF EXISTS ] nameIF EXISTS - Do not throw an error if the index does not exist. A notice is issued - in this case. + This parameter instructs PostgreSQL to search + for the first instance of any relation with the provided name. + If no relations are found a notice is issued and the command ends. + If a relation is found then one of two things will happen: + if the relation is an index it is dropped, otherwise the command fails. diff --git a/doc/src/sgml/ref/drop_materialized_view.sgml b/doc/src/sgml/ref/drop_materialized_view.sgml index c8f3bc5b0d..6647a0db0d 100644 --- a/doc/src/sgml/ref/drop_materialized_view.sgml +++ b/doc/src/sgml/ref/drop_materialized_view.sgml @@ -43,8 +43,11 @@ DROP MATERIALIZED VIEW [ IF EXISTS ] nameIF EXISTS - Do not throw an error if the materialized view does not exist. A notice - is issued in this case. + This parameter instructs PostgreSQL to search + for the first instance of any relation with the provided name. + If no relations are found a notice is issued and the command ends. + If a relation is found then one of two things will happen: + if the relation is a materialized view it is dropped, otherwise the command fails. diff --git a/doc/src/sgml/ref/drop_sequence.sgml b/doc/src/sgml/ref/drop_sequence.sgml index 387c98edbc..b3209d6d01 100644 --- a/doc/src/sgml/ref/drop_sequence.sgml +++ b/doc/src/sgml/ref/drop_sequence.sgml @@ -42,8 +42,11 @@ DROP SEQUENCE [ IF EXISTS ] name [, IF EXISTS - Do not throw an error if the sequence does not exist. A notice is issued - in this case. + This parameter instructs PostgreSQL to search + for the first instance of any relation with the provided name. + If no relations are found a notice is issued and the command ends. + If a relation is found then one of two things will happen: + if the relation is a sequence it is dropped, otherwise the command fails. diff --git a/doc/src/sgml/ref/drop_table.sgml b/doc/src/sgml/ref/drop_table.sgml index bf8996d198..9c9147f2ef 100644 --- a/doc/src/sgml/ref/drop_table.sgml +++ b/doc/src/sgml/ref/drop_table.sgml @@ -55,8 +55,11 @@ DROP TABLE [ IF EXISTS ] name [, .. IF EXISTS - Do not throw an error if the table does not exist. A notice is issued - in this case. + This parameter instructs PostgreSQL to search + for the first instance of any relation with the provided name. + If no relations are found a notice is issued and the command ends. + If a relation is found then one of two things will happen: + if the relation is a table it is dropped, otherwise the command fails. diff --git a/doc/src/sgml/ref/drop_type.sgml b/doc/src/sgml/ref/drop_type.sgml index 9e555c0624..afaa2387fe 100644 --- a/doc/src/sgml/ref/drop_type.sgml +++ b/doc/src/sgml/ref/drop_type.sgml @@ -30,7 +30,9 @@ DROP TYPE [ IF EXISTS ] name [, ... DROP TYPE removes a user-defined data type. - Only the owner of a type can remove it. + Only the owner of a type can remove it. This includes domains, + though they can be targeted specifically by using the + command. @@ -42,8 +44,15 @@ DROP TYPE [ IF EXISTS ] name [, ... IF EXISTS - Do not throw an error if the type does not exist. A notice is issued - in this case. + This parameter instructs PostgreSQL to search + for the first instance of any type definition with the provided name. + If no type definition is found a notice is issued and the command ends. + If a type definition is found then it will be dropped. + + + However, an attempt to drop the automatically created composite type of an + existing relation will fail as the relation has a dependency on the found + type definition. @@ -110,6 +119,7 @@ DROP TYPE box; + diff --git a/doc/src/sgml/ref/drop_view.sgml b/doc/src/sgml/ref/drop_view.sgml index a1c550ec3e..ff75410cf1 100644 --- a/doc/src/sgml/ref/drop_view.sgml +++ b/doc/src/sgml/ref/drop_view.sgml @@ -42,8 +42,11 @@ DROP VIEW [ IF EXISTS ] name [, ... IF EXISTS - Do not throw an error if the view does not exist. A notice is issued - in this case. + This parameter instructs PostgreSQL to search + for the first instance of any relation with the provided name. + If no relations are found a notice is issued and the command ends. + If a relation is found then one of two things will happen: + if the relation is a view it is dropped, otherwise the command fails. diff --git a/src/test/regress/expected/drop_if_exists.out b/src/test/regress/expected/drop_if_exists.out index 5e44c2c3ce..b25886272b 100644 --- a/src/test/regress/expected/drop_if_exists.out +++ b/src/test/regress/expected/drop_if_exists.out @@ -330,6 +330,125 @@ HINT: Specify the argument list to select the routine unambiguously. -- cleanup DROP PROCEDURE test_ambiguous_procname(int); DROP PROCEDURE test_ambiguous_procname(text); +-- ============== Demonstrate namespace collision behavior ======= +CREATE SCHEMA test_if_exists_first; +CREATE SCHEMA test_if_exists_second; +SET search_path TO test_if_exists_first, test_if_exists_second; +DROP TABLE test_if_exists_second.test_rel_exists; +ERROR: table "test_rel_exists" does not exist +DROP TABLE IF EXISTS test_if_exists_second.test_rel_exists; +NOTICE: table "test_rel_exists" does not exist, skipping +CREATE TABLE test_if_exists_second.test_rel_exists (a int, b text); +CREATE TABLE test_if_exists_first.test_rel_with_index (ai int, bi text); +CREATE TABLE IF NOT EXISTS test_rel_exists (c text, d int); -- IF NOT EXISTS checks only the first schema +DROP TABLE IF EXISTS test_rel_exists; -- Two matches but only the first is dropped +-- A role is not a relation so this shouldn't be affected +DROP ROLE IF EXISTS test_rel_exists; +NOTICE: role "test_rel_exists" does not exist, skipping +-- Table presence in the second schema causes a failure here +-- even though a corresponding non-schema-qualified create +-- statement would succeed. +DROP VIEW IF EXISTS test_rel_exists; +ERROR: "test_rel_exists" is not a view +HINT: Use DROP TABLE to remove a table. +DROP INDEX IF EXISTS test_rel_exists; +ERROR: "test_rel_exists" is not an index +HINT: Use DROP TABLE to remove a table. +DROP SEQUENCE IF EXISTS test_rel_exists; +ERROR: "test_rel_exists" is not a sequence +HINT: Use DROP TABLE to remove a table. +DROP MATERIALIZED VIEW IF EXISTS test_rel_exists; +ERROR: "test_rel_exists" is not a materialized view +HINT: Use DROP TABLE to remove a table. +DROP FOREIGN TABLE IF EXISTS test_rel_exists; +ERROR: "test_rel_exists" is not a foreign table +HINT: Use DROP TABLE to remove a table. +CREATE VIEW test_if_exists_first.test_rel_exists AS + SELECT 1::bigint AS d, 'e'::text AS e; +DROP TABLE test_rel_exists; +ERROR: "test_rel_exists" is not a table +HINT: Use DROP VIEW to remove a view. +DROP VIEW test_rel_exists; +CREATE INDEX test_rel_exists ON test_if_exists_first.test_rel_with_index (ai); +DROP TABLE test_rel_exists; +ERROR: "test_rel_exists" is not a table +HINT: Use DROP INDEX to remove an index. +DROP INDEX test_rel_exists; +CREATE SEQUENCE test_rel_exists; +DROP TABLE test_rel_exists; +ERROR: "test_rel_exists" is not a table +HINT: Use DROP SEQUENCE to remove a sequence. +DROP SEQUENCE test_rel_exists; +CREATE MATERIALIZED VIEW test_if_exists_first.test_rel_exists AS + SELECT 1::bigint AS d, 'e'::text AS e; +DROP TABLE test_rel_exists; +ERROR: "test_rel_exists" is not a table +HINT: Use DROP MATERIALIZED VIEW to remove a materialized view. +DROP TABLE test_if_exists_second.test_rel_exists; +DROP MATERIALIZED VIEW test_rel_exists; +DROP TABLE test_if_exists_first.test_rel_with_index; +-- Type & Domain behavior is thus: +-- If Exists Behavior +DROP DOMAIN IF EXISTS test_rel_exists; +NOTICE: type "test_rel_exists" does not exist, skipping +DROP TYPE IF EXISTS test_rel_exists; +NOTICE: type "test_rel_exists" does not exist, skipping +CREATE TABLE test_if_exists_second.test_rel_exists (a int, b text); +DROP DOMAIN IF EXISTS test_rel_exists; +ERROR: "test_rel_exists" is not a domain +CREATE DOMAIN test_rel_exists int4; +DROP TABLE test_rel_exists; +DROP DOMAIN test_rel_exists; +-- Test with domain first and type second; plus table tests +CREATE TABLE test_if_exists_second.test_rel_exists (a int, b text); +CREATE TYPE test_if_exists_second.test_rel_exists AS (c int, d text); -- type with same name exists as table creation creates a composite type +ERROR: type "test_rel_exists" already exists +CREATE DOMAIN test_if_exists_second.test_rel_exists int4; -- domain creation with same name as table fails as auto-generated type conflicts +ERROR: type "test_rel_exists" already exists +DROP TYPE IF EXISTS test_if_exists_second.test_rel_exists; -- cannot independently drop a composite type associated with a table +ERROR: cannot drop type test_rel_exists because table test_rel_exists requires it +HINT: You can drop table test_rel_exists instead. +DROP TABLE test_if_exists_second.test_rel_exists; +CREATE TYPE test_if_exists_second.test_rel_exists AS (c int, d text); +CREATE TABLE test_if_exists_second.test_rel_exists (a int, b text); -- auto-created type prevents table from being created +ERROR: relation "test_rel_exists" already exists +DROP TABLE test_if_exists_second.test_rel_exists; -- a standalone composite type cannot be dropped using the drop table command +ERROR: "test_rel_exists" is not a table +HINT: Use DROP TYPE to remove a type. +DROP DOMAIN IF EXISTS test_if_exists_second.test_rel_exists; +ERROR: "test_if_exists_second.test_rel_exists" is not a domain +CREATE DOMAIN test_rel_exists int4; +DROP TYPE IF EXISTS test_rel_exists; -- DROP TYPE Drops the Domain +DROP DOMAIN IF EXISTS test_if_exists_second.test_rel_exists; +ERROR: "test_if_exists_second.test_rel_exists" is not a domain +DROP DOMAIN test_rel_exists; -- double-checking the domain doesn't exist +ERROR: "test_rel_exists" is not a domain +DROP TYPE test_rel_exists; +-- Test with domain second and type first +CREATE TYPE test_if_exists_first.test_rel_exists AS (c int, d text); +CREATE DOMAIN test_if_exists_second.test_rel_exists int4; +DROP DOMAIN IF EXISTS test_rel_exists; +ERROR: "test_rel_exists" is not a domain +DROP TYPE IF EXISTS test_rel_exists; +DROP TYPE IF EXISTS test_if_exists_second.test_rel_exists; +DROP DOMAIN IF EXISTS test_rel_exists; +NOTICE: type "test_rel_exists" does not exist, skipping +-- /Type & Domain +-- Bug # 16492 - this script produces an error, arguably it should not +CREATE TABLE test_if_exists_first.test_rel_exists (a int, b text); +DROP TABLE IF EXISTS test_if_exists_first.test_rel_exists; +DROP VIEW IF EXISTS test_if_exists_first.test_rel_exists; +NOTICE: view "test_rel_exists" does not exist, skipping +CREATE VIEW test_if_exists_first.test_rel_exists AS + SELECT 1::int AS a, 'one'::text AS b; +DROP TABLE IF EXISTS test_if_exists_first.test_rel_exists; +ERROR: "test_rel_exists" is not a table +HINT: Use DROP VIEW to remove a view. +DROP VIEW IF EXISTS test_if_exists_first.test_rel_exists; +CREATE VIEW test_if_exists_first.test_rel_exists AS + SELECT 1::int AS a, 'one'::text AS b; +DROP VIEW test_if_exists_first.test_rel_exists; +-- /Bug # 16492 -- This test checks both the functionality of 'if exists' and the syntax -- of the drop database command. drop database test_database_exists (force); diff --git a/src/test/regress/sql/drop_if_exists.sql b/src/test/regress/sql/drop_if_exists.sql index ac6168b91f..b95189c47c 100644 --- a/src/test/regress/sql/drop_if_exists.sql +++ b/src/test/regress/sql/drop_if_exists.sql @@ -296,6 +296,104 @@ DROP ROUTINE IF EXISTS test_ambiguous_procname; DROP PROCEDURE test_ambiguous_procname(int); DROP PROCEDURE test_ambiguous_procname(text); +-- ============== Demonstrate namespace collision behavior ======= +CREATE SCHEMA test_if_exists_first; +CREATE SCHEMA test_if_exists_second; +SET search_path TO test_if_exists_first, test_if_exists_second; + +DROP TABLE test_if_exists_second.test_rel_exists; +DROP TABLE IF EXISTS test_if_exists_second.test_rel_exists; +CREATE TABLE test_if_exists_second.test_rel_exists (a int, b text); +CREATE TABLE test_if_exists_first.test_rel_with_index (ai int, bi text); + +CREATE TABLE IF NOT EXISTS test_rel_exists (c text, d int); -- IF NOT EXISTS checks only the first schema +DROP TABLE IF EXISTS test_rel_exists; -- Two matches but only the first is dropped + +-- A role is not a relation so this shouldn't be affected +DROP ROLE IF EXISTS test_rel_exists; + +-- Table presence in the second schema causes a failure here +-- even though a corresponding non-schema-qualified create +-- statement would succeed. +DROP VIEW IF EXISTS test_rel_exists; +DROP INDEX IF EXISTS test_rel_exists; +DROP SEQUENCE IF EXISTS test_rel_exists; +DROP MATERIALIZED VIEW IF EXISTS test_rel_exists; +DROP FOREIGN TABLE IF EXISTS test_rel_exists; + +CREATE VIEW test_if_exists_first.test_rel_exists AS + SELECT 1::bigint AS d, 'e'::text AS e; +DROP TABLE test_rel_exists; +DROP VIEW test_rel_exists; + +CREATE INDEX test_rel_exists ON test_if_exists_first.test_rel_with_index (ai); +DROP TABLE test_rel_exists; +DROP INDEX test_rel_exists; + +CREATE SEQUENCE test_rel_exists; +DROP TABLE test_rel_exists; +DROP SEQUENCE test_rel_exists; + +CREATE MATERIALIZED VIEW test_if_exists_first.test_rel_exists AS + SELECT 1::bigint AS d, 'e'::text AS e; +DROP TABLE test_rel_exists; + +DROP TABLE test_if_exists_second.test_rel_exists; +DROP MATERIALIZED VIEW test_rel_exists; +DROP TABLE test_if_exists_first.test_rel_with_index; + +-- Type & Domain behavior is thus: +-- If Exists Behavior +DROP DOMAIN IF EXISTS test_rel_exists; +DROP TYPE IF EXISTS test_rel_exists; + +CREATE TABLE test_if_exists_second.test_rel_exists (a int, b text); +DROP DOMAIN IF EXISTS test_rel_exists; +CREATE DOMAIN test_rel_exists int4; +DROP TABLE test_rel_exists; +DROP DOMAIN test_rel_exists; + +-- Test with domain first and type second; plus table tests +CREATE TABLE test_if_exists_second.test_rel_exists (a int, b text); +CREATE TYPE test_if_exists_second.test_rel_exists AS (c int, d text); -- type with same name exists as table creation creates a composite type +CREATE DOMAIN test_if_exists_second.test_rel_exists int4; -- domain creation with same name as table fails as auto-generated type conflicts +DROP TYPE IF EXISTS test_if_exists_second.test_rel_exists; -- cannot independently drop a composite type associated with a table +DROP TABLE test_if_exists_second.test_rel_exists; + +CREATE TYPE test_if_exists_second.test_rel_exists AS (c int, d text); +CREATE TABLE test_if_exists_second.test_rel_exists (a int, b text); -- auto-created type prevents table from being created +DROP TABLE test_if_exists_second.test_rel_exists; -- a standalone composite type cannot be dropped using the drop table command + +DROP DOMAIN IF EXISTS test_if_exists_second.test_rel_exists; +CREATE DOMAIN test_rel_exists int4; +DROP TYPE IF EXISTS test_rel_exists; -- DROP TYPE Drops the Domain +DROP DOMAIN IF EXISTS test_if_exists_second.test_rel_exists; +DROP DOMAIN test_rel_exists; -- double-checking the domain doesn't exist +DROP TYPE test_rel_exists; + +-- Test with domain second and type first +CREATE TYPE test_if_exists_first.test_rel_exists AS (c int, d text); +CREATE DOMAIN test_if_exists_second.test_rel_exists int4; +DROP DOMAIN IF EXISTS test_rel_exists; +DROP TYPE IF EXISTS test_rel_exists; +DROP TYPE IF EXISTS test_if_exists_second.test_rel_exists; + +DROP DOMAIN IF EXISTS test_rel_exists; +-- /Type & Domain + +-- Bug # 16492 - this script produces an error, arguably it should not +CREATE TABLE test_if_exists_first.test_rel_exists (a int, b text); +DROP TABLE IF EXISTS test_if_exists_first.test_rel_exists; +DROP VIEW IF EXISTS test_if_exists_first.test_rel_exists; +CREATE VIEW test_if_exists_first.test_rel_exists AS + SELECT 1::int AS a, 'one'::text AS b; +DROP TABLE IF EXISTS test_if_exists_first.test_rel_exists; +DROP VIEW IF EXISTS test_if_exists_first.test_rel_exists; +CREATE VIEW test_if_exists_first.test_rel_exists AS + SELECT 1::int AS a, 'one'::text AS b; +DROP VIEW test_if_exists_first.test_rel_exists; +-- /Bug # 16492 + -- This test checks both the functionality of 'if exists' and the syntax -- of the drop database command. drop database test_database_exists (force);