diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2000-10-05 19:11:39 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2000-10-05 19:11:39 +0000 |
commit | 05e3d0ee8666b74f11ffad16f46e372459d6e53e (patch) | |
tree | b273892bfda60f6bad315e84aaa2e9826e226931 /src/backend/parser | |
parent | 5292637f52c6db8a22f99177f228273cb69fc510 (diff) | |
download | postgresql-05e3d0ee8666b74f11ffad16f46e372459d6e53e.tar.gz postgresql-05e3d0ee8666b74f11ffad16f46e372459d6e53e.zip |
Reimplementation of UNION/INTERSECT/EXCEPT. INTERSECT/EXCEPT now meet the
SQL92 semantics, including support for ALL option. All three can be used
in subqueries and views. DISTINCT and ORDER BY work now in views, too.
This rewrite fixes many problems with cross-datatype UNIONs and INSERT/SELECT
where the SELECT yields different datatypes than the INSERT needs. I did
that by making UNION subqueries and SELECT in INSERT be treated like
subselects-in-FROM, thereby allowing an extra level of targetlist where the
datatype conversions can be inserted safely.
INITDB NEEDED!
Diffstat (limited to 'src/backend/parser')
-rw-r--r-- | src/backend/parser/analyze.c | 697 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 349 | ||||
-rw-r--r-- | src/backend/parser/parse_clause.c | 136 | ||||
-rw-r--r-- | src/backend/parser/parse_coerce.c | 96 | ||||
-rw-r--r-- | src/backend/parser/parse_expr.c | 113 |
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; } |