mirror of
https://github.com/postgres/postgres.git
synced 2025-05-29 16:21:20 +03:00
Partial code review for ALTER DOMAIN patch. Incorporates Rod Taylor's
patches of 9-Dec (permissions fix) and 13-Dec (performance) as well as a partial fix for locking issues: concurrent DROP COLUMN should not create trouble anymore. But concurrent DROP TABLE is still a risk, and there is no protection at all against creating a column of a domain while we are altering the domain.
This commit is contained in:
parent
150ffb2d50
commit
17194f4112
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/backend/commands/typecmds.c,v 1.25 2002/12/15 16:17:43 tgl Exp $
|
* $Header: /cvsroot/pgsql/src/backend/commands/typecmds.c,v 1.26 2003/01/04 00:46:08 tgl Exp $
|
||||||
*
|
*
|
||||||
* DESCRIPTION
|
* DESCRIPTION
|
||||||
* The "DefineFoo" routines take the parse tree and pick out the
|
* The "DefineFoo" routines take the parse tree and pick out the
|
||||||
@ -39,6 +39,7 @@
|
|||||||
#include "catalog/indexing.h"
|
#include "catalog/indexing.h"
|
||||||
#include "catalog/namespace.h"
|
#include "catalog/namespace.h"
|
||||||
#include "catalog/pg_constraint.h"
|
#include "catalog/pg_constraint.h"
|
||||||
|
#include "catalog/pg_depend.h"
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
#include "commands/defrem.h"
|
#include "commands/defrem.h"
|
||||||
#include "commands/tablecmds.h"
|
#include "commands/tablecmds.h"
|
||||||
@ -59,20 +60,25 @@
|
|||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
#include "utils/syscache.h"
|
#include "utils/syscache.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* result structure for get_rels_with_domain() */
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
Relation rel; /* opened and locked relation */
|
||||||
|
int natts; /* number of attributes of interest */
|
||||||
|
int *atts; /* attribute numbers */
|
||||||
|
/* atts[] is of allocated length RelationGetNumberOfAttributes(rel) */
|
||||||
|
} RelToCheck;
|
||||||
|
|
||||||
|
|
||||||
static Oid findTypeIOFunction(List *procname, Oid typeOid, bool isOutput);
|
static Oid findTypeIOFunction(List *procname, Oid typeOid, bool isOutput);
|
||||||
static List *get_rels_with_domain(Oid domainOid);
|
static List *get_rels_with_domain(Oid domainOid, LOCKMODE lockmode);
|
||||||
static void domainPermissionCheck(HeapTuple tup, TypeName *typename);
|
static void domainOwnerCheck(HeapTuple tup, TypeName *typename);
|
||||||
static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
|
static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
|
||||||
Oid baseTypeOid,
|
Oid baseTypeOid,
|
||||||
int typMod, Constraint *constr,
|
int typMod, Constraint *constr,
|
||||||
int *counter, char *domainName);
|
int *counter, char *domainName);
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
Oid relOid;
|
|
||||||
int natts;
|
|
||||||
int *atts;
|
|
||||||
} relToCheck;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* DefineType
|
* DefineType
|
||||||
@ -942,7 +948,7 @@ AlterDomainDefault(List *names, Node *defaultRaw)
|
|||||||
TypeNameToString(typename));
|
TypeNameToString(typename));
|
||||||
|
|
||||||
/* Doesn't return if user isn't allowed to alter the domain */
|
/* Doesn't return if user isn't allowed to alter the domain */
|
||||||
domainPermissionCheck(tup, typename);
|
domainOwnerCheck(tup, typename);
|
||||||
|
|
||||||
/* Setup new tuple */
|
/* Setup new tuple */
|
||||||
MemSet(new_record, (Datum) 0, sizeof(new_record));
|
MemSet(new_record, (Datum) 0, sizeof(new_record));
|
||||||
@ -1029,12 +1035,8 @@ AlterDomainNotNull(List *names, bool notNull)
|
|||||||
{
|
{
|
||||||
TypeName *typename;
|
TypeName *typename;
|
||||||
Oid domainoid;
|
Oid domainoid;
|
||||||
|
Relation typrel;
|
||||||
HeapTuple tup;
|
HeapTuple tup;
|
||||||
Relation rel;
|
|
||||||
Datum new_record[Natts_pg_type];
|
|
||||||
char new_record_nulls[Natts_pg_type];
|
|
||||||
char new_record_repl[Natts_pg_type];
|
|
||||||
HeapTuple newtuple;
|
|
||||||
Form_pg_type typTup;
|
Form_pg_type typTup;
|
||||||
|
|
||||||
/* Make a TypeName so we can use standard type lookup machinery */
|
/* Make a TypeName so we can use standard type lookup machinery */
|
||||||
@ -1044,9 +1046,9 @@ AlterDomainNotNull(List *names, bool notNull)
|
|||||||
typename->arrayBounds = NIL;
|
typename->arrayBounds = NIL;
|
||||||
|
|
||||||
/* Lock the type table */
|
/* Lock the type table */
|
||||||
rel = heap_openr(TypeRelationName, RowExclusiveLock);
|
typrel = heap_openr(TypeRelationName, RowExclusiveLock);
|
||||||
|
|
||||||
/* Use LookupTypeName here so that shell types can be removed. */
|
/* Use LookupTypeName here so that shell types can be found (why?). */
|
||||||
domainoid = LookupTypeName(typename);
|
domainoid = LookupTypeName(typename);
|
||||||
if (!OidIsValid(domainoid))
|
if (!OidIsValid(domainoid))
|
||||||
elog(ERROR, "Type \"%s\" does not exist",
|
elog(ERROR, "Type \"%s\" does not exist",
|
||||||
@ -1055,93 +1057,84 @@ AlterDomainNotNull(List *names, bool notNull)
|
|||||||
tup = SearchSysCacheCopy(TYPEOID,
|
tup = SearchSysCacheCopy(TYPEOID,
|
||||||
ObjectIdGetDatum(domainoid),
|
ObjectIdGetDatum(domainoid),
|
||||||
0, 0, 0);
|
0, 0, 0);
|
||||||
|
|
||||||
if (!HeapTupleIsValid(tup))
|
if (!HeapTupleIsValid(tup))
|
||||||
elog(ERROR, "AlterDomain: type \"%s\" does not exist",
|
elog(ERROR, "AlterDomain: type \"%s\" does not exist",
|
||||||
TypeNameToString(typename));
|
TypeNameToString(typename));
|
||||||
|
|
||||||
/* Doesn't return if user isn't allowed to alter the domain */
|
|
||||||
domainPermissionCheck(tup, typename);
|
|
||||||
|
|
||||||
typTup = (Form_pg_type) GETSTRUCT(tup);
|
typTup = (Form_pg_type) GETSTRUCT(tup);
|
||||||
|
|
||||||
/* Is the domain already set to the destination constraint? */
|
/* Doesn't return if user isn't allowed to alter the domain */
|
||||||
|
domainOwnerCheck(tup, typename);
|
||||||
|
|
||||||
|
/* Is the domain already set to the desired constraint? */
|
||||||
if (typTup->typnotnull == notNull)
|
if (typTup->typnotnull == notNull)
|
||||||
elog(ERROR, "AlterDomain: %s is already set to %s",
|
{
|
||||||
|
elog(NOTICE, "AlterDomain: %s is already set to %s",
|
||||||
TypeNameToString(typename),
|
TypeNameToString(typename),
|
||||||
notNull ? "NOT NULL" : "NULL");
|
notNull ? "NOT NULL" : "NULL");
|
||||||
|
heap_close(typrel, RowExclusiveLock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* Adding a NOT NULL constraint requires checking current domains */
|
/* Adding a NOT NULL constraint requires checking existing columns */
|
||||||
if (notNull)
|
if (notNull)
|
||||||
{
|
{
|
||||||
List *rels;
|
List *rels;
|
||||||
List *rt;
|
List *rt;
|
||||||
|
|
||||||
/* Fetch relation list with attributes based on this domain */
|
/* Fetch relation list with attributes based on this domain */
|
||||||
rels = get_rels_with_domain(domainoid);
|
/* ShareLock is sufficient to prevent concurrent data changes */
|
||||||
|
|
||||||
|
rels = get_rels_with_domain(domainoid, ShareLock);
|
||||||
|
|
||||||
foreach (rt, rels)
|
foreach (rt, rels)
|
||||||
{
|
{
|
||||||
Relation typrel;
|
RelToCheck *rtc = (RelToCheck *) lfirst(rt);
|
||||||
HeapTuple tuple;
|
Relation testrel = rtc->rel;
|
||||||
|
TupleDesc tupdesc = RelationGetDescr(testrel);
|
||||||
HeapScanDesc scan;
|
HeapScanDesc scan;
|
||||||
TupleDesc tupdesc;
|
HeapTuple tuple;
|
||||||
relToCheck *rtc = (relToCheck *) lfirst(rt);
|
|
||||||
|
|
||||||
/* Lock relation */
|
/* Scan all tuples in this relation */
|
||||||
typrel = heap_open(rtc->relOid, ExclusiveLock);
|
scan = heap_beginscan(testrel, SnapshotNow, 0, NULL);
|
||||||
|
|
||||||
tupdesc = RelationGetDescr(typrel);
|
|
||||||
|
|
||||||
/* Fetch tuples sequentially */
|
|
||||||
scan = heap_beginscan(typrel, SnapshotNow, 0, NULL);
|
|
||||||
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
|
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
/* Test attributes */
|
/* Test attributes that are of the domain */
|
||||||
for (i = 0; i < rtc->natts; i++)
|
for (i = 0; i < rtc->natts; i++)
|
||||||
{
|
{
|
||||||
|
int attnum = rtc->atts[i];
|
||||||
Datum d;
|
Datum d;
|
||||||
bool isNull;
|
bool isNull;
|
||||||
|
|
||||||
d = heap_getattr(tuple, rtc->atts[i], tupdesc, &isNull);
|
d = heap_getattr(tuple, attnum, tupdesc, &isNull);
|
||||||
|
|
||||||
if (isNull)
|
if (isNull)
|
||||||
elog(ERROR, "ALTER DOMAIN: Relation \"%s\" Attribute \"%s\" "
|
elog(ERROR, "ALTER DOMAIN: Relation \"%s\" attribute \"%s\" contains NULL values",
|
||||||
"contains NULL values",
|
RelationGetRelationName(testrel),
|
||||||
RelationGetRelationName(typrel),
|
NameStr(tupdesc->attrs[attnum - 1]->attname));
|
||||||
NameStr(*attnumAttName(typrel, rtc->atts[i])));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
heap_endscan(scan);
|
heap_endscan(scan);
|
||||||
|
|
||||||
/* Release lock */
|
/* Close each rel after processing, but keep lock */
|
||||||
heap_close(typrel, NoLock);
|
heap_close(testrel, NoLock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Okay to update pg_type row. We can scribble on typTup because it's
|
||||||
|
* a copy.
|
||||||
|
*/
|
||||||
|
typTup->typnotnull = notNull;
|
||||||
|
|
||||||
/* Setup new tuple */
|
simple_heap_update(typrel, &tup->t_self, tup);
|
||||||
MemSet(new_record, (Datum) 0, sizeof(new_record));
|
|
||||||
MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
|
|
||||||
MemSet(new_record_repl, ' ', sizeof(new_record_repl));
|
|
||||||
|
|
||||||
new_record[Anum_pg_type_typnotnull - 1] = BoolGetDatum(notNull);
|
CatalogUpdateIndexes(typrel, tup);
|
||||||
new_record_repl[Anum_pg_type_typnotnull - 1] = 'r';
|
|
||||||
|
|
||||||
/* Build the new tuple */
|
|
||||||
newtuple = heap_modifytuple(tup, rel,
|
|
||||||
new_record, new_record_nulls, new_record_repl);
|
|
||||||
|
|
||||||
simple_heap_update(rel, &tup->t_self, newtuple);
|
|
||||||
|
|
||||||
CatalogUpdateIndexes(rel, newtuple);
|
|
||||||
|
|
||||||
/* Clean up */
|
/* Clean up */
|
||||||
heap_close(rel, NoLock);
|
heap_freetuple(tup);
|
||||||
heap_freetuple(newtuple);
|
heap_close(typrel, RowExclusiveLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1186,7 +1179,7 @@ AlterDomainDropConstraint(List *names, const char *constrName, DropBehavior beha
|
|||||||
TypeNameToString(typename));
|
TypeNameToString(typename));
|
||||||
|
|
||||||
/* Doesn't return if user isn't allowed to alter the domain */
|
/* Doesn't return if user isn't allowed to alter the domain */
|
||||||
domainPermissionCheck(tup, typename);
|
domainOwnerCheck(tup, typename);
|
||||||
|
|
||||||
/* Grab an appropriate lock on the pg_constraint relation */
|
/* Grab an appropriate lock on the pg_constraint relation */
|
||||||
conrel = heap_openr(ConstraintRelationName, RowExclusiveLock);
|
conrel = heap_openr(ConstraintRelationName, RowExclusiveLock);
|
||||||
@ -1236,11 +1229,11 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
|
|||||||
{
|
{
|
||||||
TypeName *typename;
|
TypeName *typename;
|
||||||
Oid domainoid;
|
Oid domainoid;
|
||||||
|
Relation typrel;
|
||||||
HeapTuple tup;
|
HeapTuple tup;
|
||||||
Relation rel;
|
Form_pg_type typTup;
|
||||||
List *rels;
|
List *rels;
|
||||||
List *rt;
|
List *rt;
|
||||||
Form_pg_type typTup;
|
|
||||||
EState *estate;
|
EState *estate;
|
||||||
ExprContext *econtext;
|
ExprContext *econtext;
|
||||||
char *ccbin;
|
char *ccbin;
|
||||||
@ -1256,9 +1249,9 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
|
|||||||
typename->arrayBounds = NIL;
|
typename->arrayBounds = NIL;
|
||||||
|
|
||||||
/* Lock the type table */
|
/* Lock the type table */
|
||||||
rel = heap_openr(TypeRelationName, RowExclusiveLock);
|
typrel = heap_openr(TypeRelationName, RowExclusiveLock);
|
||||||
|
|
||||||
/* Use LookupTypeName here so that shell types can be found. */
|
/* Use LookupTypeName here so that shell types can be found (why?). */
|
||||||
domainoid = LookupTypeName(typename);
|
domainoid = LookupTypeName(typename);
|
||||||
if (!OidIsValid(domainoid))
|
if (!OidIsValid(domainoid))
|
||||||
elog(ERROR, "Type \"%s\" does not exist",
|
elog(ERROR, "Type \"%s\" does not exist",
|
||||||
@ -1267,15 +1260,13 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
|
|||||||
tup = SearchSysCacheCopy(TYPEOID,
|
tup = SearchSysCacheCopy(TYPEOID,
|
||||||
ObjectIdGetDatum(domainoid),
|
ObjectIdGetDatum(domainoid),
|
||||||
0, 0, 0);
|
0, 0, 0);
|
||||||
|
|
||||||
if (!HeapTupleIsValid(tup))
|
if (!HeapTupleIsValid(tup))
|
||||||
elog(ERROR, "AlterDomain: type \"%s\" does not exist",
|
elog(ERROR, "AlterDomain: type \"%s\" does not exist",
|
||||||
TypeNameToString(typename));
|
TypeNameToString(typename));
|
||||||
|
|
||||||
typTup = (Form_pg_type) GETSTRUCT(tup);
|
typTup = (Form_pg_type) GETSTRUCT(tup);
|
||||||
|
|
||||||
/* Doesn't return if user isn't allowed to alter the domain */
|
/* Doesn't return if user isn't allowed to alter the domain */
|
||||||
domainPermissionCheck(tup, typename);
|
domainOwnerCheck(tup, typename);
|
||||||
|
|
||||||
/* Check for unsupported constraint types */
|
/* Check for unsupported constraint types */
|
||||||
if (IsA(newConstraint, FkConstraint))
|
if (IsA(newConstraint, FkConstraint))
|
||||||
@ -1346,35 +1337,34 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
|
|||||||
/* build execution state for expr */
|
/* build execution state for expr */
|
||||||
exprstate = ExecPrepareExpr(expr, estate);
|
exprstate = ExecPrepareExpr(expr, estate);
|
||||||
|
|
||||||
rels = get_rels_with_domain(domainoid);
|
/* Fetch relation list with attributes based on this domain */
|
||||||
|
/* ShareLock is sufficient to prevent concurrent data changes */
|
||||||
|
|
||||||
|
rels = get_rels_with_domain(domainoid, ShareLock);
|
||||||
|
|
||||||
foreach (rt, rels)
|
foreach (rt, rels)
|
||||||
{
|
{
|
||||||
relToCheck *rtc = (relToCheck *) lfirst(rt);
|
RelToCheck *rtc = (RelToCheck *) lfirst(rt);
|
||||||
Relation testrel;
|
Relation testrel = rtc->rel;
|
||||||
TupleDesc tupdesc;
|
TupleDesc tupdesc = RelationGetDescr(testrel);
|
||||||
HeapTuple tuple;
|
|
||||||
HeapScanDesc scan;
|
HeapScanDesc scan;
|
||||||
|
HeapTuple tuple;
|
||||||
|
|
||||||
/* Lock relation against changes */
|
/* Scan all tuples in this relation */
|
||||||
testrel = heap_open(rtc->relOid, ShareLock);
|
|
||||||
|
|
||||||
tupdesc = RelationGetDescr(testrel);
|
|
||||||
|
|
||||||
/* Scan through table */
|
|
||||||
scan = heap_beginscan(testrel, SnapshotNow, 0, NULL);
|
scan = heap_beginscan(testrel, SnapshotNow, 0, NULL);
|
||||||
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
|
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
/* Loop through each attribute of the tuple with a domain */
|
/* Test attributes that are of the domain */
|
||||||
for (i = 0; i < rtc->natts; i++)
|
for (i = 0; i < rtc->natts; i++)
|
||||||
{
|
{
|
||||||
|
int attnum = rtc->atts[i];
|
||||||
Datum d;
|
Datum d;
|
||||||
bool isNull;
|
bool isNull;
|
||||||
Datum conResult;
|
Datum conResult;
|
||||||
|
|
||||||
d = heap_getattr(tuple, rtc->atts[i], tupdesc, &isNull);
|
d = heap_getattr(tuple, attnum, tupdesc, &isNull);
|
||||||
|
|
||||||
econtext->domainValue_datum = d;
|
econtext->domainValue_datum = d;
|
||||||
econtext->domainValue_isNull = isNull;
|
econtext->domainValue_isNull = isNull;
|
||||||
@ -1384,13 +1374,13 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
|
|||||||
&isNull, NULL);
|
&isNull, NULL);
|
||||||
|
|
||||||
if (!isNull && !DatumGetBool(conResult))
|
if (!isNull && !DatumGetBool(conResult))
|
||||||
elog(ERROR, "AlterDomainAddConstraint: Domain %s constraint %s failed",
|
elog(ERROR, "ALTER DOMAIN: Relation \"%s\" attribute \"%s\" contains values that fail the new constraint",
|
||||||
NameStr(typTup->typname), constr->name);
|
RelationGetRelationName(testrel),
|
||||||
|
NameStr(tupdesc->attrs[attnum - 1]->attname));
|
||||||
}
|
}
|
||||||
|
|
||||||
ResetExprContext(econtext);
|
ResetExprContext(econtext);
|
||||||
}
|
}
|
||||||
|
|
||||||
heap_endscan(scan);
|
heap_endscan(scan);
|
||||||
|
|
||||||
/* Hold relation lock till commit (XXX bad for concurrency) */
|
/* Hold relation lock till commit (XXX bad for concurrency) */
|
||||||
@ -1400,114 +1390,158 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
|
|||||||
FreeExecutorState(estate);
|
FreeExecutorState(estate);
|
||||||
|
|
||||||
/* Clean up */
|
/* Clean up */
|
||||||
heap_close(rel, NoLock);
|
heap_close(typrel, RowExclusiveLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* get_rels_with_domain
|
* get_rels_with_domain
|
||||||
*
|
*
|
||||||
* Fetch all relations / attributes which are using the domain
|
* Fetch all relations / attributes which are using the domain
|
||||||
* while maintaining a RowExclusiveLock on the pg_attribute
|
*
|
||||||
* entries.
|
* The result is a list of RelToCheck structs, one for each distinct
|
||||||
|
* relation, each containing one or more attribute numbers that are of
|
||||||
|
* the domain type. We have opened each rel and acquired the specified lock
|
||||||
|
* type on it.
|
||||||
|
*
|
||||||
|
* XXX this is completely broken because there is no way to lock the domain
|
||||||
|
* to prevent columns from being added or dropped while our command runs.
|
||||||
|
* We can partially protect against column drops by locking relations as we
|
||||||
|
* come across them, but there is still a race condition (the window between
|
||||||
|
* seeing a pg_depend entry and acquiring lock on the relation it references).
|
||||||
|
* Also, holding locks on all these relations simultaneously creates a non-
|
||||||
|
* trivial risk of deadlock. We can minimize but not eliminate the deadlock
|
||||||
|
* risk by using the weakest suitable lock (ShareLock for most callers).
|
||||||
|
*
|
||||||
|
* XXX to support domains over domains, we'd need to make this smarter,
|
||||||
|
* or make its callers smarter, so that we could find columns of derived
|
||||||
|
* domains. Arrays of domains would be a problem too.
|
||||||
*
|
*
|
||||||
* Generally used for retrieving a list of tests when adding
|
* Generally used for retrieving a list of tests when adding
|
||||||
* new constraints to a domain.
|
* new constraints to a domain.
|
||||||
*/
|
*/
|
||||||
List *
|
static List *
|
||||||
get_rels_with_domain(Oid domainOid)
|
get_rels_with_domain(Oid domainOid, LOCKMODE lockmode)
|
||||||
{
|
{
|
||||||
Relation classRel;
|
List *result = NIL;
|
||||||
HeapTuple classTup;
|
Relation depRel;
|
||||||
Relation attRel;
|
ScanKeyData key[2];
|
||||||
HeapScanDesc classScan;
|
SysScanDesc depScan;
|
||||||
List *rels = NIL;
|
HeapTuple depTup;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We need to lock the domain rows for the length of the transaction,
|
* We scan pg_depend to find those things that depend on the domain.
|
||||||
* but once all of the tables and the appropriate attributes are
|
* (We assume we can ignore refobjsubid for a domain.)
|
||||||
* found we can relese the relation lock.
|
|
||||||
*/
|
*/
|
||||||
classRel = relation_openr(RelationRelationName, ExclusiveLock);
|
depRel = relation_openr(DependRelationName, AccessShareLock);
|
||||||
attRel = relation_openr(AttributeRelationName, RowExclusiveLock);
|
|
||||||
|
|
||||||
classScan = heap_beginscan(classRel, SnapshotNow, 0, NULL);
|
ScanKeyEntryInitialize(&key[0], 0x0,
|
||||||
|
Anum_pg_depend_refclassid, F_OIDEQ,
|
||||||
|
ObjectIdGetDatum(RelOid_pg_type));
|
||||||
|
ScanKeyEntryInitialize(&key[1], 0x0,
|
||||||
|
Anum_pg_depend_refobjid, F_OIDEQ,
|
||||||
|
ObjectIdGetDatum(domainOid));
|
||||||
|
|
||||||
/* Scan through pg_class for tables */
|
depScan = systable_beginscan(depRel, DependReferenceIndex, true,
|
||||||
while ((classTup = heap_getnext(classScan, ForwardScanDirection)) != NULL)
|
SnapshotNow, 2, key);
|
||||||
|
|
||||||
|
while (HeapTupleIsValid(depTup = systable_getnext(depScan)))
|
||||||
{
|
{
|
||||||
relToCheck *rtc = NULL;
|
Form_pg_depend pg_depend = (Form_pg_depend) GETSTRUCT(depTup);
|
||||||
int nkeys = 0;
|
RelToCheck *rtc = NULL;
|
||||||
HeapTuple attTup;
|
List *rellist;
|
||||||
HeapScanDesc attScan;
|
Form_pg_attribute pg_att;
|
||||||
ScanKeyData attKey[2];
|
int ptr;
|
||||||
Form_pg_class pg_class;
|
|
||||||
|
|
||||||
/* Get our pg_class struct */
|
/* Ignore dependees that aren't user columns of tables */
|
||||||
pg_class = (Form_pg_class) GETSTRUCT(classTup);
|
/* (we assume system columns are never of domain types) */
|
||||||
|
if (pg_depend->classid != RelOid_pg_class ||
|
||||||
|
pg_depend->objsubid <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
/* Fetch attributes from pg_attribute for the relation of the type domainOid */
|
/* See if we already have an entry for this relation */
|
||||||
ScanKeyEntryInitialize(&attKey[nkeys++], 0, Anum_pg_attribute_attrelid,
|
foreach(rellist, result)
|
||||||
F_OIDEQ, ObjectIdGetDatum(HeapTupleGetOid(classTup)));
|
|
||||||
|
|
||||||
ScanKeyEntryInitialize(&attKey[nkeys++], 0, Anum_pg_attribute_atttypid,
|
|
||||||
F_OIDEQ, ObjectIdGetDatum(domainOid));
|
|
||||||
|
|
||||||
/* Setup to scan pg_attribute */
|
|
||||||
attScan = heap_beginscan(attRel, SnapshotNow, nkeys, attKey);
|
|
||||||
|
|
||||||
/* Scan through pg_attribute for attributes based on the domain */
|
|
||||||
while ((attTup = heap_getnext(attScan, ForwardScanDirection)) != NULL)
|
|
||||||
{
|
{
|
||||||
if (rtc == NULL)
|
RelToCheck *rt = (RelToCheck *) lfirst(rellist);
|
||||||
{
|
|
||||||
/* First one found for this rel */
|
|
||||||
rtc = (relToCheck *)palloc(sizeof(relToCheck));
|
|
||||||
rtc->atts = (int *)palloc(sizeof(int) * pg_class->relnatts);
|
|
||||||
rtc->relOid = HeapTupleGetOid(classTup);
|
|
||||||
rtc->natts = 0;
|
|
||||||
rels = lcons((void *)rtc, rels);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Now add the attribute */
|
if (RelationGetRelid(rt->rel) == pg_depend->objid)
|
||||||
rtc->atts[rtc->natts++] = ((Form_pg_attribute) GETSTRUCT(attTup))->attnum;
|
{
|
||||||
|
rtc = rt;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
heap_endscan(attScan);
|
if (rtc == NULL)
|
||||||
|
{
|
||||||
|
/* First attribute found for this relation */
|
||||||
|
Relation rel;
|
||||||
|
|
||||||
|
/* Acquire requested lock on relation */
|
||||||
|
rel = heap_open(pg_depend->objid, lockmode);
|
||||||
|
|
||||||
|
/* Build the RelToCheck entry with enough space for all atts */
|
||||||
|
rtc = (RelToCheck *) palloc(sizeof(RelToCheck));
|
||||||
|
rtc->rel = rel;
|
||||||
|
rtc->natts = 0;
|
||||||
|
rtc->atts = (int *) palloc(sizeof(int) * RelationGetNumberOfAttributes(rel));
|
||||||
|
result = lcons(rtc, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Confirm column has not been dropped, and is of the expected type.
|
||||||
|
* This defends against an ALTER DROP COLUMN occuring just before
|
||||||
|
* we acquired lock ... but if the whole table were dropped, we'd
|
||||||
|
* still have a problem.
|
||||||
|
*/
|
||||||
|
if (pg_depend->objsubid > RelationGetNumberOfAttributes(rtc->rel))
|
||||||
|
continue;
|
||||||
|
pg_att = rtc->rel->rd_att->attrs[pg_depend->objsubid - 1];
|
||||||
|
if (pg_att->attisdropped || pg_att->atttypid != domainOid)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Okay, add column to result. We store the columns in column-number
|
||||||
|
* order; this is just a hack to improve predictability of regression
|
||||||
|
* test output ...
|
||||||
|
*/
|
||||||
|
Assert(rtc->natts < RelationGetNumberOfAttributes(rtc->rel));
|
||||||
|
|
||||||
|
ptr = rtc->natts++;
|
||||||
|
while (ptr > 0 && rtc->atts[ptr-1] > pg_depend->objsubid)
|
||||||
|
{
|
||||||
|
rtc->atts[ptr] = rtc->atts[ptr-1];
|
||||||
|
ptr--;
|
||||||
|
}
|
||||||
|
rtc->atts[ptr] = pg_depend->objsubid;
|
||||||
}
|
}
|
||||||
|
|
||||||
heap_endscan(classScan);
|
systable_endscan(depScan);
|
||||||
|
|
||||||
/* Release pg_class, hold pg_attribute for further processing */
|
relation_close(depRel, AccessShareLock);
|
||||||
relation_close(classRel, ExclusiveLock);
|
|
||||||
relation_close(attRel, NoLock);
|
|
||||||
|
|
||||||
return rels;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* domainPermissionCheck
|
* domainOwnerCheck
|
||||||
*
|
*
|
||||||
* Throw an error if the current user doesn't have permission to modify
|
* Throw an error if the current user doesn't have permission to modify
|
||||||
* the domain in an ALTER DOMAIN statement, or if the type isn't actually
|
* the domain in an ALTER DOMAIN statement, or if the type isn't actually
|
||||||
* a domain.
|
* a domain.
|
||||||
*/
|
*/
|
||||||
void
|
static void
|
||||||
domainPermissionCheck(HeapTuple tup, TypeName *typename)
|
domainOwnerCheck(HeapTuple tup, TypeName *typename)
|
||||||
{
|
{
|
||||||
Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup);
|
Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup);
|
||||||
|
|
||||||
/* Permission check: must own type or its namespace */
|
|
||||||
if (!pg_type_ownercheck(HeapTupleGetOid(tup), GetUserId()) &&
|
|
||||||
!pg_namespace_ownercheck(typTup->typnamespace,
|
|
||||||
GetUserId()))
|
|
||||||
aclcheck_error(ACLCHECK_NOT_OWNER, TypeNameToString(typename));
|
|
||||||
|
|
||||||
/* Check that this is actually a domain */
|
/* Check that this is actually a domain */
|
||||||
if (typTup->typtype != 'd')
|
if (typTup->typtype != 'd')
|
||||||
elog(ERROR, "%s is not a domain",
|
elog(ERROR, "%s is not a domain",
|
||||||
TypeNameToString(typename));
|
TypeNameToString(typename));
|
||||||
}
|
|
||||||
|
|
||||||
|
/* Permission check: must own type */
|
||||||
|
if (!pg_type_ownercheck(HeapTupleGetOid(tup), GetUserId()))
|
||||||
|
aclcheck_error(ACLCHECK_NOT_OWNER, TypeNameToString(typename));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* domainAddConstraint - code shared between CREATE and ALTER DOMAIN
|
* domainAddConstraint - code shared between CREATE and ALTER DOMAIN
|
||||||
|
@ -201,19 +201,19 @@ create table domnotnull
|
|||||||
);
|
);
|
||||||
insert into domnotnull default values;
|
insert into domnotnull default values;
|
||||||
alter domain dnotnulltest set not null; -- fails
|
alter domain dnotnulltest set not null; -- fails
|
||||||
ERROR: ALTER DOMAIN: Relation "domnotnull" Attribute "col1" contains NULL values
|
ERROR: ALTER DOMAIN: Relation "domnotnull" attribute "col1" contains NULL values
|
||||||
update domnotnull set col1 = 5;
|
update domnotnull set col1 = 5;
|
||||||
alter domain dnotnulltest set not null; -- fails
|
alter domain dnotnulltest set not null; -- fails
|
||||||
ERROR: ALTER DOMAIN: Relation "domnotnull" Attribute "col2" contains NULL values
|
ERROR: ALTER DOMAIN: Relation "domnotnull" attribute "col2" contains NULL values
|
||||||
update domnotnull set col2 = 6;
|
update domnotnull set col2 = 6;
|
||||||
alter domain dnotnulltest set not null;
|
alter domain dnotnulltest set not null;
|
||||||
alter domain dnotnulltest set not null; -- fails
|
alter domain dnotnulltest set not null; -- fails
|
||||||
ERROR: AlterDomain: dnotnulltest is already set to NOT NULL
|
NOTICE: AlterDomain: dnotnulltest is already set to NOT NULL
|
||||||
update domnotnull set col1 = null; -- fails
|
update domnotnull set col1 = null; -- fails
|
||||||
ERROR: Domain dnotnulltest does not allow NULL values
|
ERROR: Domain dnotnulltest does not allow NULL values
|
||||||
alter domain dnotnulltest drop not null;
|
alter domain dnotnulltest drop not null;
|
||||||
alter domain dnotnulltest drop not null; -- fails
|
alter domain dnotnulltest drop not null; -- fails
|
||||||
ERROR: AlterDomain: dnotnulltest is already set to NULL
|
NOTICE: AlterDomain: dnotnulltest is already set to NULL
|
||||||
update domnotnull set col1 = null;
|
update domnotnull set col1 = null;
|
||||||
drop domain dnotnulltest cascade;
|
drop domain dnotnulltest cascade;
|
||||||
NOTICE: Drop cascades to table domnotnull column col2
|
NOTICE: Drop cascades to table domnotnull column col2
|
||||||
@ -253,7 +253,7 @@ create table domcontest (col1 con);
|
|||||||
insert into domcontest values (1);
|
insert into domcontest values (1);
|
||||||
insert into domcontest values (2);
|
insert into domcontest values (2);
|
||||||
alter domain con add constraint t check (VALUE < 1); -- fails
|
alter domain con add constraint t check (VALUE < 1); -- fails
|
||||||
ERROR: AlterDomainAddConstraint: Domain con constraint t failed
|
ERROR: ALTER DOMAIN: Relation "domcontest" attribute "col1" contains values that fail the new constraint
|
||||||
alter domain con add constraint t check (VALUE < 34);
|
alter domain con add constraint t check (VALUE < 34);
|
||||||
alter domain con add check (VALUE > 0);
|
alter domain con add check (VALUE > 0);
|
||||||
insert into domcontest values (-5); -- fails
|
insert into domcontest values (-5); -- fails
|
||||||
|
Loading…
x
Reference in New Issue
Block a user