From ba1ea9b5bde4ad743dcff071c705aa96145ec714 Mon Sep 17 00:00:00 2001 From: Onur Tirtir Date: Thu, 22 Jun 2023 18:19:15 +0300 Subject: [PATCH 1/7] Refactor the code that prepares constraint objects in an alter table stmt into a func --- src/backend/distributed/commands/table.c | 71 ++++++++++++++---------- src/include/distributed/commands.h | 2 + 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/src/backend/distributed/commands/table.c b/src/backend/distributed/commands/table.c index 390a81286..85d09d942 100644 --- a/src/backend/distributed/commands/table.c +++ b/src/backend/distributed/commands/table.c @@ -1028,30 +1028,7 @@ PreprocessAlterTableAddConstraint(AlterTableStmt *alterTableStatement, Oid relationId, Constraint *constraint) { - /* - * We should only preprocess an ADD CONSTRAINT command if we have empty conname - * This only happens when we have to create a constraint name in citus since the client does - * not specify a name. - * indexname should also be NULL to make sure this is not an - * ADD {PRIMARY KEY, UNIQUE} USING INDEX command - * which doesn't need a conname since the indexname will be used - */ - Assert(constraint->conname == NULL && constraint->indexname == NULL); - - Relation rel = RelationIdGetRelation(relationId); - - /* - * Change the alterTableCommand so that the standard utility - * hook runs it with the name we created. - */ - - constraint->conname = GenerateConstraintName(RelationGetRelationName(rel), - RelationGetNamespace(rel), - constraint); - - RelationClose(rel); - - SwitchToSequentialAndLocalExecutionIfConstraintNameTooLong(relationId, constraint); + PrepareAlterTableStmtForConstraint(alterTableStatement, relationId, constraint); char *ddlCommand = DeparseTreeNode((Node *) alterTableStatement); @@ -1067,11 +1044,6 @@ PreprocessAlterTableAddConstraint(AlterTableStmt *alterTableStatement, Oid Oid rightRelationId = RangeVarGetRelid(constraint->pktable, NoLock, false); - if (IsCitusTableType(rightRelationId, REFERENCE_TABLE)) - { - EnsureSequentialModeForAlterTableOperation(); - } - /* * If one of the relations involved in the FOREIGN KEY constraint is not a distributed table, citus errors out eventually. * PreprocessAlterTableStmt function returns an empty tasklist in those cases. @@ -1099,6 +1071,47 @@ PreprocessAlterTableAddConstraint(AlterTableStmt *alterTableStatement, Oid } +/* + * PrepareAlterTableStmtForConstraint assigns a name to the constraint if it + * does not have one and switches to sequential and local execution if the + * constraint name is too long. + */ +void +PrepareAlterTableStmtForConstraint(AlterTableStmt *alterTableStatement, + Oid relationId, + Constraint *constraint) +{ + if (constraint->conname == NULL && constraint->indexname == NULL) + { + Relation rel = RelationIdGetRelation(relationId); + + /* + * Change the alterTableCommand so that the standard utility + * hook runs it with the name we created. + */ + + constraint->conname = GenerateConstraintName(RelationGetRelationName(rel), + RelationGetNamespace(rel), + constraint); + + RelationClose(rel); + } + + SwitchToSequentialAndLocalExecutionIfConstraintNameTooLong(relationId, constraint); + + if (constraint->contype == CONSTR_FOREIGN) + { + Oid rightRelationId = RangeVarGetRelid(constraint->pktable, NoLock, + false); + + if (IsCitusTableType(rightRelationId, REFERENCE_TABLE)) + { + EnsureSequentialModeForAlterTableOperation(); + } + } +} + + /* * PreprocessAlterTableStmt determines whether a given ALTER TABLE statement * involves a distributed table. If so (and if the statement does not use diff --git a/src/include/distributed/commands.h b/src/include/distributed/commands.h index a013f3977..5c2bf2f42 100644 --- a/src/include/distributed/commands.h +++ b/src/include/distributed/commands.h @@ -582,6 +582,8 @@ extern bool ShouldEnableLocalReferenceForeignKeys(void); extern List * PreprocessAlterTableStmtAttachPartition(AlterTableStmt *alterTableStatement, const char *queryString); extern List * PostprocessAlterTableSchemaStmt(Node *node, const char *queryString); +extern void PrepareAlterTableStmtForConstraint(AlterTableStmt *alterTableStatement, + Oid relationId, Constraint *constraint); extern List * PreprocessAlterTableStmt(Node *node, const char *alterTableCommand, ProcessUtilityContext processUtilityContext); extern List * PreprocessAlterTableMoveAllStmt(Node *node, const char *queryString, From 56f1daa80068673832ae3987cf08bafca9e7fdc0 Mon Sep 17 00:00:00 2001 From: Onur Tirtir Date: Thu, 22 Jun 2023 18:23:55 +0300 Subject: [PATCH 2/7] Refactor the code that extends constraint/index names on shards into a func --- .../distributed/relay/relay_event_utility.c | 93 +++++++++++-------- 1 file changed, 55 insertions(+), 38 deletions(-) diff --git a/src/backend/distributed/relay/relay_event_utility.c b/src/backend/distributed/relay/relay_event_utility.c index b7629a38b..6924b8e8d 100644 --- a/src/backend/distributed/relay/relay_event_utility.c +++ b/src/backend/distributed/relay/relay_event_utility.c @@ -54,6 +54,9 @@ #include "utils/relcache.h" /* Local functions forward declarations */ +static void RelayEventExtendConstraintAndIndexNames(AlterTableStmt *alterTableStmt, + Constraint *constraint, + uint64 shardId); static bool UpdateWholeRowColumnReferencesWalker(Node *node, uint64 *shardId); /* exports for SQL callable functions */ @@ -150,44 +153,8 @@ RelayEventExtendNames(Node *parseTree, char *schemaName, uint64 shardId) if (command->subtype == AT_AddConstraint) { Constraint *constraint = (Constraint *) command->def; - char **constraintName = &(constraint->conname); - const bool missingOk = false; - relationId = RangeVarGetRelid(alterTableStmt->relation, - AccessShareLock, - missingOk); - - if (constraint->indexname) - { - char **indexName = &(constraint->indexname); - AppendShardIdToName(indexName, shardId); - } - - /* - * Append shardId to constraint names if - * - table is not partitioned or - * - constraint is not a CHECK constraint - * - * We do not want to append shardId to partitioned table shards because - * the names of constraints will be inherited, and the shardId will no - * longer be valid for the child table. - * - * See MergeConstraintsIntoExisting function in Postgres that requires - * inherited check constraints in child tables to have the same name - * with those in parent tables. - */ - if (!PartitionedTable(relationId) || - constraint->contype != CONSTR_CHECK) - { - /* - * constraint->conname could be empty in the case of - * ADD {PRIMARY KEY, UNIQUE} USING INDEX. - * In this case, already extended index name will be used by postgres. - */ - if (constraint->conname != NULL) - { - AppendShardIdToName(constraintName, shardId); - } - } + RelayEventExtendConstraintAndIndexNames(alterTableStmt, constraint, + shardId); } else if (command->subtype == AT_DropConstraint || command->subtype == AT_ValidateConstraint) @@ -622,6 +589,56 @@ RelayEventExtendNames(Node *parseTree, char *schemaName, uint64 shardId) } +/* + * RelayEventExtendConstraintAndIndexNames extends the names of constraints + * and indexes in given constraint with the shardId. + */ +static void +RelayEventExtendConstraintAndIndexNames(AlterTableStmt *alterTableStmt, + Constraint *constraint, + uint64 shardId) +{ + char **constraintName = &(constraint->conname); + const bool missingOk = false; + Oid relationId = RangeVarGetRelid(alterTableStmt->relation, + AccessShareLock, + missingOk); + + if (constraint->indexname) + { + char **indexName = &(constraint->indexname); + AppendShardIdToName(indexName, shardId); + } + + /* + * Append shardId to constraint names if + * - table is not partitioned or + * - constraint is not a CHECK constraint + * + * We do not want to append shardId to partitioned table shards because + * the names of constraints will be inherited, and the shardId will no + * longer be valid for the child table. + * + * See MergeConstraintsIntoExisting function in Postgres that requires + * inherited check constraints in child tables to have the same name + * with those in parent tables. + */ + if (!PartitionedTable(relationId) || + constraint->contype != CONSTR_CHECK) + { + /* + * constraint->conname could be empty in the case of + * ADD {PRIMARY KEY, UNIQUE} USING INDEX. + * In this case, already extended index name will be used by postgres. + */ + if (constraint->conname != NULL) + { + AppendShardIdToName(constraintName, shardId); + } + } +} + + /* * RelayEventExtendNamesForInterShardCommands extends relation names in the given parse * tree for certain utility commands. The function more specifically extends table, index From 001437bdfe7d7d5bff66f5e48fd3223d5a124cd0 Mon Sep 17 00:00:00 2001 From: Onur Tirtir Date: Thu, 22 Jun 2023 18:28:54 +0300 Subject: [PATCH 3/7] Refactor AppendAlterTableCmdAddConstraint to reuse it for ADD COLUMN too --- .../deparser/deparse_table_stmts.c | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/backend/distributed/deparser/deparse_table_stmts.c b/src/backend/distributed/deparser/deparse_table_stmts.c index 1d9ee1739..8d3803cfe 100644 --- a/src/backend/distributed/deparser/deparse_table_stmts.c +++ b/src/backend/distributed/deparser/deparse_table_stmts.c @@ -142,13 +142,19 @@ AppendColumnNameList(StringInfo buf, List *columns) /* - * AppendAlterTableCmdAddConstraint builds the add constraint command for index constraints - * in the ADD CONSTRAINT {PRIMARY KEY, UNIQUE, EXCLUSION} form and appends it to the buf. + * AppendAlterTableCmdConstraint builds a string required to create given + * constraint as part of an ADD CONSTRAINT subcommand and appends it to + * the buf. */ static void -AppendAlterTableCmdAddConstraint(StringInfo buf, Constraint *constraint, - AlterTableStmt *stmt) +AppendAlterTableCmdConstraint(StringInfo buf, Constraint *constraint, + AlterTableStmt *stmt, AlterTableType subtype) { + if (subtype != AT_AddConstraint) + { + ereport(ERROR, (errmsg("Unsupported alter table subtype: %d", (int) subtype))); + } + /* Need to deparse the alter table constraint command only if we are adding a constraint name.*/ if (constraint->conname == NULL) { @@ -156,7 +162,11 @@ AppendAlterTableCmdAddConstraint(StringInfo buf, Constraint *constraint, "Constraint name can not be NULL when deparsing the constraint."))); } - appendStringInfoString(buf, " ADD CONSTRAINT "); + if (subtype == AT_AddConstraint) + { + appendStringInfoString(buf, " ADD CONSTRAINT "); + } + appendStringInfo(buf, "%s ", quote_identifier(constraint->conname)); /* postgres version >= PG15 @@ -184,7 +194,10 @@ AppendAlterTableCmdAddConstraint(StringInfo buf, Constraint *constraint, #endif } - AppendColumnNameList(buf, constraint->keys); + if (subtype == AT_AddConstraint) + { + AppendColumnNameList(buf, constraint->keys); + } if (constraint->including != NULL) { @@ -275,9 +288,12 @@ AppendAlterTableCmdAddConstraint(StringInfo buf, Constraint *constraint, } else if (constraint->contype == CONSTR_FOREIGN) { - appendStringInfoString(buf, " FOREIGN KEY"); + if (subtype == AT_AddConstraint) + { + appendStringInfoString(buf, " FOREIGN KEY"); - AppendColumnNameList(buf, constraint->fk_attrs); + AppendColumnNameList(buf, constraint->fk_attrs); + } appendStringInfoString(buf, " REFERENCES"); @@ -379,8 +395,11 @@ AppendAlterTableCmdAddConstraint(StringInfo buf, Constraint *constraint, } } - /* FOREIGN KEY and CHECK constraints migth have NOT VALID option */ - if (constraint->skip_validation) + /* + * For ADD CONSTRAINT subcommand, FOREIGN KEY and CHECK constraints migth + * have NOT VALID option. + */ + if (subtype == AT_AddConstraint && constraint->skip_validation) { appendStringInfoString(buf, " NOT VALID "); } @@ -429,7 +448,7 @@ AppendAlterTableCmd(StringInfo buf, AlterTableCmd *alterTableCmd, AlterTableStmt */ if (ConstrTypeCitusCanDefaultName(constraint->contype)) { - AppendAlterTableCmdAddConstraint(buf, constraint, stmt); + AppendAlterTableCmdConstraint(buf, constraint, stmt, AT_AddConstraint); break; } } From d4789a2c3aa6d0e59b55066092e6736569064223 Mon Sep 17 00:00:00 2001 From: Onur Tirtir Date: Mon, 3 Jul 2023 15:59:36 +0300 Subject: [PATCH 4/7] Stabilize test helper sql files multi_test_helpers is run in parallel with others, so need to stabilize other test helpers too to make multi_test_helpers runnable multiple times. --- .../expected/columnar_test_helpers.out | 3 ++ .../regress/expected/failure_test_helpers.out | 45 ++--------------- .../regress/expected/multi_create_fdw.out | 6 ++- .../regress/sql/columnar_test_helpers.sql | 4 ++ src/test/regress/sql/failure_test_helpers.sql | 50 ++----------------- src/test/regress/sql/multi_create_fdw.sql | 7 ++- 6 files changed, 24 insertions(+), 91 deletions(-) diff --git a/src/test/regress/expected/columnar_test_helpers.out b/src/test/regress/expected/columnar_test_helpers.out index d85bbd54f..9a9e21057 100644 --- a/src/test/regress/expected/columnar_test_helpers.out +++ b/src/test/regress/expected/columnar_test_helpers.out @@ -1,3 +1,6 @@ +SET client_min_messages TO WARNING; +DROP SCHEMA IF EXISTS columnar_test_helpers CASCADE; +RESET client_min_messages; CREATE SCHEMA columnar_test_helpers; SET search_path TO columnar_test_helpers; CREATE OR REPLACE FUNCTION columnar_storage_info( diff --git a/src/test/regress/expected/failure_test_helpers.out b/src/test/regress/expected/failure_test_helpers.out index 8c2be9825..da63a985f 100644 --- a/src/test/regress/expected/failure_test_helpers.out +++ b/src/test/regress/expected/failure_test_helpers.out @@ -11,7 +11,7 @@ SELECT pg_reload_conf(); (1 row) -- Add some helper functions for sending commands to mitmproxy -CREATE FUNCTION citus.mitmproxy(text) RETURNS TABLE(result text) AS $$ +CREATE OR REPLACE FUNCTION citus.mitmproxy(text) RETURNS TABLE(result text) AS $$ DECLARE command ALIAS FOR $1; BEGIN @@ -26,52 +26,13 @@ BEGIN RETURN QUERY SELECT * FROM mitmproxy_result; END; $$ LANGUAGE plpgsql; -CREATE FUNCTION citus.clear_network_traffic() RETURNS void AS $$ +CREATE OR REPLACE FUNCTION citus.clear_network_traffic() RETURNS void AS $$ BEGIN PERFORM citus.mitmproxy('recorder.reset()'); RETURN; -- return void END; $$ LANGUAGE plpgsql; -CREATE FUNCTION citus.dump_network_traffic() -RETURNS TABLE(conn int, source text, message text) AS $$ -BEGIN - CREATE TEMPORARY TABLE mitmproxy_command (command text) ON COMMIT DROP; - CREATE TEMPORARY TABLE mitmproxy_result ( - conn int, source text, message text - ) ON COMMIT DROP; - - INSERT INTO mitmproxy_command VALUES ('recorder.dump()'); - - EXECUTE format('COPY mitmproxy_command TO %L', current_setting('citus.mitmfifo')); - EXECUTE format('COPY mitmproxy_result FROM %L', current_setting('citus.mitmfifo')); - - RETURN QUERY SELECT * FROM mitmproxy_result; -END; -$$ LANGUAGE plpgsql; -\c - - - :worker_2_port --- Add some helper functions for sending commands to mitmproxy -CREATE FUNCTION citus.mitmproxy(text) RETURNS TABLE(result text) AS $$ -DECLARE - command ALIAS FOR $1; -BEGIN - CREATE TEMPORARY TABLE mitmproxy_command (command text) ON COMMIT DROP; - CREATE TEMPORARY TABLE mitmproxy_result (res text) ON COMMIT DROP; - - INSERT INTO mitmproxy_command VALUES (command); - - EXECUTE format('COPY mitmproxy_command TO %L', current_setting('citus.mitmfifo')); - EXECUTE format('COPY mitmproxy_result FROM %L', current_setting('citus.mitmfifo')); - - RETURN QUERY SELECT * FROM mitmproxy_result; -END; -$$ LANGUAGE plpgsql; -CREATE FUNCTION citus.clear_network_traffic() RETURNS void AS $$ -BEGIN - PERFORM citus.mitmproxy('recorder.reset()'); - RETURN; -- return void -END; -$$ LANGUAGE plpgsql; -CREATE FUNCTION citus.dump_network_traffic() +CREATE OR REPLACE FUNCTION citus.dump_network_traffic() RETURNS TABLE(conn int, source text, message text) AS $$ BEGIN CREATE TEMPORARY TABLE mitmproxy_command (command text) ON COMMIT DROP; diff --git a/src/test/regress/expected/multi_create_fdw.out b/src/test/regress/expected/multi_create_fdw.out index e15d17546..3379a82fa 100644 --- a/src/test/regress/expected/multi_create_fdw.out +++ b/src/test/regress/expected/multi_create_fdw.out @@ -3,7 +3,11 @@ SET citus.next_shard_id TO 390000; -- get ready for the foreign data wrapper tests -- =================================================================== -- create fake fdw for use in tests -CREATE FUNCTION fake_fdw_handler() +SET client_min_messages TO WARNING; +DROP SERVER IF EXISTS fake_fdw_server CASCADE; +DROP FOREIGN DATA WRAPPER IF EXISTS fake_fdw CASCADE; +RESET client_min_messages; +CREATE OR REPLACE FUNCTION fake_fdw_handler() RETURNS fdw_handler AS 'citus' LANGUAGE C STRICT; diff --git a/src/test/regress/sql/columnar_test_helpers.sql b/src/test/regress/sql/columnar_test_helpers.sql index 2e85ebc88..d88f8b88f 100644 --- a/src/test/regress/sql/columnar_test_helpers.sql +++ b/src/test/regress/sql/columnar_test_helpers.sql @@ -1,3 +1,7 @@ +SET client_min_messages TO WARNING; +DROP SCHEMA IF EXISTS columnar_test_helpers CASCADE; +RESET client_min_messages; + CREATE SCHEMA columnar_test_helpers; SET search_path TO columnar_test_helpers; diff --git a/src/test/regress/sql/failure_test_helpers.sql b/src/test/regress/sql/failure_test_helpers.sql index 7053905ac..b7f9eae3a 100644 --- a/src/test/regress/sql/failure_test_helpers.sql +++ b/src/test/regress/sql/failure_test_helpers.sql @@ -8,7 +8,7 @@ SELECT pg_reload_conf(); -- Add some helper functions for sending commands to mitmproxy -CREATE FUNCTION citus.mitmproxy(text) RETURNS TABLE(result text) AS $$ +CREATE OR REPLACE FUNCTION citus.mitmproxy(text) RETURNS TABLE(result text) AS $$ DECLARE command ALIAS FOR $1; BEGIN @@ -24,58 +24,14 @@ BEGIN END; $$ LANGUAGE plpgsql; -CREATE FUNCTION citus.clear_network_traffic() RETURNS void AS $$ +CREATE OR REPLACE FUNCTION citus.clear_network_traffic() RETURNS void AS $$ BEGIN PERFORM citus.mitmproxy('recorder.reset()'); RETURN; -- return void END; $$ LANGUAGE plpgsql; -CREATE FUNCTION citus.dump_network_traffic() -RETURNS TABLE(conn int, source text, message text) AS $$ -BEGIN - CREATE TEMPORARY TABLE mitmproxy_command (command text) ON COMMIT DROP; - CREATE TEMPORARY TABLE mitmproxy_result ( - conn int, source text, message text - ) ON COMMIT DROP; - - INSERT INTO mitmproxy_command VALUES ('recorder.dump()'); - - EXECUTE format('COPY mitmproxy_command TO %L', current_setting('citus.mitmfifo')); - EXECUTE format('COPY mitmproxy_result FROM %L', current_setting('citus.mitmfifo')); - - RETURN QUERY SELECT * FROM mitmproxy_result; -END; -$$ LANGUAGE plpgsql; - -\c - - - :worker_2_port - --- Add some helper functions for sending commands to mitmproxy - -CREATE FUNCTION citus.mitmproxy(text) RETURNS TABLE(result text) AS $$ -DECLARE - command ALIAS FOR $1; -BEGIN - CREATE TEMPORARY TABLE mitmproxy_command (command text) ON COMMIT DROP; - CREATE TEMPORARY TABLE mitmproxy_result (res text) ON COMMIT DROP; - - INSERT INTO mitmproxy_command VALUES (command); - - EXECUTE format('COPY mitmproxy_command TO %L', current_setting('citus.mitmfifo')); - EXECUTE format('COPY mitmproxy_result FROM %L', current_setting('citus.mitmfifo')); - - RETURN QUERY SELECT * FROM mitmproxy_result; -END; -$$ LANGUAGE plpgsql; - -CREATE FUNCTION citus.clear_network_traffic() RETURNS void AS $$ -BEGIN - PERFORM citus.mitmproxy('recorder.reset()'); - RETURN; -- return void -END; -$$ LANGUAGE plpgsql; - -CREATE FUNCTION citus.dump_network_traffic() +CREATE OR REPLACE FUNCTION citus.dump_network_traffic() RETURNS TABLE(conn int, source text, message text) AS $$ BEGIN CREATE TEMPORARY TABLE mitmproxy_command (command text) ON COMMIT DROP; diff --git a/src/test/regress/sql/multi_create_fdw.sql b/src/test/regress/sql/multi_create_fdw.sql index ea9333781..cb780a5c2 100644 --- a/src/test/regress/sql/multi_create_fdw.sql +++ b/src/test/regress/sql/multi_create_fdw.sql @@ -7,7 +7,12 @@ SET citus.next_shard_id TO 390000; -- =================================================================== -- create fake fdw for use in tests -CREATE FUNCTION fake_fdw_handler() +SET client_min_messages TO WARNING; +DROP SERVER IF EXISTS fake_fdw_server CASCADE; +DROP FOREIGN DATA WRAPPER IF EXISTS fake_fdw CASCADE; +RESET client_min_messages; + +CREATE OR REPLACE FUNCTION fake_fdw_handler() RETURNS fdw_handler AS 'citus' LANGUAGE C STRICT; From ae142e176445734881875964c68a77321f124906 Mon Sep 17 00:00:00 2001 From: Onur Tirtir Date: Tue, 4 Jul 2023 18:36:23 +0300 Subject: [PATCH 5/7] Properly handle IF NOT EXISTS for ADD COLUMN --- src/backend/distributed/commands/table.c | 14 +++++++++----- .../distributed/deparser/deparse_table_stmts.c | 5 +++++ .../regress/expected/multi_sequence_default.out | 4 +++- src/test/regress/sql/multi_sequence_default.sql | 3 ++- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/backend/distributed/commands/table.c b/src/backend/distributed/commands/table.c index 85d09d942..f6502c477 100644 --- a/src/backend/distributed/commands/table.c +++ b/src/backend/distributed/commands/table.c @@ -120,7 +120,8 @@ static void SetInterShardDDLTaskRelationShardList(Task *task, static Oid get_attrdef_oid(Oid relationId, AttrNumber attnum); static char * GetAddColumnWithNextvalDefaultCmd(Oid sequenceOid, Oid relationId, - char *colname, TypeName *typeName); + char *colname, TypeName *typeName, + bool ifNotExists); static void ErrorIfAlterTableDropTableNameFromPostgresFdw(List *optionList, Oid relationId); @@ -2650,7 +2651,9 @@ PostprocessAlterTableStmt(AlterTableStmt *alterTableStatement) columnDefinition ->colname, columnDefinition - ->typeName); + ->typeName, + command-> + missing_ok); } } } @@ -2915,7 +2918,7 @@ GetAlterColumnWithNextvalDefaultCmd(Oid sequenceOid, Oid relationId, char *colna */ static char * GetAddColumnWithNextvalDefaultCmd(Oid sequenceOid, Oid relationId, char *colname, - TypeName *typeName) + TypeName *typeName, bool ifNotExists) { char *qualifiedSequenceName = generate_qualified_relation_name(sequenceOid); char *qualifiedRelationName = generate_qualified_relation_name(relationId); @@ -2940,8 +2943,9 @@ GetAddColumnWithNextvalDefaultCmd(Oid sequenceOid, Oid relationId, char *colname StringInfoData str = { 0 }; initStringInfo(&str); appendStringInfo(&str, - "ALTER TABLE %s ADD COLUMN %s %s " - "DEFAULT %s(%s::regclass)", qualifiedRelationName, colname, + "ALTER TABLE %s ADD COLUMN %s %s %s " + "DEFAULT %s(%s::regclass)", qualifiedRelationName, + ifNotExists ? "IF NOT EXISTS" : "", colname, format_type_extended(typeOid, typmod, formatFlags), quote_qualified_identifier("pg_catalog", nextvalFunctionName), quote_literal_cstr(qualifiedSequenceName)); diff --git a/src/backend/distributed/deparser/deparse_table_stmts.c b/src/backend/distributed/deparser/deparse_table_stmts.c index 8d3803cfe..c58ad0798 100644 --- a/src/backend/distributed/deparser/deparse_table_stmts.c +++ b/src/backend/distributed/deparser/deparse_table_stmts.c @@ -475,6 +475,11 @@ AppendAlterTableCmdAddColumn(StringInfo buf, AlterTableCmd *alterTableCmd) appendStringInfoString(buf, " ADD COLUMN "); + if (alterTableCmd->missing_ok) + { + appendStringInfoString(buf, "IF NOT EXISTS "); + } + ColumnDef *columnDefinition = (ColumnDef *) alterTableCmd->def; /* diff --git a/src/test/regress/expected/multi_sequence_default.out b/src/test/regress/expected/multi_sequence_default.out index 1cf2e806d..8a984f884 100644 --- a/src/test/regress/expected/multi_sequence_default.out +++ b/src/test/regress/expected/multi_sequence_default.out @@ -143,6 +143,8 @@ SELECT create_distributed_table('seq_test_4','x'); CREATE SEQUENCE seq_4; ALTER TABLE seq_test_4 ADD COLUMN a bigint DEFAULT nextval('seq_4'); +ALTER TABLE seq_test_4 ADD COLUMN IF NOT EXISTS a bigint DEFAULT nextval('seq_4'); +NOTICE: column "a" of relation "seq_test_4" already exists, skipping DROP SEQUENCE seq_4 CASCADE; NOTICE: drop cascades to default value for column a of table seq_test_4 TRUNCATE seq_test_4; @@ -879,7 +881,7 @@ ROLLBACK; -- Show that existing sequence has been renamed and a new sequence with the same name -- created for another type \c - - - :worker_1_port -SELECT seqrelid::regclass, seqtypid::regtype, seqmax, seqmin FROM pg_sequence WHERE seqrelid::regclass::text like '%sequence_rollback%' ORDER BY 1,2; +SELECT seqrelid::regclass, seqtypid::regtype, seqmax, seqmin FROM pg_sequence WHERE seqrelid::regclass::text in ('sequence_rollback', '"sequence_rollback(citus_backup_0)"') ORDER BY 1,2; seqrelid | seqtypid | seqmax | seqmin --------------------------------------------------------------------- "sequence_rollback(citus_backup_0)" | integer | 2147483647 | 1 diff --git a/src/test/regress/sql/multi_sequence_default.sql b/src/test/regress/sql/multi_sequence_default.sql index b41aba577..422a97df4 100644 --- a/src/test/regress/sql/multi_sequence_default.sql +++ b/src/test/regress/sql/multi_sequence_default.sql @@ -65,6 +65,7 @@ CREATE TABLE seq_test_4 (x int, y int); SELECT create_distributed_table('seq_test_4','x'); CREATE SEQUENCE seq_4; ALTER TABLE seq_test_4 ADD COLUMN a bigint DEFAULT nextval('seq_4'); +ALTER TABLE seq_test_4 ADD COLUMN IF NOT EXISTS a bigint DEFAULT nextval('seq_4'); DROP SEQUENCE seq_4 CASCADE; TRUNCATE seq_test_4; CREATE SEQUENCE seq_4; @@ -440,7 +441,7 @@ ROLLBACK; -- Show that existing sequence has been renamed and a new sequence with the same name -- created for another type \c - - - :worker_1_port -SELECT seqrelid::regclass, seqtypid::regtype, seqmax, seqmin FROM pg_sequence WHERE seqrelid::regclass::text like '%sequence_rollback%' ORDER BY 1,2; +SELECT seqrelid::regclass, seqtypid::regtype, seqmax, seqmin FROM pg_sequence WHERE seqrelid::regclass::text in ('sequence_rollback', '"sequence_rollback(citus_backup_0)"') ORDER BY 1,2; \c - - - :master_port From 6365f47b57079a0aade822e662408c5e119c1df2 Mon Sep 17 00:00:00 2001 From: Onur Tirtir Date: Tue, 4 Jul 2023 17:48:51 +0300 Subject: [PATCH 6/7] Properly handle index storage options for ADD CONSTRAINT / COLUMN --- .../deparser/deparse_table_stmts.c | 18 ++++++++++++++++ .../multi_alter_table_add_constraints.out | 21 +++++++++++++++++++ .../sql/multi_alter_table_add_constraints.sql | 13 ++++++++++++ 3 files changed, 52 insertions(+) diff --git a/src/backend/distributed/deparser/deparse_table_stmts.c b/src/backend/distributed/deparser/deparse_table_stmts.c index c58ad0798..d2816cd9a 100644 --- a/src/backend/distributed/deparser/deparse_table_stmts.c +++ b/src/backend/distributed/deparser/deparse_table_stmts.c @@ -11,6 +11,7 @@ */ #include "postgres.h" +#include "commands/defrem.h" #include "distributed/commands.h" #include "distributed/deparser.h" #include "distributed/version_compat.h" @@ -205,6 +206,23 @@ AppendAlterTableCmdConstraint(StringInfo buf, Constraint *constraint, AppendColumnNameList(buf, constraint->including); } + + if (constraint->options != NIL) + { + appendStringInfoString(buf, " WITH("); + + ListCell *defListCell; + foreach(defListCell, constraint->options) + { + DefElem *def = (DefElem *) lfirst(defListCell); + + bool first = (defListCell == list_head(constraint->options)); + appendStringInfo(buf, "%s%s=%s", first ? "" : ",", + def->defname, defGetString(def)); + } + + appendStringInfoChar(buf, ')'); + } } else if (constraint->contype == CONSTR_EXCLUSION) { diff --git a/src/test/regress/expected/multi_alter_table_add_constraints.out b/src/test/regress/expected/multi_alter_table_add_constraints.out index 64c6e3667..a76d34d03 100644 --- a/src/test/regress/expected/multi_alter_table_add_constraints.out +++ b/src/test/regress/expected/multi_alter_table_add_constraints.out @@ -671,6 +671,27 @@ SELECT create_distributed_table('alter_add_unique', 'x'); ALTER TABLE alter_add_unique ADD CONSTRAINT unique_constraint_test UNIQUE USING INDEX alter_unique_idx; NOTICE: ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index "alter_unique_idx" to "unique_constraint_test" ALTER TABLE alter_add_unique DROP CONSTRAINT unique_constraint_test; +CREATE TABLE unique_test_table_single_shard(id int, name varchar(20)); +SELECT create_distributed_table('unique_test_table_single_shard', 'id', shard_count=>1); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE unique_test_table_single_shard ADD UNIQUE(id, name) WITH (fillfactor=20); +SELECT (groupid = 0) AS is_coordinator, result FROM run_command_on_all_nodes( + $$SELECT get_index_defs FROM get_index_defs('sc3', 'unique_test_table_single_shard')$$ +) +JOIN pg_dist_node USING (nodeid) +ORDER BY is_coordinator DESC, result; + is_coordinator | result +--------------------------------------------------------------------- + t | [{"indexdefs": ["CREATE UNIQUE INDEX unique_test_table_single_shard_id_name_key ON sc3.unique_test_table_single_shard USING btree (id, name) WITH (fillfactor='20')"], "indexnames": ["unique_test_table_single_shard_id_name_key"]}] + f | [{"indexdefs": ["CREATE UNIQUE INDEX unique_test_table_single_shard_id_name_key ON sc3.unique_test_table_single_shard USING btree (id, name) WITH (fillfactor='20')", "CREATE UNIQUE INDEX unique_test_table_single_shard_id_name_key_1450242 ON sc3.unique_test_table_single_shard_1450242 USING btree (id, name) WITH (fillfactor='20')"], "indexnames": ["unique_test_table_single_shard_id_name_key", "unique_test_table_single_shard_id_name_key_1450242"]}] + f | [{"indexdefs": ["CREATE UNIQUE INDEX unique_test_table_single_shard_id_name_key ON sc3.unique_test_table_single_shard USING btree (id, name) WITH (fillfactor='20')", "CREATE UNIQUE INDEX unique_test_table_single_shard_id_name_key_1450242 ON sc3.unique_test_table_single_shard_1450242 USING btree (id, name) WITH (fillfactor='20')"], "indexnames": ["unique_test_table_single_shard_id_name_key", "unique_test_table_single_shard_id_name_key_1450242"]}] +(3 rows) + +DROP TABLE unique_test_table_single_shard; SET search_path TO 'public'; DROP SCHEMA sc1 CASCADE; NOTICE: drop cascades to table sc1.alter_add_prim_key diff --git a/src/test/regress/sql/multi_alter_table_add_constraints.sql b/src/test/regress/sql/multi_alter_table_add_constraints.sql index a82bb64ca..dfc31dc51 100644 --- a/src/test/regress/sql/multi_alter_table_add_constraints.sql +++ b/src/test/regress/sql/multi_alter_table_add_constraints.sql @@ -563,6 +563,19 @@ SELECT create_distributed_table('alter_add_unique', 'x'); ALTER TABLE alter_add_unique ADD CONSTRAINT unique_constraint_test UNIQUE USING INDEX alter_unique_idx; ALTER TABLE alter_add_unique DROP CONSTRAINT unique_constraint_test; +CREATE TABLE unique_test_table_single_shard(id int, name varchar(20)); +SELECT create_distributed_table('unique_test_table_single_shard', 'id', shard_count=>1); + +ALTER TABLE unique_test_table_single_shard ADD UNIQUE(id, name) WITH (fillfactor=20); + +SELECT (groupid = 0) AS is_coordinator, result FROM run_command_on_all_nodes( + $$SELECT get_index_defs FROM get_index_defs('sc3', 'unique_test_table_single_shard')$$ +) +JOIN pg_dist_node USING (nodeid) +ORDER BY is_coordinator DESC, result; + +DROP TABLE unique_test_table_single_shard; + SET search_path TO 'public'; DROP SCHEMA sc1 CASCADE; From f3cdb6d1bf3365d67f869eb63c6039422c79217d Mon Sep 17 00:00:00 2001 From: Onur Tirtir Date: Mon, 26 Jun 2023 11:00:09 +0300 Subject: [PATCH 7/7] Deparse ALTER TABLE commands if ADD COLUMN is the only subcommand And stabilize multi_alter_table_statements.sql. --- src/backend/distributed/commands/table.c | 99 +++++++-- .../deparser/deparse_table_stmts.c | 196 ++++++++++++++-- .../distributed/relay/relay_event_utility.c | 17 +- .../expected/alter_table_add_column.out | 101 +++++++++ .../regress/expected/citus_local_tables.out | 2 +- .../expected/fkeys_between_local_ref.out | 14 +- .../foreign_key_to_reference_table.out | 5 +- .../isolation_citus_dist_activity.out | 18 +- .../local_shard_utility_command_execution.out | 46 ++-- .../expected/multi_alter_table_statements.out | 210 +++++++++++------- .../expected/multi_row_router_insert.out | 4 +- .../regress/expected/multi_test_helpers.out | 132 +++++++++++ src/test/regress/expected/pg15.out | 50 +++++ ...licate_reference_tables_to_coordinator.out | 2 +- src/test/regress/expected/single_node.out | 8 +- src/test/regress/expected/single_node_0.out | 8 +- src/test/regress/multi_1_schedule | 2 +- .../regress/sql/alter_table_add_column.sql | 73 ++++++ .../regress/sql/fkeys_between_local_ref.sql | 9 +- .../sql/multi_alter_table_statements.sql | 118 +++++++--- src/test/regress/sql/multi_test_helpers.sql | 136 ++++++++++++ src/test/regress/sql/pg15.sql | 32 +++ 22 files changed, 1068 insertions(+), 214 deletions(-) create mode 100644 src/test/regress/expected/alter_table_add_column.out create mode 100644 src/test/regress/sql/alter_table_add_column.sql diff --git a/src/backend/distributed/commands/table.c b/src/backend/distributed/commands/table.c index f6502c477..a4b1d2ad1 100644 --- a/src/backend/distributed/commands/table.c +++ b/src/backend/distributed/commands/table.c @@ -76,6 +76,8 @@ static void DistributePartitionUsingParent(Oid parentRelationId, static void ErrorIfMultiLevelPartitioning(Oid parentRelationId, Oid partitionRelationId); static void ErrorIfAttachCitusTableToPgLocalTable(Oid parentRelationId, Oid partitionRelationId); +static bool DeparserSupportsAlterTableAddColumn(AlterTableStmt *alterTableStatement, + AlterTableCmd *addColumnSubCommand); static bool ATDefinesFKeyBetweenPostgresAndCitusLocalOrRef( AlterTableStmt *alterTableStatement); static bool ShouldMarkConnectedRelationsNotAutoConverted(Oid leftRelationId, @@ -1281,6 +1283,7 @@ PreprocessAlterTableStmt(Node *node, const char *alterTableCommand, * we also set skip_validation to true to prevent PostgreSQL to verify validity * of the foreign constraint in master. Validity will be checked in workers * anyway. + * - an ADD COLUMN .. that is the only subcommand in the list OR * - an ADD COLUMN .. DEFAULT nextval('..') OR * an ADD COLUMN .. SERIAL pseudo-type OR * an ALTER COLUMN .. SET DEFAULT nextval('..'). If there is we set @@ -1410,13 +1413,6 @@ PreprocessAlterTableStmt(Node *node, const char *alterTableCommand, } else if (alterTableType == AT_AddColumn) { - /* - * TODO: This code path is nothing beneficial since we do not - * support ALTER TABLE %s ADD COLUMN %s [constraint] for foreign keys. - * However, the code is kept in case we fix the constraint - * creation without a name and allow foreign key creation with the mentioned - * command. - */ ColumnDef *columnDefinition = (ColumnDef *) command->def; List *columnConstraints = columnDefinition->constraints; @@ -1440,12 +1436,36 @@ PreprocessAlterTableStmt(Node *node, const char *alterTableCommand, } } + if (DeparserSupportsAlterTableAddColumn(alterTableStatement, command)) + { + deparseAT = true; + + constraint = NULL; + foreach_ptr(constraint, columnConstraints) + { + if (ConstrTypeCitusCanDefaultName(constraint->contype)) + { + PrepareAlterTableStmtForConstraint(alterTableStatement, + leftRelationId, + constraint); + } + } + + /* + * Copy the constraints to the new subcommand because now we + * might have assigned names to some of them. + */ + ColumnDef *newColumnDef = (ColumnDef *) newCmd->def; + newColumnDef->constraints = copyObject(columnConstraints); + } + /* * We check for ADD COLUMN .. DEFAULT expr * if expr contains nextval('user_defined_seq') * we should deparse the statement */ constraint = NULL; + int constraintIdx = 0; foreach_ptr(constraint, columnConstraints) { if (constraint->contype == CONSTR_DEFAULT) @@ -1461,14 +1481,19 @@ PreprocessAlterTableStmt(Node *node, const char *alterTableCommand, deparseAT = true; useInitialDDLCommandString = false; - /* the new column definition will have no constraint */ - ColumnDef *newColDef = copyObject(columnDefinition); - newColDef->constraints = NULL; - - newCmd->def = (Node *) newColDef; + /* drop the default expression from new subcomand */ + ColumnDef *newColumnDef = (ColumnDef *) newCmd->def; + newColumnDef->constraints = + list_delete_nth_cell(newColumnDef->constraints, + constraintIdx); } } + + /* there can only be one DEFAULT constraint that can be used per column */ + break; } + + constraintIdx++; } @@ -1652,6 +1677,49 @@ PreprocessAlterTableStmt(Node *node, const char *alterTableCommand, } +/* + * DeparserSupportsAlterTableAddColumn returns true if it's safe to deparse + * the given ALTER TABLE statement that is known to contain given ADD COLUMN + * subcommand. + */ +static bool +DeparserSupportsAlterTableAddColumn(AlterTableStmt *alterTableStatement, + AlterTableCmd *addColumnSubCommand) +{ + /* + * We support deparsing for ADD COLUMN only of it's the only + * subcommand. + */ + if (list_length(alterTableStatement->cmds) == 1 && + alterTableStatement->objtype == OBJECT_TABLE) + { + ColumnDef *columnDefinition = (ColumnDef *) addColumnSubCommand->def; + Constraint *constraint = NULL; + foreach_ptr(constraint, columnDefinition->constraints) + { + if (constraint->contype == CONSTR_CHECK) + { + /* + * Given that we're in the preprocess, any reference to the + * column that we're adding would break the deparser. This + * can only be the case with CHECK constraints. For this + * reason, we skip deparsing the command and fall back to + * legacy behavior that we follow for ADD COLUMN subcommands. + * + * For other constraint types, we prepare the constraint to + * make sure that we can deparse it. + */ + return false; + } + } + + return true; + } + + return false; +} + + /* * ATDefinesFKeyBetweenPostgresAndCitusLocalOrRef returns true if given * alter table command defines foreign key between a postgres table and a @@ -3693,13 +3761,6 @@ SetupExecutionModeForAlterTable(Oid relationId, AlterTableCmd *command) } else if (alterTableType == AT_AddColumn) { - /* - * TODO: This code path will never be executed since we do not - * support foreign constraint creation via - * ALTER TABLE %s ADD COLUMN %s [constraint]. However, the code - * is kept in case we fix the constraint creation without a name - * and allow foreign key creation with the mentioned command. - */ ColumnDef *columnDefinition = (ColumnDef *) command->def; List *columnConstraints = columnDefinition->constraints; diff --git a/src/backend/distributed/deparser/deparse_table_stmts.c b/src/backend/distributed/deparser/deparse_table_stmts.c index d2816cd9a..f2b79abd4 100644 --- a/src/backend/distributed/deparser/deparse_table_stmts.c +++ b/src/backend/distributed/deparser/deparse_table_stmts.c @@ -11,6 +11,7 @@ */ #include "postgres.h" +#include "catalog/heap.h" #include "commands/defrem.h" #include "distributed/commands.h" #include "distributed/deparser.h" @@ -31,7 +32,8 @@ static void AppendAlterTableSchemaStmt(StringInfo buf, AlterObjectSchemaStmt *st static void AppendAlterTableStmt(StringInfo buf, AlterTableStmt *stmt); static void AppendAlterTableCmd(StringInfo buf, AlterTableCmd *alterTableCmd, AlterTableStmt *stmt); -static void AppendAlterTableCmdAddColumn(StringInfo buf, AlterTableCmd *alterTableCmd); +static void AppendAlterTableCmdAddColumn(StringInfo buf, AlterTableCmd *alterTableCmd, + AlterTableStmt *stmt); static void AppendAlterTableCmdDropConstraint(StringInfo buf, AlterTableCmd *alterTableCmd); @@ -144,14 +146,14 @@ AppendColumnNameList(StringInfo buf, List *columns) /* * AppendAlterTableCmdConstraint builds a string required to create given - * constraint as part of an ADD CONSTRAINT subcommand and appends it to - * the buf. + * constraint as part of an ADD CONSTRAINT or an ADD COLUMN subcommand, + * and appends it to the buf. */ static void AppendAlterTableCmdConstraint(StringInfo buf, Constraint *constraint, AlterTableStmt *stmt, AlterTableType subtype) { - if (subtype != AT_AddConstraint) + if (subtype != AT_AddConstraint && subtype != AT_AddColumn) { ereport(ERROR, (errmsg("Unsupported alter table subtype: %d", (int) subtype))); } @@ -167,6 +169,10 @@ AppendAlterTableCmdConstraint(StringInfo buf, Constraint *constraint, { appendStringInfoString(buf, " ADD CONSTRAINT "); } + else + { + appendStringInfoString(buf, " CONSTRAINT "); + } appendStringInfo(buf, "%s ", quote_identifier(constraint->conname)); @@ -218,7 +224,8 @@ AppendAlterTableCmdConstraint(StringInfo buf, Constraint *constraint, bool first = (defListCell == list_head(constraint->options)); appendStringInfo(buf, "%s%s=%s", first ? "" : ",", - def->defname, defGetString(def)); + quote_identifier(def->defname), + quote_literal_cstr(defGetString(def))); } appendStringInfoChar(buf, ')'); @@ -271,6 +278,18 @@ AppendAlterTableCmdConstraint(StringInfo buf, Constraint *constraint, } else if (constraint->contype == CONSTR_CHECK) { + if (subtype == AT_AddColumn) + { + /* + * Preprocess should've rejected deparsing such an ALTER TABLE + * command but be on the safe side. + */ + ereport(ERROR, (errmsg("cannot add check constraint to column by " + "using ADD COLUMN command"), + errhint("Consider using ALTER TABLE ... ADD CONSTRAINT " + "... CHECK command after adding the column"))); + } + LOCKMODE lockmode = AlterTableGetLockLevel(stmt->cmds); Oid leftRelationId = AlterTableLookupRelation(stmt, lockmode); @@ -416,12 +435,29 @@ AppendAlterTableCmdConstraint(StringInfo buf, Constraint *constraint, /* * For ADD CONSTRAINT subcommand, FOREIGN KEY and CHECK constraints migth * have NOT VALID option. + * + * Note that skip_validation might be true for an ADD COLUMN too but this + * is not because Postgres supports this but because Citus sets this flag + * to true for foreign key constraints added via ADD COLUMN. So we don't + * check for skip_validation for ADD COLUMN subcommand. */ if (subtype == AT_AddConstraint && constraint->skip_validation) { appendStringInfoString(buf, " NOT VALID "); } + if (subtype == AT_AddColumn && + (constraint->deferrable || constraint->initdeferred)) + { + /* + * For ADD COLUMN subcommand, the fact that whether given constraint + * is deferrable or initially deferred is indicated by another Constraint + * object, not via deferrable / initdeferred fields. + */ + ereport(ERROR, (errmsg("unexpected value set for deferrable/initdeferred " + "field for an ADD COLUMN subcommand"))); + } + if (constraint->deferrable) { appendStringInfoString(buf, " DEFERRABLE"); @@ -446,7 +482,7 @@ AppendAlterTableCmd(StringInfo buf, AlterTableCmd *alterTableCmd, AlterTableStmt { case AT_AddColumn: { - AppendAlterTableCmdAddColumn(buf, alterTableCmd); + AppendAlterTableCmdAddColumn(buf, alterTableCmd, stmt); break; } @@ -482,15 +518,72 @@ AppendAlterTableCmd(StringInfo buf, AlterTableCmd *alterTableCmd, AlterTableStmt } +/* + * GeneratedWhenStr returns the char representation of given generated_when + * value. + */ +static const char * +GeneratedWhenStr(char generatedWhen) +{ + switch (generatedWhen) + { + case 'a': + { + return "ALWAYS"; + } + + case 'd': + { + return "BY DEFAULT"; + } + + default: + ereport(ERROR, (errmsg("unrecognized generated_when: %d", + generatedWhen))); + } +} + + +/* + * DeparseRawExprForColumnDefault returns string representation of given + * rawExpr based on given column type information. + */ +static char * +DeparseRawExprForColumnDefault(Oid relationId, Oid columnTypeId, int32 columnTypeMod, + char *columnName, char attgenerated, Node *rawExpr) +{ + ParseState *pstate = make_parsestate(NULL); + Relation relation = RelationIdGetRelation(relationId); + AddRangeTableEntryToQueryCompat(pstate, relation); + + Node *defaultExpr = cookDefault(pstate, rawExpr, + columnTypeId, columnTypeMod, + columnName, attgenerated); + + List *deparseContext = deparse_context_for(get_rel_name(relationId), relationId); + + PushOverrideEmptySearchPath(CurrentMemoryContext); + char *defaultExprStr = deparse_expression(defaultExpr, deparseContext, false, false); + PopOverrideSearchPath(); + + RelationClose(relation); + + return defaultExprStr; +} + + /* * AppendAlterTableCmd builds and appends to the given buffer an AT_AddColumn command * from given AlterTableCmd object in the form ADD COLUMN ... */ static void -AppendAlterTableCmdAddColumn(StringInfo buf, AlterTableCmd *alterTableCmd) +AppendAlterTableCmdAddColumn(StringInfo buf, AlterTableCmd *alterTableCmd, + AlterTableStmt *stmt) { Assert(alterTableCmd->subtype == AT_AddColumn); + Oid relationId = AlterTableLookupRelation(stmt, NoLock); + appendStringInfoString(buf, " ADD COLUMN "); if (alterTableCmd->missing_ok) @@ -500,15 +593,6 @@ AppendAlterTableCmdAddColumn(StringInfo buf, AlterTableCmd *alterTableCmd) ColumnDef *columnDefinition = (ColumnDef *) alterTableCmd->def; - /* - * the way we use the deparser now, constraints are always NULL - * adding this check for ColumnDef consistency - */ - if (columnDefinition->constraints != NULL) - { - ereport(ERROR, (errmsg("Constraints are not supported for AT_AddColumn"))); - } - if (columnDefinition->colname) { appendStringInfo(buf, "%s ", quote_identifier(columnDefinition->colname)); @@ -520,23 +604,87 @@ AppendAlterTableCmdAddColumn(StringInfo buf, AlterTableCmd *alterTableCmd) typenameTypeIdAndMod(NULL, columnDefinition->typeName, &typeOid, &typmod); appendStringInfo(buf, "%s", format_type_extended(typeOid, typmod, formatFlags)); - if (columnDefinition->is_not_null) + + if (columnDefinition->compression) { - appendStringInfoString(buf, " NOT NULL"); + appendStringInfo(buf, " COMPRESSION %s", + quote_identifier(columnDefinition->compression)); } - /* - * the way we use the deparser now, collation is never used - * since the data type of columns that use sequences for default - * are only int,smallint and bigint (never text, varchar, char) - * Adding this part only for ColumnDef consistency - */ Oid collationOid = GetColumnDefCollation(NULL, columnDefinition, typeOid); if (OidIsValid(collationOid)) { const char *identifier = FormatCollateBEQualified(collationOid); appendStringInfo(buf, " COLLATE %s", identifier); } + + ListCell *constraintCell = NULL; + foreach(constraintCell, columnDefinition->constraints) + { + Constraint *constraint = (Constraint *) lfirst(constraintCell); + + if (constraint->contype == CONSTR_NOTNULL) + { + appendStringInfoString(buf, " NOT NULL"); + } + else if (constraint->contype == CONSTR_DEFAULT) + { + char attgenerated = '\0'; + appendStringInfo(buf, " DEFAULT %s", + DeparseRawExprForColumnDefault(relationId, typeOid, typmod, + columnDefinition->colname, + attgenerated, + constraint->raw_expr)); + } + else if (constraint->contype == CONSTR_IDENTITY) + { + /* + * Citus doesn't support adding identity columns via ALTER TABLE, + * so we don't bother teaching the deparser about them. + */ + ereport(ERROR, (errmsg("unexpectedly found identity column " + "definition in ALTER TABLE command"))); + } + else if (constraint->contype == CONSTR_GENERATED) + { + char attgenerated = 's'; + appendStringInfo(buf, " GENERATED %s AS (%s) STORED", + GeneratedWhenStr(constraint->generated_when), + DeparseRawExprForColumnDefault(relationId, typeOid, typmod, + columnDefinition->colname, + attgenerated, + constraint->raw_expr)); + } + else if (constraint->contype == CONSTR_CHECK || + constraint->contype == CONSTR_PRIMARY || + constraint->contype == CONSTR_UNIQUE || + constraint->contype == CONSTR_EXCLUSION || + constraint->contype == CONSTR_FOREIGN) + { + AppendAlterTableCmdConstraint(buf, constraint, stmt, AT_AddColumn); + } + else if (constraint->contype == CONSTR_ATTR_DEFERRABLE) + { + appendStringInfoString(buf, " DEFERRABLE"); + } + else if (constraint->contype == CONSTR_ATTR_NOT_DEFERRABLE) + { + appendStringInfoString(buf, " NOT DEFERRABLE"); + } + else if (constraint->contype == CONSTR_ATTR_DEFERRED) + { + appendStringInfoString(buf, " INITIALLY DEFERRED"); + } + else if (constraint->contype == CONSTR_ATTR_IMMEDIATE) + { + appendStringInfoString(buf, " INITIALLY IMMEDIATE"); + } + else + { + ereport(ERROR, (errmsg("unsupported constraint type"), + errdetail("constraint type: %d", constraint->contype))); + } + } } diff --git a/src/backend/distributed/relay/relay_event_utility.c b/src/backend/distributed/relay/relay_event_utility.c index 6924b8e8d..3284ead11 100644 --- a/src/backend/distributed/relay/relay_event_utility.c +++ b/src/backend/distributed/relay/relay_event_utility.c @@ -156,6 +156,16 @@ RelayEventExtendNames(Node *parseTree, char *schemaName, uint64 shardId) RelayEventExtendConstraintAndIndexNames(alterTableStmt, constraint, shardId); } + else if (command->subtype == AT_AddColumn) + { + ColumnDef *columnDefinition = (ColumnDef *) command->def; + Constraint *constraint = NULL; + foreach_ptr(constraint, columnDefinition->constraints) + { + RelayEventExtendConstraintAndIndexNames(alterTableStmt, + constraint, shardId); + } + } else if (command->subtype == AT_DropConstraint || command->subtype == AT_ValidateConstraint) { @@ -677,13 +687,6 @@ RelayEventExtendNamesForInterShardCommands(Node *parseTree, uint64 leftShardId, } else if (command->subtype == AT_AddColumn) { - /* - * TODO: This code path will never be executed since we do not - * support foreign constraint creation via - * ALTER TABLE %s ADD COLUMN %s [constraint]. However, the code - * is kept in case we fix the constraint creation without a name - * and allow foreign key creation with the mentioned command. - */ ColumnDef *columnDefinition = (ColumnDef *) command->def; List *columnConstraints = columnDefinition->constraints; diff --git a/src/test/regress/expected/alter_table_add_column.out b/src/test/regress/expected/alter_table_add_column.out new file mode 100644 index 000000000..00b15dcdb --- /dev/null +++ b/src/test/regress/expected/alter_table_add_column.out @@ -0,0 +1,101 @@ +CREATE SCHEMA alter_table_add_column; +SET search_path TO alter_table_add_column; +SET citus.next_shard_id TO 1830000; +SET citus.shard_replication_factor TO 1; +SET client_min_messages TO NOTICE; +CREATE TABLE referenced (int_col integer PRIMARY KEY); +CREATE TABLE referencing (text_col text); +SELECT create_distributed_table('referenced', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('referencing', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE SCHEMA alter_table_add_column_other_schema; +CREATE OR REPLACE FUNCTION alter_table_add_column_other_schema.my_random(numeric) + RETURNS numeric AS +$$ +BEGIN + RETURN 7 * $1; +END; +$$ +LANGUAGE plpgsql IMMUTABLE; +CREATE COLLATION caseinsensitive ( + provider = icu, + locale = 'und-u-ks-level2' +); +CREATE TYPE "simple_!\'custom_type" AS (a integer, b integer); +ALTER TABLE referencing ADD COLUMN test_1 integer DEFAULT (alter_table_add_column_other_schema.my_random(7) + random() + 5) NOT NULL CONSTRAINT fkey REFERENCES referenced(int_col) ON UPDATE SET DEFAULT ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE referencing ADD COLUMN test_2 integer UNIQUE REFERENCES referenced(int_col) ON UPDATE CASCADE ON DELETE SET DEFAULT NOT DEFERRABLE INITIALLY IMMEDIATE; +ALTER TABLE referencing ADD COLUMN test_3 integer GENERATED ALWAYS AS (test_1 * alter_table_add_column_other_schema.my_random(1)) STORED UNIQUE REFERENCES referenced(int_col) MATCH FULL; +ALTER TABLE referencing ADD COLUMN test_4 integer PRIMARY KEY WITH (fillfactor=70) NOT NULL REFERENCES referenced(int_col) MATCH SIMPLE ON UPDATE CASCADE ON DELETE SET DEFAULT; +ALTER TABLE referencing ADD COLUMN test_5 integer CONSTRAINT unique_c UNIQUE WITH (fillfactor=50); +ALTER TABLE referencing ADD COLUMN test_6 text COMPRESSION pglz COLLATE caseinsensitive NOT NULL; +ALTER TABLE referencing ADD COLUMN "test_\'!7" "simple_!\'custom_type"; +-- we give up deparsing ALTER TABLE command if it needs to create a check constraint, and we fallback to legacy behavior +ALTER TABLE referencing ADD COLUMN test_8 integer CHECK (test_8 > 0); +ERROR: cannot execute ADD COLUMN command with PRIMARY KEY, UNIQUE, FOREIGN and CHECK constraints +DETAIL: Adding a column with a constraint in one command is not supported because all constraints in Citus must have explicit names +HINT: You can issue each command separately such as ALTER TABLE referencing ADD COLUMN test_8 data_type; ALTER TABLE referencing ADD CONSTRAINT constraint_name CHECK (check_expression); +ALTER TABLE referencing ADD COLUMN test_8 integer CONSTRAINT check_test_8 CHECK (test_8 > 0); +-- try to add test_6 again, but with IF NOT EXISTS +ALTER TABLE referencing ADD COLUMN IF NOT EXISTS test_6 text; +NOTICE: column "test_6" of relation "referencing" already exists, skipping +ALTER TABLE referencing ADD COLUMN IF NOT EXISTS test_6 integer; +NOTICE: column "test_6" of relation "referencing" already exists, skipping +SELECT (groupid = 0) AS is_coordinator, result FROM run_command_on_all_nodes( + $$SELECT get_grouped_fkey_constraints FROM get_grouped_fkey_constraints('alter_table_add_column.referencing')$$ +) +JOIN pg_dist_node USING (nodeid) +ORDER BY is_coordinator DESC, result; + is_coordinator | result +--------------------------------------------------------------------- + t | [{"deferred": true, "deferable": true, "on_delete": "c", "on_update": "d", "match_type": "s", "constraint_names": ["fkey"], "referenced_tables": ["alter_table_add_column.referenced"], "referenced_columns": ["int_col"], "referencing_tables": ["alter_table_add_column.referencing"], "referencing_columns": ["test_1"], "referencing_columns_set_null_or_default": null}, {"deferred": false, "deferable": false, "on_delete": "d", "on_update": "c", "match_type": "s", "constraint_names": ["referencing__fkey"], "referenced_tables": ["alter_table_add_column.referenced"], "referenced_columns": ["int_col"], "referencing_tables": ["alter_table_add_column.referencing"], "referencing_columns": ["test_2"], "referencing_columns_set_null_or_default": null}, {"deferred": false, "deferable": false, "on_delete": "a", "on_update": "a", "match_type": "f", "constraint_names": ["referencing__fkey1"], "referenced_tables": ["alter_table_add_column.referenced"], "referenced_columns": ["int_col"], "referencing_tables": ["alter_table_add_column.referencing"], "referencing_columns": ["test_3"], "referencing_columns_set_null_or_default": null}, {"deferred": false, "deferable": false, "on_delete": "d", "on_update": "c", "match_type": "s", "constraint_names": ["referencing__fkey2"], "referenced_tables": ["alter_table_add_column.referenced"], "referenced_columns": ["int_col"], "referencing_tables": ["alter_table_add_column.referencing"], "referencing_columns": ["test_4"], "referencing_columns_set_null_or_default": null}] + f | [{"deferred": true, "deferable": true, "on_delete": "c", "on_update": "d", "match_type": "s", "constraint_names": ["fkey", "fkey_xxxxxxx"], "referenced_tables": ["alter_table_add_column.referenced", "alter_table_add_column.referenced_1830000"], "referenced_columns": ["int_col"], "referencing_tables": ["alter_table_add_column.referencing", "alter_table_add_column.referencing_1830001"], "referencing_columns": ["test_1"], "referencing_columns_set_null_or_default": null}, {"deferred": false, "deferable": false, "on_delete": "d", "on_update": "c", "match_type": "s", "constraint_names": ["referencing__fkey", "referencing__fkey_1830001"], "referenced_tables": ["alter_table_add_column.referenced", "alter_table_add_column.referenced_1830000"], "referenced_columns": ["int_col"], "referencing_tables": ["alter_table_add_column.referencing", "alter_table_add_column.referencing_1830001"], "referencing_columns": ["test_2"], "referencing_columns_set_null_or_default": null}, {"deferred": false, "deferable": false, "on_delete": "a", "on_update": "a", "match_type": "f", "constraint_names": ["referencing__fkey1", "referencing__fkey1_1830001"], "referenced_tables": ["alter_table_add_column.referenced", "alter_table_add_column.referenced_1830000"], "referenced_columns": ["int_col"], "referencing_tables": ["alter_table_add_column.referencing", "alter_table_add_column.referencing_1830001"], "referencing_columns": ["test_3"], "referencing_columns_set_null_or_default": null}, {"deferred": false, "deferable": false, "on_delete": "d", "on_update": "c", "match_type": "s", "constraint_names": ["referencing__fkey2", "referencing__fkey2_1830001"], "referenced_tables": ["alter_table_add_column.referenced", "alter_table_add_column.referenced_1830000"], "referenced_columns": ["int_col"], "referencing_tables": ["alter_table_add_column.referencing", "alter_table_add_column.referencing_1830001"], "referencing_columns": ["test_4"], "referencing_columns_set_null_or_default": null}] + f | [{"deferred": true, "deferable": true, "on_delete": "c", "on_update": "d", "match_type": "s", "constraint_names": ["fkey"], "referenced_tables": ["alter_table_add_column.referenced"], "referenced_columns": ["int_col"], "referencing_tables": ["alter_table_add_column.referencing"], "referencing_columns": ["test_1"], "referencing_columns_set_null_or_default": null}, {"deferred": false, "deferable": false, "on_delete": "d", "on_update": "c", "match_type": "s", "constraint_names": ["referencing__fkey"], "referenced_tables": ["alter_table_add_column.referenced"], "referenced_columns": ["int_col"], "referencing_tables": ["alter_table_add_column.referencing"], "referencing_columns": ["test_2"], "referencing_columns_set_null_or_default": null}, {"deferred": false, "deferable": false, "on_delete": "a", "on_update": "a", "match_type": "f", "constraint_names": ["referencing__fkey1"], "referenced_tables": ["alter_table_add_column.referenced"], "referenced_columns": ["int_col"], "referencing_tables": ["alter_table_add_column.referencing"], "referencing_columns": ["test_3"], "referencing_columns_set_null_or_default": null}, {"deferred": false, "deferable": false, "on_delete": "d", "on_update": "c", "match_type": "s", "constraint_names": ["referencing__fkey2"], "referenced_tables": ["alter_table_add_column.referenced"], "referenced_columns": ["int_col"], "referencing_tables": ["alter_table_add_column.referencing"], "referencing_columns": ["test_4"], "referencing_columns_set_null_or_default": null}] +(3 rows) + +SELECT (groupid = 0) AS is_coordinator, result FROM run_command_on_all_nodes( + $$SELECT get_index_defs FROM get_index_defs('alter_table_add_column', 'referencing')$$ +) +JOIN pg_dist_node USING (nodeid) +ORDER BY is_coordinator DESC, result; + is_coordinator | result +--------------------------------------------------------------------- + t | [{"indexdefs": ["CREATE UNIQUE INDEX referencing__key ON alter_table_add_column.referencing USING btree (test_2)"], "indexnames": ["referencing__key"]}, {"indexdefs": ["CREATE UNIQUE INDEX referencing__key1 ON alter_table_add_column.referencing USING btree (test_3)"], "indexnames": ["referencing__key1"]}, {"indexdefs": ["CREATE UNIQUE INDEX referencing_pkey ON alter_table_add_column.referencing USING btree (test_4) WITH (fillfactor='70')"], "indexnames": ["referencing_pkey"]}, {"indexdefs": ["CREATE UNIQUE INDEX unique_c ON alter_table_add_column.referencing USING btree (test_5) WITH (fillfactor='50')"], "indexnames": ["unique_c"]}] + f | [{"indexdefs": ["CREATE UNIQUE INDEX referencing__key ON alter_table_add_column.referencing USING btree (test_2)", "CREATE UNIQUE INDEX referencing__key_1830001 ON alter_table_add_column.referencing_1830001 USING btree (test_2)"], "indexnames": ["referencing__key", "referencing__key_1830001"]}, {"indexdefs": ["CREATE UNIQUE INDEX referencing__key1 ON alter_table_add_column.referencing USING btree (test_3)", "CREATE UNIQUE INDEX referencing__key1_1830001 ON alter_table_add_column.referencing_1830001 USING btree (test_3)"], "indexnames": ["referencing__key1", "referencing__key1_1830001"]}, {"indexdefs": ["CREATE UNIQUE INDEX referencing_pkey ON alter_table_add_column.referencing USING btree (test_4) WITH (fillfactor='70')", "CREATE UNIQUE INDEX referencing_pkey_1830001 ON alter_table_add_column.referencing_1830001 USING btree (test_4) WITH (fillfactor='70')"], "indexnames": ["referencing_pkey", "referencing_pkey_1830001"]}, {"indexdefs": ["CREATE UNIQUE INDEX unique_c ON alter_table_add_column.referencing USING btree (test_5) WITH (fillfactor='50')", "CREATE UNIQUE INDEX unique_c_1830001 ON alter_table_add_column.referencing_1830001 USING btree (test_5) WITH (fillfactor='50')"], "indexnames": ["unique_c", "unique_c_1830001"]}] + f | [{"indexdefs": ["CREATE UNIQUE INDEX referencing__key ON alter_table_add_column.referencing USING btree (test_2)"], "indexnames": ["referencing__key"]}, {"indexdefs": ["CREATE UNIQUE INDEX referencing__key1 ON alter_table_add_column.referencing USING btree (test_3)"], "indexnames": ["referencing__key1"]}, {"indexdefs": ["CREATE UNIQUE INDEX referencing_pkey ON alter_table_add_column.referencing USING btree (test_4) WITH (fillfactor='70')"], "indexnames": ["referencing_pkey"]}, {"indexdefs": ["CREATE UNIQUE INDEX unique_c ON alter_table_add_column.referencing USING btree (test_5) WITH (fillfactor='50')"], "indexnames": ["unique_c"]}] +(3 rows) + +SELECT (groupid = 0) AS is_coordinator, result FROM run_command_on_all_nodes( + $$SELECT get_column_defaults FROM get_column_defaults('alter_table_add_column', 'referencing')$$ +) +JOIN pg_dist_node USING (nodeid) +ORDER BY is_coordinator DESC, result; + is_coordinator | result +--------------------------------------------------------------------- + t | [{"column_name": "test_1", "column_default": "(((alter_table_add_column_other_schema.my_random((7)::numeric))::double precision + random()) + (5)::double precision)", "generation_expression": null}, {"column_name": "test_3", "column_default": null, "generation_expression": "((test_1)::numeric * alter_table_add_column_other_schema.my_random((1)::numeric))"}] + f | [{"column_name": "test_1", "column_default": "(((alter_table_add_column_other_schema.my_random((7)::numeric))::double precision + random()) + (5)::double precision)", "generation_expression": null}, {"column_name": "test_3", "column_default": null, "generation_expression": "((test_1)::numeric * alter_table_add_column_other_schema.my_random((1)::numeric))"}, {"column_name": "test_3", "column_default": null, "generation_expression": "((test_1)::numeric * alter_table_add_column_other_schema.my_random((1)::numeric))"}] + f | [{"column_name": "test_1", "column_default": "(((alter_table_add_column_other_schema.my_random((7)::numeric))::double precision + random()) + (5)::double precision)", "generation_expression": null}, {"column_name": "test_3", "column_default": null, "generation_expression": "((test_1)::numeric * alter_table_add_column_other_schema.my_random((1)::numeric))"}] +(3 rows) + +SELECT (groupid = 0) AS is_coordinator, result FROM run_command_on_all_nodes( + $$SELECT get_column_attrs FROM get_column_attrs('alter_table_add_column.referencing')$$ +) +JOIN pg_dist_node USING (nodeid) +ORDER BY is_coordinator DESC, result; + is_coordinator | result +--------------------------------------------------------------------- + t | {"relnames": ["alter_table_add_column.referencing"], "column_attrs": [{"not_null": true, "type_name": "int4", "column_name": "test_1", "collation_name": null, "compression_method": ""}, {"not_null": false, "type_name": "int4", "column_name": "test_2", "collation_name": null, "compression_method": ""}, {"not_null": false, "type_name": "int4", "column_name": "test_3", "collation_name": null, "compression_method": ""}, {"not_null": true, "type_name": "int4", "column_name": "test_4", "collation_name": null, "compression_method": ""}, {"not_null": false, "type_name": "int4", "column_name": "test_5", "collation_name": null, "compression_method": ""}, {"not_null": true, "type_name": "text", "column_name": "test_6", "collation_name": "caseinsensitive", "compression_method": "p"}, {"not_null": false, "type_name": "int4", "column_name": "test_8", "collation_name": null, "compression_method": ""}, {"not_null": false, "type_name": "simple_!\\'custom_type", "column_name": "test_\\'!7", "collation_name": null, "compression_method": ""}, {"not_null": false, "type_name": "text", "column_name": "text_col", "collation_name": "default", "compression_method": ""}]} + f | {"relnames": ["alter_table_add_column.referencing"], "column_attrs": [{"not_null": true, "type_name": "int4", "column_name": "test_1", "collation_name": null, "compression_method": ""}, {"not_null": false, "type_name": "int4", "column_name": "test_2", "collation_name": null, "compression_method": ""}, {"not_null": false, "type_name": "int4", "column_name": "test_3", "collation_name": null, "compression_method": ""}, {"not_null": true, "type_name": "int4", "column_name": "test_4", "collation_name": null, "compression_method": ""}, {"not_null": false, "type_name": "int4", "column_name": "test_5", "collation_name": null, "compression_method": ""}, {"not_null": true, "type_name": "text", "column_name": "test_6", "collation_name": "caseinsensitive", "compression_method": "p"}, {"not_null": false, "type_name": "int4", "column_name": "test_8", "collation_name": null, "compression_method": ""}, {"not_null": false, "type_name": "simple_!\\'custom_type", "column_name": "test_\\'!7", "collation_name": null, "compression_method": ""}, {"not_null": false, "type_name": "text", "column_name": "text_col", "collation_name": "default", "compression_method": ""}]} + f | {"relnames": ["alter_table_add_column.referencing_1830001", "alter_table_add_column.referencing"], "column_attrs": [{"not_null": true, "type_name": "int4", "column_name": "test_1", "collation_name": null, "compression_method": ""}, {"not_null": false, "type_name": "int4", "column_name": "test_2", "collation_name": null, "compression_method": ""}, {"not_null": false, "type_name": "int4", "column_name": "test_3", "collation_name": null, "compression_method": ""}, {"not_null": true, "type_name": "int4", "column_name": "test_4", "collation_name": null, "compression_method": ""}, {"not_null": false, "type_name": "int4", "column_name": "test_5", "collation_name": null, "compression_method": ""}, {"not_null": true, "type_name": "text", "column_name": "test_6", "collation_name": "caseinsensitive", "compression_method": "p"}, {"not_null": false, "type_name": "int4", "column_name": "test_8", "collation_name": null, "compression_method": ""}, {"not_null": false, "type_name": "simple_!\\'custom_type", "column_name": "test_\\'!7", "collation_name": null, "compression_method": ""}, {"not_null": false, "type_name": "text", "column_name": "text_col", "collation_name": "default", "compression_method": ""}]} +(3 rows) + +SET client_min_messages TO WARNING; +DROP SCHEMA alter_table_add_column, alter_table_add_column_other_schema CASCADE; diff --git a/src/test/regress/expected/citus_local_tables.out b/src/test/regress/expected/citus_local_tables.out index cfa6410ba..aca2548a5 100644 --- a/src/test/regress/expected/citus_local_tables.out +++ b/src/test/regress/expected/citus_local_tables.out @@ -494,7 +494,7 @@ CREATE TABLE local_table_4 ( b int references local_table_4(a)); NOTICE: executing the command locally: SELECT worker_apply_inter_shard_ddl_command (xxxxx, 'citus_local_tables_test_schema', xxxxx, 'citus_local_tables_test_schema', 'ALTER TABLE citus_local_tables_test_schema.local_table_4 ADD CONSTRAINT local_table_4_a_fkey FOREIGN KEY (a) REFERENCES citus_local_tables_test_schema.citus_local_table_1(a)') ALTER TABLE citus_local_table_1 ADD COLUMN b int NOT NULL; -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1504014, 'citus_local_tables_test_schema', 'ALTER TABLE citus_local_table_1 ADD COLUMN b int NOT NULL;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1504014, 'citus_local_tables_test_schema', 'ALTER TABLE citus_local_table_1 ADD COLUMN b integer NOT NULL;') -- show that we added column with NOT NULL SELECT table_name, column_name, is_nullable FROM INFORMATION_SCHEMA.COLUMNS diff --git a/src/test/regress/expected/fkeys_between_local_ref.out b/src/test/regress/expected/fkeys_between_local_ref.out index 3df007cb3..e34b8da8c 100644 --- a/src/test/regress/expected/fkeys_between_local_ref.out +++ b/src/test/regress/expected/fkeys_between_local_ref.out @@ -123,10 +123,16 @@ BEGIN; (1 row) ROLLBACK; --- this actually attempts to convert local tables to citus local tables but errors out --- as citus doesn't support defining foreign keys via add column commands -ALTER TABLE local_table_1 ADD COLUMN col_3 INT REFERENCES reference_table_1(col_1); -ERROR: cannot execute ADD COLUMN command with PRIMARY KEY, UNIQUE, FOREIGN and CHECK constraints +BEGIN; + ALTER TABLE local_table_1 ADD COLUMN col_3 INT REFERENCES reference_table_1(col_1); + -- show that we converted all 4 local tables in this schema to citus local tables + SELECT COUNT(*)=4 FROM citus_local_tables_in_schema; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +ROLLBACK; BEGIN; -- define a foreign key so that all 4 local tables become citus local tables ALTER TABLE local_table_1 ADD CONSTRAINT fkey_11 FOREIGN KEY (col_1) REFERENCES reference_table_1(col_1); diff --git a/src/test/regress/expected/foreign_key_to_reference_table.out b/src/test/regress/expected/foreign_key_to_reference_table.out index dd110349e..5a25bb41f 100644 --- a/src/test/regress/expected/foreign_key_to_reference_table.out +++ b/src/test/regress/expected/foreign_key_to_reference_table.out @@ -250,13 +250,10 @@ SELECT create_distributed_table('referencing_table', 'ref_id'); (1 row) ALTER TABLE referencing_table ADD COLUMN referencing int REFERENCES referenced_table(id) ON UPDATE CASCADE; -ERROR: cannot execute ADD COLUMN command with PRIMARY KEY, UNIQUE, FOREIGN and CHECK constraints -DETAIL: Adding a column with a constraint in one command is not supported because all constraints in Citus must have explicit names -HINT: You can issue each command separately such as ALTER TABLE referencing_table ADD COLUMN referencing data_type; ALTER TABLE referencing_table ADD CONSTRAINT constraint_name FOREIGN KEY (referencing) REFERENCES referenced_table(id) ON UPDATE CASCADE; SELECT COUNT(*) FROM table_fkeys_in_workers WHERE relid SIMILAR TO 'fkey_reference_table.%\d{2,}' AND refd_relid SIMILAR TO 'fkey_reference_table.%\d{2,}'; count --------------------------------------------------------------------- - 0 + 8 (1 row) DROP TABLE referencing_table; diff --git a/src/test/regress/expected/isolation_citus_dist_activity.out b/src/test/regress/expected/isolation_citus_dist_activity.out index 0c905c76a..94b4759c0 100644 --- a/src/test/regress/expected/isolation_citus_dist_activity.out +++ b/src/test/regress/expected/isolation_citus_dist_activity.out @@ -44,20 +44,12 @@ query |citus_nodename_for_nodeid|citus_n step s3-view-worker: SELECT query, citus_nodename_for_nodeid(citus_nodeid_for_gpid(global_pid)), citus_nodeport_for_nodeid(citus_nodeid_for_gpid(global_pid)), state, wait_event_type, wait_event, usename, datname FROM citus_stat_activity WHERE query NOT ILIKE ALL(VALUES('%pg_prepared_xacts%'), ('%COMMIT%'), ('%csa_from_one_node%')) AND is_worker_query = true AND backend_type = 'client backend' ORDER BY query DESC; -query |citus_nodename_for_nodeid|citus_nodeport_for_nodeid|state |wait_event_type|wait_event|usename |datname +query |citus_nodename_for_nodeid|citus_nodeport_for_nodeid|state |wait_event_type|wait_event|usename |datname --------------------------------------------------------------------- -SELECT worker_apply_shard_ddl_command (1300004, 'public', ' - ALTER TABLE test_table ADD COLUMN x INT; -')|localhost | 57636|idle in transaction|Client |ClientRead|postgres|regression -SELECT worker_apply_shard_ddl_command (1300003, 'public', ' - ALTER TABLE test_table ADD COLUMN x INT; -')|localhost | 57636|idle in transaction|Client |ClientRead|postgres|regression -SELECT worker_apply_shard_ddl_command (1300002, 'public', ' - ALTER TABLE test_table ADD COLUMN x INT; -')|localhost | 57636|idle in transaction|Client |ClientRead|postgres|regression -SELECT worker_apply_shard_ddl_command (1300001, 'public', ' - ALTER TABLE test_table ADD COLUMN x INT; -')|localhost | 57636|idle in transaction|Client |ClientRead|postgres|regression +SELECT worker_apply_shard_ddl_command (1300004, 'public', 'ALTER TABLE test_table ADD COLUMN x integer;')|localhost | 57636|idle in transaction|Client |ClientRead|postgres|regression +SELECT worker_apply_shard_ddl_command (1300003, 'public', 'ALTER TABLE test_table ADD COLUMN x integer;')|localhost | 57636|idle in transaction|Client |ClientRead|postgres|regression +SELECT worker_apply_shard_ddl_command (1300002, 'public', 'ALTER TABLE test_table ADD COLUMN x integer;')|localhost | 57636|idle in transaction|Client |ClientRead|postgres|regression +SELECT worker_apply_shard_ddl_command (1300001, 'public', 'ALTER TABLE test_table ADD COLUMN x integer;')|localhost | 57636|idle in transaction|Client |ClientRead|postgres|regression (4 rows) step s2-rollback: diff --git a/src/test/regress/expected/local_shard_utility_command_execution.out b/src/test/regress/expected/local_shard_utility_command_execution.out index ba70ddf74..eeacd7f4f 100644 --- a/src/test/regress/expected/local_shard_utility_command_execution.out +++ b/src/test/regress/expected/local_shard_utility_command_execution.out @@ -509,7 +509,7 @@ NOTICE: executing the command locally: DELETE FROM local_commands_test_schema.r -- add another column to dist_table -- note that we execute below DDL locally as well ALTER TABLE ref_table ADD b int; -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500035, 'local_commands_test_schema', 'ALTER TABLE ref_table ADD b int;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500035, 'local_commands_test_schema', 'ALTER TABLE ref_table ADD COLUMN b integer;') -- define self reference ALTER TABLE ref_table ADD CONSTRAINT fkey2 FOREIGN KEY(b) REFERENCES ref_table(a); NOTICE: executing the command locally: SELECT worker_apply_inter_shard_ddl_command (1500035, 'local_commands_test_schema', 1500035, 'local_commands_test_schema', 'ALTER TABLE ref_table ADD CONSTRAINT fkey2 FOREIGN KEY(b) REFERENCES ref_table(a);') @@ -629,17 +629,17 @@ NOTICE: executing the command locally: SELECT count(*) AS count FROM local_comm -- execute bunch of DDL & DROP commands succesfully ALTER TABLE dist_table ADD column c int; -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500100, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD column c int;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500103, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD column c int;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500106, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD column c int;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500109, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD column c int;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500112, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD column c int;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500115, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD column c int;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500118, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD column c int;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500121, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD column c int;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500124, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD column c int;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500127, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD column c int;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500130, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD column c int;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500100, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD COLUMN c integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500103, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD COLUMN c integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500106, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD COLUMN c integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500109, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD COLUMN c integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500112, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD COLUMN c integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500115, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD COLUMN c integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500118, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD COLUMN c integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500121, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD COLUMN c integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500124, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD COLUMN c integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500127, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD COLUMN c integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500130, 'local_commands_test_schema', 'ALTER TABLE dist_table ADD COLUMN c integer;') ALTER TABLE dist_table ALTER COLUMN c SET NOT NULL; NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500100, 'local_commands_test_schema', 'ALTER TABLE dist_table ALTER COLUMN c SET NOT NULL;') NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500103, 'local_commands_test_schema', 'ALTER TABLE dist_table ALTER COLUMN c SET NOT NULL;') @@ -758,17 +758,17 @@ NOTICE: executing the command locally: SELECT count(*) AS count FROM local_comm -- execute bunch of DDL & DROP commands succesfully ALTER TABLE partitioning_test ADD column c int; -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500165, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD column c int;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500168, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD column c int;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500171, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD column c int;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500174, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD column c int;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500177, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD column c int;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500180, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD column c int;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500183, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD column c int;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500186, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD column c int;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500189, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD column c int;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500192, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD column c int;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500195, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD column c int;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500165, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD COLUMN c integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500168, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD COLUMN c integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500171, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD COLUMN c integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500174, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD COLUMN c integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500177, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD COLUMN c integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500180, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD COLUMN c integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500183, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD COLUMN c integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500186, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD COLUMN c integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500189, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD COLUMN c integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500192, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD COLUMN c integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1500195, 'local_commands_test_schema', 'ALTER TABLE partitioning_test ADD COLUMN c integer;') TRUNCATE partitioning_test; NOTICE: executing the command locally: TRUNCATE TABLE local_commands_test_schema.partitioning_test_xxxxx CASCADE NOTICE: executing the command locally: TRUNCATE TABLE local_commands_test_schema.partitioning_test_xxxxx CASCADE diff --git a/src/test/regress/expected/multi_alter_table_statements.out b/src/test/regress/expected/multi_alter_table_statements.out index 52fe8d762..c24927504 100644 --- a/src/test/regress/expected/multi_alter_table_statements.out +++ b/src/test/regress/expected/multi_alter_table_statements.out @@ -1,7 +1,9 @@ -- -- MULTI_ALTER_TABLE_STATEMENTS -- -ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 220000; +CREATE SCHEMA multi_alter_table_statements; +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 220000; -- Check that we can run ALTER TABLE statements on distributed tables. -- We set the shardid sequence here so that the shardids in this test -- aren't affected by changes to the previous tests. @@ -48,6 +50,8 @@ SELECT relname, reloptions FROM pg_class WHERE relname LIKE 'lineitem_alter%' OR (1 row) \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 221000; -- Verify that we can add columns ALTER TABLE lineitem_alter ADD COLUMN float_column FLOAT; ALTER TABLE lineitem_alter ADD COLUMN date_column DATE; @@ -93,7 +97,9 @@ ORDER BY attnum; (27 rows) \c - - - :master_port -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.lineitem_alter'::regclass; +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 222000; +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='lineitem_alter'::regclass; Column | Type | Modifiers --------------------------------------------------------------------- l_orderkey | bigint | not null @@ -153,7 +159,7 @@ SELECT int_column1, count(*) FROM lineitem_alter GROUP BY int_column1; -- Verify that SET NOT NULL works ALTER TABLE lineitem_alter ALTER COLUMN int_column2 SET NOT NULL; -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.lineitem_alter'::regclass; +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='lineitem_alter'::regclass; Column | Type | Modifiers --------------------------------------------------------------------- l_orderkey | bigint | not null @@ -191,7 +197,7 @@ DETAIL: Failing row contains (1, 155190, 7706, 1, 17.00, 21168.23, 0.04, 0.02, END; -- Verify that DROP NOT NULL works ALTER TABLE lineitem_alter ALTER COLUMN int_column2 DROP NOT NULL; -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.lineitem_alter'::regclass; +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='lineitem_alter'::regclass; Column | Type | Modifiers --------------------------------------------------------------------- l_orderkey | bigint | not null @@ -235,7 +241,7 @@ SELECT int_column2, pg_typeof(int_column2), count(*) from lineitem_alter GROUP B (2 rows) ALTER TABLE lineitem_alter ALTER COLUMN int_column2 SET DATA TYPE FLOAT; -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.lineitem_alter'::regclass; +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='lineitem_alter'::regclass; Column | Type | Modifiers --------------------------------------------------------------------- l_orderkey | bigint | not null @@ -299,7 +305,7 @@ SELECT SUM(l_orderkey) FROM lineitem_alter; 53620791 (1 row) -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.lineitem_alter'::regclass; +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='lineitem_alter'::regclass; Column | Type | Modifiers --------------------------------------------------------------------- l_orderkey | bigint | not null @@ -324,7 +330,7 @@ SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.lineite -- Verify that we can execute commands with multiple subcommands ALTER TABLE lineitem_alter ADD COLUMN int_column1 INTEGER, ADD COLUMN int_column2 INTEGER; -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.lineitem_alter'::regclass; +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='lineitem_alter'::regclass; Column | Type | Modifiers --------------------------------------------------------------------- l_orderkey | bigint | not null @@ -353,7 +359,7 @@ ALTER TABLE lineitem_alter ADD COLUMN int_column3 INTEGER, ERROR: alter table command is currently unsupported DETAIL: Only ADD|DROP COLUMN, SET|DROP NOT NULL, SET|DROP DEFAULT, ADD|DROP|VALIDATE CONSTRAINT, SET (), RESET (), ENABLE|DISABLE|NO FORCE|FORCE ROW LEVEL SECURITY, ATTACH|DETACH PARTITION and TYPE subcommands are supported. ALTER TABLE lineitem_alter DROP COLUMN int_column1, DROP COLUMN int_column2; -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.lineitem_alter'::regclass; +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='lineitem_alter'::regclass; Column | Type | Modifiers --------------------------------------------------------------------- l_orderkey | bigint | not null @@ -410,7 +416,7 @@ ALTER TABLE IF EXISTS non_existent_table RENAME COLUMN column1 TO column2; NOTICE: relation "non_existent_table" does not exist, skipping -- Verify that none of the failed alter table commands took effect on the master -- node -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.lineitem_alter'::regclass; +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='lineitem_alter'::regclass; Column | Type | Modifiers --------------------------------------------------------------------- l_orderkey | bigint | not null @@ -461,7 +467,7 @@ BEGIN; CREATE INDEX temp_index_2 ON lineitem_alter(l_orderkey); ALTER TABLE lineitem_alter ADD COLUMN first integer; COMMIT; -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.lineitem_alter'::regclass; +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='lineitem_alter'::regclass; Column | Type | Modifiers --------------------------------------------------------------------- l_orderkey | bigint | not null @@ -536,8 +542,10 @@ SELECT indexname, tablename FROM pg_indexes WHERE tablename = 'lineitem_alter'; DROP INDEX temp_index_2; -- Add column on only one worker... \c - - - :worker_2_port -ALTER TABLE lineitem_alter_220000 ADD COLUMN first integer; +ALTER TABLE multi_alter_table_statements.lineitem_alter_220000 ADD COLUMN first integer; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 223000; -- and try to add it in a multi-statement block, which fails BEGIN; CREATE INDEX temp_index_2 ON lineitem_alter(l_orderkey); @@ -637,7 +645,7 @@ DROP INDEX replica_idx; ALTER TABLE single_shard_items REPLICA IDENTITY default; -- Drop the column from the worker... \c - - - :worker_2_port -ALTER TABLE lineitem_alter_220000 DROP COLUMN first; +ALTER TABLE multi_alter_table_statements.lineitem_alter_220000 DROP COLUMN first; -- Create table to trigger at-xact-end (deferred) failure CREATE TABLE ddl_commands (command text UNIQUE DEFERRABLE INITIALLY DEFERRED); -- Use an event trigger to log all DDL event tags in it @@ -650,6 +658,8 @@ $ldt$ LANGUAGE plpgsql; RESET citus.enable_metadata_sync; CREATE EVENT TRIGGER log_ddl_tag ON ddl_command_end EXECUTE PROCEDURE log_ddl_tag(); \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 224000; -- The above trigger will cause failure at transaction end on one placement. -- Citus always uses 2PC. 2PC should handle this "best" (no divergence) BEGIN; @@ -670,6 +680,8 @@ DROP EVENT TRIGGER log_ddl_tag; DROP FUNCTION log_ddl_tag(); DROP TABLE ddl_commands; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 225000; -- Distributed SELECTs may appear after ALTER BEGIN; CREATE INDEX temp_index_2 ON lineitem_alter(l_orderkey); @@ -724,7 +736,7 @@ SELECT create_distributed_table('test_ab', 'a', 'hash'); INSERT INTO test_ab VALUES (2, 10); INSERT INTO test_ab VALUES (2, 11); CREATE UNIQUE INDEX temp_unique_index_1 ON test_ab(a); -ERROR: could not create unique index "temp_unique_index_1_220011" +ERROR: could not create unique index "temp_unique_index_1_225006" DETAIL: Key (a)=(2) is duplicated. CONTEXT: while executing command on localhost:xxxxx SELECT shardid FROM pg_dist_shard_placement NATURAL JOIN pg_dist_shard @@ -775,6 +787,8 @@ ORDER BY attnum; (30 rows) \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 226000; -- verify that we can rename distributed tables SHOW citus.enable_ddl_propagation; citus.enable_ddl_propagation @@ -796,24 +810,28 @@ SELECT relname FROM pg_class WHERE relname LIKE 'lineitem_renamed%' ORDER BY re relname --------------------------------------------------------------------- lineitem_renamed_220000 - lineitem_renamed_220001 - lineitem_renamed_220003 + lineitem_renamed_222000 + lineitem_renamed_222002 (3 rows) \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 227000; -- revert it to original name ALTER TABLE lineitem_renamed RENAME TO lineitem_alter; -- show rename worked on one worker, too \c - - - :worker_1_port -SELECT relname FROM pg_class WHERE relname LIKE 'lineitem_alter%' AND relname <> 'lineitem_alter_220002' /* failed copy trails */ ORDER BY relname; +SELECT relname FROM pg_class WHERE relname LIKE 'lineitem_alter%' AND relname <> 'lineitem_alter_222001' /* failed copy trails */ ORDER BY relname; relname --------------------------------------------------------------------- lineitem_alter_220000 - lineitem_alter_220001 - lineitem_alter_220003 + lineitem_alter_222000 + lineitem_alter_222002 (3 rows) \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 228000; -- verify that we can set and reset storage parameters ALTER TABLE lineitem_alter SET(fillfactor=40); SELECT relname, reloptions FROM pg_class WHERE relname = 'lineitem_alter'; @@ -823,15 +841,17 @@ SELECT relname, reloptions FROM pg_class WHERE relname = 'lineitem_alter'; (1 row) \c - - - :worker_1_port -SELECT relname, reloptions FROM pg_class WHERE relname LIKE 'lineitem_alter%' AND relname <> 'lineitem_alter_220002' /* failed copy trails */ ORDER BY relname; +SELECT relname, reloptions FROM pg_class WHERE relname LIKE 'lineitem_alter%' AND relname <> 'lineitem_alter_222001' /* failed copy trails */ ORDER BY relname; relname | reloptions --------------------------------------------------------------------- lineitem_alter_220000 | {fillfactor=40} - lineitem_alter_220001 | {fillfactor=40} - lineitem_alter_220003 | {fillfactor=40} + lineitem_alter_222000 | {fillfactor=40} + lineitem_alter_222002 | {fillfactor=40} (3 rows) \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 229000; ALTER TABLE lineitem_alter RESET(fillfactor); SELECT relname, reloptions FROM pg_class WHERE relname = 'lineitem_alter'; relname | reloptions @@ -840,15 +860,17 @@ SELECT relname, reloptions FROM pg_class WHERE relname = 'lineitem_alter'; (1 row) \c - - - :worker_1_port -SELECT relname, reloptions FROM pg_class WHERE relname LIKE 'lineitem_alter%' AND relname <> 'lineitem_alter_220002' /* failed copy trails */ ORDER BY relname; +SELECT relname, reloptions FROM pg_class WHERE relname LIKE 'lineitem_alter%' AND relname <> 'lineitem_alter_222001' /* failed copy trails */ ORDER BY relname; relname | reloptions --------------------------------------------------------------------- lineitem_alter_220000 | - lineitem_alter_220001 | - lineitem_alter_220003 | + lineitem_alter_222000 | + lineitem_alter_222002 | (3 rows) \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 230000; -- verify that we can rename indexes on distributed tables CREATE INDEX temp_index_1 ON lineitem_alter(l_linenumber); ALTER INDEX temp_index_1 RENAME TO idx_lineitem_linenumber; @@ -865,11 +887,13 @@ SELECT relname FROM pg_class WHERE relname LIKE 'idx_lineitem_linenumber%' ORDER relname --------------------------------------------------------------------- idx_lineitem_linenumber_220000 - idx_lineitem_linenumber_220001 - idx_lineitem_linenumber_220003 + idx_lineitem_linenumber_222000 + idx_lineitem_linenumber_222002 (3 rows) \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 231000; -- now get rid of the index DROP INDEX idx_lineitem_linenumber; -- verify that we don't intercept DDL commands if propagation is turned off @@ -889,9 +913,11 @@ ALTER TABLE lineitem_renamed RENAME TO lineitem_alter; ALTER TABLE lineitem_alter ADD COLUMN column_only_added_to_master int; -- verify newly added column is not present in a worker shard \c - - - :worker_1_port -SELECT column_only_added_to_master FROM lineitem_alter_220000 LIMIT 0; +SELECT column_only_added_to_master FROM multi_alter_table_statements.lineitem_alter_220000 LIMIT 0; ERROR: column "column_only_added_to_master" does not exist \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 232000; -- ddl propagation flag is reset to default, disable it again SET citus.enable_ddl_propagation to false; -- following query succeeds since it accesses an previously existing column @@ -933,6 +959,8 @@ SELECT indexname, tablename FROM pg_indexes WHERE tablename like 'lineitem_alte (0 rows) \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 233000; -- verify alter table and drop sequence in the same transaction does not cause deadlock SET citus.shard_count TO 4; SET citus.shard_replication_factor TO 2; @@ -967,7 +995,7 @@ SELECT create_distributed_table('trigger_table', 'id'); -- first set a trigger on a shard \c - - - :worker_1_port SET citus.enable_metadata_sync TO OFF; -CREATE FUNCTION update_value() RETURNS trigger AS $up$ +CREATE OR REPLACE FUNCTION update_value() RETURNS trigger AS $up$ BEGIN NEW.value := 'trigger enabled'; RETURN NEW; @@ -975,9 +1003,11 @@ CREATE FUNCTION update_value() RETURNS trigger AS $up$ $up$ LANGUAGE plpgsql; RESET citus.enable_metadata_sync; CREATE TRIGGER update_value -BEFORE INSERT ON trigger_table_220017 +BEFORE INSERT ON multi_alter_table_statements.trigger_table_233004 FOR EACH ROW EXECUTE PROCEDURE update_value(); \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 234000; INSERT INTO trigger_table VALUES (1, 'trigger disabled'); SELECT value, count(*) FROM trigger_table GROUP BY value ORDER BY value; value | count @@ -1019,37 +1049,41 @@ DROP TABLESPACE super_fast_ssd; SET citus.enable_ddl_propagation to true; CREATE USER alter_table_owner WITH LOGIN; GRANT USAGE ON SCHEMA public TO alter_table_owner; +GRANT USAGE ON SCHEMA multi_alter_table_statements TO alter_table_owner; \c - alter_table_owner - :master_port -- should not be able to access table without permission -SELECT count(*) FROM lineitem_alter; +SELECT count(*) FROM multi_alter_table_statements.lineitem_alter; ERROR: permission denied for table lineitem_alter -- should not be able to drop the table as non table owner -DROP TABLE lineitem_alter; +DROP TABLE multi_alter_table_statements.lineitem_alter; ERROR: must be owner of table lineitem_alter \c - postgres - :master_port -ALTER TABLE lineitem_alter OWNER TO alter_table_owner; +ALTER TABLE multi_alter_table_statements.lineitem_alter OWNER TO alter_table_owner; \c - alter_table_owner - :master_port -- should be able to query the table as table owner -SELECT count(*) FROM lineitem_alter; +SELECT count(*) FROM multi_alter_table_statements.lineitem_alter; count --------------------------------------------------------------------- 18000 (1 row) -- should be able to drop the table as table owner -DROP TABLE lineitem_alter; +DROP TABLE multi_alter_table_statements.lineitem_alter; -- check that nothing's left over on workers, other than the leftover shard created -- during the unsuccessful COPY \c - postgres - :worker_1_port SELECT relname FROM pg_class WHERE relname LIKE 'lineitem_alter%'; relname --------------------------------------------------------------------- - lineitem_alter_220002 + lineitem_alter_222001 (1 row) \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 235000; -- drop the roles created REVOKE ALL ON SCHEMA PUBLIC FROM alter_table_owner; +REVOKE ALL ON SCHEMA multi_alter_table_statements FROM alter_table_owner; DROP ROLE alter_table_owner; -- Test alter table with drop table in the same transaction BEGIN; @@ -1071,6 +1105,8 @@ SELECT relname FROM pg_class WHERE relname LIKE 'test_table_1%'; (0 rows) \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 236000; -- verify logged info is propagated to workers when distributing the table CREATE TABLE logged_test(id int); ALTER TABLE logged_test SET UNLOGGED; @@ -1084,13 +1120,15 @@ SELECT create_distributed_table('logged_test', 'id'); SELECT relname, CASE relpersistence WHEN 'u' THEN 'unlogged' WHEN 'p' then 'logged' ELSE 'unknown' END AS logged_info FROM pg_class WHERE relname ~ 'logged_test_' ORDER BY relname; relname | logged_info --------------------------------------------------------------------- - logged_test_220022 | unlogged - logged_test_220023 | unlogged - logged_test_220024 | unlogged - logged_test_220025 | unlogged + logged_test_236000 | unlogged + logged_test_236001 | unlogged + logged_test_236002 | unlogged + logged_test_236003 | unlogged (4 rows) \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 237000; -- verify SET LOGGED/UNLOGGED works after distributing the table ALTER TABLE logged_test SET LOGGED; SELECT relname, CASE relpersistence WHEN 'u' THEN 'unlogged' WHEN 'p' then 'logged' ELSE 'unknown' END AS logged_info FROM pg_class WHERE relname ~ 'logged_test*' ORDER BY relname; @@ -1103,13 +1141,15 @@ SELECT relname, CASE relpersistence WHEN 'u' THEN 'unlogged' WHEN 'p' then 'logg SELECT relname, CASE relpersistence WHEN 'u' THEN 'unlogged' WHEN 'p' then 'logged' ELSE 'unknown' END AS logged_info FROM pg_class WHERE relname ~ 'logged_test_' ORDER BY relname; relname | logged_info --------------------------------------------------------------------- - logged_test_220022 | logged - logged_test_220023 | logged - logged_test_220024 | logged - logged_test_220025 | logged + logged_test_236000 | logged + logged_test_236001 | logged + logged_test_236002 | logged + logged_test_236003 | logged (4 rows) \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 238000; ALTER TABLE logged_test SET UNLOGGED; SELECT relname, CASE relpersistence WHEN 'u' THEN 'unlogged' WHEN 'p' then 'logged' ELSE 'unknown' END AS logged_info FROM pg_class WHERE relname ~ 'logged_test*' ORDER BY relname; relname | logged_info @@ -1121,13 +1161,15 @@ SELECT relname, CASE relpersistence WHEN 'u' THEN 'unlogged' WHEN 'p' then 'logg SELECT relname, CASE relpersistence WHEN 'u' THEN 'unlogged' WHEN 'p' then 'logged' ELSE 'unknown' END AS logged_info FROM pg_class WHERE relname ~ 'logged_test_' ORDER BY relname; relname | logged_info --------------------------------------------------------------------- - logged_test_220022 | unlogged - logged_test_220023 | unlogged - logged_test_220024 | unlogged - logged_test_220025 | unlogged + logged_test_236000 | unlogged + logged_test_236001 | unlogged + logged_test_236002 | unlogged + logged_test_236003 | unlogged (4 rows) \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 239000; DROP TABLE logged_test; -- Test WITH options on a normal simple hash-distributed table CREATE TABLE hash_dist(id bigint primary key, f1 text) WITH (fillfactor=40); @@ -1148,13 +1190,15 @@ SELECT relname, reloptions FROM pg_class WHERE relname = 'hash_dist'; SELECT relname, reloptions FROM pg_class WHERE relkind = 'r' AND relname LIKE 'hash_dist_%' ORDER BY relname; relname | reloptions --------------------------------------------------------------------- - hash_dist_220026 | {fillfactor=40} - hash_dist_220027 | {fillfactor=40} - hash_dist_220028 | {fillfactor=40} - hash_dist_220029 | {fillfactor=40} + hash_dist_239000 | {fillfactor=40} + hash_dist_239001 | {fillfactor=40} + hash_dist_239002 | {fillfactor=40} + hash_dist_239003 | {fillfactor=40} (4 rows) \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 240000; -- verify that we can set and reset index storage parameters ALTER INDEX hash_dist_pkey SET(fillfactor=40); SELECT relname, reloptions FROM pg_class WHERE relname = 'hash_dist_pkey'; @@ -1167,13 +1211,15 @@ SELECT relname, reloptions FROM pg_class WHERE relname = 'hash_dist_pkey'; SELECT relname, reloptions FROM pg_class WHERE relname LIKE 'hash_dist_pkey_%' ORDER BY relname; relname | reloptions --------------------------------------------------------------------- - hash_dist_pkey_220026 | {fillfactor=40} - hash_dist_pkey_220027 | {fillfactor=40} - hash_dist_pkey_220028 | {fillfactor=40} - hash_dist_pkey_220029 | {fillfactor=40} + hash_dist_pkey_239000 | {fillfactor=40} + hash_dist_pkey_239001 | {fillfactor=40} + hash_dist_pkey_239002 | {fillfactor=40} + hash_dist_pkey_239003 | {fillfactor=40} (4 rows) \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 241000; ALTER INDEX hash_dist_pkey RESET(fillfactor); SELECT relname, reloptions FROM pg_class WHERE relname = 'hash_dist_pkey'; relname | reloptions @@ -1185,13 +1231,15 @@ SELECT relname, reloptions FROM pg_class WHERE relname = 'hash_dist_pkey'; SELECT relname, reloptions FROM pg_class WHERE relname LIKE 'hash_dist_pkey_%' ORDER BY relname; relname | reloptions --------------------------------------------------------------------- - hash_dist_pkey_220026 | - hash_dist_pkey_220027 | - hash_dist_pkey_220028 | - hash_dist_pkey_220029 | + hash_dist_pkey_239000 | + hash_dist_pkey_239001 | + hash_dist_pkey_239002 | + hash_dist_pkey_239003 | (4 rows) \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 242000; -- verify error message on ALTER INDEX, SET TABLESPACE is unsupported ALTER INDEX hash_dist_pkey SET TABLESPACE foo; ERROR: alter index ... set tablespace ... is currently unsupported @@ -1209,13 +1257,15 @@ SELECT relname, reloptions FROM pg_class WHERE relname = 'another_index'; SELECT relname, reloptions FROM pg_class WHERE relname LIKE 'another_index_%' ORDER BY relname; relname | reloptions --------------------------------------------------------------------- - another_index_220026 | {fillfactor=50} - another_index_220027 | {fillfactor=50} - another_index_220028 | {fillfactor=50} - another_index_220029 | {fillfactor=50} + another_index_239000 | {fillfactor=50} + another_index_239001 | {fillfactor=50} + another_index_239002 | {fillfactor=50} + another_index_239003 | {fillfactor=50} (4 rows) \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 243000; -- get rid of the index DROP INDEX another_index; -- check if we fail properly when a column with un-supported constraint is added @@ -1246,15 +1296,24 @@ SELECT create_reference_table('reference_table'); (1 row) -ALTER TABLE test_table_1 ADD COLUMN test_col int REFERENCES reference_table(i) ON DELETE CASCADE; -ERROR: cannot execute ADD COLUMN command with PRIMARY KEY, UNIQUE, FOREIGN and CHECK constraints -DETAIL: Adding a column with a constraint in one command is not supported because all constraints in Citus must have explicit names -HINT: You can issue each command separately such as ALTER TABLE test_table_1 ADD COLUMN test_col data_type; ALTER TABLE test_table_1 ADD CONSTRAINT constraint_name FOREIGN KEY (test_col) REFERENCES reference_table(i) ON DELETE CASCADE; -ALTER TABLE test_table_1 ADD COLUMN test_col int REFERENCES reference_table(i) ON DELETE CASCADE ON UPDATE SET NULL; -ERROR: cannot execute ADD COLUMN command with PRIMARY KEY, UNIQUE, FOREIGN and CHECK constraints -DETAIL: Adding a column with a constraint in one command is not supported because all constraints in Citus must have explicit names -HINT: You can issue each command separately such as ALTER TABLE test_table_1 ADD COLUMN test_col data_type; ALTER TABLE test_table_1 ADD CONSTRAINT constraint_name FOREIGN KEY (test_col) REFERENCES reference_table(i) ON DELETE CASCADE ON UPDATE SET NULL; -DROP TABLE reference_table; +ALTER TABLE test_table_1 ADD COLUMN test_col_1 int REFERENCES reference_table(i) ON DELETE CASCADE; +ALTER TABLE test_table_1 ADD COLUMN test_col_2 int REFERENCES reference_table(i) ON DELETE CASCADE ON UPDATE SET NULL; +SELECT (groupid = 0) AS is_coordinator, result FROM run_command_on_all_nodes( + $$SELECT get_grouped_fkey_constraints FROM get_grouped_fkey_constraints('multi_alter_table_statements.test_table_1')$$ +) +JOIN pg_dist_node USING (nodeid) +ORDER BY is_coordinator DESC, result; + is_coordinator | result +--------------------------------------------------------------------- + t | [{"deferred": false, "deferable": false, "on_delete": "c", "on_update": "a", "match_type": "s", "constraint_names": ["test_table_1__fkey"], "referenced_tables": ["multi_alter_table_statements.reference_table"], "referenced_columns": ["i"], "referencing_tables": ["multi_alter_table_statements.test_table_1"], "referencing_columns": ["test_col_1"], "referencing_columns_set_null_or_default": null}, {"deferred": false, "deferable": false, "on_delete": "c", "on_update": "n", "match_type": "s", "constraint_names": ["test_table_1__fkey1"], "referenced_tables": ["multi_alter_table_statements.reference_table"], "referenced_columns": ["i"], "referencing_tables": ["multi_alter_table_statements.test_table_1"], "referencing_columns": ["test_col_2"], "referencing_columns_set_null_or_default": null}] + f | [{"deferred": false, "deferable": false, "on_delete": "c", "on_update": "a", "match_type": "s", "constraint_names": ["test_table_1__fkey", "test_table_1__fkey_243000", "test_table_1__fkey_243002"], "referenced_tables": ["multi_alter_table_statements.reference_table", "multi_alter_table_statements.reference_table_243004", "multi_alter_table_statements.reference_table_243004"], "referenced_columns": ["i"], "referencing_tables": ["multi_alter_table_statements.test_table_1", "multi_alter_table_statements.test_table_1_243000", "multi_alter_table_statements.test_table_1_243002"], "referencing_columns": ["test_col_1"], "referencing_columns_set_null_or_default": null}, {"deferred": false, "deferable": false, "on_delete": "c", "on_update": "n", "match_type": "s", "constraint_names": ["test_table_1__fkey1", "test_table_1__fkey1_243000", "test_table_1__fkey1_243002"], "referenced_tables": ["multi_alter_table_statements.reference_table", "multi_alter_table_statements.reference_table_243004", "multi_alter_table_statements.reference_table_243004"], "referenced_columns": ["i"], "referencing_tables": ["multi_alter_table_statements.test_table_1", "multi_alter_table_statements.test_table_1_243000", "multi_alter_table_statements.test_table_1_243002"], "referencing_columns": ["test_col_2"], "referencing_columns_set_null_or_default": null}] + f | [{"deferred": false, "deferable": false, "on_delete": "c", "on_update": "a", "match_type": "s", "constraint_names": ["test_table_1__fkey", "test_table_1__fkey_243001", "test_table_1__fkey_243003"], "referenced_tables": ["multi_alter_table_statements.reference_table", "multi_alter_table_statements.reference_table_243004", "multi_alter_table_statements.reference_table_243004"], "referenced_columns": ["i"], "referencing_tables": ["multi_alter_table_statements.test_table_1", "multi_alter_table_statements.test_table_1_243001", "multi_alter_table_statements.test_table_1_243003"], "referencing_columns": ["test_col_1"], "referencing_columns_set_null_or_default": null}, {"deferred": false, "deferable": false, "on_delete": "c", "on_update": "n", "match_type": "s", "constraint_names": ["test_table_1__fkey1", "test_table_1__fkey1_243001", "test_table_1__fkey1_243003"], "referenced_tables": ["multi_alter_table_statements.reference_table", "multi_alter_table_statements.reference_table_243004", "multi_alter_table_statements.reference_table_243004"], "referenced_columns": ["i"], "referencing_tables": ["multi_alter_table_statements.test_table_1", "multi_alter_table_statements.test_table_1_243001", "multi_alter_table_statements.test_table_1_243003"], "referencing_columns": ["test_col_2"], "referencing_columns_set_null_or_default": null}] +(3 rows) + +BEGIN; + SET LOCAL client_min_messages TO WARNING; + DROP TABLE reference_table CASCADE; +COMMIT; CREATE TABLE referenced_table(i int UNIQUE); SELECT create_distributed_table('referenced_table', 'i'); create_distributed_table @@ -1262,7 +1321,7 @@ SELECT create_distributed_table('referenced_table', 'i'); (1 row) -ALTER TABLE test_table_1 ADD COLUMN test_col int REFERENCES referenced_table(i); +ALTER TABLE test_table_1 ADD COLUMN test_col_3 int REFERENCES referenced_table(i); ERROR: cannot create foreign key constraint DETAIL: Foreign keys are supported in two cases, either in between two colocated tables including partition column in the same ordinal in the both tables or from distributed to reference tables DROP TABLE referenced_table, test_table_1; @@ -1290,8 +1349,7 @@ SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.p (schema,{test_schema_for_sequence_propagation},{}) (1 row) +SET client_min_messages TO WARNING; DROP SCHEMA test_schema_for_sequence_propagation CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to sequence test_schema_for_sequence_propagation.seq_10 -drop cascades to default value for column x of table table_without_sequence DROP TABLE table_without_sequence; +DROP SCHEMA multi_alter_table_statements CASCADE; diff --git a/src/test/regress/expected/multi_row_router_insert.out b/src/test/regress/expected/multi_row_router_insert.out index d5af7e467..b765cf370 100644 --- a/src/test/regress/expected/multi_row_router_insert.out +++ b/src/test/regress/expected/multi_row_router_insert.out @@ -89,9 +89,9 @@ NOTICE: executing the command locally: INSERT INTO multi_row_router_insert.citu INSERT INTO citus_local_table (a) VALUES (12), (13); NOTICE: executing the command locally: INSERT INTO multi_row_router_insert.citus_local_table_1511001 AS citus_table_alias (a, b) VALUES (12,100), (13,100) ALTER TABLE citus_local_table ADD COLUMN c INT DEFAULT to_number('5', '91'); -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1511001, 'multi_row_router_insert', 'ALTER TABLE citus_local_table ADD COLUMN c INT DEFAULT to_number(''5'', ''91'');') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1511001, 'multi_row_router_insert', 'ALTER TABLE citus_local_table ADD COLUMN c integer DEFAULT to_number(''5''::text, ''91''::text);') ALTER TABLE citus_local_table ADD COLUMN d INT; -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1511001, 'multi_row_router_insert', 'ALTER TABLE citus_local_table ADD COLUMN d INT;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1511001, 'multi_row_router_insert', 'ALTER TABLE citus_local_table ADD COLUMN d integer;') INSERT INTO citus_local_table (d, a, b) VALUES (13, 14, 15), (16, 17, 18), (19, 20, 21); NOTICE: executing the command locally: INSERT INTO multi_row_router_insert.citus_local_table_1511001 AS citus_table_alias (a, b, c, d) VALUES (14,15,5,13), (17,18,5,16), (20,21,5,19) SELECT * FROM citus_local_table ORDER BY 1,2,3,4; diff --git a/src/test/regress/expected/multi_test_helpers.out b/src/test/regress/expected/multi_test_helpers.out index 55f286d1f..adfc03b92 100644 --- a/src/test/regress/expected/multi_test_helpers.out +++ b/src/test/regress/expected/multi_test_helpers.out @@ -167,3 +167,135 @@ BEGIN EXECUTE 'SELECT COUNT(*) FROM pg_catalog.pg_dist_cleanup' INTO record_count; END LOOP; END$$ LANGUAGE plpgsql; +-- Returns the foreign keys where the referencing relation's name starts with +-- given prefix. +-- +-- Foreign keys are groupped by their configurations and then the constraint name, +-- referencing table, and referenced table for each distinct configuration are +-- aggregated into arrays. +CREATE OR REPLACE FUNCTION get_grouped_fkey_constraints(referencing_relname_prefix text) +RETURNS jsonb AS $func$ + DECLARE + confdelsetcols_column_ref text; + get_grouped_fkey_constraints_query text; + result jsonb; + BEGIN + -- Read confdelsetcols as null if no such column exists. + -- This can only be the case for PG versions < 15. + IF EXISTS (SELECT 1 FROM pg_attribute WHERE attrelid = 'pg_constraint'::regclass AND attname='confdelsetcols') + THEN + confdelsetcols_column_ref := '(SELECT array_agg(attname ORDER BY attnum) FROM pg_attribute WHERE attrelid = conrelid AND attnum = ANY(confdelsetcols))'; + ELSE + confdelsetcols_column_ref := '(SELECT null::smallint[])'; + END IF; + + EXECUTE format( + $$ + SELECT jsonb_agg(to_jsonb(q1.*) ORDER BY q1.constraint_names) AS fkeys_with_different_config FROM ( + SELECT array_agg(constraint_name ORDER BY constraint_oid) AS constraint_names, + array_agg(referencing_table::regclass::text ORDER BY constraint_oid) AS referencing_tables, + array_agg(referenced_table::regclass::text ORDER BY constraint_oid) AS referenced_tables, + referencing_columns, referenced_columns, deferable, deferred, on_update, on_delete, match_type, referencing_columns_set_null_or_default + FROM ( + SELECT + oid AS constraint_oid, + conname AS constraint_name, + conrelid AS referencing_table, + (SELECT array_agg(attname ORDER BY attnum) FROM pg_attribute WHERE attrelid = conrelid AND attnum = ANY(conkey)) AS referencing_columns, + confrelid AS referenced_table, + (SELECT array_agg(attname ORDER BY attnum) FROM pg_attribute WHERE attrelid = confrelid AND attnum = ANY(confkey)) AS referenced_columns, + condeferrable AS deferable, + condeferred AS deferred, + confupdtype AS on_update, + confdeltype AS on_delete, + confmatchtype AS match_type, + %2$s AS referencing_columns_set_null_or_default + FROM pg_constraint WHERE starts_with(conrelid::regclass::text, '%1$s') AND contype = 'f' + ) q2 + GROUP BY referencing_columns, referenced_columns, deferable, deferred, on_update, on_delete, match_type, referencing_columns_set_null_or_default + ) q1 + $$, + referencing_relname_prefix, + confdelsetcols_column_ref + ) INTO result; + RETURN result; + END; +$func$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION get_index_defs(schemaname text, tablename text) +RETURNS jsonb AS $func$ + DECLARE + result jsonb; + indnullsnotdistinct_column_ref text; + BEGIN + -- Not use indnullsnotdistinct in group by clause if no such column exists. + -- This can only be the case for PG versions < 15. + IF EXISTS (SELECT 1 FROM pg_attribute WHERE attrelid = 'pg_index'::regclass AND attname='indnullsnotdistinct') + THEN + indnullsnotdistinct_column_ref := ',indnullsnotdistinct'; + ELSE + indnullsnotdistinct_column_ref := ''; + END IF; + + EXECUTE format( + $$ + SELECT jsonb_agg(to_jsonb(q1.*) ORDER BY q1.indexnames) AS index_defs FROM ( + SELECT array_agg(indexname ORDER BY indexrelid) AS indexnames, + array_agg(indexdef ORDER BY indexrelid) AS indexdefs + FROM pg_indexes + JOIN pg_index + ON (indexrelid = (schemaname || '.' || indexname)::regclass) + WHERE schemaname = '%1$s' AND starts_with(tablename, '%2$s') + GROUP BY indnatts, indnkeyatts, indisunique, indisprimary, indisexclusion, + indimmediate, indisclustered, indisvalid, indisready, indislive, + indisreplident, indkey, indcollation, indclass, indoption, indexprs, + indpred %3$s + ) q1 + $$, + schemaname, tablename, indnullsnotdistinct_column_ref) INTO result; + RETURN result; + END; +$func$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION get_column_defaults(schemaname text, tablename text) +RETURNS jsonb AS $func$ + DECLARE + result jsonb; + BEGIN + EXECUTE format( + $$ + SELECT jsonb_agg(to_jsonb(q1.*) ORDER BY q1.column_name) AS column_defs FROM ( + SELECT column_name, column_default::text, generation_expression::text + FROM information_schema.columns + WHERE table_schema = '%1$s' AND table_name = '%2$s' AND + column_default IS NOT NULL OR generation_expression IS NOT NULL + ) q1 + $$, + schemaname, tablename) INTO result; + RETURN result; + END; +$func$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION get_column_attrs(relname_prefix text) +RETURNS jsonb AS $func$ + DECLARE + result jsonb; + BEGIN + EXECUTE format( + $$ + SELECT to_jsonb(q2.*) FROM ( + SELECT relnames, jsonb_agg(to_jsonb(q1.*) - 'relnames' ORDER BY q1.column_name) AS column_attrs FROM ( + SELECT array_agg(attrelid::regclass::text ORDER BY attrelid) AS relnames, + attname AS column_name, typname AS type_name, collname AS collation_name, attcompression AS compression_method, attnotnull AS not_null + FROM pg_attribute pa + LEFT JOIN pg_type pt ON (pa.atttypid = pt.oid) + LEFT JOIN pg_collation pc1 ON (pa.attcollation = pc1.oid) + JOIN pg_class pc2 ON (pa.attrelid = pc2.oid) + WHERE starts_with(attrelid::regclass::text, '%1$s') AND + attnum > 0 AND NOT attisdropped AND relkind = 'r' + GROUP BY column_name, type_name, collation_name, compression_method, not_null + ) q1 + GROUP BY relnames + ) q2 + $$, + relname_prefix) INTO result; + RETURN result; + END; +$func$ LANGUAGE plpgsql; diff --git a/src/test/regress/expected/pg15.out b/src/test/regress/expected/pg15.out index 667305225..036f8371f 100644 --- a/src/test/regress/expected/pg15.out +++ b/src/test/regress/expected/pg15.out @@ -1473,6 +1473,56 @@ SELECT run_command_on_workers($$DROP ACCESS METHOD heap2$$); (localhost,57638,t,"DROP ACCESS METHOD") (2 rows) +CREATE TABLE referenced (int_col integer PRIMARY KEY); +CREATE TABLE referencing (text_col text); +SET citus.shard_replication_factor TO 1; +SELECT create_distributed_table('referenced', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('referencing', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +RESET citus.shard_replication_factor; +CREATE OR REPLACE FUNCTION my_random(numeric) + RETURNS numeric AS +$$ +BEGIN + RETURN 7 * $1; +END; +$$ +LANGUAGE plpgsql IMMUTABLE; +ALTER TABLE referencing ADD COLUMN test_2 integer UNIQUE NULLS DISTINCT REFERENCES referenced(int_col); +ALTER TABLE referencing ADD COLUMN test_3 integer GENERATED ALWAYS AS (text_col::int * my_random(1)) STORED UNIQUE NULLS NOT DISTINCT; +SELECT (groupid = 0) AS is_coordinator, result FROM run_command_on_all_nodes( + $$SELECT get_grouped_fkey_constraints FROM get_grouped_fkey_constraints('pg15.referencing')$$ +) +JOIN pg_dist_node USING (nodeid) +ORDER BY is_coordinator DESC, result; + is_coordinator | result +--------------------------------------------------------------------- + t | [{"deferred": false, "deferable": false, "on_delete": "a", "on_update": "a", "match_type": "s", "constraint_names": ["referencing__fkey"], "referenced_tables": ["pg15.referenced"], "referenced_columns": ["int_col"], "referencing_tables": ["pg15.referencing"], "referencing_columns": ["test_2"], "referencing_columns_set_null_or_default": null}] + f | [{"deferred": false, "deferable": false, "on_delete": "a", "on_update": "a", "match_type": "s", "constraint_names": ["referencing__fkey", "referencing__fkey_960207"], "referenced_tables": ["pg15.referenced", "pg15.referenced_960206"], "referenced_columns": ["int_col"], "referencing_tables": ["pg15.referencing", "pg15.referencing_960207"], "referencing_columns": ["test_2"], "referencing_columns_set_null_or_default": null}] + f | [{"deferred": false, "deferable": false, "on_delete": "a", "on_update": "a", "match_type": "s", "constraint_names": ["referencing__fkey"], "referenced_tables": ["pg15.referenced"], "referenced_columns": ["int_col"], "referencing_tables": ["pg15.referencing"], "referencing_columns": ["test_2"], "referencing_columns_set_null_or_default": null}] +(3 rows) + +SELECT (groupid = 0) AS is_coordinator, result FROM run_command_on_all_nodes( + $$SELECT get_index_defs FROM get_index_defs('pg15', 'referencing')$$ +) +JOIN pg_dist_node USING (nodeid) +ORDER BY is_coordinator DESC, result; + is_coordinator | result +--------------------------------------------------------------------- + t | [{"indexdefs": ["CREATE UNIQUE INDEX referencing__key ON pg15.referencing USING btree (test_2)"], "indexnames": ["referencing__key"]}, {"indexdefs": ["CREATE UNIQUE INDEX referencing__key1 ON pg15.referencing USING btree (test_3) NULLS NOT DISTINCT"], "indexnames": ["referencing__key1"]}] + f | [{"indexdefs": ["CREATE UNIQUE INDEX referencing__key ON pg15.referencing USING btree (test_2)", "CREATE UNIQUE INDEX referencing__key_960207 ON pg15.referencing_960207 USING btree (test_2)"], "indexnames": ["referencing__key", "referencing__key_960207"]}, {"indexdefs": ["CREATE UNIQUE INDEX referencing__key1 ON pg15.referencing USING btree (test_3) NULLS NOT DISTINCT", "CREATE UNIQUE INDEX referencing__key1_960207 ON pg15.referencing_960207 USING btree (test_3) NULLS NOT DISTINCT"], "indexnames": ["referencing__key1", "referencing__key1_960207"]}] + f | [{"indexdefs": ["CREATE UNIQUE INDEX referencing__key ON pg15.referencing USING btree (test_2)"], "indexnames": ["referencing__key"]}, {"indexdefs": ["CREATE UNIQUE INDEX referencing__key1 ON pg15.referencing USING btree (test_3) NULLS NOT DISTINCT"], "indexnames": ["referencing__key1"]}] +(3 rows) + -- Clean up \set VERBOSITY terse SET client_min_messages TO ERROR; diff --git a/src/test/regress/expected/replicate_reference_tables_to_coordinator.out b/src/test/regress/expected/replicate_reference_tables_to_coordinator.out index 8cd4d6ffa..4dbeda307 100644 --- a/src/test/regress/expected/replicate_reference_tables_to_coordinator.out +++ b/src/test/regress/expected/replicate_reference_tables_to_coordinator.out @@ -446,7 +446,7 @@ INSERT INTO local_table VALUES (1), (2), (3), (4); INSERT INTO numbers VALUES (1), (2), (3), (4); NOTICE: executing the command locally: INSERT INTO replicate_ref_to_coordinator.numbers_8000001 AS citus_table_alias (a) VALUES (1), (2), (3), (4) ALTER TABLE numbers ADD COLUMN d int; -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (8000001, 'replicate_ref_to_coordinator', 'ALTER TABLE numbers ADD COLUMN d int;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (8000001, 'replicate_ref_to_coordinator', 'ALTER TABLE numbers ADD COLUMN d integer;') SELECT * FROM local_table JOIN numbers USING(a) ORDER BY a; NOTICE: executing the command locally: SELECT local_table.a, numbers.d FROM (replicate_ref_to_coordinator.local_table JOIN replicate_ref_to_coordinator.numbers_8000001 numbers(a, d) USING (a)) ORDER BY local_table.a a | d diff --git a/src/test/regress/expected/single_node.out b/src/test/regress/expected/single_node.out index 4b6ea0837..cecf38d46 100644 --- a/src/test/regress/expected/single_node.out +++ b/src/test/regress/expected/single_node.out @@ -2134,10 +2134,10 @@ NOTICE: executing the command locally: SELECT count(*) AS count FROM (SELECT in -- test with NULL columns ALTER TABLE non_binary_copy_test ADD COLUMN z INT; -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (90630519, 'single_node', 'ALTER TABLE non_binary_copy_test ADD COLUMN z INT;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (90630520, 'single_node', 'ALTER TABLE non_binary_copy_test ADD COLUMN z INT;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (90630521, 'single_node', 'ALTER TABLE non_binary_copy_test ADD COLUMN z INT;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (90630522, 'single_node', 'ALTER TABLE non_binary_copy_test ADD COLUMN z INT;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (90630519, 'single_node', 'ALTER TABLE non_binary_copy_test ADD COLUMN z integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (90630520, 'single_node', 'ALTER TABLE non_binary_copy_test ADD COLUMN z integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (90630521, 'single_node', 'ALTER TABLE non_binary_copy_test ADD COLUMN z integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (90630522, 'single_node', 'ALTER TABLE non_binary_copy_test ADD COLUMN z integer;') WITH cte_1 AS (INSERT INTO non_binary_copy_test SELECT * FROM non_binary_copy_test LIMIT 10000 ON CONFLICT (key) DO UPDATE SET value = (0, 'citus0')::new_type RETURNING z) SELECT bool_and(z is null) FROM cte_1; diff --git a/src/test/regress/expected/single_node_0.out b/src/test/regress/expected/single_node_0.out index 4749b9b81..dcff4c468 100644 --- a/src/test/regress/expected/single_node_0.out +++ b/src/test/regress/expected/single_node_0.out @@ -2134,10 +2134,10 @@ NOTICE: executing the command locally: SELECT count(*) AS count FROM (SELECT in -- test with NULL columns ALTER TABLE non_binary_copy_test ADD COLUMN z INT; -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (90630519, 'single_node', 'ALTER TABLE non_binary_copy_test ADD COLUMN z INT;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (90630520, 'single_node', 'ALTER TABLE non_binary_copy_test ADD COLUMN z INT;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (90630521, 'single_node', 'ALTER TABLE non_binary_copy_test ADD COLUMN z INT;') -NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (90630522, 'single_node', 'ALTER TABLE non_binary_copy_test ADD COLUMN z INT;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (90630519, 'single_node', 'ALTER TABLE non_binary_copy_test ADD COLUMN z integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (90630520, 'single_node', 'ALTER TABLE non_binary_copy_test ADD COLUMN z integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (90630521, 'single_node', 'ALTER TABLE non_binary_copy_test ADD COLUMN z integer;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (90630522, 'single_node', 'ALTER TABLE non_binary_copy_test ADD COLUMN z integer;') WITH cte_1 AS (INSERT INTO non_binary_copy_test SELECT * FROM non_binary_copy_test LIMIT 10000 ON CONFLICT (key) DO UPDATE SET value = (0, 'citus0')::new_type RETURNING z) SELECT bool_and(z is null) FROM cte_1; diff --git a/src/test/regress/multi_1_schedule b/src/test/regress/multi_1_schedule index 67473e471..947c42ec8 100644 --- a/src/test/regress/multi_1_schedule +++ b/src/test/regress/multi_1_schedule @@ -157,7 +157,7 @@ test: with_executors with_join with_partitioning with_transactions with_dml # Tests around DDL statements run on distributed tables # ---------- test: multi_index_statements -test: multi_alter_table_statements +test: multi_alter_table_statements alter_table_add_column test: multi_alter_table_add_constraints test: multi_alter_table_add_constraints_without_name test: multi_alter_table_add_foreign_key_without_name diff --git a/src/test/regress/sql/alter_table_add_column.sql b/src/test/regress/sql/alter_table_add_column.sql new file mode 100644 index 000000000..0f7e35067 --- /dev/null +++ b/src/test/regress/sql/alter_table_add_column.sql @@ -0,0 +1,73 @@ +CREATE SCHEMA alter_table_add_column; +SET search_path TO alter_table_add_column; + +SET citus.next_shard_id TO 1830000; +SET citus.shard_replication_factor TO 1; + +SET client_min_messages TO NOTICE; + +CREATE TABLE referenced (int_col integer PRIMARY KEY); +CREATE TABLE referencing (text_col text); +SELECT create_distributed_table('referenced', null); +SELECT create_distributed_table('referencing', null); + +CREATE SCHEMA alter_table_add_column_other_schema; + +CREATE OR REPLACE FUNCTION alter_table_add_column_other_schema.my_random(numeric) + RETURNS numeric AS +$$ +BEGIN + RETURN 7 * $1; +END; +$$ +LANGUAGE plpgsql IMMUTABLE; + +CREATE COLLATION caseinsensitive ( + provider = icu, + locale = 'und-u-ks-level2' +); + +CREATE TYPE "simple_!\'custom_type" AS (a integer, b integer); + +ALTER TABLE referencing ADD COLUMN test_1 integer DEFAULT (alter_table_add_column_other_schema.my_random(7) + random() + 5) NOT NULL CONSTRAINT fkey REFERENCES referenced(int_col) ON UPDATE SET DEFAULT ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE referencing ADD COLUMN test_2 integer UNIQUE REFERENCES referenced(int_col) ON UPDATE CASCADE ON DELETE SET DEFAULT NOT DEFERRABLE INITIALLY IMMEDIATE; +ALTER TABLE referencing ADD COLUMN test_3 integer GENERATED ALWAYS AS (test_1 * alter_table_add_column_other_schema.my_random(1)) STORED UNIQUE REFERENCES referenced(int_col) MATCH FULL; +ALTER TABLE referencing ADD COLUMN test_4 integer PRIMARY KEY WITH (fillfactor=70) NOT NULL REFERENCES referenced(int_col) MATCH SIMPLE ON UPDATE CASCADE ON DELETE SET DEFAULT; +ALTER TABLE referencing ADD COLUMN test_5 integer CONSTRAINT unique_c UNIQUE WITH (fillfactor=50); +ALTER TABLE referencing ADD COLUMN test_6 text COMPRESSION pglz COLLATE caseinsensitive NOT NULL; +ALTER TABLE referencing ADD COLUMN "test_\'!7" "simple_!\'custom_type"; + +-- we give up deparsing ALTER TABLE command if it needs to create a check constraint, and we fallback to legacy behavior +ALTER TABLE referencing ADD COLUMN test_8 integer CHECK (test_8 > 0); +ALTER TABLE referencing ADD COLUMN test_8 integer CONSTRAINT check_test_8 CHECK (test_8 > 0); + +-- try to add test_6 again, but with IF NOT EXISTS +ALTER TABLE referencing ADD COLUMN IF NOT EXISTS test_6 text; +ALTER TABLE referencing ADD COLUMN IF NOT EXISTS test_6 integer; + +SELECT (groupid = 0) AS is_coordinator, result FROM run_command_on_all_nodes( + $$SELECT get_grouped_fkey_constraints FROM get_grouped_fkey_constraints('alter_table_add_column.referencing')$$ +) +JOIN pg_dist_node USING (nodeid) +ORDER BY is_coordinator DESC, result; + +SELECT (groupid = 0) AS is_coordinator, result FROM run_command_on_all_nodes( + $$SELECT get_index_defs FROM get_index_defs('alter_table_add_column', 'referencing')$$ +) +JOIN pg_dist_node USING (nodeid) +ORDER BY is_coordinator DESC, result; + +SELECT (groupid = 0) AS is_coordinator, result FROM run_command_on_all_nodes( + $$SELECT get_column_defaults FROM get_column_defaults('alter_table_add_column', 'referencing')$$ +) +JOIN pg_dist_node USING (nodeid) +ORDER BY is_coordinator DESC, result; + +SELECT (groupid = 0) AS is_coordinator, result FROM run_command_on_all_nodes( + $$SELECT get_column_attrs FROM get_column_attrs('alter_table_add_column.referencing')$$ +) +JOIN pg_dist_node USING (nodeid) +ORDER BY is_coordinator DESC, result; + +SET client_min_messages TO WARNING; +DROP SCHEMA alter_table_add_column, alter_table_add_column_other_schema CASCADE; diff --git a/src/test/regress/sql/fkeys_between_local_ref.sql b/src/test/regress/sql/fkeys_between_local_ref.sql index a04040474..8f92f3eea 100644 --- a/src/test/regress/sql/fkeys_between_local_ref.sql +++ b/src/test/regress/sql/fkeys_between_local_ref.sql @@ -100,9 +100,12 @@ BEGIN; SELECT COUNT(*)=0 FROM citus_local_tables_in_schema; ROLLBACK; --- this actually attempts to convert local tables to citus local tables but errors out --- as citus doesn't support defining foreign keys via add column commands -ALTER TABLE local_table_1 ADD COLUMN col_3 INT REFERENCES reference_table_1(col_1); +BEGIN; + ALTER TABLE local_table_1 ADD COLUMN col_3 INT REFERENCES reference_table_1(col_1); + + -- show that we converted all 4 local tables in this schema to citus local tables + SELECT COUNT(*)=4 FROM citus_local_tables_in_schema; +ROLLBACK; BEGIN; -- define a foreign key so that all 4 local tables become citus local tables diff --git a/src/test/regress/sql/multi_alter_table_statements.sql b/src/test/regress/sql/multi_alter_table_statements.sql index f814caf10..10e52cb37 100644 --- a/src/test/regress/sql/multi_alter_table_statements.sql +++ b/src/test/regress/sql/multi_alter_table_statements.sql @@ -2,8 +2,9 @@ -- MULTI_ALTER_TABLE_STATEMENTS -- -ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 220000; - +CREATE SCHEMA multi_alter_table_statements; +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 220000; -- Check that we can run ALTER TABLE statements on distributed tables. -- We set the shardid sequence here so that the shardids in this test @@ -38,6 +39,8 @@ SELECT relname, reloptions FROM pg_class WHERE relname = 'lineitem_alter'; \c - - - :worker_1_port SELECT relname, reloptions FROM pg_class WHERE relname LIKE 'lineitem_alter%' ORDER BY relname; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 221000; -- Verify that we can add columns @@ -55,8 +58,10 @@ FROM JOIN pg_attribute ON (pc.oid = pg_attribute.attrelid) ORDER BY attnum; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 222000; -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.lineitem_alter'::regclass; +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='lineitem_alter'::regclass; SELECT float_column, count(*) FROM lineitem_alter GROUP BY float_column; SELECT int_column1, count(*) FROM lineitem_alter GROUP BY int_column1; @@ -75,7 +80,7 @@ SELECT int_column1, count(*) FROM lineitem_alter GROUP BY int_column1; -- Verify that SET NOT NULL works ALTER TABLE lineitem_alter ALTER COLUMN int_column2 SET NOT NULL; -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.lineitem_alter'::regclass; +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='lineitem_alter'::regclass; -- Drop default so that NULLs will be inserted for this column ALTER TABLE lineitem_alter ALTER COLUMN int_column2 DROP DEFAULT; @@ -90,7 +95,7 @@ END; -- Verify that DROP NOT NULL works ALTER TABLE lineitem_alter ALTER COLUMN int_column2 DROP NOT NULL; -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.lineitem_alter'::regclass; +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='lineitem_alter'::regclass; -- COPY should succeed now SELECT master_create_empty_shard('lineitem_alter') as shardid \gset @@ -102,7 +107,7 @@ SELECT count(*) from lineitem_alter; SELECT int_column2, pg_typeof(int_column2), count(*) from lineitem_alter GROUP BY int_column2; ALTER TABLE lineitem_alter ALTER COLUMN int_column2 SET DATA TYPE FLOAT; -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.lineitem_alter'::regclass; +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='lineitem_alter'::regclass; SELECT int_column2, pg_typeof(int_column2), count(*) from lineitem_alter GROUP BY int_column2; @@ -130,19 +135,19 @@ ALTER TABLE lineitem_alter DROP COLUMN IF EXISTS int_column2; ALTER TABLE IF EXISTS lineitem_alter RENAME COLUMN l_orderkey_renamed TO l_orderkey; SELECT SUM(l_orderkey) FROM lineitem_alter; -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.lineitem_alter'::regclass; +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='lineitem_alter'::regclass; -- Verify that we can execute commands with multiple subcommands ALTER TABLE lineitem_alter ADD COLUMN int_column1 INTEGER, ADD COLUMN int_column2 INTEGER; -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.lineitem_alter'::regclass; +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='lineitem_alter'::regclass; ALTER TABLE lineitem_alter ADD COLUMN int_column3 INTEGER, ALTER COLUMN int_column1 SET STATISTICS 10; ALTER TABLE lineitem_alter DROP COLUMN int_column1, DROP COLUMN int_column2; -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.lineitem_alter'::regclass; +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='lineitem_alter'::regclass; -- Verify that we cannot execute alter commands on the distribution column @@ -174,7 +179,7 @@ ALTER TABLE IF EXISTS non_existent_table RENAME COLUMN column1 TO column2; -- Verify that none of the failed alter table commands took effect on the master -- node -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.lineitem_alter'::regclass; +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='lineitem_alter'::regclass; -- verify that non-propagated ddl commands are allowed inside a transaction block SET citus.enable_ddl_propagation to false; @@ -198,7 +203,7 @@ CREATE INDEX temp_index_2 ON lineitem_alter(l_orderkey); ALTER TABLE lineitem_alter ADD COLUMN first integer; COMMIT; -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.lineitem_alter'::regclass; +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='lineitem_alter'::regclass; SELECT "Column", "Type", "Definition" FROM index_attrs WHERE relid = 'temp_index_2'::regclass; @@ -241,8 +246,10 @@ DROP INDEX temp_index_2; -- Add column on only one worker... \c - - - :worker_2_port -ALTER TABLE lineitem_alter_220000 ADD COLUMN first integer; +ALTER TABLE multi_alter_table_statements.lineitem_alter_220000 ADD COLUMN first integer; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 223000; -- and try to add it in a multi-statement block, which fails BEGIN; @@ -288,7 +295,7 @@ ALTER TABLE single_shard_items REPLICA IDENTITY default; -- Drop the column from the worker... \c - - - :worker_2_port -ALTER TABLE lineitem_alter_220000 DROP COLUMN first; +ALTER TABLE multi_alter_table_statements.lineitem_alter_220000 DROP COLUMN first; -- Create table to trigger at-xact-end (deferred) failure CREATE TABLE ddl_commands (command text UNIQUE DEFERRABLE INITIALLY DEFERRED); @@ -305,6 +312,8 @@ RESET citus.enable_metadata_sync; CREATE EVENT TRIGGER log_ddl_tag ON ddl_command_end EXECUTE PROCEDURE log_ddl_tag(); \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 224000; -- The above trigger will cause failure at transaction end on one placement. -- Citus always uses 2PC. 2PC should handle this "best" (no divergence) BEGIN; @@ -321,6 +330,8 @@ DROP FUNCTION log_ddl_tag(); DROP TABLE ddl_commands; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 225000; -- Distributed SELECTs may appear after ALTER BEGIN; CREATE INDEX temp_index_2 ON lineitem_alter(l_orderkey); @@ -360,6 +371,8 @@ FROM JOIN pg_attribute ON (pc.oid = pg_attribute.attrelid) ORDER BY attnum; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 226000; -- verify that we can rename distributed tables SHOW citus.enable_ddl_propagation; @@ -372,29 +385,37 @@ SELECT relname FROM pg_class WHERE relname = 'lineitem_renamed'; \c - - - :worker_1_port SELECT relname FROM pg_class WHERE relname LIKE 'lineitem_renamed%' ORDER BY relname; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 227000; -- revert it to original name ALTER TABLE lineitem_renamed RENAME TO lineitem_alter; -- show rename worked on one worker, too \c - - - :worker_1_port -SELECT relname FROM pg_class WHERE relname LIKE 'lineitem_alter%' AND relname <> 'lineitem_alter_220002' /* failed copy trails */ ORDER BY relname; +SELECT relname FROM pg_class WHERE relname LIKE 'lineitem_alter%' AND relname <> 'lineitem_alter_222001' /* failed copy trails */ ORDER BY relname; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 228000; -- verify that we can set and reset storage parameters ALTER TABLE lineitem_alter SET(fillfactor=40); SELECT relname, reloptions FROM pg_class WHERE relname = 'lineitem_alter'; \c - - - :worker_1_port -SELECT relname, reloptions FROM pg_class WHERE relname LIKE 'lineitem_alter%' AND relname <> 'lineitem_alter_220002' /* failed copy trails */ ORDER BY relname; +SELECT relname, reloptions FROM pg_class WHERE relname LIKE 'lineitem_alter%' AND relname <> 'lineitem_alter_222001' /* failed copy trails */ ORDER BY relname; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 229000; ALTER TABLE lineitem_alter RESET(fillfactor); SELECT relname, reloptions FROM pg_class WHERE relname = 'lineitem_alter'; \c - - - :worker_1_port -SELECT relname, reloptions FROM pg_class WHERE relname LIKE 'lineitem_alter%' AND relname <> 'lineitem_alter_220002' /* failed copy trails */ ORDER BY relname; +SELECT relname, reloptions FROM pg_class WHERE relname LIKE 'lineitem_alter%' AND relname <> 'lineitem_alter_222001' /* failed copy trails */ ORDER BY relname; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 230000; -- verify that we can rename indexes on distributed tables CREATE INDEX temp_index_1 ON lineitem_alter(l_linenumber); @@ -407,6 +428,8 @@ SELECT relname FROM pg_class WHERE relname = 'idx_lineitem_linenumber'; \c - - - :worker_1_port SELECT relname FROM pg_class WHERE relname LIKE 'idx_lineitem_linenumber%' ORDER BY relname; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 231000; -- now get rid of the index DROP INDEX idx_lineitem_linenumber; @@ -427,8 +450,10 @@ ALTER TABLE lineitem_alter ADD COLUMN column_only_added_to_master int; -- verify newly added column is not present in a worker shard \c - - - :worker_1_port -SELECT column_only_added_to_master FROM lineitem_alter_220000 LIMIT 0; +SELECT column_only_added_to_master FROM multi_alter_table_statements.lineitem_alter_220000 LIMIT 0; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 232000; -- ddl propagation flag is reset to default, disable it again SET citus.enable_ddl_propagation to false; @@ -458,6 +483,8 @@ SELECT indexname, tablename FROM pg_indexes WHERE tablename = 'lineitem_alter'; \c - - - :worker_1_port SELECT indexname, tablename FROM pg_indexes WHERE tablename like 'lineitem_alter_%'; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 233000; -- verify alter table and drop sequence in the same transaction does not cause deadlock SET citus.shard_count TO 4; @@ -489,7 +516,7 @@ SELECT create_distributed_table('trigger_table', 'id'); -- first set a trigger on a shard \c - - - :worker_1_port SET citus.enable_metadata_sync TO OFF; -CREATE FUNCTION update_value() RETURNS trigger AS $up$ +CREATE OR REPLACE FUNCTION update_value() RETURNS trigger AS $up$ BEGIN NEW.value := 'trigger enabled'; RETURN NEW; @@ -498,10 +525,12 @@ $up$ LANGUAGE plpgsql; RESET citus.enable_metadata_sync; CREATE TRIGGER update_value -BEFORE INSERT ON trigger_table_220017 +BEFORE INSERT ON multi_alter_table_statements.trigger_table_233004 FOR EACH ROW EXECUTE PROCEDURE update_value(); \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 234000; INSERT INTO trigger_table VALUES (1, 'trigger disabled'); SELECT value, count(*) FROM trigger_table GROUP BY value ORDER BY value; @@ -529,32 +558,36 @@ SET citus.enable_ddl_propagation to true; CREATE USER alter_table_owner WITH LOGIN; GRANT USAGE ON SCHEMA public TO alter_table_owner; +GRANT USAGE ON SCHEMA multi_alter_table_statements TO alter_table_owner; \c - alter_table_owner - :master_port -- should not be able to access table without permission -SELECT count(*) FROM lineitem_alter; +SELECT count(*) FROM multi_alter_table_statements.lineitem_alter; -- should not be able to drop the table as non table owner -DROP TABLE lineitem_alter; +DROP TABLE multi_alter_table_statements.lineitem_alter; \c - postgres - :master_port -ALTER TABLE lineitem_alter OWNER TO alter_table_owner; +ALTER TABLE multi_alter_table_statements.lineitem_alter OWNER TO alter_table_owner; \c - alter_table_owner - :master_port -- should be able to query the table as table owner -SELECT count(*) FROM lineitem_alter; +SELECT count(*) FROM multi_alter_table_statements.lineitem_alter; -- should be able to drop the table as table owner -DROP TABLE lineitem_alter; +DROP TABLE multi_alter_table_statements.lineitem_alter; -- check that nothing's left over on workers, other than the leftover shard created -- during the unsuccessful COPY \c - postgres - :worker_1_port SELECT relname FROM pg_class WHERE relname LIKE 'lineitem_alter%'; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 235000; -- drop the roles created REVOKE ALL ON SCHEMA PUBLIC FROM alter_table_owner; +REVOKE ALL ON SCHEMA multi_alter_table_statements FROM alter_table_owner; DROP ROLE alter_table_owner; -- Test alter table with drop table in the same transaction @@ -569,6 +602,8 @@ END; \c - - - :worker_1_port SELECT relname FROM pg_class WHERE relname LIKE 'test_table_1%'; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 236000; -- verify logged info is propagated to workers when distributing the table CREATE TABLE logged_test(id int); @@ -577,6 +612,8 @@ SELECT create_distributed_table('logged_test', 'id'); \c - - - :worker_1_port SELECT relname, CASE relpersistence WHEN 'u' THEN 'unlogged' WHEN 'p' then 'logged' ELSE 'unknown' END AS logged_info FROM pg_class WHERE relname ~ 'logged_test_' ORDER BY relname; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 237000; -- verify SET LOGGED/UNLOGGED works after distributing the table ALTER TABLE logged_test SET LOGGED; @@ -584,11 +621,15 @@ SELECT relname, CASE relpersistence WHEN 'u' THEN 'unlogged' WHEN 'p' then 'logg \c - - - :worker_1_port SELECT relname, CASE relpersistence WHEN 'u' THEN 'unlogged' WHEN 'p' then 'logged' ELSE 'unknown' END AS logged_info FROM pg_class WHERE relname ~ 'logged_test_' ORDER BY relname; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 238000; ALTER TABLE logged_test SET UNLOGGED; SELECT relname, CASE relpersistence WHEN 'u' THEN 'unlogged' WHEN 'p' then 'logged' ELSE 'unknown' END AS logged_info FROM pg_class WHERE relname ~ 'logged_test*' ORDER BY relname; \c - - - :worker_1_port SELECT relname, CASE relpersistence WHEN 'u' THEN 'unlogged' WHEN 'p' then 'logged' ELSE 'unknown' END AS logged_info FROM pg_class WHERE relname ~ 'logged_test_' ORDER BY relname; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 239000; DROP TABLE logged_test; -- Test WITH options on a normal simple hash-distributed table @@ -601,6 +642,8 @@ SELECT relname, reloptions FROM pg_class WHERE relname = 'hash_dist'; \c - - - :worker_1_port SELECT relname, reloptions FROM pg_class WHERE relkind = 'r' AND relname LIKE 'hash_dist_%' ORDER BY relname; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 240000; -- verify that we can set and reset index storage parameters ALTER INDEX hash_dist_pkey SET(fillfactor=40); @@ -609,6 +652,8 @@ SELECT relname, reloptions FROM pg_class WHERE relname = 'hash_dist_pkey'; \c - - - :worker_1_port SELECT relname, reloptions FROM pg_class WHERE relname LIKE 'hash_dist_pkey_%' ORDER BY relname; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 241000; ALTER INDEX hash_dist_pkey RESET(fillfactor); SELECT relname, reloptions FROM pg_class WHERE relname = 'hash_dist_pkey'; @@ -616,6 +661,8 @@ SELECT relname, reloptions FROM pg_class WHERE relname = 'hash_dist_pkey'; \c - - - :worker_1_port SELECT relname, reloptions FROM pg_class WHERE relname LIKE 'hash_dist_pkey_%' ORDER BY relname; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 242000; -- verify error message on ALTER INDEX, SET TABLESPACE is unsupported ALTER INDEX hash_dist_pkey SET TABLESPACE foo; @@ -629,6 +676,8 @@ SELECT relname, reloptions FROM pg_class WHERE relname = 'another_index'; \c - - - :worker_1_port SELECT relname, reloptions FROM pg_class WHERE relname LIKE 'another_index_%' ORDER BY relname; \c - - - :master_port +SET search_path TO multi_alter_table_statements, public; +SET citus.next_shard_id TO 243000; -- get rid of the index DROP INDEX another_index; @@ -645,13 +694,24 @@ ALTER TABLE test_table_1 ADD COLUMN test_col int CHECK (test_col > 3); CREATE TABLE reference_table(i int UNIQUE); SELECT create_reference_table('reference_table'); -ALTER TABLE test_table_1 ADD COLUMN test_col int REFERENCES reference_table(i) ON DELETE CASCADE; -ALTER TABLE test_table_1 ADD COLUMN test_col int REFERENCES reference_table(i) ON DELETE CASCADE ON UPDATE SET NULL; -DROP TABLE reference_table; + +ALTER TABLE test_table_1 ADD COLUMN test_col_1 int REFERENCES reference_table(i) ON DELETE CASCADE; +ALTER TABLE test_table_1 ADD COLUMN test_col_2 int REFERENCES reference_table(i) ON DELETE CASCADE ON UPDATE SET NULL; + +SELECT (groupid = 0) AS is_coordinator, result FROM run_command_on_all_nodes( + $$SELECT get_grouped_fkey_constraints FROM get_grouped_fkey_constraints('multi_alter_table_statements.test_table_1')$$ +) +JOIN pg_dist_node USING (nodeid) +ORDER BY is_coordinator DESC, result; + +BEGIN; + SET LOCAL client_min_messages TO WARNING; + DROP TABLE reference_table CASCADE; +COMMIT; CREATE TABLE referenced_table(i int UNIQUE); SELECT create_distributed_table('referenced_table', 'i'); -ALTER TABLE test_table_1 ADD COLUMN test_col int REFERENCES referenced_table(i); +ALTER TABLE test_table_1 ADD COLUMN test_col_3 int REFERENCES referenced_table(i); DROP TABLE referenced_table, test_table_1; -- Check sequence propagate its own dependencies while adding a column @@ -667,5 +727,7 @@ ALTER TABLE table_without_sequence ADD COLUMN x BIGINT DEFAULT nextval('test_sch SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object WHERE objid IN ('test_schema_for_sequence_propagation.seq_10'::regclass); SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object WHERE objid IN ('test_schema_for_sequence_propagation'::regnamespace); +SET client_min_messages TO WARNING; DROP SCHEMA test_schema_for_sequence_propagation CASCADE; DROP TABLE table_without_sequence; +DROP SCHEMA multi_alter_table_statements CASCADE; diff --git a/src/test/regress/sql/multi_test_helpers.sql b/src/test/regress/sql/multi_test_helpers.sql index ba6d6f17f..4ebda8971 100644 --- a/src/test/regress/sql/multi_test_helpers.sql +++ b/src/test/regress/sql/multi_test_helpers.sql @@ -180,3 +180,139 @@ BEGIN EXECUTE 'SELECT COUNT(*) FROM pg_catalog.pg_dist_cleanup' INTO record_count; END LOOP; END$$ LANGUAGE plpgsql; + +-- Returns the foreign keys where the referencing relation's name starts with +-- given prefix. +-- +-- Foreign keys are groupped by their configurations and then the constraint name, +-- referencing table, and referenced table for each distinct configuration are +-- aggregated into arrays. +CREATE OR REPLACE FUNCTION get_grouped_fkey_constraints(referencing_relname_prefix text) +RETURNS jsonb AS $func$ + DECLARE + confdelsetcols_column_ref text; + get_grouped_fkey_constraints_query text; + result jsonb; + BEGIN + -- Read confdelsetcols as null if no such column exists. + -- This can only be the case for PG versions < 15. + IF EXISTS (SELECT 1 FROM pg_attribute WHERE attrelid = 'pg_constraint'::regclass AND attname='confdelsetcols') + THEN + confdelsetcols_column_ref := '(SELECT array_agg(attname ORDER BY attnum) FROM pg_attribute WHERE attrelid = conrelid AND attnum = ANY(confdelsetcols))'; + ELSE + confdelsetcols_column_ref := '(SELECT null::smallint[])'; + END IF; + + EXECUTE format( + $$ + SELECT jsonb_agg(to_jsonb(q1.*) ORDER BY q1.constraint_names) AS fkeys_with_different_config FROM ( + SELECT array_agg(constraint_name ORDER BY constraint_oid) AS constraint_names, + array_agg(referencing_table::regclass::text ORDER BY constraint_oid) AS referencing_tables, + array_agg(referenced_table::regclass::text ORDER BY constraint_oid) AS referenced_tables, + referencing_columns, referenced_columns, deferable, deferred, on_update, on_delete, match_type, referencing_columns_set_null_or_default + FROM ( + SELECT + oid AS constraint_oid, + conname AS constraint_name, + conrelid AS referencing_table, + (SELECT array_agg(attname ORDER BY attnum) FROM pg_attribute WHERE attrelid = conrelid AND attnum = ANY(conkey)) AS referencing_columns, + confrelid AS referenced_table, + (SELECT array_agg(attname ORDER BY attnum) FROM pg_attribute WHERE attrelid = confrelid AND attnum = ANY(confkey)) AS referenced_columns, + condeferrable AS deferable, + condeferred AS deferred, + confupdtype AS on_update, + confdeltype AS on_delete, + confmatchtype AS match_type, + %2$s AS referencing_columns_set_null_or_default + FROM pg_constraint WHERE starts_with(conrelid::regclass::text, '%1$s') AND contype = 'f' + ) q2 + GROUP BY referencing_columns, referenced_columns, deferable, deferred, on_update, on_delete, match_type, referencing_columns_set_null_or_default + ) q1 + $$, + referencing_relname_prefix, + confdelsetcols_column_ref + ) INTO result; + RETURN result; + END; +$func$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION get_index_defs(schemaname text, tablename text) +RETURNS jsonb AS $func$ + DECLARE + result jsonb; + indnullsnotdistinct_column_ref text; + BEGIN + -- Not use indnullsnotdistinct in group by clause if no such column exists. + -- This can only be the case for PG versions < 15. + IF EXISTS (SELECT 1 FROM pg_attribute WHERE attrelid = 'pg_index'::regclass AND attname='indnullsnotdistinct') + THEN + indnullsnotdistinct_column_ref := ',indnullsnotdistinct'; + ELSE + indnullsnotdistinct_column_ref := ''; + END IF; + + EXECUTE format( + $$ + SELECT jsonb_agg(to_jsonb(q1.*) ORDER BY q1.indexnames) AS index_defs FROM ( + SELECT array_agg(indexname ORDER BY indexrelid) AS indexnames, + array_agg(indexdef ORDER BY indexrelid) AS indexdefs + FROM pg_indexes + JOIN pg_index + ON (indexrelid = (schemaname || '.' || indexname)::regclass) + WHERE schemaname = '%1$s' AND starts_with(tablename, '%2$s') + GROUP BY indnatts, indnkeyatts, indisunique, indisprimary, indisexclusion, + indimmediate, indisclustered, indisvalid, indisready, indislive, + indisreplident, indkey, indcollation, indclass, indoption, indexprs, + indpred %3$s + ) q1 + $$, + schemaname, tablename, indnullsnotdistinct_column_ref) INTO result; + RETURN result; + END; +$func$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION get_column_defaults(schemaname text, tablename text) +RETURNS jsonb AS $func$ + DECLARE + result jsonb; + BEGIN + EXECUTE format( + $$ + SELECT jsonb_agg(to_jsonb(q1.*) ORDER BY q1.column_name) AS column_defs FROM ( + SELECT column_name, column_default::text, generation_expression::text + FROM information_schema.columns + WHERE table_schema = '%1$s' AND table_name = '%2$s' AND + column_default IS NOT NULL OR generation_expression IS NOT NULL + ) q1 + $$, + schemaname, tablename) INTO result; + RETURN result; + END; +$func$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION get_column_attrs(relname_prefix text) +RETURNS jsonb AS $func$ + DECLARE + result jsonb; + BEGIN + EXECUTE format( + $$ + SELECT to_jsonb(q2.*) FROM ( + SELECT relnames, jsonb_agg(to_jsonb(q1.*) - 'relnames' ORDER BY q1.column_name) AS column_attrs FROM ( + SELECT array_agg(attrelid::regclass::text ORDER BY attrelid) AS relnames, + attname AS column_name, typname AS type_name, collname AS collation_name, attcompression AS compression_method, attnotnull AS not_null + FROM pg_attribute pa + LEFT JOIN pg_type pt ON (pa.atttypid = pt.oid) + LEFT JOIN pg_collation pc1 ON (pa.attcollation = pc1.oid) + JOIN pg_class pc2 ON (pa.attrelid = pc2.oid) + WHERE starts_with(attrelid::regclass::text, '%1$s') AND + attnum > 0 AND NOT attisdropped AND relkind = 'r' + GROUP BY column_name, type_name, collation_name, compression_method, not_null + ) q1 + GROUP BY relnames + ) q2 + $$, + relname_prefix) INTO result; + RETURN result; + END; +$func$ LANGUAGE plpgsql; diff --git a/src/test/regress/sql/pg15.sql b/src/test/regress/sql/pg15.sql index a8ac91901..97ba224b3 100644 --- a/src/test/regress/sql/pg15.sql +++ b/src/test/regress/sql/pg15.sql @@ -933,6 +933,38 @@ DROP TABLE mx_ddl_table2; DROP ACCESS METHOD heap2; SELECT run_command_on_workers($$DROP ACCESS METHOD heap2$$); +CREATE TABLE referenced (int_col integer PRIMARY KEY); +CREATE TABLE referencing (text_col text); + +SET citus.shard_replication_factor TO 1; +SELECT create_distributed_table('referenced', null); +SELECT create_distributed_table('referencing', null); +RESET citus.shard_replication_factor; + +CREATE OR REPLACE FUNCTION my_random(numeric) + RETURNS numeric AS +$$ +BEGIN + RETURN 7 * $1; +END; +$$ +LANGUAGE plpgsql IMMUTABLE; + +ALTER TABLE referencing ADD COLUMN test_2 integer UNIQUE NULLS DISTINCT REFERENCES referenced(int_col); +ALTER TABLE referencing ADD COLUMN test_3 integer GENERATED ALWAYS AS (text_col::int * my_random(1)) STORED UNIQUE NULLS NOT DISTINCT; + +SELECT (groupid = 0) AS is_coordinator, result FROM run_command_on_all_nodes( + $$SELECT get_grouped_fkey_constraints FROM get_grouped_fkey_constraints('pg15.referencing')$$ +) +JOIN pg_dist_node USING (nodeid) +ORDER BY is_coordinator DESC, result; + +SELECT (groupid = 0) AS is_coordinator, result FROM run_command_on_all_nodes( + $$SELECT get_index_defs FROM get_index_defs('pg15', 'referencing')$$ +) +JOIN pg_dist_node USING (nodeid) +ORDER BY is_coordinator DESC, result; + -- Clean up \set VERBOSITY terse SET client_min_messages TO ERROR;