LCOV - code coverage report
Current view: top level - src/backend/rewrite - rewriteDefine.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 86.6 % 238 206
Test Date: 2026-07-25 22:15:46 Functions: 100.0 % 10 10
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 57.0 % 256 146

             Branch data     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                 :       11957 : 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                 :       11957 :     char       *evqual = nodeToString(event_qual);
      61                 :       11957 :     char       *actiontree = nodeToString((Node *) action);
      62                 :             :     Datum       values[Natts_pg_rewrite];
      63                 :       11957 :     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                 :       11957 :     bool        is_update = false;
      72                 :             : 
      73                 :             :     /*
      74                 :             :      * Set up *nulls and *values arrays
      75                 :             :      */
      76                 :       11957 :     namestrcpy(&rname, rulname);
      77                 :       11957 :     values[Anum_pg_rewrite_rulename - 1] = NameGetDatum(&rname);
      78                 :       11957 :     values[Anum_pg_rewrite_ev_class - 1] = ObjectIdGetDatum(eventrel_oid);
      79                 :       11957 :     values[Anum_pg_rewrite_ev_type - 1] = CharGetDatum(evtype + '0');
      80                 :       11957 :     values[Anum_pg_rewrite_ev_enabled - 1] = CharGetDatum(RULE_FIRES_ON_ORIGIN);
      81                 :       11957 :     values[Anum_pg_rewrite_is_instead - 1] = BoolGetDatum(evinstead);
      82                 :       11957 :     values[Anum_pg_rewrite_ev_qual - 1] = CStringGetTextDatum(evqual);
      83                 :       11957 :     values[Anum_pg_rewrite_ev_action - 1] = CStringGetTextDatum(actiontree);
      84                 :             : 
      85                 :             :     /*
      86                 :             :      * Ready to store new pg_rewrite tuple
      87                 :             :      */
      88                 :       11957 :     pg_rewrite_desc = table_open(RewriteRelationId, RowExclusiveLock);
      89                 :             : 
      90                 :             :     /*
      91                 :             :      * Check to see if we are replacing an existing tuple
      92                 :             :      */
      93                 :       11957 :     oldtup = SearchSysCache2(RULERELNAME,
      94                 :             :                              ObjectIdGetDatum(eventrel_oid),
      95                 :             :                              PointerGetDatum(rulname));
      96                 :             : 
      97         [ +  + ]:       11957 :     if (HeapTupleIsValid(oldtup))
      98                 :             :     {
      99                 :         169 :         bool        replaces[Natts_pg_rewrite] = {0};
     100                 :             : 
     101         [ -  + ]:         169 :         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                 :         169 :         replaces[Anum_pg_rewrite_ev_type - 1] = true;
     111                 :         169 :         replaces[Anum_pg_rewrite_is_instead - 1] = true;
     112                 :         169 :         replaces[Anum_pg_rewrite_ev_qual - 1] = true;
     113                 :         169 :         replaces[Anum_pg_rewrite_ev_action - 1] = true;
     114                 :             : 
     115                 :         169 :         tup = heap_modify_tuple(oldtup, RelationGetDescr(pg_rewrite_desc),
     116                 :             :                                 values, nulls, replaces);
     117                 :             : 
     118                 :         169 :         CatalogTupleUpdate(pg_rewrite_desc, &tup->t_self, tup);
     119                 :             : 
     120                 :         169 :         ReleaseSysCache(oldtup);
     121                 :             : 
     122                 :         169 :         rewriteObjectId = ((Form_pg_rewrite) GETSTRUCT(tup))->oid;
     123                 :         169 :         is_update = true;
     124                 :             :     }
     125                 :             :     else
     126                 :             :     {
     127                 :       11788 :         rewriteObjectId = GetNewOidWithIndex(pg_rewrite_desc,
     128                 :             :                                              RewriteOidIndexId,
     129                 :             :                                              Anum_pg_rewrite_oid);
     130                 :       11788 :         values[Anum_pg_rewrite_oid - 1] = ObjectIdGetDatum(rewriteObjectId);
     131                 :             : 
     132                 :       11788 :         tup = heap_form_tuple(pg_rewrite_desc->rd_att, values, nulls);
     133                 :             : 
     134                 :       11788 :         CatalogTupleInsert(pg_rewrite_desc, tup);
     135                 :             :     }
     136                 :             : 
     137                 :             : 
     138                 :       11957 :     heap_freetuple(tup);
     139                 :             : 
     140                 :             :     /* If replacing, get rid of old dependencies and make new ones */
     141         [ +  + ]:       11957 :     if (is_update)
     142                 :         169 :         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                 :       11957 :     myself.classId = RewriteRelationId;
     151                 :       11957 :     myself.objectId = rewriteObjectId;
     152                 :       11957 :     myself.objectSubId = 0;
     153                 :             : 
     154                 :       11957 :     referenced.classId = RelationRelationId;
     155                 :       11957 :     referenced.objectId = eventrel_oid;
     156                 :       11957 :     referenced.objectSubId = 0;
     157                 :             : 
     158         [ +  + ]:       11957 :     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                 :       11957 :     recordDependencyOnExpr(&myself, (Node *) action, NIL,
     165                 :             :                            DEPENDENCY_NORMAL);
     166                 :             : 
     167         [ +  + ]:       11957 :     if (event_qual != NULL)
     168                 :             :     {
     169                 :             :         /* Find query containing OLD/NEW rtable entries */
     170                 :         175 :         Query      *qry = linitial_node(Query, action);
     171                 :             : 
     172                 :         175 :         qry = getInsertSelectQuery(qry, NULL);
     173                 :         175 :         recordDependencyOnExpr(&myself, event_qual, qry->rtable,
     174                 :             :                                DEPENDENCY_NORMAL);
     175                 :             :     }
     176                 :             : 
     177                 :             :     /* Post creation hook for new rule */
     178         [ +  + ]:       11957 :     InvokeObjectPostCreateHook(RewriteRelationId, rewriteObjectId, 0);
     179                 :             : 
     180                 :       11957 :     table_close(pg_rewrite_desc, RowExclusiveLock);
     181                 :             : 
     182                 :       11957 :     return rewriteObjectId;
     183                 :             : }
     184                 :             : 
     185                 :             : /*
     186                 :             :  * DefineRule
     187                 :             :  *      Execute a CREATE RULE command.
     188                 :             :  */
     189                 :             : ObjectAddress
     190                 :         754 : DefineRule(RuleStmt *stmt, const char *queryString)
     191                 :             : {
     192                 :             :     List       *actions;
     193                 :             :     Node       *whereClause;
     194                 :             :     Oid         relId;
     195                 :             : 
     196                 :             :     /* Parse analysis. */
     197                 :         754 :     transformRuleStmt(stmt, queryString, &actions, &whereClause);
     198                 :             : 
     199                 :             :     /*
     200                 :             :      * Find and lock the relation.  Lock level should match
     201                 :             :      * DefineQueryRewrite.
     202                 :             :      */
     203                 :         742 :     relId = RangeVarGetRelid(stmt->relation, AccessExclusiveLock, false);
     204                 :             : 
     205                 :             :     /* ... and execute */
     206                 :        1463 :     return DefineQueryRewrite(stmt->rulename,
     207                 :             :                               relId,
     208                 :             :                               whereClause,
     209                 :             :                               stmt->event,
     210                 :         742 :                               stmt->instead,
     211                 :         742 :                               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                 :       11978 : 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                 :       11978 :     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                 :       11978 :     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         [ +  + ]:       11978 :     if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
     256         [ +  + ]:       11528 :         event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
     257         [ +  + ]:       11246 :         event_relation->rd_rel->relkind != RELKIND_VIEW &&
     258         [ -  + ]:           8 :         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                 :             :     /*
     266                 :             :      * Conflict log tables are used internally for logical replication
     267                 :             :      * conflict logging and should not have rules, as it could disrupt
     268                 :             :      * conflict logging.
     269                 :             :      */
     270         [ +  + ]:       11978 :     if (IsConflictLogTableClass(event_relation->rd_rel))
     271         [ +  - ]:           4 :         ereport(ERROR,
     272                 :             :                 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     273                 :             :                  errmsg("conflict log table \"%s\" cannot have rules",
     274                 :             :                         RelationGetRelationName(event_relation)),
     275                 :             :                  errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
     276                 :             : 
     277   [ +  +  +  + ]:       11974 :     if (!allowSystemTableMods && IsSystemRelation(event_relation))
     278         [ +  - ]:           1 :         ereport(ERROR,
     279                 :             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     280                 :             :                  errmsg("permission denied: \"%s\" is a system catalog",
     281                 :             :                         RelationGetRelationName(event_relation))));
     282                 :             : 
     283                 :             :     /*
     284                 :             :      * Check user has permission to apply rules to this relation.
     285                 :             :      */
     286         [ -  + ]:       11973 :     if (!object_ownercheck(RelationRelationId, event_relid, GetUserId()))
     287                 :           0 :         aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(event_relation->rd_rel->relkind),
     288                 :           0 :                        RelationGetRelationName(event_relation));
     289                 :             : 
     290                 :             :     /*
     291                 :             :      * No rule actions that modify OLD or NEW
     292                 :             :      */
     293   [ +  -  +  +  :       23976 :     foreach(l, action)
                   +  + ]
     294                 :             :     {
     295                 :       12003 :         query = lfirst_node(Query, l);
     296         [ +  + ]:       12003 :         if (query->resultRelation == 0)
     297                 :       11449 :             continue;
     298                 :             :         /* Don't be fooled by INSERT/SELECT */
     299         [ +  + ]:         554 :         if (query != getInsertSelectQuery(query, NULL))
     300                 :          38 :             continue;
     301         [ -  + ]:         516 :         if (query->resultRelation == PRS2_OLD_VARNO)
     302         [ #  # ]:           0 :             ereport(ERROR,
     303                 :             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     304                 :             :                      errmsg("rule actions on OLD are not implemented"),
     305                 :             :                      errhint("Use views or triggers instead.")));
     306         [ -  + ]:         516 :         if (query->resultRelation == PRS2_NEW_VARNO)
     307         [ #  # ]:           0 :             ereport(ERROR,
     308                 :             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     309                 :             :                      errmsg("rule actions on NEW are not implemented"),
     310                 :             :                      errhint("Use triggers instead.")));
     311                 :             :     }
     312                 :             : 
     313         [ +  + ]:       11973 :     if (event_type == CMD_SELECT)
     314                 :             :     {
     315                 :             :         /*
     316                 :             :          * Rules ON SELECT are restricted to view definitions
     317                 :             :          *
     318                 :             :          * So this had better be a view, ...
     319                 :             :          */
     320         [ +  + ]:       11248 :         if (event_relation->rd_rel->relkind != RELKIND_VIEW &&
     321         [ +  + ]:         294 :             event_relation->rd_rel->relkind != RELKIND_MATVIEW)
     322         [ +  - ]:          12 :             ereport(ERROR,
     323                 :             :                     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     324                 :             :                      errmsg("relation \"%s\" cannot have ON SELECT rules",
     325                 :             :                             RelationGetRelationName(event_relation)),
     326                 :             :                      errdetail_relkind_not_supported(event_relation->rd_rel->relkind)));
     327                 :             : 
     328                 :             :         /*
     329                 :             :          * ... there cannot be INSTEAD NOTHING, ...
     330                 :             :          */
     331         [ -  + ]:       11236 :         if (action == NIL)
     332         [ #  # ]:           0 :             ereport(ERROR,
     333                 :             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     334                 :             :                      errmsg("INSTEAD NOTHING rules on SELECT are not implemented"),
     335                 :             :                      errhint("Use views instead.")));
     336                 :             : 
     337                 :             :         /*
     338                 :             :          * ... there cannot be multiple actions, ...
     339                 :             :          */
     340         [ -  + ]:       11236 :         if (list_length(action) > 1)
     341         [ #  # ]:           0 :             ereport(ERROR,
     342                 :             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     343                 :             :                      errmsg("multiple actions for rules on SELECT are not implemented")));
     344                 :             : 
     345                 :             :         /*
     346                 :             :          * ... the one action must be a SELECT, ...
     347                 :             :          */
     348                 :       11236 :         query = linitial_node(Query, action);
     349         [ +  - ]:       11236 :         if (!is_instead ||
     350         [ -  + ]:       11236 :             query->commandType != CMD_SELECT)
     351         [ #  # ]:           0 :             ereport(ERROR,
     352                 :             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     353                 :             :                      errmsg("rules on SELECT must have action INSTEAD SELECT")));
     354                 :             : 
     355                 :             :         /*
     356                 :             :          * ... it cannot contain data-modifying WITH ...
     357                 :             :          */
     358         [ -  + ]:       11236 :         if (query->hasModifyingCTE)
     359         [ #  # ]:           0 :             ereport(ERROR,
     360                 :             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     361                 :             :                      errmsg("rules on SELECT must not contain data-modifying statements in WITH")));
     362                 :             : 
     363                 :             :         /*
     364                 :             :          * ... there can be no rule qual, ...
     365                 :             :          */
     366         [ -  + ]:       11236 :         if (event_qual != NULL)
     367         [ #  # ]:           0 :             ereport(ERROR,
     368                 :             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     369                 :             :                      errmsg("event qualifications are not implemented for rules on SELECT")));
     370                 :             : 
     371                 :             :         /*
     372                 :             :          * ... the targetlist of the SELECT action must exactly match the
     373                 :             :          * event relation, ...
     374                 :             :          */
     375                 :       11236 :         checkRuleResultList(query->targetList,
     376                 :             :                             RelationGetDescr(event_relation),
     377                 :             :                             true,
     378                 :       11236 :                             event_relation->rd_rel->relkind !=
     379                 :             :                             RELKIND_MATVIEW);
     380                 :             : 
     381                 :             :         /*
     382                 :             :          * ... there must not be another ON SELECT rule already ...
     383                 :             :          */
     384   [ +  +  -  + ]:       11236 :         if (!replace && event_relation->rd_rules != NULL)
     385                 :             :         {
     386                 :             :             int         i;
     387                 :             : 
     388         [ #  # ]:           0 :             for (i = 0; i < event_relation->rd_rules->numLocks; i++)
     389                 :             :             {
     390                 :             :                 RewriteRule *rule;
     391                 :             : 
     392                 :           0 :                 rule = event_relation->rd_rules->rules[i];
     393         [ #  # ]:           0 :                 if (rule->event == CMD_SELECT)
     394         [ #  # ]:           0 :                     ereport(ERROR,
     395                 :             :                             (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     396                 :             :                              errmsg("\"%s\" is already a view",
     397                 :             :                                     RelationGetRelationName(event_relation))));
     398                 :             :             }
     399                 :             :         }
     400                 :             : 
     401                 :             :         /*
     402                 :             :          * ... and finally the rule must be named _RETURN.
     403                 :             :          */
     404         [ -  + ]:       11236 :         if (strcmp(rulename, ViewSelectRuleName) != 0)
     405         [ #  # ]:           0 :             ereport(ERROR,
     406                 :             :                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     407                 :             :                      errmsg("view rule for \"%s\" must be named \"%s\"",
     408                 :             :                             RelationGetRelationName(event_relation),
     409                 :             :                             ViewSelectRuleName)));
     410                 :             :     }
     411                 :             :     else
     412                 :             :     {
     413                 :             :         /*
     414                 :             :          * For non-SELECT rules, a RETURNING list can appear in at most one of
     415                 :             :          * the actions ... and there can't be any RETURNING list at all in a
     416                 :             :          * conditional or non-INSTEAD rule.  (Actually, there can be at most
     417                 :             :          * one RETURNING list across all rules on the same event, but it seems
     418                 :             :          * best to enforce that at rule expansion time.)  If there is a
     419                 :             :          * RETURNING list, it must match the event relation.
     420                 :             :          */
     421                 :         725 :         bool        haveReturning = false;
     422                 :             : 
     423   [ +  -  +  +  :        1476 :         foreach(l, action)
                   +  + ]
     424                 :             :         {
     425                 :         755 :             query = lfirst_node(Query, l);
     426                 :             : 
     427         [ +  + ]:         755 :             if (!query->returningList)
     428                 :         665 :                 continue;
     429         [ -  + ]:          90 :             if (haveReturning)
     430         [ #  # ]:           0 :                 ereport(ERROR,
     431                 :             :                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     432                 :             :                          errmsg("cannot have multiple RETURNING lists in a rule")));
     433                 :          90 :             haveReturning = true;
     434         [ -  + ]:          90 :             if (event_qual != NULL)
     435         [ #  # ]:           0 :                 ereport(ERROR,
     436                 :             :                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     437                 :             :                          errmsg("RETURNING lists are not supported in conditional rules")));
     438         [ -  + ]:          90 :             if (!is_instead)
     439         [ #  # ]:           0 :                 ereport(ERROR,
     440                 :             :                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     441                 :             :                          errmsg("RETURNING lists are not supported in non-INSTEAD rules")));
     442                 :          90 :             checkRuleResultList(query->returningList,
     443                 :             :                                 RelationGetDescr(event_relation),
     444                 :             :                                 false, false);
     445                 :             :         }
     446                 :             : 
     447                 :             :         /*
     448                 :             :          * And finally, if it's not an ON SELECT rule then it must *not* be
     449                 :             :          * named _RETURN.  This prevents accidentally or maliciously replacing
     450                 :             :          * a view's ON SELECT rule with some other kind of rule.
     451                 :             :          */
     452         [ -  + ]:         721 :         if (strcmp(rulename, ViewSelectRuleName) == 0)
     453         [ #  # ]:           0 :             ereport(ERROR,
     454                 :             :                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     455                 :             :                      errmsg("non-view rule for \"%s\" must not be named \"%s\"",
     456                 :             :                             RelationGetRelationName(event_relation),
     457                 :             :                             ViewSelectRuleName)));
     458                 :             :     }
     459                 :             : 
     460                 :             :     /*
     461                 :             :      * This rule is allowed - prepare to install it.
     462                 :             :      */
     463                 :             : 
     464                 :             :     /* discard rule if it's null action and not INSTEAD; it's a no-op */
     465   [ -  +  -  - ]:       11957 :     if (action != NIL || is_instead)
     466                 :             :     {
     467                 :       11957 :         ruleId = InsertRule(rulename,
     468                 :             :                             event_type,
     469                 :             :                             event_relid,
     470                 :             :                             is_instead,
     471                 :             :                             event_qual,
     472                 :             :                             action,
     473                 :             :                             replace);
     474                 :             : 
     475                 :             :         /*
     476                 :             :          * Set pg_class 'relhasrules' field true for event relation.
     477                 :             :          *
     478                 :             :          * Important side effect: an SI notice is broadcast to force all
     479                 :             :          * backends (including me!) to update relcache entries with the new
     480                 :             :          * rule.
     481                 :             :          */
     482                 :       11957 :         SetRelationRuleStatus(event_relid, true);
     483                 :             :     }
     484                 :             : 
     485                 :       11957 :     ObjectAddressSet(address, RewriteRelationId, ruleId);
     486                 :             : 
     487                 :             :     /* Close rel, but keep lock till commit... */
     488                 :       11957 :     table_close(event_relation, NoLock);
     489                 :             : 
     490                 :       11957 :     return address;
     491                 :             : }
     492                 :             : 
     493                 :             : /*
     494                 :             :  * checkRuleResultList
     495                 :             :  *      Verify that targetList produces output compatible with a tupledesc
     496                 :             :  *
     497                 :             :  * The targetList might be either a SELECT targetlist, or a RETURNING list;
     498                 :             :  * isSelect tells which.  This is used for choosing error messages.
     499                 :             :  *
     500                 :             :  * A SELECT targetlist may optionally require that column names match.
     501                 :             :  */
     502                 :             : static void
     503                 :       11326 : checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
     504                 :             :                     bool requireColumnNameMatch)
     505                 :             : {
     506                 :             :     ListCell   *tllist;
     507                 :             :     int         i;
     508                 :             : 
     509                 :             :     /* Only a SELECT may require a column name match. */
     510                 :             :     Assert(isSelect || !requireColumnNameMatch);
     511                 :             : 
     512                 :       11326 :     i = 0;
     513   [ +  +  +  +  :      112513 :     foreach(tllist, targetList)
                   +  + ]
     514                 :             :     {
     515                 :      101191 :         TargetEntry *tle = (TargetEntry *) lfirst(tllist);
     516                 :             :         Oid         tletypid;
     517                 :             :         int32       tletypmod;
     518                 :             :         Form_pg_attribute attr;
     519                 :             :         char       *attname;
     520                 :             : 
     521                 :             :         /* resjunk entries may be ignored */
     522         [ +  + ]:      101191 :         if (tle->resjunk)
     523                 :         379 :             continue;
     524                 :      100812 :         i++;
     525         [ +  + ]:      100812 :         if (i > resultDesc->natts)
     526   [ +  -  -  + ]:           4 :             ereport(ERROR,
     527                 :             :                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     528                 :             :                      isSelect ?
     529                 :             :                      errmsg("SELECT rule's target list has too many entries") :
     530                 :             :                      errmsg("RETURNING list has too many entries")));
     531                 :             : 
     532                 :      100808 :         attr = TupleDescAttr(resultDesc, i - 1);
     533                 :      100808 :         attname = NameStr(attr->attname);
     534                 :             : 
     535                 :             :         /*
     536                 :             :          * Disallow dropped columns in the relation.  This is not really
     537                 :             :          * expected to happen when creating an ON SELECT rule.  It'd be
     538                 :             :          * possible if someone tried to convert a relation with dropped
     539                 :             :          * columns to a view, but the only case we care about supporting
     540                 :             :          * table-to-view conversion for is pg_dump, and pg_dump won't do that.
     541                 :             :          *
     542                 :             :          * Unfortunately, the situation is also possible when adding a rule
     543                 :             :          * with RETURNING to a regular table, and rejecting that case is
     544                 :             :          * altogether more annoying.  In principle we could support it by
     545                 :             :          * modifying the targetlist to include dummy NULL columns
     546                 :             :          * corresponding to the dropped columns in the tupdesc.  However,
     547                 :             :          * places like ruleutils.c would have to be fixed to not process such
     548                 :             :          * entries, and that would take an uncertain and possibly rather large
     549                 :             :          * amount of work.  (Note we could not dodge that by marking the dummy
     550                 :             :          * columns resjunk, since it's precisely the non-resjunk tlist columns
     551                 :             :          * that are expected to correspond to table columns.)
     552                 :             :          */
     553         [ -  + ]:      100808 :         if (attr->attisdropped)
     554   [ #  #  #  # ]:           0 :             ereport(ERROR,
     555                 :             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     556                 :             :                      isSelect ?
     557                 :             :                      errmsg("cannot convert relation containing dropped columns to view") :
     558                 :             :                      errmsg("cannot create a RETURNING list for a relation containing dropped columns")));
     559                 :             : 
     560                 :             :         /* Check name match if required; no need for two error texts here */
     561   [ +  +  -  + ]:      100808 :         if (requireColumnNameMatch && strcmp(tle->resname, attname) != 0)
     562         [ #  # ]:           0 :             ereport(ERROR,
     563                 :             :                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     564                 :             :                      errmsg("SELECT rule's target entry %d has different column name from column \"%s\"",
     565                 :             :                             i, attname),
     566                 :             :                      errdetail("SELECT target entry is named \"%s\".",
     567                 :             :                                tle->resname)));
     568                 :             : 
     569                 :             :         /* Check type match. */
     570                 :      100808 :         tletypid = exprType((Node *) tle->expr);
     571         [ -  + ]:      100808 :         if (attr->atttypid != tletypid)
     572   [ #  #  #  #  :           0 :             ereport(ERROR,
                   #  # ]
     573                 :             :                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     574                 :             :                      isSelect ?
     575                 :             :                      errmsg("SELECT rule's target entry %d has different type from column \"%s\"",
     576                 :             :                             i, attname) :
     577                 :             :                      errmsg("RETURNING list's entry %d has different type from column \"%s\"",
     578                 :             :                             i, attname),
     579                 :             :                      isSelect ?
     580                 :             :                      errdetail("SELECT target entry has type %s, but column has type %s.",
     581                 :             :                                format_type_be(tletypid),
     582                 :             :                                format_type_be(attr->atttypid)) :
     583                 :             :                      errdetail("RETURNING list entry has type %s, but column has type %s.",
     584                 :             :                                format_type_be(tletypid),
     585                 :             :                                format_type_be(attr->atttypid))));
     586                 :             : 
     587                 :             :         /*
     588                 :             :          * Allow typmods to be different only if one of them is -1, ie,
     589                 :             :          * "unspecified".  This is necessary for cases like "numeric", where
     590                 :             :          * the table will have a filled-in default length but the select
     591                 :             :          * rule's expression will probably have typmod = -1.
     592                 :             :          */
     593                 :      100808 :         tletypmod = exprTypmod((Node *) tle->expr);
     594         [ -  + ]:      100808 :         if (attr->atttypmod != tletypmod &&
     595   [ #  #  #  # ]:           0 :             attr->atttypmod != -1 && tletypmod != -1)
     596   [ #  #  #  #  :           0 :             ereport(ERROR,
                   #  # ]
     597                 :             :                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     598                 :             :                      isSelect ?
     599                 :             :                      errmsg("SELECT rule's target entry %d has different size from column \"%s\"",
     600                 :             :                             i, attname) :
     601                 :             :                      errmsg("RETURNING list's entry %d has different size from column \"%s\"",
     602                 :             :                             i, attname),
     603                 :             :                      isSelect ?
     604                 :             :                      errdetail("SELECT target entry has type %s, but column has type %s.",
     605                 :             :                                format_type_with_typemod(tletypid, tletypmod),
     606                 :             :                                format_type_with_typemod(attr->atttypid,
     607                 :             :                                                         attr->atttypmod)) :
     608                 :             :                      errdetail("RETURNING list entry has type %s, but column has type %s.",
     609                 :             :                                format_type_with_typemod(tletypid, tletypmod),
     610                 :             :                                format_type_with_typemod(attr->atttypid,
     611                 :             :                                                         attr->atttypmod))));
     612                 :             :     }
     613                 :             : 
     614         [ -  + ]:       11322 :     if (i != resultDesc->natts)
     615   [ #  #  #  # ]:           0 :         ereport(ERROR,
     616                 :             :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     617                 :             :                  isSelect ?
     618                 :             :                  errmsg("SELECT rule's target list has too few entries") :
     619                 :             :                  errmsg("RETURNING list has too few entries")));
     620                 :       11322 : }
     621                 :             : 
     622                 :             : /*
     623                 :             :  * setRuleCheckAsUser
     624                 :             :  *      Recursively scan a query or expression tree and set the checkAsUser
     625                 :             :  *      field to the given userid in all RTEPermissionInfos of the query.
     626                 :             :  */
     627                 :             : void
     628                 :       53680 : setRuleCheckAsUser(Node *node, Oid userid)
     629                 :             : {
     630                 :       53680 :     (void) setRuleCheckAsUser_walker(node, &userid);
     631                 :       53680 : }
     632                 :             : 
     633                 :             : static bool
     634                 :      262756 : setRuleCheckAsUser_walker(Node *node, Oid *context)
     635                 :             : {
     636         [ +  + ]:      262756 :     if (node == NULL)
     637                 :       56627 :         return false;
     638         [ +  + ]:      206129 :     if (IsA(node, Query))
     639                 :             :     {
     640                 :       27813 :         setRuleCheckAsUser_Query((Query *) node, *context);
     641                 :       27813 :         return false;
     642                 :             :     }
     643                 :      178316 :     return expression_tree_walker(node, setRuleCheckAsUser_walker,
     644                 :             :                                   context);
     645                 :             : }
     646                 :             : 
     647                 :             : static void
     648                 :       40464 : setRuleCheckAsUser_Query(Query *qry, Oid userid)
     649                 :             : {
     650                 :             :     ListCell   *l;
     651                 :             : 
     652                 :             :     /* Set in all RTEPermissionInfos for this query. */
     653   [ +  +  +  +  :      101425 :     foreach(l, qry->rteperminfos)
                   +  + ]
     654                 :             :     {
     655                 :       60961 :         RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
     656                 :             : 
     657                 :       60961 :         perminfo->checkAsUser = userid;
     658                 :             :     }
     659                 :             : 
     660                 :             :     /* Now recurse to any subquery RTEs */
     661   [ +  +  +  +  :      143297 :     foreach(l, qry->rtable)
                   +  + ]
     662                 :             :     {
     663                 :      102833 :         RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
     664                 :             : 
     665         [ +  + ]:      102833 :         if (rte->rtekind == RTE_SUBQUERY)
     666                 :       12491 :             setRuleCheckAsUser_Query(rte->subquery, userid);
     667                 :             :     }
     668                 :             : 
     669                 :             :     /* Recurse into subquery-in-WITH */
     670   [ +  +  +  +  :       40624 :     foreach(l, qry->cteList)
                   +  + ]
     671                 :             :     {
     672                 :         160 :         CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
     673                 :             : 
     674                 :         160 :         setRuleCheckAsUser_Query(castNode(Query, cte->ctequery), userid);
     675                 :             :     }
     676                 :             : 
     677                 :             :     /* If there are sublinks, search for them and process their RTEs */
     678         [ +  + ]:       40464 :     if (qry->hasSubLinks)
     679                 :        1743 :         query_tree_walker(qry, setRuleCheckAsUser_walker, &userid,
     680                 :             :                           QTW_IGNORE_RC_SUBQUERIES);
     681                 :       40464 : }
     682                 :             : 
     683                 :             : 
     684                 :             : /*
     685                 :             :  * Change the firing semantics of an existing rule.
     686                 :             :  */
     687                 :             : void
     688                 :          29 : EnableDisableRule(Relation rel, const char *rulename,
     689                 :             :                   char fires_when)
     690                 :             : {
     691                 :             :     Relation    pg_rewrite_desc;
     692                 :          29 :     Oid         owningRel = RelationGetRelid(rel);
     693                 :             :     Oid         eventRelationOid;
     694                 :             :     HeapTuple   ruletup;
     695                 :             :     Form_pg_rewrite ruleform;
     696                 :          29 :     bool        changed = false;
     697                 :             : 
     698                 :             :     /*
     699                 :             :      * Find the rule tuple to change.
     700                 :             :      */
     701                 :          29 :     pg_rewrite_desc = table_open(RewriteRelationId, RowExclusiveLock);
     702                 :          29 :     ruletup = SearchSysCacheCopy2(RULERELNAME,
     703                 :             :                                   ObjectIdGetDatum(owningRel),
     704                 :             :                                   PointerGetDatum(rulename));
     705         [ -  + ]:          29 :     if (!HeapTupleIsValid(ruletup))
     706         [ #  # ]:           0 :         ereport(ERROR,
     707                 :             :                 (errcode(ERRCODE_UNDEFINED_OBJECT),
     708                 :             :                  errmsg("rule \"%s\" for relation \"%s\" does not exist",
     709                 :             :                         rulename, get_rel_name(owningRel))));
     710                 :             : 
     711                 :          29 :     ruleform = (Form_pg_rewrite) GETSTRUCT(ruletup);
     712                 :             : 
     713                 :             :     /*
     714                 :             :      * Verify that the user has appropriate permissions.
     715                 :             :      */
     716                 :          29 :     eventRelationOid = ruleform->ev_class;
     717                 :             :     Assert(eventRelationOid == owningRel);
     718         [ -  + ]:          29 :     if (!object_ownercheck(RelationRelationId, eventRelationOid, GetUserId()))
     719                 :           0 :         aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(eventRelationOid)),
     720                 :           0 :                        get_rel_name(eventRelationOid));
     721                 :             : 
     722                 :             :     /*
     723                 :             :      * Change ev_enabled if it is different from the desired new state.
     724                 :             :      */
     725         [ +  - ]:          29 :     if (ruleform->ev_enabled != fires_when)
     726                 :             :     {
     727                 :          29 :         ruleform->ev_enabled = fires_when;
     728                 :          29 :         CatalogTupleUpdate(pg_rewrite_desc, &ruletup->t_self, ruletup);
     729                 :             : 
     730                 :          29 :         changed = true;
     731                 :             :     }
     732                 :             : 
     733         [ +  + ]:          29 :     InvokeObjectPostAlterHook(RewriteRelationId, ruleform->oid, 0);
     734                 :             : 
     735                 :          29 :     heap_freetuple(ruletup);
     736                 :          29 :     table_close(pg_rewrite_desc, RowExclusiveLock);
     737                 :             : 
     738                 :             :     /*
     739                 :             :      * If we changed anything, broadcast a SI inval message to force each
     740                 :             :      * backend (including our own!) to rebuild relation's relcache entry.
     741                 :             :      * Otherwise they will fail to apply the change promptly.
     742                 :             :      */
     743         [ +  - ]:          29 :     if (changed)
     744                 :          29 :         CacheInvalidateRelcache(rel);
     745                 :          29 : }
     746                 :             : 
     747                 :             : 
     748                 :             : /*
     749                 :             :  * Perform permissions and integrity checks before acquiring a relation lock.
     750                 :             :  */
     751                 :             : static void
     752                 :          30 : RangeVarCallbackForRenameRule(const RangeVar *rv, Oid relid, Oid oldrelid,
     753                 :             :                               void *arg)
     754                 :             : {
     755                 :             :     HeapTuple   tuple;
     756                 :             :     Form_pg_class form;
     757                 :             : 
     758                 :          30 :     tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
     759         [ -  + ]:          30 :     if (!HeapTupleIsValid(tuple))
     760                 :           0 :         return;                 /* concurrently dropped */
     761                 :          30 :     form = (Form_pg_class) GETSTRUCT(tuple);
     762                 :             : 
     763                 :             :     /* only tables and views can have rules */
     764         [ +  + ]:          30 :     if (form->relkind != RELKIND_RELATION &&
     765         [ +  + ]:          20 :         form->relkind != RELKIND_VIEW &&
     766         [ -  + ]:           4 :         form->relkind != RELKIND_PARTITIONED_TABLE)
     767         [ #  # ]:           0 :         ereport(ERROR,
     768                 :             :                 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     769                 :             :                  errmsg("relation \"%s\" cannot have rules", rv->relname),
     770                 :             :                  errdetail_relkind_not_supported(form->relkind)));
     771                 :             : 
     772                 :             :     /*
     773                 :             :      * Conflict log tables are used internally for logical replication
     774                 :             :      * conflict logging and should not have rules, as it could disrupt
     775                 :             :      * conflict logging.
     776                 :             :      */
     777         [ +  + ]:          30 :     if (IsConflictLogTableClass(form))
     778         [ +  - ]:           4 :         ereport(ERROR,
     779                 :             :                 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     780                 :             :                  errmsg("conflict log table \"%s\" cannot have rules",
     781                 :             :                         rv->relname),
     782                 :             :                  errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
     783                 :             : 
     784   [ +  +  +  + ]:          26 :     if (!allowSystemTableMods && IsSystemClass(relid, form))
     785         [ +  - ]:           1 :         ereport(ERROR,
     786                 :             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     787                 :             :                  errmsg("permission denied: \"%s\" is a system catalog",
     788                 :             :                         rv->relname)));
     789                 :             : 
     790                 :             :     /* you must own the table to rename one of its rules */
     791         [ -  + ]:          25 :     if (!object_ownercheck(RelationRelationId, relid, GetUserId()))
     792                 :           0 :         aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
     793                 :             : 
     794                 :          25 :     ReleaseSysCache(tuple);
     795                 :             : }
     796                 :             : 
     797                 :             : /*
     798                 :             :  * Rename an existing rewrite rule.
     799                 :             :  */
     800                 :             : ObjectAddress
     801                 :          30 : RenameRewriteRule(RangeVar *relation, const char *oldName,
     802                 :             :                   const char *newName)
     803                 :             : {
     804                 :             :     Oid         relid;
     805                 :             :     Relation    targetrel;
     806                 :             :     Relation    pg_rewrite_desc;
     807                 :             :     HeapTuple   ruletup;
     808                 :             :     Form_pg_rewrite ruleform;
     809                 :             :     Oid         ruleOid;
     810                 :             :     ObjectAddress address;
     811                 :             : 
     812                 :             :     /*
     813                 :             :      * Look up name, check permissions, and acquire lock (which we will NOT
     814                 :             :      * release until end of transaction).
     815                 :             :      */
     816                 :          30 :     relid = RangeVarGetRelidExtended(relation, AccessExclusiveLock,
     817                 :             :                                      0,
     818                 :             :                                      RangeVarCallbackForRenameRule,
     819                 :             :                                      NULL);
     820                 :             : 
     821                 :             :     /* Have lock already, so just need to build relcache entry. */
     822                 :          25 :     targetrel = relation_open(relid, NoLock);
     823                 :             : 
     824                 :             :     /* Prepare to modify pg_rewrite */
     825                 :          25 :     pg_rewrite_desc = table_open(RewriteRelationId, RowExclusiveLock);
     826                 :             : 
     827                 :             :     /* Fetch the rule's entry (it had better exist) */
     828                 :          25 :     ruletup = SearchSysCacheCopy2(RULERELNAME,
     829                 :             :                                   ObjectIdGetDatum(relid),
     830                 :             :                                   PointerGetDatum(oldName));
     831         [ +  + ]:          25 :     if (!HeapTupleIsValid(ruletup))
     832         [ +  - ]:           4 :         ereport(ERROR,
     833                 :             :                 (errcode(ERRCODE_UNDEFINED_OBJECT),
     834                 :             :                  errmsg("rule \"%s\" for relation \"%s\" does not exist",
     835                 :             :                         oldName, RelationGetRelationName(targetrel))));
     836                 :          21 :     ruleform = (Form_pg_rewrite) GETSTRUCT(ruletup);
     837                 :          21 :     ruleOid = ruleform->oid;
     838                 :             : 
     839                 :             :     /* rule with the new name should not already exist */
     840         [ +  + ]:          21 :     if (IsDefinedRewriteRule(relid, newName))
     841         [ +  - ]:           4 :         ereport(ERROR,
     842                 :             :                 (errcode(ERRCODE_DUPLICATE_OBJECT),
     843                 :             :                  errmsg("rule \"%s\" for relation \"%s\" already exists",
     844                 :             :                         newName, RelationGetRelationName(targetrel))));
     845                 :             : 
     846                 :             :     /*
     847                 :             :      * We disallow renaming ON SELECT rules, because they should always be
     848                 :             :      * named "_RETURN".
     849                 :             :      */
     850         [ +  + ]:          17 :     if (ruleform->ev_type == CMD_SELECT + '0')
     851         [ +  - ]:           4 :         ereport(ERROR,
     852                 :             :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     853                 :             :                  errmsg("renaming an ON SELECT rule is not allowed")));
     854                 :             : 
     855                 :             :     /*
     856                 :             :      * Conversely, if it's not an ON SELECT rule then it must *not* be named
     857                 :             :      * _RETURN.
     858                 :             :      */
     859         [ +  + ]:          13 :     if (strcmp(newName, ViewSelectRuleName) == 0)
     860         [ +  - ]:           4 :         ereport(ERROR,
     861                 :             :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     862                 :             :                  errmsg("non-view rule for \"%s\" must not be named \"%s\"",
     863                 :             :                         RelationGetRelationName(targetrel),
     864                 :             :                         ViewSelectRuleName)));
     865                 :             : 
     866                 :             :     /* OK, do the update */
     867                 :           9 :     namestrcpy(&(ruleform->rulename), newName);
     868                 :             : 
     869                 :           9 :     CatalogTupleUpdate(pg_rewrite_desc, &ruletup->t_self, ruletup);
     870                 :             : 
     871         [ -  + ]:           9 :     InvokeObjectPostAlterHook(RewriteRelationId, ruleOid, 0);
     872                 :             : 
     873                 :           9 :     heap_freetuple(ruletup);
     874                 :           9 :     table_close(pg_rewrite_desc, RowExclusiveLock);
     875                 :             : 
     876                 :             :     /*
     877                 :             :      * Invalidate relation's relcache entry so that other backends (and this
     878                 :             :      * one too!) are sent SI message to make them rebuild relcache entries.
     879                 :             :      * (Ideally this should happen automatically...)
     880                 :             :      */
     881                 :           9 :     CacheInvalidateRelcache(targetrel);
     882                 :             : 
     883                 :           9 :     ObjectAddressSet(address, RewriteRelationId, ruleOid);
     884                 :             : 
     885                 :             :     /*
     886                 :             :      * Close rel, but keep exclusive lock!
     887                 :             :      */
     888                 :           9 :     relation_close(targetrel, NoLock);
     889                 :             : 
     890                 :           9 :     return address;
     891                 :             : }
        

Generated by: LCOV version 2.0-1