mirror of
https://github.com/postgres/postgres.git
synced 2025-10-27 00:12:01 +03:00
Refactor the handling of the various DropStmt variants so that when multiple
objects are specified, we drop them all in a single performMultipleDeletions call. This makes the RESTRICT/CASCADE checks more relaxed: it's not counted as a cascade if one of the later objects has a dependency on an earlier one. NOTICE messages about such cases go away, too. In passing, fix the permissions check for DROP CONVERSION, which for some reason was never made role-aware, and omitted the namespace-owner exemption too. Alex Hunsaker, with further fiddling by me.
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/conversioncmds.c,v 1.33 2008/03/27 03:57:33 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/conversioncmds.c,v 1.34 2008/06/14 18:04:33 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -99,31 +99,73 @@ CreateConversionCommand(CreateConversionStmt *stmt)
|
||||
* DROP CONVERSION
|
||||
*/
|
||||
void
|
||||
DropConversionCommand(List *name, DropBehavior behavior, bool missing_ok)
|
||||
DropConversionsCommand(DropStmt *drop)
|
||||
{
|
||||
Oid conversionOid;
|
||||
ObjectAddresses *objects;
|
||||
ListCell *cell;
|
||||
|
||||
conversionOid = FindConversionByName(name);
|
||||
if (!OidIsValid(conversionOid))
|
||||
/*
|
||||
* First we identify all the conversions, then we delete them in a single
|
||||
* performMultipleDeletions() call. This is to avoid unwanted
|
||||
* DROP RESTRICT errors if one of the conversions depends on another.
|
||||
* (Not that that is very likely, but we may as well do this consistently.)
|
||||
*/
|
||||
objects = new_object_addresses();
|
||||
|
||||
foreach(cell, drop->objects)
|
||||
{
|
||||
if (!missing_ok)
|
||||
List *name = (List *) lfirst(cell);
|
||||
Oid conversionOid;
|
||||
HeapTuple tuple;
|
||||
Form_pg_conversion con;
|
||||
ObjectAddress object;
|
||||
|
||||
conversionOid = FindConversionByName(name);
|
||||
|
||||
if (!OidIsValid(conversionOid))
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("conversion \"%s\" does not exist",
|
||||
NameListToString(name))));
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(NOTICE,
|
||||
(errmsg("conversion \"%s\" does not exist, skipping",
|
||||
NameListToString(name))));
|
||||
if (!drop->missing_ok)
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("conversion \"%s\" does not exist",
|
||||
NameListToString(name))));
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(NOTICE,
|
||||
(errmsg("conversion \"%s\" does not exist, skipping",
|
||||
NameListToString(name))));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
return;
|
||||
tuple = SearchSysCache(CONVOID,
|
||||
ObjectIdGetDatum(conversionOid),
|
||||
0, 0, 0);
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
elog(ERROR, "cache lookup failed for conversion %u",
|
||||
conversionOid);
|
||||
con = (Form_pg_conversion) GETSTRUCT(tuple);
|
||||
|
||||
/* Permission check: must own conversion or its namespace */
|
||||
if (!pg_conversion_ownercheck(conversionOid, GetUserId()) &&
|
||||
!pg_namespace_ownercheck(con->connamespace, GetUserId()))
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CONVERSION,
|
||||
NameStr(con->conname));
|
||||
|
||||
object.classId = ConversionRelationId;
|
||||
object.objectId = conversionOid;
|
||||
object.objectSubId = 0;
|
||||
|
||||
add_exact_object_address(&object, objects);
|
||||
|
||||
ReleaseSysCache(tuple);
|
||||
}
|
||||
|
||||
ConversionDrop(conversionOid, behavior);
|
||||
performMultipleDeletions(objects, drop->behavior);
|
||||
|
||||
free_object_addresses(objects);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.176 2008/05/12 20:01:59 alvherre Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.177 2008/06/14 18:04:33 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -21,7 +21,6 @@
|
||||
#include "access/transam.h"
|
||||
#include "access/xact.h"
|
||||
#include "catalog/catalog.h"
|
||||
#include "catalog/dependency.h"
|
||||
#include "catalog/heap.h"
|
||||
#include "catalog/index.h"
|
||||
#include "catalog/indexing.h"
|
||||
@@ -1256,33 +1255,6 @@ relationHasPrimaryKey(Relation rel)
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* RemoveIndex
|
||||
* Deletes an index.
|
||||
*/
|
||||
void
|
||||
RemoveIndex(RangeVar *relation, DropBehavior behavior)
|
||||
{
|
||||
Oid indOid;
|
||||
char relkind;
|
||||
ObjectAddress object;
|
||||
|
||||
indOid = RangeVarGetRelid(relation, false);
|
||||
relkind = get_rel_relkind(indOid);
|
||||
if (relkind != RELKIND_INDEX)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("\"%s\" is not an index",
|
||||
relation->relname)));
|
||||
|
||||
object.classId = RelationRelationId;
|
||||
object.objectId = indOid;
|
||||
object.objectSubId = 0;
|
||||
|
||||
performDeletion(&object, behavior);
|
||||
}
|
||||
|
||||
/*
|
||||
* ReindexIndex
|
||||
* Recreate a specific index.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/schemacmds.c,v 1.49 2008/01/03 21:23:15 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/schemacmds.c,v 1.50 2008/06/14 18:04:33 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -148,57 +148,76 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
|
||||
|
||||
|
||||
/*
|
||||
* RemoveSchema
|
||||
* Removes a schema.
|
||||
* RemoveSchemas
|
||||
* Implements DROP SCHEMA.
|
||||
*/
|
||||
void
|
||||
RemoveSchema(List *names, DropBehavior behavior, bool missing_ok)
|
||||
RemoveSchemas(DropStmt *drop)
|
||||
{
|
||||
char *namespaceName;
|
||||
Oid namespaceId;
|
||||
ObjectAddress object;
|
||||
|
||||
if (list_length(names) != 1)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("schema name cannot be qualified")));
|
||||
namespaceName = strVal(linitial(names));
|
||||
|
||||
namespaceId = GetSysCacheOid(NAMESPACENAME,
|
||||
CStringGetDatum(namespaceName),
|
||||
0, 0, 0);
|
||||
if (!OidIsValid(namespaceId))
|
||||
{
|
||||
if (!missing_ok)
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_SCHEMA),
|
||||
errmsg("schema \"%s\" does not exist", namespaceName)));
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(NOTICE,
|
||||
(errmsg("schema \"%s\" does not exist, skipping",
|
||||
namespaceName)));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* Permission check */
|
||||
if (!pg_namespace_ownercheck(namespaceId, GetUserId()))
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE,
|
||||
namespaceName);
|
||||
ObjectAddresses *objects;
|
||||
ListCell *cell;
|
||||
|
||||
/*
|
||||
* Do the deletion. Objects contained in the schema are removed by means
|
||||
* of their dependency links to the schema.
|
||||
* First we identify all the schemas, then we delete them in a single
|
||||
* performMultipleDeletions() call. This is to avoid unwanted
|
||||
* DROP RESTRICT errors if one of the schemas depends on another.
|
||||
*/
|
||||
object.classId = NamespaceRelationId;
|
||||
object.objectId = namespaceId;
|
||||
object.objectSubId = 0;
|
||||
objects = new_object_addresses();
|
||||
|
||||
performDeletion(&object, behavior);
|
||||
foreach(cell, drop->objects)
|
||||
{
|
||||
List *names = (List *) lfirst(cell);
|
||||
char *namespaceName;
|
||||
Oid namespaceId;
|
||||
ObjectAddress object;
|
||||
|
||||
if (list_length(names) != 1)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("schema name cannot be qualified")));
|
||||
namespaceName = strVal(linitial(names));
|
||||
|
||||
namespaceId = GetSysCacheOid(NAMESPACENAME,
|
||||
CStringGetDatum(namespaceName),
|
||||
0, 0, 0);
|
||||
|
||||
if (!OidIsValid(namespaceId))
|
||||
{
|
||||
if (!drop->missing_ok)
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_SCHEMA),
|
||||
errmsg("schema \"%s\" does not exist",
|
||||
namespaceName)));
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(NOTICE,
|
||||
(errmsg("schema \"%s\" does not exist, skipping",
|
||||
namespaceName)));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Permission check */
|
||||
if (!pg_namespace_ownercheck(namespaceId, GetUserId()))
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE,
|
||||
namespaceName);
|
||||
|
||||
object.classId = NamespaceRelationId;
|
||||
object.objectId = namespaceId;
|
||||
object.objectSubId = 0;
|
||||
|
||||
add_exact_object_address(&object, objects);
|
||||
}
|
||||
|
||||
/*
|
||||
* Do the deletions. Objects contained in the schema(s) are removed by
|
||||
* means of their dependency links to the schema.
|
||||
*/
|
||||
performMultipleDeletions(objects, drop->behavior);
|
||||
|
||||
free_object_addresses(objects);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.255 2008/05/19 04:14:24 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.256 2008/06/14 18:04:33 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -61,6 +61,7 @@
|
||||
#include "rewrite/rewriteDefine.h"
|
||||
#include "rewrite/rewriteHandler.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "storage/lmgr.h"
|
||||
#include "storage/smgr.h"
|
||||
#include "utils/acl.h"
|
||||
#include "utils/builtins.h"
|
||||
@@ -168,6 +169,53 @@ typedef struct NewColumnValue
|
||||
ExprState *exprstate; /* execution state */
|
||||
} NewColumnValue;
|
||||
|
||||
/*
|
||||
* Error-reporting support for RemoveRelations
|
||||
*/
|
||||
struct dropmsgstrings
|
||||
{
|
||||
char kind;
|
||||
int nonexistent_code;
|
||||
const char *nonexistent_msg;
|
||||
const char *skipping_msg;
|
||||
const char *nota_msg;
|
||||
const char *drophint_msg;
|
||||
};
|
||||
|
||||
static const struct dropmsgstrings dropmsgstringarray[] = {
|
||||
{RELKIND_RELATION,
|
||||
ERRCODE_UNDEFINED_TABLE,
|
||||
gettext_noop("table \"%s\" does not exist"),
|
||||
gettext_noop("table \"%s\" does not exist, skipping"),
|
||||
gettext_noop("\"%s\" is not a table"),
|
||||
gettext_noop("Use DROP TABLE to remove a table.")},
|
||||
{RELKIND_SEQUENCE,
|
||||
ERRCODE_UNDEFINED_TABLE,
|
||||
gettext_noop("sequence \"%s\" does not exist"),
|
||||
gettext_noop("sequence \"%s\" does not exist, skipping"),
|
||||
gettext_noop("\"%s\" is not a sequence"),
|
||||
gettext_noop("Use DROP SEQUENCE to remove a sequence.")},
|
||||
{RELKIND_VIEW,
|
||||
ERRCODE_UNDEFINED_TABLE,
|
||||
gettext_noop("view \"%s\" does not exist"),
|
||||
gettext_noop("view \"%s\" does not exist, skipping"),
|
||||
gettext_noop("\"%s\" is not a view"),
|
||||
gettext_noop("Use DROP VIEW to remove a view.")},
|
||||
{RELKIND_INDEX,
|
||||
ERRCODE_UNDEFINED_OBJECT,
|
||||
gettext_noop("index \"%s\" does not exist"),
|
||||
gettext_noop("index \"%s\" does not exist, skipping"),
|
||||
gettext_noop("\"%s\" is not an index"),
|
||||
gettext_noop("Use DROP INDEX to remove an index.")},
|
||||
{RELKIND_COMPOSITE_TYPE,
|
||||
ERRCODE_UNDEFINED_OBJECT,
|
||||
gettext_noop("type \"%s\" does not exist"),
|
||||
gettext_noop("type \"%s\" does not exist, skipping"),
|
||||
gettext_noop("\"%s\" is not a type"),
|
||||
gettext_noop("Use DROP TYPE to remove a type.")},
|
||||
{'\0', 0, NULL, NULL, NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
static void truncate_check_rel(Relation rel);
|
||||
static List *MergeAttributes(List *schema, List *supers, bool istemp,
|
||||
@@ -497,22 +545,175 @@ DefineRelation(CreateStmt *stmt, char relkind)
|
||||
}
|
||||
|
||||
/*
|
||||
* RemoveRelation
|
||||
* Deletes a relation.
|
||||
* Emit the right error or warning message for a "DROP" command issued on a
|
||||
* non-existent relation
|
||||
*/
|
||||
static void
|
||||
DropErrorMsgNonExistent(const char *relname, char rightkind, bool missing_ok)
|
||||
{
|
||||
const struct dropmsgstrings *rentry;
|
||||
|
||||
for (rentry = dropmsgstringarray; rentry->kind != '\0'; rentry++)
|
||||
{
|
||||
if (rentry->kind == rightkind)
|
||||
{
|
||||
if (!missing_ok)
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(rentry->nonexistent_code),
|
||||
errmsg(rentry->nonexistent_msg, relname)));
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(NOTICE, (errmsg(rentry->skipping_msg, relname)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Assert(rentry->kind != '\0'); /* Should be impossible */
|
||||
}
|
||||
|
||||
/*
|
||||
* Emit the right error message for a "DROP" command issued on a
|
||||
* relation of the wrong type
|
||||
*/
|
||||
static void
|
||||
DropErrorMsgWrongType(const char *relname, char wrongkind, char rightkind)
|
||||
{
|
||||
const struct dropmsgstrings *rentry;
|
||||
const struct dropmsgstrings *wentry;
|
||||
|
||||
for (rentry = dropmsgstringarray; rentry->kind != '\0'; rentry++)
|
||||
if (rentry->kind == rightkind)
|
||||
break;
|
||||
Assert(rentry->kind != '\0');
|
||||
|
||||
for (wentry = dropmsgstringarray; wentry->kind != '\0'; wentry++)
|
||||
if (wentry->kind == wrongkind)
|
||||
break;
|
||||
/* wrongkind could be something we don't have in our table... */
|
||||
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg(rentry->nota_msg, relname),
|
||||
(wentry->kind != '\0') ? errhint(wentry->drophint_msg) : 0));
|
||||
}
|
||||
|
||||
/*
|
||||
* RemoveRelations
|
||||
* Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW
|
||||
*/
|
||||
void
|
||||
RemoveRelation(const RangeVar *relation, DropBehavior behavior)
|
||||
RemoveRelations(DropStmt *drop)
|
||||
{
|
||||
Oid relOid;
|
||||
ObjectAddress object;
|
||||
ObjectAddresses *objects;
|
||||
char relkind;
|
||||
ListCell *cell;
|
||||
|
||||
relOid = RangeVarGetRelid(relation, false);
|
||||
/*
|
||||
* First we identify all the relations, then we delete them in a single
|
||||
* performMultipleDeletions() call. This is to avoid unwanted
|
||||
* DROP RESTRICT errors if one of the relations depends on another.
|
||||
*/
|
||||
|
||||
object.classId = RelationRelationId;
|
||||
object.objectId = relOid;
|
||||
object.objectSubId = 0;
|
||||
/* Determine required relkind */
|
||||
switch (drop->removeType)
|
||||
{
|
||||
case OBJECT_TABLE:
|
||||
relkind = RELKIND_RELATION;
|
||||
break;
|
||||
|
||||
performDeletion(&object, behavior);
|
||||
case OBJECT_INDEX:
|
||||
relkind = RELKIND_INDEX;
|
||||
break;
|
||||
|
||||
case OBJECT_SEQUENCE:
|
||||
relkind = RELKIND_SEQUENCE;
|
||||
break;
|
||||
|
||||
case OBJECT_VIEW:
|
||||
relkind = RELKIND_VIEW;
|
||||
break;
|
||||
|
||||
default:
|
||||
elog(ERROR, "unrecognized drop object type: %d",
|
||||
(int) drop->removeType);
|
||||
relkind = 0; /* keep compiler quiet */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Lock and validate each relation; build a list of object addresses */
|
||||
objects = new_object_addresses();
|
||||
|
||||
foreach(cell, drop->objects)
|
||||
{
|
||||
RangeVar *rel = makeRangeVarFromNameList((List *) lfirst(cell));
|
||||
Oid relOid;
|
||||
HeapTuple tuple;
|
||||
Form_pg_class classform;
|
||||
ObjectAddress obj;
|
||||
|
||||
/*
|
||||
* These next few steps are a great deal like relation_openrv, but we
|
||||
* don't bother building a relcache entry since we don't need it.
|
||||
*
|
||||
* Check for shared-cache-inval messages before trying to access the
|
||||
* relation. This is needed to cover the case where the name
|
||||
* identifies a rel that has been dropped and recreated since the
|
||||
* start of our transaction: if we don't flush the old syscache entry,
|
||||
* then we'll latch onto that entry and suffer an error later.
|
||||
*/
|
||||
AcceptInvalidationMessages();
|
||||
|
||||
/* Look up the appropriate relation using namespace search */
|
||||
relOid = RangeVarGetRelid(rel, true);
|
||||
|
||||
/* Not there? */
|
||||
if (!OidIsValid(relOid))
|
||||
{
|
||||
DropErrorMsgNonExistent(rel->relname, relkind, drop->missing_ok);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Get the lock before trying to fetch the syscache entry */
|
||||
LockRelationOid(relOid, AccessExclusiveLock);
|
||||
|
||||
tuple = SearchSysCache(RELOID,
|
||||
ObjectIdGetDatum(relOid),
|
||||
0, 0, 0);
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
elog(ERROR, "cache lookup failed for relation %u", relOid);
|
||||
classform = (Form_pg_class) GETSTRUCT(tuple);
|
||||
|
||||
if (classform->relkind != relkind)
|
||||
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
|
||||
|
||||
/* Allow DROP to either table owner or schema owner */
|
||||
if (!pg_class_ownercheck(relOid, GetUserId()) &&
|
||||
!pg_namespace_ownercheck(classform->relnamespace, GetUserId()))
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
|
||||
rel->relname);
|
||||
|
||||
if (!allowSystemTableMods && IsSystemClass(classform))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
||||
errmsg("permission denied: \"%s\" is a system catalog",
|
||||
rel->relname)));
|
||||
|
||||
/* OK, we're ready to delete this one */
|
||||
obj.classId = RelationRelationId;
|
||||
obj.objectId = relOid;
|
||||
obj.objectSubId = 0;
|
||||
|
||||
add_exact_object_address(&obj, objects);
|
||||
|
||||
ReleaseSysCache(tuple);
|
||||
}
|
||||
|
||||
performMultipleDeletions(objects, drop->behavior);
|
||||
|
||||
free_object_addresses(objects);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/tsearchcmds.c,v 1.11 2008/03/26 21:10:38 alvherre Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/tsearchcmds.c,v 1.12 2008/06/14 18:04:33 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -271,40 +271,59 @@ DefineTSParser(List *names, List *parameters)
|
||||
* DROP TEXT SEARCH PARSER
|
||||
*/
|
||||
void
|
||||
RemoveTSParser(List *names, DropBehavior behavior, bool missing_ok)
|
||||
RemoveTSParsers(DropStmt *drop)
|
||||
{
|
||||
Oid prsOid;
|
||||
ObjectAddress object;
|
||||
ObjectAddresses *objects;
|
||||
ListCell *cell;
|
||||
|
||||
if (!superuser())
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
||||
errmsg("must be superuser to drop text search parsers")));
|
||||
|
||||
prsOid = TSParserGetPrsid(names, true);
|
||||
if (!OidIsValid(prsOid))
|
||||
/*
|
||||
* First we identify all the objects, then we delete them in a single
|
||||
* performMultipleDeletions() call. This is to avoid unwanted
|
||||
* DROP RESTRICT errors if one of the objects depends on another.
|
||||
*/
|
||||
objects = new_object_addresses();
|
||||
|
||||
foreach(cell, drop->objects)
|
||||
{
|
||||
if (!missing_ok)
|
||||
List *names = (List *) lfirst(cell);
|
||||
Oid prsOid;
|
||||
ObjectAddress object;
|
||||
|
||||
prsOid = TSParserGetPrsid(names, true);
|
||||
|
||||
if (!OidIsValid(prsOid))
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("text search parser \"%s\" does not exist",
|
||||
if (!drop->missing_ok)
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("text search parser \"%s\" does not exist",
|
||||
NameListToString(names))));
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(NOTICE,
|
||||
(errmsg("text search parser \"%s\" does not exist, skipping",
|
||||
NameListToString(names))));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(NOTICE,
|
||||
(errmsg("text search parser \"%s\" does not exist, skipping",
|
||||
NameListToString(names))));
|
||||
}
|
||||
return;
|
||||
|
||||
object.classId = TSParserRelationId;
|
||||
object.objectId = prsOid;
|
||||
object.objectSubId = 0;
|
||||
|
||||
add_exact_object_address(&object, objects);
|
||||
}
|
||||
|
||||
object.classId = TSParserRelationId;
|
||||
object.objectId = prsOid;
|
||||
object.objectSubId = 0;
|
||||
performMultipleDeletions(objects, drop->behavior);
|
||||
|
||||
performDeletion(&object, behavior);
|
||||
free_object_addresses(objects);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -613,54 +632,72 @@ RenameTSDictionary(List *oldname, const char *newname)
|
||||
* DROP TEXT SEARCH DICTIONARY
|
||||
*/
|
||||
void
|
||||
RemoveTSDictionary(List *names, DropBehavior behavior, bool missing_ok)
|
||||
RemoveTSDictionaries(DropStmt *drop)
|
||||
{
|
||||
Oid dictOid;
|
||||
ObjectAddress object;
|
||||
HeapTuple tup;
|
||||
Oid namespaceId;
|
||||
ObjectAddresses *objects;
|
||||
ListCell *cell;
|
||||
|
||||
dictOid = TSDictionaryGetDictid(names, true);
|
||||
if (!OidIsValid(dictOid))
|
||||
/*
|
||||
* First we identify all the objects, then we delete them in a single
|
||||
* performMultipleDeletions() call. This is to avoid unwanted
|
||||
* DROP RESTRICT errors if one of the objects depends on another.
|
||||
*/
|
||||
objects = new_object_addresses();
|
||||
|
||||
foreach(cell, drop->objects)
|
||||
{
|
||||
if (!missing_ok)
|
||||
List *names = (List *) lfirst(cell);
|
||||
Oid dictOid;
|
||||
ObjectAddress object;
|
||||
HeapTuple tup;
|
||||
Oid namespaceId;
|
||||
|
||||
dictOid = TSDictionaryGetDictid(names, true);
|
||||
|
||||
if (!OidIsValid(dictOid))
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("text search dictionary \"%s\" does not exist",
|
||||
NameListToString(names))));
|
||||
if (!drop->missing_ok)
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("text search dictionary \"%s\" does not exist",
|
||||
NameListToString(names))));
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(NOTICE,
|
||||
(errmsg("text search dictionary \"%s\" does not exist, skipping",
|
||||
NameListToString(names))));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(NOTICE,
|
||||
(errmsg("text search dictionary \"%s\" does not exist, skipping",
|
||||
NameListToString(names))));
|
||||
}
|
||||
return;
|
||||
|
||||
tup = SearchSysCache(TSDICTOID,
|
||||
ObjectIdGetDatum(dictOid),
|
||||
0, 0, 0);
|
||||
if (!HeapTupleIsValid(tup)) /* should not happen */
|
||||
elog(ERROR, "cache lookup failed for text search dictionary %u",
|
||||
dictOid);
|
||||
|
||||
/* Permission check: must own dictionary or its namespace */
|
||||
namespaceId = ((Form_pg_ts_dict) GETSTRUCT(tup))->dictnamespace;
|
||||
if (!pg_ts_dict_ownercheck(dictOid, GetUserId()) &&
|
||||
!pg_namespace_ownercheck(namespaceId, GetUserId()))
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
|
||||
NameListToString(names));
|
||||
|
||||
object.classId = TSDictionaryRelationId;
|
||||
object.objectId = dictOid;
|
||||
object.objectSubId = 0;
|
||||
|
||||
add_exact_object_address(&object, objects);
|
||||
|
||||
ReleaseSysCache(tup);
|
||||
}
|
||||
|
||||
tup = SearchSysCache(TSDICTOID,
|
||||
ObjectIdGetDatum(dictOid),
|
||||
0, 0, 0);
|
||||
performMultipleDeletions(objects, drop->behavior);
|
||||
|
||||
if (!HeapTupleIsValid(tup)) /* should not happen */
|
||||
elog(ERROR, "cache lookup failed for text search dictionary %u",
|
||||
dictOid);
|
||||
|
||||
/* Permission check: must own dictionary or its namespace */
|
||||
namespaceId = ((Form_pg_ts_dict) GETSTRUCT(tup))->dictnamespace;
|
||||
if (!pg_ts_dict_ownercheck(dictOid, GetUserId()) &&
|
||||
!pg_namespace_ownercheck(namespaceId, GetUserId()))
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
|
||||
NameListToString(names));
|
||||
|
||||
ReleaseSysCache(tup);
|
||||
|
||||
object.classId = TSDictionaryRelationId;
|
||||
object.objectId = dictOid;
|
||||
object.objectSubId = 0;
|
||||
|
||||
performDeletion(&object, behavior);
|
||||
free_object_addresses(objects);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1086,40 +1123,59 @@ RenameTSTemplate(List *oldname, const char *newname)
|
||||
* DROP TEXT SEARCH TEMPLATE
|
||||
*/
|
||||
void
|
||||
RemoveTSTemplate(List *names, DropBehavior behavior, bool missing_ok)
|
||||
RemoveTSTemplates(DropStmt *drop)
|
||||
{
|
||||
Oid tmplOid;
|
||||
ObjectAddress object;
|
||||
ObjectAddresses *objects;
|
||||
ListCell *cell;
|
||||
|
||||
if (!superuser())
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
||||
errmsg("must be superuser to drop text search templates")));
|
||||
|
||||
tmplOid = TSTemplateGetTmplid(names, true);
|
||||
if (!OidIsValid(tmplOid))
|
||||
/*
|
||||
* First we identify all the objects, then we delete them in a single
|
||||
* performMultipleDeletions() call. This is to avoid unwanted
|
||||
* DROP RESTRICT errors if one of the objects depends on another.
|
||||
*/
|
||||
objects = new_object_addresses();
|
||||
|
||||
foreach(cell, drop->objects)
|
||||
{
|
||||
if (!missing_ok)
|
||||
List *names = (List *) lfirst(cell);
|
||||
Oid tmplOid;
|
||||
ObjectAddress object;
|
||||
|
||||
tmplOid = TSTemplateGetTmplid(names, true);
|
||||
|
||||
if (!OidIsValid(tmplOid))
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("text search template \"%s\" does not exist",
|
||||
NameListToString(names))));
|
||||
if (!drop->missing_ok)
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("text search template \"%s\" does not exist",
|
||||
NameListToString(names))));
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(NOTICE,
|
||||
(errmsg("text search template \"%s\" does not exist, skipping",
|
||||
NameListToString(names))));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(NOTICE,
|
||||
(errmsg("text search template \"%s\" does not exist, skipping",
|
||||
NameListToString(names))));
|
||||
}
|
||||
return;
|
||||
|
||||
object.classId = TSTemplateRelationId;
|
||||
object.objectId = tmplOid;
|
||||
object.objectSubId = 0;
|
||||
|
||||
add_exact_object_address(&object, objects);
|
||||
}
|
||||
|
||||
object.classId = TSTemplateRelationId;
|
||||
object.objectId = tmplOid;
|
||||
object.objectSubId = 0;
|
||||
performMultipleDeletions(objects, drop->behavior);
|
||||
|
||||
performDeletion(&object, behavior);
|
||||
free_object_addresses(objects);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1474,48 +1530,66 @@ RenameTSConfiguration(List *oldname, const char *newname)
|
||||
* DROP TEXT SEARCH CONFIGURATION
|
||||
*/
|
||||
void
|
||||
RemoveTSConfiguration(List *names, DropBehavior behavior, bool missing_ok)
|
||||
RemoveTSConfigurations(DropStmt *drop)
|
||||
{
|
||||
Oid cfgOid;
|
||||
Oid namespaceId;
|
||||
ObjectAddress object;
|
||||
HeapTuple tup;
|
||||
ObjectAddresses *objects;
|
||||
ListCell *cell;
|
||||
|
||||
tup = GetTSConfigTuple(names);
|
||||
/*
|
||||
* First we identify all the objects, then we delete them in a single
|
||||
* performMultipleDeletions() call. This is to avoid unwanted
|
||||
* DROP RESTRICT errors if one of the objects depends on another.
|
||||
*/
|
||||
objects = new_object_addresses();
|
||||
|
||||
if (!HeapTupleIsValid(tup))
|
||||
foreach(cell, drop->objects)
|
||||
{
|
||||
if (!missing_ok)
|
||||
List *names = (List *) lfirst(cell);
|
||||
Oid cfgOid;
|
||||
Oid namespaceId;
|
||||
ObjectAddress object;
|
||||
HeapTuple tup;
|
||||
|
||||
tup = GetTSConfigTuple(names);
|
||||
|
||||
if (!HeapTupleIsValid(tup))
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("text search configuration \"%s\" does not exist",
|
||||
NameListToString(names))));
|
||||
if (!drop->missing_ok)
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("text search configuration \"%s\" does not exist",
|
||||
NameListToString(names))));
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(NOTICE,
|
||||
(errmsg("text search configuration \"%s\" does not exist, skipping",
|
||||
NameListToString(names))));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(NOTICE,
|
||||
(errmsg("text search configuration \"%s\" does not exist, skipping",
|
||||
NameListToString(names))));
|
||||
}
|
||||
return;
|
||||
|
||||
/* Permission check: must own configuration or its namespace */
|
||||
cfgOid = HeapTupleGetOid(tup);
|
||||
namespaceId = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgnamespace;
|
||||
if (!pg_ts_config_ownercheck(cfgOid, GetUserId()) &&
|
||||
!pg_namespace_ownercheck(namespaceId, GetUserId()))
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
|
||||
NameListToString(names));
|
||||
|
||||
object.classId = TSConfigRelationId;
|
||||
object.objectId = cfgOid;
|
||||
object.objectSubId = 0;
|
||||
|
||||
add_exact_object_address(&object, objects);
|
||||
|
||||
ReleaseSysCache(tup);
|
||||
}
|
||||
|
||||
/* Permission check: must own configuration or its namespace */
|
||||
cfgOid = HeapTupleGetOid(tup);
|
||||
namespaceId = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgnamespace;
|
||||
if (!pg_ts_config_ownercheck(cfgOid, GetUserId()) &&
|
||||
!pg_namespace_ownercheck(namespaceId, GetUserId()))
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
|
||||
NameListToString(names));
|
||||
performMultipleDeletions(objects, drop->behavior);
|
||||
|
||||
ReleaseSysCache(tup);
|
||||
|
||||
object.classId = TSConfigRelationId;
|
||||
object.objectId = cfgOid;
|
||||
object.objectSubId = 0;
|
||||
|
||||
performDeletion(&object, behavior);
|
||||
free_object_addresses(objects);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.118 2008/05/09 23:32:04 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.119 2008/06/14 18:04:33 tgl Exp $
|
||||
*
|
||||
* DESCRIPTION
|
||||
* The "DefineFoo" routines take the parse tree and pick out the
|
||||
@@ -483,66 +483,93 @@ DefineType(List *names, List *parameters)
|
||||
|
||||
|
||||
/*
|
||||
* RemoveType
|
||||
* Removes a datatype.
|
||||
* RemoveTypes
|
||||
* Implements DROP TYPE and DROP DOMAIN
|
||||
*
|
||||
* Note: if DOMAIN is specified, we enforce that each type is a domain, but
|
||||
* we don't enforce the converse for DROP TYPE
|
||||
*/
|
||||
void
|
||||
RemoveType(List *names, DropBehavior behavior, bool missing_ok)
|
||||
RemoveTypes(DropStmt *drop)
|
||||
{
|
||||
TypeName *typename;
|
||||
Oid typeoid;
|
||||
HeapTuple tup;
|
||||
ObjectAddress object;
|
||||
Form_pg_type typ;
|
||||
ObjectAddresses *objects;
|
||||
ListCell *cell;
|
||||
|
||||
/* Make a TypeName so we can use standard type lookup machinery */
|
||||
typename = makeTypeNameFromNameList(names);
|
||||
/*
|
||||
* First we identify all the types, then we delete them in a single
|
||||
* performMultipleDeletions() call. This is to avoid unwanted
|
||||
* DROP RESTRICT errors if one of the types depends on another.
|
||||
*/
|
||||
objects = new_object_addresses();
|
||||
|
||||
/* Use LookupTypeName here so that shell types can be removed. */
|
||||
tup = LookupTypeName(NULL, typename, NULL);
|
||||
if (tup == NULL)
|
||||
foreach(cell, drop->objects)
|
||||
{
|
||||
if (!missing_ok)
|
||||
List *names = (List *) lfirst(cell);
|
||||
TypeName *typename;
|
||||
Oid typeoid;
|
||||
HeapTuple tup;
|
||||
ObjectAddress object;
|
||||
Form_pg_type typ;
|
||||
|
||||
/* Make a TypeName so we can use standard type lookup machinery */
|
||||
typename = makeTypeNameFromNameList(names);
|
||||
|
||||
/* Use LookupTypeName here so that shell types can be removed. */
|
||||
tup = LookupTypeName(NULL, typename, NULL);
|
||||
if (tup == NULL)
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("type \"%s\" does not exist",
|
||||
TypeNameToString(typename))));
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(NOTICE,
|
||||
(errmsg("type \"%s\" does not exist, skipping",
|
||||
TypeNameToString(typename))));
|
||||
if (!drop->missing_ok)
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("type \"%s\" does not exist",
|
||||
TypeNameToString(typename))));
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(NOTICE,
|
||||
(errmsg("type \"%s\" does not exist, skipping",
|
||||
TypeNameToString(typename))));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
return;
|
||||
typeoid = typeTypeId(tup);
|
||||
typ = (Form_pg_type) GETSTRUCT(tup);
|
||||
|
||||
/* Permission check: must own type or its namespace */
|
||||
if (!pg_type_ownercheck(typeoid, GetUserId()) &&
|
||||
!pg_namespace_ownercheck(typ->typnamespace, GetUserId()))
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE,
|
||||
TypeNameToString(typename));
|
||||
|
||||
if (drop->removeType == OBJECT_DOMAIN)
|
||||
{
|
||||
/* Check that this is actually a domain */
|
||||
if (typ->typtype != TYPTYPE_DOMAIN)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("\"%s\" is not a domain",
|
||||
TypeNameToString(typename))));
|
||||
}
|
||||
|
||||
/*
|
||||
* Note: we need no special check for array types here, as the normal
|
||||
* treatment of internal dependencies handles it just fine
|
||||
*/
|
||||
|
||||
object.classId = TypeRelationId;
|
||||
object.objectId = typeoid;
|
||||
object.objectSubId = 0;
|
||||
|
||||
add_exact_object_address(&object, objects);
|
||||
|
||||
ReleaseSysCache(tup);
|
||||
}
|
||||
|
||||
typeoid = typeTypeId(tup);
|
||||
typ = (Form_pg_type) GETSTRUCT(tup);
|
||||
performMultipleDeletions(objects, drop->behavior);
|
||||
|
||||
/* Permission check: must own type or its namespace */
|
||||
if (!pg_type_ownercheck(typeoid, GetUserId()) &&
|
||||
!pg_namespace_ownercheck(typ->typnamespace, GetUserId()))
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE,
|
||||
TypeNameToString(typename));
|
||||
|
||||
/*
|
||||
* Note: we need no special check for array types here, as the normal
|
||||
* treatment of internal dependencies handles it just fine
|
||||
*/
|
||||
|
||||
ReleaseSysCache(tup);
|
||||
|
||||
/*
|
||||
* Do the deletion
|
||||
*/
|
||||
object.classId = TypeRelationId;
|
||||
object.objectId = typeoid;
|
||||
object.objectSubId = 0;
|
||||
|
||||
performDeletion(&object, behavior);
|
||||
free_object_addresses(objects);
|
||||
}
|
||||
|
||||
|
||||
@@ -923,75 +950,6 @@ DefineDomain(CreateDomainStmt *stmt)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* RemoveDomain
|
||||
* Removes a domain.
|
||||
*
|
||||
* This is identical to RemoveType except we insist it be a domain.
|
||||
*/
|
||||
void
|
||||
RemoveDomain(List *names, DropBehavior behavior, bool missing_ok)
|
||||
{
|
||||
TypeName *typename;
|
||||
Oid typeoid;
|
||||
HeapTuple tup;
|
||||
char typtype;
|
||||
ObjectAddress object;
|
||||
|
||||
/* Make a TypeName so we can use standard type lookup machinery */
|
||||
typename = makeTypeNameFromNameList(names);
|
||||
|
||||
/* Use LookupTypeName here so that shell types can be removed. */
|
||||
tup = LookupTypeName(NULL, typename, NULL);
|
||||
if (tup == NULL)
|
||||
{
|
||||
if (!missing_ok)
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("type \"%s\" does not exist",
|
||||
TypeNameToString(typename))));
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(NOTICE,
|
||||
(errmsg("type \"%s\" does not exist, skipping",
|
||||
TypeNameToString(typename))));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
typeoid = typeTypeId(tup);
|
||||
|
||||
/* Permission check: must own type or its namespace */
|
||||
if (!pg_type_ownercheck(typeoid, GetUserId()) &&
|
||||
!pg_namespace_ownercheck(((Form_pg_type) GETSTRUCT(tup))->typnamespace,
|
||||
GetUserId()))
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE,
|
||||
TypeNameToString(typename));
|
||||
|
||||
/* Check that this is actually a domain */
|
||||
typtype = ((Form_pg_type) GETSTRUCT(tup))->typtype;
|
||||
|
||||
if (typtype != TYPTYPE_DOMAIN)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("\"%s\" is not a domain",
|
||||
TypeNameToString(typename))));
|
||||
|
||||
ReleaseSysCache(tup);
|
||||
|
||||
/*
|
||||
* Do the deletion
|
||||
*/
|
||||
object.classId = TypeRelationId;
|
||||
object.objectId = typeoid;
|
||||
object.objectSubId = 0;
|
||||
|
||||
performDeletion(&object, behavior);
|
||||
}
|
||||
|
||||
/*
|
||||
* DefineEnum
|
||||
* Registers a new enum.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.104 2008/01/01 19:45:49 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.105 2008/06/14 18:04:33 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
#include "access/heapam.h"
|
||||
#include "access/xact.h"
|
||||
#include "catalog/dependency.h"
|
||||
#include "catalog/namespace.h"
|
||||
#include "commands/defrem.h"
|
||||
#include "commands/tablecmds.h"
|
||||
@@ -446,26 +445,3 @@ DefineView(ViewStmt *stmt, const char *queryString)
|
||||
*/
|
||||
DefineViewRules(viewOid, viewParse, stmt->replace);
|
||||
}
|
||||
|
||||
/*
|
||||
* RemoveView
|
||||
*
|
||||
* Remove a view given its name
|
||||
*
|
||||
* We just have to drop the relation; the associated rules will be
|
||||
* cleaned up automatically.
|
||||
*/
|
||||
void
|
||||
RemoveView(const RangeVar *view, DropBehavior behavior)
|
||||
{
|
||||
Oid viewOid;
|
||||
ObjectAddress object;
|
||||
|
||||
viewOid = RangeVarGetRelid(view, false);
|
||||
|
||||
object.classId = RelationRelationId;
|
||||
object.objectId = viewOid;
|
||||
object.objectSubId = 0;
|
||||
|
||||
performDeletion(&object, behavior);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user