void DefineQueryRewrite(RuleStmt *stmt) { RangeVar *event_obj = stmt->relation; Node *event_qual = stmt->whereClause; CmdType event_type = stmt->event; bool is_instead = stmt->instead; bool replace = stmt->replace; List *action = stmt->actions; Relation event_relation; Oid ev_relid; int event_attno; ListCell *l; Query *query; bool RelisBecomingView = false; /* * 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 might be sufficient to * grab ShareLock to lock out insert/update/delete actions. But for now, * let's just grab AccessExclusiveLock all the time. */ event_relation = heap_openrv(event_obj, AccessExclusiveLock); ev_relid = RelationGetRelid(event_relation); /* * Check user has permission to apply rules to this relation. */ if (!pg_class_ownercheck(ev_relid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(event_relation)); /* * No rule actions that modify OLD or NEW */ foreach(l, action) { query = (Query *) lfirst(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."))); }
/* * InsertRule - * takes the arguments and inserts them as a row into the system * relation "pg_rewrite" */ static Oid InsertRule(char *rulname, int evtype, Oid eventrel_oid, AttrNumber evslot_index, bool evinstead, Node *event_qual, List *action, bool replace) { char *evqual = nodeToString(event_qual); char *actiontree = nodeToString((Node *) action); int i; 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)); i = 0; namestrcpy(&rname, rulname); values[i++] = NameGetDatum(&rname); /* rulename */ values[i++] = ObjectIdGetDatum(eventrel_oid); /* ev_class */ values[i++] = Int16GetDatum(evslot_index); /* ev_attr */ values[i++] = CharGetDatum(evtype + '0'); /* ev_type */ values[i++] = CharGetDatum(RULE_FIRES_ON_ORIGIN); /* ev_enabled */ values[i++] = BoolGetDatum(evinstead); /* is_instead */ values[i++] = CStringGetTextDatum(evqual); /* ev_qual */ values[i++] = CStringGetTextDatum(actiontree); /* ev_action */ /* * Ready to store new pg_rewrite tuple */ pg_rewrite_desc = heap_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_attr - 1] = true; 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); simple_heap_update(pg_rewrite_desc, &tup->t_self, tup); ReleaseSysCache(oldtup); rewriteObjectId = HeapTupleGetOid(tup); is_update = true; } else { tup = heap_form_tuple(pg_rewrite_desc->rd_att, values, nulls); rewriteObjectId = simple_heap_insert(pg_rewrite_desc, tup); } /* Need to update indexes in either case */ CatalogUpdateIndexes(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 = (Query *) linitial(action); qry = getInsertSelectQuery(qry, NULL); recordDependencyOnExpr(&myself, event_qual, qry->rtable, DEPENDENCY_NORMAL); } /* Post creation hook for new rule */ InvokeObjectAccessHook(OAT_POST_CREATE, RewriteRelationId, rewriteObjectId, 0); heap_close(pg_rewrite_desc, RowExclusiveLock); return rewriteObjectId; }
/* * 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. */ void DefineQueryRewrite(char *rulename, Oid event_relid, Node *event_qual, CmdType event_type, bool is_instead, bool replace, List *action) { Relation event_relation; int event_attno; ListCell *l; Query *query; bool RelisBecomingView = false; /* * 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. */ event_relation = heap_open(event_relid, AccessExclusiveLock); /* * Verify relation is of a type that rules can sensibly be applied to. */ if (event_relation->rd_rel->relkind != RELKIND_RELATION && event_relation->rd_rel->relkind != RELKIND_VIEW) 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, ACL_KIND_CLASS, RelationGetRelationName(event_relation)); /* * No rule actions that modify OLD or NEW */ foreach(l, action) { query = (Query *) lfirst(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."))); }
/* * InsertRule - * takes the arguments and inserts them as a row into the system * relation "pg_rewrite" */ static Oid InsertRule(char *rulname, int evtype, Oid eventrel_oid, AttrNumber evslot_index, bool evinstead, Node *event_qual, List *action, bool replace, Oid ruleOid) { char *evqual = nodeToString(event_qual); char *actiontree = nodeToString((Node *) action); int i; Datum values[Natts_pg_rewrite]; bool nulls[Natts_pg_rewrite]; bool replaces[Natts_pg_rewrite]; NameData rname; HeapTuple tup, oldtup; Oid rewriteObjectId; ObjectAddress myself, referenced; bool is_update = false; cqContext *pcqCtx; /* * Set up *nulls and *values arrays */ MemSet(nulls, false, sizeof(nulls)); i = 0; namestrcpy(&rname, rulname); values[i++] = NameGetDatum(&rname); /* rulename */ values[i++] = ObjectIdGetDatum(eventrel_oid); /* ev_class */ values[i++] = Int16GetDatum(evslot_index); /* ev_attr */ values[i++] = CharGetDatum(evtype + '0'); /* ev_type */ values[i++] = BoolGetDatum(evinstead); /* is_instead */ values[i++] = CStringGetTextDatum(evqual); /* ev_qual */ values[i++] = CStringGetTextDatum(actiontree); /* ev_action */ /* * Ready to store new pg_rewrite tuple */ /* * Check to see if we are replacing an existing tuple */ pcqCtx = caql_beginscan( NULL, cql("SELECT * FROM pg_rewrite " " WHERE ev_class = :1 " " AND rulename = :2 " " FOR UPDATE ", ObjectIdGetDatum(eventrel_oid), CStringGetDatum(rulname))); oldtup = caql_getnext(pcqCtx); 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_attr - 1] = true; 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 = caql_modify_current(pcqCtx, values, nulls, replaces); caql_update_current(pcqCtx, tup); /* and Update indexes (implicit) */ rewriteObjectId = HeapTupleGetOid(tup); is_update = true; } else { tup = caql_form_tuple(pcqCtx, values, nulls); if (OidIsValid(ruleOid)) HeapTupleSetOid(tup, ruleOid); rewriteObjectId = caql_insert(pcqCtx, tup); /* and Update indexes (implicit) */ } heap_freetuple(tup); /* If replacing, get rid of old dependencies and make new ones */ if (is_update) deleteDependencyRecordsFor(RewriteRelationId, rewriteObjectId); /* * 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 = (Query *) linitial(action); qry = getInsertSelectQuery(qry, NULL); recordDependencyOnExpr(&myself, event_qual, qry->rtable, DEPENDENCY_NORMAL); } caql_endscan(pcqCtx); return rewriteObjectId; }