LCOV - code coverage report
Current view: top level - src/backend/rewrite - rewriteDefine.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 200 235 85.1 %
Date: 2024-04-23 11:10:57 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-2024, 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/htup_details.h"
      18             : #include "access/relation.h"
      19             : #include "access/table.h"
      20             : #include "catalog/catalog.h"
      21             : #include "catalog/dependency.h"
      22             : #include "catalog/indexing.h"
      23             : #include "catalog/namespace.h"
      24             : #include "catalog/objectaccess.h"
      25             : #include "catalog/pg_rewrite.h"
      26             : #include "miscadmin.h"
      27             : #include "nodes/nodeFuncs.h"
      28             : #include "parser/parse_utilcmd.h"
      29             : #include "rewrite/rewriteDefine.h"
      30             : #include "rewrite/rewriteManip.h"
      31             : #include "rewrite/rewriteSupport.h"
      32             : #include "utils/acl.h"
      33             : #include "utils/builtins.h"
      34             : #include "utils/inval.h"
      35             : #include "utils/lsyscache.h"
      36             : #include "utils/rel.h"
      37             : #include "utils/syscache.h"
      38             : 
      39             : 
      40             : static void checkRuleResultList(List *targetList, TupleDesc resultDesc,
      41             :                                 bool isSelect, bool requireColumnNameMatch);
      42             : static bool setRuleCheckAsUser_walker(Node *node, Oid *context);
      43             : static void setRuleCheckAsUser_Query(Query *qry, Oid userid);
      44             : 
      45             : 
      46             : /*
      47             :  * InsertRule -
      48             :  *    takes the arguments and inserts them as a row into the system
      49             :  *    relation "pg_rewrite"
      50             :  */
      51             : static Oid
      52       14974 : InsertRule(const char *rulname,
      53             :            int evtype,
      54             :            Oid eventrel_oid,
      55             :            bool evinstead,
      56             :            Node *event_qual,
      57             :            List *action,
      58             :            bool replace)
      59             : {
      60       14974 :     char       *evqual = nodeToString(event_qual);
      61       14974 :     char       *actiontree = nodeToString((Node *) action);
      62             :     Datum       values[Natts_pg_rewrite];
      63       14974 :     bool        nulls[Natts_pg_rewrite] = {0};
      64             :     NameData    rname;
      65             :     Relation    pg_rewrite_desc;
      66             :     HeapTuple   tup,
      67             :                 oldtup;
      68             :     Oid         rewriteObjectId;
      69             :     ObjectAddress myself,
      70             :                 referenced;
      71       14974 :     bool        is_update = false;
      72             : 
      73             :     /*
      74             :      * Set up *nulls and *values arrays
      75             :      */
      76       14974 :     namestrcpy(&rname, rulname);
      77       14974 :     values[Anum_pg_rewrite_rulename - 1] = NameGetDatum(&rname);
      78       14974 :     values[Anum_pg_rewrite_ev_class - 1] = ObjectIdGetDatum(eventrel_oid);
      79       14974 :     values[Anum_pg_rewrite_ev_type - 1] = CharGetDatum(evtype + '0');
      80       14974 :     values[Anum_pg_rewrite_ev_enabled - 1] = CharGetDatum(RULE_FIRES_ON_ORIGIN);
      81       14974 :     values[Anum_pg_rewrite_is_instead - 1] = BoolGetDatum(evinstead);
      82       14974 :     values[Anum_pg_rewrite_ev_qual - 1] = CStringGetTextDatum(evqual);
      83       14974 :     values[Anum_pg_rewrite_ev_action - 1] = CStringGetTextDatum(actiontree);
      84             : 
      85             :     /*
      86             :      * Ready to store new pg_rewrite tuple
      87             :      */
      88       14974 :     pg_rewrite_desc = table_open(RewriteRelationId, RowExclusiveLock);
      89             : 
      90             :     /*
      91             :      * Check to see if we are replacing an existing tuple
      92             :      */
      93       14974 :     oldtup = SearchSysCache2(RULERELNAME,
      94             :                              ObjectIdGetDatum(eventrel_oid),
      95             :                              PointerGetDatum(rulname));
      96             : 
      97       14974 :     if (HeapTupleIsValid(oldtup))
      98             :     {
      99         242 :         bool        replaces[Natts_pg_rewrite] = {0};
     100             : 
     101         242 :         if (!replace)
     102           0 :             ereport(ERROR,
     103             :                     (errcode(ERRCODE_DUPLICATE_OBJECT),
     104             :                      errmsg("rule \"%s\" for relation \"%s\" already exists",
     105             :                             rulname, get_rel_name(eventrel_oid))));
     106             : 
     107             :         /*
     108             :          * When replacing, we don't need to replace every attribute
     109             :          */
     110         242 :         replaces[Anum_pg_rewrite_ev_type - 1] = true;
     111         242 :         replaces[Anum_pg_rewrite_is_instead - 1] = true;
     112         242 :         replaces[Anum_pg_rewrite_ev_qual - 1] = true;
     113         242 :         replaces[Anum_pg_rewrite_ev_action - 1] = true;
     114             : 
     115         242 :         tup = heap_modify_tuple(oldtup, RelationGetDescr(pg_rewrite_desc),
     116             :                                 values, nulls, replaces);
     117             : 
     118         242 :         CatalogTupleUpdate(pg_rewrite_desc, &tup->t_self, tup);
     119             : 
     120         242 :         ReleaseSysCache(oldtup);
     121             : 
     122         242 :         rewriteObjectId = ((Form_pg_rewrite) GETSTRUCT(tup))->oid;
     123         242 :         is_update = true;
     124             :     }
     125             :     else
     126             :     {
     127       14732 :         rewriteObjectId = GetNewOidWithIndex(pg_rewrite_desc,
     128             :                                              RewriteOidIndexId,
     129             :                                              Anum_pg_rewrite_oid);
     130       14732 :         values[Anum_pg_rewrite_oid - 1] = ObjectIdGetDatum(rewriteObjectId);
     131             : 
     132       14732 :         tup = heap_form_tuple(pg_rewrite_desc->rd_att, values, nulls);
     133             : 
     134       14732 :         CatalogTupleInsert(pg_rewrite_desc, tup);
     135             :     }
     136             : 
     137             : 
     138       14974 :     heap_freetuple(tup);
     139             : 
     140             :     /* If replacing, get rid of old dependencies and make new ones */
     141       14974 :     if (is_update)
     142         242 :         deleteDependencyRecordsFor(RewriteRelationId, rewriteObjectId, false);
     143             : 
     144             :     /*
     145             :      * Install dependency on rule's relation to ensure it will go away on
     146             :      * relation deletion.  If the rule is ON SELECT, make the dependency
     147             :      * implicit --- this prevents deleting a view's SELECT rule.  Other kinds
     148             :      * of rules can be AUTO.
     149             :      */
     150       14974 :     myself.classId = RewriteRelationId;
     151       14974 :     myself.objectId = rewriteObjectId;
     152       14974 :     myself.objectSubId = 0;
     153             : 
     154       14974 :     referenced.classId = RelationRelationId;
     155       14974 :     referenced.objectId = eventrel_oid;
     156       14974 :     referenced.objectSubId = 0;
     157             : 
     158       14974 :     recordDependencyOn(&myself, &referenced,
     159             :                        (evtype == CMD_SELECT) ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO);
     160             : 
     161             :     /*
     162             :      * Also install dependencies on objects referenced in action and qual.
     163             :      */
     164       14974 :     recordDependencyOnExpr(&myself, (Node *) action, NIL,
     165             :                            DEPENDENCY_NORMAL);
     166             : 
     167       14974 :     if (event_qual != NULL)
     168             :     {
     169             :         /* Find query containing OLD/NEW rtable entries */
     170         250 :         Query      *qry = linitial_node(Query, action);
     171             : 
     172         250 :         qry = getInsertSelectQuery(qry, NULL);
     173         250 :         recordDependencyOnExpr(&myself, event_qual, qry->rtable,
     174             :                                DEPENDENCY_NORMAL);
     175             :     }
     176             : 
     177             :     /* Post creation hook for new rule */
     178       14974 :     InvokeObjectPostCreateHook(RewriteRelationId, rewriteObjectId, 0);
     179             : 
     180       14974 :     table_close(pg_rewrite_desc, RowExclusiveLock);
     181             : 
     182       14974 :     return rewriteObjectId;
     183             : }
     184             : 
     185             : /*
     186             :  * DefineRule
     187             :  *      Execute a CREATE RULE command.
     188             :  */
     189             : ObjectAddress
     190        1034 : DefineRule(RuleStmt *stmt, const char *queryString)
     191             : {
     192             :     List       *actions;
     193             :     Node       *whereClause;
     194             :     Oid         relId;
     195             : 
     196             :     /* Parse analysis. */
     197        1034 :     transformRuleStmt(stmt, queryString, &actions, &whereClause);
     198             : 
     199             :     /*
     200             :      * Find and lock the relation.  Lock level should match
     201             :      * DefineQueryRewrite.
     202             :      */
     203        1016 :     relId = RangeVarGetRelid(stmt->relation, AccessExclusiveLock, false);
     204             : 
     205             :     /* ... and execute */
     206        2006 :     return DefineQueryRewrite(stmt->rulename,
     207             :                               relId,
     208             :                               whereClause,
     209             :                               stmt->event,
     210        1016 :                               stmt->instead,
     211        1016 :                               stmt->replace,
     212             :                               actions);
     213             : }
     214             : 
     215             : 
     216             : /*
     217             :  * DefineQueryRewrite
     218             :  *      Create a rule
     219             :  *
     220             :  * This is essentially the same as DefineRule() except that the rule's
     221             :  * action and qual have already been passed through parse analysis.
     222             :  */
     223             : ObjectAddress
     224       15000 : DefineQueryRewrite(const char *rulename,
     225             :                    Oid event_relid,
     226             :                    Node *event_qual,
     227             :                    CmdType event_type,
     228             :                    bool is_instead,
     229             :                    bool replace,
     230             :                    List *action)
     231             : {
     232             :     Relation    event_relation;
     233             :     ListCell   *l;
     234             :     Query      *query;
     235       15000 :     Oid         ruleId = InvalidOid;
     236             :     ObjectAddress address;
     237             : 
     238             :     /*
     239             :      * If we are installing an ON SELECT rule, we had better grab
     240             :      * AccessExclusiveLock to ensure no SELECTs are currently running on the
     241             :      * event relation. For other types of rules, it would be sufficient to
     242             :      * grab ShareRowExclusiveLock to lock out insert/update/delete actions and
     243             :      * to ensure that we lock out current CREATE RULE statements; but because
     244             :      * of race conditions in access to catalog entries, we can't do that yet.
     245             :      *
     246             :      * Note that this lock level should match the one used in DefineRule.
     247             :      */
     248       15000 :     event_relation = table_open(event_relid, AccessExclusiveLock);
     249             : 
     250             :     /*
     251             :      * Verify relation is of a type that rules can sensibly be applied to.
     252             :      * Internal callers can target materialized views, but transformRuleStmt()
     253             :      * blocks them for users.  Don't mention them in the error message.
     254             :      */
     255       15000 :     if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
     256       14406 :         event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
     257       13968 :         event_relation->rd_rel->relkind != RELKIND_VIEW &&
     258          12 :         event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
     259           0 :         ereport(ERROR,
     260             :                 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     261             :                  errmsg("relation \"%s\" cannot have rules",
     262             :                         RelationGetRelationName(event_relation)),
     263             :                  errdetail_relkind_not_supported(event_relation->rd_rel->relkind)));
     264             : 
     265       15000 :     if (!allowSystemTableMods && IsSystemRelation(event_relation))
     266           2 :         ereport(ERROR,
     267             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     268             :                  errmsg("permission denied: \"%s\" is a system catalog",
     269             :                         RelationGetRelationName(event_relation))));
     270             : 
     271             :     /*
     272             :      * Check user has permission to apply rules to this relation.
     273             :      */
     274       14998 :     if (!object_ownercheck(RelationRelationId, event_relid, GetUserId()))
     275           0 :         aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(event_relation->rd_rel->relkind),
     276           0 :                        RelationGetRelationName(event_relation));
     277             : 
     278             :     /*
     279             :      * No rule actions that modify OLD or NEW
     280             :      */
     281       30042 :     foreach(l, action)
     282             :     {
     283       15044 :         query = lfirst_node(Query, l);
     284       15044 :         if (query->resultRelation == 0)
     285       14278 :             continue;
     286             :         /* Don't be fooled by INSERT/SELECT */
     287         766 :         if (query != getInsertSelectQuery(query, NULL))
     288          58 :             continue;
     289         708 :         if (query->resultRelation == PRS2_OLD_VARNO)
     290           0 :             ereport(ERROR,
     291             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     292             :                      errmsg("rule actions on OLD are not implemented"),
     293             :                      errhint("Use views or triggers instead.")));
     294         708 :         if (query->resultRelation == PRS2_NEW_VARNO)
     295           0 :             ereport(ERROR,
     296             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     297             :                      errmsg("rule actions on NEW are not implemented"),
     298             :                      errhint("Use triggers instead.")));
     299             :     }
     300             : 
     301       14998 :     if (event_type == CMD_SELECT)
     302             :     {
     303             :         /*
     304             :          * Rules ON SELECT are restricted to view definitions
     305             :          *
     306             :          * So this had better be a view, ...
     307             :          */
     308       14002 :         if (event_relation->rd_rel->relkind != RELKIND_VIEW &&
     309         456 :             event_relation->rd_rel->relkind != RELKIND_MATVIEW)
     310          18 :             ereport(ERROR,
     311             :                     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     312             :                      errmsg("relation \"%s\" cannot have ON SELECT rules",
     313             :                             RelationGetRelationName(event_relation)),
     314             :                      errdetail_relkind_not_supported(event_relation->rd_rel->relkind)));
     315             : 
     316             :         /*
     317             :          * ... there cannot be INSTEAD NOTHING, ...
     318             :          */
     319       13984 :         if (action == NIL)
     320           0 :             ereport(ERROR,
     321             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     322             :                      errmsg("INSTEAD NOTHING rules on SELECT are not implemented"),
     323             :                      errhint("Use views instead.")));
     324             : 
     325             :         /*
     326             :          * ... there cannot be multiple actions, ...
     327             :          */
     328       13984 :         if (list_length(action) > 1)
     329           0 :             ereport(ERROR,
     330             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     331             :                      errmsg("multiple actions for rules on SELECT are not implemented")));
     332             : 
     333             :         /*
     334             :          * ... the one action must be a SELECT, ...
     335             :          */
     336       13984 :         query = linitial_node(Query, action);
     337       13984 :         if (!is_instead ||
     338       13984 :             query->commandType != CMD_SELECT)
     339           0 :             ereport(ERROR,
     340             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     341             :                      errmsg("rules on SELECT must have action INSTEAD SELECT")));
     342             : 
     343             :         /*
     344             :          * ... it cannot contain data-modifying WITH ...
     345             :          */
     346       13984 :         if (query->hasModifyingCTE)
     347           0 :             ereport(ERROR,
     348             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     349             :                      errmsg("rules on SELECT must not contain data-modifying statements in WITH")));
     350             : 
     351             :         /*
     352             :          * ... there can be no rule qual, ...
     353             :          */
     354       13984 :         if (event_qual != NULL)
     355           0 :             ereport(ERROR,
     356             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     357             :                      errmsg("event qualifications are not implemented for rules on SELECT")));
     358             : 
     359             :         /*
     360             :          * ... the targetlist of the SELECT action must exactly match the
     361             :          * event relation, ...
     362             :          */
     363       13984 :         checkRuleResultList(query->targetList,
     364             :                             RelationGetDescr(event_relation),
     365             :                             true,
     366       13984 :                             event_relation->rd_rel->relkind !=
     367             :                             RELKIND_MATVIEW);
     368             : 
     369             :         /*
     370             :          * ... there must not be another ON SELECT rule already ...
     371             :          */
     372       13984 :         if (!replace && event_relation->rd_rules != NULL)
     373             :         {
     374             :             int         i;
     375             : 
     376           0 :             for (i = 0; i < event_relation->rd_rules->numLocks; i++)
     377             :             {
     378             :                 RewriteRule *rule;
     379             : 
     380           0 :                 rule = event_relation->rd_rules->rules[i];
     381           0 :                 if (rule->event == CMD_SELECT)
     382           0 :                     ereport(ERROR,
     383             :                             (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     384             :                              errmsg("\"%s\" is already a view",
     385             :                                     RelationGetRelationName(event_relation))));
     386             :             }
     387             :         }
     388             : 
     389             :         /*
     390             :          * ... and finally the rule must be named _RETURN.
     391             :          */
     392       13984 :         if (strcmp(rulename, ViewSelectRuleName) != 0)
     393             :         {
     394             :             /*
     395             :              * In versions before 7.3, the expected name was _RETviewname. For
     396             :              * backwards compatibility with old pg_dump output, accept that
     397             :              * and silently change it to _RETURN.  Since this is just a quick
     398             :              * backwards-compatibility hack, limit the number of characters
     399             :              * checked to a few less than NAMEDATALEN; this saves having to
     400             :              * worry about where a multibyte character might have gotten
     401             :              * truncated.
     402             :              */
     403           0 :             if (strncmp(rulename, "_RET", 4) != 0 ||
     404           0 :                 strncmp(rulename + 4, RelationGetRelationName(event_relation),
     405             :                         NAMEDATALEN - 4 - 4) != 0)
     406           0 :                 ereport(ERROR,
     407             :                         (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     408             :                          errmsg("view rule for \"%s\" must be named \"%s\"",
     409             :                                 RelationGetRelationName(event_relation),
     410             :                                 ViewSelectRuleName)));
     411           0 :             rulename = pstrdup(ViewSelectRuleName);
     412             :         }
     413             :     }
     414             :     else
     415             :     {
     416             :         /*
     417             :          * For non-SELECT rules, a RETURNING list can appear in at most one of
     418             :          * the actions ... and there can't be any RETURNING list at all in a
     419             :          * conditional or non-INSTEAD rule.  (Actually, there can be at most
     420             :          * one RETURNING list across all rules on the same event, but it seems
     421             :          * best to enforce that at rule expansion time.)  If there is a
     422             :          * RETURNING list, it must match the event relation.
     423             :          */
     424         996 :         bool        haveReturning = false;
     425             : 
     426        2032 :         foreach(l, action)
     427             :         {
     428        1042 :             query = lfirst_node(Query, l);
     429             : 
     430        1042 :             if (!query->returningList)
     431         930 :                 continue;
     432         112 :             if (haveReturning)
     433           0 :                 ereport(ERROR,
     434             :                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     435             :                          errmsg("cannot have multiple RETURNING lists in a rule")));
     436         112 :             haveReturning = true;
     437         112 :             if (event_qual != NULL)
     438           0 :                 ereport(ERROR,
     439             :                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     440             :                          errmsg("RETURNING lists are not supported in conditional rules")));
     441         112 :             if (!is_instead)
     442           0 :                 ereport(ERROR,
     443             :                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     444             :                          errmsg("RETURNING lists are not supported in non-INSTEAD rules")));
     445         112 :             checkRuleResultList(query->returningList,
     446             :                                 RelationGetDescr(event_relation),
     447             :                                 false, false);
     448             :         }
     449             : 
     450             :         /*
     451             :          * And finally, if it's not an ON SELECT rule then it must *not* be
     452             :          * named _RETURN.  This prevents accidentally or maliciously replacing
     453             :          * a view's ON SELECT rule with some other kind of rule.
     454             :          */
     455         990 :         if (strcmp(rulename, ViewSelectRuleName) == 0)
     456           0 :             ereport(ERROR,
     457             :                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     458             :                      errmsg("non-view rule for \"%s\" must not be named \"%s\"",
     459             :                             RelationGetRelationName(event_relation),
     460             :                             ViewSelectRuleName)));
     461             :     }
     462             : 
     463             :     /*
     464             :      * This rule is allowed - prepare to install it.
     465             :      */
     466             : 
     467             :     /* discard rule if it's null action and not INSTEAD; it's a no-op */
     468       14974 :     if (action != NIL || is_instead)
     469             :     {
     470       14974 :         ruleId = InsertRule(rulename,
     471             :                             event_type,
     472             :                             event_relid,
     473             :                             is_instead,
     474             :                             event_qual,
     475             :                             action,
     476             :                             replace);
     477             : 
     478             :         /*
     479             :          * Set pg_class 'relhasrules' field true for event relation.
     480             :          *
     481             :          * Important side effect: an SI notice is broadcast to force all
     482             :          * backends (including me!) to update relcache entries with the new
     483             :          * rule.
     484             :          */
     485       14974 :         SetRelationRuleStatus(event_relid, true);
     486             :     }
     487             : 
     488       14974 :     ObjectAddressSet(address, RewriteRelationId, ruleId);
     489             : 
     490             :     /* Close rel, but keep lock till commit... */
     491       14974 :     table_close(event_relation, NoLock);
     492             : 
     493       14974 :     return address;
     494             : }
     495             : 
     496             : /*
     497             :  * checkRuleResultList
     498             :  *      Verify that targetList produces output compatible with a tupledesc
     499             :  *
     500             :  * The targetList might be either a SELECT targetlist, or a RETURNING list;
     501             :  * isSelect tells which.  This is used for choosing error messages.
     502             :  *
     503             :  * A SELECT targetlist may optionally require that column names match.
     504             :  */
     505             : static void
     506       14096 : checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
     507             :                     bool requireColumnNameMatch)
     508             : {
     509             :     ListCell   *tllist;
     510             :     int         i;
     511             : 
     512             :     /* Only a SELECT may require a column name match. */
     513             :     Assert(isSelect || !requireColumnNameMatch);
     514             : 
     515       14096 :     i = 0;
     516      135422 :     foreach(tllist, targetList)
     517             :     {
     518      121332 :         TargetEntry *tle = (TargetEntry *) lfirst(tllist);
     519             :         Oid         tletypid;
     520             :         int32       tletypmod;
     521             :         Form_pg_attribute attr;
     522             :         char       *attname;
     523             : 
     524             :         /* resjunk entries may be ignored */
     525      121332 :         if (tle->resjunk)
     526         518 :             continue;
     527      120814 :         i++;
     528      120814 :         if (i > resultDesc->natts)
     529           6 :             ereport(ERROR,
     530             :                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     531             :                      isSelect ?
     532             :                      errmsg("SELECT rule's target list has too many entries") :
     533             :                      errmsg("RETURNING list has too many entries")));
     534             : 
     535      120808 :         attr = TupleDescAttr(resultDesc, i - 1);
     536      120808 :         attname = NameStr(attr->attname);
     537             : 
     538             :         /*
     539             :          * Disallow dropped columns in the relation.  This is not really
     540             :          * expected to happen when creating an ON SELECT rule.  It'd be
     541             :          * possible if someone tried to convert a relation with dropped
     542             :          * columns to a view, but the only case we care about supporting
     543             :          * table-to-view conversion for is pg_dump, and pg_dump won't do that.
     544             :          *
     545             :          * Unfortunately, the situation is also possible when adding a rule
     546             :          * with RETURNING to a regular table, and rejecting that case is
     547             :          * altogether more annoying.  In principle we could support it by
     548             :          * modifying the targetlist to include dummy NULL columns
     549             :          * corresponding to the dropped columns in the tupdesc.  However,
     550             :          * places like ruleutils.c would have to be fixed to not process such
     551             :          * entries, and that would take an uncertain and possibly rather large
     552             :          * amount of work.  (Note we could not dodge that by marking the dummy
     553             :          * columns resjunk, since it's precisely the non-resjunk tlist columns
     554             :          * that are expected to correspond to table columns.)
     555             :          */
     556      120808 :         if (attr->attisdropped)
     557           0 :             ereport(ERROR,
     558             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     559             :                      isSelect ?
     560             :                      errmsg("cannot convert relation containing dropped columns to view") :
     561             :                      errmsg("cannot create a RETURNING list for a relation containing dropped columns")));
     562             : 
     563             :         /* Check name match if required; no need for two error texts here */
     564      120808 :         if (requireColumnNameMatch && strcmp(tle->resname, attname) != 0)
     565           0 :             ereport(ERROR,
     566             :                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     567             :                      errmsg("SELECT rule's target entry %d has different column name from column \"%s\"",
     568             :                             i, attname),
     569             :                      errdetail("SELECT target entry is named \"%s\".",
     570             :                                tle->resname)));
     571             : 
     572             :         /* Check type match. */
     573      120808 :         tletypid = exprType((Node *) tle->expr);
     574      120808 :         if (attr->atttypid != tletypid)
     575           0 :             ereport(ERROR,
     576             :                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     577             :                      isSelect ?
     578             :                      errmsg("SELECT rule's target entry %d has different type from column \"%s\"",
     579             :                             i, attname) :
     580             :                      errmsg("RETURNING list's entry %d has different type from column \"%s\"",
     581             :                             i, attname),
     582             :                      isSelect ?
     583             :                      errdetail("SELECT target entry has type %s, but column has type %s.",
     584             :                                format_type_be(tletypid),
     585             :                                format_type_be(attr->atttypid)) :
     586             :                      errdetail("RETURNING list entry has type %s, but column has type %s.",
     587             :                                format_type_be(tletypid),
     588             :                                format_type_be(attr->atttypid))));
     589             : 
     590             :         /*
     591             :          * Allow typmods to be different only if one of them is -1, ie,
     592             :          * "unspecified".  This is necessary for cases like "numeric", where
     593             :          * the table will have a filled-in default length but the select
     594             :          * rule's expression will probably have typmod = -1.
     595             :          */
     596      120808 :         tletypmod = exprTypmod((Node *) tle->expr);
     597      120808 :         if (attr->atttypmod != tletypmod &&
     598           0 :             attr->atttypmod != -1 && tletypmod != -1)
     599           0 :             ereport(ERROR,
     600             :                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     601             :                      isSelect ?
     602             :                      errmsg("SELECT rule's target entry %d has different size from column \"%s\"",
     603             :                             i, attname) :
     604             :                      errmsg("RETURNING list's entry %d has different size from column \"%s\"",
     605             :                             i, attname),
     606             :                      isSelect ?
     607             :                      errdetail("SELECT target entry has type %s, but column has type %s.",
     608             :                                format_type_with_typemod(tletypid, tletypmod),
     609             :                                format_type_with_typemod(attr->atttypid,
     610             :                                                         attr->atttypmod)) :
     611             :                      errdetail("RETURNING list entry has type %s, but column has type %s.",
     612             :                                format_type_with_typemod(tletypid, tletypmod),
     613             :                                format_type_with_typemod(attr->atttypid,
     614             :                                                         attr->atttypmod))));
     615             :     }
     616             : 
     617       14090 :     if (i != resultDesc->natts)
     618           0 :         ereport(ERROR,
     619             :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     620             :                  isSelect ?
     621             :                  errmsg("SELECT rule's target list has too few entries") :
     622             :                  errmsg("RETURNING list has too few entries")));
     623       14090 : }
     624             : 
     625             : /*
     626             :  * setRuleCheckAsUser
     627             :  *      Recursively scan a query or expression tree and set the checkAsUser
     628             :  *      field to the given userid in all RTEPermissionInfos of the query.
     629             :  */
     630             : void
     631       65744 : setRuleCheckAsUser(Node *node, Oid userid)
     632             : {
     633       65744 :     (void) setRuleCheckAsUser_walker(node, &userid);
     634       65744 : }
     635             : 
     636             : static bool
     637      321478 : setRuleCheckAsUser_walker(Node *node, Oid *context)
     638             : {
     639      321478 :     if (node == NULL)
     640       69710 :         return false;
     641      251768 :     if (IsA(node, Query))
     642             :     {
     643       34620 :         setRuleCheckAsUser_Query((Query *) node, *context);
     644       34620 :         return false;
     645             :     }
     646      217148 :     return expression_tree_walker(node, setRuleCheckAsUser_walker,
     647             :                                   (void *) context);
     648             : }
     649             : 
     650             : static void
     651       47242 : setRuleCheckAsUser_Query(Query *qry, Oid userid)
     652             : {
     653             :     ListCell   *l;
     654             : 
     655             :     /* Set in all RTEPermissionInfos for this query. */
     656      115784 :     foreach(l, qry->rteperminfos)
     657             :     {
     658       68542 :         RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
     659             : 
     660       68542 :         perminfo->checkAsUser = userid;
     661             :     }
     662             : 
     663             :     /* Now recurse to any subquery RTEs */
     664      159574 :     foreach(l, qry->rtable)
     665             :     {
     666      112332 :         RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
     667             : 
     668      112332 :         if (rte->rtekind == RTE_SUBQUERY)
     669       12392 :             setRuleCheckAsUser_Query(rte->subquery, userid);
     670             :     }
     671             : 
     672             :     /* Recurse into subquery-in-WITH */
     673       47472 :     foreach(l, qry->cteList)
     674             :     {
     675         230 :         CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
     676             : 
     677         230 :         setRuleCheckAsUser_Query(castNode(Query, cte->ctequery), userid);
     678             :     }
     679             : 
     680             :     /* If there are sublinks, search for them and process their RTEs */
     681       47242 :     if (qry->hasSubLinks)
     682        2272 :         query_tree_walker(qry, setRuleCheckAsUser_walker, (void *) &userid,
     683             :                           QTW_IGNORE_RC_SUBQUERIES);
     684       47242 : }
     685             : 
     686             : 
     687             : /*
     688             :  * Change the firing semantics of an existing rule.
     689             :  */
     690             : void
     691          46 : EnableDisableRule(Relation rel, const char *rulename,
     692             :                   char fires_when)
     693             : {
     694             :     Relation    pg_rewrite_desc;
     695          46 :     Oid         owningRel = RelationGetRelid(rel);
     696             :     Oid         eventRelationOid;
     697             :     HeapTuple   ruletup;
     698             :     Form_pg_rewrite ruleform;
     699          46 :     bool        changed = false;
     700             : 
     701             :     /*
     702             :      * Find the rule tuple to change.
     703             :      */
     704          46 :     pg_rewrite_desc = table_open(RewriteRelationId, RowExclusiveLock);
     705          46 :     ruletup = SearchSysCacheCopy2(RULERELNAME,
     706             :                                   ObjectIdGetDatum(owningRel),
     707             :                                   PointerGetDatum(rulename));
     708          46 :     if (!HeapTupleIsValid(ruletup))
     709           0 :         ereport(ERROR,
     710             :                 (errcode(ERRCODE_UNDEFINED_OBJECT),
     711             :                  errmsg("rule \"%s\" for relation \"%s\" does not exist",
     712             :                         rulename, get_rel_name(owningRel))));
     713             : 
     714          46 :     ruleform = (Form_pg_rewrite) GETSTRUCT(ruletup);
     715             : 
     716             :     /*
     717             :      * Verify that the user has appropriate permissions.
     718             :      */
     719          46 :     eventRelationOid = ruleform->ev_class;
     720             :     Assert(eventRelationOid == owningRel);
     721          46 :     if (!object_ownercheck(RelationRelationId, eventRelationOid, GetUserId()))
     722           0 :         aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(eventRelationOid)),
     723           0 :                        get_rel_name(eventRelationOid));
     724             : 
     725             :     /*
     726             :      * Change ev_enabled if it is different from the desired new state.
     727             :      */
     728          46 :     if (DatumGetChar(ruleform->ev_enabled) !=
     729             :         fires_when)
     730             :     {
     731          46 :         ruleform->ev_enabled = CharGetDatum(fires_when);
     732          46 :         CatalogTupleUpdate(pg_rewrite_desc, &ruletup->t_self, ruletup);
     733             : 
     734          46 :         changed = true;
     735             :     }
     736             : 
     737          46 :     InvokeObjectPostAlterHook(RewriteRelationId, ruleform->oid, 0);
     738             : 
     739          46 :     heap_freetuple(ruletup);
     740          46 :     table_close(pg_rewrite_desc, RowExclusiveLock);
     741             : 
     742             :     /*
     743             :      * If we changed anything, broadcast a SI inval message to force each
     744             :      * backend (including our own!) to rebuild relation's relcache entry.
     745             :      * Otherwise they will fail to apply the change promptly.
     746             :      */
     747          46 :     if (changed)
     748          46 :         CacheInvalidateRelcache(rel);
     749          46 : }
     750             : 
     751             : 
     752             : /*
     753             :  * Perform permissions and integrity checks before acquiring a relation lock.
     754             :  */
     755             : static void
     756          34 : RangeVarCallbackForRenameRule(const RangeVar *rv, Oid relid, Oid oldrelid,
     757             :                               void *arg)
     758             : {
     759             :     HeapTuple   tuple;
     760             :     Form_pg_class form;
     761             : 
     762          34 :     tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
     763          34 :     if (!HeapTupleIsValid(tuple))
     764           0 :         return;                 /* concurrently dropped */
     765          34 :     form = (Form_pg_class) GETSTRUCT(tuple);
     766             : 
     767             :     /* only tables and views can have rules */
     768          34 :     if (form->relkind != RELKIND_RELATION &&
     769          30 :         form->relkind != RELKIND_VIEW &&
     770           6 :         form->relkind != RELKIND_PARTITIONED_TABLE)
     771           0 :         ereport(ERROR,
     772             :                 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     773             :                  errmsg("relation \"%s\" cannot have rules", rv->relname),
     774             :                  errdetail_relkind_not_supported(form->relkind)));
     775             : 
     776          34 :     if (!allowSystemTableMods && IsSystemClass(relid, form))
     777           2 :         ereport(ERROR,
     778             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     779             :                  errmsg("permission denied: \"%s\" is a system catalog",
     780             :                         rv->relname)));
     781             : 
     782             :     /* you must own the table to rename one of its rules */
     783          32 :     if (!object_ownercheck(RelationRelationId, relid, GetUserId()))
     784           0 :         aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
     785             : 
     786          32 :     ReleaseSysCache(tuple);
     787             : }
     788             : 
     789             : /*
     790             :  * Rename an existing rewrite rule.
     791             :  */
     792             : ObjectAddress
     793          34 : RenameRewriteRule(RangeVar *relation, const char *oldName,
     794             :                   const char *newName)
     795             : {
     796             :     Oid         relid;
     797             :     Relation    targetrel;
     798             :     Relation    pg_rewrite_desc;
     799             :     HeapTuple   ruletup;
     800             :     Form_pg_rewrite ruleform;
     801             :     Oid         ruleOid;
     802             :     ObjectAddress address;
     803             : 
     804             :     /*
     805             :      * Look up name, check permissions, and acquire lock (which we will NOT
     806             :      * release until end of transaction).
     807             :      */
     808          34 :     relid = RangeVarGetRelidExtended(relation, AccessExclusiveLock,
     809             :                                      0,
     810             :                                      RangeVarCallbackForRenameRule,
     811             :                                      NULL);
     812             : 
     813             :     /* Have lock already, so just need to build relcache entry. */
     814          32 :     targetrel = relation_open(relid, NoLock);
     815             : 
     816             :     /* Prepare to modify pg_rewrite */
     817          32 :     pg_rewrite_desc = table_open(RewriteRelationId, RowExclusiveLock);
     818             : 
     819             :     /* Fetch the rule's entry (it had better exist) */
     820          32 :     ruletup = SearchSysCacheCopy2(RULERELNAME,
     821             :                                   ObjectIdGetDatum(relid),
     822             :                                   PointerGetDatum(oldName));
     823          32 :     if (!HeapTupleIsValid(ruletup))
     824           6 :         ereport(ERROR,
     825             :                 (errcode(ERRCODE_UNDEFINED_OBJECT),
     826             :                  errmsg("rule \"%s\" for relation \"%s\" does not exist",
     827             :                         oldName, RelationGetRelationName(targetrel))));
     828          26 :     ruleform = (Form_pg_rewrite) GETSTRUCT(ruletup);
     829          26 :     ruleOid = ruleform->oid;
     830             : 
     831             :     /* rule with the new name should not already exist */
     832          26 :     if (IsDefinedRewriteRule(relid, newName))
     833           6 :         ereport(ERROR,
     834             :                 (errcode(ERRCODE_DUPLICATE_OBJECT),
     835             :                  errmsg("rule \"%s\" for relation \"%s\" already exists",
     836             :                         newName, RelationGetRelationName(targetrel))));
     837             : 
     838             :     /*
     839             :      * We disallow renaming ON SELECT rules, because they should always be
     840             :      * named "_RETURN".
     841             :      */
     842          20 :     if (ruleform->ev_type == CMD_SELECT + '0')
     843           6 :         ereport(ERROR,
     844             :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     845             :                  errmsg("renaming an ON SELECT rule is not allowed")));
     846             : 
     847             :     /* OK, do the update */
     848          14 :     namestrcpy(&(ruleform->rulename), newName);
     849             : 
     850          14 :     CatalogTupleUpdate(pg_rewrite_desc, &ruletup->t_self, ruletup);
     851             : 
     852          14 :     InvokeObjectPostAlterHook(RewriteRelationId, ruleOid, 0);
     853             : 
     854          14 :     heap_freetuple(ruletup);
     855          14 :     table_close(pg_rewrite_desc, RowExclusiveLock);
     856             : 
     857             :     /*
     858             :      * Invalidate relation's relcache entry so that other backends (and this
     859             :      * one too!) are sent SI message to make them rebuild relcache entries.
     860             :      * (Ideally this should happen automatically...)
     861             :      */
     862          14 :     CacheInvalidateRelcache(targetrel);
     863             : 
     864          14 :     ObjectAddressSet(address, RewriteRelationId, ruleOid);
     865             : 
     866             :     /*
     867             :      * Close rel, but keep exclusive lock!
     868             :      */
     869          14 :     relation_close(targetrel, NoLock);
     870             : 
     871          14 :     return address;
     872             : }

Generated by: LCOV version 1.14