Header And Logo

PostgreSQL
| The world's most advanced open source database.

rewriteDefine.c

Go to the documentation of this file.
00001 /*-------------------------------------------------------------------------
00002  *
00003  * rewriteDefine.c
00004  *    routines for defining a rewrite rule
00005  *
00006  * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
00007  * Portions Copyright (c) 1994, Regents of the University of California
00008  *
00009  *
00010  * IDENTIFICATION
00011  *    src/backend/rewrite/rewriteDefine.c
00012  *
00013  *-------------------------------------------------------------------------
00014  */
00015 #include "postgres.h"
00016 
00017 #include "access/heapam.h"
00018 #include "access/htup_details.h"
00019 #include "access/multixact.h"
00020 #include "access/transam.h"
00021 #include "access/xact.h"
00022 #include "catalog/catalog.h"
00023 #include "catalog/dependency.h"
00024 #include "catalog/heap.h"
00025 #include "catalog/indexing.h"
00026 #include "catalog/namespace.h"
00027 #include "catalog/objectaccess.h"
00028 #include "catalog/pg_rewrite.h"
00029 #include "catalog/storage.h"
00030 #include "miscadmin.h"
00031 #include "nodes/nodeFuncs.h"
00032 #include "parser/parse_utilcmd.h"
00033 #include "rewrite/rewriteDefine.h"
00034 #include "rewrite/rewriteManip.h"
00035 #include "rewrite/rewriteSupport.h"
00036 #include "utils/acl.h"
00037 #include "utils/builtins.h"
00038 #include "utils/inval.h"
00039 #include "utils/lsyscache.h"
00040 #include "utils/rel.h"
00041 #include "utils/syscache.h"
00042 #include "utils/tqual.h"
00043 
00044 
00045 static void checkRuleResultList(List *targetList, TupleDesc resultDesc,
00046                     bool isSelect);
00047 static bool setRuleCheckAsUser_walker(Node *node, Oid *context);
00048 static void setRuleCheckAsUser_Query(Query *qry, Oid userid);
00049 
00050 
00051 /*
00052  * InsertRule -
00053  *    takes the arguments and inserts them as a row into the system
00054  *    relation "pg_rewrite"
00055  */
00056 static Oid
00057 InsertRule(char *rulname,
00058            int evtype,
00059            Oid eventrel_oid,
00060            AttrNumber evslot_index,
00061            bool evinstead,
00062            Node *event_qual,
00063            List *action,
00064            bool replace)
00065 {
00066     char       *evqual = nodeToString(event_qual);
00067     char       *actiontree = nodeToString((Node *) action);
00068     Datum       values[Natts_pg_rewrite];
00069     bool        nulls[Natts_pg_rewrite];
00070     bool        replaces[Natts_pg_rewrite];
00071     NameData    rname;
00072     Relation    pg_rewrite_desc;
00073     HeapTuple   tup,
00074                 oldtup;
00075     Oid         rewriteObjectId;
00076     ObjectAddress myself,
00077                 referenced;
00078     bool        is_update = false;
00079 
00080     /*
00081      * Set up *nulls and *values arrays
00082      */
00083     MemSet(nulls, false, sizeof(nulls));
00084 
00085     namestrcpy(&rname, rulname);
00086     values[Anum_pg_rewrite_rulename - 1] = NameGetDatum(&rname);
00087     values[Anum_pg_rewrite_ev_class - 1] = ObjectIdGetDatum(eventrel_oid);
00088     values[Anum_pg_rewrite_ev_attr - 1] = Int16GetDatum(evslot_index);
00089     values[Anum_pg_rewrite_ev_type - 1] = CharGetDatum(evtype + '0');
00090     values[Anum_pg_rewrite_ev_enabled - 1] = CharGetDatum(RULE_FIRES_ON_ORIGIN);
00091     values[Anum_pg_rewrite_is_instead - 1] = BoolGetDatum(evinstead);
00092     values[Anum_pg_rewrite_ev_qual - 1] = CStringGetTextDatum(evqual);
00093     values[Anum_pg_rewrite_ev_action - 1] = CStringGetTextDatum(actiontree);
00094 
00095     /*
00096      * Ready to store new pg_rewrite tuple
00097      */
00098     pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock);
00099 
00100     /*
00101      * Check to see if we are replacing an existing tuple
00102      */
00103     oldtup = SearchSysCache2(RULERELNAME,
00104                              ObjectIdGetDatum(eventrel_oid),
00105                              PointerGetDatum(rulname));
00106 
00107     if (HeapTupleIsValid(oldtup))
00108     {
00109         if (!replace)
00110             ereport(ERROR,
00111                     (errcode(ERRCODE_DUPLICATE_OBJECT),
00112                      errmsg("rule \"%s\" for relation \"%s\" already exists",
00113                             rulname, get_rel_name(eventrel_oid))));
00114 
00115         /*
00116          * When replacing, we don't need to replace every attribute
00117          */
00118         MemSet(replaces, false, sizeof(replaces));
00119         replaces[Anum_pg_rewrite_ev_attr - 1] = true;
00120         replaces[Anum_pg_rewrite_ev_type - 1] = true;
00121         replaces[Anum_pg_rewrite_is_instead - 1] = true;
00122         replaces[Anum_pg_rewrite_ev_qual - 1] = true;
00123         replaces[Anum_pg_rewrite_ev_action - 1] = true;
00124 
00125         tup = heap_modify_tuple(oldtup, RelationGetDescr(pg_rewrite_desc),
00126                                 values, nulls, replaces);
00127 
00128         simple_heap_update(pg_rewrite_desc, &tup->t_self, tup);
00129 
00130         ReleaseSysCache(oldtup);
00131 
00132         rewriteObjectId = HeapTupleGetOid(tup);
00133         is_update = true;
00134     }
00135     else
00136     {
00137         tup = heap_form_tuple(pg_rewrite_desc->rd_att, values, nulls);
00138 
00139         rewriteObjectId = simple_heap_insert(pg_rewrite_desc, tup);
00140     }
00141 
00142     /* Need to update indexes in either case */
00143     CatalogUpdateIndexes(pg_rewrite_desc, tup);
00144 
00145     heap_freetuple(tup);
00146 
00147     /* If replacing, get rid of old dependencies and make new ones */
00148     if (is_update)
00149         deleteDependencyRecordsFor(RewriteRelationId, rewriteObjectId, false);
00150 
00151     /*
00152      * Install dependency on rule's relation to ensure it will go away on
00153      * relation deletion.  If the rule is ON SELECT, make the dependency
00154      * implicit --- this prevents deleting a view's SELECT rule.  Other kinds
00155      * of rules can be AUTO.
00156      */
00157     myself.classId = RewriteRelationId;
00158     myself.objectId = rewriteObjectId;
00159     myself.objectSubId = 0;
00160 
00161     referenced.classId = RelationRelationId;
00162     referenced.objectId = eventrel_oid;
00163     referenced.objectSubId = 0;
00164 
00165     recordDependencyOn(&myself, &referenced,
00166              (evtype == CMD_SELECT) ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO);
00167 
00168     /*
00169      * Also install dependencies on objects referenced in action and qual.
00170      */
00171     recordDependencyOnExpr(&myself, (Node *) action, NIL,
00172                            DEPENDENCY_NORMAL);
00173 
00174     if (event_qual != NULL)
00175     {
00176         /* Find query containing OLD/NEW rtable entries */
00177         Query      *qry = (Query *) linitial(action);
00178 
00179         qry = getInsertSelectQuery(qry, NULL);
00180         recordDependencyOnExpr(&myself, event_qual, qry->rtable,
00181                                DEPENDENCY_NORMAL);
00182     }
00183 
00184     /* Post creation hook for new rule */
00185     InvokeObjectPostCreateHook(RewriteRelationId, rewriteObjectId, 0);
00186 
00187     heap_close(pg_rewrite_desc, RowExclusiveLock);
00188 
00189     return rewriteObjectId;
00190 }
00191 
00192 /*
00193  * DefineRule
00194  *      Execute a CREATE RULE command.
00195  */
00196 Oid
00197 DefineRule(RuleStmt *stmt, const char *queryString)
00198 {
00199     List       *actions;
00200     Node       *whereClause;
00201     Oid         relId;
00202 
00203     /* Parse analysis. */
00204     transformRuleStmt(stmt, queryString, &actions, &whereClause);
00205 
00206     /*
00207      * Find and lock the relation.  Lock level should match
00208      * DefineQueryRewrite.
00209      */
00210     relId = RangeVarGetRelid(stmt->relation, AccessExclusiveLock, false);
00211 
00212     /* ... and execute */
00213     return DefineQueryRewrite(stmt->rulename,
00214                               relId,
00215                               whereClause,
00216                               stmt->event,
00217                               stmt->instead,
00218                               stmt->replace,
00219                               actions);
00220 }
00221 
00222 
00223 /*
00224  * DefineQueryRewrite
00225  *      Create a rule
00226  *
00227  * This is essentially the same as DefineRule() except that the rule's
00228  * action and qual have already been passed through parse analysis.
00229  */
00230 Oid
00231 DefineQueryRewrite(char *rulename,
00232                    Oid event_relid,
00233                    Node *event_qual,
00234                    CmdType event_type,
00235                    bool is_instead,
00236                    bool replace,
00237                    List *action)
00238 {
00239     Relation    event_relation;
00240     int         event_attno;
00241     ListCell   *l;
00242     Query      *query;
00243     bool        RelisBecomingView = false;
00244     Oid         ruleId = InvalidOid;
00245 
00246     /*
00247      * If we are installing an ON SELECT rule, we had better grab
00248      * AccessExclusiveLock to ensure no SELECTs are currently running on the
00249      * event relation. For other types of rules, it would be sufficient to
00250      * grab ShareRowExclusiveLock to lock out insert/update/delete actions and
00251      * to ensure that we lock out current CREATE RULE statements; but because
00252      * of race conditions in access to catalog entries, we can't do that yet.
00253      *
00254      * Note that this lock level should match the one used in DefineRule.
00255      */
00256     event_relation = heap_open(event_relid, AccessExclusiveLock);
00257 
00258     /*
00259      * Verify relation is of a type that rules can sensibly be applied to.
00260      */
00261     if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
00262         event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
00263         event_relation->rd_rel->relkind != RELKIND_VIEW)
00264         ereport(ERROR,
00265                 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
00266                  errmsg("\"%s\" is not a table or view",
00267                         RelationGetRelationName(event_relation))));
00268 
00269     if (!allowSystemTableMods && IsSystemRelation(event_relation))
00270         ereport(ERROR,
00271                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
00272                  errmsg("permission denied: \"%s\" is a system catalog",
00273                         RelationGetRelationName(event_relation))));
00274 
00275     /*
00276      * Check user has permission to apply rules to this relation.
00277      */
00278     if (!pg_class_ownercheck(event_relid, GetUserId()))
00279         aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
00280                        RelationGetRelationName(event_relation));
00281 
00282     /*
00283      * No rule actions that modify OLD or NEW
00284      */
00285     foreach(l, action)
00286     {
00287         query = (Query *) lfirst(l);
00288         if (query->resultRelation == 0)
00289             continue;
00290         /* Don't be fooled by INSERT/SELECT */
00291         if (query != getInsertSelectQuery(query, NULL))
00292             continue;
00293         if (query->resultRelation == PRS2_OLD_VARNO)
00294             ereport(ERROR,
00295                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
00296                      errmsg("rule actions on OLD are not implemented"),
00297                      errhint("Use views or triggers instead.")));
00298         if (query->resultRelation == PRS2_NEW_VARNO)
00299             ereport(ERROR,
00300                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
00301                      errmsg("rule actions on NEW are not implemented"),
00302                      errhint("Use triggers instead.")));
00303     }
00304 
00305     if (event_type == CMD_SELECT)
00306     {
00307         /*
00308          * Rules ON SELECT are restricted to view definitions
00309          *
00310          * So there cannot be INSTEAD NOTHING, ...
00311          */
00312         if (list_length(action) == 0)
00313             ereport(ERROR,
00314                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
00315                errmsg("INSTEAD NOTHING rules on SELECT are not implemented"),
00316                      errhint("Use views instead.")));
00317 
00318         /*
00319          * ... there cannot be multiple actions, ...
00320          */
00321         if (list_length(action) > 1)
00322             ereport(ERROR,
00323                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
00324                      errmsg("multiple actions for rules on SELECT are not implemented")));
00325 
00326         /*
00327          * ... the one action must be a SELECT, ...
00328          */
00329         query = (Query *) linitial(action);
00330         if (!is_instead ||
00331             query->commandType != CMD_SELECT ||
00332             query->utilityStmt != NULL)
00333             ereport(ERROR,
00334                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
00335                  errmsg("rules on SELECT must have action INSTEAD SELECT")));
00336 
00337         /*
00338          * ... it cannot contain data-modifying WITH ...
00339          */
00340         if (query->hasModifyingCTE)
00341             ereport(ERROR,
00342                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
00343                      errmsg("rules on SELECT must not contain data-modifying statements in WITH")));
00344 
00345         /*
00346          * ... there can be no rule qual, ...
00347          */
00348         if (event_qual != NULL)
00349             ereport(ERROR,
00350                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
00351                      errmsg("event qualifications are not implemented for rules on SELECT")));
00352 
00353         /*
00354          * ... the targetlist of the SELECT action must exactly match the
00355          * event relation, ...
00356          */
00357         checkRuleResultList(query->targetList,
00358                             RelationGetDescr(event_relation),
00359                             true);
00360 
00361         /*
00362          * ... there must not be another ON SELECT rule already ...
00363          */
00364         if (!replace && event_relation->rd_rules != NULL)
00365         {
00366             int         i;
00367 
00368             for (i = 0; i < event_relation->rd_rules->numLocks; i++)
00369             {
00370                 RewriteRule *rule;
00371 
00372                 rule = event_relation->rd_rules->rules[i];
00373                 if (rule->event == CMD_SELECT)
00374                     ereport(ERROR,
00375                           (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
00376                            errmsg("\"%s\" is already a view",
00377                                   RelationGetRelationName(event_relation))));
00378             }
00379         }
00380 
00381         /*
00382          * ... and finally the rule must be named _RETURN.
00383          */
00384         if (strcmp(rulename, ViewSelectRuleName) != 0)
00385         {
00386             /*
00387              * In versions before 7.3, the expected name was _RETviewname. For
00388              * backwards compatibility with old pg_dump output, accept that
00389              * and silently change it to _RETURN.  Since this is just a quick
00390              * backwards-compatibility hack, limit the number of characters
00391              * checked to a few less than NAMEDATALEN; this saves having to
00392              * worry about where a multibyte character might have gotten
00393              * truncated.
00394              */
00395             if (strncmp(rulename, "_RET", 4) != 0 ||
00396                 strncmp(rulename + 4, RelationGetRelationName(event_relation),
00397                         NAMEDATALEN - 4 - 4) != 0)
00398                 ereport(ERROR,
00399                         (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
00400                          errmsg("view rule for \"%s\" must be named \"%s\"",
00401                                 RelationGetRelationName(event_relation),
00402                                 ViewSelectRuleName)));
00403             rulename = pstrdup(ViewSelectRuleName);
00404         }
00405 
00406         /*
00407          * Are we converting a relation to a view?
00408          *
00409          * If so, check that the relation is empty because the storage for the
00410          * relation is going to be deleted.  Also insist that the rel not have
00411          * any triggers, indexes, or child tables.  (Note: these tests are too
00412          * strict, because they will reject relations that once had such but
00413          * don't anymore.  But we don't really care, because this whole
00414          * business of converting relations to views is just a kluge to allow
00415          * dump/reload of views that participate in circular dependencies.)
00416          */
00417         if (event_relation->rd_rel->relkind != RELKIND_VIEW &&
00418             event_relation->rd_rel->relkind != RELKIND_MATVIEW)
00419         {
00420             HeapScanDesc scanDesc;
00421 
00422             scanDesc = heap_beginscan(event_relation, SnapshotNow, 0, NULL);
00423             if (heap_getnext(scanDesc, ForwardScanDirection) != NULL)
00424                 ereport(ERROR,
00425                         (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
00426                          errmsg("could not convert table \"%s\" to a view because it is not empty",
00427                                 RelationGetRelationName(event_relation))));
00428             heap_endscan(scanDesc);
00429 
00430             if (event_relation->rd_rel->relhastriggers)
00431                 ereport(ERROR,
00432                         (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
00433                          errmsg("could not convert table \"%s\" to a view because it has triggers",
00434                                 RelationGetRelationName(event_relation)),
00435                          errhint("In particular, the table cannot be involved in any foreign key relationships.")));
00436 
00437             if (event_relation->rd_rel->relhasindex)
00438                 ereport(ERROR,
00439                         (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
00440                          errmsg("could not convert table \"%s\" to a view because it has indexes",
00441                                 RelationGetRelationName(event_relation))));
00442 
00443             if (event_relation->rd_rel->relhassubclass)
00444                 ereport(ERROR,
00445                         (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
00446                          errmsg("could not convert table \"%s\" to a view because it has child tables",
00447                                 RelationGetRelationName(event_relation))));
00448 
00449             RelisBecomingView = true;
00450         }
00451     }
00452     else
00453     {
00454         /*
00455          * For non-SELECT rules, a RETURNING list can appear in at most one of
00456          * the actions ... and there can't be any RETURNING list at all in a
00457          * conditional or non-INSTEAD rule.  (Actually, there can be at most
00458          * one RETURNING list across all rules on the same event, but it seems
00459          * best to enforce that at rule expansion time.)  If there is a
00460          * RETURNING list, it must match the event relation.
00461          */
00462         bool        haveReturning = false;
00463 
00464         foreach(l, action)
00465         {
00466             query = (Query *) lfirst(l);
00467 
00468             if (!query->returningList)
00469                 continue;
00470             if (haveReturning)
00471                 ereport(ERROR,
00472                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
00473                   errmsg("cannot have multiple RETURNING lists in a rule")));
00474             haveReturning = true;
00475             if (event_qual != NULL)
00476                 ereport(ERROR,
00477                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
00478                          errmsg("RETURNING lists are not supported in conditional rules")));
00479             if (!is_instead)
00480                 ereport(ERROR,
00481                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
00482                          errmsg("RETURNING lists are not supported in non-INSTEAD rules")));
00483             checkRuleResultList(query->returningList,
00484                                 RelationGetDescr(event_relation),
00485                                 false);
00486         }
00487     }
00488 
00489     /*
00490      * This rule is allowed - prepare to install it.
00491      */
00492     event_attno = -1;
00493 
00494     /* discard rule if it's null action and not INSTEAD; it's a no-op */
00495     if (action != NIL || is_instead)
00496     {
00497         ruleId = InsertRule(rulename,
00498                             event_type,
00499                             event_relid,
00500                             event_attno,
00501                             is_instead,
00502                             event_qual,
00503                             action,
00504                             replace);
00505 
00506         /*
00507          * Set pg_class 'relhasrules' field TRUE for event relation.
00508          *
00509          * Important side effect: an SI notice is broadcast to force all
00510          * backends (including me!) to update relcache entries with the new
00511          * rule.
00512          */
00513         SetRelationRuleStatus(event_relid, true);
00514     }
00515 
00516     /* ---------------------------------------------------------------------
00517      * If the relation is becoming a view:
00518      * - delete the associated storage files
00519      * - get rid of any system attributes in pg_attribute; a view shouldn't
00520      *   have any of those
00521      * - remove the toast table; there is no need for it anymore, and its
00522      *   presence would make vacuum slightly more complicated
00523      * - set relkind to RELKIND_VIEW, and adjust other pg_class fields
00524      *   to be appropriate for a view
00525      *
00526      * NB: we had better have AccessExclusiveLock to do this ...
00527      * ---------------------------------------------------------------------
00528      */
00529     if (RelisBecomingView)
00530     {
00531         Relation    relationRelation;
00532         Oid         toastrelid;
00533         HeapTuple   classTup;
00534         Form_pg_class classForm;
00535 
00536         relationRelation = heap_open(RelationRelationId, RowExclusiveLock);
00537         toastrelid = event_relation->rd_rel->reltoastrelid;
00538 
00539         /* drop storage while table still looks like a table  */
00540         RelationDropStorage(event_relation);
00541         DeleteSystemAttributeTuples(event_relid);
00542 
00543         /*
00544          * Drop the toast table if any.  (This won't take care of updating
00545          * the toast fields in the relation's own pg_class entry; we handle
00546          * that below.)
00547          */
00548         if (OidIsValid(toastrelid))
00549         {
00550             ObjectAddress toastobject;
00551 
00552             /*
00553              * Delete the dependency of the toast relation on the main
00554              * relation so we can drop the former without dropping the latter.
00555              */
00556             deleteDependencyRecordsFor(RelationRelationId, toastrelid,
00557                                        false);
00558 
00559             /* Make deletion of dependency record visible */
00560             CommandCounterIncrement();
00561 
00562             /* Now drop toast table, including its index */
00563             toastobject.classId = RelationRelationId;
00564             toastobject.objectId = toastrelid;
00565             toastobject.objectSubId = 0;
00566             performDeletion(&toastobject, DROP_RESTRICT,
00567                             PERFORM_DELETION_INTERNAL);
00568         }
00569 
00570         /*
00571          * SetRelationRuleStatus may have updated the pg_class row, so we must
00572          * advance the command counter before trying to update it again.
00573          */
00574         CommandCounterIncrement();
00575 
00576         /*
00577          * Fix pg_class entry to look like a normal view's, including setting
00578          * the correct relkind and removal of reltoastrelid/reltoastidxid of
00579          * the toast table we potentially removed above.
00580          */
00581         classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(event_relid));
00582         if (!HeapTupleIsValid(classTup))
00583             elog(ERROR, "cache lookup failed for relation %u", event_relid);
00584         classForm = (Form_pg_class) GETSTRUCT(classTup);
00585 
00586         classForm->reltablespace = InvalidOid;
00587         classForm->relpages = 0;
00588         classForm->reltuples = 0;
00589         classForm->relallvisible = 0;
00590         classForm->reltoastrelid = InvalidOid;
00591         classForm->reltoastidxid = InvalidOid;
00592         classForm->relhasindex = false;
00593         classForm->relkind = RELKIND_VIEW;
00594         classForm->relhasoids = false;
00595         classForm->relhaspkey = false;
00596         classForm->relfrozenxid = InvalidTransactionId;
00597         classForm->relminmxid = InvalidMultiXactId;
00598 
00599         simple_heap_update(relationRelation, &classTup->t_self, classTup);
00600         CatalogUpdateIndexes(relationRelation, classTup);
00601 
00602         heap_freetuple(classTup);
00603         heap_close(relationRelation, RowExclusiveLock);
00604     }
00605 
00606     /* Close rel, but keep lock till commit... */
00607     heap_close(event_relation, NoLock);
00608 
00609     return ruleId;
00610 }
00611 
00612 /*
00613  * checkRuleResultList
00614  *      Verify that targetList produces output compatible with a tupledesc
00615  *
00616  * The targetList might be either a SELECT targetlist, or a RETURNING list;
00617  * isSelect tells which.  (This is mostly used for choosing error messages,
00618  * but also we don't enforce column name matching for RETURNING.)
00619  */
00620 static void
00621 checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect)
00622 {
00623     ListCell   *tllist;
00624     int         i;
00625 
00626     i = 0;
00627     foreach(tllist, targetList)
00628     {
00629         TargetEntry *tle = (TargetEntry *) lfirst(tllist);
00630         int32       tletypmod;
00631         Form_pg_attribute attr;
00632         char       *attname;
00633 
00634         /* resjunk entries may be ignored */
00635         if (tle->resjunk)
00636             continue;
00637         i++;
00638         if (i > resultDesc->natts)
00639             ereport(ERROR,
00640                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
00641                      isSelect ?
00642                    errmsg("SELECT rule's target list has too many entries") :
00643                      errmsg("RETURNING list has too many entries")));
00644 
00645         attr = resultDesc->attrs[i - 1];
00646         attname = NameStr(attr->attname);
00647 
00648         /*
00649          * Disallow dropped columns in the relation.  This won't happen in the
00650          * cases we actually care about (namely creating a view via CREATE
00651          * TABLE then CREATE RULE, or adding a RETURNING rule to a view).
00652          * Trying to cope with it is much more trouble than it's worth,
00653          * because we'd have to modify the rule to insert dummy NULLs at the
00654          * right positions.
00655          */
00656         if (attr->attisdropped)
00657             ereport(ERROR,
00658                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
00659                      errmsg("cannot convert relation containing dropped columns to view")));
00660 
00661         if (isSelect && strcmp(tle->resname, attname) != 0)
00662             ereport(ERROR,
00663                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
00664                      errmsg("SELECT rule's target entry %d has different column name from \"%s\"", i, attname)));
00665 
00666         if (attr->atttypid != exprType((Node *) tle->expr))
00667             ereport(ERROR,
00668                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
00669                      isSelect ?
00670                      errmsg("SELECT rule's target entry %d has different type from column \"%s\"",
00671                             i, attname) :
00672                      errmsg("RETURNING list's entry %d has different type from column \"%s\"",
00673                             i, attname)));
00674 
00675         /*
00676          * Allow typmods to be different only if one of them is -1, ie,
00677          * "unspecified".  This is necessary for cases like "numeric", where
00678          * the table will have a filled-in default length but the select
00679          * rule's expression will probably have typmod = -1.
00680          */
00681         tletypmod = exprTypmod((Node *) tle->expr);
00682         if (attr->atttypmod != tletypmod &&
00683             attr->atttypmod != -1 && tletypmod != -1)
00684             ereport(ERROR,
00685                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
00686                      isSelect ?
00687                      errmsg("SELECT rule's target entry %d has different size from column \"%s\"",
00688                             i, attname) :
00689                      errmsg("RETURNING list's entry %d has different size from column \"%s\"",
00690                             i, attname)));
00691     }
00692 
00693     if (i != resultDesc->natts)
00694         ereport(ERROR,
00695                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
00696                  isSelect ?
00697                  errmsg("SELECT rule's target list has too few entries") :
00698                  errmsg("RETURNING list has too few entries")));
00699 }
00700 
00701 /*
00702  * setRuleCheckAsUser
00703  *      Recursively scan a query or expression tree and set the checkAsUser
00704  *      field to the given userid in all rtable entries.
00705  *
00706  * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
00707  * RTE entry will be overridden when the view rule is expanded, and the
00708  * checkAsUser field of the NEW entry is irrelevant because that entry's
00709  * requiredPerms bits will always be zero.  However, for other types of rules
00710  * it's important to set these fields to match the rule owner.  So we just set
00711  * them always.
00712  */
00713 void
00714 setRuleCheckAsUser(Node *node, Oid userid)
00715 {
00716     (void) setRuleCheckAsUser_walker(node, &userid);
00717 }
00718 
00719 static bool
00720 setRuleCheckAsUser_walker(Node *node, Oid *context)
00721 {
00722     if (node == NULL)
00723         return false;
00724     if (IsA(node, Query))
00725     {
00726         setRuleCheckAsUser_Query((Query *) node, *context);
00727         return false;
00728     }
00729     return expression_tree_walker(node, setRuleCheckAsUser_walker,
00730                                   (void *) context);
00731 }
00732 
00733 static void
00734 setRuleCheckAsUser_Query(Query *qry, Oid userid)
00735 {
00736     ListCell   *l;
00737 
00738     /* Set all the RTEs in this query node */
00739     foreach(l, qry->rtable)
00740     {
00741         RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
00742 
00743         if (rte->rtekind == RTE_SUBQUERY)
00744         {
00745             /* Recurse into subquery in FROM */
00746             setRuleCheckAsUser_Query(rte->subquery, userid);
00747         }
00748         else
00749             rte->checkAsUser = userid;
00750     }
00751 
00752     /* Recurse into subquery-in-WITH */
00753     foreach(l, qry->cteList)
00754     {
00755         CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
00756 
00757         setRuleCheckAsUser_Query((Query *) cte->ctequery, userid);
00758     }
00759 
00760     /* If there are sublinks, search for them and process their RTEs */
00761     if (qry->hasSubLinks)
00762         query_tree_walker(qry, setRuleCheckAsUser_walker, (void *) &userid,
00763                           QTW_IGNORE_RC_SUBQUERIES);
00764 }
00765 
00766 
00767 /*
00768  * Change the firing semantics of an existing rule.
00769  */
00770 void
00771 EnableDisableRule(Relation rel, const char *rulename,
00772                   char fires_when)
00773 {
00774     Relation    pg_rewrite_desc;
00775     Oid         owningRel = RelationGetRelid(rel);
00776     Oid         eventRelationOid;
00777     HeapTuple   ruletup;
00778     bool        changed = false;
00779 
00780     /*
00781      * Find the rule tuple to change.
00782      */
00783     pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock);
00784     ruletup = SearchSysCacheCopy2(RULERELNAME,
00785                                   ObjectIdGetDatum(owningRel),
00786                                   PointerGetDatum(rulename));
00787     if (!HeapTupleIsValid(ruletup))
00788         ereport(ERROR,
00789                 (errcode(ERRCODE_UNDEFINED_OBJECT),
00790                  errmsg("rule \"%s\" for relation \"%s\" does not exist",
00791                         rulename, get_rel_name(owningRel))));
00792 
00793     /*
00794      * Verify that the user has appropriate permissions.
00795      */
00796     eventRelationOid = ((Form_pg_rewrite) GETSTRUCT(ruletup))->ev_class;
00797     Assert(eventRelationOid == owningRel);
00798     if (!pg_class_ownercheck(eventRelationOid, GetUserId()))
00799         aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
00800                        get_rel_name(eventRelationOid));
00801 
00802     /*
00803      * Change ev_enabled if it is different from the desired new state.
00804      */
00805     if (DatumGetChar(((Form_pg_rewrite) GETSTRUCT(ruletup))->ev_enabled) !=
00806         fires_when)
00807     {
00808         ((Form_pg_rewrite) GETSTRUCT(ruletup))->ev_enabled =
00809             CharGetDatum(fires_when);
00810         simple_heap_update(pg_rewrite_desc, &ruletup->t_self, ruletup);
00811 
00812         /* keep system catalog indexes current */
00813         CatalogUpdateIndexes(pg_rewrite_desc, ruletup);
00814 
00815         changed = true;
00816     }
00817 
00818     InvokeObjectPostAlterHook(RewriteRelationId,
00819                               HeapTupleGetOid(ruletup), 0);
00820 
00821     heap_freetuple(ruletup);
00822     heap_close(pg_rewrite_desc, RowExclusiveLock);
00823 
00824     /*
00825      * If we changed anything, broadcast a SI inval message to force each
00826      * backend (including our own!) to rebuild relation's relcache entry.
00827      * Otherwise they will fail to apply the change promptly.
00828      */
00829     if (changed)
00830         CacheInvalidateRelcache(rel);
00831 }
00832 
00833 
00834 /*
00835  * Perform permissions and integrity checks before acquiring a relation lock.
00836  */
00837 static void
00838 RangeVarCallbackForRenameRule(const RangeVar *rv, Oid relid, Oid oldrelid,
00839                               void *arg)
00840 {
00841     HeapTuple   tuple;
00842     Form_pg_class form;
00843 
00844     tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
00845     if (!HeapTupleIsValid(tuple))
00846         return;                 /* concurrently dropped */
00847     form = (Form_pg_class) GETSTRUCT(tuple);
00848 
00849     /* only tables and views can have rules */
00850     if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW)
00851         ereport(ERROR,
00852                 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
00853                  errmsg("\"%s\" is not a table or view", rv->relname)));
00854 
00855     if (!allowSystemTableMods && IsSystemClass(form))
00856         ereport(ERROR,
00857                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
00858                  errmsg("permission denied: \"%s\" is a system catalog",
00859                         rv->relname)));
00860 
00861     /* you must own the table to rename one of its rules */
00862     if (!pg_class_ownercheck(relid, GetUserId()))
00863         aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, rv->relname);
00864 
00865     ReleaseSysCache(tuple);
00866 }
00867 
00868 /*
00869  * Rename an existing rewrite rule.
00870  */
00871 Oid
00872 RenameRewriteRule(RangeVar *relation, const char *oldName,
00873                   const char *newName)
00874 {
00875     Oid         relid;
00876     Relation    targetrel;
00877     Relation    pg_rewrite_desc;
00878     HeapTuple   ruletup;
00879     Form_pg_rewrite ruleform;
00880     Oid         ruleOid;
00881 
00882     /*
00883      * Look up name, check permissions, and acquire lock (which we will NOT
00884      * release until end of transaction).
00885      */
00886     relid = RangeVarGetRelidExtended(relation, AccessExclusiveLock,
00887                                      false, false,
00888                                      RangeVarCallbackForRenameRule,
00889                                      NULL);
00890 
00891     /* Have lock already, so just need to build relcache entry. */
00892     targetrel = relation_open(relid, NoLock);
00893 
00894     /* Prepare to modify pg_rewrite */
00895     pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock);
00896 
00897     /* Fetch the rule's entry (it had better exist) */
00898     ruletup = SearchSysCacheCopy2(RULERELNAME,
00899                                   ObjectIdGetDatum(relid),
00900                                   PointerGetDatum(oldName));
00901     if (!HeapTupleIsValid(ruletup))
00902         ereport(ERROR,
00903                 (errcode(ERRCODE_UNDEFINED_OBJECT),
00904                  errmsg("rule \"%s\" for relation \"%s\" does not exist",
00905                         oldName, RelationGetRelationName(targetrel))));
00906     ruleform = (Form_pg_rewrite) GETSTRUCT(ruletup);
00907     ruleOid = HeapTupleGetOid(ruletup);
00908 
00909     /* rule with the new name should not already exist */
00910     if (IsDefinedRewriteRule(relid, newName))
00911         ereport(ERROR,
00912                 (errcode(ERRCODE_DUPLICATE_OBJECT),
00913                  errmsg("rule \"%s\" for relation \"%s\" already exists",
00914                         newName, RelationGetRelationName(targetrel))));
00915 
00916     /*
00917      * We disallow renaming ON SELECT rules, because they should always be
00918      * named "_RETURN".
00919      */
00920     if (ruleform->ev_type == CMD_SELECT + '0')
00921         ereport(ERROR,
00922                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
00923                  errmsg("renaming an ON SELECT rule is not allowed")));
00924 
00925     /* OK, do the update */
00926     namestrcpy(&(ruleform->rulename), newName);
00927 
00928     simple_heap_update(pg_rewrite_desc, &ruletup->t_self, ruletup);
00929 
00930     /* keep system catalog indexes current */
00931     CatalogUpdateIndexes(pg_rewrite_desc, ruletup);
00932 
00933     heap_freetuple(ruletup);
00934     heap_close(pg_rewrite_desc, RowExclusiveLock);
00935 
00936     /*
00937      * Invalidate relation's relcache entry so that other backends (and this
00938      * one too!) are sent SI message to make them rebuild relcache entries.
00939      * (Ideally this should happen automatically...)
00940      */
00941     CacheInvalidateRelcache(targetrel);
00942 
00943     /*
00944      * Close rel, but keep exclusive lock!
00945      */
00946     relation_close(targetrel, NoLock);
00947 
00948     return ruleOid;
00949 }