Merge pull request #5914 from citusdata/velioglu/alter_view_propagation

Introduce alter view propagation
pull/5881/head
Burak Velioglu 2022-05-13 13:34:25 +03:00 committed by GitHub
commit 544e6c7428
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 957 additions and 53 deletions

View File

@ -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;

View File

@ -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)

View File

@ -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

View File

@ -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;
}

View File

@ -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));
}

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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;