diff --git a/src/backend/distributed/commands/distribute_object_ops.c b/src/backend/distributed/commands/distribute_object_ops.c index 422460387..f284a53e4 100644 --- a/src/backend/distributed/commands/distribute_object_ops.c +++ b/src/backend/distributed/commands/distribute_object_ops.c @@ -828,6 +828,22 @@ static DistributeObjectOps Type_AlterObjectSchema = { .address = AlterTypeSchemaStmtObjectAddress, .markDistributed = false, }; + +/* + * PreprocessAlterViewSchemaStmt and PostprocessAlterViewSchemaStmt functions can be called + * internally by ALTER TABLE view_name SET SCHEMA ... if the ALTER TABLE command targets a + * view. In other words ALTER VIEW view_name SET SCHEMA will use the View_AlterObjectSchema + * but ALTER TABLE view_name SET SCHEMA will use Table_AlterObjectSchema but call process + * functions of View_AlterObjectSchema internally. + */ +static DistributeObjectOps View_AlterObjectSchema = { + .deparse = DeparseAlterViewSchemaStmt, + .qualify = QualifyAlterViewSchemaStmt, + .preprocess = PreprocessAlterViewSchemaStmt, + .postprocess = PostprocessAlterViewSchemaStmt, + .address = AlterViewSchemaStmtObjectAddress, + .markDistributed = false, +}; static DistributeObjectOps Type_AlterOwner = { .deparse = DeparseAlterTypeOwnerStmt, .qualify = QualifyAlterTypeOwnerStmt, @@ -844,6 +860,22 @@ static DistributeObjectOps Type_AlterTable = { .address = AlterTypeStmtObjectAddress, .markDistributed = false, }; + +/* + * PreprocessAlterViewStmt and PostprocessAlterViewStmt functions can be called internally + * by ALTER TABLE view_name SET/RESET ... if the ALTER TABLE command targets a view. In + * other words ALTER VIEW view_name SET/RESET will use the View_AlterView + * but ALTER TABLE view_name SET/RESET will use Table_AlterTable but call process + * functions of View_AlterView internally. + */ +static DistributeObjectOps View_AlterView = { + .deparse = DeparseAlterViewStmt, + .qualify = QualifyAlterViewStmt, + .preprocess = PreprocessAlterViewStmt, + .postprocess = PostprocessAlterViewStmt, + .address = AlterViewStmtObjectAddress, + .markDistributed = false, +}; static DistributeObjectOps Type_Drop = { .deparse = DeparseDropTypeStmt, .qualify = NULL, @@ -868,6 +900,21 @@ static DistributeObjectOps Type_Rename = { .address = RenameTypeStmtObjectAddress, .markDistributed = false, }; + +/* + * PreprocessRenameViewStmt function can be called internally by ALTER TABLE view_name + * RENAME ... if the ALTER TABLE command targets a view or a view's column. In other words + * ALTER VIEW view_name RENAME will use the View_Rename but ALTER TABLE view_name RENAME + * will use Any_Rename but call process functions of View_Rename internally. + */ +static DistributeObjectOps View_Rename = { + .deparse = DeparseRenameViewStmt, + .qualify = QualifyRenameViewStmt, + .preprocess = PreprocessRenameViewStmt, + .postprocess = NULL, + .address = RenameViewStmtObjectAddress, + .markDistributed = false, +}; static DistributeObjectOps Trigger_Rename = { .deparse = NULL, .qualify = NULL, @@ -1021,6 +1068,11 @@ GetDistributeObjectOps(Node *node) return &Type_AlterObjectSchema; } + case OBJECT_VIEW: + { + return &View_AlterObjectSchema; + } + default: { return &NoDistributeOps; @@ -1157,6 +1209,11 @@ GetDistributeObjectOps(Node *node) return &Sequence_AlterOwner; } + case OBJECT_VIEW: + { + return &View_AlterView; + } + default: { return &NoDistributeOps; @@ -1512,6 +1569,27 @@ GetDistributeObjectOps(Node *node) return &Trigger_Rename; } + case OBJECT_VIEW: + { + return &View_Rename; + } + + case OBJECT_COLUMN: + { + switch (stmt->relationType) + { + case OBJECT_VIEW: + { + return &View_Rename; + } + + default: + { + return &Any_Rename; + } + } + } + default: { return &Any_Rename; diff --git a/src/backend/distributed/commands/rename.c b/src/backend/distributed/commands/rename.c index 3ece05a0a..6511aed81 100644 --- a/src/backend/distributed/commands/rename.c +++ b/src/backend/distributed/commands/rename.c @@ -36,11 +36,12 @@ PreprocessRenameStmt(Node *node, const char *renameCommand, /* * We only support some of the PostgreSQL supported RENAME statements, and - * our list include only renaming table and index (related) objects. + * our list include only renaming table, index, policy and view (related) objects. */ if (!IsAlterTableRenameStmt(renameStmt) && !IsIndexRenameStmt(renameStmt) && - !IsPolicyRenameStmt(renameStmt)) + !IsPolicyRenameStmt(renameStmt) && + !IsViewRenameStmt(renameStmt)) { return NIL; } @@ -48,7 +49,7 @@ PreprocessRenameStmt(Node *node, const char *renameCommand, /* * The lock levels here should be same as the ones taken in * RenameRelation(), renameatt() and RenameConstraint(). However, since all - * three statements have identical lock levels, we just use a single statement. + * four statements have identical lock levels, we just use a single statement. */ objectRelationId = RangeVarGetRelid(renameStmt->relation, AccessExclusiveLock, @@ -63,14 +64,31 @@ PreprocessRenameStmt(Node *node, const char *renameCommand, return NIL; } - /* check whether we are dealing with a sequence here */ - if (get_rel_relkind(objectRelationId) == RELKIND_SEQUENCE) + /* + * Check whether we are dealing with a sequence or view here and route queries + * accordingly to the right processor function. We need to check both objects here + * since PG supports targeting sequences and views with ALTER TABLE commands. + */ + char relKind = get_rel_relkind(objectRelationId); + if (relKind == RELKIND_SEQUENCE) { RenameStmt *stmtCopy = copyObject(renameStmt); stmtCopy->renameType = OBJECT_SEQUENCE; return PreprocessRenameSequenceStmt((Node *) stmtCopy, renameCommand, processUtilityContext); } + else if (relKind == RELKIND_VIEW) + { + RenameStmt *stmtCopy = copyObject(renameStmt); + stmtCopy->relationType = OBJECT_VIEW; + if (stmtCopy->renameType == OBJECT_TABLE) + { + stmtCopy->renameType = OBJECT_VIEW; + } + + return PreprocessRenameViewStmt((Node *) stmtCopy, renameCommand, + processUtilityContext); + } /* we have no planning to do unless the table is distributed */ switch (renameStmt->renameType) diff --git a/src/backend/distributed/commands/table.c b/src/backend/distributed/commands/table.c index 832df667c..72d761433 100644 --- a/src/backend/distributed/commands/table.c +++ b/src/backend/distributed/commands/table.c @@ -651,12 +651,21 @@ PostprocessAlterTableSchemaStmt(Node *node, const char *queryString) */ ObjectAddress tableAddress = GetObjectAddressFromParseTree((Node *) stmt, true); - /* check whether we are dealing with a sequence here */ - if (get_rel_relkind(tableAddress.objectId) == RELKIND_SEQUENCE) + /* + * Check whether we are dealing with a sequence or view here and route queries + * accordingly to the right processor function. + */ + char relKind = get_rel_relkind(tableAddress.objectId); + if (relKind == RELKIND_SEQUENCE) { stmt->objectType = OBJECT_SEQUENCE; return PostprocessAlterSequenceSchemaStmt((Node *) stmt, queryString); } + else if (relKind == RELKIND_VIEW) + { + stmt->objectType = OBJECT_VIEW; + return PostprocessAlterViewSchemaStmt((Node *) stmt, queryString); + } if (!ShouldPropagate() || !IsCitusTable(tableAddress.objectId)) { @@ -699,18 +708,26 @@ PreprocessAlterTableStmt(Node *node, const char *alterTableCommand, } /* - * check whether we are dealing with a sequence here + * check whether we are dealing with a sequence or view here * if yes, it must be ALTER TABLE .. OWNER TO .. command - * since this is the only ALTER command of a sequence that + * since this is the only ALTER command of a sequence or view that * passes through an AlterTableStmt */ - if (get_rel_relkind(leftRelationId) == RELKIND_SEQUENCE) + char relKind = get_rel_relkind(leftRelationId); + if (relKind == RELKIND_SEQUENCE) { AlterTableStmt *stmtCopy = copyObject(alterTableStatement); AlterTableStmtObjType_compat(stmtCopy) = OBJECT_SEQUENCE; return PreprocessAlterSequenceOwnerStmt((Node *) stmtCopy, alterTableCommand, processUtilityContext); } + else if (relKind == RELKIND_VIEW) + { + AlterTableStmt *stmtCopy = copyObject(alterTableStatement); + AlterTableStmtObjType_compat(stmtCopy) = OBJECT_VIEW; + return PreprocessAlterViewStmt((Node *) stmtCopy, alterTableCommand, + processUtilityContext); + } /* * AlterTableStmt applies also to INDEX relations, and we have support for @@ -1758,18 +1775,31 @@ PreprocessAlterTableSchemaStmt(Node *node, const char *queryString, { return NIL; } + ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, stmt->missing_ok); Oid relationId = address.objectId; - /* check whether we are dealing with a sequence here */ - if (get_rel_relkind(relationId) == RELKIND_SEQUENCE) + /* + * Check whether we are dealing with a sequence or view here and route queries + * accordingly to the right processor function. We need to check both objects here + * since PG supports targeting sequences and views with ALTER TABLE commands. + */ + char relKind = get_rel_relkind(relationId); + if (relKind == RELKIND_SEQUENCE) { AlterObjectSchemaStmt *stmtCopy = copyObject(stmt); stmtCopy->objectType = OBJECT_SEQUENCE; return PreprocessAlterSequenceSchemaStmt((Node *) stmtCopy, queryString, processUtilityContext); } + else if (relKind == RELKIND_VIEW) + { + AlterObjectSchemaStmt *stmtCopy = copyObject(stmt); + stmtCopy->objectType = OBJECT_VIEW; + return PreprocessAlterViewSchemaStmt((Node *) stmtCopy, queryString, + processUtilityContext); + } /* first check whether a distributed relation is affected */ if (!OidIsValid(relationId) || !IsCitusTable(relationId)) @@ -1939,12 +1969,19 @@ PostprocessAlterTableStmt(AlterTableStmt *alterTableStatement) * since this is the only ALTER command of a sequence that * passes through an AlterTableStmt */ - if (get_rel_relkind(relationId) == RELKIND_SEQUENCE) + char relKind = get_rel_relkind(relationId); + if (relKind == RELKIND_SEQUENCE) { AlterTableStmtObjType_compat(alterTableStatement) = OBJECT_SEQUENCE; PostprocessAlterSequenceOwnerStmt((Node *) alterTableStatement, NULL); return; } + else if (relKind == RELKIND_VIEW) + { + AlterTableStmtObjType_compat(alterTableStatement) = OBJECT_VIEW; + PostprocessAlterViewStmt((Node *) alterTableStatement, NULL); + return; + } /* * Before ensuring each dependency exist, update dependent sequences diff --git a/src/backend/distributed/commands/view.c b/src/backend/distributed/commands/view.c index 2f1ffc7c6..46b3ef48a 100644 --- a/src/backend/distributed/commands/view.c +++ b/src/backend/distributed/commands/view.c @@ -37,6 +37,7 @@ static List * FilterNameListForDistributedViews(List *viewNamesList, bool missing_ok); static void AppendQualifiedViewNameToCreateViewCommand(StringInfo buf, Oid viewOid); +static void AppendViewDefinitionToCreateViewCommand(StringInfo buf, Oid viewOid); static void AppendAliasesToCreateViewCommand(StringInfo createViewCommand, Oid viewOid); static void AppendOptionsToCreateViewCommand(StringInfo createViewCommand, Oid viewOid); @@ -101,45 +102,9 @@ PostprocessViewStmt(Node *node, const char *queryString) } /* If the view has any unsupported dependency, create it locally */ - DeferredErrorMessage *errMsg = DeferErrorIfHasUnsupportedDependency(&viewAddress); - - if (errMsg != NULL) + if (ErrorOrWarnIfObjectHasUnsupportedDependency(&viewAddress)) { - /* - * Don't need to give any warning/error messages if there is no worker nodes in - * the cluster as user's experience won't be affected on the single node even - * if the view won't be distributed. - */ - if (!HasAnyNodes()) - { - return NIL; - } - - /* - * Since Citus drops and recreates views while converting a table type, giving a - * NOTICE message is enough if the process in table type conversion function call - */ - if (InTableTypeConversionFunctionCall) - { - RaiseDeferredError(errMsg, DEBUG1); - return NIL; - } - - /* - * If the view is already distributed, we should provide an error to not have - * different definition of view on coordinator and worker nodes. If the view - * is not distributed yet, we can create it locally to not affect user's local - * usage experience. - */ - if (IsObjectDistributed(&viewAddress)) - { - RaiseDeferredError(errMsg, ERROR); - } - else - { - RaiseDeferredError(errMsg, WARNING); - return NIL; - } + return NIL; } EnsureDependenciesExistOnAllNodes(&viewAddress); @@ -409,7 +374,7 @@ AppendOptionsToCreateViewCommand(StringInfo createViewCommand, Oid viewOid) * AppendViewDefinitionToCreateViewCommand adds the definition of the given view to the * given create view command. */ -void +static void AppendViewDefinitionToCreateViewCommand(StringInfo buf, Oid viewOid) { /* @@ -460,3 +425,250 @@ AlterViewOwnerCommand(Oid viewOid) return alterOwnerCommand->data; } + + +/* + * PreprocessAlterViewStmt is invoked for alter view statements. + */ +List * +PreprocessAlterViewStmt(Node *node, const char *queryString, ProcessUtilityContext + processUtilityContext) +{ + AlterTableStmt *stmt = castNode(AlterTableStmt, node); + + ObjectAddress viewAddress = GetObjectAddressFromParseTree((Node *) stmt, true); + if (!ShouldPropagateObject(&viewAddress)) + { + return NIL; + } + + QualifyTreeNode((Node *) stmt); + + EnsureCoordinator(); + EnsureSequentialMode(OBJECT_VIEW); + + /* reconstruct alter statement in a portable fashion */ + const char *alterViewStmtSql = DeparseTreeNode((Node *) stmt); + + List *commands = list_make3(DISABLE_DDL_PROPAGATION, + (void *) alterViewStmtSql, + ENABLE_DDL_PROPAGATION); + + return NodeDDLTaskList(NON_COORDINATOR_NODES, commands); +} + + +/* + * PostprocessAlterViewStmt is invoked for alter view statements. + */ +List * +PostprocessAlterViewStmt(Node *node, const char *queryString) +{ + AlterTableStmt *stmt = castNode(AlterTableStmt, node); + Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_VIEW); + + ObjectAddress viewAddress = GetObjectAddressFromParseTree((Node *) stmt, true); + if (!ShouldPropagateObject(&viewAddress)) + { + return NIL; + } + + if (IsObjectAddressOwnedByExtension(&viewAddress, NULL)) + { + return NIL; + } + + /* If the view has any unsupported dependency, create it locally */ + if (ErrorOrWarnIfObjectHasUnsupportedDependency(&viewAddress)) + { + return NIL; + } + + EnsureDependenciesExistOnAllNodes(&viewAddress); + + return NIL; +} + + +/* + * AlterViewStmtObjectAddress returns the ObjectAddress for the subject of the + * ALTER VIEW statement. + */ +ObjectAddress +AlterViewStmtObjectAddress(Node *node, bool missing_ok) +{ + AlterTableStmt *stmt = castNode(AlterTableStmt, node); + Oid viewOid = RangeVarGetRelid(stmt->relation, NoLock, missing_ok); + + ObjectAddress viewAddress = { 0 }; + ObjectAddressSet(viewAddress, RelationRelationId, viewOid); + + return viewAddress; +} + + +/* + * PreprocessRenameViewStmt is called when the user is renaming the view or the column of + * the view. + */ +List * +PreprocessRenameViewStmt(Node *node, const char *queryString, + ProcessUtilityContext processUtilityContext) +{ + ObjectAddress typeAddress = GetObjectAddressFromParseTree(node, true); + if (!ShouldPropagateObject(&typeAddress)) + { + return NIL; + } + + EnsureCoordinator(); + + /* fully qualify */ + QualifyTreeNode(node); + + /* deparse sql*/ + const char *renameStmtSql = DeparseTreeNode(node); + + EnsureSequentialMode(OBJECT_VIEW); + + /* to prevent recursion with mx we disable ddl propagation */ + List *commands = list_make3(DISABLE_DDL_PROPAGATION, + (void *) renameStmtSql, + ENABLE_DDL_PROPAGATION); + + return NodeDDLTaskList(NON_COORDINATOR_NODES, commands); +} + + +/* + * RenameViewStmtObjectAddress returns the ObjectAddress of the view that is the object + * of the RenameStmt. Errors if missing_ok is false. + */ +ObjectAddress +RenameViewStmtObjectAddress(Node *node, bool missing_ok) +{ + RenameStmt *stmt = castNode(RenameStmt, node); + + Oid viewOid = RangeVarGetRelid(stmt->relation, NoLock, missing_ok); + + ObjectAddress viewAddress = { 0 }; + ObjectAddressSet(viewAddress, RelationRelationId, viewOid); + + return viewAddress; +} + + +/* + * PreprocessAlterViewSchemaStmt is executed before the statement is applied to the local + * postgres instance. + */ +List * +PreprocessAlterViewSchemaStmt(Node *node, const char *queryString, + ProcessUtilityContext processUtilityContext) +{ + AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node); + + ObjectAddress typeAddress = GetObjectAddressFromParseTree((Node *) stmt, true); + if (!ShouldPropagateObject(&typeAddress)) + { + return NIL; + } + + EnsureCoordinator(); + EnsureSequentialMode(OBJECT_VIEW); + + QualifyTreeNode((Node *) stmt); + + const char *sql = DeparseTreeNode((Node *) stmt); + + List *commands = list_make3(DISABLE_DDL_PROPAGATION, + (void *) sql, + ENABLE_DDL_PROPAGATION); + + return NodeDDLTaskList(NON_COORDINATOR_NODES, commands); +} + + +/* + * PostprocessAlterViewSchemaStmt is executed after the change has been applied locally, we + * can now use the new dependencies of the view to ensure all its dependencies exist on + * the workers before we apply the commands remotely. + */ +List * +PostprocessAlterViewSchemaStmt(Node *node, const char *queryString) +{ + AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node); + + ObjectAddress viewAddress = GetObjectAddressFromParseTree((Node *) stmt, true); + if (!ShouldPropagateObject(&viewAddress)) + { + return NIL; + } + + /* dependencies have changed (schema) let's ensure they exist */ + EnsureDependenciesExistOnAllNodes(&viewAddress); + + return NIL; +} + + +/* + * AlterViewSchemaStmtObjectAddress returns the ObjectAddress of the view that is the object + * of the alter schema statement. + */ +ObjectAddress +AlterViewSchemaStmtObjectAddress(Node *node, bool missing_ok) +{ + AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node); + + Oid viewOid = RangeVarGetRelid(stmt->relation, NoLock, true); + + /* + * Since it can be called both before and after executing the standardProcess utility, + * we need to check both old and new schemas + */ + if (viewOid == InvalidOid) + { + Oid schemaId = get_namespace_oid(stmt->newschema, missing_ok); + viewOid = get_relname_relid(stmt->relation->relname, schemaId); + + /* + * if the view is still invalid we couldn't find the view, error with the same + * message postgres would error with it missing_ok is false (not ok to miss) + */ + if (!missing_ok && viewOid == InvalidOid) + { + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("view \"%s\" does not exist", + stmt->relation->relname))); + } + } + + ObjectAddress viewAddress = { 0 }; + ObjectAddressSet(viewAddress, RelationRelationId, viewOid); + + return viewAddress; +} + + +/* + * IsViewRenameStmt returns whether the passed-in RenameStmt is the following + * form: + * + * - ALTER VIEW RENAME + * - ALTER VIEW RENAME COLUMN + */ +bool +IsViewRenameStmt(RenameStmt *renameStmt) +{ + bool isViewRenameStmt = false; + + if (renameStmt->renameType == OBJECT_VIEW || + (renameStmt->renameType == OBJECT_COLUMN && + renameStmt->relationType == OBJECT_VIEW)) + { + isViewRenameStmt = true; + } + + return isViewRenameStmt; +} diff --git a/src/backend/distributed/deparser/deparse_view_stmts.c b/src/backend/distributed/deparser/deparse_view_stmts.c index b669a92c3..39c4ccb63 100644 --- a/src/backend/distributed/deparser/deparse_view_stmts.c +++ b/src/backend/distributed/deparser/deparse_view_stmts.c @@ -24,6 +24,13 @@ static void AppendDropViewStmt(StringInfo buf, DropStmt *stmt); static void AppendViewNameList(StringInfo buf, List *objects); +static void AppendAlterViewStmt(StringInfo buf, AlterTableStmt *stmt); +static void AppendAlterViewCmd(StringInfo buf, AlterTableCmd *alterTableCmd); +static void AppendAlterViewOwnerStmt(StringInfo buf, AlterTableCmd *alterTableCmd); +static void AppendAlterViewSetOptionsStmt(StringInfo buf, AlterTableCmd *alterTableCmd); +static void AppendAlterViewResetOptionsStmt(StringInfo buf, AlterTableCmd *alterTableCmd); +static void AppendRenameViewStmt(StringInfo buf, RenameStmt *stmt); +static void AppendAlterViewSchemaStmt(StringInfo buf, AlterObjectSchemaStmt *stmt); /* * DeparseDropViewStmt deparses the given DROP VIEW statement. @@ -92,3 +99,212 @@ AppendViewNameList(StringInfo buf, List *viewNamesList) isFirstView = false; } } + + +/* + * DeparseAlterViewStmt deparses the given ALTER VIEW statement. + */ +char * +DeparseAlterViewStmt(Node *node) +{ + AlterTableStmt *stmt = castNode(AlterTableStmt, node); + StringInfoData str = { 0 }; + initStringInfo(&str); + + AppendAlterViewStmt(&str, stmt); + + return str.data; +} + + +static void +AppendAlterViewStmt(StringInfo buf, AlterTableStmt *stmt) +{ + const char *identifier = quote_qualified_identifier(stmt->relation->schemaname, + stmt->relation->relname); + + appendStringInfo(buf, "ALTER VIEW %s ", identifier); + + AlterTableCmd *alterTableCmd = castNode(AlterTableCmd, lfirst(list_head(stmt->cmds))); + AppendAlterViewCmd(buf, alterTableCmd); + + appendStringInfoString(buf, ";"); +} + + +static void +AppendAlterViewCmd(StringInfo buf, AlterTableCmd *alterTableCmd) +{ + switch (alterTableCmd->subtype) + { + case AT_ChangeOwner: + { + AppendAlterViewOwnerStmt(buf, alterTableCmd); + break; + } + + case AT_SetRelOptions: + { + AppendAlterViewSetOptionsStmt(buf, alterTableCmd); + break; + } + + case AT_ResetRelOptions: + { + AppendAlterViewResetOptionsStmt(buf, alterTableCmd); + break; + } + + case AT_ColumnDefault: + { + elog(ERROR, "Citus doesn't support setting or resetting default values for a " + "column of view"); + break; + } + + default: + { + /* + * ALTER VIEW command only supports for the cases checked above but an + * ALTER TABLE commands targeting views may have different cases. To let + * PG throw the right error locally, we don't throw any error here + */ + break; + } + } +} + + +static void +AppendAlterViewOwnerStmt(StringInfo buf, AlterTableCmd *alterTableCmd) +{ + appendStringInfo(buf, "OWNER TO %s", RoleSpecString(alterTableCmd->newowner, true)); +} + + +static void +AppendAlterViewSetOptionsStmt(StringInfo buf, AlterTableCmd *alterTableCmd) +{ + ListCell *lc = NULL; + bool initialOption = true; + foreach(lc, (List *) alterTableCmd->def) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (initialOption) + { + appendStringInfo(buf, "SET ("); + initialOption = false; + } + else + { + appendStringInfo(buf, ","); + } + + appendStringInfo(buf, "%s", def->defname); + if (def->arg != NULL) + { + appendStringInfo(buf, "="); + appendStringInfo(buf, "%s", defGetString(def)); + } + } + + appendStringInfo(buf, ")"); +} + + +static void +AppendAlterViewResetOptionsStmt(StringInfo buf, AlterTableCmd *alterTableCmd) +{ + ListCell *lc = NULL; + bool initialOption = true; + foreach(lc, (List *) alterTableCmd->def) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (initialOption) + { + appendStringInfo(buf, "RESET ("); + initialOption = false; + } + else + { + appendStringInfo(buf, ","); + } + + appendStringInfo(buf, "%s", def->defname); + } + + appendStringInfo(buf, ")"); +} + + +char * +DeparseRenameViewStmt(Node *node) +{ + RenameStmt *stmt = castNode(RenameStmt, node); + StringInfoData str = { 0 }; + initStringInfo(&str); + + AppendRenameViewStmt(&str, stmt); + + return str.data; +} + + +static void +AppendRenameViewStmt(StringInfo buf, RenameStmt *stmt) +{ + switch (stmt->renameType) + { + case OBJECT_COLUMN: + { + const char *identifier = + quote_qualified_identifier(stmt->relation->schemaname, + stmt->relation->relname); + appendStringInfo(buf, "ALTER VIEW %s RENAME COLUMN %s TO %s;", identifier, + quote_identifier(stmt->subname), quote_identifier( + stmt->newname)); + break; + } + + case OBJECT_VIEW: + { + const char *identifier = + quote_qualified_identifier(stmt->relation->schemaname, + stmt->relation->relname); + appendStringInfo(buf, "ALTER VIEW %s RENAME TO %s;", identifier, + quote_identifier(stmt->newname)); + break; + } + + default: + { + ereport(ERROR, (errmsg("unsupported subtype for alter view rename command"), + errdetail("sub command type: %d", stmt->renameType))); + } + } +} + + +char * +DeparseAlterViewSchemaStmt(Node *node) +{ + AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node); + StringInfoData str = { 0 }; + initStringInfo(&str); + + AppendAlterViewSchemaStmt(&str, stmt); + + return str.data; +} + + +static void +AppendAlterViewSchemaStmt(StringInfo buf, AlterObjectSchemaStmt *stmt) +{ + const char *identifier = quote_qualified_identifier(stmt->relation->schemaname, + stmt->relation->relname); + appendStringInfo(buf, "ALTER VIEW %s SET SCHEMA %s;", identifier, quote_identifier( + stmt->newschema)); +} diff --git a/src/backend/distributed/deparser/qualify_view_stmt.c b/src/backend/distributed/deparser/qualify_view_stmt.c index c2bd7efc1..b787bf9b7 100644 --- a/src/backend/distributed/deparser/qualify_view_stmt.c +++ b/src/backend/distributed/deparser/qualify_view_stmt.c @@ -18,6 +18,8 @@ #include "utils/guc.h" #include "utils/lsyscache.h" +static void QualifyViewRangeVar(RangeVar *view); + /* * QualifyDropViewStmt quailifies the view names of the DROP VIEW statement. */ @@ -52,3 +54,54 @@ QualifyDropViewStmt(Node *node) stmt->objects = qualifiedViewNames; } + + +/* + * QualifyAlterViewStmt quailifies the view name of the ALTER VIEW statement. + */ +void +QualifyAlterViewStmt(Node *node) +{ + AlterTableStmt *stmt = castNode(AlterTableStmt, node); + RangeVar *view = stmt->relation; + QualifyViewRangeVar(view); +} + + +/* + * QualifyRenameViewStmt quailifies the view name of the ALTER VIEW ... RENAME statement. + */ +void +QualifyRenameViewStmt(Node *node) +{ + RenameStmt *stmt = castNode(RenameStmt, node); + RangeVar *view = stmt->relation; + QualifyViewRangeVar(view); +} + + +/* + * QualifyAlterViewSchemaStmt quailifies the view name of the ALTER VIEW ... SET SCHEMA statement. + */ +void +QualifyAlterViewSchemaStmt(Node *node) +{ + AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node); + RangeVar *view = stmt->relation; + QualifyViewRangeVar(view); +} + + +/* + * QualifyViewRangeVar qualifies the given view RangeVar if it is not qualified. + */ +static void +QualifyViewRangeVar(RangeVar *view) +{ + if (view->schemaname == NULL) + { + Oid viewOid = RelnameGetRelid(view->relname); + Oid schemaOid = get_rel_namespace(viewOid); + view->schemaname = get_namespace_name(schemaOid); + } +} diff --git a/src/backend/distributed/metadata/dependency.c b/src/backend/distributed/metadata/dependency.c index 823552788..e4a863fdc 100644 --- a/src/backend/distributed/metadata/dependency.c +++ b/src/backend/distributed/metadata/dependency.c @@ -766,6 +766,58 @@ SupportedDependencyByCitus(const ObjectAddress *address) } +/* + * ErrorOrWarnIfObjectHasUnsupportedDependency returns false without throwing any message if + * object doesn't have any unsupported dependency, else throws a message with proper level + * (except the cluster doesn't have any node) and return true. + */ +bool +ErrorOrWarnIfObjectHasUnsupportedDependency(ObjectAddress *objectAddress) +{ + DeferredErrorMessage *errMsg = DeferErrorIfHasUnsupportedDependency(objectAddress); + if (errMsg != NULL) + { + /* + * Don't need to give any messages if there is no worker nodes in + * the cluster as user's experience won't be affected on the single node even + * if the object won't be distributed. + */ + if (!HasAnyNodes()) + { + return true; + } + + /* + * Since Citus drops and recreates some object while converting a table type + * giving a DEBUG1 message is enough if the process in table type conversion + * function call + */ + if (InTableTypeConversionFunctionCall) + { + RaiseDeferredError(errMsg, DEBUG1); + } + /* + * If the view is object distributed, we should provide an error to not have + * different definition of object on coordinator and worker nodes. If the object + * is not distributed yet, we can create it locally to not affect user's local + * usage experience. + */ + else if (IsObjectDistributed(objectAddress)) + { + RaiseDeferredError(errMsg, ERROR); + } + else + { + RaiseDeferredError(errMsg, WARNING); + } + + return true; + } + + return false; +} + + /* * DeferErrorIfHasUnsupportedDependency returns deferred error message if the given * object has any undistributable dependency. diff --git a/src/include/distributed/commands.h b/src/include/distributed/commands.h index f5af6cc4a..8a027146d 100644 --- a/src/include/distributed/commands.h +++ b/src/include/distributed/commands.h @@ -658,12 +658,25 @@ extern List * PreprocessViewStmt(Node *node, const char *queryString, ProcessUtilityContext processUtilityContext); extern List * PostprocessViewStmt(Node *node, const char *queryString); extern ObjectAddress ViewStmtObjectAddress(Node *node, bool missing_ok); +extern ObjectAddress AlterViewStmtObjectAddress(Node *node, bool missing_ok); extern List * PreprocessDropViewStmt(Node *node, const char *queryString, ProcessUtilityContext processUtilityContext); extern char * CreateViewDDLCommand(Oid viewOid); extern char * AlterViewOwnerCommand(Oid viewOid); extern char * DeparseViewStmt(Node *node); extern char * DeparseDropViewStmt(Node *node); +extern List * CreateViewDDLCommandsIdempotent(Oid viewOid); +extern List * PreprocessAlterViewStmt(Node *node, const char *queryString, + ProcessUtilityContext processUtilityContext); +extern List * PostprocessAlterViewStmt(Node *node, const char *queryString); +extern List * PreprocessRenameViewStmt(Node *node, const char *queryString, + ProcessUtilityContext processUtilityContext); +extern ObjectAddress RenameViewStmtObjectAddress(Node *node, bool missing_ok); +extern List * PreprocessAlterViewSchemaStmt(Node *node, const char *queryString, + ProcessUtilityContext processUtilityContext); +extern List * PostprocessAlterViewSchemaStmt(Node *node, const char *queryString); +extern ObjectAddress AlterViewSchemaStmtObjectAddress(Node *node, bool missing_ok); +extern bool IsViewRenameStmt(RenameStmt *renameStmt); /* trigger.c - forward declarations */ extern List * GetExplicitTriggerCommandList(Oid relationId); diff --git a/src/include/distributed/deparser.h b/src/include/distributed/deparser.h index 42d1eb954..86bc9cc3d 100644 --- a/src/include/distributed/deparser.h +++ b/src/include/distributed/deparser.h @@ -146,7 +146,14 @@ extern ObjectAddress RenameAttributeStmtObjectAddress(Node *stmt, bool missing_o /* forward declarations for deparse_view_stmts.c */ extern void QualifyDropViewStmt(Node *node); -extern void AppendViewDefinitionToCreateViewCommand(StringInfo buf, Oid viewOid); +extern void QualifyAlterViewStmt(Node *node); +extern void QualifyRenameViewStmt(Node *node); +extern void QualifyAlterViewSchemaStmt(Node *node); +extern char * DeparseRenameViewStmt(Node *stmt); +extern char * DeparseAlterViewStmt(Node *node); +extern char * DeparseDropViewStmt(Node *node); +extern char * DeparseAlterViewSchemaStmt(Node *node); + /* forward declarations for deparse_function_stmts.c */ extern char * DeparseDropFunctionStmt(Node *stmt); diff --git a/src/include/distributed/metadata/dependency.h b/src/include/distributed/metadata/dependency.h index af11c5f2a..32016afc7 100644 --- a/src/include/distributed/metadata/dependency.h +++ b/src/include/distributed/metadata/dependency.h @@ -22,6 +22,7 @@ extern List * GetUniqueDependenciesList(List *objectAddressesList); extern List * GetDependenciesForObject(const ObjectAddress *target); extern List * GetAllSupportedDependenciesForObject(const ObjectAddress *target); extern List * GetAllDependenciesForObject(const ObjectAddress *target); +extern bool ErrorOrWarnIfObjectHasUnsupportedDependency(ObjectAddress *objectAddress); extern DeferredErrorMessage * DeferErrorIfHasUnsupportedDependency(const ObjectAddress * objectAddress); diff --git a/src/test/regress/expected/view_propagation.out b/src/test/regress/expected/view_propagation.out index 22c287b6e..e0f3e2075 100644 --- a/src/test/regress/expected/view_propagation.out +++ b/src/test/regress/expected/view_propagation.out @@ -435,6 +435,135 @@ ERROR: cannot change name of view column "id" to "a" HINT: Use ALTER VIEW ... RENAME COLUMN ... to change name of view column instead. CREATE OR REPLACE VIEW view_for_unsup_commands AS SELECT id FROM table_to_test_unsup_view; ERROR: cannot drop columns from view +-- ALTER VIEW PROPAGATION +CREATE TABLE alter_view_table(id int, val1 text); +SELECT create_distributed_table('alter_view_table','id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE VIEW alter_view_1 AS SELECT * FROM alter_view_table; +-- Set/drop default value is not supported by Citus +ALTER VIEW alter_view_1 ALTER COLUMN val1 SET DEFAULT random()::text; +ERROR: Citus doesn't support setting or resetting default values for a column of view +ALTER TABLE alter_view_1 ALTER COLUMN val1 SET DEFAULT random()::text; +ERROR: Citus doesn't support setting or resetting default values for a column of view +ALTER VIEW alter_view_1 ALTER COLUMN val1 DROP DEFAULT; +ERROR: Citus doesn't support setting or resetting default values for a column of view +ALTER TABLE alter_view_1 ALTER COLUMN val1 DROP DEFAULT; +ERROR: Citus doesn't support setting or resetting default values for a column of view +-- Set/reset options view alter view/alter table commands +ALTER VIEW alter_view_1 SET (check_option=cascaded); +ALTER VIEW alter_view_1 SET (security_barrier); +ALTER VIEW alter_view_1 SET (check_option=cascaded, security_barrier); +ALTER VIEW alter_view_1 SET (check_option=cascaded, security_barrier = true); +ALTER TABLE alter_view_1 SET (check_option=cascaded); +ALTER TABLE alter_view_1 SET (security_barrier); +ALTER TABLE alter_view_1 SET (check_option=cascaded, security_barrier); +ALTER TABLE alter_view_1 SET (check_option=cascaded, security_barrier = true); +-- Check the definition on both coordinator and worker node +SELECT definition FROM pg_views WHERE viewname = 'alter_view_1'; + definition +--------------------------------------------------------------------- + SELECT alter_view_table.id,+ + alter_view_table.val1 + + FROM alter_view_table; +(1 row) + +SELECT relname, reloptions +FROM pg_class +WHERE oid = 'view_prop_schema.alter_view_1'::regclass::oid; + relname | reloptions +--------------------------------------------------------------------- + alter_view_1 | {check_option=cascaded,security_barrier=true} +(1 row) + +\c - - - :worker_1_port +SELECT definition FROM pg_views WHERE viewname = 'alter_view_1'; + definition +--------------------------------------------------------------------- + SELECT alter_view_table.id,+ + alter_view_table.val1 + + FROM view_prop_schema.alter_view_table; +(1 row) + +SELECT relname, reloptions +FROM pg_class +WHERE oid = 'view_prop_schema.alter_view_1'::regclass::oid; + relname | reloptions +--------------------------------------------------------------------- + alter_view_1 | {check_option=cascaded,security_barrier=true} +(1 row) + +\c - - - :master_port +SET search_path to view_prop_schema; +ALTER TABLE alter_view_1 RESET (check_option, security_barrier); +ALTER VIEW alter_view_1 RESET (check_option, security_barrier); +-- Change the schema of the view +ALTER TABLE alter_view_1 SET SCHEMA view_prop_schema_inner; +ALTER VIEW view_prop_schema_inner.alter_view_1 SET SCHEMA view_prop_schema; +-- Rename view and view's column name +ALTER VIEW alter_view_1 RENAME COLUMN val1 TO val2; +ALTER VIEW alter_view_1 RENAME val2 TO val1; +ALTER VIEW alter_view_1 RENAME TO alter_view_2; +ALTER TABLE alter_view_2 RENAME COLUMN val1 TO val2; +ALTER TABLE alter_view_2 RENAME val2 TO val1; +ALTER TABLE alter_view_2 RENAME TO alter_view_1; +-- Alter owner vith alter view/alter table +SET client_min_messages TO ERROR; +CREATE USER alter_view_user; +SELECT 1 FROM run_command_on_workers($$CREATE USER alter_view_user;$$); + ?column? +--------------------------------------------------------------------- + 1 + 1 +(2 rows) + +RESET client_min_messages; +ALTER VIEW alter_view_1 OWNER TO alter_view_user; +ALTER TABLE alter_view_1 OWNER TO alter_view_user; +-- Alter view owned by extension +CREATE TABLE table_for_ext_owned_view(id int); +CREATE VIEW extension_owned_view AS SELECT * FROM table_for_ext_owned_view; +WARNING: "view extension_owned_view" has dependency to "table table_for_ext_owned_view" that is not in Citus' metadata +DETAIL: "view extension_owned_view" will be created only locally +HINT: Distribute "table table_for_ext_owned_view" first to distribute "view extension_owned_view" +CREATE EXTENSION seg; +ALTER EXTENSION seg ADD VIEW extension_owned_view; +NOTICE: Citus does not propagate adding/dropping member objects +HINT: You can add/drop the member objects on the workers as well. +SELECT create_distributed_table('table_for_ext_owned_view','id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE OR REPLACE VIEW extension_owned_view AS SELECT * FROM table_for_ext_owned_view; +-- Since the view is owned by extension Citus shouldn't propagate it +SELECT * FROM (SELECT pg_identify_object_as_address(classid, objid, objsubid) as obj_identifier from pg_catalog.pg_dist_object) as obj_identifiers where obj_identifier::text like '%extension_owned_view%'; + obj_identifier +--------------------------------------------------------------------- +(0 rows) + +-- Try syncing metadata after running ALTER VIEW commands +SELECT start_metadata_sync_to_node('localhost', :worker_1_port); + start_metadata_sync_to_node +--------------------------------------------------------------------- + +(1 row) + +-- Alter non-existing view +ALTER VIEW IF EXISTS non_existing_view ALTER COLUMN val1 SET DEFAULT random()::text; +NOTICE: relation "non_existing_view" does not exist, skipping +ALTER VIEW IF EXISTS non_existing_view SET (check_option=cascaded); +NOTICE: relation "non_existing_view" does not exist, skipping +ALTER VIEW IF EXISTS non_existing_view RENAME COLUMN val1 TO val2; +NOTICE: relation "non_existing_view" does not exist, skipping +ALTER VIEW IF EXISTS non_existing_view RENAME val2 TO val1; +NOTICE: relation "non_existing_view" does not exist, skipping +ALTER VIEW IF EXISTS non_existing_view SET SCHEMA view_prop_schema; +NOTICE: relation "non_existing_view" does not exist, skipping SET client_min_messages TO ERROR; DROP SCHEMA view_prop_schema_inner CASCADE; DROP SCHEMA view_prop_schema CASCADE; diff --git a/src/test/regress/sql/view_propagation.sql b/src/test/regress/sql/view_propagation.sql index deb9f050c..ccdebf5ef 100644 --- a/src/test/regress/sql/view_propagation.sql +++ b/src/test/regress/sql/view_propagation.sql @@ -268,6 +268,94 @@ CREATE VIEW view_for_unsup_commands AS SELECT * FROM table_to_test_unsup_view; CREATE OR REPLACE VIEW view_for_unsup_commands(a,b) AS SELECT * FROM table_to_test_unsup_view; CREATE OR REPLACE VIEW view_for_unsup_commands AS SELECT id FROM table_to_test_unsup_view; +-- ALTER VIEW PROPAGATION +CREATE TABLE alter_view_table(id int, val1 text); +SELECT create_distributed_table('alter_view_table','id'); + +CREATE VIEW alter_view_1 AS SELECT * FROM alter_view_table; + +-- Set/drop default value is not supported by Citus +ALTER VIEW alter_view_1 ALTER COLUMN val1 SET DEFAULT random()::text; +ALTER TABLE alter_view_1 ALTER COLUMN val1 SET DEFAULT random()::text; + +ALTER VIEW alter_view_1 ALTER COLUMN val1 DROP DEFAULT; +ALTER TABLE alter_view_1 ALTER COLUMN val1 DROP DEFAULT; + +-- Set/reset options view alter view/alter table commands +ALTER VIEW alter_view_1 SET (check_option=cascaded); +ALTER VIEW alter_view_1 SET (security_barrier); +ALTER VIEW alter_view_1 SET (check_option=cascaded, security_barrier); +ALTER VIEW alter_view_1 SET (check_option=cascaded, security_barrier = true); + +ALTER TABLE alter_view_1 SET (check_option=cascaded); +ALTER TABLE alter_view_1 SET (security_barrier); +ALTER TABLE alter_view_1 SET (check_option=cascaded, security_barrier); +ALTER TABLE alter_view_1 SET (check_option=cascaded, security_barrier = true); + +-- Check the definition on both coordinator and worker node +SELECT definition FROM pg_views WHERE viewname = 'alter_view_1'; + +SELECT relname, reloptions +FROM pg_class +WHERE oid = 'view_prop_schema.alter_view_1'::regclass::oid; + +\c - - - :worker_1_port +SELECT definition FROM pg_views WHERE viewname = 'alter_view_1'; + +SELECT relname, reloptions +FROM pg_class +WHERE oid = 'view_prop_schema.alter_view_1'::regclass::oid; + +\c - - - :master_port +SET search_path to view_prop_schema; + +ALTER TABLE alter_view_1 RESET (check_option, security_barrier); +ALTER VIEW alter_view_1 RESET (check_option, security_barrier); + +-- Change the schema of the view +ALTER TABLE alter_view_1 SET SCHEMA view_prop_schema_inner; +ALTER VIEW view_prop_schema_inner.alter_view_1 SET SCHEMA view_prop_schema; + +-- Rename view and view's column name +ALTER VIEW alter_view_1 RENAME COLUMN val1 TO val2; +ALTER VIEW alter_view_1 RENAME val2 TO val1; +ALTER VIEW alter_view_1 RENAME TO alter_view_2; + +ALTER TABLE alter_view_2 RENAME COLUMN val1 TO val2; +ALTER TABLE alter_view_2 RENAME val2 TO val1; +ALTER TABLE alter_view_2 RENAME TO alter_view_1; + +-- Alter owner vith alter view/alter table +SET client_min_messages TO ERROR; +CREATE USER alter_view_user; +SELECT 1 FROM run_command_on_workers($$CREATE USER alter_view_user;$$); +RESET client_min_messages; +ALTER VIEW alter_view_1 OWNER TO alter_view_user; +ALTER TABLE alter_view_1 OWNER TO alter_view_user; + +-- Alter view owned by extension +CREATE TABLE table_for_ext_owned_view(id int); +CREATE VIEW extension_owned_view AS SELECT * FROM table_for_ext_owned_view; + +CREATE EXTENSION seg; +ALTER EXTENSION seg ADD VIEW extension_owned_view; + +SELECT create_distributed_table('table_for_ext_owned_view','id'); +CREATE OR REPLACE VIEW extension_owned_view AS SELECT * FROM table_for_ext_owned_view; + +-- Since the view is owned by extension Citus shouldn't propagate it +SELECT * FROM (SELECT pg_identify_object_as_address(classid, objid, objsubid) as obj_identifier from pg_catalog.pg_dist_object) as obj_identifiers where obj_identifier::text like '%extension_owned_view%'; + +-- Try syncing metadata after running ALTER VIEW commands +SELECT start_metadata_sync_to_node('localhost', :worker_1_port); + +-- Alter non-existing view +ALTER VIEW IF EXISTS non_existing_view ALTER COLUMN val1 SET DEFAULT random()::text; +ALTER VIEW IF EXISTS non_existing_view SET (check_option=cascaded); +ALTER VIEW IF EXISTS non_existing_view RENAME COLUMN val1 TO val2; +ALTER VIEW IF EXISTS non_existing_view RENAME val2 TO val1; +ALTER VIEW IF EXISTS non_existing_view SET SCHEMA view_prop_schema; + SET client_min_messages TO ERROR; DROP SCHEMA view_prop_schema_inner CASCADE; DROP SCHEMA view_prop_schema CASCADE;