mirror of https://github.com/citusdata/citus.git
Merge pull request #5914 from citusdata/velioglu/alter_view_propagation
Introduce alter view propagationpull/5881/head
commit
544e6c7428
|
@ -828,6 +828,22 @@ static DistributeObjectOps Type_AlterObjectSchema = {
|
||||||
.address = AlterTypeSchemaStmtObjectAddress,
|
.address = AlterTypeSchemaStmtObjectAddress,
|
||||||
.markDistributed = false,
|
.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 = {
|
static DistributeObjectOps Type_AlterOwner = {
|
||||||
.deparse = DeparseAlterTypeOwnerStmt,
|
.deparse = DeparseAlterTypeOwnerStmt,
|
||||||
.qualify = QualifyAlterTypeOwnerStmt,
|
.qualify = QualifyAlterTypeOwnerStmt,
|
||||||
|
@ -844,6 +860,22 @@ static DistributeObjectOps Type_AlterTable = {
|
||||||
.address = AlterTypeStmtObjectAddress,
|
.address = AlterTypeStmtObjectAddress,
|
||||||
.markDistributed = false,
|
.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 = {
|
static DistributeObjectOps Type_Drop = {
|
||||||
.deparse = DeparseDropTypeStmt,
|
.deparse = DeparseDropTypeStmt,
|
||||||
.qualify = NULL,
|
.qualify = NULL,
|
||||||
|
@ -868,6 +900,21 @@ static DistributeObjectOps Type_Rename = {
|
||||||
.address = RenameTypeStmtObjectAddress,
|
.address = RenameTypeStmtObjectAddress,
|
||||||
.markDistributed = false,
|
.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 = {
|
static DistributeObjectOps Trigger_Rename = {
|
||||||
.deparse = NULL,
|
.deparse = NULL,
|
||||||
.qualify = NULL,
|
.qualify = NULL,
|
||||||
|
@ -1021,6 +1068,11 @@ GetDistributeObjectOps(Node *node)
|
||||||
return &Type_AlterObjectSchema;
|
return &Type_AlterObjectSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case OBJECT_VIEW:
|
||||||
|
{
|
||||||
|
return &View_AlterObjectSchema;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
return &NoDistributeOps;
|
return &NoDistributeOps;
|
||||||
|
@ -1157,6 +1209,11 @@ GetDistributeObjectOps(Node *node)
|
||||||
return &Sequence_AlterOwner;
|
return &Sequence_AlterOwner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case OBJECT_VIEW:
|
||||||
|
{
|
||||||
|
return &View_AlterView;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
return &NoDistributeOps;
|
return &NoDistributeOps;
|
||||||
|
@ -1512,6 +1569,27 @@ GetDistributeObjectOps(Node *node)
|
||||||
return &Trigger_Rename;
|
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:
|
default:
|
||||||
{
|
{
|
||||||
return &Any_Rename;
|
return &Any_Rename;
|
||||||
|
|
|
@ -36,11 +36,12 @@ PreprocessRenameStmt(Node *node, const char *renameCommand,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We only support some of the PostgreSQL supported RENAME statements, and
|
* 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) &&
|
if (!IsAlterTableRenameStmt(renameStmt) &&
|
||||||
!IsIndexRenameStmt(renameStmt) &&
|
!IsIndexRenameStmt(renameStmt) &&
|
||||||
!IsPolicyRenameStmt(renameStmt))
|
!IsPolicyRenameStmt(renameStmt) &&
|
||||||
|
!IsViewRenameStmt(renameStmt))
|
||||||
{
|
{
|
||||||
return NIL;
|
return NIL;
|
||||||
}
|
}
|
||||||
|
@ -48,7 +49,7 @@ PreprocessRenameStmt(Node *node, const char *renameCommand,
|
||||||
/*
|
/*
|
||||||
* The lock levels here should be same as the ones taken in
|
* The lock levels here should be same as the ones taken in
|
||||||
* RenameRelation(), renameatt() and RenameConstraint(). However, since all
|
* 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,
|
objectRelationId = RangeVarGetRelid(renameStmt->relation,
|
||||||
AccessExclusiveLock,
|
AccessExclusiveLock,
|
||||||
|
@ -63,14 +64,31 @@ PreprocessRenameStmt(Node *node, const char *renameCommand,
|
||||||
return NIL;
|
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);
|
RenameStmt *stmtCopy = copyObject(renameStmt);
|
||||||
stmtCopy->renameType = OBJECT_SEQUENCE;
|
stmtCopy->renameType = OBJECT_SEQUENCE;
|
||||||
return PreprocessRenameSequenceStmt((Node *) stmtCopy, renameCommand,
|
return PreprocessRenameSequenceStmt((Node *) stmtCopy, renameCommand,
|
||||||
processUtilityContext);
|
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 */
|
/* we have no planning to do unless the table is distributed */
|
||||||
switch (renameStmt->renameType)
|
switch (renameStmt->renameType)
|
||||||
|
|
|
@ -651,12 +651,21 @@ PostprocessAlterTableSchemaStmt(Node *node, const char *queryString)
|
||||||
*/
|
*/
|
||||||
ObjectAddress tableAddress = GetObjectAddressFromParseTree((Node *) stmt, true);
|
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;
|
stmt->objectType = OBJECT_SEQUENCE;
|
||||||
return PostprocessAlterSequenceSchemaStmt((Node *) stmt, queryString);
|
return PostprocessAlterSequenceSchemaStmt((Node *) stmt, queryString);
|
||||||
}
|
}
|
||||||
|
else if (relKind == RELKIND_VIEW)
|
||||||
|
{
|
||||||
|
stmt->objectType = OBJECT_VIEW;
|
||||||
|
return PostprocessAlterViewSchemaStmt((Node *) stmt, queryString);
|
||||||
|
}
|
||||||
|
|
||||||
if (!ShouldPropagate() || !IsCitusTable(tableAddress.objectId))
|
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
|
* 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
|
* 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);
|
AlterTableStmt *stmtCopy = copyObject(alterTableStatement);
|
||||||
AlterTableStmtObjType_compat(stmtCopy) = OBJECT_SEQUENCE;
|
AlterTableStmtObjType_compat(stmtCopy) = OBJECT_SEQUENCE;
|
||||||
return PreprocessAlterSequenceOwnerStmt((Node *) stmtCopy, alterTableCommand,
|
return PreprocessAlterSequenceOwnerStmt((Node *) stmtCopy, alterTableCommand,
|
||||||
processUtilityContext);
|
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
|
* AlterTableStmt applies also to INDEX relations, and we have support for
|
||||||
|
@ -1758,18 +1775,31 @@ PreprocessAlterTableSchemaStmt(Node *node, const char *queryString,
|
||||||
{
|
{
|
||||||
return NIL;
|
return NIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt,
|
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt,
|
||||||
stmt->missing_ok);
|
stmt->missing_ok);
|
||||||
Oid relationId = address.objectId;
|
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);
|
AlterObjectSchemaStmt *stmtCopy = copyObject(stmt);
|
||||||
stmtCopy->objectType = OBJECT_SEQUENCE;
|
stmtCopy->objectType = OBJECT_SEQUENCE;
|
||||||
return PreprocessAlterSequenceSchemaStmt((Node *) stmtCopy, queryString,
|
return PreprocessAlterSequenceSchemaStmt((Node *) stmtCopy, queryString,
|
||||||
processUtilityContext);
|
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 */
|
/* first check whether a distributed relation is affected */
|
||||||
if (!OidIsValid(relationId) || !IsCitusTable(relationId))
|
if (!OidIsValid(relationId) || !IsCitusTable(relationId))
|
||||||
|
@ -1939,12 +1969,19 @@ PostprocessAlterTableStmt(AlterTableStmt *alterTableStatement)
|
||||||
* since this is the only ALTER command of a sequence that
|
* since this is the only ALTER command of a sequence that
|
||||||
* passes through an AlterTableStmt
|
* 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;
|
AlterTableStmtObjType_compat(alterTableStatement) = OBJECT_SEQUENCE;
|
||||||
PostprocessAlterSequenceOwnerStmt((Node *) alterTableStatement, NULL);
|
PostprocessAlterSequenceOwnerStmt((Node *) alterTableStatement, NULL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else if (relKind == RELKIND_VIEW)
|
||||||
|
{
|
||||||
|
AlterTableStmtObjType_compat(alterTableStatement) = OBJECT_VIEW;
|
||||||
|
PostprocessAlterViewStmt((Node *) alterTableStatement, NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Before ensuring each dependency exist, update dependent sequences
|
* Before ensuring each dependency exist, update dependent sequences
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
|
|
||||||
static List * FilterNameListForDistributedViews(List *viewNamesList, bool missing_ok);
|
static List * FilterNameListForDistributedViews(List *viewNamesList, bool missing_ok);
|
||||||
static void AppendQualifiedViewNameToCreateViewCommand(StringInfo buf, Oid viewOid);
|
static void AppendQualifiedViewNameToCreateViewCommand(StringInfo buf, Oid viewOid);
|
||||||
|
static void AppendViewDefinitionToCreateViewCommand(StringInfo buf, Oid viewOid);
|
||||||
static void AppendAliasesToCreateViewCommand(StringInfo createViewCommand, Oid viewOid);
|
static void AppendAliasesToCreateViewCommand(StringInfo createViewCommand, Oid viewOid);
|
||||||
static void AppendOptionsToCreateViewCommand(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 */
|
/* If the view has any unsupported dependency, create it locally */
|
||||||
DeferredErrorMessage *errMsg = DeferErrorIfHasUnsupportedDependency(&viewAddress);
|
if (ErrorOrWarnIfObjectHasUnsupportedDependency(&viewAddress))
|
||||||
|
|
||||||
if (errMsg != NULL)
|
|
||||||
{
|
{
|
||||||
/*
|
return NIL;
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureDependenciesExistOnAllNodes(&viewAddress);
|
EnsureDependenciesExistOnAllNodes(&viewAddress);
|
||||||
|
@ -409,7 +374,7 @@ AppendOptionsToCreateViewCommand(StringInfo createViewCommand, Oid viewOid)
|
||||||
* AppendViewDefinitionToCreateViewCommand adds the definition of the given view to the
|
* AppendViewDefinitionToCreateViewCommand adds the definition of the given view to the
|
||||||
* given create view command.
|
* given create view command.
|
||||||
*/
|
*/
|
||||||
void
|
static void
|
||||||
AppendViewDefinitionToCreateViewCommand(StringInfo buf, Oid viewOid)
|
AppendViewDefinitionToCreateViewCommand(StringInfo buf, Oid viewOid)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
@ -460,3 +425,250 @@ AlterViewOwnerCommand(Oid viewOid)
|
||||||
|
|
||||||
return alterOwnerCommand->data;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -24,6 +24,13 @@
|
||||||
|
|
||||||
static void AppendDropViewStmt(StringInfo buf, DropStmt *stmt);
|
static void AppendDropViewStmt(StringInfo buf, DropStmt *stmt);
|
||||||
static void AppendViewNameList(StringInfo buf, List *objects);
|
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.
|
* DeparseDropViewStmt deparses the given DROP VIEW statement.
|
||||||
|
@ -92,3 +99,212 @@ AppendViewNameList(StringInfo buf, List *viewNamesList)
|
||||||
isFirstView = false;
|
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));
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
#include "utils/guc.h"
|
#include "utils/guc.h"
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
|
|
||||||
|
static void QualifyViewRangeVar(RangeVar *view);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* QualifyDropViewStmt quailifies the view names of the DROP VIEW statement.
|
* QualifyDropViewStmt quailifies the view names of the DROP VIEW statement.
|
||||||
*/
|
*/
|
||||||
|
@ -52,3 +54,54 @@ QualifyDropViewStmt(Node *node)
|
||||||
|
|
||||||
stmt->objects = qualifiedViewNames;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
* DeferErrorIfHasUnsupportedDependency returns deferred error message if the given
|
||||||
* object has any undistributable dependency.
|
* object has any undistributable dependency.
|
||||||
|
|
|
@ -658,12 +658,25 @@ extern List * PreprocessViewStmt(Node *node, const char *queryString,
|
||||||
ProcessUtilityContext processUtilityContext);
|
ProcessUtilityContext processUtilityContext);
|
||||||
extern List * PostprocessViewStmt(Node *node, const char *queryString);
|
extern List * PostprocessViewStmt(Node *node, const char *queryString);
|
||||||
extern ObjectAddress ViewStmtObjectAddress(Node *node, bool missing_ok);
|
extern ObjectAddress ViewStmtObjectAddress(Node *node, bool missing_ok);
|
||||||
|
extern ObjectAddress AlterViewStmtObjectAddress(Node *node, bool missing_ok);
|
||||||
extern List * PreprocessDropViewStmt(Node *node, const char *queryString,
|
extern List * PreprocessDropViewStmt(Node *node, const char *queryString,
|
||||||
ProcessUtilityContext processUtilityContext);
|
ProcessUtilityContext processUtilityContext);
|
||||||
extern char * CreateViewDDLCommand(Oid viewOid);
|
extern char * CreateViewDDLCommand(Oid viewOid);
|
||||||
extern char * AlterViewOwnerCommand(Oid viewOid);
|
extern char * AlterViewOwnerCommand(Oid viewOid);
|
||||||
extern char * DeparseViewStmt(Node *node);
|
extern char * DeparseViewStmt(Node *node);
|
||||||
extern char * DeparseDropViewStmt(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 */
|
/* trigger.c - forward declarations */
|
||||||
extern List * GetExplicitTriggerCommandList(Oid relationId);
|
extern List * GetExplicitTriggerCommandList(Oid relationId);
|
||||||
|
|
|
@ -146,7 +146,14 @@ extern ObjectAddress RenameAttributeStmtObjectAddress(Node *stmt, bool missing_o
|
||||||
|
|
||||||
/* forward declarations for deparse_view_stmts.c */
|
/* forward declarations for deparse_view_stmts.c */
|
||||||
extern void QualifyDropViewStmt(Node *node);
|
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 */
|
/* forward declarations for deparse_function_stmts.c */
|
||||||
extern char * DeparseDropFunctionStmt(Node *stmt);
|
extern char * DeparseDropFunctionStmt(Node *stmt);
|
||||||
|
|
|
@ -22,6 +22,7 @@ extern List * GetUniqueDependenciesList(List *objectAddressesList);
|
||||||
extern List * GetDependenciesForObject(const ObjectAddress *target);
|
extern List * GetDependenciesForObject(const ObjectAddress *target);
|
||||||
extern List * GetAllSupportedDependenciesForObject(const ObjectAddress *target);
|
extern List * GetAllSupportedDependenciesForObject(const ObjectAddress *target);
|
||||||
extern List * GetAllDependenciesForObject(const ObjectAddress *target);
|
extern List * GetAllDependenciesForObject(const ObjectAddress *target);
|
||||||
|
extern bool ErrorOrWarnIfObjectHasUnsupportedDependency(ObjectAddress *objectAddress);
|
||||||
extern DeferredErrorMessage * DeferErrorIfHasUnsupportedDependency(const
|
extern DeferredErrorMessage * DeferErrorIfHasUnsupportedDependency(const
|
||||||
ObjectAddress *
|
ObjectAddress *
|
||||||
objectAddress);
|
objectAddress);
|
||||||
|
|
|
@ -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.
|
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;
|
CREATE OR REPLACE VIEW view_for_unsup_commands AS SELECT id FROM table_to_test_unsup_view;
|
||||||
ERROR: cannot drop columns from 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;
|
SET client_min_messages TO ERROR;
|
||||||
DROP SCHEMA view_prop_schema_inner CASCADE;
|
DROP SCHEMA view_prop_schema_inner CASCADE;
|
||||||
DROP SCHEMA view_prop_schema CASCADE;
|
DROP SCHEMA view_prop_schema CASCADE;
|
||||||
|
|
|
@ -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(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;
|
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;
|
SET client_min_messages TO ERROR;
|
||||||
DROP SCHEMA view_prop_schema_inner CASCADE;
|
DROP SCHEMA view_prop_schema_inner CASCADE;
|
||||||
DROP SCHEMA view_prop_schema CASCADE;
|
DROP SCHEMA view_prop_schema CASCADE;
|
||||||
|
|
Loading…
Reference in New Issue