LCOV - differential code coverage report
Current view: top level - src/backend/rewrite - rewriteDefine.c (source / functions) Coverage Total Hit LBC UIC UBC GBC GIC GNC CBC EUB ECB DUB DCB
Current: Differential Code Coverage HEAD vs 15 Lines: 85.2 % 237 202 4 28 3 9 132 12 49 13 110 10 39
Current Date: 2023-04-08 15:15:32 Functions: 100.0 % 10 10 9 1 6 3
Baseline: 15
Baseline Date: 2023-04-08 15:09:40
Legend: Lines: hit not hit

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

Generated by: LCOV version v1.16-55-g56c0a2a