#include "parser/parse_node.h"
Go to the source code of this file.
Functions | |
List * | transformCreateStmt (CreateStmt *stmt, const char *queryString) |
List * | transformAlterTableStmt (AlterTableStmt *stmt, const char *queryString) |
IndexStmt * | transformIndexStmt (IndexStmt *stmt, const char *queryString) |
void | transformRuleStmt (RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause) |
List * | transformCreateSchemaStmt (CreateSchemaStmt *stmt) |
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; }
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); }