LCOV - code coverage report
Current view: top level - src/backend/rewrite - rewriteDefine.c (source / functions) Hit Total Coverage
Test: PostgreSQL 12devel Lines: 238 279 85.3 %
Date: 2018-09-20 15:21:09 Functions: 10 10 100.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-2018, 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       37334 : InsertRule(const char *rulname,
      60             :            int evtype,
      61             :            Oid eventrel_oid,
      62             :            bool evinstead,
      63             :            Node *event_qual,
      64             :            List *action,
      65             :            bool replace)
      66             : {
      67       37334 :     char       *evqual = nodeToString(event_qual);
      68       37334 :     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       37334 :     bool        is_update = false;
      80             : 
      81             :     /*
      82             :      * Set up *nulls and *values arrays
      83             :      */
      84       37334 :     MemSet(nulls, false, sizeof(nulls));
      85             : 
      86       37334 :     namestrcpy(&rname, rulname);
      87       37334 :     values[Anum_pg_rewrite_rulename - 1] = NameGetDatum(&rname);
      88       37334 :     values[Anum_pg_rewrite_ev_class - 1] = ObjectIdGetDatum(eventrel_oid);
      89       37334 :     values[Anum_pg_rewrite_ev_type - 1] = CharGetDatum(evtype + '0');
      90       37334 :     values[Anum_pg_rewrite_ev_enabled - 1] = CharGetDatum(RULE_FIRES_ON_ORIGIN);
      91       37334 :     values[Anum_pg_rewrite_is_instead - 1] = BoolGetDatum(evinstead);
      92       37334 :     values[Anum_pg_rewrite_ev_qual - 1] = CStringGetTextDatum(evqual);
      93       37334 :     values[Anum_pg_rewrite_ev_action - 1] = CStringGetTextDatum(actiontree);
      94             : 
      95             :     /*
      96             :      * Ready to store new pg_rewrite tuple
      97             :      */
      98       37334 :     pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock);
      99             : 
     100             :     /*
     101             :      * Check to see if we are replacing an existing tuple
     102             :      */
     103       37334 :     oldtup = SearchSysCache2(RULERELNAME,
     104             :                              ObjectIdGetDatum(eventrel_oid),
     105             :                              PointerGetDatum(rulname));
     106             : 
     107       37334 :     if (HeapTupleIsValid(oldtup))
     108             :     {
     109         118 :         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         118 :         MemSet(replaces, false, sizeof(replaces));
     119         118 :         replaces[Anum_pg_rewrite_ev_type - 1] = true;
     120         118 :         replaces[Anum_pg_rewrite_is_instead - 1] = true;
     121         118 :         replaces[Anum_pg_rewrite_ev_qual - 1] = true;
     122         118 :         replaces[Anum_pg_rewrite_ev_action - 1] = true;
     123             : 
     124         118 :         tup = heap_modify_tuple(oldtup, RelationGetDescr(pg_rewrite_desc),
     125             :                                 values, nulls, replaces);
     126             : 
     127         118 :         CatalogTupleUpdate(pg_rewrite_desc, &tup->t_self, tup);
     128             : 
     129         118 :         ReleaseSysCache(oldtup);
     130             : 
     131         118 :         rewriteObjectId = HeapTupleGetOid(tup);
     132         118 :         is_update = true;
     133             :     }
     134             :     else
     135             :     {
     136       37216 :         tup = heap_form_tuple(pg_rewrite_desc->rd_att, values, nulls);
     137             : 
     138       37216 :         rewriteObjectId = CatalogTupleInsert(pg_rewrite_desc, tup);
     139             :     }
     140             : 
     141             : 
     142       37334 :     heap_freetuple(tup);
     143             : 
     144             :     /* If replacing, get rid of old dependencies and make new ones */
     145       37334 :     if (is_update)
     146         118 :         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       37334 :     myself.classId = RewriteRelationId;
     155       37334 :     myself.objectId = rewriteObjectId;
     156       37334 :     myself.objectSubId = 0;
     157             : 
     158       37334 :     referenced.classId = RelationRelationId;
     159       37334 :     referenced.objectId = eventrel_oid;
     160       37334 :     referenced.objectSubId = 0;
     161             : 
     162       37334 :     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       37334 :     recordDependencyOnExpr(&myself, (Node *) action, NIL,
     169             :                            DEPENDENCY_NORMAL);
     170             : 
     171       37334 :     if (event_qual != NULL)
     172             :     {
     173             :         /* Find query containing OLD/NEW rtable entries */
     174         404 :         Query      *qry = linitial_node(Query, action);
     175             : 
     176         404 :         qry = getInsertSelectQuery(qry, NULL);
     177         404 :         recordDependencyOnExpr(&myself, event_qual, qry->rtable,
     178             :                                DEPENDENCY_NORMAL);
     179             :     }
     180             : 
     181             :     /* Post creation hook for new rule */
     182       37334 :     InvokeObjectPostCreateHook(RewriteRelationId, rewriteObjectId, 0);
     183             : 
     184       37334 :     heap_close(pg_rewrite_desc, RowExclusiveLock);
     185             : 
     186       37334 :     return rewriteObjectId;
     187             : }
     188             : 
     189             : /*
     190             :  * DefineRule
     191             :  *      Execute a CREATE RULE command.
     192             :  */
     193             : ObjectAddress
     194        1066 : DefineRule(RuleStmt *stmt, const char *queryString)
     195             : {
     196             :     List       *actions;
     197             :     Node       *whereClause;
     198             :     Oid         relId;
     199             : 
     200             :     /* Parse analysis. */
     201        1066 :     transformRuleStmt(stmt, queryString, &actions, &whereClause);
     202             : 
     203             :     /*
     204             :      * Find and lock the relation.  Lock level should match
     205             :      * DefineQueryRewrite.
     206             :      */
     207        1058 :     relId = RangeVarGetRelid(stmt->relation, AccessExclusiveLock, false);
     208             : 
     209             :     /* ... and execute */
     210        3174 :     return DefineQueryRewrite(stmt->rulename,
     211             :                               relId,
     212             :                               whereClause,
     213             :                               stmt->event,
     214        1058 :                               stmt->instead,
     215        1058 :                               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       37354 : DefineQueryRewrite(const 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       37354 :     bool        RelisBecomingView = false;
     240       37354 :     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       37354 :     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       74368 :     if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
     261       73884 :         event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
     262       36878 :         event_relation->rd_rel->relkind != RELKIND_VIEW &&
     263           8 :         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       37354 :     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       37354 :     if (!pg_class_ownercheck(event_relid, GetUserId()))
     279           0 :         aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(event_relation->rd_rel->relkind),
     280           0 :                        RelationGetRelationName(event_relation));
     281             : 
     282             :     /*
     283             :      * No rule actions that modify OLD or NEW
     284             :      */
     285       74736 :     foreach(l, action)
     286             :     {
     287       37382 :         query = lfirst_node(Query, l);
     288       37382 :         if (query->resultRelation == 0)
     289       36958 :             continue;
     290             :         /* Don't be fooled by INSERT/SELECT */
     291         424 :         if (query != getInsertSelectQuery(query, NULL))
     292          22 :             continue;
     293         402 :         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         402 :         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       37354 :     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       36322 :         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       36322 :         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       36322 :         query = linitial_node(Query, action);
     330       72644 :         if (!is_instead ||
     331       36322 :             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       36322 :         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       36322 :         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       36322 :         checkRuleResultList(query->targetList,
     357             :                             RelationGetDescr(event_relation),
     358             :                             true,
     359       36322 :                             event_relation->rd_rel->relkind !=
     360             :                             RELKIND_MATVIEW);
     361             : 
     362             :         /*
     363             :          * ... there must not be another ON SELECT rule already ...
     364             :          */
     365       36322 :         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       36322 :         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       36492 :         if (event_relation->rd_rel->relkind != RELKIND_VIEW &&
     420         170 :             event_relation->rd_rel->relkind != RELKIND_MATVIEW)
     421             :         {
     422             :             HeapScanDesc scanDesc;
     423             :             Snapshot    snapshot;
     424             : 
     425          26 :             if (event_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
     426           4 :                 ereport(ERROR,
     427             :                         (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     428             :                          errmsg("cannot convert partitioned table \"%s\" to a view",
     429             :                                 RelationGetRelationName(event_relation))));
     430             : 
     431          22 :             if (event_relation->rd_rel->relispartition)
     432           4 :                 ereport(ERROR,
     433             :                         (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     434             :                          errmsg("cannot convert partition \"%s\" to a view",
     435             :                                 RelationGetRelationName(event_relation))));
     436             : 
     437          18 :             snapshot = RegisterSnapshot(GetLatestSnapshot());
     438          18 :             scanDesc = heap_beginscan(event_relation, snapshot, 0, NULL);
     439          18 :             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          18 :             heap_endscan(scanDesc);
     445          18 :             UnregisterSnapshot(snapshot);
     446             : 
     447          18 :             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          18 :             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          18 :             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          18 :             if (event_relation->rd_rel->relrowsecurity)
     467           4 :                 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          14 :             if (relation_has_policies(event_relation))
     473           4 :                 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          10 :             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        1032 :         bool        haveReturning = false;
     492             : 
     493        2088 :         foreach(l, action)
     494             :         {
     495        1060 :             query = lfirst_node(Query, l);
     496             : 
     497        1060 :             if (!query->returningList)
     498         994 :                 continue;
     499          66 :             if (haveReturning)
     500           0 :                 ereport(ERROR,
     501             :                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     502             :                          errmsg("cannot have multiple RETURNING lists in a rule")));
     503          66 :             haveReturning = true;
     504          66 :             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          66 :             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          66 :             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       37334 :     if (action != NIL || is_instead)
     524             :     {
     525       37334 :         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       37334 :         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       37334 :     if (RelisBecomingView)
     557             :     {
     558             :         Relation    relationRelation;
     559             :         Oid         toastrelid;
     560             :         HeapTuple   classTup;
     561             :         Form_pg_class classForm;
     562             : 
     563          10 :         relationRelation = heap_open(RelationRelationId, RowExclusiveLock);
     564          10 :         toastrelid = event_relation->rd_rel->reltoastrelid;
     565             : 
     566             :         /* drop storage while table still looks like a table  */
     567          10 :         RelationDropStorage(event_relation);
     568          10 :         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          10 :         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           6 :             deleteDependencyRecordsFor(RelationRelationId, toastrelid,
     584             :                                        false);
     585             : 
     586             :             /* Make deletion of dependency record visible */
     587           6 :             CommandCounterIncrement();
     588             : 
     589             :             /* Now drop toast table, including its index */
     590           6 :             toastobject.classId = RelationRelationId;
     591           6 :             toastobject.objectId = toastrelid;
     592           6 :             toastobject.objectSubId = 0;
     593           6 :             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          10 :         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          10 :         classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(event_relid));
     609          10 :         if (!HeapTupleIsValid(classTup))
     610           0 :             elog(ERROR, "cache lookup failed for relation %u", event_relid);
     611          10 :         classForm = (Form_pg_class) GETSTRUCT(classTup);
     612             : 
     613          10 :         classForm->reltablespace = InvalidOid;
     614          10 :         classForm->relpages = 0;
     615          10 :         classForm->reltuples = 0;
     616          10 :         classForm->relallvisible = 0;
     617          10 :         classForm->reltoastrelid = InvalidOid;
     618          10 :         classForm->relhasindex = false;
     619          10 :         classForm->relkind = RELKIND_VIEW;
     620          10 :         classForm->relhasoids = false;
     621          10 :         classForm->relfrozenxid = InvalidTransactionId;
     622          10 :         classForm->relminmxid = InvalidMultiXactId;
     623          10 :         classForm->relreplident = REPLICA_IDENTITY_NOTHING;
     624             : 
     625          10 :         CatalogTupleUpdate(relationRelation, &classTup->t_self, classTup);
     626             : 
     627          10 :         heap_freetuple(classTup);
     628          10 :         heap_close(relationRelation, RowExclusiveLock);
     629             :     }
     630             : 
     631       37334 :     ObjectAddressSet(address, RewriteRelationId, ruleId);
     632             : 
     633             :     /* Close rel, but keep lock till commit... */
     634       37334 :     heap_close(event_relation, NoLock);
     635             : 
     636       37334 :     return address;
     637             : }
     638             : 
     639             : /*
     640             :  * checkRuleResultList
     641             :  *      Verify that targetList produces output compatible with a tupledesc
     642             :  *
     643             :  * The targetList might be either a SELECT targetlist, or a RETURNING list;
     644             :  * isSelect tells which.  This is used for choosing error messages.
     645             :  *
     646             :  * A SELECT targetlist may optionally require that column names match.
     647             :  */
     648             : static void
     649       36388 : checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
     650             :                     bool requireColumnNameMatch)
     651             : {
     652             :     ListCell   *tllist;
     653             :     int         i;
     654             : 
     655             :     /* Only a SELECT may require a column name match. */
     656             :     Assert(isSelect || !requireColumnNameMatch);
     657             : 
     658       36388 :     i = 0;
     659      387410 :     foreach(tllist, targetList)
     660             :     {
     661      351026 :         TargetEntry *tle = (TargetEntry *) lfirst(tllist);
     662             :         Oid         tletypid;
     663             :         int32       tletypmod;
     664             :         Form_pg_attribute attr;
     665             :         char       *attname;
     666             : 
     667             :         /* resjunk entries may be ignored */
     668      351026 :         if (tle->resjunk)
     669        2952 :             continue;
     670      348074 :         i++;
     671      348074 :         if (i > resultDesc->natts)
     672           4 :             ereport(ERROR,
     673             :                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     674             :                      isSelect ?
     675             :                      errmsg("SELECT rule's target list has too many entries") :
     676             :                      errmsg("RETURNING list has too many entries")));
     677             : 
     678      348070 :         attr = TupleDescAttr(resultDesc, i - 1);
     679      348070 :         attname = NameStr(attr->attname);
     680             : 
     681             :         /*
     682             :          * Disallow dropped columns in the relation.  This is not really
     683             :          * expected to happen when creating an ON SELECT rule.  It'd be
     684             :          * possible if someone tried to convert a relation with dropped
     685             :          * columns to a view, but the only case we care about supporting
     686             :          * table-to-view conversion for is pg_dump, and pg_dump won't do that.
     687             :          *
     688             :          * Unfortunately, the situation is also possible when adding a rule
     689             :          * with RETURNING to a regular table, and rejecting that case is
     690             :          * altogether more annoying.  In principle we could support it by
     691             :          * modifying the targetlist to include dummy NULL columns
     692             :          * corresponding to the dropped columns in the tupdesc.  However,
     693             :          * places like ruleutils.c would have to be fixed to not process such
     694             :          * entries, and that would take an uncertain and possibly rather large
     695             :          * amount of work.  (Note we could not dodge that by marking the dummy
     696             :          * columns resjunk, since it's precisely the non-resjunk tlist columns
     697             :          * that are expected to correspond to table columns.)
     698             :          */
     699      348070 :         if (attr->attisdropped)
     700           0 :             ereport(ERROR,
     701             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     702             :                      isSelect ?
     703             :                      errmsg("cannot convert relation containing dropped columns to view") :
     704             :                      errmsg("cannot create a RETURNING list for a relation containing dropped columns")));
     705             : 
     706             :         /* Check name match if required; no need for two error texts here */
     707      348070 :         if (requireColumnNameMatch && strcmp(tle->resname, attname) != 0)
     708           0 :             ereport(ERROR,
     709             :                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     710             :                      errmsg("SELECT rule's target entry %d has different column name from column \"%s\"",
     711             :                             i, attname),
     712             :                      errdetail("SELECT target entry is named \"%s\".",
     713             :                                tle->resname)));
     714             : 
     715             :         /* Check type match. */
     716      348070 :         tletypid = exprType((Node *) tle->expr);
     717      348070 :         if (attr->atttypid != tletypid)
     718           0 :             ereport(ERROR,
     719             :                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     720             :                      isSelect ?
     721             :                      errmsg("SELECT rule's target entry %d has different type from column \"%s\"",
     722             :                             i, attname) :
     723             :                      errmsg("RETURNING list's entry %d has different type from column \"%s\"",
     724             :                             i, attname),
     725             :                      isSelect ?
     726             :                      errdetail("SELECT target entry has type %s, but column has type %s.",
     727             :                                format_type_be(tletypid),
     728             :                                format_type_be(attr->atttypid)) :
     729             :                      errdetail("RETURNING list entry has type %s, but column has type %s.",
     730             :                                format_type_be(tletypid),
     731             :                                format_type_be(attr->atttypid))));
     732             : 
     733             :         /*
     734             :          * Allow typmods to be different only if one of them is -1, ie,
     735             :          * "unspecified".  This is necessary for cases like "numeric", where
     736             :          * the table will have a filled-in default length but the select
     737             :          * rule's expression will probably have typmod = -1.
     738             :          */
     739      348070 :         tletypmod = exprTypmod((Node *) tle->expr);
     740      348070 :         if (attr->atttypmod != tletypmod &&
     741           0 :             attr->atttypmod != -1 && tletypmod != -1)
     742           0 :             ereport(ERROR,
     743             :                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     744             :                      isSelect ?
     745             :                      errmsg("SELECT rule's target entry %d has different size from column \"%s\"",
     746             :                             i, attname) :
     747             :                      errmsg("RETURNING list's entry %d has different size from column \"%s\"",
     748             :                             i, attname),
     749             :                      isSelect ?
     750             :                      errdetail("SELECT target entry has type %s, but column has type %s.",
     751             :                                format_type_with_typemod(tletypid, tletypmod),
     752             :                                format_type_with_typemod(attr->atttypid,
     753             :                                                         attr->atttypmod)) :
     754             :                      errdetail("RETURNING list entry has type %s, but column has type %s.",
     755             :                                format_type_with_typemod(tletypid, tletypmod),
     756             :                                format_type_with_typemod(attr->atttypid,
     757             :                                                         attr->atttypmod))));
     758             :     }
     759             : 
     760       36384 :     if (i != resultDesc->natts)
     761           0 :         ereport(ERROR,
     762             :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     763             :                  isSelect ?
     764             :                  errmsg("SELECT rule's target list has too few entries") :
     765             :                  errmsg("RETURNING list has too few entries")));
     766       36384 : }
     767             : 
     768             : /*
     769             :  * setRuleCheckAsUser
     770             :  *      Recursively scan a query or expression tree and set the checkAsUser
     771             :  *      field to the given userid in all rtable entries.
     772             :  *
     773             :  * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
     774             :  * RTE entry will be overridden when the view rule is expanded, and the
     775             :  * checkAsUser field of the NEW entry is irrelevant because that entry's
     776             :  * requiredPerms bits will always be zero.  However, for other types of rules
     777             :  * it's important to set these fields to match the rule owner.  So we just set
     778             :  * them always.
     779             :  */
     780             : void
     781       29508 : setRuleCheckAsUser(Node *node, Oid userid)
     782             : {
     783       29508 :     (void) setRuleCheckAsUser_walker(node, &userid);
     784       29508 : }
     785             : 
     786             : static bool
     787       90052 : setRuleCheckAsUser_walker(Node *node, Oid *context)
     788             : {
     789       90052 :     if (node == NULL)
     790       24658 :         return false;
     791       65394 :     if (IsA(node, Query))
     792             :     {
     793       15620 :         setRuleCheckAsUser_Query((Query *) node, *context);
     794       15620 :         return false;
     795             :     }
     796       49774 :     return expression_tree_walker(node, setRuleCheckAsUser_walker,
     797             :                                   (void *) context);
     798             : }
     799             : 
     800             : static void
     801       30242 : setRuleCheckAsUser_Query(Query *qry, Oid userid)
     802             : {
     803             :     ListCell   *l;
     804             : 
     805             :     /* Set all the RTEs in this query node */
     806      138092 :     foreach(l, qry->rtable)
     807             :     {
     808      107850 :         RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
     809             : 
     810      107850 :         if (rte->rtekind == RTE_SUBQUERY)
     811             :         {
     812             :             /* Recurse into subquery in FROM */
     813       14576 :             setRuleCheckAsUser_Query(rte->subquery, userid);
     814             :         }
     815             :         else
     816       93274 :             rte->checkAsUser = userid;
     817             :     }
     818             : 
     819             :     /* Recurse into subquery-in-WITH */
     820       30288 :     foreach(l, qry->cteList)
     821             :     {
     822          46 :         CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
     823             : 
     824          46 :         setRuleCheckAsUser_Query(castNode(Query, cte->ctequery), userid);
     825             :     }
     826             : 
     827             :     /* If there are sublinks, search for them and process their RTEs */
     828       30242 :     if (qry->hasSubLinks)
     829         660 :         query_tree_walker(qry, setRuleCheckAsUser_walker, (void *) &userid,
     830             :                           QTW_IGNORE_RC_SUBQUERIES);
     831       30242 : }
     832             : 
     833             : 
     834             : /*
     835             :  * Change the firing semantics of an existing rule.
     836             :  */
     837             : void
     838          12 : EnableDisableRule(Relation rel, const char *rulename,
     839             :                   char fires_when)
     840             : {
     841             :     Relation    pg_rewrite_desc;
     842          12 :     Oid         owningRel = RelationGetRelid(rel);
     843             :     Oid         eventRelationOid;
     844             :     HeapTuple   ruletup;
     845          12 :     bool        changed = false;
     846             : 
     847             :     /*
     848             :      * Find the rule tuple to change.
     849             :      */
     850          12 :     pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock);
     851          12 :     ruletup = SearchSysCacheCopy2(RULERELNAME,
     852             :                                   ObjectIdGetDatum(owningRel),
     853             :                                   PointerGetDatum(rulename));
     854          12 :     if (!HeapTupleIsValid(ruletup))
     855           0 :         ereport(ERROR,
     856             :                 (errcode(ERRCODE_UNDEFINED_OBJECT),
     857             :                  errmsg("rule \"%s\" for relation \"%s\" does not exist",
     858             :                         rulename, get_rel_name(owningRel))));
     859             : 
     860             :     /*
     861             :      * Verify that the user has appropriate permissions.
     862             :      */
     863          12 :     eventRelationOid = ((Form_pg_rewrite) GETSTRUCT(ruletup))->ev_class;
     864             :     Assert(eventRelationOid == owningRel);
     865          12 :     if (!pg_class_ownercheck(eventRelationOid, GetUserId()))
     866           0 :         aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(eventRelationOid)),
     867           0 :                        get_rel_name(eventRelationOid));
     868             : 
     869             :     /*
     870             :      * Change ev_enabled if it is different from the desired new state.
     871             :      */
     872          12 :     if (DatumGetChar(((Form_pg_rewrite) GETSTRUCT(ruletup))->ev_enabled) !=
     873             :         fires_when)
     874             :     {
     875          12 :         ((Form_pg_rewrite) GETSTRUCT(ruletup))->ev_enabled =
     876             :             CharGetDatum(fires_when);
     877          12 :         CatalogTupleUpdate(pg_rewrite_desc, &ruletup->t_self, ruletup);
     878             : 
     879          12 :         changed = true;
     880             :     }
     881             : 
     882          12 :     InvokeObjectPostAlterHook(RewriteRelationId,
     883             :                               HeapTupleGetOid(ruletup), 0);
     884             : 
     885          12 :     heap_freetuple(ruletup);
     886          12 :     heap_close(pg_rewrite_desc, RowExclusiveLock);
     887             : 
     888             :     /*
     889             :      * If we changed anything, broadcast a SI inval message to force each
     890             :      * backend (including our own!) to rebuild relation's relcache entry.
     891             :      * Otherwise they will fail to apply the change promptly.
     892             :      */
     893          12 :     if (changed)
     894          12 :         CacheInvalidateRelcache(rel);
     895          12 : }
     896             : 
     897             : 
     898             : /*
     899             :  * Perform permissions and integrity checks before acquiring a relation lock.
     900             :  */
     901             : static void
     902          20 : RangeVarCallbackForRenameRule(const RangeVar *rv, Oid relid, Oid oldrelid,
     903             :                               void *arg)
     904             : {
     905             :     HeapTuple   tuple;
     906             :     Form_pg_class form;
     907             : 
     908          20 :     tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
     909          20 :     if (!HeapTupleIsValid(tuple))
     910           0 :         return;                 /* concurrently dropped */
     911          20 :     form = (Form_pg_class) GETSTRUCT(tuple);
     912             : 
     913             :     /* only tables and views can have rules */
     914          40 :     if (form->relkind != RELKIND_RELATION &&
     915          24 :         form->relkind != RELKIND_VIEW &&
     916           4 :         form->relkind != RELKIND_PARTITIONED_TABLE)
     917           0 :         ereport(ERROR,
     918             :                 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     919             :                  errmsg("\"%s\" is not a table or view", rv->relname)));
     920             : 
     921          20 :     if (!allowSystemTableMods && IsSystemClass(relid, form))
     922           0 :         ereport(ERROR,
     923             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     924             :                  errmsg("permission denied: \"%s\" is a system catalog",
     925             :                         rv->relname)));
     926             : 
     927             :     /* you must own the table to rename one of its rules */
     928          20 :     if (!pg_class_ownercheck(relid, GetUserId()))
     929           0 :         aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
     930             : 
     931          20 :     ReleaseSysCache(tuple);
     932             : }
     933             : 
     934             : /*
     935             :  * Rename an existing rewrite rule.
     936             :  */
     937             : ObjectAddress
     938          20 : RenameRewriteRule(RangeVar *relation, const char *oldName,
     939             :                   const char *newName)
     940             : {
     941             :     Oid         relid;
     942             :     Relation    targetrel;
     943             :     Relation    pg_rewrite_desc;
     944             :     HeapTuple   ruletup;
     945             :     Form_pg_rewrite ruleform;
     946             :     Oid         ruleOid;
     947             :     ObjectAddress address;
     948             : 
     949             :     /*
     950             :      * Look up name, check permissions, and acquire lock (which we will NOT
     951             :      * release until end of transaction).
     952             :      */
     953          20 :     relid = RangeVarGetRelidExtended(relation, AccessExclusiveLock,
     954             :                                      0,
     955             :                                      RangeVarCallbackForRenameRule,
     956             :                                      NULL);
     957             : 
     958             :     /* Have lock already, so just need to build relcache entry. */
     959          20 :     targetrel = relation_open(relid, NoLock);
     960             : 
     961             :     /* Prepare to modify pg_rewrite */
     962          20 :     pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock);
     963             : 
     964             :     /* Fetch the rule's entry (it had better exist) */
     965          20 :     ruletup = SearchSysCacheCopy2(RULERELNAME,
     966             :                                   ObjectIdGetDatum(relid),
     967             :                                   PointerGetDatum(oldName));
     968          20 :     if (!HeapTupleIsValid(ruletup))
     969           4 :         ereport(ERROR,
     970             :                 (errcode(ERRCODE_UNDEFINED_OBJECT),
     971             :                  errmsg("rule \"%s\" for relation \"%s\" does not exist",
     972             :                         oldName, RelationGetRelationName(targetrel))));
     973          16 :     ruleform = (Form_pg_rewrite) GETSTRUCT(ruletup);
     974          16 :     ruleOid = HeapTupleGetOid(ruletup);
     975             : 
     976             :     /* rule with the new name should not already exist */
     977          16 :     if (IsDefinedRewriteRule(relid, newName))
     978           4 :         ereport(ERROR,
     979             :                 (errcode(ERRCODE_DUPLICATE_OBJECT),
     980             :                  errmsg("rule \"%s\" for relation \"%s\" already exists",
     981             :                         newName, RelationGetRelationName(targetrel))));
     982             : 
     983             :     /*
     984             :      * We disallow renaming ON SELECT rules, because they should always be
     985             :      * named "_RETURN".
     986             :      */
     987          12 :     if (ruleform->ev_type == CMD_SELECT + '0')
     988           4 :         ereport(ERROR,
     989             :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     990             :                  errmsg("renaming an ON SELECT rule is not allowed")));
     991             : 
     992             :     /* OK, do the update */
     993           8 :     namestrcpy(&(ruleform->rulename), newName);
     994             : 
     995           8 :     CatalogTupleUpdate(pg_rewrite_desc, &ruletup->t_self, ruletup);
     996             : 
     997           8 :     heap_freetuple(ruletup);
     998           8 :     heap_close(pg_rewrite_desc, RowExclusiveLock);
     999             : 
    1000             :     /*
    1001             :      * Invalidate relation's relcache entry so that other backends (and this
    1002             :      * one too!) are sent SI message to make them rebuild relcache entries.
    1003             :      * (Ideally this should happen automatically...)
    1004             :      */
    1005           8 :     CacheInvalidateRelcache(targetrel);
    1006             : 
    1007           8 :     ObjectAddressSet(address, RewriteRelationId, ruleOid);
    1008             : 
    1009             :     /*
    1010             :      * Close rel, but keep exclusive lock!
    1011             :      */
    1012           8 :     relation_close(targetrel, NoLock);
    1013             : 
    1014           8 :     return address;
    1015             : }

Generated by: LCOV version 1.13