Header And Logo

PostgreSQL
| The world's most advanced open source database.

Functions

parse_utilcmd.h File Reference

#include "parser/parse_node.h"
Include dependency graph for parse_utilcmd.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Functions

ListtransformCreateStmt (CreateStmt *stmt, const char *queryString)
ListtransformAlterTableStmt (AlterTableStmt *stmt, const char *queryString)
IndexStmttransformIndexStmt (IndexStmt *stmt, const char *queryString)
void transformRuleStmt (RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause)
ListtransformCreateSchemaStmt (CreateSchemaStmt *stmt)

Function Documentation

List* transformAlterTableStmt ( AlterTableStmt stmt,
const char *  queryString 
)

Definition at line 2326 of file parse_utilcmd.c.

References CreateStmtContext::alist, AlterTableGetLockLevel(), Assert, AT_AddColumn, AT_AddColumnToView, AT_AddConstraint, AT_AddIndexConstraint, AT_ProcessedConstraint, CreateStmtContext::blist, CreateStmtContext::ckconstraints, AlterTableStmt::cmds, CreateStmtContext::columns, CONSTR_FOREIGN, ColumnDef::constraints, copyObject(), AlterTableCmd::def, elog, ereport, errmsg(), ERROR, CreateStmtContext::fkconstraints, CreateStmtContext::hasoids, IndexStmt::indexOid, CreateStmtContext::inh_indexes, CreateStmtContext::inhRelations, IsA, CreateStmtContext::isalter, CreateStmtContext::isforeign, CreateStmtContext::ixconstraints, lappend(), lfirst, list_concat(), make_parsestate(), makeNode, AlterTableStmt::missing_ok, nodeTag, NoLock, NOTICE, NULL, OBJECT_FOREIGN_TABLE, OidIsValid, ParseState::p_sourcetext, CreateStmtContext::pkey, CreateStmtContext::pstate, ColumnDef::raw_default, CreateStmtContext::rel, CreateStmtContext::relation, AlterTableStmt::relation, relation_close(), relation_openrv_extended(), AlterTableStmt::relkind, RangeVar::relname, CreateStmtContext::stmtType, AlterTableCmd::subtype, transformColumnDefinition(), transformFKConstraints(), transformIndexConstraints(), transformIndexStmt(), and transformTableConstraint().

Referenced by ATPostAlterTypeParse(), and ProcessUtilitySlow().

{
    Relation    rel;
    ParseState *pstate;
    CreateStmtContext cxt;
    List       *result;
    List       *save_alist;
    ListCell   *lcmd,
               *l;
    List       *newcmds = NIL;
    bool        skipValidation = true;
    AlterTableCmd *newcmd;
    LOCKMODE    lockmode;

    /*
     * We must not scribble on the passed-in AlterTableStmt, so copy it. (This
     * is overkill, but easy.)
     */
    stmt = (AlterTableStmt *) copyObject(stmt);

    /*
     * Determine the appropriate lock level for this list of subcommands.
     */
    lockmode = AlterTableGetLockLevel(stmt->cmds);

    /*
     * Acquire appropriate lock on the target relation, which will be held
     * until end of transaction.  This ensures any decisions we make here
     * based on the state of the relation will still be good at execution. We
     * must get lock now because execution will later require it; taking a
     * lower grade lock now and trying to upgrade later risks deadlock.  Any
     * new commands we add after this must not upgrade the lock level
     * requested here.
     */
    rel = relation_openrv_extended(stmt->relation, lockmode, stmt->missing_ok);
    if (rel == NULL)
    {
        /* this message is consistent with relation_openrv */
        ereport(NOTICE,
                (errmsg("relation \"%s\" does not exist, skipping",
                        stmt->relation->relname)));
        return NIL;
    }

    /* Set up pstate and CreateStmtContext */
    pstate = make_parsestate(NULL);
    pstate->p_sourcetext = queryString;

    cxt.pstate = pstate;
    if (stmt->relkind == OBJECT_FOREIGN_TABLE)
    {
        cxt.stmtType = "ALTER FOREIGN TABLE";
        cxt.isforeign = true;
    }
    else
    {
        cxt.stmtType = "ALTER TABLE";
        cxt.isforeign = false;
    }
    cxt.relation = stmt->relation;
    cxt.rel = rel;
    cxt.inhRelations = NIL;
    cxt.isalter = true;
    cxt.hasoids = false;        /* need not be right */
    cxt.columns = NIL;
    cxt.ckconstraints = NIL;
    cxt.fkconstraints = NIL;
    cxt.ixconstraints = NIL;
    cxt.inh_indexes = NIL;
    cxt.blist = NIL;
    cxt.alist = NIL;
    cxt.pkey = NULL;

    /*
     * The only subtypes that currently require parse transformation handling
     * are ADD COLUMN and ADD CONSTRAINT.  These largely re-use code from
     * CREATE TABLE.
     */
    foreach(lcmd, stmt->cmds)
    {
        AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);

        switch (cmd->subtype)
        {
            case AT_AddColumn:
            case AT_AddColumnToView:
                {
                    ColumnDef  *def = (ColumnDef *) cmd->def;

                    Assert(IsA(def, ColumnDef));
                    transformColumnDefinition(&cxt, def);

                    /*
                     * If the column has a non-null default, we can't skip
                     * validation of foreign keys.
                     */
                    if (def->raw_default != NULL)
                        skipValidation = false;

                    /*
                     * All constraints are processed in other ways. Remove the
                     * original list
                     */
                    def->constraints = NIL;

                    newcmds = lappend(newcmds, cmd);
                    break;
                }
            case AT_AddConstraint:

                /*
                 * The original AddConstraint cmd node doesn't go to newcmds
                 */
                if (IsA(cmd->def, Constraint))
                {
                    transformTableConstraint(&cxt, (Constraint *) cmd->def);
                    if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
                        skipValidation = false;
                }
                else
                    elog(ERROR, "unrecognized node type: %d",
                         (int) nodeTag(cmd->def));
                break;

            case AT_ProcessedConstraint:

                /*
                 * Already-transformed ADD CONSTRAINT, so just make it look
                 * like the standard case.
                 */
                cmd->subtype = AT_AddConstraint;
                newcmds = lappend(newcmds, cmd);
                break;

            default:
                newcmds = lappend(newcmds, cmd);
                break;
        }
    }

    /*
     * transformIndexConstraints wants cxt.alist to contain only index
     * statements, so transfer anything we already have into save_alist
     * immediately.
     */
    save_alist = cxt.alist;
    cxt.alist = NIL;

    /* Postprocess index and FK constraints */
    transformIndexConstraints(&cxt);

    transformFKConstraints(&cxt, skipValidation, true);

    /*
     * Push any index-creation commands into the ALTER, so that they can be
     * scheduled nicely by tablecmds.c.  Note that tablecmds.c assumes that
     * the IndexStmt attached to an AT_AddIndex or AT_AddIndexConstraint
     * subcommand has already been through transformIndexStmt.
     */
    foreach(l, cxt.alist)
    {
        IndexStmt  *idxstmt = (IndexStmt *) lfirst(l);

        Assert(IsA(idxstmt, IndexStmt));
        idxstmt = transformIndexStmt(idxstmt, queryString);
        newcmd = makeNode(AlterTableCmd);
        newcmd->subtype = OidIsValid(idxstmt->indexOid) ? AT_AddIndexConstraint : AT_AddIndex;
        newcmd->def = (Node *) idxstmt;
        newcmds = lappend(newcmds, newcmd);
    }
    cxt.alist = NIL;

    /* Append any CHECK or FK constraints to the commands list */
    foreach(l, cxt.ckconstraints)
    {
        newcmd = makeNode(AlterTableCmd);
        newcmd->subtype = AT_AddConstraint;
        newcmd->def = (Node *) lfirst(l);
        newcmds = lappend(newcmds, newcmd);
    }
    foreach(l, cxt.fkconstraints)
    {
        newcmd = makeNode(AlterTableCmd);
        newcmd->subtype = AT_AddConstraint;
        newcmd->def = (Node *) lfirst(l);
        newcmds = lappend(newcmds, newcmd);
    }

    /* Close rel but keep lock */
    relation_close(rel, NoLock);

    /*
     * Output results.
     */
    stmt->cmds = newcmds;

    result = lappend(cxt.blist, stmt);
    result = list_concat(result, cxt.alist);
    result = list_concat(result, save_alist);

    return result;
}

List* transformCreateSchemaStmt ( CreateSchemaStmt stmt  ) 

Definition at line 2708 of file parse_utilcmd.c.

References CreateSchemaStmt::authid, CreateSchemaStmtContext::authid, element(), elog, ERROR, CreateSchemaStmtContext::grants, CreateSchemaStmtContext::indexes, lappend(), lfirst, list_concat(), nodeTag, CreateTrigStmt::relation, IndexStmt::relation, CreateStmt::relation, CreateSchemaStmt::schemaElts, RangeVar::schemaname, CreateSchemaStmt::schemaname, CreateSchemaStmtContext::schemaname, CreateSeqStmt::sequence, CreateSchemaStmtContext::sequences, setSchemaName(), CreateSchemaStmtContext::stmtType, T_CreateSeqStmt, T_CreateStmt, T_CreateTrigStmt, T_GrantStmt, T_IndexStmt, T_ViewStmt, CreateSchemaStmtContext::tables, CreateSchemaStmtContext::triggers, ViewStmt::view, and CreateSchemaStmtContext::views.

Referenced by CreateSchemaCommand().

{
    CreateSchemaStmtContext cxt;
    List       *result;
    ListCell   *elements;

    cxt.stmtType = "CREATE SCHEMA";
    cxt.schemaname = stmt->schemaname;
    cxt.authid = stmt->authid;
    cxt.sequences = NIL;
    cxt.tables = NIL;
    cxt.views = NIL;
    cxt.indexes = NIL;
    cxt.triggers = NIL;
    cxt.grants = NIL;

    /*
     * Run through each schema element in the schema element list. Separate
     * statements by type, and do preliminary analysis.
     */
    foreach(elements, stmt->schemaElts)
    {
        Node       *element = lfirst(elements);

        switch (nodeTag(element))
        {
            case T_CreateSeqStmt:
                {
                    CreateSeqStmt *elp = (CreateSeqStmt *) element;

                    setSchemaName(cxt.schemaname, &elp->sequence->schemaname);
                    cxt.sequences = lappend(cxt.sequences, element);
                }
                break;

            case T_CreateStmt:
                {
                    CreateStmt *elp = (CreateStmt *) element;

                    setSchemaName(cxt.schemaname, &elp->relation->schemaname);

                    /*
                     * XXX todo: deal with constraints
                     */
                    cxt.tables = lappend(cxt.tables, element);
                }
                break;

            case T_ViewStmt:
                {
                    ViewStmt   *elp = (ViewStmt *) element;

                    setSchemaName(cxt.schemaname, &elp->view->schemaname);

                    /*
                     * XXX todo: deal with references between views
                     */
                    cxt.views = lappend(cxt.views, element);
                }
                break;

            case T_IndexStmt:
                {
                    IndexStmt  *elp = (IndexStmt *) element;

                    setSchemaName(cxt.schemaname, &elp->relation->schemaname);
                    cxt.indexes = lappend(cxt.indexes, element);
                }
                break;

            case T_CreateTrigStmt:
                {
                    CreateTrigStmt *elp = (CreateTrigStmt *) element;

                    setSchemaName(cxt.schemaname, &elp->relation->schemaname);
                    cxt.triggers = lappend(cxt.triggers, element);
                }
                break;

            case T_GrantStmt:
                cxt.grants = lappend(cxt.grants, element);
                break;

            default:
                elog(ERROR, "unrecognized node type: %d",
                     (int) nodeTag(element));
        }
    }

    result = NIL;
    result = list_concat(result, cxt.sequences);
    result = list_concat(result, cxt.tables);
    result = list_concat(result, cxt.views);
    result = list_concat(result, cxt.indexes);
    result = list_concat(result, cxt.triggers);
    result = list_concat(result, cxt.grants);

    return result;
}

List* transformCreateStmt ( CreateStmt stmt,
const char *  queryString 
)

Definition at line 143 of file parse_utilcmd.c.

References CreateStmtContext::alist, Assert, CreateStmtContext::blist, CreateStmtContext::ckconstraints, CreateStmtContext::columns, CreateStmt::constraints, copyObject(), element(), elog, ereport, errcode(), errmsg(), ERROR, CreateStmtContext::fkconstraints, get_namespace_name(), CreateStmtContext::hasoids, CreateStmt::if_not_exists, CreateStmtContext::inh_indexes, CreateStmt::inhRelations, CreateStmtContext::inhRelations, interpretOidsOption(), IsA, CreateStmtContext::isalter, CreateStmtContext::isforeign, CreateStmtContext::ixconstraints, lappend(), lfirst, list_concat(), make_parsestate(), NIL, nodeTag, NoLock, NOTICE, NULL, CreateStmt::ofTypename, OidIsValid, CreateStmt::options, ParseState::p_sourcetext, CreateStmtContext::pkey, CreateStmtContext::pstate, RangeVarGetAndCheckCreationNamespace(), CreateStmtContext::rel, CreateStmtContext::relation, CreateStmt::relation, RangeVar::relname, RangeVar::relpersistence, RELPERSISTENCE_TEMP, RangeVar::schemaname, CreateStmtContext::stmtType, T_ColumnDef, T_Constraint, T_TableLikeClause, CreateStmt::tableElts, transformColumnDefinition(), transformFKConstraints(), transformIndexConstraints(), transformOfType(), transformTableConstraint(), and transformTableLikeClause().

Referenced by ProcessUtilitySlow().

{
    ParseState *pstate;
    CreateStmtContext cxt;
    List       *result;
    List       *save_alist;
    ListCell   *elements;
    Oid         namespaceid;
    Oid         existing_relid;

    /*
     * We must not scribble on the passed-in CreateStmt, so copy it.  (This is
     * overkill, but easy.)
     */
    stmt = (CreateStmt *) copyObject(stmt);

    /*
     * Look up the creation namespace.  This also checks permissions on the
     * target namespace, locks it against concurrent drops, checks for a
     * preexisting relation in that namespace with the same name, and updates
     * stmt->relation->relpersistence if the select namespace is temporary.
     */
    namespaceid =
        RangeVarGetAndCheckCreationNamespace(stmt->relation, NoLock,
                                             &existing_relid);

    /*
     * If the relation already exists and the user specified "IF NOT EXISTS",
     * bail out with a NOTICE.
     */
    if (stmt->if_not_exists && OidIsValid(existing_relid))
    {
        ereport(NOTICE,
                (errcode(ERRCODE_DUPLICATE_TABLE),
                 errmsg("relation \"%s\" already exists, skipping",
                        stmt->relation->relname)));
        return NIL;
    }

    /*
     * If the target relation name isn't schema-qualified, make it so.  This
     * prevents some corner cases in which added-on rewritten commands might
     * think they should apply to other relations that have the same name and
     * are earlier in the search path.  But a local temp table is effectively
     * specified to be in pg_temp, so no need for anything extra in that case.
     */
    if (stmt->relation->schemaname == NULL
        && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
        stmt->relation->schemaname = get_namespace_name(namespaceid);

    /* Set up pstate and CreateStmtContext */
    pstate = make_parsestate(NULL);
    pstate->p_sourcetext = queryString;

    cxt.pstate = pstate;
    if (IsA(stmt, CreateForeignTableStmt))
    {
        cxt.stmtType = "CREATE FOREIGN TABLE";
        cxt.isforeign = true;
    }
    else
    {
        cxt.stmtType = "CREATE TABLE";
        cxt.isforeign = false;
    }
    cxt.relation = stmt->relation;
    cxt.rel = NULL;
    cxt.inhRelations = stmt->inhRelations;
    cxt.isalter = false;
    cxt.columns = NIL;
    cxt.ckconstraints = NIL;
    cxt.fkconstraints = NIL;
    cxt.ixconstraints = NIL;
    cxt.inh_indexes = NIL;
    cxt.blist = NIL;
    cxt.alist = NIL;
    cxt.pkey = NULL;
    cxt.hasoids = interpretOidsOption(stmt->options, true);

    Assert(!stmt->ofTypename || !stmt->inhRelations);   /* grammar enforces */

    if (stmt->ofTypename)
        transformOfType(&cxt, stmt->ofTypename);

    /*
     * Run through each primary element in the table creation clause. Separate
     * column defs from constraints, and do preliminary analysis.
     */
    foreach(elements, stmt->tableElts)
    {
        Node       *element = lfirst(elements);

        switch (nodeTag(element))
        {
            case T_ColumnDef:
                transformColumnDefinition(&cxt, (ColumnDef *) element);
                break;

            case T_Constraint:
                transformTableConstraint(&cxt, (Constraint *) element);
                break;

            case T_TableLikeClause:
                transformTableLikeClause(&cxt, (TableLikeClause *) element);
                break;

            default:
                elog(ERROR, "unrecognized node type: %d",
                     (int) nodeTag(element));
                break;
        }
    }

    /*
     * transformIndexConstraints wants cxt.alist to contain only index
     * statements, so transfer anything we already have into save_alist.
     */
    save_alist = cxt.alist;
    cxt.alist = NIL;

    Assert(stmt->constraints == NIL);

    /*
     * Postprocess constraints that give rise to index definitions.
     */
    transformIndexConstraints(&cxt);

    /*
     * Postprocess foreign-key constraints.
     */
    transformFKConstraints(&cxt, true, false);

    /*
     * Output results.
     */
    stmt->tableElts = cxt.columns;
    stmt->constraints = cxt.ckconstraints;

    result = lappend(cxt.blist, stmt);
    result = list_concat(result, cxt.alist);
    result = list_concat(result, save_alist);

    return result;
}

IndexStmt* transformIndexStmt ( IndexStmt stmt,
const char *  queryString 
)

Definition at line 1912 of file parse_utilcmd.c.

References addRangeTableEntry(), addRTEtoQuery(), assign_expr_collations(), IndexStmt::concurrent, copyObject(), ereport, errcode(), errmsg(), ERROR, IndexElem::expr, EXPR_KIND_INDEX_EXPRESSION, EXPR_KIND_INDEX_PREDICATE, expression_returns_set(), FigureIndexColname(), free_parsestate(), heap_close, heap_openrv(), IndexElem::indexcolname, IndexStmt::indexParams, lfirst, list_length(), make_parsestate(), NoLock, NULL, ParseState::p_rtable, ParseState::p_sourcetext, IndexStmt::relation, ShareLock, ShareUpdateExclusiveLock, transformExpr(), transformWhereClause(), and IndexStmt::whereClause.

Referenced by ATPostAlterTypeParse(), ProcessUtilitySlow(), and transformAlterTableStmt().

{
    Relation    rel;
    ParseState *pstate;
    RangeTblEntry *rte;
    ListCell   *l;

    /*
     * We must not scribble on the passed-in IndexStmt, so copy it.  (This is
     * overkill, but easy.)
     */
    stmt = (IndexStmt *) copyObject(stmt);

    /*
     * Open the parent table with appropriate locking.  We must do this
     * because addRangeTableEntry() would acquire only AccessShareLock,
     * leaving DefineIndex() needing to do a lock upgrade with consequent risk
     * of deadlock.  Make sure this stays in sync with the type of lock
     * DefineIndex() wants. If we are being called by ALTER TABLE, we will
     * already hold a higher lock.
     */
    rel = heap_openrv(stmt->relation,
                  (stmt->concurrent ? ShareUpdateExclusiveLock : ShareLock));

    /* Set up pstate */
    pstate = make_parsestate(NULL);
    pstate->p_sourcetext = queryString;

    /*
     * Put the parent table into the rtable so that the expressions can refer
     * to its fields without qualification.
     */
    rte = addRangeTableEntry(pstate, stmt->relation, NULL, false, true);

    /* no to join list, yes to namespaces */
    addRTEtoQuery(pstate, rte, false, true, true);

    /* take care of the where clause */
    if (stmt->whereClause)
    {
        stmt->whereClause = transformWhereClause(pstate,
                                                 stmt->whereClause,
                                                 EXPR_KIND_INDEX_PREDICATE,
                                                 "WHERE");
        /* we have to fix its collations too */
        assign_expr_collations(pstate, stmt->whereClause);
    }

    /* take care of any index expressions */
    foreach(l, stmt->indexParams)
    {
        IndexElem  *ielem = (IndexElem *) lfirst(l);

        if (ielem->expr)
        {
            /* Extract preliminary index col name before transforming expr */
            if (ielem->indexcolname == NULL)
                ielem->indexcolname = FigureIndexColname(ielem->expr);

            /* Now do parse transformation of the expression */
            ielem->expr = transformExpr(pstate, ielem->expr,
                                        EXPR_KIND_INDEX_EXPRESSION);

            /* We have to fix its collations too */
            assign_expr_collations(pstate, ielem->expr);

            /*
             * transformExpr() should have already rejected subqueries,
             * aggregates, and window functions, based on the EXPR_KIND_ for
             * an index expression.
             *
             * Also reject expressions returning sets; this is for consistency
             * with what transformWhereClause() checks for the predicate.
             * DefineIndex() will make more checks.
             */
            if (expression_returns_set(ielem->expr))
                ereport(ERROR,
                        (errcode(ERRCODE_DATATYPE_MISMATCH),
                         errmsg("index expression cannot return a set")));
        }
    }

    /*
     * Check that only the base rel is mentioned.  (This should be dead code
     * now that add_missing_from is history.)
     */
    if (list_length(pstate->p_rtable) != 1)
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
                 errmsg("index expressions and predicates can refer only to the table being indexed")));

    free_parsestate(pstate);

    /* Close relation, but keep the lock */
    heap_close(rel, NoLock);

    return stmt;
}

void transformRuleStmt ( RuleStmt stmt,
const char *  queryString,
List **  actions,
Node **  whereClause 
)

Definition at line 2025 of file parse_utilcmd.c.

References AccessExclusiveLock, RuleStmt::actions, addRangeTableEntryForRelation(), addRTEtoQuery(), assign_expr_collations(), CMD_DELETE, CMD_INSERT, CMD_SELECT, CMD_UPDATE, CMD_UTILITY, Query::commandType, copyObject(), Query::cteList, elog, ereport, errcode(), errmsg(), ERROR, RuleStmt::event, EXPR_KIND_WHERE, free_parsestate(), getInsertSelectQuery(), heap_close, heap_openrv(), Query::jointree, lappend(), lfirst, list_length(), list_make1, make_parsestate(), makeAlias(), makeFromExpr(), makeNode, NIL, NoLock, NULL, ParseState::p_joinlist, ParseState::p_rtable, ParseState::p_sourcetext, PRS2_NEW_VARNO, PRS2_OLD_VARNO, rangeTableEntry_used(), RelationData::rd_rel, RuleStmt::relation, RELKIND_MATVIEW, RangeTblEntry::requiredPerms, Query::rtable, Query::setOperations, transformStmt(), transformWhereClause(), and RuleStmt::whereClause.

Referenced by DefineRule().

{
    Relation    rel;
    ParseState *pstate;
    RangeTblEntry *oldrte;
    RangeTblEntry *newrte;

    /*
     * To avoid deadlock, make sure the first thing we do is grab
     * AccessExclusiveLock on the target relation.  This will be needed by
     * DefineQueryRewrite(), and we don't want to grab a lesser lock
     * beforehand.
     */
    rel = heap_openrv(stmt->relation, AccessExclusiveLock);

    if (rel->rd_rel->relkind == RELKIND_MATVIEW)
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 errmsg("rules on materialized views are not supported")));

    /* Set up pstate */
    pstate = make_parsestate(NULL);
    pstate->p_sourcetext = queryString;

    /*
     * NOTE: 'OLD' must always have a varno equal to 1 and 'NEW' equal to 2.
     * Set up their RTEs in the main pstate for use in parsing the rule
     * qualification.
     */
    oldrte = addRangeTableEntryForRelation(pstate, rel,
                                           makeAlias("old", NIL),
                                           false, false);
    newrte = addRangeTableEntryForRelation(pstate, rel,
                                           makeAlias("new", NIL),
                                           false, false);
    /* Must override addRangeTableEntry's default access-check flags */
    oldrte->requiredPerms = 0;
    newrte->requiredPerms = 0;

    /*
     * They must be in the namespace too for lookup purposes, but only add the
     * one(s) that are relevant for the current kind of rule.  In an UPDATE
     * rule, quals must refer to OLD.field or NEW.field to be unambiguous, but
     * there's no need to be so picky for INSERT & DELETE.  We do not add them
     * to the joinlist.
     */
    switch (stmt->event)
    {
        case CMD_SELECT:
            addRTEtoQuery(pstate, oldrte, false, true, true);
            break;
        case CMD_UPDATE:
            addRTEtoQuery(pstate, oldrte, false, true, true);
            addRTEtoQuery(pstate, newrte, false, true, true);
            break;
        case CMD_INSERT:
            addRTEtoQuery(pstate, newrte, false, true, true);
            break;
        case CMD_DELETE:
            addRTEtoQuery(pstate, oldrte, false, true, true);
            break;
        default:
            elog(ERROR, "unrecognized event type: %d",
                 (int) stmt->event);
            break;
    }

    /* take care of the where clause */
    *whereClause = transformWhereClause(pstate,
                                      (Node *) copyObject(stmt->whereClause),
                                        EXPR_KIND_WHERE,
                                        "WHERE");
    /* we have to fix its collations too */
    assign_expr_collations(pstate, *whereClause);

    /* this is probably dead code without add_missing_from: */
    if (list_length(pstate->p_rtable) != 2)     /* naughty, naughty... */
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                 errmsg("rule WHERE condition cannot contain references to other relations")));

    /*
     * 'instead nothing' rules with a qualification need a query rangetable so
     * the rewrite handler can add the negated rule qualification to the
     * original query. We create a query with the new command type CMD_NOTHING
     * here that is treated specially by the rewrite system.
     */
    if (stmt->actions == NIL)
    {
        Query      *nothing_qry = makeNode(Query);

        nothing_qry->commandType = CMD_NOTHING;
        nothing_qry->rtable = pstate->p_rtable;
        nothing_qry->jointree = makeFromExpr(NIL, NULL);        /* no join wanted */

        *actions = list_make1(nothing_qry);
    }
    else
    {
        ListCell   *l;
        List       *newactions = NIL;

        /*
         * transform each statement, like parse_sub_analyze()
         */
        foreach(l, stmt->actions)
        {
            Node       *action = (Node *) lfirst(l);
            ParseState *sub_pstate = make_parsestate(NULL);
            Query      *sub_qry,
                       *top_subqry;
            bool        has_old,
                        has_new;

            /*
             * Since outer ParseState isn't parent of inner, have to pass down
             * the query text by hand.
             */
            sub_pstate->p_sourcetext = queryString;

            /*
             * Set up OLD/NEW in the rtable for this statement.  The entries
             * are added only to relnamespace, not varnamespace, because we
             * don't want them to be referred to by unqualified field names
             * nor "*" in the rule actions.  We decide later whether to put
             * them in the joinlist.
             */
            oldrte = addRangeTableEntryForRelation(sub_pstate, rel,
                                                   makeAlias("old", NIL),
                                                   false, false);
            newrte = addRangeTableEntryForRelation(sub_pstate, rel,
                                                   makeAlias("new", NIL),
                                                   false, false);
            oldrte->requiredPerms = 0;
            newrte->requiredPerms = 0;
            addRTEtoQuery(sub_pstate, oldrte, false, true, false);
            addRTEtoQuery(sub_pstate, newrte, false, true, false);

            /* Transform the rule action statement */
            top_subqry = transformStmt(sub_pstate,
                                       (Node *) copyObject(action));

            /*
             * We cannot support utility-statement actions (eg NOTIFY) with
             * nonempty rule WHERE conditions, because there's no way to make
             * the utility action execute conditionally.
             */
            if (top_subqry->commandType == CMD_UTILITY &&
                *whereClause != NULL)
                ereport(ERROR,
                        (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                         errmsg("rules with WHERE conditions can only have SELECT, INSERT, UPDATE, or DELETE actions")));

            /*
             * If the action is INSERT...SELECT, OLD/NEW have been pushed down
             * into the SELECT, and that's what we need to look at. (Ugly
             * kluge ... try to fix this when we redesign querytrees.)
             */
            sub_qry = getInsertSelectQuery(top_subqry, NULL);

            /*
             * If the sub_qry is a setop, we cannot attach any qualifications
             * to it, because the planner won't notice them.  This could
             * perhaps be relaxed someday, but for now, we may as well reject
             * such a rule immediately.
             */
            if (sub_qry->setOperations != NULL && *whereClause != NULL)
                ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                         errmsg("conditional UNION/INTERSECT/EXCEPT statements are not implemented")));

            /*
             * Validate action's use of OLD/NEW, qual too
             */
            has_old =
                rangeTableEntry_used((Node *) sub_qry, PRS2_OLD_VARNO, 0) ||
                rangeTableEntry_used(*whereClause, PRS2_OLD_VARNO, 0);
            has_new =
                rangeTableEntry_used((Node *) sub_qry, PRS2_NEW_VARNO, 0) ||
                rangeTableEntry_used(*whereClause, PRS2_NEW_VARNO, 0);

            switch (stmt->event)
            {
                case CMD_SELECT:
                    if (has_old)
                        ereport(ERROR,
                                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                                 errmsg("ON SELECT rule cannot use OLD")));
                    if (has_new)
                        ereport(ERROR,
                                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                                 errmsg("ON SELECT rule cannot use NEW")));
                    break;
                case CMD_UPDATE:
                    /* both are OK */
                    break;
                case CMD_INSERT:
                    if (has_old)
                        ereport(ERROR,
                                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                                 errmsg("ON INSERT rule cannot use OLD")));
                    break;
                case CMD_DELETE:
                    if (has_new)
                        ereport(ERROR,
                                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                                 errmsg("ON DELETE rule cannot use NEW")));
                    break;
                default:
                    elog(ERROR, "unrecognized event type: %d",
                         (int) stmt->event);
                    break;
            }

            /*
             * OLD/NEW are not allowed in WITH queries, because they would
             * amount to outer references for the WITH, which we disallow.
             * However, they were already in the outer rangetable when we
             * analyzed the query, so we have to check.
             *
             * Note that in the INSERT...SELECT case, we need to examine the
             * CTE lists of both top_subqry and sub_qry.
             *
             * Note that we aren't digging into the body of the query looking
             * for WITHs in nested sub-SELECTs.  A WITH down there can
             * legitimately refer to OLD/NEW, because it'd be an
             * indirect-correlated outer reference.
             */
            if (rangeTableEntry_used((Node *) top_subqry->cteList,
                                     PRS2_OLD_VARNO, 0) ||
                rangeTableEntry_used((Node *) sub_qry->cteList,
                                     PRS2_OLD_VARNO, 0))
                ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                         errmsg("cannot refer to OLD within WITH query")));
            if (rangeTableEntry_used((Node *) top_subqry->cteList,
                                     PRS2_NEW_VARNO, 0) ||
                rangeTableEntry_used((Node *) sub_qry->cteList,
                                     PRS2_NEW_VARNO, 0))
                ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                         errmsg("cannot refer to NEW within WITH query")));

            /*
             * For efficiency's sake, add OLD to the rule action's jointree
             * only if it was actually referenced in the statement or qual.
             *
             * For INSERT, NEW is not really a relation (only a reference to
             * the to-be-inserted tuple) and should never be added to the
             * jointree.
             *
             * For UPDATE, we treat NEW as being another kind of reference to
             * OLD, because it represents references to *transformed* tuples
             * of the existing relation.  It would be wrong to enter NEW
             * separately in the jointree, since that would cause a double
             * join of the updated relation.  It's also wrong to fail to make
             * a jointree entry if only NEW and not OLD is mentioned.
             */
            if (has_old || (has_new && stmt->event == CMD_UPDATE))
            {
                /*
                 * If sub_qry is a setop, manipulating its jointree will do no
                 * good at all, because the jointree is dummy. (This should be
                 * a can't-happen case because of prior tests.)
                 */
                if (sub_qry->setOperations != NULL)
                    ereport(ERROR,
                            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                             errmsg("conditional UNION/INTERSECT/EXCEPT statements are not implemented")));
                /* hack so we can use addRTEtoQuery() */
                sub_pstate->p_rtable = sub_qry->rtable;
                sub_pstate->p_joinlist = sub_qry->jointree->fromlist;
                addRTEtoQuery(sub_pstate, oldrte, true, false, false);
                sub_qry->jointree->fromlist = sub_pstate->p_joinlist;
            }

            newactions = lappend(newactions, top_subqry);

            free_parsestate(sub_pstate);
        }

        *actions = newactions;
    }

    free_parsestate(pstate);

    /* Close relation, but keep the exclusive lock */
    heap_close(rel, NoLock);
}