LCOV - code coverage report
Current view: top level - src/backend/rewrite - rewriteDefine.c (source / functions) Hit Total Coverage
Test: PostgreSQL Lines: 223 282 79.1 %
Date: 2017-09-29 13:40:31 Functions: 9 10 90.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * rewriteDefine.c
       4             :  *    routines for defining a rewrite rule
       5             :  *
       6             :  * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
       7             :  * Portions Copyright (c) 1994, Regents of the University of California
       8             :  *
       9             :  *
      10             :  * IDENTIFICATION
      11             :  *    src/backend/rewrite/rewriteDefine.c
      12             :  *
      13             :  *-------------------------------------------------------------------------
      14             :  */
      15             : #include "postgres.h"
      16             : 
      17             : #include "access/heapam.h"
      18             : #include "access/htup_details.h"
      19             : #include "access/multixact.h"
      20             : #include "access/transam.h"
      21             : #include "access/xact.h"
      22             : #include "catalog/catalog.h"
      23             : #include "catalog/dependency.h"
      24             : #include "catalog/heap.h"
      25             : #include "catalog/indexing.h"
      26             : #include "catalog/namespace.h"
      27             : #include "catalog/objectaccess.h"
      28             : #include "catalog/pg_rewrite.h"
      29             : #include "catalog/storage.h"
      30             : #include "commands/policy.h"
      31             : #include "miscadmin.h"
      32             : #include "nodes/nodeFuncs.h"
      33             : #include "parser/parse_utilcmd.h"
      34             : #include "rewrite/rewriteDefine.h"
      35             : #include "rewrite/rewriteManip.h"
      36             : #include "rewrite/rewriteSupport.h"
      37             : #include "utils/acl.h"
      38             : #include "utils/builtins.h"
      39             : #include "utils/inval.h"
      40             : #include "utils/lsyscache.h"
      41             : #include "utils/rel.h"
      42             : #include "utils/snapmgr.h"
      43             : #include "utils/syscache.h"
      44             : #include "utils/tqual.h"
      45             : 
      46             : 
      47             : static void checkRuleResultList(List *targetList, TupleDesc resultDesc,
      48             :                     bool isSelect, bool requireColumnNameMatch);
      49             : static bool setRuleCheckAsUser_walker(Node *node, Oid *context);
      50             : static void setRuleCheckAsUser_Query(Query *qry, Oid userid);
      51             : 
      52             : 
      53             : /*
      54             :  * InsertRule -
      55             :  *    takes the arguments and inserts them as a row into the system
      56             :  *    relation "pg_rewrite"
      57             :  */
      58             : static Oid
      59         523 : InsertRule(char *rulname,
      60             :            int evtype,
      61             :            Oid eventrel_oid,
      62             :            bool evinstead,
      63             :            Node *event_qual,
      64             :            List *action,
      65             :            bool replace)
      66             : {
      67         523 :     char       *evqual = nodeToString(event_qual);
      68         523 :     char       *actiontree = nodeToString((Node *) action);
      69             :     Datum       values[Natts_pg_rewrite];
      70             :     bool        nulls[Natts_pg_rewrite];
      71             :     bool        replaces[Natts_pg_rewrite];
      72             :     NameData    rname;
      73             :     Relation    pg_rewrite_desc;
      74             :     HeapTuple   tup,
      75             :                 oldtup;
      76             :     Oid         rewriteObjectId;
      77             :     ObjectAddress myself,
      78             :                 referenced;
      79         523 :     bool        is_update = false;
      80             : 
      81             :     /*
      82             :      * Set up *nulls and *values arrays
      83             :      */
      84         523 :     MemSet(nulls, false, sizeof(nulls));
      85             : 
      86         523 :     namestrcpy(&rname, rulname);
      87         523 :     values[Anum_pg_rewrite_rulename - 1] = NameGetDatum(&rname);
      88         523 :     values[Anum_pg_rewrite_ev_class - 1] = ObjectIdGetDatum(eventrel_oid);
      89         523 :     values[Anum_pg_rewrite_ev_type - 1] = CharGetDatum(evtype + '0');
      90         523 :     values[Anum_pg_rewrite_ev_enabled - 1] = CharGetDatum(RULE_FIRES_ON_ORIGIN);
      91         523 :     values[Anum_pg_rewrite_is_instead - 1] = BoolGetDatum(evinstead);
      92         523 :     values[Anum_pg_rewrite_ev_qual - 1] = CStringGetTextDatum(evqual);
      93         523 :     values[Anum_pg_rewrite_ev_action - 1] = CStringGetTextDatum(actiontree);
      94             : 
      95             :     /*
      96             :      * Ready to store new pg_rewrite tuple
      97             :      */
      98         523 :     pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock);
      99             : 
     100             :     /*
     101             :      * Check to see if we are replacing an existing tuple
     102             :      */
     103         523 :     oldtup = SearchSysCache2(RULERELNAME,
     104             :                              ObjectIdGetDatum(eventrel_oid),
     105             :                              PointerGetDatum(rulname));
     106             : 
     107         523 :     if (HeapTupleIsValid(oldtup))
     108             :     {
     109          22 :         if (!replace)
     110           0 :             ereport(ERROR,
     111             :                     (errcode(ERRCODE_DUPLICATE_OBJECT),
     112             :                      errmsg("rule \"%s\" for relation \"%s\" already exists",
     113             :                             rulname, get_rel_name(eventrel_oid))));
     114             : 
     115             :         /*
     116             :          * When replacing, we don't need to replace every attribute
     117             :          */
     118          22 :         MemSet(replaces, false, sizeof(replaces));
     119          22 :         replaces[Anum_pg_rewrite_ev_type - 1] = true;
     120          22 :         replaces[Anum_pg_rewrite_is_instead - 1] = true;
     121          22 :         replaces[Anum_pg_rewrite_ev_qual - 1] = true;
     122          22 :         replaces[Anum_pg_rewrite_ev_action - 1] = true;
     123             : 
     124          22 :         tup = heap_modify_tuple(oldtup, RelationGetDescr(pg_rewrite_desc),
     125             :                                 values, nulls, replaces);
     126             : 
     127          22 :         CatalogTupleUpdate(pg_rewrite_desc, &tup->t_self, tup);
     128             : 
     129          22 :         ReleaseSysCache(oldtup);
     130             : 
     131          22 :         rewriteObjectId = HeapTupleGetOid(tup);
     132          22 :         is_update = true;
     133             :     }
     134             :     else
     135             :     {
     136         501 :         tup = heap_form_tuple(pg_rewrite_desc->rd_att, values, nulls);
     137             : 
     138         501 :         rewriteObjectId = CatalogTupleInsert(pg_rewrite_desc, tup);
     139             :     }
     140             : 
     141             : 
     142         523 :     heap_freetuple(tup);
     143             : 
     144             :     /* If replacing, get rid of old dependencies and make new ones */
     145         523 :     if (is_update)
     146          22 :         deleteDependencyRecordsFor(RewriteRelationId, rewriteObjectId, false);
     147             : 
     148             :     /*
     149             :      * Install dependency on rule's relation to ensure it will go away on
     150             :      * relation deletion.  If the rule is ON SELECT, make the dependency
     151             :      * implicit --- this prevents deleting a view's SELECT rule.  Other kinds
     152             :      * of rules can be AUTO.
     153             :      */
     154         523 :     myself.classId = RewriteRelationId;
     155         523 :     myself.objectId = rewriteObjectId;
     156         523 :     myself.objectSubId = 0;
     157             : 
     158         523 :     referenced.classId = RelationRelationId;
     159         523 :     referenced.objectId = eventrel_oid;
     160         523 :     referenced.objectSubId = 0;
     161             : 
     162         523 :     recordDependencyOn(&myself, &referenced,
     163             :                        (evtype == CMD_SELECT) ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO);
     164             : 
     165             :     /*
     166             :      * Also install dependencies on objects referenced in action and qual.
     167             :      */
     168         523 :     recordDependencyOnExpr(&myself, (Node *) action, NIL,
     169             :                            DEPENDENCY_NORMAL);
     170             : 
     171         523 :     if (event_qual != NULL)
     172             :     {
     173             :         /* Find query containing OLD/NEW rtable entries */
     174          23 :         Query      *qry = linitial_node(Query, action);
     175             : 
     176          23 :         qry = getInsertSelectQuery(qry, NULL);
     177          23 :         recordDependencyOnExpr(&myself, event_qual, qry->rtable,
     178             :                                DEPENDENCY_NORMAL);
     179             :     }
     180             : 
     181             :     /* Post creation hook for new rule */
     182         523 :     InvokeObjectPostCreateHook(RewriteRelationId, rewriteObjectId, 0);
     183             : 
     184         523 :     heap_close(pg_rewrite_desc, RowExclusiveLock);
     185             : 
     186         523 :     return rewriteObjectId;
     187             : }
     188             : 
     189             : /*
     190             :  * DefineRule
     191             :  *      Execute a CREATE RULE command.
     192             :  */
     193             : ObjectAddress
     194         101 : DefineRule(RuleStmt *stmt, const char *queryString)
     195             : {
     196             :     List       *actions;
     197             :     Node       *whereClause;
     198             :     Oid         relId;
     199             : 
     200             :     /* Parse analysis. */
     201         101 :     transformRuleStmt(stmt, queryString, &actions, &whereClause);
     202             : 
     203             :     /*
     204             :      * Find and lock the relation.  Lock level should match
     205             :      * DefineQueryRewrite.
     206             :      */
     207          99 :     relId = RangeVarGetRelid(stmt->relation, AccessExclusiveLock, false);
     208             : 
     209             :     /* ... and execute */
     210         297 :     return DefineQueryRewrite(stmt->rulename,
     211             :                               relId,
     212             :                               whereClause,
     213             :                               stmt->event,
     214          99 :                               stmt->instead,
     215          99 :                               stmt->replace,
     216             :                               actions);
     217             : }
     218             : 
     219             : 
     220             : /*
     221             :  * DefineQueryRewrite
     222             :  *      Create a rule
     223             :  *
     224             :  * This is essentially the same as DefineRule() except that the rule's
     225             :  * action and qual have already been passed through parse analysis.
     226             :  */
     227             : ObjectAddress
     228         528 : DefineQueryRewrite(char *rulename,
     229             :                    Oid event_relid,
     230             :                    Node *event_qual,
     231             :                    CmdType event_type,
     232             :                    bool is_instead,
     233             :                    bool replace,
     234             :                    List *action)
     235             : {
     236             :     Relation    event_relation;
     237             :     ListCell   *l;
     238             :     Query      *query;
     239         528 :     bool        RelisBecomingView = false;
     240         528 :     Oid         ruleId = InvalidOid;
     241             :     ObjectAddress address;
     242             : 
     243             :     /*
     244             :      * If we are installing an ON SELECT rule, we had better grab
     245             :      * AccessExclusiveLock to ensure no SELECTs are currently running on the
     246             :      * event relation. For other types of rules, it would be sufficient to
     247             :      * grab ShareRowExclusiveLock to lock out insert/update/delete actions and
     248             :      * to ensure that we lock out current CREATE RULE statements; but because
     249             :      * of race conditions in access to catalog entries, we can't do that yet.
     250             :      *
     251             :      * Note that this lock level should match the one used in DefineRule.
     252             :      */
     253         528 :     event_relation = heap_open(event_relid, AccessExclusiveLock);
     254             : 
     255             :     /*
     256             :      * Verify relation is of a type that rules can sensibly be applied to.
     257             :      * Internal callers can target materialized views, but transformRuleStmt()
     258             :      * blocks them for users.  Don't mention them in the error message.
     259             :      */
     260         989 :     if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
     261         896 :         event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
     262         437 :         event_relation->rd_rel->relkind != RELKIND_VIEW &&
     263           2 :         event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
     264           0 :         ereport(ERROR,
     265             :                 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     266             :                  errmsg("\"%s\" is not a table or view",
     267             :                         RelationGetRelationName(event_relation))));
     268             : 
     269         528 :     if (!allowSystemTableMods && IsSystemRelation(event_relation))
     270           0 :         ereport(ERROR,
     271             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     272             :                  errmsg("permission denied: \"%s\" is a system catalog",
     273             :                         RelationGetRelationName(event_relation))));
     274             : 
     275             :     /*
     276             :      * Check user has permission to apply rules to this relation.
     277             :      */
     278         528 :     if (!pg_class_ownercheck(event_relid, GetUserId()))
     279           0 :         aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
     280           0 :                        RelationGetRelationName(event_relation));
     281             : 
     282             :     /*
     283             :      * No rule actions that modify OLD or NEW
     284             :      */
     285        1062 :     foreach(l, action)
     286             :     {
     287         534 :         query = lfirst_node(Query, l);
     288         534 :         if (query->resultRelation == 0)
     289         446 :             continue;
     290             :         /* Don't be fooled by INSERT/SELECT */
     291          88 :         if (query != getInsertSelectQuery(query, NULL))
     292           5 :             continue;
     293          83 :         if (query->resultRelation == PRS2_OLD_VARNO)
     294           0 :             ereport(ERROR,
     295             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     296             :                      errmsg("rule actions on OLD are not implemented"),
     297             :                      errhint("Use views or triggers instead.")));
     298          83 :         if (query->resultRelation == PRS2_NEW_VARNO)
     299           0 :             ereport(ERROR,
     300             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     301             :                      errmsg("rule actions on NEW are not implemented"),
     302             :                      errhint("Use triggers instead.")));
     303             :     }
     304             : 
     305         528 :     if (event_type == CMD_SELECT)
     306             :     {
     307             :         /*
     308             :          * Rules ON SELECT are restricted to view definitions
     309             :          *
     310             :          * So there cannot be INSTEAD NOTHING, ...
     311             :          */
     312         435 :         if (list_length(action) == 0)
     313           0 :             ereport(ERROR,
     314             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     315             :                      errmsg("INSTEAD NOTHING rules on SELECT are not implemented"),
     316             :                      errhint("Use views instead.")));
     317             : 
     318             :         /*
     319             :          * ... there cannot be multiple actions, ...
     320             :          */
     321         435 :         if (list_length(action) > 1)
     322           0 :             ereport(ERROR,
     323             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     324             :                      errmsg("multiple actions for rules on SELECT are not implemented")));
     325             : 
     326             :         /*
     327             :          * ... the one action must be a SELECT, ...
     328             :          */
     329         435 :         query = linitial_node(Query, action);
     330         870 :         if (!is_instead ||
     331         435 :             query->commandType != CMD_SELECT)
     332           0 :             ereport(ERROR,
     333             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     334             :                      errmsg("rules on SELECT must have action INSTEAD SELECT")));
     335             : 
     336             :         /*
     337             :          * ... it cannot contain data-modifying WITH ...
     338             :          */
     339         435 :         if (query->hasModifyingCTE)
     340           0 :             ereport(ERROR,
     341             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     342             :                      errmsg("rules on SELECT must not contain data-modifying statements in WITH")));
     343             : 
     344             :         /*
     345             :          * ... there can be no rule qual, ...
     346             :          */
     347         435 :         if (event_qual != NULL)
     348           0 :             ereport(ERROR,
     349             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     350             :                      errmsg("event qualifications are not implemented for rules on SELECT")));
     351             : 
     352             :         /*
     353             :          * ... the targetlist of the SELECT action must exactly match the
     354             :          * event relation, ...
     355             :          */
     356         435 :         checkRuleResultList(query->targetList,
     357             :                             RelationGetDescr(event_relation),
     358             :                             true,
     359         435 :                             event_relation->rd_rel->relkind !=
     360             :                             RELKIND_MATVIEW);
     361             : 
     362             :         /*
     363             :          * ... there must not be another ON SELECT rule already ...
     364             :          */
     365         435 :         if (!replace && event_relation->rd_rules != NULL)
     366             :         {
     367             :             int         i;
     368             : 
     369           0 :             for (i = 0; i < event_relation->rd_rules->numLocks; i++)
     370             :             {
     371             :                 RewriteRule *rule;
     372             : 
     373           0 :                 rule = event_relation->rd_rules->rules[i];
     374           0 :                 if (rule->event == CMD_SELECT)
     375           0 :                     ereport(ERROR,
     376             :                             (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     377             :                              errmsg("\"%s\" is already a view",
     378             :                                     RelationGetRelationName(event_relation))));
     379             :             }
     380             :         }
     381             : 
     382             :         /*
     383             :          * ... and finally the rule must be named _RETURN.
     384             :          */
     385         435 :         if (strcmp(rulename, ViewSelectRuleName) != 0)
     386             :         {
     387             :             /*
     388             :              * In versions before 7.3, the expected name was _RETviewname. For
     389             :              * backwards compatibility with old pg_dump output, accept that
     390             :              * and silently change it to _RETURN.  Since this is just a quick
     391             :              * backwards-compatibility hack, limit the number of characters
     392             :              * checked to a few less than NAMEDATALEN; this saves having to
     393             :              * worry about where a multibyte character might have gotten
     394             :              * truncated.
     395             :              */
     396           0 :             if (strncmp(rulename, "_RET", 4) != 0 ||
     397           0 :                 strncmp(rulename + 4, RelationGetRelationName(event_relation),
     398             :                         NAMEDATALEN - 4 - 4) != 0)
     399           0 :                 ereport(ERROR,
     400             :                         (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     401             :                          errmsg("view rule for \"%s\" must be named \"%s\"",
     402             :                                 RelationGetRelationName(event_relation),
     403             :                                 ViewSelectRuleName)));
     404           0 :             rulename = pstrdup(ViewSelectRuleName);
     405             :         }
     406             : 
     407             :         /*
     408             :          * Are we converting a relation to a view?
     409             :          *
     410             :          * If so, check that the relation is empty because the storage for the
     411             :          * relation is going to be deleted.  Also insist that the rel not have
     412             :          * any triggers, indexes, child tables, policies, or RLS enabled.
     413             :          * (Note: these tests are too strict, because they will reject
     414             :          * relations that once had such but don't anymore.  But we don't
     415             :          * really care, because this whole business of converting relations to
     416             :          * views is just a kluge to allow dump/reload of views that
     417             :          * participate in circular dependencies.)
     418             :          */
     419         467 :         if (event_relation->rd_rel->relkind != RELKIND_VIEW &&
     420          32 :             event_relation->rd_rel->relkind != RELKIND_MATVIEW)
     421             :         {
     422             :             HeapScanDesc scanDesc;
     423             :             Snapshot    snapshot;
     424             : 
     425           6 :             if (event_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
     426           1 :                 ereport(ERROR,
     427             :                         (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     428             :                          errmsg("could not convert partitioned table \"%s\" to a view",
     429             :                                 RelationGetRelationName(event_relation))));
     430             : 
     431           5 :             if (event_relation->rd_rel->relispartition)
     432           1 :                 ereport(ERROR,
     433             :                         (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     434             :                          errmsg("could not convert partition \"%s\" to a view",
     435             :                                 RelationGetRelationName(event_relation))));
     436             : 
     437           4 :             snapshot = RegisterSnapshot(GetLatestSnapshot());
     438           4 :             scanDesc = heap_beginscan(event_relation, snapshot, 0, NULL);
     439           4 :             if (heap_getnext(scanDesc, ForwardScanDirection) != NULL)
     440           0 :                 ereport(ERROR,
     441             :                         (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     442             :                          errmsg("could not convert table \"%s\" to a view because it is not empty",
     443             :                                 RelationGetRelationName(event_relation))));
     444           4 :             heap_endscan(scanDesc);
     445           4 :             UnregisterSnapshot(snapshot);
     446             : 
     447           4 :             if (event_relation->rd_rel->relhastriggers)
     448           0 :                 ereport(ERROR,
     449             :                         (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     450             :                          errmsg("could not convert table \"%s\" to a view because it has triggers",
     451             :                                 RelationGetRelationName(event_relation)),
     452             :                          errhint("In particular, the table cannot be involved in any foreign key relationships.")));
     453             : 
     454           4 :             if (event_relation->rd_rel->relhasindex)
     455           0 :                 ereport(ERROR,
     456             :                         (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     457             :                          errmsg("could not convert table \"%s\" to a view because it has indexes",
     458             :                                 RelationGetRelationName(event_relation))));
     459             : 
     460           4 :             if (event_relation->rd_rel->relhassubclass)
     461           0 :                 ereport(ERROR,
     462             :                         (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     463             :                          errmsg("could not convert table \"%s\" to a view because it has child tables",
     464             :                                 RelationGetRelationName(event_relation))));
     465             : 
     466           4 :             if (event_relation->rd_rel->relrowsecurity)
     467           1 :                 ereport(ERROR,
     468             :                         (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     469             :                          errmsg("could not convert table \"%s\" to a view because it has row security enabled",
     470             :                                 RelationGetRelationName(event_relation))));
     471             : 
     472           3 :             if (relation_has_policies(event_relation))
     473           1 :                 ereport(ERROR,
     474             :                         (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     475             :                          errmsg("could not convert table \"%s\" to a view because it has row security policies",
     476             :                                 RelationGetRelationName(event_relation))));
     477             : 
     478           2 :             RelisBecomingView = true;
     479             :         }
     480             :     }
     481             :     else
     482             :     {
     483             :         /*
     484             :          * For non-SELECT rules, a RETURNING list can appear in at most one of
     485             :          * the actions ... and there can't be any RETURNING list at all in a
     486             :          * conditional or non-INSTEAD rule.  (Actually, there can be at most
     487             :          * one RETURNING list across all rules on the same event, but it seems
     488             :          * best to enforce that at rule expansion time.)  If there is a
     489             :          * RETURNING list, it must match the event relation.
     490             :          */
     491          93 :         bool        haveReturning = false;
     492             : 
     493         191 :         foreach(l, action)
     494             :         {
     495          99 :             query = lfirst_node(Query, l);
     496             : 
     497          99 :             if (!query->returningList)
     498          83 :                 continue;
     499          16 :             if (haveReturning)
     500           0 :                 ereport(ERROR,
     501             :                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     502             :                          errmsg("cannot have multiple RETURNING lists in a rule")));
     503          16 :             haveReturning = true;
     504          16 :             if (event_qual != NULL)
     505           0 :                 ereport(ERROR,
     506             :                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     507             :                          errmsg("RETURNING lists are not supported in conditional rules")));
     508          16 :             if (!is_instead)
     509           0 :                 ereport(ERROR,
     510             :                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     511             :                          errmsg("RETURNING lists are not supported in non-INSTEAD rules")));
     512          16 :             checkRuleResultList(query->returningList,
     513             :                                 RelationGetDescr(event_relation),
     514             :                                 false, false);
     515             :         }
     516             :     }
     517             : 
     518             :     /*
     519             :      * This rule is allowed - prepare to install it.
     520             :      */
     521             : 
     522             :     /* discard rule if it's null action and not INSTEAD; it's a no-op */
     523         523 :     if (action != NIL || is_instead)
     524             :     {
     525         523 :         ruleId = InsertRule(rulename,
     526             :                             event_type,
     527             :                             event_relid,
     528             :                             is_instead,
     529             :                             event_qual,
     530             :                             action,
     531             :                             replace);
     532             : 
     533             :         /*
     534             :          * Set pg_class 'relhasrules' field TRUE for event relation.
     535             :          *
     536             :          * Important side effect: an SI notice is broadcast to force all
     537             :          * backends (including me!) to update relcache entries with the new
     538             :          * rule.
     539             :          */
     540         523 :         SetRelationRuleStatus(event_relid, true);
     541             :     }
     542             : 
     543             :     /* ---------------------------------------------------------------------
     544             :      * If the relation is becoming a view:
     545             :      * - delete the associated storage files
     546             :      * - get rid of any system attributes in pg_attribute; a view shouldn't
     547             :      *   have any of those
     548             :      * - remove the toast table; there is no need for it anymore, and its
     549             :      *   presence would make vacuum slightly more complicated
     550             :      * - set relkind to RELKIND_VIEW, and adjust other pg_class fields
     551             :      *   to be appropriate for a view
     552             :      *
     553             :      * NB: we had better have AccessExclusiveLock to do this ...
     554             :      * ---------------------------------------------------------------------
     555             :      */
     556         523 :     if (RelisBecomingView)
     557             :     {
     558             :         Relation    relationRelation;
     559             :         Oid         toastrelid;
     560             :         HeapTuple   classTup;
     561             :         Form_pg_class classForm;
     562             : 
     563           2 :         relationRelation = heap_open(RelationRelationId, RowExclusiveLock);
     564           2 :         toastrelid = event_relation->rd_rel->reltoastrelid;
     565             : 
     566             :         /* drop storage while table still looks like a table  */
     567           2 :         RelationDropStorage(event_relation);
     568           2 :         DeleteSystemAttributeTuples(event_relid);
     569             : 
     570             :         /*
     571             :          * Drop the toast table if any.  (This won't take care of updating the
     572             :          * toast fields in the relation's own pg_class entry; we handle that
     573             :          * below.)
     574             :          */
     575           2 :         if (OidIsValid(toastrelid))
     576             :         {
     577             :             ObjectAddress toastobject;
     578             : 
     579             :             /*
     580             :              * Delete the dependency of the toast relation on the main
     581             :              * relation so we can drop the former without dropping the latter.
     582             :              */
     583           1 :             deleteDependencyRecordsFor(RelationRelationId, toastrelid,
     584             :                                        false);
     585             : 
     586             :             /* Make deletion of dependency record visible */
     587           1 :             CommandCounterIncrement();
     588             : 
     589             :             /* Now drop toast table, including its index */
     590           1 :             toastobject.classId = RelationRelationId;
     591           1 :             toastobject.objectId = toastrelid;
     592           1 :             toastobject.objectSubId = 0;
     593           1 :             performDeletion(&toastobject, DROP_RESTRICT,
     594             :                             PERFORM_DELETION_INTERNAL);
     595             :         }
     596             : 
     597             :         /*
     598             :          * SetRelationRuleStatus may have updated the pg_class row, so we must
     599             :          * advance the command counter before trying to update it again.
     600             :          */
     601           2 :         CommandCounterIncrement();
     602             : 
     603             :         /*
     604             :          * Fix pg_class entry to look like a normal view's, including setting
     605             :          * the correct relkind and removal of reltoastrelid of the toast table
     606             :          * we potentially removed above.
     607             :          */
     608           2 :         classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(event_relid));
     609           2 :         if (!HeapTupleIsValid(classTup))
     610           0 :             elog(ERROR, "cache lookup failed for relation %u", event_relid);
     611           2 :         classForm = (Form_pg_class) GETSTRUCT(classTup);
     612             : 
     613           2 :         classForm->reltablespace = InvalidOid;
     614           2 :         classForm->relpages = 0;
     615           2 :         classForm->reltuples = 0;
     616           2 :         classForm->relallvisible = 0;
     617           2 :         classForm->reltoastrelid = InvalidOid;
     618           2 :         classForm->relhasindex = false;
     619           2 :         classForm->relkind = RELKIND_VIEW;
     620           2 :         classForm->relhasoids = false;
     621           2 :         classForm->relhaspkey = false;
     622           2 :         classForm->relfrozenxid = InvalidTransactionId;
     623           2 :         classForm->relminmxid = InvalidMultiXactId;
     624           2 :         classForm->relreplident = REPLICA_IDENTITY_NOTHING;
     625             : 
     626           2 :         CatalogTupleUpdate(relationRelation, &classTup->t_self, classTup);
     627             : 
     628           2 :         heap_freetuple(classTup);
     629           2 :         heap_close(relationRelation, RowExclusiveLock);
     630             :     }
     631             : 
     632         523 :     ObjectAddressSet(address, RewriteRelationId, ruleId);
     633             : 
     634             :     /* Close rel, but keep lock till commit... */
     635         523 :     heap_close(event_relation, NoLock);
     636             : 
     637         523 :     return address;
     638             : }
     639             : 
     640             : /*
     641             :  * checkRuleResultList
     642             :  *      Verify that targetList produces output compatible with a tupledesc
     643             :  *
     644             :  * The targetList might be either a SELECT targetlist, or a RETURNING list;
     645             :  * isSelect tells which.  This is used for choosing error messages.
     646             :  *
     647             :  * A SELECT targetlist may optionally require that column names match.
     648             :  */
     649             : static void
     650         451 : checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
     651             :                     bool requireColumnNameMatch)
     652             : {
     653             :     ListCell   *tllist;
     654             :     int         i;
     655             : 
     656             :     /* Only a SELECT may require a column name match. */
     657         451 :     Assert(isSelect || !requireColumnNameMatch);
     658             : 
     659         451 :     i = 0;
     660        2467 :     foreach(tllist, targetList)
     661             :     {
     662        2017 :         TargetEntry *tle = (TargetEntry *) lfirst(tllist);
     663             :         Oid         tletypid;
     664             :         int32       tletypmod;
     665             :         Form_pg_attribute attr;
     666             :         char       *attname;
     667             : 
     668             :         /* resjunk entries may be ignored */
     669        2017 :         if (tle->resjunk)
     670          12 :             continue;
     671        2005 :         i++;
     672        2005 :         if (i > resultDesc->natts)
     673           1 :             ereport(ERROR,
     674             :                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     675             :                      isSelect ?
     676             :                      errmsg("SELECT rule's target list has too many entries") :
     677             :                      errmsg("RETURNING list has too many entries")));
     678             : 
     679        2004 :         attr = TupleDescAttr(resultDesc, i - 1);
     680        2004 :         attname = NameStr(attr->attname);
     681             : 
     682             :         /*
     683             :          * Disallow dropped columns in the relation.  This is not really
     684             :          * expected to happen when creating an ON SELECT rule.  It'd be
     685             :          * possible if someone tried to convert a relation with dropped
     686             :          * columns to a view, but the only case we care about supporting
     687             :          * table-to-view conversion for is pg_dump, and pg_dump won't do that.
     688             :          *
     689             :          * Unfortunately, the situation is also possible when adding a rule
     690             :          * with RETURNING to a regular table, and rejecting that case is
     691             :          * altogether more annoying.  In principle we could support it by
     692             :          * modifying the targetlist to include dummy NULL columns
     693             :          * corresponding to the dropped columns in the tupdesc.  However,
     694             :          * places like ruleutils.c would have to be fixed to not process such
     695             :          * entries, and that would take an uncertain and possibly rather large
     696             :          * amount of work.  (Note we could not dodge that by marking the dummy
     697             :          * columns resjunk, since it's precisely the non-resjunk tlist columns
     698             :          * that are expected to correspond to table columns.)
     699             :          */
     700        2004 :         if (attr->attisdropped)
     701           0 :             ereport(ERROR,
     702             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     703             :                      isSelect ?
     704             :                      errmsg("cannot convert relation containing dropped columns to view") :
     705             :                      errmsg("cannot create a RETURNING list for a relation containing dropped columns")));
     706             : 
     707             :         /* Check name match if required; no need for two error texts here */
     708        2004 :         if (requireColumnNameMatch && strcmp(tle->resname, attname) != 0)
     709           0 :             ereport(ERROR,
     710             :                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     711             :                      errmsg("SELECT rule's target entry %d has different column name from column \"%s\"",
     712             :                             i, attname),
     713             :                      errdetail("SELECT target entry is named \"%s\".",
     714             :                                tle->resname)));
     715             : 
     716             :         /* Check type match. */
     717        2004 :         tletypid = exprType((Node *) tle->expr);
     718        2004 :         if (attr->atttypid != tletypid)
     719           0 :             ereport(ERROR,
     720             :                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     721             :                      isSelect ?
     722             :                      errmsg("SELECT rule's target entry %d has different type from column \"%s\"",
     723             :                             i, attname) :
     724             :                      errmsg("RETURNING list's entry %d has different type from column \"%s\"",
     725             :                             i, attname),
     726             :                      isSelect ?
     727             :                      errdetail("SELECT target entry has type %s, but column has type %s.",
     728             :                                format_type_be(tletypid),
     729             :                                format_type_be(attr->atttypid)) :
     730             :                      errdetail("RETURNING list entry has type %s, but column has type %s.",
     731             :                                format_type_be(tletypid),
     732             :                                format_type_be(attr->atttypid))));
     733             : 
     734             :         /*
     735             :          * Allow typmods to be different only if one of them is -1, ie,
     736             :          * "unspecified".  This is necessary for cases like "numeric", where
     737             :          * the table will have a filled-in default length but the select
     738             :          * rule's expression will probably have typmod = -1.
     739             :          */
     740        2004 :         tletypmod = exprTypmod((Node *) tle->expr);
     741        2004 :         if (attr->atttypmod != tletypmod &&
     742           0 :             attr->atttypmod != -1 && tletypmod != -1)
     743           0 :             ereport(ERROR,
     744             :                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     745             :                      isSelect ?
     746             :                      errmsg("SELECT rule's target entry %d has different size from column \"%s\"",
     747             :                             i, attname) :
     748             :                      errmsg("RETURNING list's entry %d has different size from column \"%s\"",
     749             :                             i, attname),
     750             :                      isSelect ?
     751             :                      errdetail("SELECT target entry has type %s, but column has type %s.",
     752             :                                format_type_with_typemod(tletypid, tletypmod),
     753             :                                format_type_with_typemod(attr->atttypid,
     754             :                                                         attr->atttypmod)) :
     755             :                      errdetail("RETURNING list entry has type %s, but column has type %s.",
     756             :                                format_type_with_typemod(tletypid, tletypmod),
     757             :                                format_type_with_typemod(attr->atttypid,
     758             :                                                         attr->atttypmod))));
     759             :     }
     760             : 
     761         450 :     if (i != resultDesc->natts)
     762           0 :         ereport(ERROR,
     763             :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     764             :                  isSelect ?
     765             :                  errmsg("SELECT rule's target list has too few entries") :
     766             :                  errmsg("RETURNING list has too few entries")));
     767         450 : }
     768             : 
     769             : /*
     770             :  * setRuleCheckAsUser
     771             :  *      Recursively scan a query or expression tree and set the checkAsUser
     772             :  *      field to the given userid in all rtable entries.
     773             :  *
     774             :  * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
     775             :  * RTE entry will be overridden when the view rule is expanded, and the
     776             :  * checkAsUser field of the NEW entry is irrelevant because that entry's
     777             :  * requiredPerms bits will always be zero.  However, for other types of rules
     778             :  * it's important to set these fields to match the rule owner.  So we just set
     779             :  * them always.
     780             :  */
     781             : void
     782        1968 : setRuleCheckAsUser(Node *node, Oid userid)
     783             : {
     784        1968 :     (void) setRuleCheckAsUser_walker(node, &userid);
     785        1968 : }
     786             : 
     787             : static bool
     788        7111 : setRuleCheckAsUser_walker(Node *node, Oid *context)
     789             : {
     790        7111 :     if (node == NULL)
     791        1994 :         return false;
     792        5117 :     if (IsA(node, Query))
     793             :     {
     794        1086 :         setRuleCheckAsUser_Query((Query *) node, *context);
     795        1086 :         return false;
     796             :     }
     797        4031 :     return expression_tree_walker(node, setRuleCheckAsUser_walker,
     798             :                                   (void *) context);
     799             : }
     800             : 
     801             : static void
     802        1260 : setRuleCheckAsUser_Query(Query *qry, Oid userid)
     803             : {
     804             :     ListCell   *l;
     805             : 
     806             :     /* Set all the RTEs in this query node */
     807        5173 :     foreach(l, qry->rtable)
     808             :     {
     809        3913 :         RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
     810             : 
     811        3913 :         if (rte->rtekind == RTE_SUBQUERY)
     812             :         {
     813             :             /* Recurse into subquery in FROM */
     814         166 :             setRuleCheckAsUser_Query(rte->subquery, userid);
     815             :         }
     816             :         else
     817        3747 :             rte->checkAsUser = userid;
     818             :     }
     819             : 
     820             :     /* Recurse into subquery-in-WITH */
     821        1268 :     foreach(l, qry->cteList)
     822             :     {
     823           8 :         CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
     824             : 
     825           8 :         setRuleCheckAsUser_Query(castNode(Query, cte->ctequery), userid);
     826             :     }
     827             : 
     828             :     /* If there are sublinks, search for them and process their RTEs */
     829        1260 :     if (qry->hasSubLinks)
     830          63 :         query_tree_walker(qry, setRuleCheckAsUser_walker, (void *) &userid,
     831             :                           QTW_IGNORE_RC_SUBQUERIES);
     832        1260 : }
     833             : 
     834             : 
     835             : /*
     836             :  * Change the firing semantics of an existing rule.
     837             :  */
     838             : void
     839           0 : EnableDisableRule(Relation rel, const char *rulename,
     840             :                   char fires_when)
     841             : {
     842             :     Relation    pg_rewrite_desc;
     843           0 :     Oid         owningRel = RelationGetRelid(rel);
     844             :     Oid         eventRelationOid;
     845             :     HeapTuple   ruletup;
     846           0 :     bool        changed = false;
     847             : 
     848             :     /*
     849             :      * Find the rule tuple to change.
     850             :      */
     851           0 :     pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock);
     852           0 :     ruletup = SearchSysCacheCopy2(RULERELNAME,
     853             :                                   ObjectIdGetDatum(owningRel),
     854             :                                   PointerGetDatum(rulename));
     855           0 :     if (!HeapTupleIsValid(ruletup))
     856           0 :         ereport(ERROR,
     857             :                 (errcode(ERRCODE_UNDEFINED_OBJECT),
     858             :                  errmsg("rule \"%s\" for relation \"%s\" does not exist",
     859             :                         rulename, get_rel_name(owningRel))));
     860             : 
     861             :     /*
     862             :      * Verify that the user has appropriate permissions.
     863             :      */
     864           0 :     eventRelationOid = ((Form_pg_rewrite) GETSTRUCT(ruletup))->ev_class;
     865           0 :     Assert(eventRelationOid == owningRel);
     866           0 :     if (!pg_class_ownercheck(eventRelationOid, GetUserId()))
     867           0 :         aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
     868           0 :                        get_rel_name(eventRelationOid));
     869             : 
     870             :     /*
     871             :      * Change ev_enabled if it is different from the desired new state.
     872             :      */
     873           0 :     if (DatumGetChar(((Form_pg_rewrite) GETSTRUCT(ruletup))->ev_enabled) !=
     874             :         fires_when)
     875             :     {
     876           0 :         ((Form_pg_rewrite) GETSTRUCT(ruletup))->ev_enabled =
     877             :             CharGetDatum(fires_when);
     878           0 :         CatalogTupleUpdate(pg_rewrite_desc, &ruletup->t_self, ruletup);
     879             : 
     880           0 :         changed = true;
     881             :     }
     882             : 
     883           0 :     InvokeObjectPostAlterHook(RewriteRelationId,
     884             :                               HeapTupleGetOid(ruletup), 0);
     885             : 
     886           0 :     heap_freetuple(ruletup);
     887           0 :     heap_close(pg_rewrite_desc, RowExclusiveLock);
     888             : 
     889             :     /*
     890             :      * If we changed anything, broadcast a SI inval message to force each
     891             :      * backend (including our own!) to rebuild relation's relcache entry.
     892             :      * Otherwise they will fail to apply the change promptly.
     893             :      */
     894           0 :     if (changed)
     895           0 :         CacheInvalidateRelcache(rel);
     896           0 : }
     897             : 
     898             : 
     899             : /*
     900             :  * Perform permissions and integrity checks before acquiring a relation lock.
     901             :  */
     902             : static void
     903           5 : RangeVarCallbackForRenameRule(const RangeVar *rv, Oid relid, Oid oldrelid,
     904             :                               void *arg)
     905             : {
     906             :     HeapTuple   tuple;
     907             :     Form_pg_class form;
     908             : 
     909           5 :     tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
     910           5 :     if (!HeapTupleIsValid(tuple))
     911           5 :         return;                 /* concurrently dropped */
     912           5 :     form = (Form_pg_class) GETSTRUCT(tuple);
     913             : 
     914             :     /* only tables and views can have rules */
     915          10 :     if (form->relkind != RELKIND_RELATION &&
     916           6 :         form->relkind != RELKIND_VIEW &&
     917           1 :         form->relkind != RELKIND_PARTITIONED_TABLE)
     918           0 :         ereport(ERROR,
     919             :                 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     920             :                  errmsg("\"%s\" is not a table or view", rv->relname)));
     921             : 
     922           5 :     if (!allowSystemTableMods && IsSystemClass(relid, form))
     923           0 :         ereport(ERROR,
     924             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     925             :                  errmsg("permission denied: \"%s\" is a system catalog",
     926             :                         rv->relname)));
     927             : 
     928             :     /* you must own the table to rename one of its rules */
     929           5 :     if (!pg_class_ownercheck(relid, GetUserId()))
     930           0 :         aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, rv->relname);
     931             : 
     932           5 :     ReleaseSysCache(tuple);
     933             : }
     934             : 
     935             : /*
     936             :  * Rename an existing rewrite rule.
     937             :  */
     938             : ObjectAddress
     939           5 : RenameRewriteRule(RangeVar *relation, const char *oldName,
     940             :                   const char *newName)
     941             : {
     942             :     Oid         relid;
     943             :     Relation    targetrel;
     944             :     Relation    pg_rewrite_desc;
     945             :     HeapTuple   ruletup;
     946             :     Form_pg_rewrite ruleform;
     947             :     Oid         ruleOid;
     948             :     ObjectAddress address;
     949             : 
     950             :     /*
     951             :      * Look up name, check permissions, and acquire lock (which we will NOT
     952             :      * release until end of transaction).
     953             :      */
     954           5 :     relid = RangeVarGetRelidExtended(relation, AccessExclusiveLock,
     955             :                                      false, false,
     956             :                                      RangeVarCallbackForRenameRule,
     957             :                                      NULL);
     958             : 
     959             :     /* Have lock already, so just need to build relcache entry. */
     960           5 :     targetrel = relation_open(relid, NoLock);
     961             : 
     962             :     /* Prepare to modify pg_rewrite */
     963           5 :     pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock);
     964             : 
     965             :     /* Fetch the rule's entry (it had better exist) */
     966           5 :     ruletup = SearchSysCacheCopy2(RULERELNAME,
     967             :                                   ObjectIdGetDatum(relid),
     968             :                                   PointerGetDatum(oldName));
     969           5 :     if (!HeapTupleIsValid(ruletup))
     970           1 :         ereport(ERROR,
     971             :                 (errcode(ERRCODE_UNDEFINED_OBJECT),
     972             :                  errmsg("rule \"%s\" for relation \"%s\" does not exist",
     973             :                         oldName, RelationGetRelationName(targetrel))));
     974           4 :     ruleform = (Form_pg_rewrite) GETSTRUCT(ruletup);
     975           4 :     ruleOid = HeapTupleGetOid(ruletup);
     976             : 
     977             :     /* rule with the new name should not already exist */
     978           4 :     if (IsDefinedRewriteRule(relid, newName))
     979           1 :         ereport(ERROR,
     980             :                 (errcode(ERRCODE_DUPLICATE_OBJECT),
     981             :                  errmsg("rule \"%s\" for relation \"%s\" already exists",
     982             :                         newName, RelationGetRelationName(targetrel))));
     983             : 
     984             :     /*
     985             :      * We disallow renaming ON SELECT rules, because they should always be
     986             :      * named "_RETURN".
     987             :      */
     988           3 :     if (ruleform->ev_type == CMD_SELECT + '0')
     989           1 :         ereport(ERROR,
     990             :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     991             :                  errmsg("renaming an ON SELECT rule is not allowed")));
     992             : 
     993             :     /* OK, do the update */
     994           2 :     namestrcpy(&(ruleform->rulename), newName);
     995             : 
     996           2 :     CatalogTupleUpdate(pg_rewrite_desc, &ruletup->t_self, ruletup);
     997             : 
     998           2 :     heap_freetuple(ruletup);
     999           2 :     heap_close(pg_rewrite_desc, RowExclusiveLock);
    1000             : 
    1001             :     /*
    1002             :      * Invalidate relation's relcache entry so that other backends (and this
    1003             :      * one too!) are sent SI message to make them rebuild relcache entries.
    1004             :      * (Ideally this should happen automatically...)
    1005             :      */
    1006           2 :     CacheInvalidateRelcache(targetrel);
    1007             : 
    1008           2 :     ObjectAddressSet(address, RewriteRelationId, ruleOid);
    1009             : 
    1010             :     /*
    1011             :      * Close rel, but keep exclusive lock!
    1012             :      */
    1013           2 :     relation_close(targetrel, NoLock);
    1014             : 
    1015           2 :     return address;
    1016             : }

Generated by: LCOV version 1.11