postgres.git / summary / log / commit / refs

commit    d1c8aa0b09f18bba1343ce2e4aa537a17a8a972f
Author:   Nathan Bossart <nathan@postgresql.org>
Commit:   Noah Misch <noah@leadboat.com>
Date:     Mon Aug 10 13:38:19 2026 +0000
First released in: 14.24, 15.19, 16.15, 17.11, 18.6

    Check for USAGE privilege on types used by stored expressions.
    
    This omission allowed roles without USAGE on a type to create
    stored expressions that depend on it, which could prevent the owner
    from changing the type later.
    
    The checks deliberately live in the command paths rather than the
    dependency-recording routines.  Those routines also run whenever
    the server re-derives an existing expression, and re-checking there
    would break routine maintenance for an owner who has since lost
    USAGE on a type its objects already reference.  (Checking in the
    dependency-recording routines would also require additional
    CommandCounterIncrement() calls to avoid spurious errors.)
    
    The addition of a parameter to AlterDomainAddConstraint() breaks
    ABI compatibility, but we are unaware of any impacted third-party
    code.
    
    Reported-by: Noah Misch <noah@leadboat.com>
    Author: Nathan Bossart <nathandbossart@gmail.com>
    Reviewed-by: Noah Misch <noah@leadboat.com>
    Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
    Reviewed-by: Robert Haas <robertmhaas@gmail.com>
    Security: CVE-2026-6470
    Backpatch-through: 14


src/backend/catalog/dependency.c | 81 ++++++++++++++++++++++++++++++++ src/backend/catalog/heap.c | 22 +++++++++ src/backend/catalog/index.c | 3 ++ src/backend/catalog/pg_attrdef.c | 3 ++ src/backend/catalog/pg_constraint.c | 3 ++ src/backend/catalog/pg_proc.c | 6 +++ src/backend/catalog/pg_publication.c | 4 ++ src/backend/catalog/pg_type.c | 6 +++ src/backend/commands/indexcmds.c | 16 +++++++ src/backend/commands/policy.c | 8 ++++ src/backend/commands/statscmds.c | 5 ++ src/backend/commands/tablecmds.c | 19 +++++++- src/backend/commands/trigger.c | 5 ++ src/backend/commands/typecmds.c | 33 +++++++++++-- src/backend/parser/parse_utilcmd.c | 10 ++++ src/backend/rewrite/rewriteDefine.c | 2 + src/backend/tcop/utility.c | 3 +- src/include/catalog/dependency.h | 4 ++ src/include/commands/typecmds.h | 3 +- src/test/regress/expected/privileges.out | 46 ++++++++++++++++++ src/test/regress/sql/privileges.sql | 43 +++++++++++++++++ 21 files changed, 316 insertions(+), 9 deletions(-) diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 5336b47e50f..3692864d449 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -1684,6 +1684,87 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender, free_object_addresses(context.addrs); } +/* + * We require USAGE on a type to store a dependency on it. This helper + * function does the appropriate privilege checks. + * + * NB: Other objects have privileges of their own, but recording those + * dependencies doesn't require holding them. For example, an expression may + * reference a function for which the user lacks EXECUTE. Instead, EXECUTE is + * checked when the function is executed. + */ +static void +check_usage_on_types(ObjectAddresses *addrs, Oid roleid) +{ + for (int i = 0; i < addrs->numrefs; i++) + { + ObjectAddress *ref = &addrs->refs[i]; + AclResult aclresult; + + if (ref->classId != TypeRelationId) + continue; + + /* we don't record dependencies on pinned types */ + if (IsPinnedObject(ref->classId, ref->objectId)) + continue; + + aclresult = object_aclcheck(ref->classId, ref->objectId, + roleid, ACL_USAGE); + if (aclresult != ACLCHECK_OK) + aclcheck_error_type(aclresult, ref->objectId); + } +} + +/* + * CheckUsageOnTypesInExpr - require USAGE on all types named by an expression + * + * rtable is the rangetable for interpreting Vars (or NIL if none are + * expected). roleid is the role whose USAGE is required. + */ +void +CheckUsageOnTypesInExpr(Node *expr, List *rtable, Oid roleid) +{ + find_expr_references_context context; + + context.addrs = new_object_addresses(); + + /* Set up interpretation for Vars at varlevelsup = 0 */ + context.rtables = list_make1(rtable); + + find_expr_references_walker(expr, &context); + eliminate_duplicate_dependencies(context.addrs); + check_usage_on_types(context.addrs, roleid); + free_object_addresses(context.addrs); +} + +/* + * CheckUsageOnTypesInSingleRelExpr - as above, for a single-rel expression + * + * Like recordDependencyOnSingleRelExpr(), this handles expressions whose Vars + * all refer to one relation. roleid is the role whose USAGE is required. + */ +void +CheckUsageOnTypesInSingleRelExpr(Node *expr, Oid relId, Oid roleid) +{ + find_expr_references_context context; + RangeTblEntry rte = {0}; + + context.addrs = new_object_addresses(); + + /* We gin up a rather bogus rangetable list to handle Vars */ + rte.type = T_RangeTblEntry; + rte.rtekind = RTE_RELATION; + rte.relid = relId; + rte.relkind = RELKIND_RELATION; + rte.rellockmode = AccessShareLock; + context.rtables = list_make1(list_make1(&rte)); + + find_expr_references_walker(expr, &context); + eliminate_duplicate_dependencies(context.addrs); + check_usage_on_types(context.addrs, roleid); + free_object_addresses(context.addrs); +} + /* * Recursively search an expression tree for object references. * diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index bdfab33cad0..58210c0ead6 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -2142,6 +2142,9 @@ SetAttrMissing(Oid relid, char *attname, char *value) * in the pg_class entry for the relation. * * The OID of the new constraint is returned. + * + * NB: Caller is responsible for ensuring the user has USAGE on all types expr + * depends on. */ static Oid StoreRelCheck(Relation rel, const char *ccname, Node *expr, @@ -2402,6 +2405,13 @@ AddRelationNewConstraints(Relation rel, castNode(Const, expr)->constisnull)) continue; + /* + * The below call to StoreAttrDefault() adds the dependencies on + * types. We are responsible for checking USAGE. + */ + if (!is_internal) + CheckUsageOnTypesInSingleRelExpr(expr, RelationGetRelid(rel), GetUserId()); + defOid = StoreAttrDefault(rel, colDef->attnum, expr, is_internal, false); @@ -2441,6 +2451,14 @@ AddRelationNewConstraints(Relation rel, */ expr = cookConstraint(pstate, cdef->raw_expr, RelationGetRelationName(rel)); + + /* + * The below call to StoreRelCheck() calls + * CreateConstraintEntry(), which adds the dependencies on + * types. We are responsible for checking USAGE. + */ + if (!is_internal) + CheckUsageOnTypesInSingleRelExpr(expr, RelationGetRelid(rel), GetUserId()); } else { @@ -3495,12 +3513,16 @@ StorePartitionKey(Relation rel, * columns, i.e. they become internally dependent on the whole table. */ if (partexprs) + { + CheckUsageOnTypesInSingleRelExpr((Node *) partexprs, RelationGetRelid(rel), + GetUserId()); recordDependencyOnSingleRelExpr(&myself, (Node *) partexprs, RelationGetRelid(rel), DEPENDENCY_NORMAL, DEPENDENCY_INTERNAL, true /* reverse the self-deps */ ); + } /* * We must invalidate the relcache so that the next diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index c124d3f0eb7..192d614434c 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -723,6 +723,9 @@ UpdateIndexRelation(Oid indexoid, * constraintId: if not NULL, receives OID of created constraint * * Returns the OID of the created index. + * + * NB: Caller is responsible for ensuring the user has USAGE on all types + * indexInfo->ii_{Expressions,Predicate} depend on. */ Oid index_create(Relation heapRelation, diff --git a/src/backend/catalog/pg_attrdef.c b/src/backend/catalog/pg_attrdef.c index 65296bd9e05..0356a7df5d1 100644 --- a/src/backend/catalog/pg_attrdef.c +++ b/src/backend/catalog/pg_attrdef.c @@ -41,6 +41,9 @@ * for this is that the missing value must never be updated after it is set, * which can only be when a column is added to the table. Otherwise we would * in effect be changing existing tuples. + * + * NB: Caller is responsible for ensuring the user has USAGE on all types expr + * depends on. */ Oid StoreAttrDefault(Relation rel, AttrNumber attnum, diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index b6fd46b019f..c0f77796ffa 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -43,6 +43,9 @@ * from the constraint to the things it depends on. * * The new constraint's OID is returned. + * + * NB: Caller is responsible for ensuring the user has USAGE on all types + * conExpr depends on. */ Oid CreateConstraintEntry(const char *constraintName, diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index dd3d6cd4fbd..ba292af1160 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -642,12 +642,18 @@ ProcedureCreate(const char *procedureName, /* dependency on SQL routine body */ if (languageObjectId == SQLlanguageId && prosqlbody) + { + CheckUsageOnTypesInExpr(prosqlbody, NIL, GetUserId()); recordDependencyOnExpr(&myself, prosqlbody, NIL, DEPENDENCY_NORMAL); + } /* dependency on parameter default expressions */ if (parameterDefaults) + { + CheckUsageOnTypesInExpr((Node *) parameterDefaults, NIL, GetUserId()); recordDependencyOnExpr(&myself, (Node *) parameterDefaults, NIL, DEPENDENCY_NORMAL); + } /* dependency on owner */ if (!is_update) diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c index 0b21076dbc4..aacea0ec5c5 100644 --- a/src/backend/catalog/pg_publication.c +++ b/src/backend/catalog/pg_publication.c @@ -32,6 +32,7 @@ #include "catalog/pg_type.h" #include "commands/publicationcmds.h" #include "funcapi.h" +#include "miscadmin.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/catcache.h" @@ -446,9 +447,12 @@ publication_add_relation(Oid pubid, PublicationRelInfo *pri, /* Add dependency on the objects mentioned in the qualifications */ if (pri->whereClause) + { + CheckUsageOnTypesInSingleRelExpr(pri->whereClause, relid, GetUserId()); recordDependencyOnSingleRelExpr(&myself, pri->whereClause, relid, DEPENDENCY_NORMAL, DEPENDENCY_NORMAL, false); + } /* Add dependency on the columns, if any are listed */ for (int i = 0; i < natts; i++) diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c index 3316f88c144..bc9c6e5bfba 100644 --- a/src/backend/catalog/pg_type.c +++ b/src/backend/catalog/pg_type.c @@ -189,6 +189,9 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId) * Returns the ObjectAddress assigned to the new type. * If newTypeOid is zero (the normal case), a new OID is created; * otherwise we use exactly that OID. + * + * NB: Caller is responsible for ensuring the user has USAGE + * on all types defaultTypeBin depends on. * ---------------------------------------------------------------- */ ObjectAddress @@ -552,6 +555,9 @@ TypeCreate(Oid newTypeOid, * type already belongs to the current extension. That's the behavior we * want when replacing a shell type, which is the only case where both flags * are true. + * + * NB: Caller is responsible for ensuring the user has USAGE on all types + * defaultExpr depends on. */ void GenerateTypeDependencies(HeapTuple typeTuple, diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 21a41cea813..73ca9b9876b 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -928,6 +928,22 @@ DefineIndex(Oid tableId, amcanorder, stmt->isconstraint, root_save_userid, root_save_sec_context, &root_save_nestlevel); + /* + * The below call to index_create() creates the dependencies on types. We + * are responsible for checking USAGE. + */ + if (check_rights) + { + if (indexInfo->ii_Expressions) + CheckUsageOnTypesInSingleRelExpr((Node *) indexInfo->ii_Expressions, + tableId, + root_save_userid); + if (indexInfo->ii_Predicate) + CheckUsageOnTypesInSingleRelExpr((Node *) indexInfo->ii_Predicate, + tableId, + root_save_userid); + } + /* * Extra checks when creating a PRIMARY KEY index. */ diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c index 6ff3eba8249..9b5d0caadfa 100644 --- a/src/backend/commands/policy.c +++ b/src/backend/commands/policy.c @@ -724,9 +724,12 @@ CreatePolicy(CreatePolicyStmt *stmt) recordDependencyOn(&myself, &target, DEPENDENCY_AUTO); + CheckUsageOnTypesInExpr(qual, qual_pstate->p_rtable, GetUserId()); recordDependencyOnExpr(&myself, qual, qual_pstate->p_rtable, DEPENDENCY_NORMAL); + CheckUsageOnTypesInExpr(with_check_qual, with_check_pstate->p_rtable, + GetUserId()); recordDependencyOnExpr(&myself, with_check_qual, with_check_pstate->p_rtable, DEPENDENCY_NORMAL); @@ -1055,8 +1058,13 @@ AlterPolicy(AlterPolicyStmt *stmt) recordDependencyOn(&myself, &target, DEPENDENCY_AUTO); + if (stmt->qual) + CheckUsageOnTypesInExpr(qual, qual_parse_rtable, GetUserId()); recordDependencyOnExpr(&myself, qual, qual_parse_rtable, DEPENDENCY_NORMAL); + if (stmt->with_check) + CheckUsageOnTypesInExpr(with_check_qual, with_check_parse_rtable, + GetUserId()); recordDependencyOnExpr(&myself, with_check_qual, with_check_parse_rtable, DEPENDENCY_NORMAL); diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index 5f920e3a55c..4587d197d77 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -578,11 +578,16 @@ CreateStatistics(List *relids, CreateStatsStmt *stmt, bool check_rights) * just like we do for index expressions. */ if (stxexprs) + { + if (check_rights) + CheckUsageOnTypesInSingleRelExpr((Node *) stxexprs, relid, GetUserId()); + recordDependencyOnSingleRelExpr(&myself, (Node *) stxexprs, relid, DEPENDENCY_NORMAL, DEPENDENCY_AUTO, false); + } /* * Also add dependencies on namespace and owner. These are required diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 2a142b4ebfd..2d9047f5b20 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -895,6 +895,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, stmt->partbound != NULL, &old_constraints); + /* + * NB: The defaults and constraints we just inherited are already cooked, + * so they don't get the USAGE checks that AddRelationNewConstraints() + * applies to raw ones. That's intentional: the parent's own catalog + * entries already depend on those types, so copying them pins nothing + * new, and creating a child requires owning the parent, anyway. + */ + /* * Create a tuple descriptor from the relation schema. Note that this * deals with column names, types, and not-null constraints, but not @@ -5338,7 +5346,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, address = AlterDomainAddConstraint(((AlterDomainStmt *) cmd->def)->typeName, ((AlterDomainStmt *) cmd->def)->def, - NULL); + NULL, true); break; case AT_ReAddComment: /* Re-add existing comment */ address = CommentObject((CommentStmt *) cmd->def); @@ -7997,7 +8005,14 @@ ATExecCookedColumnDefault(Relation rel, AttrNumber attnum, { ObjectAddress address; - /* We assume no checking is required */ + /* + * This is used for a cooked default copied by CREATE TABLE ... LIKE, + * which adds new type dependencies. Such a default doesn't go through + * AddRelationNewConstraints(), and StoreAttrDefault() leaves the + * privilege checks to its caller, so we must check for USAGE on the types + * here. + */ + CheckUsageOnTypesInSingleRelExpr(newDefault, RelationGetRelid(rel), GetUserId()); /* * Remove any old default for the column. We use RESTRICT here for diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index ce9b7d3e7bc..20085685332 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -1126,8 +1126,13 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString, * expression (eg, functions, as well as any columns used). */ if (whenRtable != NIL) + { + if (!isInternal) + CheckUsageOnTypesInExpr(whenClause, whenRtable, GetUserId()); + recordDependencyOnExpr(&myself, whenClause, whenRtable, DEPENDENCY_NORMAL); + } /* Post creation hook for new trigger */ InvokeObjectPostCreateHookArg(TriggerRelationId, trigoid, 0, diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 5514c4726e3..37bb83757c1 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -133,7 +133,8 @@ static void checkEnumOwner(HeapTuple tup); static char *domainAddCheckConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid, int typMod, Constraint *constr, - const char *domainName, ObjectAddress *constrAddr); + const char *domainName, ObjectAddress *constrAddr, + bool is_readd); static Node *replace_domain_constraint_value(ParseState *pstate, ColumnRef *cref); static void domainAddNotNullConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid, @@ -1014,6 +1015,13 @@ DefineDomain(CreateDomainStmt *stmt) } } + /* + * The below call to TypeCreate() calls GenerateTypeDependencies(), which + * adds the dependencies on types. We are responsible for checking USAGE. + */ + if (defaultValueBin) + CheckUsageOnTypesInExpr(stringToNode(defaultValueBin), NIL, GetUserId()); + /* Allocate OID for array type */ domainArrayOid = AssignTypeArrayOid(); @@ -1111,7 +1119,7 @@ DefineDomain(CreateDomainStmt *stmt) case CONSTR_CHECK: domainAddCheckConstraint(address.objectId, domainNamespace, basetypeoid, basetypeMod, - constr, domainName, NULL); + constr, domainName, NULL, false); break; case CONSTR_NOTNULL: @@ -2650,6 +2658,12 @@ AlterDomainDefault(List *names, Node *defaultRaw) } else { + /* + * The below call to GenerateTypeDependencies() creates the + * dependencies on types. We are responsible for checking USAGE. + */ + CheckUsageOnTypesInExpr(defaultExpr, NIL, GetUserId()); + /* * Expression must be stored as a nodeToString result, but we also * require a valid textual representation (mainly to make life @@ -2906,7 +2920,7 @@ AlterDomainDropConstraint(List *names, const char *constrName, */ ObjectAddress AlterDomainAddConstraint(List *names, Node *newConstraint, - ObjectAddress *constrAddr) + ObjectAddress *constrAddr, bool is_readd) { TypeName *typename; Oid domainoid; @@ -2993,7 +3007,8 @@ AlterDomainAddConstraint(List *names, Node *newConstraint, ccbin = domainAddCheckConstraint(domainoid, typTup->typnamespace, typTup->typbasetype, typTup->typtypmod, - constr, NameStr(typTup->typname), constrAddr); + constr, NameStr(typTup->typname), constrAddr, + is_readd); /* @@ -3520,7 +3535,8 @@ checkDomainOwner(HeapTuple tup) static char * domainAddCheckConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid, int typMod, Constraint *constr, - const char *domainName, ObjectAddress *constrAddr) + const char *domainName, ObjectAddress *constrAddr, + bool is_readd) { Node *expr; char *ccbin; @@ -3583,6 +3599,13 @@ domainAddCheckConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid, */ assign_expr_collations(pstate, expr); + /* + * The below call to CreateConstraintEntry() creates the dependencies on + * types. We are responsible for checking USAGE. + */ + if (!is_readd) + CheckUsageOnTypesInExpr(expr, NIL, GetUserId()); + /* * Domains don't allow variables (this is probably dead code now that * add_missing_from is history, but let's be sure). diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 09fe02fa805..6165472e40a 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1310,6 +1310,16 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause) ccname, RelationGetRelationName(relation)))); + /* + * Copying a CHECK constraint adds new references. Since the + * constraint arrives pre-cooked, it bypasses the checks in + * AddRelationNewConstraints(), so we must check for USAGE on + * types here. + */ + CheckUsageOnTypesInSingleRelExpr(stringToNode(ccbin), + RelationGetRelid(relation), + GetUserId()); + n = makeNode(Constraint); n->contype = CONSTR_CHECK; n->conname = pstrdup(ccname); diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c index 601242ea26d..c14d15df214 100644 --- a/src/backend/rewrite/rewriteDefine.c +++ b/src/backend/rewrite/rewriteDefine.c @@ -161,6 +161,7 @@ InsertRule(const char *rulname, /* * Also install dependencies on objects referenced in action and qual. */ + CheckUsageOnTypesInExpr((Node *) action, NIL, GetUserId()); recordDependencyOnExpr(&myself, (Node *) action, NIL, DEPENDENCY_NORMAL); @@ -170,6 +171,7 @@ InsertRule(const char *rulname, Query *qry = linitial_node(Query, action); qry = getInsertSelectQuery(qry, NULL); + CheckUsageOnTypesInExpr(event_qual, qry->rtable, GetUserId()); recordDependencyOnExpr(&myself, event_qual, qry->rtable, DEPENDENCY_NORMAL); } diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4c149f8a10c..29ef8beaa81 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1364,7 +1364,8 @@ ProcessUtilitySlow(ParseState *pstate, address = AlterDomainAddConstraint(stmt->typeName, stmt->def, - &secondaryObject); + &secondaryObject, + false); break; case 'X': /* DROP CONSTRAINT */ address = diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index f6d23a7f446..cc07bcce54a 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -120,6 +120,10 @@ extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender, DependencyType self_behavior, bool reverse_self); +extern void CheckUsageOnTypesInExpr(Node *expr, List *rtable, Oid roleid); + +extern void CheckUsageOnTypesInSingleRelExpr(Node *expr, Oid relId, Oid roleid); + extern ObjectAddresses *new_object_addresses(void); extern void add_exact_object_address(const ObjectAddress *object, diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h index e1b02927c4b..48c1eef3270 100644 --- a/src/include/commands/typecmds.h +++ b/src/include/commands/typecmds.h @@ -35,7 +35,8 @@ extern Oid AssignTypeMultirangeArrayOid(void); extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw); extern ObjectAddress AlterDomainNotNull(List *names, bool notNull); extern ObjectAddress AlterDomainAddConstraint(List *names, Node *newConstraint, - ObjectAddress *constrAddr); + ObjectAddress *constrAddr, + bool is_readd); extern ObjectAddress AlterDomainValidateConstraint(List *names, const char *constrName); extern ObjectAddress AlterDomainDropConstraint(List *names, const char *constrName, DropBehavior behavior, bool missing_ok); diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index 1c87f03a79d..78149a8045c 100644 --- a/src/test/regress/expected/privileges.out +++ b/src/test/regress/expected/privileges.out @@ -1321,10 +1321,14 @@ CREATE CAST (priv_testdomain1 AS priv_testdomain3a) WITH FUNCTION castfunc(int); ERROR: permission denied for type priv_testdomain1 DROP FUNCTION castfunc(int) CASCADE; DROP DOMAIN priv_testdomain3a; +CREATE DOMAIN priv_testdomain4a AS int CHECK (VALUE > ('(0,1)'::priv_testtype1).a); +ERROR: permission denied for type priv_testtype1 CREATE FUNCTION priv_testfunc5a(a priv_testdomain1) RETURNS int LANGUAGE SQL AS $$ SELECT $1 $$; ERROR: permission denied for type priv_testdomain1 CREATE FUNCTION priv_testfunc6a(b int) RETURNS priv_testdomain1 LANGUAGE SQL AS $$ SELECT $1::priv_testdomain1 $$; ERROR: permission denied for type priv_testdomain1 +CREATE FUNCTION priv_testfunc7a(a int DEFAULT ('(0,1)'::priv_testtype1).a) RETURNS int LANGUAGE SQL AS $$ SELECT $1 $$; +ERROR: permission denied for type priv_testtype1 CREATE OPERATOR !+! (PROCEDURE = int4pl, LEFTARG = priv_testdomain1, RIGHTARG = priv_testdomain1); ERROR: permission denied for type priv_testdomain1 CREATE TABLE test5a (a int, b priv_testdomain1); @@ -1338,6 +1342,8 @@ ALTER TABLE test9a ADD COLUMN c priv_testdomain1; ERROR: permission denied for type priv_testdomain1 ALTER TABLE test9a ALTER COLUMN b TYPE priv_testdomain1; ERROR: permission denied for type priv_testdomain1 +CREATE INDEX ON test9a ((a::priv_testdomain1)); +ERROR: permission denied for type public.priv_testdomain1 CREATE TYPE test7a AS (a int, b priv_testdomain1); ERROR: permission denied for type priv_testdomain1 CREATE TYPE test8a AS (a int, b int); @@ -1345,10 +1351,22 @@ ALTER TYPE test8a ADD ATTRIBUTE c priv_testdomain1; ERROR: permission denied for type priv_testdomain1 ALTER TYPE test8a ALTER ATTRIBUTE b TYPE priv_testdomain1; ERROR: permission denied for type priv_testdomain1 +CREATE DOMAIN priv_testdomain5a AS test8a CHECK ((VALUE).a > 0 AND ('(0,1)'::priv_testtype1).a >= 0); +ERROR: permission denied for type priv_testtype1 CREATE TABLE test11a AS (SELECT 1::priv_testdomain1 AS a); ERROR: permission denied for type priv_testdomain1 +CREATE VIEW test16a AS SELECT ('(0,1)'::priv_testtype1).a; +ERROR: permission denied for type priv_testtype1 +CREATE TABLE test17a (a int) PARTITION BY RANGE ((a + ('(0,1)'::priv_testtype1).a)); +ERROR: permission denied for type priv_testtype1 +CREATE POLICY priv_testpolicy1a ON test9a USING (('(0,1)'::priv_testtype1).a > 0); +ERROR: permission denied for type priv_testtype1 REVOKE ALL ON TYPE priv_testtype1 FROM PUBLIC; ERROR: permission denied for type priv_testtype1 +CREATE TABLE test12 (c int DEFAULT 0 CHECK (c > 0 AND '(0,1)'::priv_testtype1 IS NOT NULL)); +ERROR: permission denied for type priv_testtype1 +CREATE TABLE test13 (c int DEFAULT ('(0,1)'::priv_testtype1).a); +ERROR: permission denied for type priv_testtype1 SET SESSION AUTHORIZATION regress_priv_user2; -- commands that should succeed CREATE AGGREGATE priv_testagg1b(priv_testdomain1) (sfunc = int4_sum, stype = bigint); @@ -1357,8 +1375,10 @@ CREATE DOMAIN priv_testdomain3b AS int; CREATE FUNCTION castfunc(int) RETURNS priv_testdomain3b AS $$ SELECT $1::priv_testdomain3b $$ LANGUAGE SQL; CREATE CAST (priv_testdomain1 AS priv_testdomain3b) WITH FUNCTION castfunc(int); WARNING: cast will be ignored because the source data type is a domain +CREATE DOMAIN priv_testdomain4b AS int CHECK (VALUE > ('(0,1)'::priv_testtype1).a); CREATE FUNCTION priv_testfunc5b(a priv_testdomain1) RETURNS int LANGUAGE SQL AS $$ SELECT $1 $$; CREATE FUNCTION priv_testfunc6b(b int) RETURNS priv_testdomain1 LANGUAGE SQL AS $$ SELECT $1::priv_testdomain1 $$; +CREATE FUNCTION priv_testfunc7b(a int DEFAULT ('(0,1)'::priv_testtype1).a) RETURNS int LANGUAGE SQL AS $$ SELECT $1 $$; CREATE OPERATOR !! (PROCEDURE = priv_testfunc5b, RIGHTARG = priv_testdomain1); CREATE TABLE test5b (a int, b priv_testdomain1); CREATE TABLE test6b OF priv_testtype1; @@ -1366,19 +1386,41 @@ CREATE TABLE test10b (a int[], b priv_testtype1[]); CREATE TABLE test9b (a int, b int); ALTER TABLE test9b ADD COLUMN c priv_testdomain1; ALTER TABLE test9b ALTER COLUMN b TYPE priv_testdomain1; +CREATE INDEX ON test9b ((a::priv_testdomain1)); CREATE TYPE test7b AS (a int, b priv_testdomain1); CREATE TYPE test8b AS (a int, b int); ALTER TYPE test8b ADD ATTRIBUTE c priv_testdomain1; ALTER TYPE test8b ALTER ATTRIBUTE b TYPE priv_testdomain1; +CREATE DOMAIN priv_testdomain5b AS test8b CHECK ((VALUE).a > 0 AND ('(0,1)'::priv_testtype1).a >= 0); CREATE TABLE test11b AS (SELECT 1::priv_testdomain1 AS a); +CREATE VIEW test16b AS SELECT ('(0,1)'::priv_testtype1).a; +CREATE TABLE test17b (a int) PARTITION BY RANGE ((a + ('(0,1)'::priv_testtype1).a)); +CREATE POLICY priv_testpolicy1b ON test9b USING (('(0,1)'::priv_testtype1).a > 0); REVOKE ALL ON TYPE priv_testtype1 FROM PUBLIC; WARNING: no privileges could be revoked for "priv_testtype1" +CREATE TABLE test12 (c int DEFAULT 0 CHECK (c > 0 AND '(0,1)'::priv_testtype1 IS NOT NULL)); +CREATE TABLE test13 (c int DEFAULT ('(0,1)'::priv_testtype1).a); +-- new stored expressions require USAGE on types, rebuilds do not +\c - +REVOKE USAGE ON TYPE priv_testtype1 FROM regress_priv_user2; +SET SESSION AUTHORIZATION regress_priv_user2; +ALTER TABLE test12 ALTER COLUMN c TYPE bigint; +ALTER TYPE test8b ALTER ATTRIBUTE a TYPE bigint CASCADE; +ALTER TABLE test12 ALTER COLUMN c SET DEFAULT ('(0,1)'::priv_testtype1).a; +ERROR: permission denied for type priv_testtype1 +CREATE TABLE test14 (LIKE test12 INCLUDING CONSTRAINTS); +ERROR: permission denied for type priv_testtype1 +CREATE TABLE test15 (LIKE test13 INCLUDING DEFAULTS); +ERROR: permission denied for type priv_testtype1 \c - DROP AGGREGATE priv_testagg1b(priv_testdomain1); DROP DOMAIN priv_testdomain2b; +DROP DOMAIN priv_testdomain4b; +DROP DOMAIN priv_testdomain5b; DROP OPERATOR !! (NONE, priv_testdomain1); DROP FUNCTION priv_testfunc5b(a priv_testdomain1); DROP FUNCTION priv_testfunc6b(b int); +DROP FUNCTION priv_testfunc7b(a int); DROP TABLE test5b; DROP TABLE test6b; DROP TABLE test9b; @@ -1388,7 +1430,11 @@ DROP TYPE test8b; DROP CAST (priv_testdomain1 AS priv_testdomain3b); DROP FUNCTION castfunc(int) CASCADE; DROP DOMAIN priv_testdomain3b; +DROP VIEW test16b; DROP TABLE test11b; +DROP TABLE test17b; +DROP TABLE test12; +DROP TABLE test13; DROP TYPE priv_testtype1; -- ok DROP DOMAIN priv_testdomain1; -- ok -- truncate diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql index 0b417955b6a..8fc81eae557 100644 --- a/src/test/regress/sql/privileges.sql +++ b/src/test/regress/sql/privileges.sql @@ -931,8 +931,11 @@ CREATE CAST (priv_testdomain1 AS priv_testdomain3a) WITH FUNCTION castfunc(int); DROP FUNCTION castfunc(int) CASCADE; DROP DOMAIN priv_testdomain3a; +CREATE DOMAIN priv_testdomain4a AS int CHECK (VALUE > ('(0,1)'::priv_testtype1).a); + CREATE FUNCTION priv_testfunc5a(a priv_testdomain1) RETURNS int LANGUAGE SQL AS $$ SELECT $1 $$; CREATE FUNCTION priv_testfunc6a(b int) RETURNS priv_testdomain1 LANGUAGE SQL AS $$ SELECT $1::priv_testdomain1 $$; +CREATE FUNCTION priv_testfunc7a(a int DEFAULT ('(0,1)'::priv_testtype1).a) RETURNS int LANGUAGE SQL AS $$ SELECT $1 $$; CREATE OPERATOR !+! (PROCEDURE = int4pl, LEFTARG = priv_testdomain1, RIGHTARG = priv_testdomain1); @@ -943,6 +946,7 @@ CREATE TABLE test10a (a int[], b priv_testtype1[]); CREATE TABLE test9a (a int, b int); ALTER TABLE test9a ADD COLUMN c priv_testdomain1; ALTER TABLE test9a ALTER COLUMN b TYPE priv_testdomain1; +CREATE INDEX ON test9a ((a::priv_testdomain1)); CREATE TYPE test7a AS (a int, b priv_testdomain1); @@ -950,10 +954,19 @@ CREATE TYPE test8a AS (a int, b int); ALTER TYPE test8a ADD ATTRIBUTE c priv_testdomain1; ALTER TYPE test8a ALTER ATTRIBUTE b TYPE priv_testdomain1; +CREATE DOMAIN priv_testdomain5a AS test8a CHECK ((VALUE).a > 0 AND ('(0,1)'::priv_testtype1).a >= 0); + CREATE TABLE test11a AS (SELECT 1::priv_testdomain1 AS a); +CREATE VIEW test16a AS SELECT ('(0,1)'::priv_testtype1).a; +CREATE TABLE test17a (a int) PARTITION BY RANGE ((a + ('(0,1)'::priv_testtype1).a)); +CREATE POLICY priv_testpolicy1a ON test9a USING (('(0,1)'::priv_testtype1).a > 0); + REVOKE ALL ON TYPE priv_testtype1 FROM PUBLIC; +CREATE TABLE test12 (c int DEFAULT 0 CHECK (c > 0 AND '(0,1)'::priv_testtype1 IS NOT NULL)); +CREATE TABLE test13 (c int DEFAULT ('(0,1)'::priv_testtype1).a); + SET SESSION AUTHORIZATION regress_priv_user2; -- commands that should succeed @@ -966,8 +979,11 @@ CREATE DOMAIN priv_testdomain3b AS int; CREATE FUNCTION castfunc(int) RETURNS priv_testdomain3b AS $$ SELECT $1::priv_testdomain3b $$ LANGUAGE SQL; CREATE CAST (priv_testdomain1 AS priv_testdomain3b) WITH FUNCTION castfunc(int); +CREATE DOMAIN priv_testdomain4b AS int CHECK (VALUE > ('(0,1)'::priv_testtype1).a); + CREATE FUNCTION priv_testfunc5b(a priv_testdomain1) RETURNS int LANGUAGE SQL AS $$ SELECT $1 $$; CREATE FUNCTION priv_testfunc6b(b int) RETURNS priv_testdomain1 LANGUAGE SQL AS $$ SELECT $1::priv_testdomain1 $$; +CREATE FUNCTION priv_testfunc7b(a int DEFAULT ('(0,1)'::priv_testtype1).a) RETURNS int LANGUAGE SQL AS $$ SELECT $1 $$; CREATE OPERATOR !! (PROCEDURE = priv_testfunc5b, RIGHTARG = priv_testdomain1); @@ -978,6 +994,7 @@ CREATE TABLE test10b (a int[], b priv_testtype1[]); CREATE TABLE test9b (a int, b int); ALTER TABLE test9b ADD COLUMN c priv_testdomain1; ALTER TABLE test9b ALTER COLUMN b TYPE priv_testdomain1; +CREATE INDEX ON test9b ((a::priv_testdomain1)); CREATE TYPE test7b AS (a int, b priv_testdomain1); @@ -985,16 +1002,38 @@ CREATE TYPE test8b AS (a int, b int); ALTER TYPE test8b ADD ATTRIBUTE c priv_testdomain1; ALTER TYPE test8b ALTER ATTRIBUTE b TYPE priv_testdomain1; +CREATE DOMAIN priv_testdomain5b AS test8b CHECK ((VALUE).a > 0 AND ('(0,1)'::priv_testtype1).a >= 0); + CREATE TABLE test11b AS (SELECT 1::priv_testdomain1 AS a); +CREATE VIEW test16b AS SELECT ('(0,1)'::priv_testtype1).a; +CREATE TABLE test17b (a int) PARTITION BY RANGE ((a + ('(0,1)'::priv_testtype1).a)); +CREATE POLICY priv_testpolicy1b ON test9b USING (('(0,1)'::priv_testtype1).a > 0); + REVOKE ALL ON TYPE priv_testtype1 FROM PUBLIC; +CREATE TABLE test12 (c int DEFAULT 0 CHECK (c > 0 AND '(0,1)'::priv_testtype1 IS NOT NULL)); +CREATE TABLE test13 (c int DEFAULT ('(0,1)'::priv_testtype1).a); + +-- new stored expressions require USAGE on types, rebuilds do not +\c - +REVOKE USAGE ON TYPE priv_testtype1 FROM regress_priv_user2; +SET SESSION AUTHORIZATION regress_priv_user2; +ALTER TABLE test12 ALTER COLUMN c TYPE bigint; +ALTER TYPE test8b ALTER ATTRIBUTE a TYPE bigint CASCADE; +ALTER TABLE test12 ALTER COLUMN c SET DEFAULT ('(0,1)'::priv_testtype1).a; +CREATE TABLE test14 (LIKE test12 INCLUDING CONSTRAINTS); +CREATE TABLE test15 (LIKE test13 INCLUDING DEFAULTS); + \c - DROP AGGREGATE priv_testagg1b(priv_testdomain1); DROP DOMAIN priv_testdomain2b; +DROP DOMAIN priv_testdomain4b; +DROP DOMAIN priv_testdomain5b; DROP OPERATOR !! (NONE, priv_testdomain1); DROP FUNCTION priv_testfunc5b(a priv_testdomain1); DROP FUNCTION priv_testfunc6b(b int); +DROP FUNCTION priv_testfunc7b(a int); DROP TABLE test5b; DROP TABLE test6b; DROP TABLE test9b; @@ -1004,7 +1043,11 @@ DROP TYPE test8b; DROP CAST (priv_testdomain1 AS priv_testdomain3b); DROP FUNCTION castfunc(int) CASCADE; DROP DOMAIN priv_testdomain3b; +DROP VIEW test16b; DROP TABLE test11b; +DROP TABLE test17b; +DROP TABLE test12; +DROP TABLE test13; DROP TYPE priv_testtype1; -- ok DROP DOMAIN priv_testdomain1; -- ok [parent: fd6d3e7e9d55]