aboutsummaryrefslogtreecommitdiff
path: root/src/backend/parser
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/parser')
-rw-r--r--src/backend/parser/analyze.c697
-rw-r--r--src/backend/parser/gram.y349
-rw-r--r--src/backend/parser/parse_clause.c136
-rw-r--r--src/backend/parser/parse_coerce.c96
-rw-r--r--src/backend/parser/parse_expr.c113
5 files changed, 718 insertions, 673 deletions
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 169075c47e2..ffedca05ed9 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: analyze.c,v 1.159 2000/09/29 18:21:36 tgl Exp $
+ * $Id: analyze.c,v 1.160 2000/10/05 19:11:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -20,8 +20,10 @@
#include "nodes/makefuncs.h"
#include "parser/analyze.h"
#include "parser/parse.h"
+#include "parser/parsetree.h"
#include "parser/parse_agg.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
#include "parser/parse_type.h"
@@ -44,11 +46,13 @@ static Query *transformIndexStmt(ParseState *pstate, IndexStmt *stmt);
static Query *transformExtendStmt(ParseState *pstate, ExtendStmt *stmt);
static Query *transformRuleStmt(ParseState *query, RuleStmt *stmt);
static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
+static Query *transformSetOperationStmt(ParseState *pstate, SetOperationStmt *stmt);
+static Node *transformSetOperationTree(ParseState *pstate, Node *node);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
-static Query *transformCursorStmt(ParseState *pstate, SelectStmt *stmt);
static Query *transformCreateStmt(ParseState *pstate, CreateStmt *stmt);
static Query *transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt);
+static List *getSetColTypes(ParseState *pstate, Node *node);
static void transformForUpdate(Query *qry, List *forUpdate);
static void transformFkeyGetPrimaryKey(FkConstraint *fkconstraint);
static void transformConstraintAttrs(List *constraintList);
@@ -156,7 +160,7 @@ transformStmt(ParseState *pstate, Node *parseTree)
{
ViewStmt *n = (ViewStmt *) parseTree;
- n->query = (Query *) transformStmt(pstate, (Node *) n->query);
+ n->query = transformStmt(pstate, (Node *) n->query);
/*
* If a list of column names was given, run through and
@@ -258,20 +262,17 @@ transformStmt(ParseState *pstate, Node *parseTree)
break;
case T_SelectStmt:
- if (!((SelectStmt *) parseTree)->portalname)
- {
- result = transformSelectStmt(pstate, (SelectStmt *) parseTree);
- result->limitOffset = ((SelectStmt *) parseTree)->limitOffset;
- result->limitCount = ((SelectStmt *) parseTree)->limitCount;
- }
- else
- result = transformCursorStmt(pstate, (SelectStmt *) parseTree);
+ result = transformSelectStmt(pstate, (SelectStmt *) parseTree);
+ break;
+
+ case T_SetOperationStmt:
+ result = transformSetOperationStmt(pstate, (SetOperationStmt *) parseTree);
break;
default:
/*
- * other statments don't require any transformation-- just
+ * other statements don't require any transformation-- just
* return the original parsetree, yea!
*/
result = makeNode(Query);
@@ -313,7 +314,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
if (pstate->p_hasAggs)
parseCheckAggregates(pstate, qry, qual);
- return (Query *) qry;
+ return qry;
}
/*
@@ -324,7 +325,6 @@ static Query *
transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
{
Query *qry = makeNode(Query);
- Node *qual;
List *icolumns;
List *attrnos;
List *attnos;
@@ -335,59 +335,89 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
qry->commandType = CMD_INSERT;
pstate->p_is_insert = true;
- /*----------
- * Initial processing steps are just like SELECT, which should not
- * be surprising, since we may be handling an INSERT ... SELECT.
- * It is important that we finish processing all the SELECT subclauses
- * before we start doing any INSERT-specific processing; otherwise
- * the behavior of SELECT within INSERT might be different from a
- * stand-alone SELECT. (Indeed, Postgres up through 6.5 had bugs of
- * just that nature...)
- *----------
- */
-
- /* set up a range table --- note INSERT target is not in it yet */
- makeRangeTable(pstate, stmt->fromClause);
-
- qry->targetList = transformTargetList(pstate, stmt->targetList);
-
- qual = transformWhereClause(pstate, stmt->whereClause);
-
- /*
- * Initial processing of HAVING clause is just like WHERE clause.
- * Additional work will be done in optimizer/plan/planner.c.
- */
- qry->havingQual = transformWhereClause(pstate, stmt->havingClause);
-
- qry->groupClause = transformGroupClause(pstate,
- stmt->groupClause,
- qry->targetList);
-
- /* An InsertStmt has no sortClause */
- qry->sortClause = NIL;
-
- qry->distinctClause = transformDistinctClause(pstate,
- stmt->distinctClause,
- qry->targetList,
- &qry->sortClause);
-
- qry->hasSubLinks = pstate->p_hasSubLinks;
- qry->hasAggs = pstate->p_hasAggs;
- if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
- parseCheckAggregates(pstate, qry, qual);
-
/*
- * The INSERT INTO ... SELECT ... could have a UNION in child, so
- * unionClause may be false
+ * Is it INSERT ... SELECT or INSERT ... VALUES?
*/
- qry->unionall = stmt->unionall;
+ if (stmt->selectStmt)
+ {
+ List *selectList;
+ Query *selectQuery;
+ RangeTblEntry *rte;
+ RangeTblRef *rtr;
- /*
- * Just hand through the unionClause and intersectClause. We will
- * handle it in the function Except_Intersect_Rewrite()
- */
- qry->unionClause = stmt->unionClause;
- qry->intersectClause = stmt->intersectClause;
+ /*
+ * Process the source SELECT.
+ *
+ * It is important that this be handled just like a standalone SELECT;
+ * otherwise the behavior of SELECT within INSERT might be different
+ * from a stand-alone SELECT. (Indeed, Postgres up through 6.5 had
+ * bugs of just that nature...)
+ */
+ selectList = parse_analyze(makeList1(stmt->selectStmt), pstate);
+ Assert(length(selectList) == 1);
+
+ selectQuery = (Query *) lfirst(selectList);
+ Assert(IsA(selectQuery, Query));
+ Assert(selectQuery->commandType == CMD_SELECT);
+ if (selectQuery->into || selectQuery->isPortal)
+ elog(ERROR, "INSERT ... SELECT may not specify INTO");
+ /*
+ * Make the source be a subquery in the INSERT's rangetable,
+ * and add it to the joinlist.
+ */
+ rte = addRangeTableEntryForSubquery(pstate,
+ selectQuery,
+ makeAttr("*SELECT*", NULL),
+ true);
+ rtr = makeNode(RangeTblRef);
+ /* assume new rte is at end */
+ rtr->rtindex = length(pstate->p_rtable);
+ Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
+ pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
+ /*
+ * Generate a targetlist for the INSERT that selects all
+ * the non-resjunk columns from the subquery. (We need this to
+ * be separate from the subquery's tlist because we may add
+ * columns, insert datatype coercions, etc.)
+ *
+ * HACK: constants in the INSERT's targetlist are copied up as-is
+ * rather than being referenced as subquery outputs. This is mainly
+ * to ensure that when we try to coerce them to the target column's
+ * datatype, the right things happen for UNKNOWN constants.
+ * Otherwise this fails:
+ * INSERT INTO foo SELECT 'bar', ... FROM baz
+ */
+ qry->targetList = NIL;
+ foreach(tl, selectQuery->targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(tl);
+ Resdom *resnode = tle->resdom;
+ Node *expr;
+
+ if (resnode->resjunk)
+ continue;
+ if (tle->expr && IsA(tle->expr, Const))
+ expr = tle->expr;
+ else
+ expr = (Node *) makeVar(rtr->rtindex,
+ resnode->resno,
+ resnode->restype,
+ resnode->restypmod,
+ 0);
+ resnode = copyObject(resnode);
+ resnode->resno = (AttrNumber) pstate->p_last_resno++;
+ qry->targetList = lappend(qry->targetList,
+ makeTargetEntry(resnode, expr));
+ }
+ }
+ else
+ {
+ /*
+ * For INSERT ... VALUES, transform the given list of values
+ * to form a targetlist for the INSERT.
+ */
+ qry->targetList = transformTargetList(pstate, stmt->targetList);
+ }
/*
* Now we are done with SELECT-like processing, and can get on with
@@ -400,11 +430,6 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
*/
setTargetTable(pstate, stmt->relname, false, false);
- /* now the range table and jointree will not change */
- qry->rtable = pstate->p_rtable;
- qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
- qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL);
-
/* Prepare to assign non-conflicting resnos to resjunk attributes */
if (pstate->p_last_resno <= pstate->p_target_relation->rd_rel->relnatts)
pstate->p_last_resno = pstate->p_target_relation->rd_rel->relnatts + 1;
@@ -412,7 +437,9 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/* Validate stmt->cols list, or build default list if no list given */
icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos);
- /* Prepare non-junk columns for assignment to target table */
+ /*
+ * Prepare columns for assignment to target table.
+ */
numuseratts = 0;
attnos = attrnos;
foreach(tl, qry->targetList)
@@ -421,18 +448,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
Resdom *resnode = tle->resdom;
Ident *id;
- if (resnode->resjunk)
- {
-
- /*
- * Resjunk nodes need no additional processing, but be sure
- * they have names and resnos that do not match any target
- * columns; else rewriter or planner might get confused.
- */
- resnode->resname = "?resjunk?";
- resnode->resno = (AttrNumber) pstate->p_last_resno++;
- continue;
- }
+ Assert(!resnode->resjunk);
if (icolumns == NIL || attnos == NIL)
elog(ERROR, "INSERT has more expressions than target columns");
id = (Ident *) lfirst(icolumns);
@@ -458,7 +474,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
* have defaults and were not assigned to by the user.
*
* XXX wouldn't it make more sense to do this further downstream, after
- * the rule rewriter?
+ * the rule rewriter? As is, altering a column default will not change
+ * the behavior of INSERTs in already-defined rules.
*/
rd_att = pstate->p_target_relation->rd_att;
if (rd_att->constr && rd_att->constr->num_defval > 0)
@@ -498,13 +515,17 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
}
}
- if (stmt->forUpdate != NULL)
- transformForUpdate(qry, stmt->forUpdate);
+ /* done building the range table and jointree */
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+ qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL);
- /* in case of subselects in default clauses... */
qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasAggs = pstate->p_hasAggs;
+ if (pstate->p_hasAggs)
+ parseCheckAggregates(pstate, qry, NULL);
- return (Query *) qry;
+ return qry;
}
/*
@@ -1608,6 +1629,7 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
* transformSelectStmt -
* transforms a Select Statement
*
+ * Note: this is also used for DECLARE CURSOR statements.
*/
static Query *
transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
@@ -1617,13 +1639,42 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
qry->commandType = CMD_SELECT;
+ if (stmt->portalname)
+ {
+ /* DECLARE CURSOR */
+ if (stmt->into)
+ elog(ERROR, "DECLARE CURSOR must not specify INTO");
+ if (stmt->forUpdate)
+ elog(ERROR, "DECLARE/UPDATE is not supported"
+ "\n\tCursors must be READ ONLY");
+ /*
+ * 15 august 1991 -- since 3.0 postgres does locking
+ * right, we discovered that portals were violating
+ * locking protocol. portal locks cannot span xacts.
+ * as a short-term fix, we installed the check here.
+ * -- mao
+ */
+ if (!IsTransactionBlock())
+ elog(ERROR, "DECLARE CURSOR may only be used in begin/end transaction blocks");
+
+ qry->into = stmt->portalname;
+ qry->isTemp = stmt->istemp;
+ qry->isPortal = TRUE;
+ qry->isBinary = stmt->binary; /* internal portal */
+ }
+ else
+ {
+ /* SELECT */
+ qry->into = stmt->into;
+ qry->isTemp = stmt->istemp;
+ qry->isPortal = FALSE;
+ qry->isBinary = FALSE;
+ }
+
/* set up a range table */
makeRangeTable(pstate, stmt->fromClause);
- qry->into = stmt->into;
- qry->isTemp = stmt->istemp;
- qry->isPortal = FALSE;
-
+ /* transform targetlist and WHERE */
qry->targetList = transformTargetList(pstate, stmt->targetList);
qual = transformWhereClause(pstate, stmt->whereClause);
@@ -1647,34 +1698,357 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
qry->targetList,
&qry->sortClause);
+ qry->limitOffset = stmt->limitOffset;
+ qry->limitCount = stmt->limitCount;
+
qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasAggs = pstate->p_hasAggs;
if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
parseCheckAggregates(pstate, qry, qual);
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
+
+ if (stmt->forUpdate != NULL)
+ transformForUpdate(qry, stmt->forUpdate);
+
+ return qry;
+}
+
+/*
+ * transformSetOperationsStmt -
+ * transforms a SetOperations Statement
+ *
+ * SetOperations is actually just a SELECT, but with UNION/INTERSECT/EXCEPT
+ * structure to it. We must transform each leaf SELECT and build up a top-
+ * level Query that contains the leaf SELECTs as subqueries in its rangetable.
+ * The SetOperations tree (with leaf SelectStmts replaced by RangeTblRef nodes)
+ * becomes the setOperations field of the top-level Query.
+ */
+static Query *
+transformSetOperationStmt(ParseState *pstate, SetOperationStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+ Node *node;
+ SelectStmt *leftmostSelect;
+ Query *leftmostQuery;
+ char *into;
+ char *portalname;
+ bool binary;
+ bool istemp;
+ List *sortClause;
+ Node *limitOffset;
+ Node *limitCount;
+ List *forUpdate;
+ List *lefttl,
+ *dtlist;
+ int tllen;
+
+ qry->commandType = CMD_SELECT;
+
/*
- * The INSERT INTO ... SELECT ... could have a UNION in child, so
- * unionClause may be false
+ * Find leftmost leaf SelectStmt and extract the one-time-only items
+ * from it.
*/
- qry->unionall = stmt->unionall;
+ node = stmt->larg;
+ while (node && IsA(node, SetOperationStmt))
+ node = ((SetOperationStmt *) node)->larg;
+ Assert(node && IsA(node, SelectStmt));
+ leftmostSelect = (SelectStmt *) node;
+
+ into = leftmostSelect->into;
+ portalname = leftmostSelect->portalname;
+ binary = leftmostSelect->binary;
+ istemp = leftmostSelect->istemp;
+ sortClause = leftmostSelect->sortClause;
+ limitOffset = leftmostSelect->limitOffset;
+ limitCount = leftmostSelect->limitCount;
+ forUpdate = leftmostSelect->forUpdate;
+
+ /* clear them to prevent complaints in transformSetOperationTree() */
+ leftmostSelect->into = NULL;
+ leftmostSelect->portalname = NULL;
+ leftmostSelect->binary = false;
+ leftmostSelect->istemp = false;
+ leftmostSelect->sortClause = NIL;
+ leftmostSelect->limitOffset = NULL;
+ leftmostSelect->limitCount = NULL;
+ leftmostSelect->forUpdate = NIL;
+
+ /* We don't actually support forUpdate with set ops at the moment. */
+ if (forUpdate)
+ elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT");
/*
- * Just hand through the unionClause and intersectClause. We will
- * handle it in the function Except_Intersect_Rewrite()
+ * Recursively transform the components of the tree.
*/
- qry->unionClause = stmt->unionClause;
- qry->intersectClause = stmt->intersectClause;
+ stmt = (SetOperationStmt *)
+ transformSetOperationTree(pstate, (Node *) stmt);
+ Assert(stmt && IsA(stmt, SetOperationStmt));
+ qry->setOperations = (Node *) stmt;
+
+ /*
+ * Re-find leftmost SELECT (now it's a sub-query in rangetable)
+ */
+ node = stmt->larg;
+ while (node && IsA(node, SetOperationStmt))
+ node = ((SetOperationStmt *) node)->larg;
+ Assert(node && IsA(node, RangeTblRef));
+ leftmostQuery = rt_fetch(((RangeTblRef *) node)->rtindex,
+ pstate->p_rtable)->subquery;
+ Assert(leftmostQuery != NULL);
+ /*
+ * Generate dummy targetlist for outer query using column names of
+ * leftmost select and common datatypes of topmost set operation
+ */
+ qry->targetList = NIL;
+ lefttl = leftmostQuery->targetList;
+ foreach(dtlist, stmt->colTypes)
+ {
+ Oid colType = (Oid) lfirsti(dtlist);
+ char *colName = ((TargetEntry *) lfirst(lefttl))->resdom->resname;
+ Resdom *resdom;
+ Node *expr;
+
+ resdom = makeResdom((AttrNumber) pstate->p_last_resno++,
+ colType,
+ -1,
+ pstrdup(colName),
+ false);
+ expr = (Node *) makeVar(1,
+ resdom->resno,
+ colType,
+ -1,
+ 0);
+ qry->targetList = lappend(qry->targetList,
+ makeTargetEntry(resdom, expr));
+ lefttl = lnext(lefttl);
+ }
+ /*
+ * Insert one-time items into top-level query
+ *
+ * This needs to agree with transformSelectStmt!
+ */
+ if (portalname)
+ {
+ /* DECLARE CURSOR */
+ if (into)
+ elog(ERROR, "DECLARE CURSOR must not specify INTO");
+ if (forUpdate)
+ elog(ERROR, "DECLARE/UPDATE is not supported"
+ "\n\tCursors must be READ ONLY");
+ /*
+ * 15 august 1991 -- since 3.0 postgres does locking
+ * right, we discovered that portals were violating
+ * locking protocol. portal locks cannot span xacts.
+ * as a short-term fix, we installed the check here.
+ * -- mao
+ */
+ if (!IsTransactionBlock())
+ elog(ERROR, "DECLARE CURSOR may only be used in begin/end transaction blocks");
+
+ qry->into = portalname;
+ qry->isTemp = istemp;
+ qry->isPortal = TRUE;
+ qry->isBinary = binary; /* internal portal */
+ }
+ else
+ {
+ /* SELECT */
+ qry->into = into;
+ qry->isTemp = istemp;
+ qry->isPortal = FALSE;
+ qry->isBinary = FALSE;
+ }
+
+ /*
+ * For now, we don't support resjunk sort clauses on the output of a
+ * setOperation tree --- you can only use the SQL92-spec options of
+ * selecting an output column by name or number. Enforce by checking
+ * that transformSortClause doesn't add any items to tlist.
+ */
+ tllen = length(qry->targetList);
+
+ qry->sortClause = transformSortClause(pstate,
+ sortClause,
+ qry->targetList);
+
+ if (tllen != length(qry->targetList))
+ elog(ERROR, "ORDER BY on a UNION/INTERSECT/EXCEPT result must be on one of the result columns");
+
+ qry->limitOffset = limitOffset;
+ qry->limitCount = limitCount;
+
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasAggs = pstate->p_hasAggs;
+ if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
+ parseCheckAggregates(pstate, qry, NULL);
qry->rtable = pstate->p_rtable;
- qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
- if (stmt->forUpdate != NULL)
- transformForUpdate(qry, stmt->forUpdate);
+ if (forUpdate != NULL)
+ transformForUpdate(qry, forUpdate);
- return (Query *) qry;
+ return qry;
+}
+
+/*
+ * transformSetOperationTree
+ * Recursively transform leaves and internal nodes of a set-op tree
+ */
+static Node *
+transformSetOperationTree(ParseState *pstate, Node *node)
+{
+ if (IsA(node, SelectStmt))
+ {
+ SelectStmt *stmt = (SelectStmt *) node;
+ List *save_rtable;
+ List *selectList;
+ Query *selectQuery;
+ char selectName[32];
+ RangeTblEntry *rte;
+ RangeTblRef *rtr;
+
+ /*
+ * Validity-check leaf SELECTs for disallowed ops. INTO check is
+ * necessary, the others should have been disallowed by grammar.
+ */
+ if (stmt->into)
+ elog(ERROR, "INTO is only allowed on first SELECT of UNION/INTERSECT/EXCEPT");
+ if (stmt->portalname)
+ elog(ERROR, "Portal is only allowed on first SELECT of UNION/INTERSECT/EXCEPT");
+ if (stmt->sortClause)
+ elog(ERROR, "ORDER BY is only allowed at end of UNION/INTERSECT/EXCEPT");
+ if (stmt->limitOffset || stmt->limitCount)
+ elog(ERROR, "LIMIT is only allowed at end of UNION/INTERSECT/EXCEPT");
+ if (stmt->forUpdate)
+ elog(ERROR, "FOR UPDATE is only allowed at end of UNION/INTERSECT/EXCEPT");
+ /*
+ * Transform SelectStmt into a Query. We do not want any previously
+ * transformed leaf queries to be visible in the outer context of
+ * this sub-query, so temporarily make the top-level pstate have an
+ * empty rtable. (We needn't do the same with the joinlist because
+ * we aren't entering anything in the top-level joinlist.)
+ */
+ save_rtable = pstate->p_rtable;
+ pstate->p_rtable = NIL;
+ selectList = parse_analyze(makeList1(stmt), pstate);
+ pstate->p_rtable = save_rtable;
+
+ Assert(length(selectList) == 1);
+ selectQuery = (Query *) lfirst(selectList);
+ /*
+ * Make the leaf query be a subquery in the top-level rangetable.
+ */
+ sprintf(selectName, "*SELECT* %d", length(pstate->p_rtable) + 1);
+ rte = addRangeTableEntryForSubquery(pstate,
+ selectQuery,
+ makeAttr(pstrdup(selectName),
+ NULL),
+ false);
+ /*
+ * Return a RangeTblRef to replace the SelectStmt in the set-op tree.
+ */
+ rtr = makeNode(RangeTblRef);
+ /* assume new rte is at end */
+ rtr->rtindex = length(pstate->p_rtable);
+ Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
+ return (Node *) rtr;
+ }
+ else if (IsA(node, SetOperationStmt))
+ {
+ SetOperationStmt *op = (SetOperationStmt *) node;
+ List *lcoltypes;
+ List *rcoltypes;
+ const char *context;
+
+ context = (op->op == SETOP_UNION ? "UNION" :
+ (op->op == SETOP_INTERSECT ? "INTERSECT" :
+ "EXCEPT"));
+ /*
+ * Recursively transform the child nodes.
+ */
+ op->larg = transformSetOperationTree(pstate, op->larg);
+ op->rarg = transformSetOperationTree(pstate, op->rarg);
+ /*
+ * Verify that the two children have the same number of non-junk
+ * columns, and determine the types of the merged output columns.
+ */
+ lcoltypes = getSetColTypes(pstate, op->larg);
+ rcoltypes = getSetColTypes(pstate, op->rarg);
+ if (length(lcoltypes) != length(rcoltypes))
+ elog(ERROR, "Each %s query must have the same number of columns",
+ context);
+ op->colTypes = NIL;
+ while (lcoltypes != NIL)
+ {
+ Oid lcoltype = (Oid) lfirsti(lcoltypes);
+ Oid rcoltype = (Oid) lfirsti(rcoltypes);
+ Oid rescoltype;
+
+ rescoltype = select_common_type(makeListi2(lcoltype, rcoltype),
+ context);
+ op->colTypes = lappendi(op->colTypes, rescoltype);
+ lcoltypes = lnext(lcoltypes);
+ rcoltypes = lnext(rcoltypes);
+ }
+ return (Node *) op;
+ }
+ else
+ {
+ elog(ERROR, "transformSetOperationTree: unexpected node %d",
+ (int) nodeTag(node));
+ return NULL; /* keep compiler quiet */
+ }
}
/*
+ * getSetColTypes
+ * Get output column types of an (already transformed) set-op node
+ */
+static List *
+getSetColTypes(ParseState *pstate, Node *node)
+{
+ if (IsA(node, RangeTblRef))
+ {
+ RangeTblRef *rtr = (RangeTblRef *) node;
+ RangeTblEntry *rte = rt_fetch(rtr->rtindex, pstate->p_rtable);
+ Query *selectQuery = rte->subquery;
+ List *result = NIL;
+ List *tl;
+
+ Assert(selectQuery != NULL);
+ /* Get types of non-junk columns */
+ foreach(tl, selectQuery->targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(tl);
+ Resdom *resnode = tle->resdom;
+
+ if (resnode->resjunk)
+ continue;
+ result = lappendi(result, resnode->restype);
+ }
+ return result;
+ }
+ else if (IsA(node, SetOperationStmt))
+ {
+ SetOperationStmt *op = (SetOperationStmt *) node;
+
+ /* Result already computed during transformation of node */
+ Assert(op->colTypes != NIL);
+ return op->colTypes;
+ }
+ else
+ {
+ elog(ERROR, "getSetColTypes: unexpected node %d",
+ (int) nodeTag(node));
+ return NIL; /* keep compiler quiet */
+ }
+}
+
+
+/*
* transformUpdateStmt -
* transforms an update statement
*
@@ -1756,26 +2130,6 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
if (origTargetList != NIL)
elog(ERROR, "UPDATE target count mismatch --- internal error");
- return (Query *) qry;
-}
-
-/*
- * transformCursorStmt -
- * transform a Create Cursor Statement
- *
- */
-static Query *
-transformCursorStmt(ParseState *pstate, SelectStmt *stmt)
-{
- Query *qry;
-
- qry = transformSelectStmt(pstate, stmt);
-
- qry->into = stmt->portalname;
- qry->isTemp = stmt->istemp;
- qry->isPortal = TRUE;
- qry->isBinary = stmt->binary; /* internal portal */
-
return qry;
}
@@ -2039,106 +2393,11 @@ transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt)
return qry;
}
-
-/* This function steps through the tree
- * built up by the select_w_o_sort rule
- * and builds a list of all SelectStmt Nodes found
- * The built up list is handed back in **select_list.
- * If one of the SelectStmt Nodes has the 'unionall' flag
- * set to true *unionall_present hands back 'true' */
-void
-create_select_list(Node *ptr, List **select_list, bool *unionall_present)
-{
- if (IsA(ptr, SelectStmt))
- {
- *select_list = lappend(*select_list, ptr);
- if (((SelectStmt *) ptr)->unionall == TRUE)
- *unionall_present = TRUE;
- return;
- }
-
- /* Recursively call for all arguments. A NOT expr has no lexpr! */
- if (((A_Expr *) ptr)->lexpr != NULL)
- create_select_list(((A_Expr *) ptr)->lexpr, select_list, unionall_present);
- create_select_list(((A_Expr *) ptr)->rexpr, select_list, unionall_present);
-}
-
-/* Changes the A_Expr Nodes to Expr Nodes and exchanges ANDs and ORs.
- * The reason for the exchange is easy: We implement INTERSECTs and EXCEPTs
- * by rewriting these queries to semantically equivalent queries that use
- * IN and NOT IN subselects. To be able to use all three operations
- * (UNIONs INTERSECTs and EXCEPTs) in one complex query we have to
- * translate the queries into Disjunctive Normal Form (DNF). Unfortunately
- * there is no function 'dnfify' but there is a function 'cnfify'
- * which produces DNF when we exchange ANDs and ORs before calling
- * 'cnfify' and exchange them back in the result.
- *
- * If an EXCEPT or INTERSECT is present *intersect_present
- * hands back 'true' */
-Node *
-A_Expr_to_Expr(Node *ptr, bool *intersect_present)
-{
- Node *result = NULL;
-
- switch (nodeTag(ptr))
- {
- case T_A_Expr:
- {
- A_Expr *a = (A_Expr *) ptr;
-
- switch (a->oper)
- {
- case AND:
- {
- Expr *expr = makeNode(Expr);
- Node *lexpr = A_Expr_to_Expr(((A_Expr *) ptr)->lexpr, intersect_present);
- Node *rexpr = A_Expr_to_Expr(((A_Expr *) ptr)->rexpr, intersect_present);
-
- *intersect_present = TRUE;
-
- expr->typeOid = BOOLOID;
- expr->opType = OR_EXPR;
- expr->args = makeList2(lexpr, rexpr);
- result = (Node *) expr;
- break;
- }
- case OR:
- {
- Expr *expr = makeNode(Expr);
- Node *lexpr = A_Expr_to_Expr(((A_Expr *) ptr)->lexpr, intersect_present);
- Node *rexpr = A_Expr_to_Expr(((A_Expr *) ptr)->rexpr, intersect_present);
-
- expr->typeOid = BOOLOID;
- expr->opType = AND_EXPR;
- expr->args = makeList2(lexpr, rexpr);
- result = (Node *) expr;
- break;
- }
- case NOT:
- {
- Expr *expr = makeNode(Expr);
- Node *rexpr = A_Expr_to_Expr(((A_Expr *) ptr)->rexpr, intersect_present);
-
- expr->typeOid = BOOLOID;
- expr->opType = NOT_EXPR;
- expr->args = makeList1(rexpr);
- result = (Node *) expr;
- break;
- }
- }
- break;
- }
- default:
- result = ptr;
- }
- return result;
-}
-
void
CheckSelectForUpdate(Query *qry)
{
- if (qry->unionClause || qry->intersectClause)
- elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT clause");
+ if (qry->setOperations)
+ elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT");
if (qry->distinctClause != NIL)
elog(ERROR, "SELECT FOR UPDATE is not allowed with DISTINCT clause");
if (qry->groupClause != NIL)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31aea152fa6..f13b942abd8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.193 2000/09/29 18:21:36 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.194 2000/10/05 19:11:33 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -78,6 +78,7 @@ static Node *makeA_Expr(int oper, char *opname, Node *lexpr, Node *rexpr);
static Node *makeTypeCast(Node *arg, TypeName *typename);
static Node *makeRowExpr(char *opr, List *largs, List *rargs);
static void mapTargetColumns(List *source, List *target);
+static SelectStmt *findLeftmostSelect(Node *node);
static bool exprIsNullConstant(Node *arg);
static Node *doNegate(Node *n);
static void doNegateFloat(Value *v);
@@ -112,25 +113,26 @@ static void doNegateFloat(Value *v);
VersionStmt *vstmt;
DefineStmt *dstmt;
RuleStmt *rstmt;
- InsertStmt *astmt;
+ InsertStmt *istmt;
}
%type <node> stmt,
- AlterSchemaStmt, AlterTableStmt, ClosePortalStmt,
- CopyStmt, CreateStmt, CreateAsStmt, CreateSchemaStmt, CreateSeqStmt, DefineStmt, DropStmt,
- TruncateStmt, CommentStmt,
- ExtendStmt, FetchStmt, GrantStmt, CreateTrigStmt, DropSchemaStmt, DropTrigStmt,
- CreatePLangStmt, DropPLangStmt,
- IndexStmt, ListenStmt, UnlistenStmt, LockStmt, OptimizableStmt,
- ProcedureStmt, ReindexStmt, RemoveAggrStmt, RemoveOperStmt,
- RemoveFuncStmt, RemoveStmt,
- RenameStmt, RevokeStmt, RuleStmt, SetSessionStmt, TransactionStmt, ViewStmt, LoadStmt,
- CreatedbStmt, DropdbStmt, VacuumStmt, CursorStmt, SubSelect,
- UpdateStmt, InsertStmt, select_clause, SelectStmt, NotifyStmt, DeleteStmt,
- ClusterStmt, ExplainStmt, VariableSetStmt, VariableShowStmt, VariableResetStmt,
- CreateUserStmt, AlterUserStmt, DropUserStmt, RuleActionStmt,
- RuleActionStmtOrEmpty, ConstraintsSetStmt,
- CreateGroupStmt, AlterGroupStmt, DropGroupStmt
+ AlterGroupStmt, AlterSchemaStmt, AlterTableStmt, AlterUserStmt,
+ ClosePortalStmt, ClusterStmt, CommentStmt, ConstraintsSetStmt,
+ CopyStmt, CreateAsStmt, CreateGroupStmt, CreatePLangStmt,
+ CreateSchemaStmt, CreateSeqStmt, CreateStmt, CreateTrigStmt,
+ CreateUserStmt, CreatedbStmt, CursorStmt, DefineStmt, DeleteStmt,
+ DropGroupStmt, DropPLangStmt, DropSchemaStmt, DropStmt, DropTrigStmt,
+ DropUserStmt, DropdbStmt, ExplainStmt, ExtendStmt, FetchStmt,
+ GrantStmt, IndexStmt, InsertStmt, ListenStmt, LoadStmt, LockStmt,
+ NotifyStmt, OptimizableStmt, ProcedureStmt, ReindexStmt,
+ RemoveAggrStmt, RemoveFuncStmt, RemoveOperStmt, RemoveStmt,
+ RenameStmt, RevokeStmt, RuleActionStmt, RuleActionStmtOrEmpty,
+ RuleStmt, SelectStmt, SetSessionStmt, TransactionStmt, TruncateStmt,
+ UnlistenStmt, UpdateStmt, VacuumStmt, VariableResetStmt,
+ VariableSetStmt, VariableShowStmt, ViewStmt
+
+%type <node> select_clause, select_subclause
%type <list> SessionList
%type <node> SessionClause
@@ -212,7 +214,7 @@ static void doNegateFloat(Value *v);
%type <list> OptSeqList
%type <defelt> OptSeqElem
-%type <astmt> insert_rest
+%type <istmt> insert_rest
%type <node> OptTableElement, ConstraintElem
%type <node> columnDef
@@ -1533,16 +1535,16 @@ OptInherit: INHERITS '(' relation_name_list ')' { $$ = $3; }
CreateAsStmt: CREATE OptTemp TABLE relation_name OptUnder OptCreateAs AS SelectStmt
{
- SelectStmt *n = (SelectStmt *)$8;
- if ($5 != NIL)
- yyerror("CREATE TABLE/AS SELECT does not support UNDER");
- if ($6 != NIL)
- mapTargetColumns($6, n->targetList);
+ SelectStmt *n = findLeftmostSelect($8);
if (n->into != NULL)
elog(ERROR,"CREATE TABLE/AS SELECT may not specify INTO");
n->istemp = $2;
n->into = $4;
- $$ = (Node *)n;
+ if ($5 != NIL)
+ yyerror("CREATE TABLE/AS SELECT does not support UNDER");
+ if ($6 != NIL)
+ mapTargetColumns($6, n->targetList);
+ $$ = $8;
}
;
@@ -2909,11 +2911,7 @@ ViewStmt: CREATE VIEW name opt_column_list AS SelectStmt
ViewStmt *n = makeNode(ViewStmt);
n->viewname = $3;
n->aliases = $4;
- n->query = (Query *)$6;
- if (((SelectStmt *)n->query)->unionClause != NULL)
- elog(ERROR,"UNION in views is not implemented");
- if (((SelectStmt *)n->query)->forUpdate != NULL)
- elog(ERROR, "SELECT FOR UPDATE is not allowed in CREATE VIEW");
+ n->query = (Query *) $6;
$$ = (Node *)n;
}
;
@@ -3156,78 +3154,37 @@ InsertStmt: INSERT INTO relation_name insert_rest
insert_rest: VALUES '(' target_list ')'
{
$$ = makeNode(InsertStmt);
- $$->cols = NULL;
- $$->distinctClause = NIL;
+ $$->cols = NIL;
$$->targetList = $3;
- $$->fromClause = NIL;
- $$->whereClause = NULL;
- $$->groupClause = NIL;
- $$->havingClause = NULL;
- $$->unionClause = NIL;
+ $$->selectStmt = NULL;
}
| DEFAULT VALUES
{
$$ = makeNode(InsertStmt);
- $$->distinctClause = NIL;
+ $$->cols = NIL;
$$->targetList = NIL;
- $$->fromClause = NIL;
- $$->whereClause = NULL;
- $$->groupClause = NIL;
- $$->havingClause = NULL;
- $$->unionClause = NIL;
- $$->intersectClause = NIL;
- }
-/* We want the full power of SelectStatements including INTERSECT and EXCEPT
- * for insertion. However, we can't support sort or limit clauses.
- */
+ $$->selectStmt = NULL;
+ }
| SelectStmt
{
- SelectStmt *n = (SelectStmt *) $1;
- if (n->sortClause)
- elog(ERROR, "ORDER BY is not allowed in INSERT/SELECT");
$$ = makeNode(InsertStmt);
$$->cols = NIL;
- $$->distinctClause = n->distinctClause;
- $$->targetList = n->targetList;
- $$->fromClause = n->fromClause;
- $$->whereClause = n->whereClause;
- $$->groupClause = n->groupClause;
- $$->havingClause = n->havingClause;
- $$->unionClause = n->unionClause;
- $$->intersectClause = n->intersectClause;
- $$->unionall = n->unionall;
- $$->forUpdate = n->forUpdate;
+ $$->targetList = NIL;
+ $$->selectStmt = $1;
}
| '(' columnList ')' VALUES '(' target_list ')'
{
$$ = makeNode(InsertStmt);
$$->cols = $2;
- $$->distinctClause = NIL;
$$->targetList = $6;
- $$->fromClause = NIL;
- $$->whereClause = NULL;
- $$->groupClause = NIL;
- $$->havingClause = NULL;
- $$->unionClause = NIL;
- $$->intersectClause = NIL;
+ $$->selectStmt = NULL;
}
| '(' columnList ')' SelectStmt
{
- SelectStmt *n = (SelectStmt *) $4;
- if (n->sortClause)
- elog(ERROR, "ORDER BY is not allowed in INSERT/SELECT");
$$ = makeNode(InsertStmt);
$$->cols = $2;
- $$->distinctClause = n->distinctClause;
- $$->targetList = n->targetList;
- $$->fromClause = n->fromClause;
- $$->whereClause = n->whereClause;
- $$->groupClause = n->groupClause;
- $$->havingClause = n->havingClause;
- $$->unionClause = n->unionClause;
- $$->intersectClause = n->intersectClause;
- $$->unionall = n->unionall;
- $$->forUpdate = n->forUpdate;
+ $$->targetList = NIL;
+ $$->selectStmt = $4;
}
;
@@ -3324,26 +3281,10 @@ UpdateStmt: UPDATE opt_only relation_name
*****************************************************************************/
CursorStmt: DECLARE name opt_cursor CURSOR FOR SelectStmt
{
- SelectStmt *n;
-
- n= (SelectStmt *)$6;
- /* from PORTAL name */
- /*
- * 15 august 1991 -- since 3.0 postgres does locking
- * right, we discovered that portals were violating
- * locking protocol. portal locks cannot span xacts.
- * as a short-term fix, we installed the check here.
- * -- mao
- */
- if (!IsTransactionBlock())
- elog(ERROR,"Named portals may only be used in begin/end transaction blocks");
-
+ SelectStmt *n = findLeftmostSelect($6);
n->portalname = $2;
n->binary = $3;
- if (n->forUpdate != NULL)
- elog(ERROR,"DECLARE/UPDATE is not supported"
- "\n\tCursors must be READ ONLY");
- $$ = (Node *)n;
+ $$ = $6;
}
;
@@ -3364,92 +3305,22 @@ opt_cursor: BINARY { $$ = TRUE; }
/* A complete SELECT statement looks like this. Note sort, for_update,
* and limit clauses can only appear once, not in each set operation.
*
- * The rule returns a SelectStmt Node having the set operations attached to
- * unionClause and intersectClause (NIL if no set operations were present)
+ * The rule returns either a SelectStmt node or a SetOperationStmt tree.
+ * One-time clauses are attached to the leftmost SelectStmt leaf.
+ *
+ * NOTE: only the leftmost SelectStmt leaf should have INTO, either.
+ * However, this is not checked by the grammar; parse analysis must check it.
*/
SelectStmt: select_clause sort_clause for_update_clause opt_select_limit
{
- if IsA($1, SelectStmt)
- {
- /* There were no set operations, so just attach the
- * one-time clauses.
- */
- SelectStmt *n = (SelectStmt *) $1;
- n->sortClause = $2;
- n->forUpdate = $3;
- n->limitOffset = nth(0, $4);
- n->limitCount = nth(1, $4);
- $$ = (Node *) n;
- }
- else
- {
- /* There were set operations. The root of the operator
- * tree is delivered by $1, but we must hand back a
- * SelectStmt node not an A_Expr Node.
- * So we find the leftmost 'SelectStmt' in the operator
- * tree $1 (which is the first Select Statement in the
- * query), which will be the returned node.
- * Then we attach the whole operator tree to that node's
- * 'intersectClause', and a list of all 'SelectStmt' Nodes
- * in the tree to its 'unionClause'. (NOTE that this means
- * the top node has indirect recursive pointers to itself!
- * This would cause trouble if we tried copyObject!!)
- * The intersectClause and unionClause subtrees will be
- * left untouched by the main parser, and will only be
- * processed when control gets to the function
- * Except_Intersect_Rewrite() (in rewriteHandler.c).
- */
- Node *op = (Node *) $1;
- List *select_list = NIL;
- SelectStmt *first_select;
- bool intersect_present = FALSE,
- unionall_present = FALSE;
-
- /* Take the operator tree as an argument and create a
- * list of all SelectStmt Nodes found in the tree.
- *
- * If one of the SelectStmt Nodes has the 'unionall' flag
- * set to true the 'unionall_present' flag is also set to
- * true.
- */
- create_select_list(op, &select_list, &unionall_present);
-
- /* Replace all the A_Expr Nodes in the operator tree by
- * Expr Nodes.
- *
- * If an INTERSECT or an EXCEPT is present, the
- * 'intersect_present' flag is set to true
- */
- op = A_Expr_to_Expr(op, &intersect_present);
+ SelectStmt *n = findLeftmostSelect($1);
- /* If both flags are set to true we have a UNION ALL
- * statement mixed up with INTERSECT or EXCEPT
- * which can not be handled at the moment.
- */
- if (intersect_present && unionall_present)
- elog(ERROR, "UNION ALL not allowed in mixed set operations");
-
- /* Get the leftmost SeletStmt Node (which automatically
- * represents the first Select Statement of the query!)
- */
- first_select = (SelectStmt *) lfirst(select_list);
-
- /* Attach the list of all SeletStmt Nodes to unionClause */
- first_select->unionClause = select_list;
-
- /* Attach the whole operator tree to intersectClause */
- first_select->intersectClause = (List *) op;
-
- /* finally attach the sort clause &etc */
- first_select->sortClause = $2;
- first_select->forUpdate = $3;
- first_select->limitOffset = nth(0, $4);
- first_select->limitCount = nth(1, $4);
- $$ = (Node *) first_select;
- }
- if (((SelectStmt *)$$)->forUpdate != NULL && QueryIsRule)
- elog(ERROR, "SELECT/FOR UPDATE is not allowed in CREATE RULE");
+ n->sortClause = $2;
+ n->forUpdate = $3;
+ n->limitOffset = nth(0, $4);
+ n->limitCount = nth(1, $4);
+ $$ = $1;
}
;
@@ -3458,82 +3329,69 @@ SelectStmt: select_clause sort_clause for_update_clause opt_select_limit
* the ordering of the set operations. Without '(' and ')' we want the
* operations to be ordered per the precedence specs at the head of this file.
*
+ * Since parentheses around SELECTs also appear in the expression grammar,
+ * there is a parse ambiguity if parentheses are allowed at the top level of a
+ * select_clause: are the parens part of the expression or part of the select?
+ * We separate select_clause into two levels to resolve this: select_clause
+ * can have top-level parentheses, select_subclause cannot.
+ *
* Note that sort clauses cannot be included at this level --- a sort clause
* can only appear at the end of the complete Select, and it will be handled
* by the topmost SelectStmt rule. Likewise FOR UPDATE and LIMIT.
- *
- * The rule builds up an operator tree using A_Expr Nodes. AND Nodes represent
- * INTERSECTs, OR Nodes represent UNIONs, and AND NOT nodes represent EXCEPTs.
- * The SelectStatements to be connected are the left and right arguments to
- * the A_Expr Nodes.
- * If no set operations appear in the query, the tree consists only of one
- * SelectStmt Node.
*/
-select_clause: '(' select_clause ')'
+select_clause: '(' select_subclause ')'
{
$$ = $2;
}
- | SubSelect
+ | select_subclause
{
$$ = $1;
}
- | select_clause EXCEPT opt_all select_clause
- {
- $$ = (Node *)makeA_Expr(AND,NULL,$1,
- makeA_Expr(NOT,NULL,NULL,$4));
- if ($3)
- elog(ERROR, "EXCEPT ALL is not implemented yet");
- }
- | select_clause UNION opt_all select_clause
- {
- if (IsA($4, SelectStmt))
- {
- SelectStmt *n = (SelectStmt *)$4;
- n->unionall = $3;
- /* NOTE: if UNION ALL appears with a parenthesized set
- * operation to its right, the ALL is silently discarded.
- * Should we generate an error instead? I think it may
- * be OK since ALL with UNION to its right is ignored
- * anyway...
- */
- }
- $$ = (Node *)makeA_Expr(OR,NULL,$1,$4);
- }
- | select_clause INTERSECT opt_all select_clause
- {
- $$ = (Node *)makeA_Expr(AND,NULL,$1,$4);
- if ($3)
- elog(ERROR, "INTERSECT ALL is not implemented yet");
- }
- ;
+ ;
-SubSelect: SELECT opt_distinct target_list
+select_subclause: SELECT opt_distinct target_list
result from_clause where_clause
group_clause having_clause
{
SelectStmt *n = makeNode(SelectStmt);
n->distinctClause = $2;
- n->unionall = FALSE;
n->targetList = $3;
- /* This is new: Subselects support the INTO clause
- * which allows queries that are not part of the
- * SQL92 standard and should not be formulated!
- * We need it for INTERSECT and EXCEPT and I did not
- * want to create a new rule 'SubSelect1' including the
- * feature. If it makes troubles we will have to add
- * a new rule and change this to prevent INTOs in
- * Subselects again.
- */
n->istemp = (bool) ((Value *) lfirst($4))->val.ival;
n->into = (char *) lnext($4);
-
n->fromClause = $5;
n->whereClause = $6;
n->groupClause = $7;
n->havingClause = $8;
$$ = (Node *)n;
}
- ;
+ | select_clause UNION opt_all select_clause
+ {
+ SetOperationStmt *n = makeNode(SetOperationStmt);
+ n->op = SETOP_UNION;
+ n->all = $3;
+ n->larg = $1;
+ n->rarg = $4;
+ $$ = (Node *) n;
+ }
+ | select_clause INTERSECT opt_all select_clause
+ {
+ SetOperationStmt *n = makeNode(SetOperationStmt);
+ n->op = SETOP_INTERSECT;
+ n->all = $3;
+ n->larg = $1;
+ n->rarg = $4;
+ $$ = (Node *) n;
+ }
+ | select_clause EXCEPT opt_all select_clause
+ {
+ SetOperationStmt *n = makeNode(SetOperationStmt);
+ n->op = SETOP_EXCEPT;
+ n->all = $3;
+ n->larg = $1;
+ n->rarg = $4;
+ $$ = (Node *) n;
+ }
+ ;
/* easy way to return two values. Can someone improve this? bjm */
result: INTO OptTempTableName { $$ = $2; }
@@ -3763,7 +3621,7 @@ table_ref: relation_expr
$1->name = $2;
$$ = (Node *) $1;
}
- | '(' select_clause ')' alias_clause
+ | '(' select_subclause ')' alias_clause
{
RangeSubselect *n = makeNode(RangeSubselect);
n->subquery = $2;
@@ -4316,7 +4174,7 @@ opt_interval: datetime { $$ = makeList1($1); }
* Define row_descriptor to allow yacc to break the reduce/reduce conflict
* with singleton expressions.
*/
-row_expr: '(' row_descriptor ')' IN '(' SubSelect ')'
+row_expr: '(' row_descriptor ')' IN '(' select_subclause ')'
{
SubLink *n = makeNode(SubLink);
n->lefthand = $2;
@@ -4326,7 +4184,7 @@ row_expr: '(' row_descriptor ')' IN '(' SubSelect ')'
n->subselect = $6;
$$ = (Node *)n;
}
- | '(' row_descriptor ')' NOT IN '(' SubSelect ')'
+ | '(' row_descriptor ')' NOT IN '(' select_subclause ')'
{
SubLink *n = makeNode(SubLink);
n->lefthand = $2;
@@ -4336,7 +4194,7 @@ row_expr: '(' row_descriptor ')' IN '(' SubSelect ')'
n->subselect = $7;
$$ = (Node *)n;
}
- | '(' row_descriptor ')' all_Op sub_type '(' SubSelect ')'
+ | '(' row_descriptor ')' all_Op sub_type '(' select_subclause ')'
{
SubLink *n = makeNode(SubLink);
n->lefthand = $2;
@@ -4349,7 +4207,7 @@ row_expr: '(' row_descriptor ')' IN '(' SubSelect ')'
n->subselect = $7;
$$ = (Node *)n;
}
- | '(' row_descriptor ')' all_Op '(' SubSelect ')'
+ | '(' row_descriptor ')' all_Op '(' select_subclause ')'
{
SubLink *n = makeNode(SubLink);
n->lefthand = $2;
@@ -4680,7 +4538,7 @@ a_expr: c_expr
$$ = n;
}
}
- | a_expr all_Op sub_type '(' SubSelect ')'
+ | a_expr all_Op sub_type '(' select_subclause ')'
{
SubLink *n = makeNode(SubLink);
n->lefthand = makeList1($1);
@@ -5076,7 +4934,7 @@ c_expr: attr
n->agg_distinct = FALSE;
$$ = (Node *)n;
}
- | '(' SubSelect ')'
+ | '(' select_subclause ')'
{
SubLink *n = makeNode(SubLink);
n->lefthand = NIL;
@@ -5086,7 +4944,7 @@ c_expr: attr
n->subselect = $2;
$$ = (Node *)n;
}
- | EXISTS '(' SubSelect ')'
+ | EXISTS '(' select_subclause ')'
{
SubLink *n = makeNode(SubLink);
n->lefthand = NIL;
@@ -5185,7 +5043,7 @@ trim_list: a_expr FROM expr_list
{ $$ = $1; }
;
-in_expr: SubSelect
+in_expr: select_subclause
{
SubLink *n = makeNode(SubLink);
n->subselect = $1;
@@ -5912,6 +5770,19 @@ mapTargetColumns(List *src, List *dst)
} /* mapTargetColumns() */
+/* findLeftmostSelect()
+ * Find the leftmost SelectStmt in a SetOperationStmt parsetree.
+ */
+static SelectStmt *
+findLeftmostSelect(Node *node)
+{
+ while (node && IsA(node, SetOperationStmt))
+ node = ((SetOperationStmt *) node)->larg;
+ Assert(node && IsA(node, SelectStmt));
+ return (SelectStmt *) node;
+}
+
+
/* xlateSqlFunc()
* Convert alternate function names to internal Postgres functions.
*
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index cc849ebf07b..20233ed1950 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.68 2000/09/29 18:21:36 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.69 2000/10/05 19:11:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -347,7 +347,8 @@ transformTableEntry(ParseState *pstate, RangeVar *r)
static RangeTblRef *
transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
{
- SelectStmt *subquery = (SelectStmt *) r->subquery;
+ List *save_rtable;
+ List *save_joinlist;
List *parsetrees;
Query *query;
RangeTblEntry *rte;
@@ -362,19 +363,21 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
elog(ERROR, "sub-select in FROM must have an alias");
/*
- * subquery node might not be SelectStmt if user wrote something like
- * FROM (SELECT ... UNION SELECT ...). Our current implementation of
- * UNION/INTERSECT/EXCEPT is too messy to deal with here, so punt until
- * we redesign querytrees to make it more reasonable.
+ * Analyze and transform the subquery. This is a bit tricky because
+ * we don't want the subquery to be able to see any FROM items already
+ * created in the current query (per SQL92, the scope of a FROM item
+ * does not include other FROM items). But it does need to be able to
+ * see any further-up parent states, so we can't just pass a null parent
+ * pstate link. So, temporarily make the current query level have an
+ * empty rtable and joinlist.
*/
- if (subquery == NULL || !IsA(subquery, SelectStmt))
- elog(ERROR, "Set operations not yet supported in subselects in FROM");
-
- /*
- * Analyze and transform the subquery as if it were an independent
- * statement (we do NOT want it to see the outer query as a parent).
- */
- parsetrees = parse_analyze(makeList1(subquery), NULL);
+ save_rtable = pstate->p_rtable;
+ save_joinlist = pstate->p_joinlist;
+ pstate->p_rtable = NIL;
+ pstate->p_joinlist = NIL;
+ parsetrees = parse_analyze(makeList1(r->subquery), pstate);
+ pstate->p_rtable = save_rtable;
+ pstate->p_joinlist = save_joinlist;
/*
* Check that we got something reasonable. Some of these conditions
@@ -1181,108 +1184,3 @@ exprIsInSortList(Node *expr, List *sortList, List *targetList)
}
return false;
}
-
-/* transformUnionClause()
- * Transform a UNION clause.
- * Note that the union clause is actually a fully-formed select structure.
- * So, it is evaluated as a select, then the resulting target fields
- * are matched up to ensure correct types in the results.
- * The select clause parsing is done recursively, so the unions are evaluated
- * right-to-left. One might want to look at all columns from all clauses before
- * trying to coerce, but unless we keep track of the call depth we won't know
- * when to do this because of the recursion.
- * Let's just try matching in pairs for now (right to left) and see if it works.
- * - thomas 1998-05-22
- */
-#ifdef NOT_USED
-static List *
-transformUnionClause(List *unionClause, List *targetlist)
-{
- List *union_list = NIL;
- List *qlist,
- *qlist_item;
-
- if (unionClause)
- {
- /* recursion */
- qlist = parse_analyze(unionClause, NULL);
-
- foreach(qlist_item, qlist)
- {
- Query *query = (Query *) lfirst(qlist_item);
- List *prev_target = targetlist;
- List *next_target;
- int prev_len = 0,
- next_len = 0;
-
- foreach(prev_target, targetlist)
- if (!((TargetEntry *) lfirst(prev_target))->resdom->resjunk)
- prev_len++;
-
- foreach(next_target, query->targetList)
- if (!((TargetEntry *) lfirst(next_target))->resdom->resjunk)
- next_len++;
-
- if (prev_len != next_len)
- elog(ERROR, "Each UNION clause must have the same number of columns");
-
- foreach(next_target, query->targetList)
- {
- Oid itype;
- Oid otype;
-
- otype = ((TargetEntry *) lfirst(prev_target))->resdom->restype;
- itype = ((TargetEntry *) lfirst(next_target))->resdom->restype;
-
- /* one or both is a NULL column? then don't convert... */
- if (otype == InvalidOid)
- {
- /* propagate a known type forward, if available */
- if (itype != InvalidOid)
- ((TargetEntry *) lfirst(prev_target))->resdom->restype = itype;
-#if FALSE
- else
- {
- ((TargetEntry *) lfirst(prev_target))->resdom->restype = UNKNOWNOID;
- ((TargetEntry *) lfirst(next_target))->resdom->restype = UNKNOWNOID;
- }
-#endif
- }
- else if (itype == InvalidOid)
- {
- }
- /* they don't match in type? then convert... */
- else if (itype != otype)
- {
- Node *expr;
-
- expr = ((TargetEntry *) lfirst(next_target))->expr;
- expr = CoerceTargetExpr(NULL, expr, itype, otype, -1);
- if (expr == NULL)
- {
- elog(ERROR, "Unable to transform %s to %s"
- "\n\tEach UNION clause must have compatible target types",
- typeidTypeName(itype),
- typeidTypeName(otype));
- }
- ((TargetEntry *) lfirst(next_target))->expr = expr;
- ((TargetEntry *) lfirst(next_target))->resdom->restype = otype;
- }
-
- /* both are UNKNOWN? then evaluate as text... */
- else if (itype == UNKNOWNOID)
- {
- ((TargetEntry *) lfirst(next_target))->resdom->restype = TEXTOID;
- ((TargetEntry *) lfirst(prev_target))->resdom->restype = TEXTOID;
- }
- prev_target = lnext(prev_target);
- }
- union_list = lappend(union_list, query);
- }
- return union_list;
- }
- else
- return NIL;
-}
-
-#endif
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index bd098fb6c68..ef13d67cf1c 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.46 2000/07/30 22:13:50 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.47 2000/10/05 19:11:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -314,6 +314,100 @@ coerce_type_typmod(ParseState *pstate, Node *node,
}
+/* select_common_type()
+ * Determine the common supertype of a list of input expression types.
+ * This is used for determining the output type of CASE and UNION
+ * constructs.
+ *
+ * typeids is a nonempty integer list of type OIDs. Note that earlier items
+ * in the list will be preferred if there is doubt.
+ * 'context' is a phrase to use in the error message if we fail to select
+ * a usable type.
+ *
+ * XXX this code is WRONG, since (for example) given the input (int4,int8)
+ * it will select int4, whereas according to SQL92 clause 9.3 the correct
+ * answer is clearly int8. To fix this we need a notion of a promotion
+ * hierarchy within type categories --- something more complete than
+ * just a single preferred type.
+ */
+Oid
+select_common_type(List *typeids, const char *context)
+{
+ Oid ptype;
+ CATEGORY pcategory;
+ List *l;
+
+ Assert(typeids != NIL);
+ ptype = (Oid) lfirsti(typeids);
+ pcategory = TypeCategory(ptype);
+ foreach(l, lnext(typeids))
+ {
+ Oid ntype = (Oid) lfirsti(l);
+
+ /* move on to next one if no new information... */
+ if (ntype && (ntype != UNKNOWNOID) && (ntype != ptype))
+ {
+ if (!ptype || ptype == UNKNOWNOID)
+ {
+ /* so far, only nulls so take anything... */
+ ptype = ntype;
+ pcategory = TypeCategory(ptype);
+ }
+ else if (TypeCategory(ntype) != pcategory)
+ {
+ /*
+ * both types in different categories? then
+ * not much hope...
+ */
+ elog(ERROR, "%s types \"%s\" and \"%s\" not matched",
+ context, typeidTypeName(ptype), typeidTypeName(ntype));
+ }
+ else if (IsPreferredType(pcategory, ntype)
+ && can_coerce_type(1, &ptype, &ntype))
+ {
+ /*
+ * new one is preferred and can convert? then
+ * take it...
+ */
+ ptype = ntype;
+ pcategory = TypeCategory(ptype);
+ }
+ }
+ }
+ return ptype;
+}
+
+/* coerce_to_common_type()
+ * Coerce an expression to the given type.
+ *
+ * This is used following select_common_type() to coerce the individual
+ * expressions to the desired type. 'context' is a phrase to use in the
+ * error message if we fail to coerce.
+ *
+ * NOTE: pstate may be NULL.
+ */
+Node *
+coerce_to_common_type(ParseState *pstate, Node *node,
+ Oid targetTypeId,
+ const char *context)
+{
+ Oid inputTypeId = exprType(node);
+
+ if (inputTypeId == targetTypeId)
+ return node; /* no work */
+ if (can_coerce_type(1, &inputTypeId, &targetTypeId))
+ {
+ node = coerce_type(pstate, node, inputTypeId, targetTypeId, -1);
+ }
+ else
+ {
+ elog(ERROR, "%s unable to convert to type \"%s\"",
+ context, typeidTypeName(targetTypeId));
+ }
+ return node;
+}
+
+
/* TypeCategory()
* Assign a category to the specified OID.
*/
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 591fdab8782..7b647124d1f 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.84 2000/09/29 18:21:36 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.85 2000/10/05 19:11:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -412,9 +412,9 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
{
CaseExpr *c = (CaseExpr *) expr;
CaseWhen *w;
+ List *typeids = NIL;
List *args;
Oid ptype;
- CATEGORY pcategory;
/* transform the list of arguments */
foreach(args, c->args)
@@ -432,6 +432,7 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
w->expr = (Node *) a;
}
lfirst(args) = transformExpr(pstate, (Node *) w, precedence);
+ typeids = lappendi(typeids, exprType(w->result));
}
/*
@@ -452,104 +453,26 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
c->defresult = (Node *) n;
}
c->defresult = transformExpr(pstate, c->defresult, precedence);
+ /*
+ * Note: default result is considered the most significant
+ * type in determining preferred type. This is how the code
+ * worked before, but it seems a little bogus to me --- tgl
+ */
+ typeids = lconsi(exprType(c->defresult), typeids);
- /* now check types across result clauses... */
- c->casetype = exprType(c->defresult);
- ptype = c->casetype;
- pcategory = TypeCategory(ptype);
- foreach(args, c->args)
- {
- Oid wtype;
-
- w = lfirst(args);
- wtype = exprType(w->result);
- /* move on to next one if no new information... */
- if (wtype && (wtype != UNKNOWNOID)
- && (wtype != ptype))
- {
- if (!ptype || ptype == UNKNOWNOID)
- {
- /* so far, only nulls so take anything... */
- ptype = wtype;
- pcategory = TypeCategory(ptype);
- }
- else if ((TypeCategory(wtype) != pcategory)
- || ((TypeCategory(wtype) == USER_TYPE)
- && (TypeCategory(c->casetype) == USER_TYPE)))
- {
-
- /*
- * both types in different categories? then
- * not much hope...
- */
- elog(ERROR, "CASE/WHEN types '%s' and '%s' not matched",
- typeidTypeName(c->casetype), typeidTypeName(wtype));
- }
- else if (IsPreferredType(pcategory, wtype)
- && can_coerce_type(1, &ptype, &wtype))
- {
-
- /*
- * new one is preferred and can convert? then
- * take it...
- */
- ptype = wtype;
- pcategory = TypeCategory(ptype);
- }
- }
- }
+ ptype = select_common_type(typeids, "CASE");
+ c->casetype = ptype;
/* Convert default result clause, if necessary */
- if (c->casetype != ptype)
- {
- if (!c->casetype || c->casetype == UNKNOWNOID)
- {
+ c->defresult = coerce_to_common_type(pstate, c->defresult,
+ ptype, "CASE/ELSE");
- /*
- * default clause is NULL, so assign preferred
- * type from WHEN clauses...
- */
- c->casetype = ptype;
- }
- else if (can_coerce_type(1, &c->casetype, &ptype))
- {
- c->defresult = coerce_type(pstate, c->defresult,
- c->casetype, ptype, -1);
- c->casetype = ptype;
- }
- else
- {
- elog(ERROR, "CASE/ELSE unable to convert to type '%s'",
- typeidTypeName(ptype));
- }
- }
-
- /* Convert when clauses, if not null and if necessary */
+ /* Convert when-clause results, if necessary */
foreach(args, c->args)
{
- Oid wtype;
-
w = lfirst(args);
- wtype = exprType(w->result);
-
- /*
- * only bother with conversion if not NULL and
- * different type...
- */
- if (wtype && (wtype != UNKNOWNOID)
- && (wtype != ptype))
- {
- if (can_coerce_type(1, &wtype, &ptype))
- {
- w->result = coerce_type(pstate, w->result, wtype,
- ptype, -1);
- }
- else
- {
- elog(ERROR, "CASE/WHEN unable to convert to type '%s'",
- typeidTypeName(ptype));
- }
- }
+ w->result = coerce_to_common_type(pstate, w->result,
+ ptype, "CASE/WHEN");
}
result = expr;
@@ -560,7 +483,7 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
{
CaseWhen *w = (CaseWhen *) expr;
- w->expr = transformExpr(pstate, (Node *) w->expr, precedence);
+ w->expr = transformExpr(pstate, w->expr, precedence);
if (exprType(w->expr) != BOOLOID)
elog(ERROR, "WHEN clause must have a boolean result");
@@ -575,7 +498,7 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
n->val.type = T_Null;
w->result = (Node *) n;
}
- w->result = transformExpr(pstate, (Node *) w->result, precedence);
+ w->result = transformExpr(pstate, w->result, precedence);
result = expr;
break;
}