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

Generated by: LCOV version 1.13