LCOV - code coverage report
Current view: top level - src/backend/rewrite - rewriteDefine.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 85.1 % 235 200
Test Date: 2026-03-03 14:15:12 Functions: 100.0 % 10 10
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-2026, 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         9395 : 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         9395 :     char       *evqual = nodeToString(event_qual);
      61         9395 :     char       *actiontree = nodeToString((Node *) action);
      62              :     Datum       values[Natts_pg_rewrite];
      63         9395 :     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         9395 :     bool        is_update = false;
      72              : 
      73              :     /*
      74              :      * Set up *nulls and *values arrays
      75              :      */
      76         9395 :     namestrcpy(&rname, rulname);
      77         9395 :     values[Anum_pg_rewrite_rulename - 1] = NameGetDatum(&rname);
      78         9395 :     values[Anum_pg_rewrite_ev_class - 1] = ObjectIdGetDatum(eventrel_oid);
      79         9395 :     values[Anum_pg_rewrite_ev_type - 1] = CharGetDatum(evtype + '0');
      80         9395 :     values[Anum_pg_rewrite_ev_enabled - 1] = CharGetDatum(RULE_FIRES_ON_ORIGIN);
      81         9395 :     values[Anum_pg_rewrite_is_instead - 1] = BoolGetDatum(evinstead);
      82         9395 :     values[Anum_pg_rewrite_ev_qual - 1] = CStringGetTextDatum(evqual);
      83         9395 :     values[Anum_pg_rewrite_ev_action - 1] = CStringGetTextDatum(actiontree);
      84              : 
      85              :     /*
      86              :      * Ready to store new pg_rewrite tuple
      87              :      */
      88         9395 :     pg_rewrite_desc = table_open(RewriteRelationId, RowExclusiveLock);
      89              : 
      90              :     /*
      91              :      * Check to see if we are replacing an existing tuple
      92              :      */
      93         9395 :     oldtup = SearchSysCache2(RULERELNAME,
      94              :                              ObjectIdGetDatum(eventrel_oid),
      95              :                              PointerGetDatum(rulname));
      96              : 
      97         9395 :     if (HeapTupleIsValid(oldtup))
      98              :     {
      99          121 :         bool        replaces[Natts_pg_rewrite] = {0};
     100              : 
     101          121 :         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          121 :         replaces[Anum_pg_rewrite_ev_type - 1] = true;
     111          121 :         replaces[Anum_pg_rewrite_is_instead - 1] = true;
     112          121 :         replaces[Anum_pg_rewrite_ev_qual - 1] = true;
     113          121 :         replaces[Anum_pg_rewrite_ev_action - 1] = true;
     114              : 
     115          121 :         tup = heap_modify_tuple(oldtup, RelationGetDescr(pg_rewrite_desc),
     116              :                                 values, nulls, replaces);
     117              : 
     118          121 :         CatalogTupleUpdate(pg_rewrite_desc, &tup->t_self, tup);
     119              : 
     120          121 :         ReleaseSysCache(oldtup);
     121              : 
     122          121 :         rewriteObjectId = ((Form_pg_rewrite) GETSTRUCT(tup))->oid;
     123          121 :         is_update = true;
     124              :     }
     125              :     else
     126              :     {
     127         9274 :         rewriteObjectId = GetNewOidWithIndex(pg_rewrite_desc,
     128              :                                              RewriteOidIndexId,
     129              :                                              Anum_pg_rewrite_oid);
     130         9274 :         values[Anum_pg_rewrite_oid - 1] = ObjectIdGetDatum(rewriteObjectId);
     131              : 
     132         9274 :         tup = heap_form_tuple(pg_rewrite_desc->rd_att, values, nulls);
     133              : 
     134         9274 :         CatalogTupleInsert(pg_rewrite_desc, tup);
     135              :     }
     136              : 
     137              : 
     138         9395 :     heap_freetuple(tup);
     139              : 
     140              :     /* If replacing, get rid of old dependencies and make new ones */
     141         9395 :     if (is_update)
     142          121 :         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         9395 :     myself.classId = RewriteRelationId;
     151         9395 :     myself.objectId = rewriteObjectId;
     152         9395 :     myself.objectSubId = 0;
     153              : 
     154         9395 :     referenced.classId = RelationRelationId;
     155         9395 :     referenced.objectId = eventrel_oid;
     156         9395 :     referenced.objectSubId = 0;
     157              : 
     158         9395 :     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         9395 :     recordDependencyOnExpr(&myself, (Node *) action, NIL,
     165              :                            DEPENDENCY_NORMAL);
     166              : 
     167         9395 :     if (event_qual != NULL)
     168              :     {
     169              :         /* Find query containing OLD/NEW rtable entries */
     170          136 :         Query      *qry = linitial_node(Query, action);
     171              : 
     172          136 :         qry = getInsertSelectQuery(qry, NULL);
     173          136 :         recordDependencyOnExpr(&myself, event_qual, qry->rtable,
     174              :                                DEPENDENCY_NORMAL);
     175              :     }
     176              : 
     177              :     /* Post creation hook for new rule */
     178         9395 :     InvokeObjectPostCreateHook(RewriteRelationId, rewriteObjectId, 0);
     179              : 
     180         9395 :     table_close(pg_rewrite_desc, RowExclusiveLock);
     181              : 
     182         9395 :     return rewriteObjectId;
     183              : }
     184              : 
     185              : /*
     186              :  * DefineRule
     187              :  *      Execute a CREATE RULE command.
     188              :  */
     189              : ObjectAddress
     190          554 : DefineRule(RuleStmt *stmt, const char *queryString)
     191              : {
     192              :     List       *actions;
     193              :     Node       *whereClause;
     194              :     Oid         relId;
     195              : 
     196              :     /* Parse analysis. */
     197          554 :     transformRuleStmt(stmt, queryString, &actions, &whereClause);
     198              : 
     199              :     /*
     200              :      * Find and lock the relation.  Lock level should match
     201              :      * DefineQueryRewrite.
     202              :      */
     203          545 :     relId = RangeVarGetRelid(stmt->relation, AccessExclusiveLock, false);
     204              : 
     205              :     /* ... and execute */
     206         1077 :     return DefineQueryRewrite(stmt->rulename,
     207              :                               relId,
     208              :                               whereClause,
     209              :                               stmt->event,
     210          545 :                               stmt->instead,
     211          545 :                               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         9408 : 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         9408 :     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         9408 :     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         9408 :     if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
     256         9099 :         event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
     257         8871 :         event_relation->rd_rel->relkind != RELKIND_VIEW &&
     258            6 :         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         9408 :     if (!allowSystemTableMods && IsSystemRelation(event_relation))
     266            1 :         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         9407 :     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        18837 :     foreach(l, action)
     282              :     {
     283         9430 :         query = lfirst_node(Query, l);
     284         9430 :         if (query->resultRelation == 0)
     285         9035 :             continue;
     286              :         /* Don't be fooled by INSERT/SELECT */
     287          395 :         if (query != getInsertSelectQuery(query, NULL))
     288           29 :             continue;
     289          366 :         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          366 :         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         9407 :     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         8872 :         if (event_relation->rd_rel->relkind != RELKIND_VIEW &&
     309          237 :             event_relation->rd_rel->relkind != RELKIND_MATVIEW)
     310            9 :             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         8863 :         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         8863 :         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         8863 :         query = linitial_node(Query, action);
     337         8863 :         if (!is_instead ||
     338         8863 :             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         8863 :         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         8863 :         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         8863 :         checkRuleResultList(query->targetList,
     364              :                             RelationGetDescr(event_relation),
     365              :                             true,
     366         8863 :                             event_relation->rd_rel->relkind !=
     367              :                             RELKIND_MATVIEW);
     368              : 
     369              :         /*
     370              :          * ... there must not be another ON SELECT rule already ...
     371              :          */
     372         8863 :         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         8863 :         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          535 :         bool        haveReturning = false;
     425              : 
     426         1090 :         foreach(l, action)
     427              :         {
     428          558 :             query = lfirst_node(Query, l);
     429              : 
     430          558 :             if (!query->returningList)
     431          490 :                 continue;
     432           68 :             if (haveReturning)
     433            0 :                 ereport(ERROR,
     434              :                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     435              :                          errmsg("cannot have multiple RETURNING lists in a rule")));
     436           68 :             haveReturning = true;
     437           68 :             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           68 :             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           68 :             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          532 :         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         9395 :     if (action != NIL || is_instead)
     469              :     {
     470         9395 :         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         9395 :         SetRelationRuleStatus(event_relid, true);
     486              :     }
     487              : 
     488         9395 :     ObjectAddressSet(address, RewriteRelationId, ruleId);
     489              : 
     490              :     /* Close rel, but keep lock till commit... */
     491         9395 :     table_close(event_relation, NoLock);
     492              : 
     493         9395 :     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         8931 : 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         8931 :     i = 0;
     516        90434 :     foreach(tllist, targetList)
     517              :     {
     518        81506 :         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        81506 :         if (tle->resjunk)
     526          328 :             continue;
     527        81178 :         i++;
     528        81178 :         if (i > resultDesc->natts)
     529            3 :             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        81175 :         attr = TupleDescAttr(resultDesc, i - 1);
     536        81175 :         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        81175 :         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        81175 :         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        81175 :         tletypid = exprType((Node *) tle->expr);
     574        81175 :         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        81175 :         tletypmod = exprTypmod((Node *) tle->expr);
     597        81175 :         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         8928 :     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         8928 : }
     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        43474 : setRuleCheckAsUser(Node *node, Oid userid)
     632              : {
     633        43474 :     (void) setRuleCheckAsUser_walker(node, &userid);
     634        43474 : }
     635              : 
     636              : static bool
     637       218442 : setRuleCheckAsUser_walker(Node *node, Oid *context)
     638              : {
     639       218442 :     if (node == NULL)
     640        45414 :         return false;
     641       173028 :     if (IsA(node, Query))
     642              :     {
     643        22705 :         setRuleCheckAsUser_Query((Query *) node, *context);
     644        22705 :         return false;
     645              :     }
     646       150323 :     return expression_tree_walker(node, setRuleCheckAsUser_walker,
     647              :                                   context);
     648              : }
     649              : 
     650              : static void
     651        34048 : setRuleCheckAsUser_Query(Query *qry, Oid userid)
     652              : {
     653              :     ListCell   *l;
     654              : 
     655              :     /* Set in all RTEPermissionInfos for this query. */
     656        86285 :     foreach(l, qry->rteperminfos)
     657              :     {
     658        52237 :         RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
     659              : 
     660        52237 :         perminfo->checkAsUser = userid;
     661              :     }
     662              : 
     663              :     /* Now recurse to any subquery RTEs */
     664       124278 :     foreach(l, qry->rtable)
     665              :     {
     666        90230 :         RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
     667              : 
     668        90230 :         if (rte->rtekind == RTE_SUBQUERY)
     669        11228 :             setRuleCheckAsUser_Query(rte->subquery, userid);
     670              :     }
     671              : 
     672              :     /* Recurse into subquery-in-WITH */
     673        34163 :     foreach(l, qry->cteList)
     674              :     {
     675          115 :         CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
     676              : 
     677          115 :         setRuleCheckAsUser_Query(castNode(Query, cte->ctequery), userid);
     678              :     }
     679              : 
     680              :     /* If there are sublinks, search for them and process their RTEs */
     681        34048 :     if (qry->hasSubLinks)
     682         1466 :         query_tree_walker(qry, setRuleCheckAsUser_walker, &userid,
     683              :                           QTW_IGNORE_RC_SUBQUERIES);
     684        34048 : }
     685              : 
     686              : 
     687              : /*
     688              :  * Change the firing semantics of an existing rule.
     689              :  */
     690              : void
     691           23 : EnableDisableRule(Relation rel, const char *rulename,
     692              :                   char fires_when)
     693              : {
     694              :     Relation    pg_rewrite_desc;
     695           23 :     Oid         owningRel = RelationGetRelid(rel);
     696              :     Oid         eventRelationOid;
     697              :     HeapTuple   ruletup;
     698              :     Form_pg_rewrite ruleform;
     699           23 :     bool        changed = false;
     700              : 
     701              :     /*
     702              :      * Find the rule tuple to change.
     703              :      */
     704           23 :     pg_rewrite_desc = table_open(RewriteRelationId, RowExclusiveLock);
     705           23 :     ruletup = SearchSysCacheCopy2(RULERELNAME,
     706              :                                   ObjectIdGetDatum(owningRel),
     707              :                                   PointerGetDatum(rulename));
     708           23 :     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           23 :     ruleform = (Form_pg_rewrite) GETSTRUCT(ruletup);
     715              : 
     716              :     /*
     717              :      * Verify that the user has appropriate permissions.
     718              :      */
     719           23 :     eventRelationOid = ruleform->ev_class;
     720              :     Assert(eventRelationOid == owningRel);
     721           23 :     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           23 :     if (ruleform->ev_enabled != fires_when)
     729              :     {
     730           23 :         ruleform->ev_enabled = fires_when;
     731           23 :         CatalogTupleUpdate(pg_rewrite_desc, &ruletup->t_self, ruletup);
     732              : 
     733           23 :         changed = true;
     734              :     }
     735              : 
     736           23 :     InvokeObjectPostAlterHook(RewriteRelationId, ruleform->oid, 0);
     737              : 
     738           23 :     heap_freetuple(ruletup);
     739           23 :     table_close(pg_rewrite_desc, RowExclusiveLock);
     740              : 
     741              :     /*
     742              :      * If we changed anything, broadcast a SI inval message to force each
     743              :      * backend (including our own!) to rebuild relation's relcache entry.
     744              :      * Otherwise they will fail to apply the change promptly.
     745              :      */
     746           23 :     if (changed)
     747           23 :         CacheInvalidateRelcache(rel);
     748           23 : }
     749              : 
     750              : 
     751              : /*
     752              :  * Perform permissions and integrity checks before acquiring a relation lock.
     753              :  */
     754              : static void
     755           17 : RangeVarCallbackForRenameRule(const RangeVar *rv, Oid relid, Oid oldrelid,
     756              :                               void *arg)
     757              : {
     758              :     HeapTuple   tuple;
     759              :     Form_pg_class form;
     760              : 
     761           17 :     tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
     762           17 :     if (!HeapTupleIsValid(tuple))
     763            0 :         return;                 /* concurrently dropped */
     764           17 :     form = (Form_pg_class) GETSTRUCT(tuple);
     765              : 
     766              :     /* only tables and views can have rules */
     767           17 :     if (form->relkind != RELKIND_RELATION &&
     768           15 :         form->relkind != RELKIND_VIEW &&
     769            3 :         form->relkind != RELKIND_PARTITIONED_TABLE)
     770            0 :         ereport(ERROR,
     771              :                 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     772              :                  errmsg("relation \"%s\" cannot have rules", rv->relname),
     773              :                  errdetail_relkind_not_supported(form->relkind)));
     774              : 
     775           17 :     if (!allowSystemTableMods && IsSystemClass(relid, form))
     776            1 :         ereport(ERROR,
     777              :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     778              :                  errmsg("permission denied: \"%s\" is a system catalog",
     779              :                         rv->relname)));
     780              : 
     781              :     /* you must own the table to rename one of its rules */
     782           16 :     if (!object_ownercheck(RelationRelationId, relid, GetUserId()))
     783            0 :         aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
     784              : 
     785           16 :     ReleaseSysCache(tuple);
     786              : }
     787              : 
     788              : /*
     789              :  * Rename an existing rewrite rule.
     790              :  */
     791              : ObjectAddress
     792           17 : RenameRewriteRule(RangeVar *relation, const char *oldName,
     793              :                   const char *newName)
     794              : {
     795              :     Oid         relid;
     796              :     Relation    targetrel;
     797              :     Relation    pg_rewrite_desc;
     798              :     HeapTuple   ruletup;
     799              :     Form_pg_rewrite ruleform;
     800              :     Oid         ruleOid;
     801              :     ObjectAddress address;
     802              : 
     803              :     /*
     804              :      * Look up name, check permissions, and acquire lock (which we will NOT
     805              :      * release until end of transaction).
     806              :      */
     807           17 :     relid = RangeVarGetRelidExtended(relation, AccessExclusiveLock,
     808              :                                      0,
     809              :                                      RangeVarCallbackForRenameRule,
     810              :                                      NULL);
     811              : 
     812              :     /* Have lock already, so just need to build relcache entry. */
     813           16 :     targetrel = relation_open(relid, NoLock);
     814              : 
     815              :     /* Prepare to modify pg_rewrite */
     816           16 :     pg_rewrite_desc = table_open(RewriteRelationId, RowExclusiveLock);
     817              : 
     818              :     /* Fetch the rule's entry (it had better exist) */
     819           16 :     ruletup = SearchSysCacheCopy2(RULERELNAME,
     820              :                                   ObjectIdGetDatum(relid),
     821              :                                   PointerGetDatum(oldName));
     822           16 :     if (!HeapTupleIsValid(ruletup))
     823            3 :         ereport(ERROR,
     824              :                 (errcode(ERRCODE_UNDEFINED_OBJECT),
     825              :                  errmsg("rule \"%s\" for relation \"%s\" does not exist",
     826              :                         oldName, RelationGetRelationName(targetrel))));
     827           13 :     ruleform = (Form_pg_rewrite) GETSTRUCT(ruletup);
     828           13 :     ruleOid = ruleform->oid;
     829              : 
     830              :     /* rule with the new name should not already exist */
     831           13 :     if (IsDefinedRewriteRule(relid, newName))
     832            3 :         ereport(ERROR,
     833              :                 (errcode(ERRCODE_DUPLICATE_OBJECT),
     834              :                  errmsg("rule \"%s\" for relation \"%s\" already exists",
     835              :                         newName, RelationGetRelationName(targetrel))));
     836              : 
     837              :     /*
     838              :      * We disallow renaming ON SELECT rules, because they should always be
     839              :      * named "_RETURN".
     840              :      */
     841           10 :     if (ruleform->ev_type == CMD_SELECT + '0')
     842            3 :         ereport(ERROR,
     843              :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     844              :                  errmsg("renaming an ON SELECT rule is not allowed")));
     845              : 
     846              :     /* OK, do the update */
     847            7 :     namestrcpy(&(ruleform->rulename), newName);
     848              : 
     849            7 :     CatalogTupleUpdate(pg_rewrite_desc, &ruletup->t_self, ruletup);
     850              : 
     851            7 :     InvokeObjectPostAlterHook(RewriteRelationId, ruleOid, 0);
     852              : 
     853            7 :     heap_freetuple(ruletup);
     854            7 :     table_close(pg_rewrite_desc, RowExclusiveLock);
     855              : 
     856              :     /*
     857              :      * Invalidate relation's relcache entry so that other backends (and this
     858              :      * one too!) are sent SI message to make them rebuild relcache entries.
     859              :      * (Ideally this should happen automatically...)
     860              :      */
     861            7 :     CacheInvalidateRelcache(targetrel);
     862              : 
     863            7 :     ObjectAddressSet(address, RewriteRelationId, ruleOid);
     864              : 
     865              :     /*
     866              :      * Close rel, but keep exclusive lock!
     867              :      */
     868            7 :     relation_close(targetrel, NoLock);
     869              : 
     870            7 :     return address;
     871              : }
        

Generated by: LCOV version 2.0-1