diff options
Diffstat (limited to 'src/backend/rewrite')
-rw-r--r-- | src/backend/rewrite/Makefile | 24 | ||||
-rw-r--r-- | src/backend/rewrite/rewriteDefine.c | 1036 | ||||
-rw-r--r-- | src/backend/rewrite/rewriteHandler.c | 4169 | ||||
-rw-r--r-- | src/backend/rewrite/rewriteManip.c | 1533 | ||||
-rw-r--r-- | src/backend/rewrite/rewriteRemove.c | 100 | ||||
-rw-r--r-- | src/backend/rewrite/rewriteSearchCycle.c | 681 | ||||
-rw-r--r-- | src/backend/rewrite/rewriteSupport.c | 117 | ||||
-rw-r--r-- | src/backend/rewrite/rowsecurity.c | 792 |
8 files changed, 8452 insertions, 0 deletions
diff --git a/src/backend/rewrite/Makefile b/src/backend/rewrite/Makefile new file mode 100644 index 0000000..4680752 --- /dev/null +++ b/src/backend/rewrite/Makefile @@ -0,0 +1,24 @@ +#------------------------------------------------------------------------- +# +# Makefile-- +# Makefile for rewrite +# +# IDENTIFICATION +# src/backend/rewrite/Makefile +# +#------------------------------------------------------------------------- + +subdir = src/backend/rewrite +top_builddir = ../../.. +include $(top_builddir)/src/Makefile.global + +OBJS = \ + rewriteDefine.o \ + rewriteHandler.o \ + rewriteManip.o \ + rewriteRemove.o \ + rewriteSearchCycle.o \ + rewriteSupport.o \ + rowsecurity.o + +include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c new file mode 100644 index 0000000..27e4ef9 --- /dev/null +++ b/src/backend/rewrite/rewriteDefine.c @@ -0,0 +1,1036 @@ +/*------------------------------------------------------------------------- + * + * rewriteDefine.c + * routines for defining a rewrite rule + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/rewrite/rewriteDefine.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/heapam.h" +#include "access/htup_details.h" +#include "access/multixact.h" +#include "access/tableam.h" +#include "access/transam.h" +#include "access/xact.h" +#include "catalog/catalog.h" +#include "catalog/dependency.h" +#include "catalog/heap.h" +#include "catalog/namespace.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_rewrite.h" +#include "catalog/storage.h" +#include "commands/policy.h" +#include "miscadmin.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_utilcmd.h" +#include "rewrite/rewriteDefine.h" +#include "rewrite/rewriteManip.h" +#include "rewrite/rewriteSupport.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/inval.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" + + +static void checkRuleResultList(List *targetList, TupleDesc resultDesc, + bool isSelect, bool requireColumnNameMatch); +static bool setRuleCheckAsUser_walker(Node *node, Oid *context); +static void setRuleCheckAsUser_Query(Query *qry, Oid userid); + + +/* + * InsertRule - + * takes the arguments and inserts them as a row into the system + * relation "pg_rewrite" + */ +static Oid +InsertRule(const char *rulname, + int evtype, + Oid eventrel_oid, + bool evinstead, + Node *event_qual, + List *action, + bool replace) +{ + char *evqual = nodeToString(event_qual); + char *actiontree = nodeToString((Node *) action); + Datum values[Natts_pg_rewrite]; + bool nulls[Natts_pg_rewrite]; + bool replaces[Natts_pg_rewrite]; + NameData rname; + Relation pg_rewrite_desc; + HeapTuple tup, + oldtup; + Oid rewriteObjectId; + ObjectAddress myself, + referenced; + bool is_update = false; + + /* + * Set up *nulls and *values arrays + */ + MemSet(nulls, false, sizeof(nulls)); + + namestrcpy(&rname, rulname); + values[Anum_pg_rewrite_rulename - 1] = NameGetDatum(&rname); + values[Anum_pg_rewrite_ev_class - 1] = ObjectIdGetDatum(eventrel_oid); + values[Anum_pg_rewrite_ev_type - 1] = CharGetDatum(evtype + '0'); + values[Anum_pg_rewrite_ev_enabled - 1] = CharGetDatum(RULE_FIRES_ON_ORIGIN); + values[Anum_pg_rewrite_is_instead - 1] = BoolGetDatum(evinstead); + values[Anum_pg_rewrite_ev_qual - 1] = CStringGetTextDatum(evqual); + values[Anum_pg_rewrite_ev_action - 1] = CStringGetTextDatum(actiontree); + + /* + * Ready to store new pg_rewrite tuple + */ + pg_rewrite_desc = table_open(RewriteRelationId, RowExclusiveLock); + + /* + * Check to see if we are replacing an existing tuple + */ + oldtup = SearchSysCache2(RULERELNAME, + ObjectIdGetDatum(eventrel_oid), + PointerGetDatum(rulname)); + + if (HeapTupleIsValid(oldtup)) + { + if (!replace) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("rule \"%s\" for relation \"%s\" already exists", + rulname, get_rel_name(eventrel_oid)))); + + /* + * When replacing, we don't need to replace every attribute + */ + MemSet(replaces, false, sizeof(replaces)); + replaces[Anum_pg_rewrite_ev_type - 1] = true; + replaces[Anum_pg_rewrite_is_instead - 1] = true; + replaces[Anum_pg_rewrite_ev_qual - 1] = true; + replaces[Anum_pg_rewrite_ev_action - 1] = true; + + tup = heap_modify_tuple(oldtup, RelationGetDescr(pg_rewrite_desc), + values, nulls, replaces); + + CatalogTupleUpdate(pg_rewrite_desc, &tup->t_self, tup); + + ReleaseSysCache(oldtup); + + rewriteObjectId = ((Form_pg_rewrite) GETSTRUCT(tup))->oid; + is_update = true; + } + else + { + rewriteObjectId = GetNewOidWithIndex(pg_rewrite_desc, + RewriteOidIndexId, + Anum_pg_rewrite_oid); + values[Anum_pg_rewrite_oid - 1] = ObjectIdGetDatum(rewriteObjectId); + + tup = heap_form_tuple(pg_rewrite_desc->rd_att, values, nulls); + + CatalogTupleInsert(pg_rewrite_desc, tup); + } + + + heap_freetuple(tup); + + /* If replacing, get rid of old dependencies and make new ones */ + if (is_update) + deleteDependencyRecordsFor(RewriteRelationId, rewriteObjectId, false); + + /* + * Install dependency on rule's relation to ensure it will go away on + * relation deletion. If the rule is ON SELECT, make the dependency + * implicit --- this prevents deleting a view's SELECT rule. Other kinds + * of rules can be AUTO. + */ + myself.classId = RewriteRelationId; + myself.objectId = rewriteObjectId; + myself.objectSubId = 0; + + referenced.classId = RelationRelationId; + referenced.objectId = eventrel_oid; + referenced.objectSubId = 0; + + recordDependencyOn(&myself, &referenced, + (evtype == CMD_SELECT) ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO); + + /* + * Also install dependencies on objects referenced in action and qual. + */ + recordDependencyOnExpr(&myself, (Node *) action, NIL, + DEPENDENCY_NORMAL); + + if (event_qual != NULL) + { + /* Find query containing OLD/NEW rtable entries */ + Query *qry = linitial_node(Query, action); + + qry = getInsertSelectQuery(qry, NULL); + recordDependencyOnExpr(&myself, event_qual, qry->rtable, + DEPENDENCY_NORMAL); + } + + /* Post creation hook for new rule */ + InvokeObjectPostCreateHook(RewriteRelationId, rewriteObjectId, 0); + + table_close(pg_rewrite_desc, RowExclusiveLock); + + return rewriteObjectId; +} + +/* + * DefineRule + * Execute a CREATE RULE command. + */ +ObjectAddress +DefineRule(RuleStmt *stmt, const char *queryString) +{ + List *actions; + Node *whereClause; + Oid relId; + + /* Parse analysis. */ + transformRuleStmt(stmt, queryString, &actions, &whereClause); + + /* + * Find and lock the relation. Lock level should match + * DefineQueryRewrite. + */ + relId = RangeVarGetRelid(stmt->relation, AccessExclusiveLock, false); + + /* ... and execute */ + return DefineQueryRewrite(stmt->rulename, + relId, + whereClause, + stmt->event, + stmt->instead, + stmt->replace, + actions); +} + + +/* + * DefineQueryRewrite + * Create a rule + * + * This is essentially the same as DefineRule() except that the rule's + * action and qual have already been passed through parse analysis. + */ +ObjectAddress +DefineQueryRewrite(const char *rulename, + Oid event_relid, + Node *event_qual, + CmdType event_type, + bool is_instead, + bool replace, + List *action) +{ + Relation event_relation; + ListCell *l; + Query *query; + bool RelisBecomingView = false; + Oid ruleId = InvalidOid; + ObjectAddress address; + + /* + * If we are installing an ON SELECT rule, we had better grab + * AccessExclusiveLock to ensure no SELECTs are currently running on the + * event relation. For other types of rules, it would be sufficient to + * grab ShareRowExclusiveLock to lock out insert/update/delete actions and + * to ensure that we lock out current CREATE RULE statements; but because + * of race conditions in access to catalog entries, we can't do that yet. + * + * Note that this lock level should match the one used in DefineRule. + */ + event_relation = table_open(event_relid, AccessExclusiveLock); + + /* + * Verify relation is of a type that rules can sensibly be applied to. + * Internal callers can target materialized views, but transformRuleStmt() + * blocks them for users. Don't mention them in the error message. + */ + if (event_relation->rd_rel->relkind != RELKIND_RELATION && + event_relation->rd_rel->relkind != RELKIND_MATVIEW && + event_relation->rd_rel->relkind != RELKIND_VIEW && + event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table or view", + RelationGetRelationName(event_relation)))); + + if (!allowSystemTableMods && IsSystemRelation(event_relation)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied: \"%s\" is a system catalog", + RelationGetRelationName(event_relation)))); + + /* + * Check user has permission to apply rules to this relation. + */ + if (!pg_class_ownercheck(event_relid, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(event_relation->rd_rel->relkind), + RelationGetRelationName(event_relation)); + + /* + * No rule actions that modify OLD or NEW + */ + foreach(l, action) + { + query = lfirst_node(Query, l); + if (query->resultRelation == 0) + continue; + /* Don't be fooled by INSERT/SELECT */ + if (query != getInsertSelectQuery(query, NULL)) + continue; + if (query->resultRelation == PRS2_OLD_VARNO) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("rule actions on OLD are not implemented"), + errhint("Use views or triggers instead."))); + if (query->resultRelation == PRS2_NEW_VARNO) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("rule actions on NEW are not implemented"), + errhint("Use triggers instead."))); + } + + if (event_type == CMD_SELECT) + { + /* + * Rules ON SELECT are restricted to view definitions + * + * So there cannot be INSTEAD NOTHING, ... + */ + if (list_length(action) == 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("INSTEAD NOTHING rules on SELECT are not implemented"), + errhint("Use views instead."))); + + /* + * ... there cannot be multiple actions, ... + */ + if (list_length(action) > 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("multiple actions for rules on SELECT are not implemented"))); + + /* + * ... the one action must be a SELECT, ... + */ + query = linitial_node(Query, action); + if (!is_instead || + query->commandType != CMD_SELECT) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("rules on SELECT must have action INSTEAD SELECT"))); + + /* + * ... it cannot contain data-modifying WITH ... + */ + if (query->hasModifyingCTE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("rules on SELECT must not contain data-modifying statements in WITH"))); + + /* + * ... there can be no rule qual, ... + */ + if (event_qual != NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("event qualifications are not implemented for rules on SELECT"))); + + /* + * ... the targetlist of the SELECT action must exactly match the + * event relation, ... + */ + checkRuleResultList(query->targetList, + RelationGetDescr(event_relation), + true, + event_relation->rd_rel->relkind != + RELKIND_MATVIEW); + + /* + * ... there must not be another ON SELECT rule already ... + */ + if (!replace && event_relation->rd_rules != NULL) + { + int i; + + for (i = 0; i < event_relation->rd_rules->numLocks; i++) + { + RewriteRule *rule; + + rule = event_relation->rd_rules->rules[i]; + if (rule->event == CMD_SELECT) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("\"%s\" is already a view", + RelationGetRelationName(event_relation)))); + } + } + + /* + * ... and finally the rule must be named _RETURN. + */ + if (strcmp(rulename, ViewSelectRuleName) != 0) + { + /* + * In versions before 7.3, the expected name was _RETviewname. For + * backwards compatibility with old pg_dump output, accept that + * and silently change it to _RETURN. Since this is just a quick + * backwards-compatibility hack, limit the number of characters + * checked to a few less than NAMEDATALEN; this saves having to + * worry about where a multibyte character might have gotten + * truncated. + */ + if (strncmp(rulename, "_RET", 4) != 0 || + strncmp(rulename + 4, RelationGetRelationName(event_relation), + NAMEDATALEN - 4 - 4) != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("view rule for \"%s\" must be named \"%s\"", + RelationGetRelationName(event_relation), + ViewSelectRuleName))); + rulename = pstrdup(ViewSelectRuleName); + } + + /* + * Are we converting a relation to a view? + * + * If so, check that the relation is empty because the storage for the + * relation is going to be deleted. Also insist that the rel not be + * involved in partitioning, nor have any triggers, indexes, child or + * parent tables, RLS policies, or RLS enabled. (Note: some of these + * tests are too strict, because they will reject relations that once + * had such but don't anymore. But we don't really care, because this + * whole business of converting relations to views is just an obsolete + * kluge to allow dump/reload of views that participate in circular + * dependencies.) + */ + if (event_relation->rd_rel->relkind != RELKIND_VIEW && + event_relation->rd_rel->relkind != RELKIND_MATVIEW) + { + TableScanDesc scanDesc; + Snapshot snapshot; + TupleTableSlot *slot; + + if (event_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot convert partitioned table \"%s\" to a view", + RelationGetRelationName(event_relation)))); + + /* only case left: */ + Assert(event_relation->rd_rel->relkind == RELKIND_RELATION); + + if (event_relation->rd_rel->relispartition) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot convert partition \"%s\" to a view", + RelationGetRelationName(event_relation)))); + + snapshot = RegisterSnapshot(GetLatestSnapshot()); + scanDesc = table_beginscan(event_relation, snapshot, 0, NULL); + slot = table_slot_create(event_relation, NULL); + if (table_scan_getnextslot(scanDesc, ForwardScanDirection, slot)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("could not convert table \"%s\" to a view because it is not empty", + RelationGetRelationName(event_relation)))); + ExecDropSingleTupleTableSlot(slot); + table_endscan(scanDesc); + UnregisterSnapshot(snapshot); + + if (event_relation->rd_rel->relhastriggers) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("could not convert table \"%s\" to a view because it has triggers", + RelationGetRelationName(event_relation)), + errhint("In particular, the table cannot be involved in any foreign key relationships."))); + + if (event_relation->rd_rel->relhasindex) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("could not convert table \"%s\" to a view because it has indexes", + RelationGetRelationName(event_relation)))); + + if (event_relation->rd_rel->relhassubclass) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("could not convert table \"%s\" to a view because it has child tables", + RelationGetRelationName(event_relation)))); + + if (has_superclass(RelationGetRelid(event_relation))) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("could not convert table \"%s\" to a view because it has parent tables", + RelationGetRelationName(event_relation)))); + + if (event_relation->rd_rel->relrowsecurity) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("could not convert table \"%s\" to a view because it has row security enabled", + RelationGetRelationName(event_relation)))); + + if (relation_has_policies(event_relation)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("could not convert table \"%s\" to a view because it has row security policies", + RelationGetRelationName(event_relation)))); + + RelisBecomingView = true; + } + } + else + { + /* + * For non-SELECT rules, a RETURNING list can appear in at most one of + * the actions ... and there can't be any RETURNING list at all in a + * conditional or non-INSTEAD rule. (Actually, there can be at most + * one RETURNING list across all rules on the same event, but it seems + * best to enforce that at rule expansion time.) If there is a + * RETURNING list, it must match the event relation. + */ + bool haveReturning = false; + + foreach(l, action) + { + query = lfirst_node(Query, l); + + if (!query->returningList) + continue; + if (haveReturning) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot have multiple RETURNING lists in a rule"))); + haveReturning = true; + if (event_qual != NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("RETURNING lists are not supported in conditional rules"))); + if (!is_instead) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("RETURNING lists are not supported in non-INSTEAD rules"))); + checkRuleResultList(query->returningList, + RelationGetDescr(event_relation), + false, false); + } + } + + /* + * This rule is allowed - prepare to install it. + */ + + /* discard rule if it's null action and not INSTEAD; it's a no-op */ + if (action != NIL || is_instead) + { + ruleId = InsertRule(rulename, + event_type, + event_relid, + is_instead, + event_qual, + action, + replace); + + /* + * Set pg_class 'relhasrules' field true for event relation. + * + * Important side effect: an SI notice is broadcast to force all + * backends (including me!) to update relcache entries with the new + * rule. + */ + SetRelationRuleStatus(event_relid, true); + } + + /* --------------------------------------------------------------------- + * If the relation is becoming a view: + * - delete the associated storage files + * - get rid of any system attributes in pg_attribute; a view shouldn't + * have any of those + * - remove the toast table; there is no need for it anymore, and its + * presence would make vacuum slightly more complicated + * - set relkind to RELKIND_VIEW, and adjust other pg_class fields + * to be appropriate for a view + * + * NB: we had better have AccessExclusiveLock to do this ... + * --------------------------------------------------------------------- + */ + if (RelisBecomingView) + { + Relation relationRelation; + Oid toastrelid; + HeapTuple classTup; + Form_pg_class classForm; + + relationRelation = table_open(RelationRelationId, RowExclusiveLock); + toastrelid = event_relation->rd_rel->reltoastrelid; + + /* drop storage while table still looks like a table */ + RelationDropStorage(event_relation); + DeleteSystemAttributeTuples(event_relid); + + /* + * Drop the toast table if any. (This won't take care of updating the + * toast fields in the relation's own pg_class entry; we handle that + * below.) + */ + if (OidIsValid(toastrelid)) + { + ObjectAddress toastobject; + + /* + * Delete the dependency of the toast relation on the main + * relation so we can drop the former without dropping the latter. + */ + deleteDependencyRecordsFor(RelationRelationId, toastrelid, + false); + + /* Make deletion of dependency record visible */ + CommandCounterIncrement(); + + /* Now drop toast table, including its index */ + toastobject.classId = RelationRelationId; + toastobject.objectId = toastrelid; + toastobject.objectSubId = 0; + performDeletion(&toastobject, DROP_RESTRICT, + PERFORM_DELETION_INTERNAL); + } + + /* + * SetRelationRuleStatus may have updated the pg_class row, so we must + * advance the command counter before trying to update it again. + */ + CommandCounterIncrement(); + + /* + * Fix pg_class entry to look like a normal view's, including setting + * the correct relkind and removal of reltoastrelid of the toast table + * we potentially removed above. + */ + classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(event_relid)); + if (!HeapTupleIsValid(classTup)) + elog(ERROR, "cache lookup failed for relation %u", event_relid); + classForm = (Form_pg_class) GETSTRUCT(classTup); + + classForm->relam = InvalidOid; + classForm->reltablespace = InvalidOid; + classForm->relpages = 0; + classForm->reltuples = -1; + classForm->relallvisible = 0; + classForm->reltoastrelid = InvalidOid; + classForm->relhasindex = false; + classForm->relkind = RELKIND_VIEW; + classForm->relfrozenxid = InvalidTransactionId; + classForm->relminmxid = InvalidMultiXactId; + classForm->relreplident = REPLICA_IDENTITY_NOTHING; + + CatalogTupleUpdate(relationRelation, &classTup->t_self, classTup); + + heap_freetuple(classTup); + table_close(relationRelation, RowExclusiveLock); + } + + ObjectAddressSet(address, RewriteRelationId, ruleId); + + /* Close rel, but keep lock till commit... */ + table_close(event_relation, NoLock); + + return address; +} + +/* + * checkRuleResultList + * Verify that targetList produces output compatible with a tupledesc + * + * The targetList might be either a SELECT targetlist, or a RETURNING list; + * isSelect tells which. This is used for choosing error messages. + * + * A SELECT targetlist may optionally require that column names match. + */ +static void +checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect, + bool requireColumnNameMatch) +{ + ListCell *tllist; + int i; + + /* Only a SELECT may require a column name match. */ + Assert(isSelect || !requireColumnNameMatch); + + i = 0; + foreach(tllist, targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(tllist); + Oid tletypid; + int32 tletypmod; + Form_pg_attribute attr; + char *attname; + + /* resjunk entries may be ignored */ + if (tle->resjunk) + continue; + i++; + if (i > resultDesc->natts) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + isSelect ? + errmsg("SELECT rule's target list has too many entries") : + errmsg("RETURNING list has too many entries"))); + + attr = TupleDescAttr(resultDesc, i - 1); + attname = NameStr(attr->attname); + + /* + * Disallow dropped columns in the relation. This is not really + * expected to happen when creating an ON SELECT rule. It'd be + * possible if someone tried to convert a relation with dropped + * columns to a view, but the only case we care about supporting + * table-to-view conversion for is pg_dump, and pg_dump won't do that. + * + * Unfortunately, the situation is also possible when adding a rule + * with RETURNING to a regular table, and rejecting that case is + * altogether more annoying. In principle we could support it by + * modifying the targetlist to include dummy NULL columns + * corresponding to the dropped columns in the tupdesc. However, + * places like ruleutils.c would have to be fixed to not process such + * entries, and that would take an uncertain and possibly rather large + * amount of work. (Note we could not dodge that by marking the dummy + * columns resjunk, since it's precisely the non-resjunk tlist columns + * that are expected to correspond to table columns.) + */ + if (attr->attisdropped) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + isSelect ? + errmsg("cannot convert relation containing dropped columns to view") : + errmsg("cannot create a RETURNING list for a relation containing dropped columns"))); + + /* Check name match if required; no need for two error texts here */ + if (requireColumnNameMatch && strcmp(tle->resname, attname) != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("SELECT rule's target entry %d has different column name from column \"%s\"", + i, attname), + errdetail("SELECT target entry is named \"%s\".", + tle->resname))); + + /* Check type match. */ + tletypid = exprType((Node *) tle->expr); + if (attr->atttypid != tletypid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + isSelect ? + errmsg("SELECT rule's target entry %d has different type from column \"%s\"", + i, attname) : + errmsg("RETURNING list's entry %d has different type from column \"%s\"", + i, attname), + isSelect ? + errdetail("SELECT target entry has type %s, but column has type %s.", + format_type_be(tletypid), + format_type_be(attr->atttypid)) : + errdetail("RETURNING list entry has type %s, but column has type %s.", + format_type_be(tletypid), + format_type_be(attr->atttypid)))); + + /* + * Allow typmods to be different only if one of them is -1, ie, + * "unspecified". This is necessary for cases like "numeric", where + * the table will have a filled-in default length but the select + * rule's expression will probably have typmod = -1. + */ + tletypmod = exprTypmod((Node *) tle->expr); + if (attr->atttypmod != tletypmod && + attr->atttypmod != -1 && tletypmod != -1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + isSelect ? + errmsg("SELECT rule's target entry %d has different size from column \"%s\"", + i, attname) : + errmsg("RETURNING list's entry %d has different size from column \"%s\"", + i, attname), + isSelect ? + errdetail("SELECT target entry has type %s, but column has type %s.", + format_type_with_typemod(tletypid, tletypmod), + format_type_with_typemod(attr->atttypid, + attr->atttypmod)) : + errdetail("RETURNING list entry has type %s, but column has type %s.", + format_type_with_typemod(tletypid, tletypmod), + format_type_with_typemod(attr->atttypid, + attr->atttypmod)))); + } + + if (i != resultDesc->natts) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + isSelect ? + errmsg("SELECT rule's target list has too few entries") : + errmsg("RETURNING list has too few entries"))); +} + +/* + * setRuleCheckAsUser + * Recursively scan a query or expression tree and set the checkAsUser + * field to the given userid in all rtable entries. + * + * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD + * RTE entry will be overridden when the view rule is expanded, and the + * checkAsUser field of the NEW entry is irrelevant because that entry's + * requiredPerms bits will always be zero. However, for other types of rules + * it's important to set these fields to match the rule owner. So we just set + * them always. + */ +void +setRuleCheckAsUser(Node *node, Oid userid) +{ + (void) setRuleCheckAsUser_walker(node, &userid); +} + +static bool +setRuleCheckAsUser_walker(Node *node, Oid *context) +{ + if (node == NULL) + return false; + if (IsA(node, Query)) + { + setRuleCheckAsUser_Query((Query *) node, *context); + return false; + } + return expression_tree_walker(node, setRuleCheckAsUser_walker, + (void *) context); +} + +static void +setRuleCheckAsUser_Query(Query *qry, Oid userid) +{ + ListCell *l; + + /* Set all the RTEs in this query node */ + foreach(l, qry->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(l); + + if (rte->rtekind == RTE_SUBQUERY) + { + /* Recurse into subquery in FROM */ + setRuleCheckAsUser_Query(rte->subquery, userid); + } + else + rte->checkAsUser = userid; + } + + /* Recurse into subquery-in-WITH */ + foreach(l, qry->cteList) + { + CommonTableExpr *cte = (CommonTableExpr *) lfirst(l); + + setRuleCheckAsUser_Query(castNode(Query, cte->ctequery), userid); + } + + /* If there are sublinks, search for them and process their RTEs */ + if (qry->hasSubLinks) + query_tree_walker(qry, setRuleCheckAsUser_walker, (void *) &userid, + QTW_IGNORE_RC_SUBQUERIES); +} + + +/* + * Change the firing semantics of an existing rule. + */ +void +EnableDisableRule(Relation rel, const char *rulename, + char fires_when) +{ + Relation pg_rewrite_desc; + Oid owningRel = RelationGetRelid(rel); + Oid eventRelationOid; + HeapTuple ruletup; + Form_pg_rewrite ruleform; + bool changed = false; + + /* + * Find the rule tuple to change. + */ + pg_rewrite_desc = table_open(RewriteRelationId, RowExclusiveLock); + ruletup = SearchSysCacheCopy2(RULERELNAME, + ObjectIdGetDatum(owningRel), + PointerGetDatum(rulename)); + if (!HeapTupleIsValid(ruletup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("rule \"%s\" for relation \"%s\" does not exist", + rulename, get_rel_name(owningRel)))); + + ruleform = (Form_pg_rewrite) GETSTRUCT(ruletup); + + /* + * Verify that the user has appropriate permissions. + */ + eventRelationOid = ruleform->ev_class; + Assert(eventRelationOid == owningRel); + if (!pg_class_ownercheck(eventRelationOid, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(eventRelationOid)), + get_rel_name(eventRelationOid)); + + /* + * Change ev_enabled if it is different from the desired new state. + */ + if (DatumGetChar(ruleform->ev_enabled) != + fires_when) + { + ruleform->ev_enabled = CharGetDatum(fires_when); + CatalogTupleUpdate(pg_rewrite_desc, &ruletup->t_self, ruletup); + + changed = true; + } + + InvokeObjectPostAlterHook(RewriteRelationId, ruleform->oid, 0); + + heap_freetuple(ruletup); + table_close(pg_rewrite_desc, RowExclusiveLock); + + /* + * If we changed anything, broadcast a SI inval message to force each + * backend (including our own!) to rebuild relation's relcache entry. + * Otherwise they will fail to apply the change promptly. + */ + if (changed) + CacheInvalidateRelcache(rel); +} + + +/* + * Perform permissions and integrity checks before acquiring a relation lock. + */ +static void +RangeVarCallbackForRenameRule(const RangeVar *rv, Oid relid, Oid oldrelid, + void *arg) +{ + HeapTuple tuple; + Form_pg_class form; + + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tuple)) + return; /* concurrently dropped */ + form = (Form_pg_class) GETSTRUCT(tuple); + + /* only tables and views can have rules */ + if (form->relkind != RELKIND_RELATION && + form->relkind != RELKIND_VIEW && + form->relkind != RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table or view", rv->relname))); + + if (!allowSystemTableMods && IsSystemClass(relid, form)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied: \"%s\" is a system catalog", + rv->relname))); + + /* you must own the table to rename one of its rules */ + if (!pg_class_ownercheck(relid, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relid)), rv->relname); + + ReleaseSysCache(tuple); +} + +/* + * Rename an existing rewrite rule. + */ +ObjectAddress +RenameRewriteRule(RangeVar *relation, const char *oldName, + const char *newName) +{ + Oid relid; + Relation targetrel; + Relation pg_rewrite_desc; + HeapTuple ruletup; + Form_pg_rewrite ruleform; + Oid ruleOid; + ObjectAddress address; + + /* + * Look up name, check permissions, and acquire lock (which we will NOT + * release until end of transaction). + */ + relid = RangeVarGetRelidExtended(relation, AccessExclusiveLock, + 0, + RangeVarCallbackForRenameRule, + NULL); + + /* Have lock already, so just need to build relcache entry. */ + targetrel = relation_open(relid, NoLock); + + /* Prepare to modify pg_rewrite */ + pg_rewrite_desc = table_open(RewriteRelationId, RowExclusiveLock); + + /* Fetch the rule's entry (it had better exist) */ + ruletup = SearchSysCacheCopy2(RULERELNAME, + ObjectIdGetDatum(relid), + PointerGetDatum(oldName)); + if (!HeapTupleIsValid(ruletup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("rule \"%s\" for relation \"%s\" does not exist", + oldName, RelationGetRelationName(targetrel)))); + ruleform = (Form_pg_rewrite) GETSTRUCT(ruletup); + ruleOid = ruleform->oid; + + /* rule with the new name should not already exist */ + if (IsDefinedRewriteRule(relid, newName)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("rule \"%s\" for relation \"%s\" already exists", + newName, RelationGetRelationName(targetrel)))); + + /* + * We disallow renaming ON SELECT rules, because they should always be + * named "_RETURN". + */ + if (ruleform->ev_type == CMD_SELECT + '0') + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("renaming an ON SELECT rule is not allowed"))); + + /* OK, do the update */ + namestrcpy(&(ruleform->rulename), newName); + + CatalogTupleUpdate(pg_rewrite_desc, &ruletup->t_self, ruletup); + + InvokeObjectPostAlterHook(RewriteRelationId, ruleOid, 0); + + heap_freetuple(ruletup); + table_close(pg_rewrite_desc, RowExclusiveLock); + + /* + * Invalidate relation's relcache entry so that other backends (and this + * one too!) are sent SI message to make them rebuild relcache entries. + * (Ideally this should happen automatically...) + */ + CacheInvalidateRelcache(targetrel); + + ObjectAddressSet(address, RewriteRelationId, ruleOid); + + /* + * Close rel, but keep exclusive lock! + */ + relation_close(targetrel, NoLock); + + return address; +} diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c new file mode 100644 index 0000000..9521e81 --- /dev/null +++ b/src/backend/rewrite/rewriteHandler.c @@ -0,0 +1,4169 @@ +/*------------------------------------------------------------------------- + * + * rewriteHandler.c + * Primary module of query rewriter. + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/rewrite/rewriteHandler.c + * + * NOTES + * Some of the terms used in this file are of historic nature: "retrieve" + * was the PostQUEL keyword for what today is SELECT. "RIR" stands for + * "Retrieve-Instead-Retrieve", that is an ON SELECT DO INSTEAD SELECT rule + * (which has to be unconditional and where only one rule can exist on each + * relation). + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/relation.h" +#include "access/sysattr.h" +#include "access/table.h" +#include "catalog/dependency.h" +#include "catalog/pg_type.h" +#include "commands/trigger.h" +#include "foreign/fdwapi.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/optimizer.h" +#include "parser/analyze.h" +#include "parser/parse_coerce.h" +#include "parser/parse_relation.h" +#include "parser/parsetree.h" +#include "rewrite/rewriteDefine.h" +#include "rewrite/rewriteHandler.h" +#include "rewrite/rewriteManip.h" +#include "rewrite/rewriteSearchCycle.h" +#include "rewrite/rowsecurity.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" + + +/* We use a list of these to detect recursion in RewriteQuery */ +typedef struct rewrite_event +{ + Oid relation; /* OID of relation having rules */ + CmdType event; /* type of rule being fired */ +} rewrite_event; + +typedef struct acquireLocksOnSubLinks_context +{ + bool for_execute; /* AcquireRewriteLocks' forExecute param */ +} acquireLocksOnSubLinks_context; + +static bool acquireLocksOnSubLinks(Node *node, + acquireLocksOnSubLinks_context *context); +static Query *rewriteRuleAction(Query *parsetree, + Query *rule_action, + Node *rule_qual, + int rt_index, + CmdType event, + bool *returning_flag); +static List *adjustJoinTreeList(Query *parsetree, bool removert, int rt_index); +static List *rewriteTargetListIU(List *targetList, + CmdType commandType, + OverridingKind override, + Relation target_relation, + RangeTblEntry *values_rte, + int values_rte_index, + Bitmapset **unused_values_attrnos); +static TargetEntry *process_matched_tle(TargetEntry *src_tle, + TargetEntry *prior_tle, + const char *attrName); +static Node *get_assignment_input(Node *node); +static Bitmapset *findDefaultOnlyColumns(RangeTblEntry *rte); +static bool rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti, + Relation target_relation, bool force_nulls, + Bitmapset *unused_cols); +static void markQueryForLocking(Query *qry, Node *jtnode, + LockClauseStrength strength, LockWaitPolicy waitPolicy, + bool pushedDown); +static List *matchLocks(CmdType event, RuleLock *rulelocks, + int varno, Query *parsetree, bool *hasUpdate); +static Query *fireRIRrules(Query *parsetree, List *activeRIRs); +static bool view_has_instead_trigger(Relation view, CmdType event); +static Bitmapset *adjust_view_column_set(Bitmapset *cols, List *targetlist); + + +/* + * AcquireRewriteLocks - + * Acquire suitable locks on all the relations mentioned in the Query. + * These locks will ensure that the relation schemas don't change under us + * while we are rewriting, planning, and executing the query. + * + * Caution: this may modify the querytree, therefore caller should usually + * have done a copyObject() to make a writable copy of the querytree in the + * current memory context. + * + * forExecute indicates that the query is about to be executed. If so, + * we'll acquire the lock modes specified in the RTE rellockmode fields. + * If forExecute is false, AccessShareLock is acquired on all relations. + * This case is suitable for ruleutils.c, for example, where we only need + * schema stability and we don't intend to actually modify any relations. + * + * forUpdatePushedDown indicates that a pushed-down FOR [KEY] UPDATE/SHARE + * applies to the current subquery, requiring all rels to be opened with at + * least RowShareLock. This should always be false at the top of the + * recursion. When it is true, we adjust RTE rellockmode fields to reflect + * the higher lock level. This flag is ignored if forExecute is false. + * + * A secondary purpose of this routine is to fix up JOIN RTE references to + * dropped columns (see details below). Such RTEs are modified in-place. + * + * This processing can, and for efficiency's sake should, be skipped when the + * querytree has just been built by the parser: parse analysis already got + * all the same locks we'd get here, and the parser will have omitted dropped + * columns from JOINs to begin with. But we must do this whenever we are + * dealing with a querytree produced earlier than the current command. + * + * About JOINs and dropped columns: although the parser never includes an + * already-dropped column in a JOIN RTE's alias var list, it is possible for + * such a list in a stored rule to include references to dropped columns. + * (If the column is not explicitly referenced anywhere else in the query, + * the dependency mechanism won't consider it used by the rule and so won't + * prevent the column drop.) To support get_rte_attribute_is_dropped(), we + * replace join alias vars that reference dropped columns with null pointers. + * + * (In PostgreSQL 8.0, we did not do this processing but instead had + * get_rte_attribute_is_dropped() recurse to detect dropped columns in joins. + * That approach had horrible performance unfortunately; in particular + * construction of a nested join was O(N^2) in the nesting depth.) + */ +void +AcquireRewriteLocks(Query *parsetree, + bool forExecute, + bool forUpdatePushedDown) +{ + ListCell *l; + int rt_index; + acquireLocksOnSubLinks_context context; + + context.for_execute = forExecute; + + /* + * First, process RTEs of the current query level. + */ + rt_index = 0; + foreach(l, parsetree->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(l); + Relation rel; + LOCKMODE lockmode; + List *newaliasvars; + Index curinputvarno; + RangeTblEntry *curinputrte; + ListCell *ll; + + ++rt_index; + switch (rte->rtekind) + { + case RTE_RELATION: + + /* + * Grab the appropriate lock type for the relation, and do not + * release it until end of transaction. This protects the + * rewriter, planner, and executor against schema changes + * mid-query. + * + * If forExecute is false, ignore rellockmode and just use + * AccessShareLock. + */ + if (!forExecute) + lockmode = AccessShareLock; + else if (forUpdatePushedDown) + { + /* Upgrade RTE's lock mode to reflect pushed-down lock */ + if (rte->rellockmode == AccessShareLock) + rte->rellockmode = RowShareLock; + lockmode = rte->rellockmode; + } + else + lockmode = rte->rellockmode; + + rel = table_open(rte->relid, lockmode); + + /* + * While we have the relation open, update the RTE's relkind, + * just in case it changed since this rule was made. + */ + rte->relkind = rel->rd_rel->relkind; + + table_close(rel, NoLock); + break; + + case RTE_JOIN: + + /* + * Scan the join's alias var list to see if any columns have + * been dropped, and if so replace those Vars with null + * pointers. + * + * Since a join has only two inputs, we can expect to see + * multiple references to the same input RTE; optimize away + * multiple fetches. + */ + newaliasvars = NIL; + curinputvarno = 0; + curinputrte = NULL; + foreach(ll, rte->joinaliasvars) + { + Var *aliasitem = (Var *) lfirst(ll); + Var *aliasvar = aliasitem; + + /* Look through any implicit coercion */ + aliasvar = (Var *) strip_implicit_coercions((Node *) aliasvar); + + /* + * If the list item isn't a simple Var, then it must + * represent a merged column, ie a USING column, and so it + * couldn't possibly be dropped, since it's referenced in + * the join clause. (Conceivably it could also be a null + * pointer already? But that's OK too.) + */ + if (aliasvar && IsA(aliasvar, Var)) + { + /* + * The elements of an alias list have to refer to + * earlier RTEs of the same rtable, because that's the + * order the planner builds things in. So we already + * processed the referenced RTE, and so it's safe to + * use get_rte_attribute_is_dropped on it. (This might + * not hold after rewriting or planning, but it's OK + * to assume here.) + */ + Assert(aliasvar->varlevelsup == 0); + if (aliasvar->varno != curinputvarno) + { + curinputvarno = aliasvar->varno; + if (curinputvarno >= rt_index) + elog(ERROR, "unexpected varno %d in JOIN RTE %d", + curinputvarno, rt_index); + curinputrte = rt_fetch(curinputvarno, + parsetree->rtable); + } + if (get_rte_attribute_is_dropped(curinputrte, + aliasvar->varattno)) + { + /* Replace the join alias item with a NULL */ + aliasitem = NULL; + } + } + newaliasvars = lappend(newaliasvars, aliasitem); + } + rte->joinaliasvars = newaliasvars; + break; + + case RTE_SUBQUERY: + + /* + * The subquery RTE itself is all right, but we have to + * recurse to process the represented subquery. + */ + AcquireRewriteLocks(rte->subquery, + forExecute, + (forUpdatePushedDown || + get_parse_rowmark(parsetree, rt_index) != NULL)); + break; + + default: + /* ignore other types of RTEs */ + break; + } + } + + /* Recurse into subqueries in WITH */ + foreach(l, parsetree->cteList) + { + CommonTableExpr *cte = (CommonTableExpr *) lfirst(l); + + AcquireRewriteLocks((Query *) cte->ctequery, forExecute, false); + } + + /* + * Recurse into sublink subqueries, too. But we already did the ones in + * the rtable and cteList. + */ + if (parsetree->hasSubLinks) + query_tree_walker(parsetree, acquireLocksOnSubLinks, &context, + QTW_IGNORE_RC_SUBQUERIES); +} + +/* + * Walker to find sublink subqueries for AcquireRewriteLocks + */ +static bool +acquireLocksOnSubLinks(Node *node, acquireLocksOnSubLinks_context *context) +{ + if (node == NULL) + return false; + if (IsA(node, SubLink)) + { + SubLink *sub = (SubLink *) node; + + /* Do what we came for */ + AcquireRewriteLocks((Query *) sub->subselect, + context->for_execute, + false); + /* Fall through to process lefthand args of SubLink */ + } + + /* + * Do NOT recurse into Query nodes, because AcquireRewriteLocks already + * processed subselects of subselects for us. + */ + return expression_tree_walker(node, acquireLocksOnSubLinks, context); +} + + +/* + * rewriteRuleAction - + * Rewrite the rule action with appropriate qualifiers (taken from + * the triggering query). + * + * Input arguments: + * parsetree - original query + * rule_action - one action (query) of a rule + * rule_qual - WHERE condition of rule, or NULL if unconditional + * rt_index - RT index of result relation in original query + * event - type of rule event + * Output arguments: + * *returning_flag - set true if we rewrite RETURNING clause in rule_action + * (must be initialized to false) + * Return value: + * rewritten form of rule_action + */ +static Query * +rewriteRuleAction(Query *parsetree, + Query *rule_action, + Node *rule_qual, + int rt_index, + CmdType event, + bool *returning_flag) +{ + int current_varno, + new_varno; + int rt_length; + Query *sub_action; + Query **sub_action_ptr; + acquireLocksOnSubLinks_context context; + + context.for_execute = true; + + /* + * Make modifiable copies of rule action and qual (what we're passed are + * the stored versions in the relcache; don't touch 'em!). + */ + rule_action = copyObject(rule_action); + rule_qual = copyObject(rule_qual); + + /* + * Acquire necessary locks and fix any deleted JOIN RTE entries. + */ + AcquireRewriteLocks(rule_action, true, false); + (void) acquireLocksOnSubLinks(rule_qual, &context); + + current_varno = rt_index; + rt_length = list_length(parsetree->rtable); + new_varno = PRS2_NEW_VARNO + rt_length; + + /* + * Adjust rule action and qual to offset its varnos, so that we can merge + * its rtable with the main parsetree's rtable. + * + * If the rule action is an INSERT...SELECT, the OLD/NEW rtable entries + * will be in the SELECT part, and we have to modify that rather than the + * top-level INSERT (kluge!). + */ + sub_action = getInsertSelectQuery(rule_action, &sub_action_ptr); + + OffsetVarNodes((Node *) sub_action, rt_length, 0); + OffsetVarNodes(rule_qual, rt_length, 0); + /* but references to OLD should point at original rt_index */ + ChangeVarNodes((Node *) sub_action, + PRS2_OLD_VARNO + rt_length, rt_index, 0); + ChangeVarNodes(rule_qual, + PRS2_OLD_VARNO + rt_length, rt_index, 0); + + /* + * Generate expanded rtable consisting of main parsetree's rtable plus + * rule action's rtable; this becomes the complete rtable for the rule + * action. Some of the entries may be unused after we finish rewriting, + * but we leave them all in place for two reasons: + * + * We'd have a much harder job to adjust the query's varnos if we + * selectively removed RT entries. + * + * If the rule is INSTEAD, then the original query won't be executed at + * all, and so its rtable must be preserved so that the executor will do + * the correct permissions checks on it. + * + * RT entries that are not referenced in the completed jointree will be + * ignored by the planner, so they do not affect query semantics. But any + * permissions checks specified in them will be applied during executor + * startup (see ExecCheckRTEPerms()). This allows us to check that the + * caller has, say, insert-permission on a view, when the view is not + * semantically referenced at all in the resulting query. + * + * When a rule is not INSTEAD, the permissions checks done on its copied + * RT entries will be redundant with those done during execution of the + * original query, but we don't bother to treat that case differently. + * + * NOTE: because planner will destructively alter rtable, we must ensure + * that rule action's rtable is separate and shares no substructure with + * the main rtable. Hence do a deep copy here. + */ + sub_action->rtable = list_concat(copyObject(parsetree->rtable), + sub_action->rtable); + + /* + * There could have been some SubLinks in parsetree's rtable, in which + * case we'd better mark the sub_action correctly. + */ + if (parsetree->hasSubLinks && !sub_action->hasSubLinks) + { + ListCell *lc; + + foreach(lc, parsetree->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); + + switch (rte->rtekind) + { + case RTE_RELATION: + sub_action->hasSubLinks = + checkExprHasSubLink((Node *) rte->tablesample); + break; + case RTE_FUNCTION: + sub_action->hasSubLinks = + checkExprHasSubLink((Node *) rte->functions); + break; + case RTE_TABLEFUNC: + sub_action->hasSubLinks = + checkExprHasSubLink((Node *) rte->tablefunc); + break; + case RTE_VALUES: + sub_action->hasSubLinks = + checkExprHasSubLink((Node *) rte->values_lists); + break; + default: + /* other RTE types don't contain bare expressions */ + break; + } + if (sub_action->hasSubLinks) + break; /* no need to keep scanning rtable */ + } + } + + /* + * Also, we might have absorbed some RTEs with RLS conditions into the + * sub_action. If so, mark it as hasRowSecurity, whether or not those + * RTEs will be referenced after we finish rewriting. (Note: currently + * this is a no-op because RLS conditions aren't added till later, but it + * seems like good future-proofing to do this anyway.) + */ + sub_action->hasRowSecurity |= parsetree->hasRowSecurity; + + /* + * Each rule action's jointree should be the main parsetree's jointree + * plus that rule's jointree, but usually *without* the original rtindex + * that we're replacing (if present, which it won't be for INSERT). Note + * that if the rule action refers to OLD, its jointree will add a + * reference to rt_index. If the rule action doesn't refer to OLD, but + * either the rule_qual or the user query quals do, then we need to keep + * the original rtindex in the jointree to provide data for the quals. We + * don't want the original rtindex to be joined twice, however, so avoid + * keeping it if the rule action mentions it. + * + * As above, the action's jointree must not share substructure with the + * main parsetree's. + */ + if (sub_action->commandType != CMD_UTILITY) + { + bool keeporig; + List *newjointree; + + Assert(sub_action->jointree != NULL); + keeporig = (!rangeTableEntry_used((Node *) sub_action->jointree, + rt_index, 0)) && + (rangeTableEntry_used(rule_qual, rt_index, 0) || + rangeTableEntry_used(parsetree->jointree->quals, rt_index, 0)); + newjointree = adjustJoinTreeList(parsetree, !keeporig, rt_index); + if (newjointree != NIL) + { + /* + * If sub_action is a setop, manipulating its jointree will do no + * good at all, because the jointree is dummy. (Perhaps someday + * we could push the joining and quals down to the member + * statements of the setop?) + */ + if (sub_action->setOperations != NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("conditional UNION/INTERSECT/EXCEPT statements are not implemented"))); + + sub_action->jointree->fromlist = + list_concat(newjointree, sub_action->jointree->fromlist); + + /* + * There could have been some SubLinks in newjointree, in which + * case we'd better mark the sub_action correctly. + */ + if (parsetree->hasSubLinks && !sub_action->hasSubLinks) + sub_action->hasSubLinks = + checkExprHasSubLink((Node *) newjointree); + } + } + + /* + * If the original query has any CTEs, copy them into the rule action. But + * we don't need them for a utility action. + */ + if (parsetree->cteList != NIL && sub_action->commandType != CMD_UTILITY) + { + ListCell *lc; + + /* + * Annoying implementation restriction: because CTEs are identified by + * name within a cteList, we can't merge a CTE from the original query + * if it has the same name as any CTE in the rule action. + * + * This could possibly be fixed by using some sort of internally + * generated ID, instead of names, to link CTE RTEs to their CTEs. + * However, decompiling the results would be quite confusing; note the + * merge of hasRecursive flags below, which could change the apparent + * semantics of such redundantly-named CTEs. + */ + foreach(lc, parsetree->cteList) + { + CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, sub_action->cteList) + { + CommonTableExpr *cte2 = (CommonTableExpr *) lfirst(lc2); + + if (strcmp(cte->ctename, cte2->ctename) == 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("WITH query name \"%s\" appears in both a rule action and the query being rewritten", + cte->ctename))); + } + } + + /* OK, it's safe to combine the CTE lists */ + sub_action->cteList = list_concat(sub_action->cteList, + copyObject(parsetree->cteList)); + /* ... and don't forget about the associated flags */ + sub_action->hasRecursive |= parsetree->hasRecursive; + sub_action->hasModifyingCTE |= parsetree->hasModifyingCTE; + + /* + * If rule_action is different from sub_action (i.e., the rule action + * is an INSERT...SELECT), then we might have just added some + * data-modifying CTEs that are not at the top query level. This is + * disallowed by the parser and we mustn't generate such trees here + * either, so throw an error. + * + * Conceivably such cases could be supported by attaching the original + * query's CTEs to rule_action not sub_action. But to do that, we'd + * have to increment ctelevelsup in RTEs and SubLinks copied from the + * original query. For now, it doesn't seem worth the trouble. + */ + if (sub_action->hasModifyingCTE && rule_action != sub_action) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("INSERT...SELECT rule actions are not supported for queries having data-modifying statements in WITH"))); + } + + /* + * Event Qualification forces copying of parsetree and splitting into two + * queries one w/rule_qual, one w/NOT rule_qual. Also add user query qual + * onto rule action + */ + AddQual(sub_action, rule_qual); + + AddQual(sub_action, parsetree->jointree->quals); + + /* + * Rewrite new.attribute with right hand side of target-list entry for + * appropriate field name in insert/update. + * + * KLUGE ALERT: since ReplaceVarsFromTargetList returns a mutated copy, we + * can't just apply it to sub_action; we have to remember to update the + * sublink inside rule_action, too. + */ + if ((event == CMD_INSERT || event == CMD_UPDATE) && + sub_action->commandType != CMD_UTILITY) + { + sub_action = (Query *) + ReplaceVarsFromTargetList((Node *) sub_action, + new_varno, + 0, + rt_fetch(new_varno, sub_action->rtable), + parsetree->targetList, + (event == CMD_UPDATE) ? + REPLACEVARS_CHANGE_VARNO : + REPLACEVARS_SUBSTITUTE_NULL, + current_varno, + NULL); + if (sub_action_ptr) + *sub_action_ptr = sub_action; + else + rule_action = sub_action; + } + + /* + * If rule_action has a RETURNING clause, then either throw it away if the + * triggering query has no RETURNING clause, or rewrite it to emit what + * the triggering query's RETURNING clause asks for. Throw an error if + * more than one rule has a RETURNING clause. + */ + if (!parsetree->returningList) + rule_action->returningList = NIL; + else if (rule_action->returningList) + { + if (*returning_flag) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot have RETURNING lists in multiple rules"))); + *returning_flag = true; + rule_action->returningList = (List *) + ReplaceVarsFromTargetList((Node *) parsetree->returningList, + parsetree->resultRelation, + 0, + rt_fetch(parsetree->resultRelation, + parsetree->rtable), + rule_action->returningList, + REPLACEVARS_REPORT_ERROR, + 0, + &rule_action->hasSubLinks); + + /* + * There could have been some SubLinks in parsetree's returningList, + * in which case we'd better mark the rule_action correctly. + */ + if (parsetree->hasSubLinks && !rule_action->hasSubLinks) + rule_action->hasSubLinks = + checkExprHasSubLink((Node *) rule_action->returningList); + } + + return rule_action; +} + +/* + * Copy the query's jointree list, and optionally attempt to remove any + * occurrence of the given rt_index as a top-level join item (we do not look + * for it within join items; this is OK because we are only expecting to find + * it as an UPDATE or DELETE target relation, which will be at the top level + * of the join). Returns modified jointree list --- this is a separate copy + * sharing no nodes with the original. + */ +static List * +adjustJoinTreeList(Query *parsetree, bool removert, int rt_index) +{ + List *newjointree = copyObject(parsetree->jointree->fromlist); + ListCell *l; + + if (removert) + { + foreach(l, newjointree) + { + RangeTblRef *rtr = lfirst(l); + + if (IsA(rtr, RangeTblRef) && + rtr->rtindex == rt_index) + { + newjointree = foreach_delete_current(newjointree, l); + break; + } + } + } + return newjointree; +} + + +/* + * rewriteTargetListIU - rewrite INSERT/UPDATE targetlist into standard form + * + * This has the following responsibilities: + * + * 1. For an INSERT, add tlist entries to compute default values for any + * attributes that have defaults and are not assigned to in the given tlist. + * (We do not insert anything for default-less attributes, however. The + * planner will later insert NULLs for them, but there's no reason to slow + * down rewriter processing with extra tlist nodes.) Also, for both INSERT + * and UPDATE, replace explicit DEFAULT specifications with column default + * expressions. + * + * 2. Merge multiple entries for the same target attribute, or declare error + * if we can't. Multiple entries are only allowed for INSERT/UPDATE of + * portions of an array or record field, for example + * UPDATE table SET foo[2] = 42, foo[4] = 43; + * We can merge such operations into a single assignment op. Essentially, + * the expression we want to produce in this case is like + * foo = array_set_element(array_set_element(foo, 2, 42), 4, 43) + * + * 3. Sort the tlist into standard order: non-junk fields in order by resno, + * then junk fields (these in no particular order). + * + * We must do items 1 and 2 before firing rewrite rules, else rewritten + * references to NEW.foo will produce wrong or incomplete results. Item 3 + * is not needed for rewriting, but it is helpful for the planner, and we + * can do it essentially for free while handling the other items. + * + * If values_rte is non-NULL (i.e., we are doing a multi-row INSERT using + * values from a VALUES RTE), we populate *unused_values_attrnos with the + * attribute numbers of any unused columns from the VALUES RTE. This can + * happen for identity and generated columns whose targetlist entries are + * replaced with generated expressions (if INSERT ... OVERRIDING USER VALUE is + * used, or all the values to be inserted are DEFAULT). This information is + * required by rewriteValuesRTE() to handle any DEFAULT items in the unused + * columns. The caller must have initialized *unused_values_attrnos to NULL. + */ +static List * +rewriteTargetListIU(List *targetList, + CmdType commandType, + OverridingKind override, + Relation target_relation, + RangeTblEntry *values_rte, + int values_rte_index, + Bitmapset **unused_values_attrnos) +{ + TargetEntry **new_tles; + List *new_tlist = NIL; + List *junk_tlist = NIL; + Form_pg_attribute att_tup; + int attrno, + next_junk_attrno, + numattrs; + ListCell *temp; + Bitmapset *default_only_cols = NULL; + + /* + * We process the normal (non-junk) attributes by scanning the input tlist + * once and transferring TLEs into an array, then scanning the array to + * build an output tlist. This avoids O(N^2) behavior for large numbers + * of attributes. + * + * Junk attributes are tossed into a separate list during the same tlist + * scan, then appended to the reconstructed tlist. + */ + numattrs = RelationGetNumberOfAttributes(target_relation); + new_tles = (TargetEntry **) palloc0(numattrs * sizeof(TargetEntry *)); + next_junk_attrno = numattrs + 1; + + foreach(temp, targetList) + { + TargetEntry *old_tle = (TargetEntry *) lfirst(temp); + + if (!old_tle->resjunk) + { + /* Normal attr: stash it into new_tles[] */ + attrno = old_tle->resno; + if (attrno < 1 || attrno > numattrs) + elog(ERROR, "bogus resno %d in targetlist", attrno); + att_tup = TupleDescAttr(target_relation->rd_att, attrno - 1); + + /* We can (and must) ignore deleted attributes */ + if (att_tup->attisdropped) + continue; + + /* Merge with any prior assignment to same attribute */ + new_tles[attrno - 1] = + process_matched_tle(old_tle, + new_tles[attrno - 1], + NameStr(att_tup->attname)); + } + else + { + /* + * Copy all resjunk tlist entries to junk_tlist, and assign them + * resnos above the last real resno. + * + * Typical junk entries include ORDER BY or GROUP BY expressions + * (are these actually possible in an INSERT or UPDATE?), system + * attribute references, etc. + */ + + /* Get the resno right, but don't copy unnecessarily */ + if (old_tle->resno != next_junk_attrno) + { + old_tle = flatCopyTargetEntry(old_tle); + old_tle->resno = next_junk_attrno; + } + junk_tlist = lappend(junk_tlist, old_tle); + next_junk_attrno++; + } + } + + for (attrno = 1; attrno <= numattrs; attrno++) + { + TargetEntry *new_tle = new_tles[attrno - 1]; + bool apply_default; + + att_tup = TupleDescAttr(target_relation->rd_att, attrno - 1); + + /* We can (and must) ignore deleted attributes */ + if (att_tup->attisdropped) + continue; + + /* + * Handle the two cases where we need to insert a default expression: + * it's an INSERT and there's no tlist entry for the column, or the + * tlist entry is a DEFAULT placeholder node. + */ + apply_default = ((new_tle == NULL && commandType == CMD_INSERT) || + (new_tle && new_tle->expr && IsA(new_tle->expr, SetToDefault))); + + if (commandType == CMD_INSERT) + { + int values_attrno = 0; + + /* Source attribute number for values that come from a VALUES RTE */ + if (values_rte && new_tle && IsA(new_tle->expr, Var)) + { + Var *var = (Var *) new_tle->expr; + + if (var->varno == values_rte_index) + values_attrno = var->varattno; + } + + /* + * Can only insert DEFAULT into GENERATED ALWAYS identity columns, + * unless either OVERRIDING USER VALUE or OVERRIDING SYSTEM VALUE + * is specified. + */ + if (att_tup->attidentity == ATTRIBUTE_IDENTITY_ALWAYS && !apply_default) + { + if (override == OVERRIDING_USER_VALUE) + apply_default = true; + else if (override != OVERRIDING_SYSTEM_VALUE) + { + /* + * If this column's values come from a VALUES RTE, test + * whether it contains only SetToDefault items. Since the + * VALUES list might be quite large, we arrange to only + * scan it once. + */ + if (values_attrno != 0) + { + if (default_only_cols == NULL) + default_only_cols = findDefaultOnlyColumns(values_rte); + + if (bms_is_member(values_attrno, default_only_cols)) + apply_default = true; + } + + if (!apply_default) + ereport(ERROR, + (errcode(ERRCODE_GENERATED_ALWAYS), + errmsg("cannot insert a non-DEFAULT value into column \"%s\"", + NameStr(att_tup->attname)), + errdetail("Column \"%s\" is an identity column defined as GENERATED ALWAYS.", + NameStr(att_tup->attname)), + errhint("Use OVERRIDING SYSTEM VALUE to override."))); + } + } + + /* + * Although inserting into a GENERATED BY DEFAULT identity column + * is allowed, apply the default if OVERRIDING USER VALUE is + * specified. + */ + if (att_tup->attidentity == ATTRIBUTE_IDENTITY_BY_DEFAULT && + override == OVERRIDING_USER_VALUE) + apply_default = true; + + /* + * Can only insert DEFAULT into generated columns, regardless of + * any OVERRIDING clauses. + */ + if (att_tup->attgenerated && !apply_default) + { + /* + * If this column's values come from a VALUES RTE, test + * whether it contains only SetToDefault items, as above. + */ + if (values_attrno != 0) + { + if (default_only_cols == NULL) + default_only_cols = findDefaultOnlyColumns(values_rte); + + if (bms_is_member(values_attrno, default_only_cols)) + apply_default = true; + } + + if (!apply_default) + ereport(ERROR, + (errcode(ERRCODE_GENERATED_ALWAYS), + errmsg("cannot insert a non-DEFAULT value into column \"%s\"", + NameStr(att_tup->attname)), + errdetail("Column \"%s\" is a generated column.", + NameStr(att_tup->attname)))); + } + + /* + * For an INSERT from a VALUES RTE, return the attribute numbers + * of any VALUES columns that will no longer be used (due to the + * targetlist entry being replaced by a default expression). + */ + if (values_attrno != 0 && apply_default && unused_values_attrnos) + *unused_values_attrnos = bms_add_member(*unused_values_attrnos, + values_attrno); + } + + /* + * Updates to identity and generated columns follow the same rules as + * above, except that UPDATE doesn't admit OVERRIDING clauses. Also, + * the source can't be a VALUES RTE, so we needn't consider that. + */ + if (commandType == CMD_UPDATE) + { + if (att_tup->attidentity == ATTRIBUTE_IDENTITY_ALWAYS && + new_tle && !apply_default) + ereport(ERROR, + (errcode(ERRCODE_GENERATED_ALWAYS), + errmsg("column \"%s\" can only be updated to DEFAULT", + NameStr(att_tup->attname)), + errdetail("Column \"%s\" is an identity column defined as GENERATED ALWAYS.", + NameStr(att_tup->attname)))); + + if (att_tup->attgenerated && new_tle && !apply_default) + ereport(ERROR, + (errcode(ERRCODE_GENERATED_ALWAYS), + errmsg("column \"%s\" can only be updated to DEFAULT", + NameStr(att_tup->attname)), + errdetail("Column \"%s\" is a generated column.", + NameStr(att_tup->attname)))); + } + + if (att_tup->attgenerated) + { + /* + * stored generated column will be fixed in executor + */ + new_tle = NULL; + } + else if (apply_default) + { + Node *new_expr; + + new_expr = build_column_default(target_relation, attrno); + + /* + * If there is no default (ie, default is effectively NULL), we + * can omit the tlist entry in the INSERT case, since the planner + * can insert a NULL for itself, and there's no point in spending + * any more rewriter cycles on the entry. But in the UPDATE case + * we've got to explicitly set the column to NULL. + */ + if (!new_expr) + { + if (commandType == CMD_INSERT) + new_tle = NULL; + else + { + new_expr = (Node *) makeConst(att_tup->atttypid, + -1, + att_tup->attcollation, + att_tup->attlen, + (Datum) 0, + true, /* isnull */ + att_tup->attbyval); + /* this is to catch a NOT NULL domain constraint */ + new_expr = coerce_to_domain(new_expr, + InvalidOid, -1, + att_tup->atttypid, + COERCION_IMPLICIT, + COERCE_IMPLICIT_CAST, + -1, + false); + } + } + + if (new_expr) + new_tle = makeTargetEntry((Expr *) new_expr, + attrno, + pstrdup(NameStr(att_tup->attname)), + false); + } + + if (new_tle) + new_tlist = lappend(new_tlist, new_tle); + } + + pfree(new_tles); + + return list_concat(new_tlist, junk_tlist); +} + + +/* + * Convert a matched TLE from the original tlist into a correct new TLE. + * + * This routine detects and handles multiple assignments to the same target + * attribute. (The attribute name is needed only for error messages.) + */ +static TargetEntry * +process_matched_tle(TargetEntry *src_tle, + TargetEntry *prior_tle, + const char *attrName) +{ + TargetEntry *result; + CoerceToDomain *coerce_expr = NULL; + Node *src_expr; + Node *prior_expr; + Node *src_input; + Node *prior_input; + Node *priorbottom; + Node *newexpr; + + if (prior_tle == NULL) + { + /* + * Normal case where this is the first assignment to the attribute. + */ + return src_tle; + } + + /*---------- + * Multiple assignments to same attribute. Allow only if all are + * FieldStore or SubscriptingRef assignment operations. This is a bit + * tricky because what we may actually be looking at is a nest of + * such nodes; consider + * UPDATE tab SET col.fld1.subfld1 = x, col.fld2.subfld2 = y + * The two expressions produced by the parser will look like + * FieldStore(col, fld1, FieldStore(placeholder, subfld1, x)) + * FieldStore(col, fld2, FieldStore(placeholder, subfld2, y)) + * However, we can ignore the substructure and just consider the top + * FieldStore or SubscriptingRef from each assignment, because it works to + * combine these as + * FieldStore(FieldStore(col, fld1, + * FieldStore(placeholder, subfld1, x)), + * fld2, FieldStore(placeholder, subfld2, y)) + * Note the leftmost expression goes on the inside so that the + * assignments appear to occur left-to-right. + * + * For FieldStore, instead of nesting we can generate a single + * FieldStore with multiple target fields. We must nest when + * SubscriptingRefs are involved though. + * + * As a further complication, the destination column might be a domain, + * resulting in each assignment containing a CoerceToDomain node over a + * FieldStore or SubscriptingRef. These should have matching target + * domains, so we strip them and reconstitute a single CoerceToDomain over + * the combined FieldStore/SubscriptingRef nodes. (Notice that this has the + * result that the domain's checks are applied only after we do all the + * field or element updates, not after each one. This is arguably desirable.) + *---------- + */ + src_expr = (Node *) src_tle->expr; + prior_expr = (Node *) prior_tle->expr; + + if (src_expr && IsA(src_expr, CoerceToDomain) && + prior_expr && IsA(prior_expr, CoerceToDomain) && + ((CoerceToDomain *) src_expr)->resulttype == + ((CoerceToDomain *) prior_expr)->resulttype) + { + /* we assume without checking that resulttypmod/resultcollid match */ + coerce_expr = (CoerceToDomain *) src_expr; + src_expr = (Node *) ((CoerceToDomain *) src_expr)->arg; + prior_expr = (Node *) ((CoerceToDomain *) prior_expr)->arg; + } + + src_input = get_assignment_input(src_expr); + prior_input = get_assignment_input(prior_expr); + if (src_input == NULL || + prior_input == NULL || + exprType(src_expr) != exprType(prior_expr)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("multiple assignments to same column \"%s\"", + attrName))); + + /* + * Prior TLE could be a nest of assignments if we do this more than once. + */ + priorbottom = prior_input; + for (;;) + { + Node *newbottom = get_assignment_input(priorbottom); + + if (newbottom == NULL) + break; /* found the original Var reference */ + priorbottom = newbottom; + } + if (!equal(priorbottom, src_input)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("multiple assignments to same column \"%s\"", + attrName))); + + /* + * Looks OK to nest 'em. + */ + if (IsA(src_expr, FieldStore)) + { + FieldStore *fstore = makeNode(FieldStore); + + if (IsA(prior_expr, FieldStore)) + { + /* combine the two */ + memcpy(fstore, prior_expr, sizeof(FieldStore)); + fstore->newvals = + list_concat_copy(((FieldStore *) prior_expr)->newvals, + ((FieldStore *) src_expr)->newvals); + fstore->fieldnums = + list_concat_copy(((FieldStore *) prior_expr)->fieldnums, + ((FieldStore *) src_expr)->fieldnums); + } + else + { + /* general case, just nest 'em */ + memcpy(fstore, src_expr, sizeof(FieldStore)); + fstore->arg = (Expr *) prior_expr; + } + newexpr = (Node *) fstore; + } + else if (IsA(src_expr, SubscriptingRef)) + { + SubscriptingRef *sbsref = makeNode(SubscriptingRef); + + memcpy(sbsref, src_expr, sizeof(SubscriptingRef)); + sbsref->refexpr = (Expr *) prior_expr; + newexpr = (Node *) sbsref; + } + else + { + elog(ERROR, "cannot happen"); + newexpr = NULL; + } + + if (coerce_expr) + { + /* put back the CoerceToDomain */ + CoerceToDomain *newcoerce = makeNode(CoerceToDomain); + + memcpy(newcoerce, coerce_expr, sizeof(CoerceToDomain)); + newcoerce->arg = (Expr *) newexpr; + newexpr = (Node *) newcoerce; + } + + result = flatCopyTargetEntry(src_tle); + result->expr = (Expr *) newexpr; + return result; +} + +/* + * If node is an assignment node, return its input; else return NULL + */ +static Node * +get_assignment_input(Node *node) +{ + if (node == NULL) + return NULL; + if (IsA(node, FieldStore)) + { + FieldStore *fstore = (FieldStore *) node; + + return (Node *) fstore->arg; + } + else if (IsA(node, SubscriptingRef)) + { + SubscriptingRef *sbsref = (SubscriptingRef *) node; + + if (sbsref->refassgnexpr == NULL) + return NULL; + + return (Node *) sbsref->refexpr; + } + + return NULL; +} + +/* + * Make an expression tree for the default value for a column. + * + * If there is no default, return a NULL instead. + */ +Node * +build_column_default(Relation rel, int attrno) +{ + TupleDesc rd_att = rel->rd_att; + Form_pg_attribute att_tup = TupleDescAttr(rd_att, attrno - 1); + Oid atttype = att_tup->atttypid; + int32 atttypmod = att_tup->atttypmod; + Node *expr = NULL; + Oid exprtype; + + if (att_tup->attidentity) + { + NextValueExpr *nve = makeNode(NextValueExpr); + + nve->seqid = getIdentitySequence(RelationGetRelid(rel), attrno, false); + nve->typeId = att_tup->atttypid; + + return (Node *) nve; + } + + /* + * If relation has a default for this column, fetch that expression. + */ + if (att_tup->atthasdef) + { + if (rd_att->constr && rd_att->constr->num_defval > 0) + { + AttrDefault *defval = rd_att->constr->defval; + int ndef = rd_att->constr->num_defval; + + while (--ndef >= 0) + { + if (attrno == defval[ndef].adnum) + { + /* Found it, convert string representation to node tree. */ + expr = stringToNode(defval[ndef].adbin); + break; + } + } + } + if (expr == NULL) + elog(ERROR, "default expression not found for attribute %d of relation \"%s\"", + attrno, RelationGetRelationName(rel)); + } + + /* + * No per-column default, so look for a default for the type itself. But + * not for generated columns. + */ + if (expr == NULL && !att_tup->attgenerated) + expr = get_typdefault(atttype); + + if (expr == NULL) + return NULL; /* No default anywhere */ + + /* + * Make sure the value is coerced to the target column type; this will + * generally be true already, but there seem to be some corner cases + * involving domain defaults where it might not be true. This should match + * the parser's processing of non-defaulted expressions --- see + * transformAssignedExpr(). + */ + exprtype = exprType(expr); + + expr = coerce_to_target_type(NULL, /* no UNKNOWN params here */ + expr, exprtype, + atttype, atttypmod, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (expr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("column \"%s\" is of type %s" + " but default expression is of type %s", + NameStr(att_tup->attname), + format_type_be(atttype), + format_type_be(exprtype)), + errhint("You will need to rewrite or cast the expression."))); + + return expr; +} + + +/* Does VALUES RTE contain any SetToDefault items? */ +static bool +searchForDefault(RangeTblEntry *rte) +{ + ListCell *lc; + + foreach(lc, rte->values_lists) + { + List *sublist = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, sublist) + { + Node *col = (Node *) lfirst(lc2); + + if (IsA(col, SetToDefault)) + return true; + } + } + return false; +} + + +/* + * Search a VALUES RTE for columns that contain only SetToDefault items, + * returning a Bitmapset containing the attribute numbers of any such columns. + */ +static Bitmapset * +findDefaultOnlyColumns(RangeTblEntry *rte) +{ + Bitmapset *default_only_cols = NULL; + ListCell *lc; + + foreach(lc, rte->values_lists) + { + List *sublist = (List *) lfirst(lc); + ListCell *lc2; + int i; + + if (default_only_cols == NULL) + { + /* Populate the initial result bitmap from the first row */ + i = 0; + foreach(lc2, sublist) + { + Node *col = (Node *) lfirst(lc2); + + i++; + if (IsA(col, SetToDefault)) + default_only_cols = bms_add_member(default_only_cols, i); + } + } + else + { + /* Update the result bitmap from this next row */ + i = 0; + foreach(lc2, sublist) + { + Node *col = (Node *) lfirst(lc2); + + i++; + if (!IsA(col, SetToDefault)) + default_only_cols = bms_del_member(default_only_cols, i); + } + } + + /* + * If no column in the rows read so far contains only DEFAULT items, + * we are done. + */ + if (bms_is_empty(default_only_cols)) + break; + } + + return default_only_cols; +} + + +/* + * When processing INSERT ... VALUES with a VALUES RTE (ie, multiple VALUES + * lists), we have to replace any DEFAULT items in the VALUES lists with + * the appropriate default expressions. The other aspects of targetlist + * rewriting need be applied only to the query's targetlist proper. + * + * For an auto-updatable view, each DEFAULT item in the VALUES list is + * replaced with the default from the view, if it has one. Otherwise it is + * left untouched so that the underlying base relation's default can be + * applied instead (when we later recurse to here after rewriting the query + * to refer to the base relation instead of the view). + * + * For other types of relation, including rule- and trigger-updatable views, + * all DEFAULT items are replaced, and if the target relation doesn't have a + * default, the value is explicitly set to NULL. + * + * Additionally, if force_nulls is true, the target relation's defaults are + * ignored and all DEFAULT items in the VALUES list are explicitly set to + * NULL, regardless of the target relation's type. This is used for the + * product queries generated by DO ALSO rules attached to an auto-updatable + * view, for which we will have already called this function with force_nulls + * false. For these product queries, we must then force any remaining DEFAULT + * items to NULL to provide concrete values for the rule actions. + * Essentially, this is a mix of the 2 cases above --- the original query is + * an insert into an auto-updatable view, and the product queries are inserts + * into a rule-updatable view. + * + * Finally, if a DEFAULT item is found in a column mentioned in unused_cols, + * it is explicitly set to NULL. This happens for columns in the VALUES RTE + * whose corresponding targetlist entries have already been replaced with the + * relation's default expressions, so that any values in those columns of the + * VALUES RTE are no longer used. This can happen for identity and generated + * columns (if INSERT ... OVERRIDING USER VALUE is used, or all the values to + * be inserted are DEFAULT). In principle we could replace all entries in + * such a column with NULL, whether DEFAULT or not; but it doesn't seem worth + * the trouble. + * + * Note that we may have subscripted or field assignment targetlist entries, + * as well as more complex expressions from already-replaced DEFAULT items if + * we have recursed to here for an auto-updatable view. However, it ought to + * be impossible for such entries to have DEFAULTs assigned to them, except + * for unused columns, as described above --- we should only have to replace + * DEFAULT items for targetlist entries that contain simple Vars referencing + * the VALUES RTE, or which are no longer referred to by the targetlist. + * + * Returns true if all DEFAULT items were replaced, and false if some were + * left untouched. + */ +static bool +rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti, + Relation target_relation, bool force_nulls, + Bitmapset *unused_cols) +{ + List *newValues; + ListCell *lc; + bool isAutoUpdatableView; + bool allReplaced; + int numattrs; + int *attrnos; + + /* + * Rebuilding all the lists is a pretty expensive proposition in a big + * VALUES list, and it's a waste of time if there aren't any DEFAULT + * placeholders. So first scan to see if there are any. + * + * We skip this check if force_nulls is true, because we know that there + * are DEFAULT items present in that case. + */ + if (!force_nulls && !searchForDefault(rte)) + return true; /* nothing to do */ + + /* + * Scan the targetlist for entries referring to the VALUES RTE, and note + * the target attributes. As noted above, we should only need to do this + * for targetlist entries containing simple Vars --- nothing else in the + * VALUES RTE should contain DEFAULT items (except possibly for unused + * columns), and we complain if such a thing does occur. + */ + numattrs = list_length(linitial(rte->values_lists)); + attrnos = (int *) palloc0(numattrs * sizeof(int)); + + foreach(lc, parsetree->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc); + + if (IsA(tle->expr, Var)) + { + Var *var = (Var *) tle->expr; + + if (var->varno == rti) + { + int attrno = var->varattno; + + Assert(attrno >= 1 && attrno <= numattrs); + attrnos[attrno - 1] = tle->resno; + } + } + } + + /* + * Check if the target relation is an auto-updatable view, in which case + * unresolved defaults will be left untouched rather than being set to + * NULL. If force_nulls is true, we always set DEFAULT items to NULL, so + * skip this check in that case --- it isn't an auto-updatable view. + */ + isAutoUpdatableView = false; + if (!force_nulls && + target_relation->rd_rel->relkind == RELKIND_VIEW && + !view_has_instead_trigger(target_relation, CMD_INSERT)) + { + List *locks; + bool hasUpdate; + bool found; + ListCell *l; + + /* Look for an unconditional DO INSTEAD rule */ + locks = matchLocks(CMD_INSERT, target_relation->rd_rules, + parsetree->resultRelation, parsetree, &hasUpdate); + + found = false; + foreach(l, locks) + { + RewriteRule *rule_lock = (RewriteRule *) lfirst(l); + + if (rule_lock->isInstead && + rule_lock->qual == NULL) + { + found = true; + break; + } + } + + /* + * If we didn't find an unconditional DO INSTEAD rule, assume that the + * view is auto-updatable. If it isn't, rewriteTargetView() will + * throw an error. + */ + if (!found) + isAutoUpdatableView = true; + } + + newValues = NIL; + allReplaced = true; + foreach(lc, rte->values_lists) + { + List *sublist = (List *) lfirst(lc); + List *newList = NIL; + ListCell *lc2; + int i; + + Assert(list_length(sublist) == numattrs); + + i = 0; + foreach(lc2, sublist) + { + Node *col = (Node *) lfirst(lc2); + int attrno = attrnos[i++]; + + if (IsA(col, SetToDefault)) + { + Form_pg_attribute att_tup; + Node *new_expr; + + /* + * If this column isn't used, just replace the DEFAULT with + * NULL (attrno will be 0 in this case because the targetlist + * entry will have been replaced by the default expression). + */ + if (bms_is_member(i, unused_cols)) + { + SetToDefault *def = (SetToDefault *) col; + + newList = lappend(newList, + makeNullConst(def->typeId, + def->typeMod, + def->collation)); + continue; + } + + if (attrno == 0) + elog(ERROR, "cannot set value in column %d to DEFAULT", i); + att_tup = TupleDescAttr(target_relation->rd_att, attrno - 1); + + if (!force_nulls && !att_tup->attisdropped) + new_expr = build_column_default(target_relation, attrno); + else + new_expr = NULL; /* force a NULL if dropped */ + + /* + * If there is no default (ie, default is effectively NULL), + * we've got to explicitly set the column to NULL, unless the + * target relation is an auto-updatable view. + */ + if (!new_expr) + { + if (isAutoUpdatableView) + { + /* Leave the value untouched */ + newList = lappend(newList, col); + allReplaced = false; + continue; + } + + new_expr = (Node *) makeConst(att_tup->atttypid, + -1, + att_tup->attcollation, + att_tup->attlen, + (Datum) 0, + true, /* isnull */ + att_tup->attbyval); + /* this is to catch a NOT NULL domain constraint */ + new_expr = coerce_to_domain(new_expr, + InvalidOid, -1, + att_tup->atttypid, + COERCION_IMPLICIT, + COERCE_IMPLICIT_CAST, + -1, + false); + } + newList = lappend(newList, new_expr); + } + else + newList = lappend(newList, col); + } + newValues = lappend(newValues, newList); + } + rte->values_lists = newValues; + + pfree(attrnos); + + return allReplaced; +} + + +/* + * Record in target_rte->extraUpdatedCols the indexes of any generated columns + * that depend on any columns mentioned in target_rte->updatedCols. + */ +void +fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation) +{ + TupleDesc tupdesc = RelationGetDescr(target_relation); + TupleConstr *constr = tupdesc->constr; + + target_rte->extraUpdatedCols = NULL; + + if (constr && constr->has_generated_stored) + { + for (int i = 0; i < constr->num_defval; i++) + { + AttrDefault *defval = &constr->defval[i]; + Node *expr; + Bitmapset *attrs_used = NULL; + + /* skip if not generated column */ + if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated) + continue; + + /* identify columns this generated column depends on */ + expr = stringToNode(defval->adbin); + pull_varattnos(expr, 1, &attrs_used); + + if (bms_overlap(target_rte->updatedCols, attrs_used)) + target_rte->extraUpdatedCols = + bms_add_member(target_rte->extraUpdatedCols, + defval->adnum - FirstLowInvalidHeapAttributeNumber); + } + } +} + + +/* + * matchLocks - + * match the list of locks and returns the matching rules + */ +static List * +matchLocks(CmdType event, + RuleLock *rulelocks, + int varno, + Query *parsetree, + bool *hasUpdate) +{ + List *matching_locks = NIL; + int nlocks; + int i; + + if (rulelocks == NULL) + return NIL; + + if (parsetree->commandType != CMD_SELECT) + { + if (parsetree->resultRelation != varno) + return NIL; + } + + nlocks = rulelocks->numLocks; + + for (i = 0; i < nlocks; i++) + { + RewriteRule *oneLock = rulelocks->rules[i]; + + if (oneLock->event == CMD_UPDATE) + *hasUpdate = true; + + /* + * Suppress ON INSERT/UPDATE/DELETE rules that are disabled or + * configured to not fire during the current sessions replication + * role. ON SELECT rules will always be applied in order to keep views + * working even in LOCAL or REPLICA role. + */ + if (oneLock->event != CMD_SELECT) + { + if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA) + { + if (oneLock->enabled == RULE_FIRES_ON_ORIGIN || + oneLock->enabled == RULE_DISABLED) + continue; + } + else /* ORIGIN or LOCAL ROLE */ + { + if (oneLock->enabled == RULE_FIRES_ON_REPLICA || + oneLock->enabled == RULE_DISABLED) + continue; + } + } + + if (oneLock->event == event) + { + if (parsetree->commandType != CMD_SELECT || + rangeTableEntry_used((Node *) parsetree, varno, 0)) + matching_locks = lappend(matching_locks, oneLock); + } + } + + return matching_locks; +} + + +/* + * ApplyRetrieveRule - expand an ON SELECT rule + */ +static Query * +ApplyRetrieveRule(Query *parsetree, + RewriteRule *rule, + int rt_index, + Relation relation, + List *activeRIRs) +{ + Query *rule_action; + RangeTblEntry *rte, + *subrte; + RowMarkClause *rc; + + if (list_length(rule->actions) != 1) + elog(ERROR, "expected just one rule action"); + if (rule->qual != NULL) + elog(ERROR, "cannot handle qualified ON SELECT rule"); + + if (rt_index == parsetree->resultRelation) + { + /* + * We have a view as the result relation of the query, and it wasn't + * rewritten by any rule. This case is supported if there is an + * INSTEAD OF trigger that will trap attempts to insert/update/delete + * view rows. The executor will check that; for the moment just plow + * ahead. We have two cases: + * + * For INSERT, we needn't do anything. The unmodified RTE will serve + * fine as the result relation. + * + * For UPDATE/DELETE, we need to expand the view so as to have source + * data for the operation. But we also need an unmodified RTE to + * serve as the target. So, copy the RTE and add the copy to the + * rangetable. Note that the copy does not get added to the jointree. + * Also note that there's a hack in fireRIRrules to avoid calling this + * function again when it arrives at the copied RTE. + */ + if (parsetree->commandType == CMD_INSERT) + return parsetree; + else if (parsetree->commandType == CMD_UPDATE || + parsetree->commandType == CMD_DELETE) + { + RangeTblEntry *newrte; + Var *var; + TargetEntry *tle; + + rte = rt_fetch(rt_index, parsetree->rtable); + newrte = copyObject(rte); + parsetree->rtable = lappend(parsetree->rtable, newrte); + parsetree->resultRelation = list_length(parsetree->rtable); + + /* + * There's no need to do permissions checks twice, so wipe out the + * permissions info for the original RTE (we prefer to keep the + * bits set on the result RTE). + */ + rte->requiredPerms = 0; + rte->checkAsUser = InvalidOid; + rte->selectedCols = NULL; + rte->insertedCols = NULL; + rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; + + /* + * For the most part, Vars referencing the view should remain as + * they are, meaning that they implicitly represent OLD values. + * But in the RETURNING list if any, we want such Vars to + * represent NEW values, so change them to reference the new RTE. + * + * Since ChangeVarNodes scribbles on the tree in-place, copy the + * RETURNING list first for safety. + */ + parsetree->returningList = copyObject(parsetree->returningList); + ChangeVarNodes((Node *) parsetree->returningList, rt_index, + parsetree->resultRelation, 0); + + /* + * To allow the executor to compute the original view row to pass + * to the INSTEAD OF trigger, we add a resjunk whole-row Var + * referencing the original RTE. This will later get expanded + * into a RowExpr computing all the OLD values of the view row. + */ + var = makeWholeRowVar(rte, rt_index, 0, false); + tle = makeTargetEntry((Expr *) var, + list_length(parsetree->targetList) + 1, + pstrdup("wholerow"), + true); + + parsetree->targetList = lappend(parsetree->targetList, tle); + + /* Now, continue with expanding the original view RTE */ + } + else + elog(ERROR, "unrecognized commandType: %d", + (int) parsetree->commandType); + } + + /* + * Check if there's a FOR [KEY] UPDATE/SHARE clause applying to this view. + * + * Note: we needn't explicitly consider any such clauses appearing in + * ancestor query levels; their effects have already been pushed down to + * here by markQueryForLocking, and will be reflected in "rc". + */ + rc = get_parse_rowmark(parsetree, rt_index); + + /* + * Make a modifiable copy of the view query, and acquire needed locks on + * the relations it mentions. Force at least RowShareLock for all such + * rels if there's a FOR [KEY] UPDATE/SHARE clause affecting this view. + */ + rule_action = copyObject(linitial(rule->actions)); + + AcquireRewriteLocks(rule_action, true, (rc != NULL)); + + /* + * If FOR [KEY] UPDATE/SHARE of view, mark all the contained tables as + * implicit FOR [KEY] UPDATE/SHARE, the same as the parser would have done + * if the view's subquery had been written out explicitly. + */ + if (rc != NULL) + markQueryForLocking(rule_action, (Node *) rule_action->jointree, + rc->strength, rc->waitPolicy, true); + + /* + * Recursively expand any view references inside the view. + * + * Note: this must happen after markQueryForLocking. That way, any UPDATE + * permission bits needed for sub-views are initially applied to their + * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their + * OLD rangetable entries by the action below (in a recursive call of this + * routine). + */ + rule_action = fireRIRrules(rule_action, activeRIRs); + + /* + * Now, plug the view query in as a subselect, converting the relation's + * original RTE to a subquery RTE. + */ + rte = rt_fetch(rt_index, parsetree->rtable); + + rte->rtekind = RTE_SUBQUERY; + rte->subquery = rule_action; + rte->security_barrier = RelationIsSecurityView(relation); + /* Clear fields that should not be set in a subquery RTE */ + rte->relid = InvalidOid; + rte->relkind = 0; + rte->rellockmode = 0; + rte->tablesample = NULL; + rte->inh = false; /* must not be set for a subquery */ + + /* + * We move the view's permission check data down to its rangetable. The + * checks will actually be done against the OLD entry therein. + */ + subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable); + Assert(subrte->relid == relation->rd_id); + subrte->requiredPerms = rte->requiredPerms; + subrte->checkAsUser = rte->checkAsUser; + subrte->selectedCols = rte->selectedCols; + subrte->insertedCols = rte->insertedCols; + subrte->updatedCols = rte->updatedCols; + subrte->extraUpdatedCols = rte->extraUpdatedCols; + + rte->requiredPerms = 0; /* no permission check on subquery itself */ + rte->checkAsUser = InvalidOid; + rte->selectedCols = NULL; + rte->insertedCols = NULL; + rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; + + return parsetree; +} + +/* + * Recursively mark all relations used by a view as FOR [KEY] UPDATE/SHARE. + * + * This may generate an invalid query, eg if some sub-query uses an + * aggregate. We leave it to the planner to detect that. + * + * NB: this must agree with the parser's transformLockingClause() routine. + * However, unlike the parser we have to be careful not to mark a view's + * OLD and NEW rels for updating. The best way to handle that seems to be + * to scan the jointree to determine which rels are used. + */ +static void +markQueryForLocking(Query *qry, Node *jtnode, + LockClauseStrength strength, LockWaitPolicy waitPolicy, + bool pushedDown) +{ + if (jtnode == NULL) + return; + if (IsA(jtnode, RangeTblRef)) + { + int rti = ((RangeTblRef *) jtnode)->rtindex; + RangeTblEntry *rte = rt_fetch(rti, qry->rtable); + + if (rte->rtekind == RTE_RELATION) + { + applyLockingClause(qry, rti, strength, waitPolicy, pushedDown); + rte->requiredPerms |= ACL_SELECT_FOR_UPDATE; + } + else if (rte->rtekind == RTE_SUBQUERY) + { + applyLockingClause(qry, rti, strength, waitPolicy, pushedDown); + /* FOR UPDATE/SHARE of subquery is propagated to subquery's rels */ + markQueryForLocking(rte->subquery, (Node *) rte->subquery->jointree, + strength, waitPolicy, true); + } + /* other RTE types are unaffected by FOR UPDATE */ + } + else if (IsA(jtnode, FromExpr)) + { + FromExpr *f = (FromExpr *) jtnode; + ListCell *l; + + foreach(l, f->fromlist) + markQueryForLocking(qry, lfirst(l), strength, waitPolicy, pushedDown); + } + else if (IsA(jtnode, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) jtnode; + + markQueryForLocking(qry, j->larg, strength, waitPolicy, pushedDown); + markQueryForLocking(qry, j->rarg, strength, waitPolicy, pushedDown); + } + else + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(jtnode)); +} + + +/* + * fireRIRonSubLink - + * Apply fireRIRrules() to each SubLink (subselect in expression) found + * in the given tree. + * + * NOTE: although this has the form of a walker, we cheat and modify the + * SubLink nodes in-place. It is caller's responsibility to ensure that + * no unwanted side-effects occur! + * + * This is unlike most of the other routines that recurse into subselects, + * because we must take control at the SubLink node in order to replace + * the SubLink's subselect link with the possibly-rewritten subquery. + */ +static bool +fireRIRonSubLink(Node *node, List *activeRIRs) +{ + if (node == NULL) + return false; + if (IsA(node, SubLink)) + { + SubLink *sub = (SubLink *) node; + + /* Do what we came for */ + sub->subselect = (Node *) fireRIRrules((Query *) sub->subselect, + activeRIRs); + /* Fall through to process lefthand args of SubLink */ + } + + /* + * Do NOT recurse into Query nodes, because fireRIRrules already processed + * subselects of subselects for us. + */ + return expression_tree_walker(node, fireRIRonSubLink, + (void *) activeRIRs); +} + + +/* + * fireRIRrules - + * Apply all RIR rules on each rangetable entry in the given query + * + * activeRIRs is a list of the OIDs of views we're already processing RIR + * rules for, used to detect/reject recursion. + */ +static Query * +fireRIRrules(Query *parsetree, List *activeRIRs) +{ + int origResultRelation = parsetree->resultRelation; + int rt_index; + ListCell *lc; + + /* + * Expand SEARCH and CYCLE clauses in CTEs. + * + * This is just a convenient place to do this, since we are already + * looking at each Query. + */ + foreach(lc, parsetree->cteList) + { + CommonTableExpr *cte = lfirst_node(CommonTableExpr, lc); + + if (cte->search_clause || cte->cycle_clause) + { + cte = rewriteSearchAndCycle(cte); + lfirst(lc) = cte; + } + } + + /* + * don't try to convert this into a foreach loop, because rtable list can + * get changed each time through... + */ + rt_index = 0; + while (rt_index < list_length(parsetree->rtable)) + { + RangeTblEntry *rte; + Relation rel; + List *locks; + RuleLock *rules; + RewriteRule *rule; + int i; + + ++rt_index; + + rte = rt_fetch(rt_index, parsetree->rtable); + + /* + * A subquery RTE can't have associated rules, so there's nothing to + * do to this level of the query, but we must recurse into the + * subquery to expand any rule references in it. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + rte->subquery = fireRIRrules(rte->subquery, activeRIRs); + continue; + } + + /* + * Joins and other non-relation RTEs can be ignored completely. + */ + if (rte->rtekind != RTE_RELATION) + continue; + + /* + * Always ignore RIR rules for materialized views referenced in + * queries. (This does not prevent refreshing MVs, since they aren't + * referenced in their own query definitions.) + * + * Note: in the future we might want to allow MVs to be conditionally + * expanded as if they were regular views, if they are not scannable. + * In that case this test would need to be postponed till after we've + * opened the rel, so that we could check its state. + */ + if (rte->relkind == RELKIND_MATVIEW) + continue; + + /* + * In INSERT ... ON CONFLICT, ignore the EXCLUDED pseudo-relation; + * even if it points to a view, we needn't expand it, and should not + * because we want the RTE to remain of RTE_RELATION type. Otherwise, + * it would get changed to RTE_SUBQUERY type, which is an + * untested/unsupported situation. + */ + if (parsetree->onConflict && + rt_index == parsetree->onConflict->exclRelIndex) + continue; + + /* + * If the table is not referenced in the query, then we ignore it. + * This prevents infinite expansion loop due to new rtable entries + * inserted by expansion of a rule. A table is referenced if it is + * part of the join set (a source table), or is referenced by any Var + * nodes, or is the result table. + */ + if (rt_index != parsetree->resultRelation && + !rangeTableEntry_used((Node *) parsetree, rt_index, 0)) + continue; + + /* + * Also, if this is a new result relation introduced by + * ApplyRetrieveRule, we don't want to do anything more with it. + */ + if (rt_index == parsetree->resultRelation && + rt_index != origResultRelation) + continue; + + /* + * We can use NoLock here since either the parser or + * AcquireRewriteLocks should have locked the rel already. + */ + rel = table_open(rte->relid, NoLock); + + /* + * Collect the RIR rules that we must apply + */ + rules = rel->rd_rules; + if (rules != NULL) + { + locks = NIL; + for (i = 0; i < rules->numLocks; i++) + { + rule = rules->rules[i]; + if (rule->event != CMD_SELECT) + continue; + + locks = lappend(locks, rule); + } + + /* + * If we found any, apply them --- but first check for recursion! + */ + if (locks != NIL) + { + ListCell *l; + + if (list_member_oid(activeRIRs, RelationGetRelid(rel))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("infinite recursion detected in rules for relation \"%s\"", + RelationGetRelationName(rel)))); + activeRIRs = lappend_oid(activeRIRs, RelationGetRelid(rel)); + + foreach(l, locks) + { + rule = lfirst(l); + + parsetree = ApplyRetrieveRule(parsetree, + rule, + rt_index, + rel, + activeRIRs); + } + + activeRIRs = list_delete_last(activeRIRs); + } + } + + table_close(rel, NoLock); + } + + /* Recurse into subqueries in WITH */ + foreach(lc, parsetree->cteList) + { + CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); + + cte->ctequery = (Node *) + fireRIRrules((Query *) cte->ctequery, activeRIRs); + } + + /* + * Recurse into sublink subqueries, too. But we already did the ones in + * the rtable and cteList. + */ + if (parsetree->hasSubLinks) + query_tree_walker(parsetree, fireRIRonSubLink, (void *) activeRIRs, + QTW_IGNORE_RC_SUBQUERIES); + + /* + * Apply any row-level security policies. We do this last because it + * requires special recursion detection if the new quals have sublink + * subqueries, and if we did it in the loop above query_tree_walker would + * then recurse into those quals a second time. + */ + rt_index = 0; + foreach(lc, parsetree->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); + Relation rel; + List *securityQuals; + List *withCheckOptions; + bool hasRowSecurity; + bool hasSubLinks; + + ++rt_index; + + /* Only normal relations can have RLS policies */ + if (rte->rtekind != RTE_RELATION || + (rte->relkind != RELKIND_RELATION && + rte->relkind != RELKIND_PARTITIONED_TABLE)) + continue; + + rel = table_open(rte->relid, NoLock); + + /* + * Fetch any new security quals that must be applied to this RTE. + */ + get_row_security_policies(parsetree, rte, rt_index, + &securityQuals, &withCheckOptions, + &hasRowSecurity, &hasSubLinks); + + if (securityQuals != NIL || withCheckOptions != NIL) + { + if (hasSubLinks) + { + acquireLocksOnSubLinks_context context; + + /* + * Recursively process the new quals, checking for infinite + * recursion. + */ + if (list_member_oid(activeRIRs, RelationGetRelid(rel))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("infinite recursion detected in policy for relation \"%s\"", + RelationGetRelationName(rel)))); + + activeRIRs = lappend_oid(activeRIRs, RelationGetRelid(rel)); + + /* + * get_row_security_policies just passed back securityQuals + * and/or withCheckOptions, and there were SubLinks, make sure + * we lock any relations which are referenced. + * + * These locks would normally be acquired by the parser, but + * securityQuals and withCheckOptions are added post-parsing. + */ + context.for_execute = true; + (void) acquireLocksOnSubLinks((Node *) securityQuals, &context); + (void) acquireLocksOnSubLinks((Node *) withCheckOptions, + &context); + + /* + * Now that we have the locks on anything added by + * get_row_security_policies, fire any RIR rules for them. + */ + expression_tree_walker((Node *) securityQuals, + fireRIRonSubLink, (void *) activeRIRs); + + expression_tree_walker((Node *) withCheckOptions, + fireRIRonSubLink, (void *) activeRIRs); + + activeRIRs = list_delete_last(activeRIRs); + } + + /* + * Add the new security barrier quals to the start of the RTE's + * list so that they get applied before any existing barrier quals + * (which would have come from a security-barrier view, and should + * get lower priority than RLS conditions on the table itself). + */ + rte->securityQuals = list_concat(securityQuals, + rte->securityQuals); + + parsetree->withCheckOptions = list_concat(withCheckOptions, + parsetree->withCheckOptions); + } + + /* + * Make sure the query is marked correctly if row-level security + * applies, or if the new quals had sublinks. + */ + if (hasRowSecurity) + parsetree->hasRowSecurity = true; + if (hasSubLinks) + parsetree->hasSubLinks = true; + + table_close(rel, NoLock); + } + + return parsetree; +} + + +/* + * Modify the given query by adding 'AND rule_qual IS NOT TRUE' to its + * qualification. This is used to generate suitable "else clauses" for + * conditional INSTEAD rules. (Unfortunately we must use "x IS NOT TRUE", + * not just "NOT x" which the planner is much smarter about, else we will + * do the wrong thing when the qual evaluates to NULL.) + * + * The rule_qual may contain references to OLD or NEW. OLD references are + * replaced by references to the specified rt_index (the relation that the + * rule applies to). NEW references are only possible for INSERT and UPDATE + * queries on the relation itself, and so they should be replaced by copies + * of the related entries in the query's own targetlist. + */ +static Query * +CopyAndAddInvertedQual(Query *parsetree, + Node *rule_qual, + int rt_index, + CmdType event) +{ + /* Don't scribble on the passed qual (it's in the relcache!) */ + Node *new_qual = copyObject(rule_qual); + acquireLocksOnSubLinks_context context; + + context.for_execute = true; + + /* + * In case there are subqueries in the qual, acquire necessary locks and + * fix any deleted JOIN RTE entries. (This is somewhat redundant with + * rewriteRuleAction, but not entirely ... consider restructuring so that + * we only need to process the qual this way once.) + */ + (void) acquireLocksOnSubLinks(new_qual, &context); + + /* Fix references to OLD */ + ChangeVarNodes(new_qual, PRS2_OLD_VARNO, rt_index, 0); + /* Fix references to NEW */ + if (event == CMD_INSERT || event == CMD_UPDATE) + new_qual = ReplaceVarsFromTargetList(new_qual, + PRS2_NEW_VARNO, + 0, + rt_fetch(rt_index, + parsetree->rtable), + parsetree->targetList, + (event == CMD_UPDATE) ? + REPLACEVARS_CHANGE_VARNO : + REPLACEVARS_SUBSTITUTE_NULL, + rt_index, + &parsetree->hasSubLinks); + /* And attach the fixed qual */ + AddInvertedQual(parsetree, new_qual); + + return parsetree; +} + + +/* + * fireRules - + * Iterate through rule locks applying rules. + * + * Input arguments: + * parsetree - original query + * rt_index - RT index of result relation in original query + * event - type of rule event + * locks - list of rules to fire + * Output arguments: + * *instead_flag - set true if any unqualified INSTEAD rule is found + * (must be initialized to false) + * *returning_flag - set true if we rewrite RETURNING clause in any rule + * (must be initialized to false) + * *qual_product - filled with modified original query if any qualified + * INSTEAD rule is found (must be initialized to NULL) + * Return value: + * list of rule actions adjusted for use with this query + * + * Qualified INSTEAD rules generate their action with the qualification + * condition added. They also generate a modified version of the original + * query with the negated qualification added, so that it will run only for + * rows that the qualified action doesn't act on. (If there are multiple + * qualified INSTEAD rules, we AND all the negated quals onto a single + * modified original query.) We won't execute the original, unmodified + * query if we find either qualified or unqualified INSTEAD rules. If + * we find both, the modified original query is discarded too. + */ +static List * +fireRules(Query *parsetree, + int rt_index, + CmdType event, + List *locks, + bool *instead_flag, + bool *returning_flag, + Query **qual_product) +{ + List *results = NIL; + ListCell *l; + + foreach(l, locks) + { + RewriteRule *rule_lock = (RewriteRule *) lfirst(l); + Node *event_qual = rule_lock->qual; + List *actions = rule_lock->actions; + QuerySource qsrc; + ListCell *r; + + /* Determine correct QuerySource value for actions */ + if (rule_lock->isInstead) + { + if (event_qual != NULL) + qsrc = QSRC_QUAL_INSTEAD_RULE; + else + { + qsrc = QSRC_INSTEAD_RULE; + *instead_flag = true; /* report unqualified INSTEAD */ + } + } + else + qsrc = QSRC_NON_INSTEAD_RULE; + + if (qsrc == QSRC_QUAL_INSTEAD_RULE) + { + /* + * If there are INSTEAD rules with qualifications, the original + * query is still performed. But all the negated rule + * qualifications of the INSTEAD rules are added so it does its + * actions only in cases where the rule quals of all INSTEAD rules + * are false. Think of it as the default action in a case. We save + * this in *qual_product so RewriteQuery() can add it to the query + * list after we mangled it up enough. + * + * If we have already found an unqualified INSTEAD rule, then + * *qual_product won't be used, so don't bother building it. + */ + if (!*instead_flag) + { + if (*qual_product == NULL) + *qual_product = copyObject(parsetree); + *qual_product = CopyAndAddInvertedQual(*qual_product, + event_qual, + rt_index, + event); + } + } + + /* Now process the rule's actions and add them to the result list */ + foreach(r, actions) + { + Query *rule_action = lfirst(r); + + if (rule_action->commandType == CMD_NOTHING) + continue; + + rule_action = rewriteRuleAction(parsetree, rule_action, + event_qual, rt_index, event, + returning_flag); + + rule_action->querySource = qsrc; + rule_action->canSetTag = false; /* might change later */ + + results = lappend(results, rule_action); + } + } + + return results; +} + + +/* + * get_view_query - get the Query from a view's _RETURN rule. + * + * Caller should have verified that the relation is a view, and therefore + * we should find an ON SELECT action. + * + * Note that the pointer returned is into the relcache and therefore must + * be treated as read-only to the caller and not modified or scribbled on. + */ +Query * +get_view_query(Relation view) +{ + int i; + + Assert(view->rd_rel->relkind == RELKIND_VIEW); + + for (i = 0; i < view->rd_rules->numLocks; i++) + { + RewriteRule *rule = view->rd_rules->rules[i]; + + if (rule->event == CMD_SELECT) + { + /* A _RETURN rule should have only one action */ + if (list_length(rule->actions) != 1) + elog(ERROR, "invalid _RETURN rule action specification"); + + return (Query *) linitial(rule->actions); + } + } + + elog(ERROR, "failed to find _RETURN rule for view"); + return NULL; /* keep compiler quiet */ +} + + +/* + * view_has_instead_trigger - does view have an INSTEAD OF trigger for event? + * + * If it does, we don't want to treat it as auto-updatable. This test can't + * be folded into view_query_is_auto_updatable because it's not an error + * condition. + */ +static bool +view_has_instead_trigger(Relation view, CmdType event) +{ + TriggerDesc *trigDesc = view->trigdesc; + + switch (event) + { + case CMD_INSERT: + if (trigDesc && trigDesc->trig_insert_instead_row) + return true; + break; + case CMD_UPDATE: + if (trigDesc && trigDesc->trig_update_instead_row) + return true; + break; + case CMD_DELETE: + if (trigDesc && trigDesc->trig_delete_instead_row) + return true; + break; + default: + elog(ERROR, "unrecognized CmdType: %d", (int) event); + break; + } + return false; +} + + +/* + * view_col_is_auto_updatable - test whether the specified column of a view + * is auto-updatable. Returns NULL (if the column can be updated) or a message + * string giving the reason that it cannot be. + * + * The returned string has not been translated; if it is shown as an error + * message, the caller should apply _() to translate it. + * + * Note that the checks performed here are local to this view. We do not check + * whether the referenced column of the underlying base relation is updatable. + */ +static const char * +view_col_is_auto_updatable(RangeTblRef *rtr, TargetEntry *tle) +{ + Var *var = (Var *) tle->expr; + + /* + * For now, the only updatable columns we support are those that are Vars + * referring to user columns of the underlying base relation. + * + * The view targetlist may contain resjunk columns (e.g., a view defined + * like "SELECT * FROM t ORDER BY a+b" is auto-updatable) but such columns + * are not auto-updatable, and in fact should never appear in the outer + * query's targetlist. + */ + if (tle->resjunk) + return gettext_noop("Junk view columns are not updatable."); + + if (!IsA(var, Var) || + var->varno != rtr->rtindex || + var->varlevelsup != 0) + return gettext_noop("View columns that are not columns of their base relation are not updatable."); + + if (var->varattno < 0) + return gettext_noop("View columns that refer to system columns are not updatable."); + + if (var->varattno == 0) + return gettext_noop("View columns that return whole-row references are not updatable."); + + return NULL; /* the view column is updatable */ +} + + +/* + * view_query_is_auto_updatable - test whether the specified view definition + * represents an auto-updatable view. Returns NULL (if the view can be updated) + * or a message string giving the reason that it cannot be. + + * The returned string has not been translated; if it is shown as an error + * message, the caller should apply _() to translate it. + * + * If check_cols is true, the view is required to have at least one updatable + * column (necessary for INSERT/UPDATE). Otherwise the view's columns are not + * checked for updatability. See also view_cols_are_auto_updatable. + * + * Note that the checks performed here are only based on the view definition. + * We do not check whether any base relations referred to by the view are + * updatable. + */ +const char * +view_query_is_auto_updatable(Query *viewquery, bool check_cols) +{ + RangeTblRef *rtr; + RangeTblEntry *base_rte; + + /*---------- + * Check if the view is simply updatable. According to SQL-92 this means: + * - No DISTINCT clause. + * - Each TLE is a column reference, and each column appears at most once. + * - FROM contains exactly one base relation. + * - No GROUP BY or HAVING clauses. + * - No set operations (UNION, INTERSECT or EXCEPT). + * - No sub-queries in the WHERE clause that reference the target table. + * + * We ignore that last restriction since it would be complex to enforce + * and there isn't any actual benefit to disallowing sub-queries. (The + * semantic issues that the standard is presumably concerned about don't + * arise in Postgres, since any such sub-query will not see any updates + * executed by the outer query anyway, thanks to MVCC snapshotting.) + * + * We also relax the second restriction by supporting part of SQL:1999 + * feature T111, which allows for a mix of updatable and non-updatable + * columns, provided that an INSERT or UPDATE doesn't attempt to assign to + * a non-updatable column. + * + * In addition we impose these constraints, involving features that are + * not part of SQL-92: + * - No CTEs (WITH clauses). + * - No OFFSET or LIMIT clauses (this matches a SQL:2008 restriction). + * - No system columns (including whole-row references) in the tlist. + * - No window functions in the tlist. + * - No set-returning functions in the tlist. + * + * Note that we do these checks without recursively expanding the view. + * If the base relation is a view, we'll recursively deal with it later. + *---------- + */ + if (viewquery->distinctClause != NIL) + return gettext_noop("Views containing DISTINCT are not automatically updatable."); + + if (viewquery->groupClause != NIL || viewquery->groupingSets) + return gettext_noop("Views containing GROUP BY are not automatically updatable."); + + if (viewquery->havingQual != NULL) + return gettext_noop("Views containing HAVING are not automatically updatable."); + + if (viewquery->setOperations != NULL) + return gettext_noop("Views containing UNION, INTERSECT, or EXCEPT are not automatically updatable."); + + if (viewquery->cteList != NIL) + return gettext_noop("Views containing WITH are not automatically updatable."); + + if (viewquery->limitOffset != NULL || viewquery->limitCount != NULL) + return gettext_noop("Views containing LIMIT or OFFSET are not automatically updatable."); + + /* + * We must not allow window functions or set returning functions in the + * targetlist. Otherwise we might end up inserting them into the quals of + * the main query. We must also check for aggregates in the targetlist in + * case they appear without a GROUP BY. + * + * These restrictions ensure that each row of the view corresponds to a + * unique row in the underlying base relation. + */ + if (viewquery->hasAggs) + return gettext_noop("Views that return aggregate functions are not automatically updatable."); + + if (viewquery->hasWindowFuncs) + return gettext_noop("Views that return window functions are not automatically updatable."); + + if (viewquery->hasTargetSRFs) + return gettext_noop("Views that return set-returning functions are not automatically updatable."); + + /* + * The view query should select from a single base relation, which must be + * a table or another view. + */ + if (list_length(viewquery->jointree->fromlist) != 1) + return gettext_noop("Views that do not select from a single table or view are not automatically updatable."); + + rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist); + if (!IsA(rtr, RangeTblRef)) + return gettext_noop("Views that do not select from a single table or view are not automatically updatable."); + + base_rte = rt_fetch(rtr->rtindex, viewquery->rtable); + if (base_rte->rtekind != RTE_RELATION || + (base_rte->relkind != RELKIND_RELATION && + base_rte->relkind != RELKIND_FOREIGN_TABLE && + base_rte->relkind != RELKIND_VIEW && + base_rte->relkind != RELKIND_PARTITIONED_TABLE)) + return gettext_noop("Views that do not select from a single table or view are not automatically updatable."); + + if (base_rte->tablesample) + return gettext_noop("Views containing TABLESAMPLE are not automatically updatable."); + + /* + * Check that the view has at least one updatable column. This is required + * for INSERT/UPDATE but not for DELETE. + */ + if (check_cols) + { + ListCell *cell; + bool found; + + found = false; + foreach(cell, viewquery->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(cell); + + if (view_col_is_auto_updatable(rtr, tle) == NULL) + { + found = true; + break; + } + } + + if (!found) + return gettext_noop("Views that have no updatable columns are not automatically updatable."); + } + + return NULL; /* the view is updatable */ +} + + +/* + * view_cols_are_auto_updatable - test whether all of the required columns of + * an auto-updatable view are actually updatable. Returns NULL (if all the + * required columns can be updated) or a message string giving the reason that + * they cannot be. + * + * The returned string has not been translated; if it is shown as an error + * message, the caller should apply _() to translate it. + * + * This should be used for INSERT/UPDATE to ensure that we don't attempt to + * assign to any non-updatable columns. + * + * Additionally it may be used to retrieve the set of updatable columns in the + * view, or if one or more of the required columns is not updatable, the name + * of the first offending non-updatable column. + * + * The caller must have already verified that this is an auto-updatable view + * using view_query_is_auto_updatable. + * + * Note that the checks performed here are only based on the view definition. + * We do not check whether the referenced columns of the base relation are + * updatable. + */ +static const char * +view_cols_are_auto_updatable(Query *viewquery, + Bitmapset *required_cols, + Bitmapset **updatable_cols, + char **non_updatable_col) +{ + RangeTblRef *rtr; + AttrNumber col; + ListCell *cell; + + /* + * The caller should have verified that this view is auto-updatable and so + * there should be a single base relation. + */ + Assert(list_length(viewquery->jointree->fromlist) == 1); + rtr = linitial_node(RangeTblRef, viewquery->jointree->fromlist); + + /* Initialize the optional return values */ + if (updatable_cols != NULL) + *updatable_cols = NULL; + if (non_updatable_col != NULL) + *non_updatable_col = NULL; + + /* Test each view column for updatability */ + col = -FirstLowInvalidHeapAttributeNumber; + foreach(cell, viewquery->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(cell); + const char *col_update_detail; + + col++; + col_update_detail = view_col_is_auto_updatable(rtr, tle); + + if (col_update_detail == NULL) + { + /* The column is updatable */ + if (updatable_cols != NULL) + *updatable_cols = bms_add_member(*updatable_cols, col); + } + else if (bms_is_member(col, required_cols)) + { + /* The required column is not updatable */ + if (non_updatable_col != NULL) + *non_updatable_col = tle->resname; + return col_update_detail; + } + } + + return NULL; /* all the required view columns are updatable */ +} + + +/* + * relation_is_updatable - determine which update events the specified + * relation supports. + * + * Note that views may contain a mix of updatable and non-updatable columns. + * For a view to support INSERT/UPDATE it must have at least one updatable + * column, but there is no such restriction for DELETE. If include_cols is + * non-NULL, then only the specified columns are considered when testing for + * updatability. + * + * Unlike the preceding functions, this does recurse to look at a view's + * base relations, so it needs to detect recursion. To do that, we pass + * a list of currently-considered outer relations. External callers need + * only pass NIL. + * + * This is used for the information_schema views, which have separate concepts + * of "updatable" and "trigger updatable". A relation is "updatable" if it + * can be updated without the need for triggers (either because it has a + * suitable RULE, or because it is simple enough to be automatically updated). + * A relation is "trigger updatable" if it has a suitable INSTEAD OF trigger. + * The SQL standard regards this as not necessarily updatable, presumably + * because there is no way of knowing what the trigger will actually do. + * The information_schema views therefore call this function with + * include_triggers = false. However, other callers might only care whether + * data-modifying SQL will work, so they can pass include_triggers = true + * to have trigger updatability included in the result. + * + * The return value is a bitmask of rule event numbers indicating which of + * the INSERT, UPDATE and DELETE operations are supported. (We do it this way + * so that we can test for UPDATE plus DELETE support in a single call.) + */ +int +relation_is_updatable(Oid reloid, + List *outer_reloids, + bool include_triggers, + Bitmapset *include_cols) +{ + int events = 0; + Relation rel; + RuleLock *rulelocks; + +#define ALL_EVENTS ((1 << CMD_INSERT) | (1 << CMD_UPDATE) | (1 << CMD_DELETE)) + + /* Since this function recurses, it could be driven to stack overflow */ + check_stack_depth(); + + rel = try_relation_open(reloid, AccessShareLock); + + /* + * If the relation doesn't exist, return zero rather than throwing an + * error. This is helpful since scanning an information_schema view under + * MVCC rules can result in referencing rels that have actually been + * deleted already. + */ + if (rel == NULL) + return 0; + + /* If we detect a recursive view, report that it is not updatable */ + if (list_member_oid(outer_reloids, RelationGetRelid(rel))) + { + relation_close(rel, AccessShareLock); + return 0; + } + + /* If the relation is a table, it is always updatable */ + if (rel->rd_rel->relkind == RELKIND_RELATION || + rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + relation_close(rel, AccessShareLock); + return ALL_EVENTS; + } + + /* Look for unconditional DO INSTEAD rules, and note supported events */ + rulelocks = rel->rd_rules; + if (rulelocks != NULL) + { + int i; + + for (i = 0; i < rulelocks->numLocks; i++) + { + if (rulelocks->rules[i]->isInstead && + rulelocks->rules[i]->qual == NULL) + { + events |= ((1 << rulelocks->rules[i]->event) & ALL_EVENTS); + } + } + + /* If we have rules for all events, we're done */ + if (events == ALL_EVENTS) + { + relation_close(rel, AccessShareLock); + return events; + } + } + + /* Similarly look for INSTEAD OF triggers, if they are to be included */ + if (include_triggers) + { + TriggerDesc *trigDesc = rel->trigdesc; + + if (trigDesc) + { + if (trigDesc->trig_insert_instead_row) + events |= (1 << CMD_INSERT); + if (trigDesc->trig_update_instead_row) + events |= (1 << CMD_UPDATE); + if (trigDesc->trig_delete_instead_row) + events |= (1 << CMD_DELETE); + + /* If we have triggers for all events, we're done */ + if (events == ALL_EVENTS) + { + relation_close(rel, AccessShareLock); + return events; + } + } + } + + /* If this is a foreign table, check which update events it supports */ + if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) + { + FdwRoutine *fdwroutine = GetFdwRoutineForRelation(rel, false); + + if (fdwroutine->IsForeignRelUpdatable != NULL) + events |= fdwroutine->IsForeignRelUpdatable(rel); + else + { + /* Assume presence of executor functions is sufficient */ + if (fdwroutine->ExecForeignInsert != NULL) + events |= (1 << CMD_INSERT); + if (fdwroutine->ExecForeignUpdate != NULL) + events |= (1 << CMD_UPDATE); + if (fdwroutine->ExecForeignDelete != NULL) + events |= (1 << CMD_DELETE); + } + + relation_close(rel, AccessShareLock); + return events; + } + + /* Check if this is an automatically updatable view */ + if (rel->rd_rel->relkind == RELKIND_VIEW) + { + Query *viewquery = get_view_query(rel); + + if (view_query_is_auto_updatable(viewquery, false) == NULL) + { + Bitmapset *updatable_cols; + int auto_events; + RangeTblRef *rtr; + RangeTblEntry *base_rte; + Oid baseoid; + + /* + * Determine which of the view's columns are updatable. If there + * are none within the set of columns we are looking at, then the + * view doesn't support INSERT/UPDATE, but it may still support + * DELETE. + */ + view_cols_are_auto_updatable(viewquery, NULL, + &updatable_cols, NULL); + + if (include_cols != NULL) + updatable_cols = bms_int_members(updatable_cols, include_cols); + + if (bms_is_empty(updatable_cols)) + auto_events = (1 << CMD_DELETE); /* May support DELETE */ + else + auto_events = ALL_EVENTS; /* May support all events */ + + /* + * The base relation must also support these update commands. + * Tables are always updatable, but for any other kind of base + * relation we must do a recursive check limited to the columns + * referenced by the locally updatable columns in this view. + */ + rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist); + base_rte = rt_fetch(rtr->rtindex, viewquery->rtable); + Assert(base_rte->rtekind == RTE_RELATION); + + if (base_rte->relkind != RELKIND_RELATION && + base_rte->relkind != RELKIND_PARTITIONED_TABLE) + { + baseoid = base_rte->relid; + outer_reloids = lappend_oid(outer_reloids, + RelationGetRelid(rel)); + include_cols = adjust_view_column_set(updatable_cols, + viewquery->targetList); + auto_events &= relation_is_updatable(baseoid, + outer_reloids, + include_triggers, + include_cols); + outer_reloids = list_delete_last(outer_reloids); + } + events |= auto_events; + } + } + + /* If we reach here, the relation may support some update commands */ + relation_close(rel, AccessShareLock); + return events; +} + + +/* + * adjust_view_column_set - map a set of column numbers according to targetlist + * + * This is used with simply-updatable views to map column-permissions sets for + * the view columns onto the matching columns in the underlying base relation. + * The targetlist is expected to be a list of plain Vars of the underlying + * relation (as per the checks above in view_query_is_auto_updatable). + */ +static Bitmapset * +adjust_view_column_set(Bitmapset *cols, List *targetlist) +{ + Bitmapset *result = NULL; + int col; + + col = -1; + while ((col = bms_next_member(cols, col)) >= 0) + { + /* bit numbers are offset by FirstLowInvalidHeapAttributeNumber */ + AttrNumber attno = col + FirstLowInvalidHeapAttributeNumber; + + if (attno == InvalidAttrNumber) + { + /* + * There's a whole-row reference to the view. For permissions + * purposes, treat it as a reference to each column available from + * the view. (We should *not* convert this to a whole-row + * reference to the base relation, since the view may not touch + * all columns of the base relation.) + */ + ListCell *lc; + + foreach(lc, targetlist) + { + TargetEntry *tle = lfirst_node(TargetEntry, lc); + Var *var; + + if (tle->resjunk) + continue; + var = castNode(Var, tle->expr); + result = bms_add_member(result, + var->varattno - FirstLowInvalidHeapAttributeNumber); + } + } + else + { + /* + * Views do not have system columns, so we do not expect to see + * any other system attnos here. If we do find one, the error + * case will apply. + */ + TargetEntry *tle = get_tle_by_resno(targetlist, attno); + + if (tle != NULL && !tle->resjunk && IsA(tle->expr, Var)) + { + Var *var = (Var *) tle->expr; + + result = bms_add_member(result, + var->varattno - FirstLowInvalidHeapAttributeNumber); + } + else + elog(ERROR, "attribute number %d not found in view targetlist", + attno); + } + } + + return result; +} + + +/* + * rewriteTargetView - + * Attempt to rewrite a query where the target relation is a view, so that + * the view's base relation becomes the target relation. + * + * Note that the base relation here may itself be a view, which may or may not + * have INSTEAD OF triggers or rules to handle the update. That is handled by + * the recursion in RewriteQuery. + */ +static Query * +rewriteTargetView(Query *parsetree, Relation view) +{ + Query *viewquery; + const char *auto_update_detail; + RangeTblRef *rtr; + int base_rt_index; + int new_rt_index; + RangeTblEntry *base_rte; + RangeTblEntry *view_rte; + RangeTblEntry *new_rte; + Relation base_rel; + List *view_targetlist; + ListCell *lc; + + /* + * Get the Query from the view's ON SELECT rule. We're going to munge the + * Query to change the view's base relation into the target relation, + * along with various other changes along the way, so we need to make a + * copy of it (get_view_query() returns a pointer into the relcache, so we + * have to treat it as read-only). + */ + viewquery = copyObject(get_view_query(view)); + + /* The view must be updatable, else fail */ + auto_update_detail = + view_query_is_auto_updatable(viewquery, + parsetree->commandType != CMD_DELETE); + + if (auto_update_detail) + { + /* messages here should match execMain.c's CheckValidResultRel */ + switch (parsetree->commandType) + { + case CMD_INSERT: + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot insert into view \"%s\"", + RelationGetRelationName(view)), + errdetail_internal("%s", _(auto_update_detail)), + errhint("To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule."))); + break; + case CMD_UPDATE: + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot update view \"%s\"", + RelationGetRelationName(view)), + errdetail_internal("%s", _(auto_update_detail)), + errhint("To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule."))); + break; + case CMD_DELETE: + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot delete from view \"%s\"", + RelationGetRelationName(view)), + errdetail_internal("%s", _(auto_update_detail)), + errhint("To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule."))); + break; + default: + elog(ERROR, "unrecognized CmdType: %d", + (int) parsetree->commandType); + break; + } + } + + /* + * For INSERT/UPDATE the modified columns must all be updatable. Note that + * we get the modified columns from the query's targetlist, not from the + * result RTE's insertedCols and/or updatedCols set, since + * rewriteTargetListIU may have added additional targetlist entries for + * view defaults, and these must also be updatable. + */ + if (parsetree->commandType != CMD_DELETE) + { + Bitmapset *modified_cols = NULL; + char *non_updatable_col; + + foreach(lc, parsetree->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc); + + if (!tle->resjunk) + modified_cols = bms_add_member(modified_cols, + tle->resno - FirstLowInvalidHeapAttributeNumber); + } + + if (parsetree->onConflict) + { + foreach(lc, parsetree->onConflict->onConflictSet) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc); + + if (!tle->resjunk) + modified_cols = bms_add_member(modified_cols, + tle->resno - FirstLowInvalidHeapAttributeNumber); + } + } + + auto_update_detail = view_cols_are_auto_updatable(viewquery, + modified_cols, + NULL, + &non_updatable_col); + if (auto_update_detail) + { + /* + * This is a different error, caused by an attempt to update a + * non-updatable column in an otherwise updatable view. + */ + switch (parsetree->commandType) + { + case CMD_INSERT: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot insert into column \"%s\" of view \"%s\"", + non_updatable_col, + RelationGetRelationName(view)), + errdetail_internal("%s", _(auto_update_detail)))); + break; + case CMD_UPDATE: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot update column \"%s\" of view \"%s\"", + non_updatable_col, + RelationGetRelationName(view)), + errdetail_internal("%s", _(auto_update_detail)))); + break; + default: + elog(ERROR, "unrecognized CmdType: %d", + (int) parsetree->commandType); + break; + } + } + } + + /* Locate RTE describing the view in the outer query */ + view_rte = rt_fetch(parsetree->resultRelation, parsetree->rtable); + + /* + * If we get here, view_query_is_auto_updatable() has verified that the + * view contains a single base relation. + */ + Assert(list_length(viewquery->jointree->fromlist) == 1); + rtr = linitial_node(RangeTblRef, viewquery->jointree->fromlist); + + base_rt_index = rtr->rtindex; + base_rte = rt_fetch(base_rt_index, viewquery->rtable); + Assert(base_rte->rtekind == RTE_RELATION); + + /* + * Up to now, the base relation hasn't been touched at all in our query. + * We need to acquire lock on it before we try to do anything with it. + * (The subsequent recursive call of RewriteQuery will suppose that we + * already have the right lock!) Since it will become the query target + * relation, RowExclusiveLock is always the right thing. + */ + base_rel = table_open(base_rte->relid, RowExclusiveLock); + + /* + * While we have the relation open, update the RTE's relkind, just in case + * it changed since this view was made (cf. AcquireRewriteLocks). + */ + base_rte->relkind = base_rel->rd_rel->relkind; + + /* + * If the view query contains any sublink subqueries then we need to also + * acquire locks on any relations they refer to. We know that there won't + * be any subqueries in the range table or CTEs, so we can skip those, as + * in AcquireRewriteLocks. + */ + if (viewquery->hasSubLinks) + { + acquireLocksOnSubLinks_context context; + + context.for_execute = true; + query_tree_walker(viewquery, acquireLocksOnSubLinks, &context, + QTW_IGNORE_RC_SUBQUERIES); + } + + /* + * Create a new target RTE describing the base relation, and add it to the + * outer query's rangetable. (What's happening in the next few steps is + * very much like what the planner would do to "pull up" the view into the + * outer query. Perhaps someday we should refactor things enough so that + * we can share code with the planner.) + * + * Be sure to set rellockmode to the correct thing for the target table. + * Since we copied the whole viewquery above, we can just scribble on + * base_rte instead of copying it. + */ + new_rte = base_rte; + new_rte->rellockmode = RowExclusiveLock; + + parsetree->rtable = lappend(parsetree->rtable, new_rte); + new_rt_index = list_length(parsetree->rtable); + + /* + * INSERTs never inherit. For UPDATE/DELETE, we use the view query's + * inheritance flag for the base relation. + */ + if (parsetree->commandType == CMD_INSERT) + new_rte->inh = false; + + /* + * Adjust the view's targetlist Vars to reference the new target RTE, ie + * make their varnos be new_rt_index instead of base_rt_index. There can + * be no Vars for other rels in the tlist, so this is sufficient to pull + * up the tlist expressions for use in the outer query. The tlist will + * provide the replacement expressions used by ReplaceVarsFromTargetList + * below. + */ + view_targetlist = viewquery->targetList; + + ChangeVarNodes((Node *) view_targetlist, + base_rt_index, + new_rt_index, + 0); + + /* + * Mark the new target RTE for the permissions checks that we want to + * enforce against the view owner, as distinct from the query caller. At + * the relation level, require the same INSERT/UPDATE/DELETE permissions + * that the query caller needs against the view. We drop the ACL_SELECT + * bit that is presumably in new_rte->requiredPerms initially. + * + * Note: the original view RTE remains in the query's rangetable list. + * Although it will be unused in the query plan, we need it there so that + * the executor still performs appropriate permissions checks for the + * query caller's use of the view. + */ + new_rte->checkAsUser = view->rd_rel->relowner; + new_rte->requiredPerms = view_rte->requiredPerms; + + /* + * Now for the per-column permissions bits. + * + * Initially, new_rte contains selectedCols permission check bits for all + * base-rel columns referenced by the view, but since the view is a SELECT + * query its insertedCols/updatedCols is empty. We set insertedCols and + * updatedCols to include all the columns the outer query is trying to + * modify, adjusting the column numbers as needed. But we leave + * selectedCols as-is, so the view owner must have read permission for all + * columns used in the view definition, even if some of them are not read + * by the outer query. We could try to limit selectedCols to only columns + * used in the transformed query, but that does not correspond to what + * happens in ordinary SELECT usage of a view: all referenced columns must + * have read permission, even if optimization finds that some of them can + * be discarded during query transformation. The flattening we're doing + * here is an optional optimization, too. (If you are unpersuaded and + * want to change this, note that applying adjust_view_column_set to + * view_rte->selectedCols is clearly *not* the right answer, since that + * neglects base-rel columns used in the view's WHERE quals.) + * + * This step needs the modified view targetlist, so we have to do things + * in this order. + */ + Assert(bms_is_empty(new_rte->insertedCols) && + bms_is_empty(new_rte->updatedCols)); + + new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols, + view_targetlist); + + new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols, + view_targetlist); + + /* + * Move any security barrier quals from the view RTE onto the new target + * RTE. Any such quals should now apply to the new target RTE and will + * not reference the original view RTE in the rewritten query. + */ + new_rte->securityQuals = view_rte->securityQuals; + view_rte->securityQuals = NIL; + + /* + * Now update all Vars in the outer query that reference the view to + * reference the appropriate column of the base relation instead. + */ + parsetree = (Query *) + ReplaceVarsFromTargetList((Node *) parsetree, + parsetree->resultRelation, + 0, + view_rte, + view_targetlist, + REPLACEVARS_REPORT_ERROR, + 0, + &parsetree->hasSubLinks); + + /* + * Update all other RTI references in the query that point to the view + * (for example, parsetree->resultRelation itself) to point to the new + * base relation instead. Vars will not be affected since none of them + * reference parsetree->resultRelation any longer. + */ + ChangeVarNodes((Node *) parsetree, + parsetree->resultRelation, + new_rt_index, + 0); + Assert(parsetree->resultRelation == new_rt_index); + + /* + * For INSERT/UPDATE we must also update resnos in the targetlist to refer + * to columns of the base relation, since those indicate the target + * columns to be affected. + * + * Note that this destroys the resno ordering of the targetlist, but that + * will be fixed when we recurse through rewriteQuery, which will invoke + * rewriteTargetListIU again on the updated targetlist. + */ + if (parsetree->commandType != CMD_DELETE) + { + foreach(lc, parsetree->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc); + TargetEntry *view_tle; + + if (tle->resjunk) + continue; + + view_tle = get_tle_by_resno(view_targetlist, tle->resno); + if (view_tle != NULL && !view_tle->resjunk && IsA(view_tle->expr, Var)) + tle->resno = ((Var *) view_tle->expr)->varattno; + else + elog(ERROR, "attribute number %d not found in view targetlist", + tle->resno); + } + } + + /* + * For INSERT .. ON CONFLICT .. DO UPDATE, we must also update assorted + * stuff in the onConflict data structure. + */ + if (parsetree->onConflict && + parsetree->onConflict->action == ONCONFLICT_UPDATE) + { + Index old_exclRelIndex, + new_exclRelIndex; + ParseNamespaceItem *new_exclNSItem; + RangeTblEntry *new_exclRte; + List *tmp_tlist; + + /* + * Like the INSERT/UPDATE code above, update the resnos in the + * auxiliary UPDATE targetlist to refer to columns of the base + * relation. + */ + foreach(lc, parsetree->onConflict->onConflictSet) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc); + TargetEntry *view_tle; + + if (tle->resjunk) + continue; + + view_tle = get_tle_by_resno(view_targetlist, tle->resno); + if (view_tle != NULL && !view_tle->resjunk && IsA(view_tle->expr, Var)) + tle->resno = ((Var *) view_tle->expr)->varattno; + else + elog(ERROR, "attribute number %d not found in view targetlist", + tle->resno); + } + + /* + * Also, create a new RTE for the EXCLUDED pseudo-relation, using the + * query's new base rel (which may well have a different column list + * from the view, hence we need a new column alias list). This should + * match transformOnConflictClause. In particular, note that the + * relkind is set to composite to signal that we're not dealing with + * an actual relation, and no permissions checks are wanted. + */ + old_exclRelIndex = parsetree->onConflict->exclRelIndex; + + new_exclNSItem = addRangeTableEntryForRelation(make_parsestate(NULL), + base_rel, + RowExclusiveLock, + makeAlias("excluded", NIL), + false, false); + new_exclRte = new_exclNSItem->p_rte; + new_exclRte->relkind = RELKIND_COMPOSITE_TYPE; + new_exclRte->requiredPerms = 0; + /* other permissions fields in new_exclRte are already empty */ + + parsetree->rtable = lappend(parsetree->rtable, new_exclRte); + new_exclRelIndex = parsetree->onConflict->exclRelIndex = + list_length(parsetree->rtable); + + /* + * Replace the targetlist for the EXCLUDED pseudo-relation with a new + * one, representing the columns from the new base relation. + */ + parsetree->onConflict->exclRelTlist = + BuildOnConflictExcludedTargetlist(base_rel, new_exclRelIndex); + + /* + * Update all Vars in the ON CONFLICT clause that refer to the old + * EXCLUDED pseudo-relation. We want to use the column mappings + * defined in the view targetlist, but we need the outputs to refer to + * the new EXCLUDED pseudo-relation rather than the new target RTE. + * Also notice that "EXCLUDED.*" will be expanded using the view's + * rowtype, which seems correct. + */ + tmp_tlist = copyObject(view_targetlist); + + ChangeVarNodes((Node *) tmp_tlist, new_rt_index, + new_exclRelIndex, 0); + + parsetree->onConflict = (OnConflictExpr *) + ReplaceVarsFromTargetList((Node *) parsetree->onConflict, + old_exclRelIndex, + 0, + view_rte, + tmp_tlist, + REPLACEVARS_REPORT_ERROR, + 0, + &parsetree->hasSubLinks); + } + + /* + * For UPDATE/DELETE, pull up any WHERE quals from the view. We know that + * any Vars in the quals must reference the one base relation, so we need + * only adjust their varnos to reference the new target (just the same as + * we did with the view targetlist). + * + * If it's a security-barrier view, its WHERE quals must be applied before + * quals from the outer query, so we attach them to the RTE as security + * barrier quals rather than adding them to the main WHERE clause. + * + * For INSERT, the view's quals can be ignored in the main query. + */ + if (parsetree->commandType != CMD_INSERT && + viewquery->jointree->quals != NULL) + { + Node *viewqual = (Node *) viewquery->jointree->quals; + + /* + * Even though we copied viewquery already at the top of this + * function, we must duplicate the viewqual again here, because we may + * need to use the quals again below for a WithCheckOption clause. + */ + viewqual = copyObject(viewqual); + + ChangeVarNodes(viewqual, base_rt_index, new_rt_index, 0); + + if (RelationIsSecurityView(view)) + { + /* + * The view's quals go in front of existing barrier quals: those + * would have come from an outer level of security-barrier view, + * and so must get evaluated later. + * + * Note: the parsetree has been mutated, so the new_rte pointer is + * stale and needs to be re-computed. + */ + new_rte = rt_fetch(new_rt_index, parsetree->rtable); + new_rte->securityQuals = lcons(viewqual, new_rte->securityQuals); + + /* + * Do not set parsetree->hasRowSecurity, because these aren't RLS + * conditions (they aren't affected by enabling/disabling RLS). + */ + + /* + * Make sure that the query is marked correctly if the added qual + * has sublinks. + */ + if (!parsetree->hasSubLinks) + parsetree->hasSubLinks = checkExprHasSubLink(viewqual); + } + else + AddQual(parsetree, (Node *) viewqual); + } + + /* + * For INSERT/UPDATE, if the view has the WITH CHECK OPTION, or any parent + * view specified WITH CASCADED CHECK OPTION, add the quals from the view + * to the query's withCheckOptions list. + */ + if (parsetree->commandType != CMD_DELETE) + { + bool has_wco = RelationHasCheckOption(view); + bool cascaded = RelationHasCascadedCheckOption(view); + + /* + * If the parent view has a cascaded check option, treat this view as + * if it also had a cascaded check option. + * + * New WithCheckOptions are added to the start of the list, so if + * there is a cascaded check option, it will be the first item in the + * list. + */ + if (parsetree->withCheckOptions != NIL) + { + WithCheckOption *parent_wco = + (WithCheckOption *) linitial(parsetree->withCheckOptions); + + if (parent_wco->cascaded) + { + has_wco = true; + cascaded = true; + } + } + + /* + * Add the new WithCheckOption to the start of the list, so that + * checks on inner views are run before checks on outer views, as + * required by the SQL standard. + * + * If the new check is CASCADED, we need to add it even if this view + * has no quals, since there may be quals on child views. A LOCAL + * check can be omitted if this view has no quals. + */ + if (has_wco && (cascaded || viewquery->jointree->quals != NULL)) + { + WithCheckOption *wco; + + wco = makeNode(WithCheckOption); + wco->kind = WCO_VIEW_CHECK; + wco->relname = pstrdup(RelationGetRelationName(view)); + wco->polname = NULL; + wco->qual = NULL; + wco->cascaded = cascaded; + + parsetree->withCheckOptions = lcons(wco, + parsetree->withCheckOptions); + + if (viewquery->jointree->quals != NULL) + { + wco->qual = (Node *) viewquery->jointree->quals; + ChangeVarNodes(wco->qual, base_rt_index, new_rt_index, 0); + + /* + * Make sure that the query is marked correctly if the added + * qual has sublinks. We can skip this check if the query is + * already marked, or if the command is an UPDATE, in which + * case the same qual will have already been added, and this + * check will already have been done. + */ + if (!parsetree->hasSubLinks && + parsetree->commandType != CMD_UPDATE) + parsetree->hasSubLinks = checkExprHasSubLink(wco->qual); + } + } + } + + table_close(base_rel, NoLock); + + return parsetree; +} + + +/* + * RewriteQuery - + * rewrites the query and apply the rules again on the queries rewritten + * + * rewrite_events is a list of open query-rewrite actions, so we can detect + * infinite recursion. + */ +static List * +RewriteQuery(Query *parsetree, List *rewrite_events) +{ + CmdType event = parsetree->commandType; + bool instead = false; + bool returning = false; + bool updatableview = false; + Query *qual_product = NULL; + List *rewritten = NIL; + ListCell *lc1; + + /* + * First, recursively process any insert/update/delete statements in WITH + * clauses. (We have to do this first because the WITH clauses may get + * copied into rule actions below.) + */ + foreach(lc1, parsetree->cteList) + { + CommonTableExpr *cte = lfirst_node(CommonTableExpr, lc1); + Query *ctequery = castNode(Query, cte->ctequery); + List *newstuff; + + if (ctequery->commandType == CMD_SELECT) + continue; + + newstuff = RewriteQuery(ctequery, rewrite_events); + + /* + * Currently we can only handle unconditional, single-statement DO + * INSTEAD rules correctly; we have to get exactly one non-utility + * Query out of the rewrite operation to stuff back into the CTE node. + */ + if (list_length(newstuff) == 1) + { + /* Must check it's not a utility command */ + ctequery = linitial_node(Query, newstuff); + if (!(ctequery->commandType == CMD_SELECT || + ctequery->commandType == CMD_UPDATE || + ctequery->commandType == CMD_INSERT || + ctequery->commandType == CMD_DELETE)) + { + /* + * Currently it could only be NOTIFY; this error message will + * need work if we ever allow other utility commands in rules. + */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("DO INSTEAD NOTIFY rules are not supported for data-modifying statements in WITH"))); + } + /* WITH queries should never be canSetTag */ + Assert(!ctequery->canSetTag); + /* Push the single Query back into the CTE node */ + cte->ctequery = (Node *) ctequery; + } + else if (newstuff == NIL) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("DO INSTEAD NOTHING rules are not supported for data-modifying statements in WITH"))); + } + else + { + ListCell *lc2; + + /* examine queries to determine which error message to issue */ + foreach(lc2, newstuff) + { + Query *q = (Query *) lfirst(lc2); + + if (q->querySource == QSRC_QUAL_INSTEAD_RULE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("conditional DO INSTEAD rules are not supported for data-modifying statements in WITH"))); + if (q->querySource == QSRC_NON_INSTEAD_RULE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("DO ALSO rules are not supported for data-modifying statements in WITH"))); + } + + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("multi-statement DO INSTEAD rules are not supported for data-modifying statements in WITH"))); + } + } + + /* + * If the statement is an insert, update, or delete, adjust its targetlist + * as needed, and then fire INSERT/UPDATE/DELETE rules on it. + * + * SELECT rules are handled later when we have all the queries that should + * get executed. Also, utilities aren't rewritten at all (do we still + * need that check?) + */ + if (event != CMD_SELECT && event != CMD_UTILITY) + { + int result_relation; + RangeTblEntry *rt_entry; + Relation rt_entry_relation; + List *locks; + List *product_queries; + bool hasUpdate = false; + int values_rte_index = 0; + bool defaults_remaining = false; + + result_relation = parsetree->resultRelation; + Assert(result_relation != 0); + rt_entry = rt_fetch(result_relation, parsetree->rtable); + Assert(rt_entry->rtekind == RTE_RELATION); + + /* + * We can use NoLock here since either the parser or + * AcquireRewriteLocks should have locked the rel already. + */ + rt_entry_relation = table_open(rt_entry->relid, NoLock); + + /* + * Rewrite the targetlist as needed for the command type. + */ + if (event == CMD_INSERT) + { + RangeTblEntry *values_rte = NULL; + + /* + * If it's an INSERT ... VALUES (...), (...), ... there will be a + * single RTE for the VALUES targetlists. + */ + if (list_length(parsetree->jointree->fromlist) == 1) + { + RangeTblRef *rtr = (RangeTblRef *) linitial(parsetree->jointree->fromlist); + + if (IsA(rtr, RangeTblRef)) + { + RangeTblEntry *rte = rt_fetch(rtr->rtindex, + parsetree->rtable); + + if (rte->rtekind == RTE_VALUES) + { + values_rte = rte; + values_rte_index = rtr->rtindex; + } + } + } + + if (values_rte) + { + Bitmapset *unused_values_attrnos = NULL; + + /* Process the main targetlist ... */ + parsetree->targetList = rewriteTargetListIU(parsetree->targetList, + parsetree->commandType, + parsetree->override, + rt_entry_relation, + values_rte, + values_rte_index, + &unused_values_attrnos); + /* ... and the VALUES expression lists */ + if (!rewriteValuesRTE(parsetree, values_rte, values_rte_index, + rt_entry_relation, false, + unused_values_attrnos)) + defaults_remaining = true; + } + else + { + /* Process just the main targetlist */ + parsetree->targetList = + rewriteTargetListIU(parsetree->targetList, + parsetree->commandType, + parsetree->override, + rt_entry_relation, + NULL, 0, NULL); + } + + if (parsetree->onConflict && + parsetree->onConflict->action == ONCONFLICT_UPDATE) + { + parsetree->onConflict->onConflictSet = + rewriteTargetListIU(parsetree->onConflict->onConflictSet, + CMD_UPDATE, + parsetree->override, + rt_entry_relation, + NULL, 0, NULL); + } + } + else if (event == CMD_UPDATE) + { + parsetree->targetList = + rewriteTargetListIU(parsetree->targetList, + parsetree->commandType, + parsetree->override, + rt_entry_relation, + NULL, 0, NULL); + + /* Also populate extraUpdatedCols (for generated columns) */ + fill_extraUpdatedCols(rt_entry, rt_entry_relation); + } + else if (event == CMD_DELETE) + { + /* Nothing to do here */ + } + else + elog(ERROR, "unrecognized commandType: %d", (int) event); + + /* + * Collect and apply the appropriate rules. + */ + locks = matchLocks(event, rt_entry_relation->rd_rules, + result_relation, parsetree, &hasUpdate); + + product_queries = fireRules(parsetree, + result_relation, + event, + locks, + &instead, + &returning, + &qual_product); + + /* + * If we have a VALUES RTE with any remaining untouched DEFAULT items, + * and we got any product queries, finalize the VALUES RTE for each + * product query (replacing the remaining DEFAULT items with NULLs). + * We don't do this for the original query, because we know that it + * must be an auto-insert on a view, and so should use the base + * relation's defaults for any remaining DEFAULT items. + */ + if (defaults_remaining && product_queries != NIL) + { + ListCell *n; + + /* + * Each product query has its own copy of the VALUES RTE at the + * same index in the rangetable, so we must finalize each one. + */ + foreach(n, product_queries) + { + Query *pt = (Query *) lfirst(n); + RangeTblEntry *values_rte = rt_fetch(values_rte_index, + pt->rtable); + + rewriteValuesRTE(pt, values_rte, values_rte_index, + rt_entry_relation, + true, /* Force remaining defaults to NULL */ + NULL); + } + } + + /* + * If there was no unqualified INSTEAD rule, and the target relation + * is a view without any INSTEAD OF triggers, see if the view can be + * automatically updated. If so, we perform the necessary query + * transformation here and add the resulting query to the + * product_queries list, so that it gets recursively rewritten if + * necessary. + * + * If the view cannot be automatically updated, we throw an error here + * which is OK since the query would fail at runtime anyway. Throwing + * the error here is preferable to the executor check since we have + * more detailed information available about why the view isn't + * updatable. + */ + if (!instead && + rt_entry_relation->rd_rel->relkind == RELKIND_VIEW && + !view_has_instead_trigger(rt_entry_relation, event)) + { + /* + * If there were any qualified INSTEAD rules, don't allow the view + * to be automatically updated (an unqualified INSTEAD rule or + * INSTEAD OF trigger is required). + * + * The messages here should match execMain.c's CheckValidResultRel + * and in principle make those checks in executor unnecessary, but + * we keep them just in case. + */ + if (qual_product != NULL) + { + switch (parsetree->commandType) + { + case CMD_INSERT: + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot insert into view \"%s\"", + RelationGetRelationName(rt_entry_relation)), + errdetail("Views with conditional DO INSTEAD rules are not automatically updatable."), + errhint("To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule."))); + break; + case CMD_UPDATE: + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot update view \"%s\"", + RelationGetRelationName(rt_entry_relation)), + errdetail("Views with conditional DO INSTEAD rules are not automatically updatable."), + errhint("To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule."))); + break; + case CMD_DELETE: + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot delete from view \"%s\"", + RelationGetRelationName(rt_entry_relation)), + errdetail("Views with conditional DO INSTEAD rules are not automatically updatable."), + errhint("To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule."))); + break; + default: + elog(ERROR, "unrecognized CmdType: %d", + (int) parsetree->commandType); + break; + } + } + + /* + * Attempt to rewrite the query to automatically update the view. + * This throws an error if the view can't be automatically + * updated. + */ + parsetree = rewriteTargetView(parsetree, rt_entry_relation); + + /* + * At this point product_queries contains any DO ALSO rule + * actions. Add the rewritten query before or after those. This + * must match the handling the original query would have gotten + * below, if we allowed it to be included again. + */ + if (parsetree->commandType == CMD_INSERT) + product_queries = lcons(parsetree, product_queries); + else + product_queries = lappend(product_queries, parsetree); + + /* + * Set the "instead" flag, as if there had been an unqualified + * INSTEAD, to prevent the original query from being included a + * second time below. The transformation will have rewritten any + * RETURNING list, so we can also set "returning" to forestall + * throwing an error below. + */ + instead = true; + returning = true; + updatableview = true; + } + + /* + * If we got any product queries, recursively rewrite them --- but + * first check for recursion! + */ + if (product_queries != NIL) + { + ListCell *n; + rewrite_event *rev; + + foreach(n, rewrite_events) + { + rev = (rewrite_event *) lfirst(n); + if (rev->relation == RelationGetRelid(rt_entry_relation) && + rev->event == event) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("infinite recursion detected in rules for relation \"%s\"", + RelationGetRelationName(rt_entry_relation)))); + } + + rev = (rewrite_event *) palloc(sizeof(rewrite_event)); + rev->relation = RelationGetRelid(rt_entry_relation); + rev->event = event; + rewrite_events = lappend(rewrite_events, rev); + + foreach(n, product_queries) + { + Query *pt = (Query *) lfirst(n); + List *newstuff; + + newstuff = RewriteQuery(pt, rewrite_events); + rewritten = list_concat(rewritten, newstuff); + } + + rewrite_events = list_delete_last(rewrite_events); + } + + /* + * If there is an INSTEAD, and the original query has a RETURNING, we + * have to have found a RETURNING in the rule(s), else fail. (Because + * DefineQueryRewrite only allows RETURNING in unconditional INSTEAD + * rules, there's no need to worry whether the substituted RETURNING + * will actually be executed --- it must be.) + */ + if ((instead || qual_product != NULL) && + parsetree->returningList && + !returning) + { + switch (event) + { + case CMD_INSERT: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot perform INSERT RETURNING on relation \"%s\"", + RelationGetRelationName(rt_entry_relation)), + errhint("You need an unconditional ON INSERT DO INSTEAD rule with a RETURNING clause."))); + break; + case CMD_UPDATE: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot perform UPDATE RETURNING on relation \"%s\"", + RelationGetRelationName(rt_entry_relation)), + errhint("You need an unconditional ON UPDATE DO INSTEAD rule with a RETURNING clause."))); + break; + case CMD_DELETE: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot perform DELETE RETURNING on relation \"%s\"", + RelationGetRelationName(rt_entry_relation)), + errhint("You need an unconditional ON DELETE DO INSTEAD rule with a RETURNING clause."))); + break; + default: + elog(ERROR, "unrecognized commandType: %d", + (int) event); + break; + } + } + + /* + * Updatable views are supported by ON CONFLICT, so don't prevent that + * case from proceeding + */ + if (parsetree->onConflict && + (product_queries != NIL || hasUpdate) && + !updatableview) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("INSERT with ON CONFLICT clause cannot be used with table that has INSERT or UPDATE rules"))); + + table_close(rt_entry_relation, NoLock); + } + + /* + * For INSERTs, the original query is done first; for UPDATE/DELETE, it is + * done last. This is needed because update and delete rule actions might + * not do anything if they are invoked after the update or delete is + * performed. The command counter increment between the query executions + * makes the deleted (and maybe the updated) tuples disappear so the scans + * for them in the rule actions cannot find them. + * + * If we found any unqualified INSTEAD, the original query is not done at + * all, in any form. Otherwise, we add the modified form if qualified + * INSTEADs were found, else the unmodified form. + */ + if (!instead) + { + if (parsetree->commandType == CMD_INSERT) + { + if (qual_product != NULL) + rewritten = lcons(qual_product, rewritten); + else + rewritten = lcons(parsetree, rewritten); + } + else + { + if (qual_product != NULL) + rewritten = lappend(rewritten, qual_product); + else + rewritten = lappend(rewritten, parsetree); + } + } + + /* + * If the original query has a CTE list, and we generated more than one + * non-utility result query, we have to fail because we'll have copied the + * CTE list into each result query. That would break the expectation of + * single evaluation of CTEs. This could possibly be fixed by + * restructuring so that a CTE list can be shared across multiple Query + * and PlannableStatement nodes. + */ + if (parsetree->cteList != NIL) + { + int qcount = 0; + + foreach(lc1, rewritten) + { + Query *q = (Query *) lfirst(lc1); + + if (q->commandType != CMD_UTILITY) + qcount++; + } + if (qcount > 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("WITH cannot be used in a query that is rewritten by rules into multiple queries"))); + } + + return rewritten; +} + + +/* + * QueryRewrite - + * Primary entry point to the query rewriter. + * Rewrite one query via query rewrite system, possibly returning 0 + * or many queries. + * + * NOTE: the parsetree must either have come straight from the parser, + * or have been scanned by AcquireRewriteLocks to acquire suitable locks. + */ +List * +QueryRewrite(Query *parsetree) +{ + uint64 input_query_id = parsetree->queryId; + List *querylist; + List *results; + ListCell *l; + CmdType origCmdType; + bool foundOriginalQuery; + Query *lastInstead; + + /* + * This function is only applied to top-level original queries + */ + Assert(parsetree->querySource == QSRC_ORIGINAL); + Assert(parsetree->canSetTag); + + /* + * Step 1 + * + * Apply all non-SELECT rules possibly getting 0 or many queries + */ + querylist = RewriteQuery(parsetree, NIL); + + /* + * Step 2 + * + * Apply all the RIR rules on each query + * + * This is also a handy place to mark each query with the original queryId + */ + results = NIL; + foreach(l, querylist) + { + Query *query = (Query *) lfirst(l); + + query = fireRIRrules(query, NIL); + + query->queryId = input_query_id; + + results = lappend(results, query); + } + + /* + * Step 3 + * + * Determine which, if any, of the resulting queries is supposed to set + * the command-result tag; and update the canSetTag fields accordingly. + * + * If the original query is still in the list, it sets the command tag. + * Otherwise, the last INSTEAD query of the same kind as the original is + * allowed to set the tag. (Note these rules can leave us with no query + * setting the tag. The tcop code has to cope with this by setting up a + * default tag based on the original un-rewritten query.) + * + * The Asserts verify that at most one query in the result list is marked + * canSetTag. If we aren't checking asserts, we can fall out of the loop + * as soon as we find the original query. + */ + origCmdType = parsetree->commandType; + foundOriginalQuery = false; + lastInstead = NULL; + + foreach(l, results) + { + Query *query = (Query *) lfirst(l); + + if (query->querySource == QSRC_ORIGINAL) + { + Assert(query->canSetTag); + Assert(!foundOriginalQuery); + foundOriginalQuery = true; +#ifndef USE_ASSERT_CHECKING + break; +#endif + } + else + { + Assert(!query->canSetTag); + if (query->commandType == origCmdType && + (query->querySource == QSRC_INSTEAD_RULE || + query->querySource == QSRC_QUAL_INSTEAD_RULE)) + lastInstead = query; + } + } + + if (!foundOriginalQuery && lastInstead != NULL) + lastInstead->canSetTag = true; + + return results; +} diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c new file mode 100644 index 0000000..d4e0b8b --- /dev/null +++ b/src/backend/rewrite/rewriteManip.c @@ -0,0 +1,1533 @@ +/*------------------------------------------------------------------------- + * + * rewriteManip.c + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/rewrite/rewriteManip.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "nodes/pathnodes.h" +#include "nodes/plannodes.h" +#include "parser/parse_coerce.h" +#include "parser/parse_relation.h" +#include "parser/parsetree.h" +#include "rewrite/rewriteManip.h" + + +typedef struct +{ + int sublevels_up; +} contain_aggs_of_level_context; + +typedef struct +{ + int agg_location; + int sublevels_up; +} locate_agg_of_level_context; + +typedef struct +{ + int win_location; +} locate_windowfunc_context; + +static bool contain_aggs_of_level_walker(Node *node, + contain_aggs_of_level_context *context); +static bool locate_agg_of_level_walker(Node *node, + locate_agg_of_level_context *context); +static bool contain_windowfuncs_walker(Node *node, void *context); +static bool locate_windowfunc_walker(Node *node, + locate_windowfunc_context *context); +static bool checkExprHasSubLink_walker(Node *node, void *context); +static Relids offset_relid_set(Relids relids, int offset); +static Relids adjust_relid_set(Relids relids, int oldrelid, int newrelid); + + +/* + * contain_aggs_of_level - + * Check if an expression contains an aggregate function call of a + * specified query level. + * + * The objective of this routine is to detect whether there are aggregates + * belonging to the given query level. Aggregates belonging to subqueries + * or outer queries do NOT cause a true result. We must recurse into + * subqueries to detect outer-reference aggregates that logically belong to + * the specified query level. + */ +bool +contain_aggs_of_level(Node *node, int levelsup) +{ + contain_aggs_of_level_context context; + + context.sublevels_up = levelsup; + + /* + * Must be prepared to start with a Query or a bare expression tree; if + * it's a Query, we don't want to increment sublevels_up. + */ + return query_or_expression_tree_walker(node, + contain_aggs_of_level_walker, + (void *) &context, + 0); +} + +static bool +contain_aggs_of_level_walker(Node *node, + contain_aggs_of_level_context *context) +{ + if (node == NULL) + return false; + if (IsA(node, Aggref)) + { + if (((Aggref *) node)->agglevelsup == context->sublevels_up) + return true; /* abort the tree traversal and return true */ + /* else fall through to examine argument */ + } + if (IsA(node, GroupingFunc)) + { + if (((GroupingFunc *) node)->agglevelsup == context->sublevels_up) + return true; + /* else fall through to examine argument */ + } + if (IsA(node, Query)) + { + /* Recurse into subselects */ + bool result; + + context->sublevels_up++; + result = query_tree_walker((Query *) node, + contain_aggs_of_level_walker, + (void *) context, 0); + context->sublevels_up--; + return result; + } + return expression_tree_walker(node, contain_aggs_of_level_walker, + (void *) context); +} + +/* + * locate_agg_of_level - + * Find the parse location of any aggregate of the specified query level. + * + * Returns -1 if no such agg is in the querytree, or if they all have + * unknown parse location. (The former case is probably caller error, + * but we don't bother to distinguish it from the latter case.) + * + * Note: it might seem appropriate to merge this functionality into + * contain_aggs_of_level, but that would complicate that function's API. + * Currently, the only uses of this function are for error reporting, + * and so shaving cycles probably isn't very important. + */ +int +locate_agg_of_level(Node *node, int levelsup) +{ + locate_agg_of_level_context context; + + context.agg_location = -1; /* in case we find nothing */ + context.sublevels_up = levelsup; + + /* + * Must be prepared to start with a Query or a bare expression tree; if + * it's a Query, we don't want to increment sublevels_up. + */ + (void) query_or_expression_tree_walker(node, + locate_agg_of_level_walker, + (void *) &context, + 0); + + return context.agg_location; +} + +static bool +locate_agg_of_level_walker(Node *node, + locate_agg_of_level_context *context) +{ + if (node == NULL) + return false; + if (IsA(node, Aggref)) + { + if (((Aggref *) node)->agglevelsup == context->sublevels_up && + ((Aggref *) node)->location >= 0) + { + context->agg_location = ((Aggref *) node)->location; + return true; /* abort the tree traversal and return true */ + } + /* else fall through to examine argument */ + } + if (IsA(node, GroupingFunc)) + { + if (((GroupingFunc *) node)->agglevelsup == context->sublevels_up && + ((GroupingFunc *) node)->location >= 0) + { + context->agg_location = ((GroupingFunc *) node)->location; + return true; /* abort the tree traversal and return true */ + } + } + if (IsA(node, Query)) + { + /* Recurse into subselects */ + bool result; + + context->sublevels_up++; + result = query_tree_walker((Query *) node, + locate_agg_of_level_walker, + (void *) context, 0); + context->sublevels_up--; + return result; + } + return expression_tree_walker(node, locate_agg_of_level_walker, + (void *) context); +} + +/* + * contain_windowfuncs - + * Check if an expression contains a window function call of the + * current query level. + */ +bool +contain_windowfuncs(Node *node) +{ + /* + * Must be prepared to start with a Query or a bare expression tree; if + * it's a Query, we don't want to increment sublevels_up. + */ + return query_or_expression_tree_walker(node, + contain_windowfuncs_walker, + NULL, + 0); +} + +static bool +contain_windowfuncs_walker(Node *node, void *context) +{ + if (node == NULL) + return false; + if (IsA(node, WindowFunc)) + return true; /* abort the tree traversal and return true */ + /* Mustn't recurse into subselects */ + return expression_tree_walker(node, contain_windowfuncs_walker, + (void *) context); +} + +/* + * locate_windowfunc - + * Find the parse location of any windowfunc of the current query level. + * + * Returns -1 if no such windowfunc is in the querytree, or if they all have + * unknown parse location. (The former case is probably caller error, + * but we don't bother to distinguish it from the latter case.) + * + * Note: it might seem appropriate to merge this functionality into + * contain_windowfuncs, but that would complicate that function's API. + * Currently, the only uses of this function are for error reporting, + * and so shaving cycles probably isn't very important. + */ +int +locate_windowfunc(Node *node) +{ + locate_windowfunc_context context; + + context.win_location = -1; /* in case we find nothing */ + + /* + * Must be prepared to start with a Query or a bare expression tree; if + * it's a Query, we don't want to increment sublevels_up. + */ + (void) query_or_expression_tree_walker(node, + locate_windowfunc_walker, + (void *) &context, + 0); + + return context.win_location; +} + +static bool +locate_windowfunc_walker(Node *node, locate_windowfunc_context *context) +{ + if (node == NULL) + return false; + if (IsA(node, WindowFunc)) + { + if (((WindowFunc *) node)->location >= 0) + { + context->win_location = ((WindowFunc *) node)->location; + return true; /* abort the tree traversal and return true */ + } + /* else fall through to examine argument */ + } + /* Mustn't recurse into subselects */ + return expression_tree_walker(node, locate_windowfunc_walker, + (void *) context); +} + +/* + * checkExprHasSubLink - + * Check if an expression contains a SubLink. + */ +bool +checkExprHasSubLink(Node *node) +{ + /* + * If a Query is passed, examine it --- but we should not recurse into + * sub-Queries that are in its rangetable or CTE list. + */ + return query_or_expression_tree_walker(node, + checkExprHasSubLink_walker, + NULL, + QTW_IGNORE_RC_SUBQUERIES); +} + +static bool +checkExprHasSubLink_walker(Node *node, void *context) +{ + if (node == NULL) + return false; + if (IsA(node, SubLink)) + return true; /* abort the tree traversal and return true */ + return expression_tree_walker(node, checkExprHasSubLink_walker, context); +} + +/* + * Check for MULTIEXPR Param within expression tree + * + * We intentionally don't descend into SubLinks: only Params at the current + * query level are of interest. + */ +static bool +contains_multiexpr_param(Node *node, void *context) +{ + if (node == NULL) + return false; + if (IsA(node, Param)) + { + if (((Param *) node)->paramkind == PARAM_MULTIEXPR) + return true; /* abort the tree traversal and return true */ + return false; + } + return expression_tree_walker(node, contains_multiexpr_param, context); +} + + +/* + * OffsetVarNodes - adjust Vars when appending one query's RT to another + * + * Find all Var nodes in the given tree with varlevelsup == sublevels_up, + * and increment their varno fields (rangetable indexes) by 'offset'. + * The varnosyn fields are adjusted similarly. Also, adjust other nodes + * that contain rangetable indexes, such as RangeTblRef and JoinExpr. + * + * NOTE: although this has the form of a walker, we cheat and modify the + * nodes in-place. The given expression tree should have been copied + * earlier to ensure that no unwanted side-effects occur! + */ + +typedef struct +{ + int offset; + int sublevels_up; +} OffsetVarNodes_context; + +static bool +OffsetVarNodes_walker(Node *node, OffsetVarNodes_context *context) +{ + if (node == NULL) + return false; + if (IsA(node, Var)) + { + Var *var = (Var *) node; + + if (var->varlevelsup == context->sublevels_up) + { + var->varno += context->offset; + if (var->varnosyn > 0) + var->varnosyn += context->offset; + } + return false; + } + if (IsA(node, CurrentOfExpr)) + { + CurrentOfExpr *cexpr = (CurrentOfExpr *) node; + + if (context->sublevels_up == 0) + cexpr->cvarno += context->offset; + return false; + } + if (IsA(node, RangeTblRef)) + { + RangeTblRef *rtr = (RangeTblRef *) node; + + if (context->sublevels_up == 0) + rtr->rtindex += context->offset; + /* the subquery itself is visited separately */ + return false; + } + if (IsA(node, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) node; + + if (j->rtindex && context->sublevels_up == 0) + j->rtindex += context->offset; + /* fall through to examine children */ + } + if (IsA(node, PlaceHolderVar)) + { + PlaceHolderVar *phv = (PlaceHolderVar *) node; + + if (phv->phlevelsup == context->sublevels_up) + { + phv->phrels = offset_relid_set(phv->phrels, + context->offset); + } + /* fall through to examine children */ + } + if (IsA(node, AppendRelInfo)) + { + AppendRelInfo *appinfo = (AppendRelInfo *) node; + + if (context->sublevels_up == 0) + { + appinfo->parent_relid += context->offset; + appinfo->child_relid += context->offset; + } + /* fall through to examine children */ + } + /* Shouldn't need to handle other planner auxiliary nodes here */ + Assert(!IsA(node, PlanRowMark)); + Assert(!IsA(node, SpecialJoinInfo)); + Assert(!IsA(node, PlaceHolderInfo)); + Assert(!IsA(node, MinMaxAggInfo)); + + if (IsA(node, Query)) + { + /* Recurse into subselects */ + bool result; + + context->sublevels_up++; + result = query_tree_walker((Query *) node, OffsetVarNodes_walker, + (void *) context, 0); + context->sublevels_up--; + return result; + } + return expression_tree_walker(node, OffsetVarNodes_walker, + (void *) context); +} + +void +OffsetVarNodes(Node *node, int offset, int sublevels_up) +{ + OffsetVarNodes_context context; + + context.offset = offset; + context.sublevels_up = sublevels_up; + + /* + * Must be prepared to start with a Query or a bare expression tree; if + * it's a Query, go straight to query_tree_walker to make sure that + * sublevels_up doesn't get incremented prematurely. + */ + if (node && IsA(node, Query)) + { + Query *qry = (Query *) node; + + /* + * If we are starting at a Query, and sublevels_up is zero, then we + * must also fix rangetable indexes in the Query itself --- namely + * resultRelation, exclRelIndex and rowMarks entries. sublevels_up + * cannot be zero when recursing into a subquery, so there's no need + * to have the same logic inside OffsetVarNodes_walker. + */ + if (sublevels_up == 0) + { + ListCell *l; + + if (qry->resultRelation) + qry->resultRelation += offset; + + if (qry->onConflict && qry->onConflict->exclRelIndex) + qry->onConflict->exclRelIndex += offset; + + foreach(l, qry->rowMarks) + { + RowMarkClause *rc = (RowMarkClause *) lfirst(l); + + rc->rti += offset; + } + } + query_tree_walker(qry, OffsetVarNodes_walker, + (void *) &context, 0); + } + else + OffsetVarNodes_walker(node, &context); +} + +static Relids +offset_relid_set(Relids relids, int offset) +{ + Relids result = NULL; + int rtindex; + + rtindex = -1; + while ((rtindex = bms_next_member(relids, rtindex)) >= 0) + result = bms_add_member(result, rtindex + offset); + return result; +} + +/* + * ChangeVarNodes - adjust Var nodes for a specific change of RT index + * + * Find all Var nodes in the given tree belonging to a specific relation + * (identified by sublevels_up and rt_index), and change their varno fields + * to 'new_index'. The varnosyn fields are changed too. Also, adjust other + * nodes that contain rangetable indexes, such as RangeTblRef and JoinExpr. + * + * NOTE: although this has the form of a walker, we cheat and modify the + * nodes in-place. The given expression tree should have been copied + * earlier to ensure that no unwanted side-effects occur! + */ + +typedef struct +{ + int rt_index; + int new_index; + int sublevels_up; +} ChangeVarNodes_context; + +static bool +ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context) +{ + if (node == NULL) + return false; + if (IsA(node, Var)) + { + Var *var = (Var *) node; + + if (var->varlevelsup == context->sublevels_up && + var->varno == context->rt_index) + { + var->varno = context->new_index; + /* If the syntactic referent is same RTE, fix it too */ + if (var->varnosyn == context->rt_index) + var->varnosyn = context->new_index; + } + return false; + } + if (IsA(node, CurrentOfExpr)) + { + CurrentOfExpr *cexpr = (CurrentOfExpr *) node; + + if (context->sublevels_up == 0 && + cexpr->cvarno == context->rt_index) + cexpr->cvarno = context->new_index; + return false; + } + if (IsA(node, RangeTblRef)) + { + RangeTblRef *rtr = (RangeTblRef *) node; + + if (context->sublevels_up == 0 && + rtr->rtindex == context->rt_index) + rtr->rtindex = context->new_index; + /* the subquery itself is visited separately */ + return false; + } + if (IsA(node, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) node; + + if (context->sublevels_up == 0 && + j->rtindex == context->rt_index) + j->rtindex = context->new_index; + /* fall through to examine children */ + } + if (IsA(node, PlaceHolderVar)) + { + PlaceHolderVar *phv = (PlaceHolderVar *) node; + + if (phv->phlevelsup == context->sublevels_up) + { + phv->phrels = adjust_relid_set(phv->phrels, + context->rt_index, + context->new_index); + } + /* fall through to examine children */ + } + if (IsA(node, PlanRowMark)) + { + PlanRowMark *rowmark = (PlanRowMark *) node; + + if (context->sublevels_up == 0) + { + if (rowmark->rti == context->rt_index) + rowmark->rti = context->new_index; + if (rowmark->prti == context->rt_index) + rowmark->prti = context->new_index; + } + return false; + } + if (IsA(node, AppendRelInfo)) + { + AppendRelInfo *appinfo = (AppendRelInfo *) node; + + if (context->sublevels_up == 0) + { + if (appinfo->parent_relid == context->rt_index) + appinfo->parent_relid = context->new_index; + if (appinfo->child_relid == context->rt_index) + appinfo->child_relid = context->new_index; + } + /* fall through to examine children */ + } + /* Shouldn't need to handle other planner auxiliary nodes here */ + Assert(!IsA(node, SpecialJoinInfo)); + Assert(!IsA(node, PlaceHolderInfo)); + Assert(!IsA(node, MinMaxAggInfo)); + + if (IsA(node, Query)) + { + /* Recurse into subselects */ + bool result; + + context->sublevels_up++; + result = query_tree_walker((Query *) node, ChangeVarNodes_walker, + (void *) context, 0); + context->sublevels_up--; + return result; + } + return expression_tree_walker(node, ChangeVarNodes_walker, + (void *) context); +} + +void +ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up) +{ + ChangeVarNodes_context context; + + context.rt_index = rt_index; + context.new_index = new_index; + context.sublevels_up = sublevels_up; + + /* + * Must be prepared to start with a Query or a bare expression tree; if + * it's a Query, go straight to query_tree_walker to make sure that + * sublevels_up doesn't get incremented prematurely. + */ + if (node && IsA(node, Query)) + { + Query *qry = (Query *) node; + + /* + * If we are starting at a Query, and sublevels_up is zero, then we + * must also fix rangetable indexes in the Query itself --- namely + * resultRelation and rowMarks entries. sublevels_up cannot be zero + * when recursing into a subquery, so there's no need to have the same + * logic inside ChangeVarNodes_walker. + */ + if (sublevels_up == 0) + { + ListCell *l; + + if (qry->resultRelation == rt_index) + qry->resultRelation = new_index; + + /* this is unlikely to ever be used, but ... */ + if (qry->onConflict && qry->onConflict->exclRelIndex == rt_index) + qry->onConflict->exclRelIndex = new_index; + + foreach(l, qry->rowMarks) + { + RowMarkClause *rc = (RowMarkClause *) lfirst(l); + + if (rc->rti == rt_index) + rc->rti = new_index; + } + } + query_tree_walker(qry, ChangeVarNodes_walker, + (void *) &context, 0); + } + else + ChangeVarNodes_walker(node, &context); +} + +/* + * Substitute newrelid for oldrelid in a Relid set + */ +static Relids +adjust_relid_set(Relids relids, int oldrelid, int newrelid) +{ + if (bms_is_member(oldrelid, relids)) + { + /* Ensure we have a modifiable copy */ + relids = bms_copy(relids); + /* Remove old, add new */ + relids = bms_del_member(relids, oldrelid); + relids = bms_add_member(relids, newrelid); + } + return relids; +} + +/* + * IncrementVarSublevelsUp - adjust Var nodes when pushing them down in tree + * + * Find all Var nodes in the given tree having varlevelsup >= min_sublevels_up, + * and add delta_sublevels_up to their varlevelsup value. This is needed when + * an expression that's correct for some nesting level is inserted into a + * subquery. Ordinarily the initial call has min_sublevels_up == 0 so that + * all Vars are affected. The point of min_sublevels_up is that we can + * increment it when we recurse into a sublink, so that local variables in + * that sublink are not affected, only outer references to vars that belong + * to the expression's original query level or parents thereof. + * + * Likewise for other nodes containing levelsup fields, such as Aggref. + * + * NOTE: although this has the form of a walker, we cheat and modify the + * Var nodes in-place. The given expression tree should have been copied + * earlier to ensure that no unwanted side-effects occur! + */ + +typedef struct +{ + int delta_sublevels_up; + int min_sublevels_up; +} IncrementVarSublevelsUp_context; + +static bool +IncrementVarSublevelsUp_walker(Node *node, + IncrementVarSublevelsUp_context *context) +{ + if (node == NULL) + return false; + if (IsA(node, Var)) + { + Var *var = (Var *) node; + + if (var->varlevelsup >= context->min_sublevels_up) + var->varlevelsup += context->delta_sublevels_up; + return false; /* done here */ + } + if (IsA(node, CurrentOfExpr)) + { + /* this should not happen */ + if (context->min_sublevels_up == 0) + elog(ERROR, "cannot push down CurrentOfExpr"); + return false; + } + if (IsA(node, Aggref)) + { + Aggref *agg = (Aggref *) node; + + if (agg->agglevelsup >= context->min_sublevels_up) + agg->agglevelsup += context->delta_sublevels_up; + /* fall through to recurse into argument */ + } + if (IsA(node, GroupingFunc)) + { + GroupingFunc *grp = (GroupingFunc *) node; + + if (grp->agglevelsup >= context->min_sublevels_up) + grp->agglevelsup += context->delta_sublevels_up; + /* fall through to recurse into argument */ + } + if (IsA(node, PlaceHolderVar)) + { + PlaceHolderVar *phv = (PlaceHolderVar *) node; + + if (phv->phlevelsup >= context->min_sublevels_up) + phv->phlevelsup += context->delta_sublevels_up; + /* fall through to recurse into argument */ + } + if (IsA(node, RangeTblEntry)) + { + RangeTblEntry *rte = (RangeTblEntry *) node; + + if (rte->rtekind == RTE_CTE) + { + if (rte->ctelevelsup >= context->min_sublevels_up) + rte->ctelevelsup += context->delta_sublevels_up; + } + return false; /* allow range_table_walker to continue */ + } + if (IsA(node, Query)) + { + /* Recurse into subselects */ + bool result; + + context->min_sublevels_up++; + result = query_tree_walker((Query *) node, + IncrementVarSublevelsUp_walker, + (void *) context, + QTW_EXAMINE_RTES_BEFORE); + context->min_sublevels_up--; + return result; + } + return expression_tree_walker(node, IncrementVarSublevelsUp_walker, + (void *) context); +} + +void +IncrementVarSublevelsUp(Node *node, int delta_sublevels_up, + int min_sublevels_up) +{ + IncrementVarSublevelsUp_context context; + + context.delta_sublevels_up = delta_sublevels_up; + context.min_sublevels_up = min_sublevels_up; + + /* + * Must be prepared to start with a Query or a bare expression tree; if + * it's a Query, we don't want to increment sublevels_up. + */ + query_or_expression_tree_walker(node, + IncrementVarSublevelsUp_walker, + (void *) &context, + QTW_EXAMINE_RTES_BEFORE); +} + +/* + * IncrementVarSublevelsUp_rtable - + * Same as IncrementVarSublevelsUp, but to be invoked on a range table. + */ +void +IncrementVarSublevelsUp_rtable(List *rtable, int delta_sublevels_up, + int min_sublevels_up) +{ + IncrementVarSublevelsUp_context context; + + context.delta_sublevels_up = delta_sublevels_up; + context.min_sublevels_up = min_sublevels_up; + + range_table_walker(rtable, + IncrementVarSublevelsUp_walker, + (void *) &context, + QTW_EXAMINE_RTES_BEFORE); +} + + +/* + * rangeTableEntry_used - detect whether an RTE is referenced somewhere + * in var nodes or join or setOp trees of a query or expression. + */ + +typedef struct +{ + int rt_index; + int sublevels_up; +} rangeTableEntry_used_context; + +static bool +rangeTableEntry_used_walker(Node *node, + rangeTableEntry_used_context *context) +{ + if (node == NULL) + return false; + if (IsA(node, Var)) + { + Var *var = (Var *) node; + + if (var->varlevelsup == context->sublevels_up && + var->varno == context->rt_index) + return true; + return false; + } + if (IsA(node, CurrentOfExpr)) + { + CurrentOfExpr *cexpr = (CurrentOfExpr *) node; + + if (context->sublevels_up == 0 && + cexpr->cvarno == context->rt_index) + return true; + return false; + } + if (IsA(node, RangeTblRef)) + { + RangeTblRef *rtr = (RangeTblRef *) node; + + if (rtr->rtindex == context->rt_index && + context->sublevels_up == 0) + return true; + /* the subquery itself is visited separately */ + return false; + } + if (IsA(node, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) node; + + if (j->rtindex == context->rt_index && + context->sublevels_up == 0) + return true; + /* fall through to examine children */ + } + /* Shouldn't need to handle planner auxiliary nodes here */ + Assert(!IsA(node, PlaceHolderVar)); + Assert(!IsA(node, PlanRowMark)); + Assert(!IsA(node, SpecialJoinInfo)); + Assert(!IsA(node, AppendRelInfo)); + Assert(!IsA(node, PlaceHolderInfo)); + Assert(!IsA(node, MinMaxAggInfo)); + + if (IsA(node, Query)) + { + /* Recurse into subselects */ + bool result; + + context->sublevels_up++; + result = query_tree_walker((Query *) node, rangeTableEntry_used_walker, + (void *) context, 0); + context->sublevels_up--; + return result; + } + return expression_tree_walker(node, rangeTableEntry_used_walker, + (void *) context); +} + +bool +rangeTableEntry_used(Node *node, int rt_index, int sublevels_up) +{ + rangeTableEntry_used_context context; + + context.rt_index = rt_index; + context.sublevels_up = sublevels_up; + + /* + * Must be prepared to start with a Query or a bare expression tree; if + * it's a Query, we don't want to increment sublevels_up. + */ + return query_or_expression_tree_walker(node, + rangeTableEntry_used_walker, + (void *) &context, + 0); +} + + +/* + * If the given Query is an INSERT ... SELECT construct, extract and + * return the sub-Query node that represents the SELECT part. Otherwise + * return the given Query. + * + * If subquery_ptr is not NULL, then *subquery_ptr is set to the location + * of the link to the SELECT subquery inside parsetree, or NULL if not an + * INSERT ... SELECT. + * + * This is a hack needed because transformations on INSERT ... SELECTs that + * appear in rule actions should be applied to the source SELECT, not to the + * INSERT part. Perhaps this can be cleaned up with redesigned querytrees. + */ +Query * +getInsertSelectQuery(Query *parsetree, Query ***subquery_ptr) +{ + Query *selectquery; + RangeTblEntry *selectrte; + RangeTblRef *rtr; + + if (subquery_ptr) + *subquery_ptr = NULL; + + if (parsetree == NULL) + return parsetree; + if (parsetree->commandType != CMD_INSERT) + return parsetree; + + /* + * Currently, this is ONLY applied to rule-action queries, and so we + * expect to find the OLD and NEW placeholder entries in the given query. + * If they're not there, it must be an INSERT/SELECT in which they've been + * pushed down to the SELECT. + */ + if (list_length(parsetree->rtable) >= 2 && + strcmp(rt_fetch(PRS2_OLD_VARNO, parsetree->rtable)->eref->aliasname, + "old") == 0 && + strcmp(rt_fetch(PRS2_NEW_VARNO, parsetree->rtable)->eref->aliasname, + "new") == 0) + return parsetree; + Assert(parsetree->jointree && IsA(parsetree->jointree, FromExpr)); + if (list_length(parsetree->jointree->fromlist) != 1) + elog(ERROR, "expected to find SELECT subquery"); + rtr = (RangeTblRef *) linitial(parsetree->jointree->fromlist); + Assert(IsA(rtr, RangeTblRef)); + selectrte = rt_fetch(rtr->rtindex, parsetree->rtable); + selectquery = selectrte->subquery; + if (!(selectquery && IsA(selectquery, Query) && + selectquery->commandType == CMD_SELECT)) + elog(ERROR, "expected to find SELECT subquery"); + if (list_length(selectquery->rtable) >= 2 && + strcmp(rt_fetch(PRS2_OLD_VARNO, selectquery->rtable)->eref->aliasname, + "old") == 0 && + strcmp(rt_fetch(PRS2_NEW_VARNO, selectquery->rtable)->eref->aliasname, + "new") == 0) + { + if (subquery_ptr) + *subquery_ptr = &(selectrte->subquery); + return selectquery; + } + elog(ERROR, "could not find rule placeholders"); + return NULL; /* not reached */ +} + + +/* + * Add the given qualifier condition to the query's WHERE clause + */ +void +AddQual(Query *parsetree, Node *qual) +{ + Node *copy; + + if (qual == NULL) + return; + + if (parsetree->commandType == CMD_UTILITY) + { + /* + * There's noplace to put the qual on a utility statement. + * + * If it's a NOTIFY, silently ignore the qual; this means that the + * NOTIFY will execute, whether or not there are any qualifying rows. + * While clearly wrong, this is much more useful than refusing to + * execute the rule at all, and extra NOTIFY events are harmless for + * typical uses of NOTIFY. + * + * If it isn't a NOTIFY, error out, since unconditional execution of + * other utility stmts is unlikely to be wanted. (This case is not + * currently allowed anyway, but keep the test for safety.) + */ + if (parsetree->utilityStmt && IsA(parsetree->utilityStmt, NotifyStmt)) + return; + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("conditional utility statements are not implemented"))); + } + + if (parsetree->setOperations != NULL) + { + /* + * There's noplace to put the qual on a setop statement, either. (This + * could be fixed, but right now the planner simply ignores any qual + * condition on a setop query.) + */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("conditional UNION/INTERSECT/EXCEPT statements are not implemented"))); + } + + /* INTERSECT wants the original, but we need to copy - Jan */ + copy = copyObject(qual); + + parsetree->jointree->quals = make_and_qual(parsetree->jointree->quals, + copy); + + /* + * We had better not have stuck an aggregate into the WHERE clause. + */ + Assert(!contain_aggs_of_level(copy, 0)); + + /* + * Make sure query is marked correctly if added qual has sublinks. Need + * not search qual when query is already marked. + */ + if (!parsetree->hasSubLinks) + parsetree->hasSubLinks = checkExprHasSubLink(copy); +} + + +/* + * Invert the given clause and add it to the WHERE qualifications of the + * given querytree. Inversion means "x IS NOT TRUE", not just "NOT x", + * else we will do the wrong thing when x evaluates to NULL. + */ +void +AddInvertedQual(Query *parsetree, Node *qual) +{ + BooleanTest *invqual; + + if (qual == NULL) + return; + + /* Need not copy input qual, because AddQual will... */ + invqual = makeNode(BooleanTest); + invqual->arg = (Expr *) qual; + invqual->booltesttype = IS_NOT_TRUE; + invqual->location = -1; + + AddQual(parsetree, (Node *) invqual); +} + + +/* + * replace_rte_variables() finds all Vars in an expression tree + * that reference a particular RTE, and replaces them with substitute + * expressions obtained from a caller-supplied callback function. + * + * When invoking replace_rte_variables on a portion of a Query, pass the + * address of the containing Query's hasSubLinks field as outer_hasSubLinks. + * Otherwise, pass NULL, but inserting a SubLink into a non-Query expression + * will then cause an error. + * + * Note: the business with inserted_sublink is needed to update hasSubLinks + * in subqueries when the replacement adds a subquery inside a subquery. + * Messy, isn't it? We do not need to do similar pushups for hasAggs, + * because it isn't possible for this transformation to insert a level-zero + * aggregate reference into a subquery --- it could only insert outer aggs. + * Likewise for hasWindowFuncs. + * + * Note: usually, we'd not expose the mutator function or context struct + * for a function like this. We do so because callbacks often find it + * convenient to recurse directly to the mutator on sub-expressions of + * what they will return. + */ +Node * +replace_rte_variables(Node *node, int target_varno, int sublevels_up, + replace_rte_variables_callback callback, + void *callback_arg, + bool *outer_hasSubLinks) +{ + Node *result; + replace_rte_variables_context context; + + context.callback = callback; + context.callback_arg = callback_arg; + context.target_varno = target_varno; + context.sublevels_up = sublevels_up; + + /* + * We try to initialize inserted_sublink to true if there is no need to + * detect new sublinks because the query already has some. + */ + if (node && IsA(node, Query)) + context.inserted_sublink = ((Query *) node)->hasSubLinks; + else if (outer_hasSubLinks) + context.inserted_sublink = *outer_hasSubLinks; + else + context.inserted_sublink = false; + + /* + * Must be prepared to start with a Query or a bare expression tree; if + * it's a Query, we don't want to increment sublevels_up. + */ + result = query_or_expression_tree_mutator(node, + replace_rte_variables_mutator, + (void *) &context, + 0); + + if (context.inserted_sublink) + { + if (result && IsA(result, Query)) + ((Query *) result)->hasSubLinks = true; + else if (outer_hasSubLinks) + *outer_hasSubLinks = true; + else + elog(ERROR, "replace_rte_variables inserted a SubLink, but has noplace to record it"); + } + + return result; +} + +Node * +replace_rte_variables_mutator(Node *node, + replace_rte_variables_context *context) +{ + if (node == NULL) + return NULL; + if (IsA(node, Var)) + { + Var *var = (Var *) node; + + if (var->varno == context->target_varno && + var->varlevelsup == context->sublevels_up) + { + /* Found a matching variable, make the substitution */ + Node *newnode; + + newnode = context->callback(var, context); + /* Detect if we are adding a sublink to query */ + if (!context->inserted_sublink) + context->inserted_sublink = checkExprHasSubLink(newnode); + return newnode; + } + /* otherwise fall through to copy the var normally */ + } + else if (IsA(node, CurrentOfExpr)) + { + CurrentOfExpr *cexpr = (CurrentOfExpr *) node; + + if (cexpr->cvarno == context->target_varno && + context->sublevels_up == 0) + { + /* + * We get here if a WHERE CURRENT OF expression turns out to apply + * to a view. Someday we might be able to translate the + * expression to apply to an underlying table of the view, but + * right now it's not implemented. + */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("WHERE CURRENT OF on a view is not implemented"))); + } + /* otherwise fall through to copy the expr normally */ + } + else if (IsA(node, Query)) + { + /* Recurse into RTE subquery or not-yet-planned sublink subquery */ + Query *newnode; + bool save_inserted_sublink; + + context->sublevels_up++; + save_inserted_sublink = context->inserted_sublink; + context->inserted_sublink = ((Query *) node)->hasSubLinks; + newnode = query_tree_mutator((Query *) node, + replace_rte_variables_mutator, + (void *) context, + 0); + newnode->hasSubLinks |= context->inserted_sublink; + context->inserted_sublink = save_inserted_sublink; + context->sublevels_up--; + return (Node *) newnode; + } + return expression_tree_mutator(node, replace_rte_variables_mutator, + (void *) context); +} + + +/* + * map_variable_attnos() finds all user-column Vars in an expression tree + * that reference a particular RTE, and adjusts their varattnos according + * to the given mapping array (varattno n is replaced by attno_map[n-1]). + * Vars for system columns are not modified. + * + * A zero in the mapping array represents a dropped column, which should not + * appear in the expression. + * + * If the expression tree contains a whole-row Var for the target RTE, + * *found_whole_row is set to true. In addition, if to_rowtype is + * not InvalidOid, we replace the Var with a Var of that vartype, inserting + * a ConvertRowtypeExpr to map back to the rowtype expected by the expression. + * (Therefore, to_rowtype had better be a child rowtype of the rowtype of the + * RTE we're changing references to.) Callers that don't provide to_rowtype + * should report an error if *found_whole_row is true; we don't do that here + * because we don't know exactly what wording for the error message would + * be most appropriate. The caller will be aware of the context. + * + * This could be built using replace_rte_variables and a callback function, + * but since we don't ever need to insert sublinks, replace_rte_variables is + * overly complicated. + */ + +typedef struct +{ + int target_varno; /* RTE index to search for */ + int sublevels_up; /* (current) nesting depth */ + const AttrMap *attno_map; /* map array for user attnos */ + Oid to_rowtype; /* change whole-row Vars to this type */ + bool *found_whole_row; /* output flag */ +} map_variable_attnos_context; + +static Node * +map_variable_attnos_mutator(Node *node, + map_variable_attnos_context *context) +{ + if (node == NULL) + return NULL; + if (IsA(node, Var)) + { + Var *var = (Var *) node; + + if (var->varno == context->target_varno && + var->varlevelsup == context->sublevels_up) + { + /* Found a matching variable, make the substitution */ + Var *newvar = (Var *) palloc(sizeof(Var)); + int attno = var->varattno; + + *newvar = *var; /* initially copy all fields of the Var */ + + if (attno > 0) + { + /* user-defined column, replace attno */ + if (attno > context->attno_map->maplen || + context->attno_map->attnums[attno - 1] == 0) + elog(ERROR, "unexpected varattno %d in expression to be mapped", + attno); + newvar->varattno = context->attno_map->attnums[attno - 1]; + /* If the syntactic referent is same RTE, fix it too */ + if (newvar->varnosyn == context->target_varno) + newvar->varattnosyn = newvar->varattno; + } + else if (attno == 0) + { + /* whole-row variable, warn caller */ + *(context->found_whole_row) = true; + + /* If the caller expects us to convert the Var, do so. */ + if (OidIsValid(context->to_rowtype) && + context->to_rowtype != var->vartype) + { + ConvertRowtypeExpr *r; + + /* This certainly won't work for a RECORD variable. */ + Assert(var->vartype != RECORDOID); + + /* Var itself is changed to the requested type. */ + newvar->vartype = context->to_rowtype; + + /* + * Add a conversion node on top to convert back to the + * original type expected by the expression. + */ + r = makeNode(ConvertRowtypeExpr); + r->arg = (Expr *) newvar; + r->resulttype = var->vartype; + r->convertformat = COERCE_IMPLICIT_CAST; + r->location = -1; + + return (Node *) r; + } + } + return (Node *) newvar; + } + /* otherwise fall through to copy the var normally */ + } + else if (IsA(node, ConvertRowtypeExpr)) + { + ConvertRowtypeExpr *r = (ConvertRowtypeExpr *) node; + Var *var = (Var *) r->arg; + + /* + * If this is coercing a whole-row Var that we need to convert, then + * just convert the Var without adding an extra ConvertRowtypeExpr. + * Effectively we're simplifying var::parenttype::grandparenttype into + * just var::grandparenttype. This avoids building stacks of CREs if + * this function is applied repeatedly. + */ + if (IsA(var, Var) && + var->varno == context->target_varno && + var->varlevelsup == context->sublevels_up && + var->varattno == 0 && + OidIsValid(context->to_rowtype) && + context->to_rowtype != var->vartype) + { + ConvertRowtypeExpr *newnode; + Var *newvar = (Var *) palloc(sizeof(Var)); + + /* whole-row variable, warn caller */ + *(context->found_whole_row) = true; + + *newvar = *var; /* initially copy all fields of the Var */ + + /* This certainly won't work for a RECORD variable. */ + Assert(var->vartype != RECORDOID); + + /* Var itself is changed to the requested type. */ + newvar->vartype = context->to_rowtype; + + newnode = (ConvertRowtypeExpr *) palloc(sizeof(ConvertRowtypeExpr)); + *newnode = *r; /* initially copy all fields of the CRE */ + newnode->arg = (Expr *) newvar; + + return (Node *) newnode; + } + /* otherwise fall through to process the expression normally */ + } + else if (IsA(node, Query)) + { + /* Recurse into RTE subquery or not-yet-planned sublink subquery */ + Query *newnode; + + context->sublevels_up++; + newnode = query_tree_mutator((Query *) node, + map_variable_attnos_mutator, + (void *) context, + 0); + context->sublevels_up--; + return (Node *) newnode; + } + return expression_tree_mutator(node, map_variable_attnos_mutator, + (void *) context); +} + +Node * +map_variable_attnos(Node *node, + int target_varno, int sublevels_up, + const AttrMap *attno_map, + Oid to_rowtype, bool *found_whole_row) +{ + map_variable_attnos_context context; + + context.target_varno = target_varno; + context.sublevels_up = sublevels_up; + context.attno_map = attno_map; + context.to_rowtype = to_rowtype; + context.found_whole_row = found_whole_row; + + *found_whole_row = false; + + /* + * Must be prepared to start with a Query or a bare expression tree; if + * it's a Query, we don't want to increment sublevels_up. + */ + return query_or_expression_tree_mutator(node, + map_variable_attnos_mutator, + (void *) &context, + 0); +} + + +/* + * ReplaceVarsFromTargetList - replace Vars with items from a targetlist + * + * Vars matching target_varno and sublevels_up are replaced by the + * entry with matching resno from targetlist, if there is one. + * + * If there is no matching resno for such a Var, the action depends on the + * nomatch_option: + * REPLACEVARS_REPORT_ERROR: throw an error + * REPLACEVARS_CHANGE_VARNO: change Var's varno to nomatch_varno + * REPLACEVARS_SUBSTITUTE_NULL: replace Var with a NULL Const of same type + * + * The caller must also provide target_rte, the RTE describing the target + * relation. This is needed to handle whole-row Vars referencing the target. + * We expand such Vars into RowExpr constructs. + * + * outer_hasSubLinks works the same as for replace_rte_variables(). + */ + +typedef struct +{ + RangeTblEntry *target_rte; + List *targetlist; + ReplaceVarsNoMatchOption nomatch_option; + int nomatch_varno; +} ReplaceVarsFromTargetList_context; + +static Node * +ReplaceVarsFromTargetList_callback(Var *var, + replace_rte_variables_context *context) +{ + ReplaceVarsFromTargetList_context *rcon = (ReplaceVarsFromTargetList_context *) context->callback_arg; + TargetEntry *tle; + + if (var->varattno == InvalidAttrNumber) + { + /* Must expand whole-tuple reference into RowExpr */ + RowExpr *rowexpr; + List *colnames; + List *fields; + + /* + * If generating an expansion for a var of a named rowtype (ie, this + * is a plain relation RTE), then we must include dummy items for + * dropped columns. If the var is RECORD (ie, this is a JOIN), then + * omit dropped columns. Either way, attach column names to the + * RowExpr for use of ruleutils.c. + */ + expandRTE(rcon->target_rte, + var->varno, var->varlevelsup, var->location, + (var->vartype != RECORDOID), + &colnames, &fields); + /* Adjust the generated per-field Vars... */ + fields = (List *) replace_rte_variables_mutator((Node *) fields, + context); + rowexpr = makeNode(RowExpr); + rowexpr->args = fields; + rowexpr->row_typeid = var->vartype; + rowexpr->row_format = COERCE_IMPLICIT_CAST; + rowexpr->colnames = colnames; + rowexpr->location = var->location; + + return (Node *) rowexpr; + } + + /* Normal case referencing one targetlist element */ + tle = get_tle_by_resno(rcon->targetlist, var->varattno); + + if (tle == NULL || tle->resjunk) + { + /* Failed to find column in targetlist */ + switch (rcon->nomatch_option) + { + case REPLACEVARS_REPORT_ERROR: + /* fall through, throw error below */ + break; + + case REPLACEVARS_CHANGE_VARNO: + var = (Var *) copyObject(var); + var->varno = rcon->nomatch_varno; + /* we leave the syntactic referent alone */ + return (Node *) var; + + case REPLACEVARS_SUBSTITUTE_NULL: + + /* + * If Var is of domain type, we should add a CoerceToDomain + * node, in case there is a NOT NULL domain constraint. + */ + return coerce_to_domain((Node *) makeNullConst(var->vartype, + var->vartypmod, + var->varcollid), + InvalidOid, -1, + var->vartype, + COERCION_IMPLICIT, + COERCE_IMPLICIT_CAST, + -1, + false); + } + elog(ERROR, "could not find replacement targetlist entry for attno %d", + var->varattno); + return NULL; /* keep compiler quiet */ + } + else + { + /* Make a copy of the tlist item to return */ + Expr *newnode = copyObject(tle->expr); + + /* Must adjust varlevelsup if tlist item is from higher query */ + if (var->varlevelsup > 0) + IncrementVarSublevelsUp((Node *) newnode, var->varlevelsup, 0); + + /* + * Check to see if the tlist item contains a PARAM_MULTIEXPR Param, + * and throw error if so. This case could only happen when expanding + * an ON UPDATE rule's NEW variable and the referenced tlist item in + * the original UPDATE command is part of a multiple assignment. There + * seems no practical way to handle such cases without multiple + * evaluation of the multiple assignment's sub-select, which would + * create semantic oddities that users of rules would probably prefer + * not to cope with. So treat it as an unimplemented feature. + */ + if (contains_multiexpr_param((Node *) newnode, NULL)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("NEW variables in ON UPDATE rules cannot reference columns that are part of a multiple assignment in the subject UPDATE command"))); + + return (Node *) newnode; + } +} + +Node * +ReplaceVarsFromTargetList(Node *node, + int target_varno, int sublevels_up, + RangeTblEntry *target_rte, + List *targetlist, + ReplaceVarsNoMatchOption nomatch_option, + int nomatch_varno, + bool *outer_hasSubLinks) +{ + ReplaceVarsFromTargetList_context context; + + context.target_rte = target_rte; + context.targetlist = targetlist; + context.nomatch_option = nomatch_option; + context.nomatch_varno = nomatch_varno; + + return replace_rte_variables(node, target_varno, sublevels_up, + ReplaceVarsFromTargetList_callback, + (void *) &context, + outer_hasSubLinks); +} diff --git a/src/backend/rewrite/rewriteRemove.c b/src/backend/rewrite/rewriteRemove.c new file mode 100644 index 0000000..a48b15e --- /dev/null +++ b/src/backend/rewrite/rewriteRemove.c @@ -0,0 +1,100 @@ +/*------------------------------------------------------------------------- + * + * rewriteRemove.c + * routines for removing rewrite rules + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/rewrite/rewriteRemove.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "access/table.h" +#include "catalog/catalog.h" +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/pg_rewrite.h" +#include "miscadmin.h" +#include "rewrite/rewriteRemove.h" +#include "utils/acl.h" +#include "utils/fmgroids.h" +#include "utils/inval.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/syscache.h" + +/* + * Guts of rule deletion. + */ +void +RemoveRewriteRuleById(Oid ruleOid) +{ + Relation RewriteRelation; + ScanKeyData skey[1]; + SysScanDesc rcscan; + Relation event_relation; + HeapTuple tuple; + Oid eventRelationOid; + + /* + * Open the pg_rewrite relation. + */ + RewriteRelation = table_open(RewriteRelationId, RowExclusiveLock); + + /* + * Find the tuple for the target rule. + */ + ScanKeyInit(&skey[0], + Anum_pg_rewrite_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(ruleOid)); + + rcscan = systable_beginscan(RewriteRelation, RewriteOidIndexId, true, + NULL, 1, skey); + + tuple = systable_getnext(rcscan); + + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for rule %u", ruleOid); + + /* + * We had better grab AccessExclusiveLock to ensure that no queries are + * going on that might depend on this rule. (Note: a weaker lock would + * suffice if it's not an ON SELECT rule.) + */ + eventRelationOid = ((Form_pg_rewrite) GETSTRUCT(tuple))->ev_class; + event_relation = table_open(eventRelationOid, AccessExclusiveLock); + + if (!allowSystemTableMods && IsSystemRelation(event_relation)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied: \"%s\" is a system catalog", + RelationGetRelationName(event_relation)))); + + /* + * Now delete the pg_rewrite tuple for the rule + */ + CatalogTupleDelete(RewriteRelation, &tuple->t_self); + + systable_endscan(rcscan); + + table_close(RewriteRelation, RowExclusiveLock); + + /* + * Issue shared-inval notice to force all backends (including me!) to + * update relcache entries with the new rule set. + */ + CacheInvalidateRelcache(event_relation); + + /* Close rel, but keep lock till commit... */ + table_close(event_relation, NoLock); +} diff --git a/src/backend/rewrite/rewriteSearchCycle.c b/src/backend/rewrite/rewriteSearchCycle.c new file mode 100644 index 0000000..175f33c --- /dev/null +++ b/src/backend/rewrite/rewriteSearchCycle.c @@ -0,0 +1,681 @@ +/*------------------------------------------------------------------------- + * + * rewriteSearchCycle.c + * Support for rewriting SEARCH and CYCLE clauses. + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/rewrite/rewriteSearchCycle.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/pg_operator_d.h" +#include "catalog/pg_type_d.h" +#include "nodes/makefuncs.h" +#include "nodes/pg_list.h" +#include "nodes/parsenodes.h" +#include "nodes/primnodes.h" +#include "parser/analyze.h" +#include "parser/parsetree.h" +#include "rewrite/rewriteManip.h" +#include "rewrite/rewriteSearchCycle.h" +#include "utils/fmgroids.h" + + +/*---------- + * Rewrite a CTE with SEARCH or CYCLE clause + * + * Consider a CTE like + * + * WITH RECURSIVE ctename (col1, col2, col3) AS ( + * query1 + * UNION [ALL] + * SELECT trosl FROM ctename + * ) + * + * With a search clause + * + * SEARCH BREADTH FIRST BY col1, col2 SET sqc + * + * the CTE is rewritten to + * + * WITH RECURSIVE ctename (col1, col2, col3, sqc) AS ( + * SELECT col1, col2, col3, -- original WITH column list + * ROW(0, col1, col2) -- initial row of search columns + * FROM (query1) "*TLOCRN*" (col1, col2, col3) + * UNION [ALL] + * SELECT col1, col2, col3, -- same as above + * ROW(sqc.depth + 1, col1, col2) -- count depth + * FROM (SELECT trosl, ctename.sqc FROM ctename) "*TROCRN*" (col1, col2, col3, sqc) + * ) + * + * (This isn't quite legal SQL: sqc.depth is meant to refer to the first + * column of sqc, which has a row type, but the field names are not defined + * here. Representing this properly in SQL would be more complicated (and the + * SQL standard actually does it in that more complicated way), but the + * internal representation allows us to construct it this way.) + * + * With a search clause + * + * SEARCH DEPTH FIRST BY col1, col2 SET sqc + * + * the CTE is rewritten to + * + * WITH RECURSIVE ctename (col1, col2, col3, sqc) AS ( + * SELECT col1, col2, col3, -- original WITH column list + * ARRAY[ROW(col1, col2)] -- initial row of search columns + * FROM (query1) "*TLOCRN*" (col1, col2, col3) + * UNION [ALL] + * SELECT col1, col2, col3, -- same as above + * sqc || ARRAY[ROW(col1, col2)] -- record rows seen + * FROM (SELECT trosl, ctename.sqc FROM ctename) "*TROCRN*" (col1, col2, col3, sqc) + * ) + * + * With a cycle clause + * + * CYCLE col1, col2 SET cmc TO 'Y' DEFAULT 'N' USING cpa + * + * (cmc = cycle mark column, cpa = cycle path) the CTE is rewritten to + * + * WITH RECURSIVE ctename (col1, col2, col3, cmc, cpa) AS ( + * SELECT col1, col2, col3, -- original WITH column list + * 'N', -- cycle mark default + * ARRAY[ROW(col1, col2)] -- initial row of cycle columns + * FROM (query1) "*TLOCRN*" (col1, col2, col3) + * UNION [ALL] + * SELECT col1, col2, col3, -- same as above + * CASE WHEN ROW(col1, col2) = ANY (ARRAY[cpa]) THEN 'Y' ELSE 'N' END, -- compute cycle mark column + * cpa || ARRAY[ROW(col1, col2)] -- record rows seen + * FROM (SELECT trosl, ctename.cmc, ctename.cpa FROM ctename) "*TROCRN*" (col1, col2, col3, cmc, cpa) + * WHERE cmc <> 'Y' + * ) + * + * The expression to compute the cycle mark column in the right-hand query is + * written as + * + * CASE WHEN ROW(col1, col2) IN (SELECT p.* FROM TABLE(cpa) p) THEN cmv ELSE cmd END + * + * in the SQL standard, but in PostgreSQL we can use the scalar-array operator + * expression shown above. + * + * Also, in some of the cases where operators are shown above we actually + * directly produce the underlying function call. + * + * If both a search clause and a cycle clause is specified, then the search + * clause column is added before the cycle clause columns. + */ + +/* + * Make a RowExpr from the specified column names, which have to be among the + * output columns of the CTE. + */ +static RowExpr * +make_path_rowexpr(const CommonTableExpr *cte, const List *col_list) +{ + RowExpr *rowexpr; + ListCell *lc; + + rowexpr = makeNode(RowExpr); + rowexpr->row_typeid = RECORDOID; + rowexpr->row_format = COERCE_IMPLICIT_CAST; + rowexpr->location = -1; + + foreach(lc, col_list) + { + char *colname = strVal(lfirst(lc)); + + for (int i = 0; i < list_length(cte->ctecolnames); i++) + { + char *colname2 = strVal(list_nth(cte->ctecolnames, i)); + + if (strcmp(colname, colname2) == 0) + { + Var *var; + + var = makeVar(1, i + 1, + list_nth_oid(cte->ctecoltypes, i), + list_nth_int(cte->ctecoltypmods, i), + list_nth_oid(cte->ctecolcollations, i), + 0); + rowexpr->args = lappend(rowexpr->args, var); + rowexpr->colnames = lappend(rowexpr->colnames, makeString(colname)); + break; + } + } + } + + return rowexpr; +} + +/* + * Wrap a RowExpr in an ArrayExpr, for the initial search depth first or cycle + * row. + */ +static Expr * +make_path_initial_array(RowExpr *rowexpr) +{ + ArrayExpr *arr; + + arr = makeNode(ArrayExpr); + arr->array_typeid = RECORDARRAYOID; + arr->element_typeid = RECORDOID; + arr->location = -1; + arr->elements = list_make1(rowexpr); + + return (Expr *) arr; +} + +/* + * Make an array catenation expression like + * + * cpa || ARRAY[ROW(cols)] + * + * where the varattno of cpa is provided as path_varattno. + */ +static Expr * +make_path_cat_expr(RowExpr *rowexpr, AttrNumber path_varattno) +{ + ArrayExpr *arr; + FuncExpr *fexpr; + + arr = makeNode(ArrayExpr); + arr->array_typeid = RECORDARRAYOID; + arr->element_typeid = RECORDOID; + arr->location = -1; + arr->elements = list_make1(rowexpr); + + fexpr = makeFuncExpr(F_ARRAY_CAT, RECORDARRAYOID, + list_make2(makeVar(1, path_varattno, RECORDARRAYOID, -1, 0, 0), + arr), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + + return (Expr *) fexpr; +} + +/* + * The real work happens here. + */ +CommonTableExpr * +rewriteSearchAndCycle(CommonTableExpr *cte) +{ + Query *ctequery; + SetOperationStmt *sos; + int rti1, + rti2; + RangeTblEntry *rte1, + *rte2, + *newrte; + Query *newq1, + *newq2; + Query *newsubquery; + RangeTblRef *rtr; + Oid search_seq_type = InvalidOid; + AttrNumber sqc_attno = InvalidAttrNumber; + AttrNumber cmc_attno = InvalidAttrNumber; + AttrNumber cpa_attno = InvalidAttrNumber; + TargetEntry *tle; + RowExpr *cycle_col_rowexpr = NULL; + RowExpr *search_col_rowexpr = NULL; + List *ewcl; + int cte_rtindex = -1; + + Assert(cte->search_clause || cte->cycle_clause); + + cte = copyObject(cte); + + ctequery = castNode(Query, cte->ctequery); + + /* + * The top level of the CTE's query should be a UNION. Find the two + * subqueries. + */ + Assert(ctequery->setOperations); + sos = castNode(SetOperationStmt, ctequery->setOperations); + Assert(sos->op == SETOP_UNION); + + rti1 = castNode(RangeTblRef, sos->larg)->rtindex; + rti2 = castNode(RangeTblRef, sos->rarg)->rtindex; + + rte1 = rt_fetch(rti1, ctequery->rtable); + rte2 = rt_fetch(rti2, ctequery->rtable); + + Assert(rte1->rtekind == RTE_SUBQUERY); + Assert(rte2->rtekind == RTE_SUBQUERY); + + /* + * We'll need this a few times later. + */ + if (cte->search_clause) + { + if (cte->search_clause->search_breadth_first) + search_seq_type = RECORDOID; + else + search_seq_type = RECORDARRAYOID; + } + + /* + * Attribute numbers of the added columns in the CTE's column list + */ + if (cte->search_clause) + sqc_attno = list_length(cte->ctecolnames) + 1; + if (cte->cycle_clause) + { + cmc_attno = list_length(cte->ctecolnames) + 1; + cpa_attno = list_length(cte->ctecolnames) + 2; + if (cte->search_clause) + { + cmc_attno++; + cpa_attno++; + } + } + + /* + * Make new left subquery + */ + newq1 = makeNode(Query); + newq1->commandType = CMD_SELECT; + newq1->canSetTag = true; + + newrte = makeNode(RangeTblEntry); + newrte->rtekind = RTE_SUBQUERY; + newrte->alias = makeAlias("*TLOCRN*", cte->ctecolnames); + newrte->eref = newrte->alias; + newsubquery = copyObject(rte1->subquery); + IncrementVarSublevelsUp((Node *) newsubquery, 1, 1); + newrte->subquery = newsubquery; + newrte->inFromCl = true; + newq1->rtable = list_make1(newrte); + + rtr = makeNode(RangeTblRef); + rtr->rtindex = 1; + newq1->jointree = makeFromExpr(list_make1(rtr), NULL); + + /* + * Make target list + */ + for (int i = 0; i < list_length(cte->ctecolnames); i++) + { + Var *var; + + var = makeVar(1, i + 1, + list_nth_oid(cte->ctecoltypes, i), + list_nth_int(cte->ctecoltypmods, i), + list_nth_oid(cte->ctecolcollations, i), + 0); + tle = makeTargetEntry((Expr *) var, i + 1, strVal(list_nth(cte->ctecolnames, i)), false); + tle->resorigtbl = castNode(TargetEntry, list_nth(rte1->subquery->targetList, i))->resorigtbl; + tle->resorigcol = castNode(TargetEntry, list_nth(rte1->subquery->targetList, i))->resorigcol; + newq1->targetList = lappend(newq1->targetList, tle); + } + + if (cte->search_clause) + { + Expr *texpr; + + search_col_rowexpr = make_path_rowexpr(cte, cte->search_clause->search_col_list); + if (cte->search_clause->search_breadth_first) + { + search_col_rowexpr->args = lcons(makeConst(INT8OID, -1, InvalidOid, sizeof(int64), + Int64GetDatum(0), false, FLOAT8PASSBYVAL), + search_col_rowexpr->args); + search_col_rowexpr->colnames = lcons(makeString("*DEPTH*"), search_col_rowexpr->colnames); + texpr = (Expr *) search_col_rowexpr; + } + else + texpr = make_path_initial_array(search_col_rowexpr); + tle = makeTargetEntry(texpr, + list_length(newq1->targetList) + 1, + cte->search_clause->search_seq_column, + false); + newq1->targetList = lappend(newq1->targetList, tle); + } + if (cte->cycle_clause) + { + tle = makeTargetEntry((Expr *) cte->cycle_clause->cycle_mark_default, + list_length(newq1->targetList) + 1, + cte->cycle_clause->cycle_mark_column, + false); + newq1->targetList = lappend(newq1->targetList, tle); + cycle_col_rowexpr = make_path_rowexpr(cte, cte->cycle_clause->cycle_col_list); + tle = makeTargetEntry(make_path_initial_array(cycle_col_rowexpr), + list_length(newq1->targetList) + 1, + cte->cycle_clause->cycle_path_column, + false); + newq1->targetList = lappend(newq1->targetList, tle); + } + + rte1->subquery = newq1; + + if (cte->search_clause) + { + rte1->eref->colnames = lappend(rte1->eref->colnames, makeString(cte->search_clause->search_seq_column)); + } + if (cte->cycle_clause) + { + rte1->eref->colnames = lappend(rte1->eref->colnames, makeString(cte->cycle_clause->cycle_mark_column)); + rte1->eref->colnames = lappend(rte1->eref->colnames, makeString(cte->cycle_clause->cycle_path_column)); + } + + /* + * Make new right subquery + */ + newq2 = makeNode(Query); + newq2->commandType = CMD_SELECT; + newq2->canSetTag = true; + + newrte = makeNode(RangeTblEntry); + newrte->rtekind = RTE_SUBQUERY; + ewcl = copyObject(cte->ctecolnames); + if (cte->search_clause) + { + ewcl = lappend(ewcl, makeString(cte->search_clause->search_seq_column)); + } + if (cte->cycle_clause) + { + ewcl = lappend(ewcl, makeString(cte->cycle_clause->cycle_mark_column)); + ewcl = lappend(ewcl, makeString(cte->cycle_clause->cycle_path_column)); + } + newrte->alias = makeAlias("*TROCRN*", ewcl); + newrte->eref = newrte->alias; + + /* + * Find the reference to the recursive CTE in the right UNION subquery's + * range table. We expect it to be two levels up from the UNION subquery + * (and must check that to avoid being fooled by sub-WITHs with the same + * CTE name). There will not be more than one such reference, because the + * parser would have rejected that (see checkWellFormedRecursion() in + * parse_cte.c). However, the parser doesn't insist that the reference + * appear in the UNION subquery's topmost range table, so we might fail to + * find it at all. That's an unimplemented case for the moment. + */ + for (int rti = 1; rti <= list_length(rte2->subquery->rtable); rti++) + { + RangeTblEntry *e = rt_fetch(rti, rte2->subquery->rtable); + + if (e->rtekind == RTE_CTE && + strcmp(cte->ctename, e->ctename) == 0 && + e->ctelevelsup == 2) + { + cte_rtindex = rti; + break; + } + } + if (cte_rtindex <= 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("with a SEARCH or CYCLE clause, the recursive reference to WITH query \"%s\" must be at the top level of its right-hand SELECT", + cte->ctename))); + + newsubquery = copyObject(rte2->subquery); + IncrementVarSublevelsUp((Node *) newsubquery, 1, 1); + + /* + * Add extra columns to target list of subquery of right subquery + */ + if (cte->search_clause) + { + Var *var; + + /* ctename.sqc */ + var = makeVar(cte_rtindex, sqc_attno, + search_seq_type, -1, InvalidOid, 0); + tle = makeTargetEntry((Expr *) var, + list_length(newsubquery->targetList) + 1, + cte->search_clause->search_seq_column, + false); + newsubquery->targetList = lappend(newsubquery->targetList, tle); + } + if (cte->cycle_clause) + { + Var *var; + + /* ctename.cmc */ + var = makeVar(cte_rtindex, cmc_attno, + cte->cycle_clause->cycle_mark_type, + cte->cycle_clause->cycle_mark_typmod, + cte->cycle_clause->cycle_mark_collation, 0); + tle = makeTargetEntry((Expr *) var, + list_length(newsubquery->targetList) + 1, + cte->cycle_clause->cycle_mark_column, + false); + newsubquery->targetList = lappend(newsubquery->targetList, tle); + + /* ctename.cpa */ + var = makeVar(cte_rtindex, cpa_attno, + RECORDARRAYOID, -1, InvalidOid, 0); + tle = makeTargetEntry((Expr *) var, + list_length(newsubquery->targetList) + 1, + cte->cycle_clause->cycle_path_column, + false); + newsubquery->targetList = lappend(newsubquery->targetList, tle); + } + + newrte->subquery = newsubquery; + newrte->inFromCl = true; + newq2->rtable = list_make1(newrte); + + rtr = makeNode(RangeTblRef); + rtr->rtindex = 1; + + if (cte->cycle_clause) + { + Expr *expr; + + /* + * Add cmc <> cmv condition + */ + expr = make_opclause(cte->cycle_clause->cycle_mark_neop, BOOLOID, false, + (Expr *) makeVar(1, cmc_attno, + cte->cycle_clause->cycle_mark_type, + cte->cycle_clause->cycle_mark_typmod, + cte->cycle_clause->cycle_mark_collation, 0), + (Expr *) cte->cycle_clause->cycle_mark_value, + InvalidOid, + cte->cycle_clause->cycle_mark_collation); + + newq2->jointree = makeFromExpr(list_make1(rtr), (Node *) expr); + } + else + newq2->jointree = makeFromExpr(list_make1(rtr), NULL); + + /* + * Make target list + */ + for (int i = 0; i < list_length(cte->ctecolnames); i++) + { + Var *var; + + var = makeVar(1, i + 1, + list_nth_oid(cte->ctecoltypes, i), + list_nth_int(cte->ctecoltypmods, i), + list_nth_oid(cte->ctecolcollations, i), + 0); + tle = makeTargetEntry((Expr *) var, i + 1, strVal(list_nth(cte->ctecolnames, i)), false); + tle->resorigtbl = castNode(TargetEntry, list_nth(rte2->subquery->targetList, i))->resorigtbl; + tle->resorigcol = castNode(TargetEntry, list_nth(rte2->subquery->targetList, i))->resorigcol; + newq2->targetList = lappend(newq2->targetList, tle); + } + + if (cte->search_clause) + { + Expr *texpr; + + if (cte->search_clause->search_breadth_first) + { + FieldSelect *fs; + FuncExpr *fexpr; + + /* + * ROW(sqc.depth + 1, cols) + */ + + search_col_rowexpr = copyObject(search_col_rowexpr); + + fs = makeNode(FieldSelect); + fs->arg = (Expr *) makeVar(1, sqc_attno, RECORDOID, -1, 0, 0); + fs->fieldnum = 1; + fs->resulttype = INT8OID; + fs->resulttypmod = -1; + + fexpr = makeFuncExpr(F_INT8INC, INT8OID, list_make1(fs), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + + lfirst(list_head(search_col_rowexpr->args)) = fexpr; + + texpr = (Expr *) search_col_rowexpr; + } + else + { + /* + * sqc || ARRAY[ROW(cols)] + */ + texpr = make_path_cat_expr(search_col_rowexpr, sqc_attno); + } + tle = makeTargetEntry(texpr, + list_length(newq2->targetList) + 1, + cte->search_clause->search_seq_column, + false); + newq2->targetList = lappend(newq2->targetList, tle); + } + + if (cte->cycle_clause) + { + ScalarArrayOpExpr *saoe; + CaseExpr *caseexpr; + CaseWhen *casewhen; + + /* + * CASE WHEN ROW(cols) = ANY (ARRAY[cpa]) THEN cmv ELSE cmd END + */ + + saoe = makeNode(ScalarArrayOpExpr); + saoe->location = -1; + saoe->opno = RECORD_EQ_OP; + saoe->useOr = true; + saoe->args = list_make2(cycle_col_rowexpr, + makeVar(1, cpa_attno, RECORDARRAYOID, -1, 0, 0)); + + caseexpr = makeNode(CaseExpr); + caseexpr->location = -1; + caseexpr->casetype = cte->cycle_clause->cycle_mark_type; + caseexpr->casecollid = cte->cycle_clause->cycle_mark_collation; + casewhen = makeNode(CaseWhen); + casewhen->location = -1; + casewhen->expr = (Expr *) saoe; + casewhen->result = (Expr *) cte->cycle_clause->cycle_mark_value; + caseexpr->args = list_make1(casewhen); + caseexpr->defresult = (Expr *) cte->cycle_clause->cycle_mark_default; + + tle = makeTargetEntry((Expr *) caseexpr, + list_length(newq2->targetList) + 1, + cte->cycle_clause->cycle_mark_column, + false); + newq2->targetList = lappend(newq2->targetList, tle); + + /* + * cpa || ARRAY[ROW(cols)] + */ + tle = makeTargetEntry(make_path_cat_expr(cycle_col_rowexpr, cpa_attno), + list_length(newq2->targetList) + 1, + cte->cycle_clause->cycle_path_column, + false); + newq2->targetList = lappend(newq2->targetList, tle); + } + + rte2->subquery = newq2; + + if (cte->search_clause) + { + rte2->eref->colnames = lappend(rte2->eref->colnames, makeString(cte->search_clause->search_seq_column)); + } + if (cte->cycle_clause) + { + rte2->eref->colnames = lappend(rte2->eref->colnames, makeString(cte->cycle_clause->cycle_mark_column)); + rte2->eref->colnames = lappend(rte2->eref->colnames, makeString(cte->cycle_clause->cycle_path_column)); + } + + /* + * Add the additional columns to the SetOperationStmt + */ + if (cte->search_clause) + { + sos->colTypes = lappend_oid(sos->colTypes, search_seq_type); + sos->colTypmods = lappend_int(sos->colTypmods, -1); + sos->colCollations = lappend_oid(sos->colCollations, InvalidOid); + if (!sos->all) + sos->groupClauses = lappend(sos->groupClauses, + makeSortGroupClauseForSetOp(search_seq_type, true)); + } + if (cte->cycle_clause) + { + sos->colTypes = lappend_oid(sos->colTypes, cte->cycle_clause->cycle_mark_type); + sos->colTypmods = lappend_int(sos->colTypmods, cte->cycle_clause->cycle_mark_typmod); + sos->colCollations = lappend_oid(sos->colCollations, cte->cycle_clause->cycle_mark_collation); + if (!sos->all) + sos->groupClauses = lappend(sos->groupClauses, + makeSortGroupClauseForSetOp(cte->cycle_clause->cycle_mark_type, true)); + + sos->colTypes = lappend_oid(sos->colTypes, RECORDARRAYOID); + sos->colTypmods = lappend_int(sos->colTypmods, -1); + sos->colCollations = lappend_oid(sos->colCollations, InvalidOid); + if (!sos->all) + sos->groupClauses = lappend(sos->groupClauses, + makeSortGroupClauseForSetOp(RECORDARRAYOID, true)); + } + + /* + * Add the additional columns to the CTE query's target list + */ + if (cte->search_clause) + { + ctequery->targetList = lappend(ctequery->targetList, + makeTargetEntry((Expr *) makeVar(1, sqc_attno, + search_seq_type, -1, InvalidOid, 0), + list_length(ctequery->targetList) + 1, + cte->search_clause->search_seq_column, + false)); + } + if (cte->cycle_clause) + { + ctequery->targetList = lappend(ctequery->targetList, + makeTargetEntry((Expr *) makeVar(1, cmc_attno, + cte->cycle_clause->cycle_mark_type, + cte->cycle_clause->cycle_mark_typmod, + cte->cycle_clause->cycle_mark_collation, 0), + list_length(ctequery->targetList) + 1, + cte->cycle_clause->cycle_mark_column, + false)); + ctequery->targetList = lappend(ctequery->targetList, + makeTargetEntry((Expr *) makeVar(1, cpa_attno, + RECORDARRAYOID, -1, InvalidOid, 0), + list_length(ctequery->targetList) + 1, + cte->cycle_clause->cycle_path_column, + false)); + } + + /* + * Add the additional columns to the CTE's output columns + */ + cte->ctecolnames = ewcl; + if (cte->search_clause) + { + cte->ctecoltypes = lappend_oid(cte->ctecoltypes, search_seq_type); + cte->ctecoltypmods = lappend_int(cte->ctecoltypmods, -1); + cte->ctecolcollations = lappend_oid(cte->ctecolcollations, InvalidOid); + } + if (cte->cycle_clause) + { + cte->ctecoltypes = lappend_oid(cte->ctecoltypes, cte->cycle_clause->cycle_mark_type); + cte->ctecoltypmods = lappend_int(cte->ctecoltypmods, cte->cycle_clause->cycle_mark_typmod); + cte->ctecolcollations = lappend_oid(cte->ctecolcollations, cte->cycle_clause->cycle_mark_collation); + + cte->ctecoltypes = lappend_oid(cte->ctecoltypes, RECORDARRAYOID); + cte->ctecoltypmods = lappend_int(cte->ctecoltypmods, -1); + cte->ctecolcollations = lappend_oid(cte->ctecolcollations, InvalidOid); + } + + return cte; +} diff --git a/src/backend/rewrite/rewriteSupport.c b/src/backend/rewrite/rewriteSupport.c new file mode 100644 index 0000000..85f1ac9 --- /dev/null +++ b/src/backend/rewrite/rewriteSupport.c @@ -0,0 +1,117 @@ +/*------------------------------------------------------------------------- + * + * rewriteSupport.c + * + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/rewrite/rewriteSupport.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/table.h" +#include "catalog/indexing.h" +#include "catalog/pg_rewrite.h" +#include "rewrite/rewriteSupport.h" +#include "utils/fmgroids.h" +#include "utils/inval.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/syscache.h" + + +/* + * Is there a rule by the given name? + */ +bool +IsDefinedRewriteRule(Oid owningRel, const char *ruleName) +{ + return SearchSysCacheExists2(RULERELNAME, + ObjectIdGetDatum(owningRel), + PointerGetDatum(ruleName)); +} + + +/* + * SetRelationRuleStatus + * Set the value of the relation's relhasrules field in pg_class. + * + * NOTE: caller must be holding an appropriate lock on the relation. + * + * NOTE: an important side-effect of this operation is that an SI invalidation + * message is sent out to all backends --- including me --- causing relcache + * entries to be flushed or updated with the new set of rules for the table. + * This must happen even if we find that no change is needed in the pg_class + * row. + */ +void +SetRelationRuleStatus(Oid relationId, bool relHasRules) +{ + Relation relationRelation; + HeapTuple tuple; + Form_pg_class classForm; + + /* + * Find the tuple to update in pg_class, using syscache for the lookup. + */ + relationRelation = table_open(RelationRelationId, RowExclusiveLock); + tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relationId)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", relationId); + classForm = (Form_pg_class) GETSTRUCT(tuple); + + if (classForm->relhasrules != relHasRules) + { + /* Do the update */ + classForm->relhasrules = relHasRules; + + CatalogTupleUpdate(relationRelation, &tuple->t_self, tuple); + } + else + { + /* no need to change tuple, but force relcache rebuild anyway */ + CacheInvalidateRelcacheByTuple(tuple); + } + + heap_freetuple(tuple); + table_close(relationRelation, RowExclusiveLock); +} + +/* + * Find rule oid. + * + * If missing_ok is false, throw an error if rule name not found. If + * true, just return InvalidOid. + */ +Oid +get_rewrite_oid(Oid relid, const char *rulename, bool missing_ok) +{ + HeapTuple tuple; + Form_pg_rewrite ruleform; + Oid ruleoid; + + /* Find the rule's pg_rewrite tuple, get its OID */ + tuple = SearchSysCache2(RULERELNAME, + ObjectIdGetDatum(relid), + PointerGetDatum(rulename)); + if (!HeapTupleIsValid(tuple)) + { + if (missing_ok) + return InvalidOid; + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("rule \"%s\" for relation \"%s\" does not exist", + rulename, get_rel_name(relid)))); + } + ruleform = (Form_pg_rewrite) GETSTRUCT(tuple); + Assert(relid == ruleform->ev_class); + ruleoid = ruleform->oid; + ReleaseSysCache(tuple); + return ruleoid; +} diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c new file mode 100644 index 0000000..e10f949 --- /dev/null +++ b/src/backend/rewrite/rowsecurity.c @@ -0,0 +1,792 @@ +/* + * rewrite/rowsecurity.c + * Routines to support policies for row-level security (aka RLS). + * + * Policies in PostgreSQL provide a mechanism to limit what records are + * returned to a user and what records a user is permitted to add to a table. + * + * Policies can be defined for specific roles, specific commands, or provided + * by an extension. Row security can also be enabled for a table without any + * policies being explicitly defined, in which case a default-deny policy is + * applied. + * + * Any part of the system which is returning records back to the user, or + * which is accepting records from the user to add to a table, needs to + * consider the policies associated with the table (if any). For normal + * queries, this is handled by calling get_row_security_policies() during + * rewrite, for each RTE in the query. This returns the expressions defined + * by the table's policies as a list that is prepended to the securityQuals + * list for the RTE. For queries which modify the table, any WITH CHECK + * clauses from the table's policies are also returned and prepended to the + * list of WithCheckOptions for the Query to check each row that is being + * added to the table. Other parts of the system (eg: COPY) simply construct + * a normal query and use that, if RLS is to be applied. + * + * The check to see if RLS should be enabled is provided through + * check_enable_rls(), which returns an enum (defined in rowsecurity.h) to + * indicate if RLS should be enabled (RLS_ENABLED), or bypassed (RLS_NONE or + * RLS_NONE_ENV). RLS_NONE_ENV indicates that RLS should be bypassed + * in the current environment, but that may change if the row_security GUC or + * the current role changes. + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "access/table.h" +#include "catalog/pg_class.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_policy.h" +#include "catalog/pg_type.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "nodes/pg_list.h" +#include "nodes/plannodes.h" +#include "parser/parsetree.h" +#include "rewrite/rewriteDefine.h" +#include "rewrite/rewriteHandler.h" +#include "rewrite/rewriteManip.h" +#include "rewrite/rowsecurity.h" +#include "tcop/utility.h" +#include "utils/acl.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/rls.h" +#include "utils/syscache.h" + +static void get_policies_for_relation(Relation relation, + CmdType cmd, Oid user_id, + List **permissive_policies, + List **restrictive_policies); + +static void sort_policies_by_name(List *policies); + +static int row_security_policy_cmp(const ListCell *a, const ListCell *b); + +static void add_security_quals(int rt_index, + List *permissive_policies, + List *restrictive_policies, + List **securityQuals, + bool *hasSubLinks); + +static void add_with_check_options(Relation rel, + int rt_index, + WCOKind kind, + List *permissive_policies, + List *restrictive_policies, + List **withCheckOptions, + bool *hasSubLinks, + bool force_using); + +static bool check_role_for_policy(ArrayType *policy_roles, Oid user_id); + +/* + * hooks to allow extensions to add their own security policies + * + * row_security_policy_hook_permissive can be used to add policies which + * are combined with the other permissive policies, using OR. + * + * row_security_policy_hook_restrictive can be used to add policies which + * are enforced, regardless of other policies (they are combined using AND). + */ +row_security_policy_hook_type row_security_policy_hook_permissive = NULL; +row_security_policy_hook_type row_security_policy_hook_restrictive = NULL; + +/* + * Get any row security quals and WithCheckOption checks that should be + * applied to the specified RTE. + * + * In addition, hasRowSecurity is set to true if row-level security is enabled + * (even if this RTE doesn't have any row security quals), and hasSubLinks is + * set to true if any of the quals returned contain sublinks. + */ +void +get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index, + List **securityQuals, List **withCheckOptions, + bool *hasRowSecurity, bool *hasSubLinks) +{ + Oid user_id; + int rls_status; + Relation rel; + CmdType commandType; + List *permissive_policies; + List *restrictive_policies; + + /* Defaults for the return values */ + *securityQuals = NIL; + *withCheckOptions = NIL; + *hasRowSecurity = false; + *hasSubLinks = false; + + /* If this is not a normal relation, just return immediately */ + if (rte->relkind != RELKIND_RELATION && + rte->relkind != RELKIND_PARTITIONED_TABLE) + return; + + /* Switch to checkAsUser if it's set */ + user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId(); + + /* Determine the state of RLS for this, pass checkAsUser explicitly */ + rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false); + + /* If there is no RLS on this table at all, nothing to do */ + if (rls_status == RLS_NONE) + return; + + /* + * RLS_NONE_ENV means we are not doing any RLS now, but that may change + * with changes to the environment, so we mark it as hasRowSecurity to + * force a re-plan when the environment changes. + */ + if (rls_status == RLS_NONE_ENV) + { + /* + * Indicate that this query may involve RLS and must therefore be + * replanned if the environment changes (GUCs, role), but we are not + * adding anything here. + */ + *hasRowSecurity = true; + + return; + } + + /* + * RLS is enabled for this relation. + * + * Get the security policies that should be applied, based on the command + * type. Note that if this isn't the target relation, we actually want + * the relation's SELECT policies, regardless of the query command type, + * for example in UPDATE t1 ... FROM t2 we need to apply t1's UPDATE + * policies and t2's SELECT policies. + */ + rel = table_open(rte->relid, NoLock); + + commandType = rt_index == root->resultRelation ? + root->commandType : CMD_SELECT; + + /* + * In some cases, we need to apply USING policies (which control the + * visibility of records) associated with multiple command types (see + * specific cases below). + * + * When considering the order in which to apply these USING policies, we + * prefer to apply higher privileged policies, those which allow the user + * to lock records (UPDATE and DELETE), first, followed by policies which + * don't (SELECT). + * + * Note that the optimizer is free to push down and reorder quals which + * use leakproof functions. + * + * In all cases, if there are no policy clauses allowing access to rows in + * the table for the specific type of operation, then a single + * always-false clause (a default-deny policy) will be added (see + * add_security_quals). + */ + + /* + * For a SELECT, if UPDATE privileges are required (eg: the user has + * specified FOR [KEY] UPDATE/SHARE), then add the UPDATE USING quals + * first. + * + * This way, we filter out any records from the SELECT FOR SHARE/UPDATE + * which the user does not have access to via the UPDATE USING policies, + * similar to how we require normal UPDATE rights for these queries. + */ + if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE) + { + List *update_permissive_policies; + List *update_restrictive_policies; + + get_policies_for_relation(rel, CMD_UPDATE, user_id, + &update_permissive_policies, + &update_restrictive_policies); + + add_security_quals(rt_index, + update_permissive_policies, + update_restrictive_policies, + securityQuals, + hasSubLinks); + } + + /* + * For SELECT, UPDATE and DELETE, add security quals to enforce the USING + * policies. These security quals control access to existing table rows. + * Restrictive policies are combined together using AND, and permissive + * policies are combined together using OR. + */ + + get_policies_for_relation(rel, commandType, user_id, &permissive_policies, + &restrictive_policies); + + if (commandType == CMD_SELECT || + commandType == CMD_UPDATE || + commandType == CMD_DELETE) + add_security_quals(rt_index, + permissive_policies, + restrictive_policies, + securityQuals, + hasSubLinks); + + /* + * Similar to above, during an UPDATE or DELETE, if SELECT rights are also + * required (eg: when a RETURNING clause exists, or the user has provided + * a WHERE clause which involves columns from the relation), we collect up + * CMD_SELECT policies and add them via add_security_quals first. + * + * This way, we filter out any records which are not visible through an + * ALL or SELECT USING policy. + */ + if ((commandType == CMD_UPDATE || commandType == CMD_DELETE) && + rte->requiredPerms & ACL_SELECT) + { + List *select_permissive_policies; + List *select_restrictive_policies; + + get_policies_for_relation(rel, CMD_SELECT, user_id, + &select_permissive_policies, + &select_restrictive_policies); + + add_security_quals(rt_index, + select_permissive_policies, + select_restrictive_policies, + securityQuals, + hasSubLinks); + } + + /* + * For INSERT and UPDATE, add withCheckOptions to verify that any new + * records added are consistent with the security policies. This will use + * each policy's WITH CHECK clause, or its USING clause if no explicit + * WITH CHECK clause is defined. + */ + if (commandType == CMD_INSERT || commandType == CMD_UPDATE) + { + /* This should be the target relation */ + Assert(rt_index == root->resultRelation); + + add_with_check_options(rel, rt_index, + commandType == CMD_INSERT ? + WCO_RLS_INSERT_CHECK : WCO_RLS_UPDATE_CHECK, + permissive_policies, + restrictive_policies, + withCheckOptions, + hasSubLinks, + false); + + /* + * Get and add ALL/SELECT policies, if SELECT rights are required for + * this relation (eg: when RETURNING is used). These are added as WCO + * policies rather than security quals to ensure that an error is + * raised if a policy is violated; otherwise, we might end up silently + * dropping rows to be added. + */ + if (rte->requiredPerms & ACL_SELECT) + { + List *select_permissive_policies = NIL; + List *select_restrictive_policies = NIL; + + get_policies_for_relation(rel, CMD_SELECT, user_id, + &select_permissive_policies, + &select_restrictive_policies); + add_with_check_options(rel, rt_index, + commandType == CMD_INSERT ? + WCO_RLS_INSERT_CHECK : WCO_RLS_UPDATE_CHECK, + select_permissive_policies, + select_restrictive_policies, + withCheckOptions, + hasSubLinks, + true); + } + + /* + * For INSERT ... ON CONFLICT DO UPDATE we need additional policy + * checks for the UPDATE which may be applied to the same RTE. + */ + if (commandType == CMD_INSERT && + root->onConflict && root->onConflict->action == ONCONFLICT_UPDATE) + { + List *conflict_permissive_policies; + List *conflict_restrictive_policies; + List *conflict_select_permissive_policies = NIL; + List *conflict_select_restrictive_policies = NIL; + + /* Get the policies that apply to the auxiliary UPDATE */ + get_policies_for_relation(rel, CMD_UPDATE, user_id, + &conflict_permissive_policies, + &conflict_restrictive_policies); + + /* + * Enforce the USING clauses of the UPDATE policies using WCOs + * rather than security quals. This ensures that an error is + * raised if the conflicting row cannot be updated due to RLS, + * rather than the change being silently dropped. + */ + add_with_check_options(rel, rt_index, + WCO_RLS_CONFLICT_CHECK, + conflict_permissive_policies, + conflict_restrictive_policies, + withCheckOptions, + hasSubLinks, + true); + + /* + * Get and add ALL/SELECT policies, as WCO_RLS_CONFLICT_CHECK WCOs + * to ensure they are considered when taking the UPDATE path of an + * INSERT .. ON CONFLICT DO UPDATE, if SELECT rights are required + * for this relation, also as WCO policies, again, to avoid + * silently dropping data. See above. + */ + if (rte->requiredPerms & ACL_SELECT) + { + get_policies_for_relation(rel, CMD_SELECT, user_id, + &conflict_select_permissive_policies, + &conflict_select_restrictive_policies); + add_with_check_options(rel, rt_index, + WCO_RLS_CONFLICT_CHECK, + conflict_select_permissive_policies, + conflict_select_restrictive_policies, + withCheckOptions, + hasSubLinks, + true); + } + + /* Enforce the WITH CHECK clauses of the UPDATE policies */ + add_with_check_options(rel, rt_index, + WCO_RLS_UPDATE_CHECK, + conflict_permissive_policies, + conflict_restrictive_policies, + withCheckOptions, + hasSubLinks, + false); + + /* + * Add ALL/SELECT policies as WCO_RLS_UPDATE_CHECK WCOs, to ensure + * that the final updated row is visible when taking the UPDATE + * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights + * are required for this relation. + */ + if (rte->requiredPerms & ACL_SELECT) + add_with_check_options(rel, rt_index, + WCO_RLS_UPDATE_CHECK, + conflict_select_permissive_policies, + conflict_select_restrictive_policies, + withCheckOptions, + hasSubLinks, + true); + } + } + + table_close(rel, NoLock); + + /* + * Copy checkAsUser to the row security quals and WithCheckOption checks, + * in case they contain any subqueries referring to other relations. + */ + setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser); + setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser); + + /* + * Mark this query as having row security, so plancache can invalidate it + * when necessary (eg: role changes) + */ + *hasRowSecurity = true; +} + +/* + * get_policies_for_relation + * + * Returns lists of permissive and restrictive policies to be applied to the + * specified relation, based on the command type and role. + * + * This includes any policies added by extensions. + */ +static void +get_policies_for_relation(Relation relation, CmdType cmd, Oid user_id, + List **permissive_policies, + List **restrictive_policies) +{ + ListCell *item; + + *permissive_policies = NIL; + *restrictive_policies = NIL; + + /* First find all internal policies for the relation. */ + foreach(item, relation->rd_rsdesc->policies) + { + bool cmd_matches = false; + RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); + + /* Always add ALL policies, if they exist. */ + if (policy->polcmd == '*') + cmd_matches = true; + else + { + /* Check whether the policy applies to the specified command type */ + switch (cmd) + { + case CMD_SELECT: + if (policy->polcmd == ACL_SELECT_CHR) + cmd_matches = true; + break; + case CMD_INSERT: + if (policy->polcmd == ACL_INSERT_CHR) + cmd_matches = true; + break; + case CMD_UPDATE: + if (policy->polcmd == ACL_UPDATE_CHR) + cmd_matches = true; + break; + case CMD_DELETE: + if (policy->polcmd == ACL_DELETE_CHR) + cmd_matches = true; + break; + default: + elog(ERROR, "unrecognized policy command type %d", + (int) cmd); + break; + } + } + + /* + * Add this policy to the relevant list of policies if it applies to + * the specified role. + */ + if (cmd_matches && check_role_for_policy(policy->roles, user_id)) + { + if (policy->permissive) + *permissive_policies = lappend(*permissive_policies, policy); + else + *restrictive_policies = lappend(*restrictive_policies, policy); + } + } + + /* + * We sort restrictive policies by name so that any WCOs they generate are + * checked in a well-defined order. + */ + sort_policies_by_name(*restrictive_policies); + + /* + * Then add any permissive or restrictive policies defined by extensions. + * These are simply appended to the lists of internal policies, if they + * apply to the specified role. + */ + if (row_security_policy_hook_restrictive) + { + List *hook_policies = + (*row_security_policy_hook_restrictive) (cmd, relation); + + /* + * As with built-in restrictive policies, we sort any hook-provided + * restrictive policies by name also. Note that we also intentionally + * always check all built-in restrictive policies, in name order, + * before checking restrictive policies added by hooks, in name order. + */ + sort_policies_by_name(hook_policies); + + foreach(item, hook_policies) + { + RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); + + if (check_role_for_policy(policy->roles, user_id)) + *restrictive_policies = lappend(*restrictive_policies, policy); + } + } + + if (row_security_policy_hook_permissive) + { + List *hook_policies = + (*row_security_policy_hook_permissive) (cmd, relation); + + foreach(item, hook_policies) + { + RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); + + if (check_role_for_policy(policy->roles, user_id)) + *permissive_policies = lappend(*permissive_policies, policy); + } + } +} + +/* + * sort_policies_by_name + * + * This is only used for restrictive policies, ensuring that any + * WithCheckOptions they generate are applied in a well-defined order. + * This is not necessary for permissive policies, since they are all combined + * together using OR into a single WithCheckOption check. + */ +static void +sort_policies_by_name(List *policies) +{ + list_sort(policies, row_security_policy_cmp); +} + +/* + * list_sort comparator to sort RowSecurityPolicy entries by name + */ +static int +row_security_policy_cmp(const ListCell *a, const ListCell *b) +{ + const RowSecurityPolicy *pa = (const RowSecurityPolicy *) lfirst(a); + const RowSecurityPolicy *pb = (const RowSecurityPolicy *) lfirst(b); + + /* Guard against NULL policy names from extensions */ + if (pa->policy_name == NULL) + return pb->policy_name == NULL ? 0 : 1; + if (pb->policy_name == NULL) + return -1; + + return strcmp(pa->policy_name, pb->policy_name); +} + +/* + * add_security_quals + * + * Add security quals to enforce the specified RLS policies, restricting + * access to existing data in a table. If there are no policies controlling + * access to the table, then all access is prohibited --- i.e., an implicit + * default-deny policy is used. + * + * New security quals are added to securityQuals, and hasSubLinks is set to + * true if any of the quals added contain sublink subqueries. + */ +static void +add_security_quals(int rt_index, + List *permissive_policies, + List *restrictive_policies, + List **securityQuals, + bool *hasSubLinks) +{ + ListCell *item; + List *permissive_quals = NIL; + Expr *rowsec_expr; + + /* + * First collect up the permissive quals. If we do not find any + * permissive policies then no rows are visible (this is handled below). + */ + foreach(item, permissive_policies) + { + RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); + + if (policy->qual != NULL) + { + permissive_quals = lappend(permissive_quals, + copyObject(policy->qual)); + *hasSubLinks |= policy->hassublinks; + } + } + + /* + * We must have permissive quals, always, or no rows are visible. + * + * If we do not, then we simply return a single 'false' qual which results + * in no rows being visible. + */ + if (permissive_quals != NIL) + { + /* + * We now know that permissive policies exist, so we can now add + * security quals based on the USING clauses from the restrictive + * policies. Since these need to be combined together using AND, we + * can just add them one at a time. + */ + foreach(item, restrictive_policies) + { + RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); + Expr *qual; + + if (policy->qual != NULL) + { + qual = copyObject(policy->qual); + ChangeVarNodes((Node *) qual, 1, rt_index, 0); + + *securityQuals = list_append_unique(*securityQuals, qual); + *hasSubLinks |= policy->hassublinks; + } + } + + /* + * Then add a single security qual combining together the USING + * clauses from all the permissive policies using OR. + */ + if (list_length(permissive_quals) == 1) + rowsec_expr = (Expr *) linitial(permissive_quals); + else + rowsec_expr = makeBoolExpr(OR_EXPR, permissive_quals, -1); + + ChangeVarNodes((Node *) rowsec_expr, 1, rt_index, 0); + *securityQuals = list_append_unique(*securityQuals, rowsec_expr); + } + else + + /* + * A permissive policy must exist for rows to be visible at all. + * Therefore, if there were no permissive policies found, return a + * single always-false clause. + */ + *securityQuals = lappend(*securityQuals, + makeConst(BOOLOID, -1, InvalidOid, + sizeof(bool), BoolGetDatum(false), + false, true)); +} + +/* + * add_with_check_options + * + * Add WithCheckOptions of the specified kind to check that new records + * added by an INSERT or UPDATE are consistent with the specified RLS + * policies. Normally new data must satisfy the WITH CHECK clauses from the + * policies. If a policy has no explicit WITH CHECK clause, its USING clause + * is used instead. In the special case of an UPDATE arising from an + * INSERT ... ON CONFLICT DO UPDATE, existing records are first checked using + * a WCO_RLS_CONFLICT_CHECK WithCheckOption, which always uses the USING + * clauses from RLS policies. + * + * New WCOs are added to withCheckOptions, and hasSubLinks is set to true if + * any of the check clauses added contain sublink subqueries. + */ +static void +add_with_check_options(Relation rel, + int rt_index, + WCOKind kind, + List *permissive_policies, + List *restrictive_policies, + List **withCheckOptions, + bool *hasSubLinks, + bool force_using) +{ + ListCell *item; + List *permissive_quals = NIL; + +#define QUAL_FOR_WCO(policy) \ + ( !force_using && \ + (policy)->with_check_qual != NULL ? \ + (policy)->with_check_qual : (policy)->qual ) + + /* + * First collect up the permissive policy clauses, similar to + * add_security_quals. + */ + foreach(item, permissive_policies) + { + RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); + Expr *qual = QUAL_FOR_WCO(policy); + + if (qual != NULL) + { + permissive_quals = lappend(permissive_quals, copyObject(qual)); + *hasSubLinks |= policy->hassublinks; + } + } + + /* + * There must be at least one permissive qual found or no rows are allowed + * to be added. This is the same as in add_security_quals. + * + * If there are no permissive_quals then we fall through and return a + * single 'false' WCO, preventing all new rows. + */ + if (permissive_quals != NIL) + { + /* + * Add a single WithCheckOption for all the permissive policy clauses, + * combining them together using OR. This check has no policy name, + * since if the check fails it means that no policy granted permission + * to perform the update, rather than any particular policy being + * violated. + */ + WithCheckOption *wco; + + wco = makeNode(WithCheckOption); + wco->kind = kind; + wco->relname = pstrdup(RelationGetRelationName(rel)); + wco->polname = NULL; + wco->cascaded = false; + + if (list_length(permissive_quals) == 1) + wco->qual = (Node *) linitial(permissive_quals); + else + wco->qual = (Node *) makeBoolExpr(OR_EXPR, permissive_quals, -1); + + ChangeVarNodes(wco->qual, 1, rt_index, 0); + + *withCheckOptions = list_append_unique(*withCheckOptions, wco); + + /* + * Now add WithCheckOptions for each of the restrictive policy clauses + * (which will be combined together using AND). We use a separate + * WithCheckOption for each restrictive policy to allow the policy + * name to be included in error reports if the policy is violated. + */ + foreach(item, restrictive_policies) + { + RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); + Expr *qual = QUAL_FOR_WCO(policy); + WithCheckOption *wco; + + if (qual != NULL) + { + qual = copyObject(qual); + ChangeVarNodes((Node *) qual, 1, rt_index, 0); + + wco = makeNode(WithCheckOption); + wco->kind = kind; + wco->relname = pstrdup(RelationGetRelationName(rel)); + wco->polname = pstrdup(policy->policy_name); + wco->qual = (Node *) qual; + wco->cascaded = false; + + *withCheckOptions = list_append_unique(*withCheckOptions, wco); + *hasSubLinks |= policy->hassublinks; + } + } + } + else + { + /* + * If there were no policy clauses to check new data, add a single + * always-false WCO (a default-deny policy). + */ + WithCheckOption *wco; + + wco = makeNode(WithCheckOption); + wco->kind = kind; + wco->relname = pstrdup(RelationGetRelationName(rel)); + wco->polname = NULL; + wco->qual = (Node *) makeConst(BOOLOID, -1, InvalidOid, + sizeof(bool), BoolGetDatum(false), + false, true); + wco->cascaded = false; + + *withCheckOptions = lappend(*withCheckOptions, wco); + } +} + +/* + * check_role_for_policy - + * determines if the policy should be applied for the current role + */ +static bool +check_role_for_policy(ArrayType *policy_roles, Oid user_id) +{ + int i; + Oid *roles = (Oid *) ARR_DATA_PTR(policy_roles); + + /* Quick fall-thru for policies applied to all roles */ + if (roles[0] == ACL_ID_PUBLIC) + return true; + + for (i = 0; i < ARR_DIMS(policy_roles)[0]; i++) + { + if (has_privs_of_role(user_id, roles[i])) + return true; + } + + return false; +} |