diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c index c7bc666c2f..9e0cef44ab 100644 --- a/src/backend/executor/nodeLimit.c +++ b/src/backend/executor/nodeLimit.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/nodeLimit.c,v 1.1 2000/10/26 21:35:15 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/nodeLimit.c,v 1.2 2000/11/05 00:15:52 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -188,17 +188,11 @@ recompute_limits(Limit *node) econtext, &isNull, NULL)); - /* Interpret NULL count as no count */ + /* Interpret NULL count as no count (LIMIT ALL) */ if (isNull) limitstate->noCount = true; - else - { - /* Currently, LIMIT 0 is specified as meaning no limit. - * I think this is pretty bogus, but ... - */ - if (limitstate->count <= 0) - limitstate->noCount = true; - } + else if (limitstate->count < 0) + limitstate->count = 0; } else { diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 9435a396f0..406e85ce62 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.128 2000/10/31 10:22:10 petere Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.129 2000/11/05 00:15:52 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1826,6 +1826,7 @@ _copySelectStmt(SelectStmt *from) Node_Copy(from, newnode, distinctClause); if (from->into) newnode->into = pstrdup(from->into); + newnode->istemp = from->istemp; Node_Copy(from, newnode, targetList); Node_Copy(from, newnode, fromClause); Node_Copy(from, newnode, whereClause); @@ -1835,10 +1836,13 @@ _copySelectStmt(SelectStmt *from) if (from->portalname) newnode->portalname = pstrdup(from->portalname); newnode->binary = from->binary; - newnode->istemp = from->istemp; Node_Copy(from, newnode, limitOffset); Node_Copy(from, newnode, limitCount); Node_Copy(from, newnode, forUpdate); + newnode->op = from->op; + newnode->all = from->all; + Node_Copy(from, newnode, larg); + Node_Copy(from, newnode, rarg); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 8519fd6115..08087136a1 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -20,7 +20,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.78 2000/10/31 10:22:10 petere Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.79 2000/11/05 00:15:52 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -686,6 +686,8 @@ _equalSelectStmt(SelectStmt *a, SelectStmt *b) return false; if (!equalstr(a->into, b->into)) return false; + if (a->istemp != b->istemp) + return false; if (!equal(a->targetList, b->targetList)) return false; if (!equal(a->fromClause, b->fromClause)) @@ -702,14 +704,20 @@ _equalSelectStmt(SelectStmt *a, SelectStmt *b) return false; if (a->binary != b->binary) return false; - if (a->istemp != b->istemp) - return false; if (!equal(a->limitOffset, b->limitOffset)) return false; if (!equal(a->limitCount, b->limitCount)) return false; if (!equal(a->forUpdate, b->forUpdate)) return false; + if (a->op != b->op) + return false; + if (a->all != b->all) + return false; + if (!equal(a->larg, b->larg)) + return false; + if (!equal(a->rarg, b->rarg)) + return false; return true; } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index f9c70f7137..5951e8f0c9 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.93 2000/10/26 21:36:09 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.94 2000/11/05 00:15:53 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -809,25 +809,26 @@ union_planner(Query *parse, if (IsA(parse->limitCount, Const)) { Const *limitc = (Const *) parse->limitCount; - int count = (int) (limitc->constvalue); + int32 count = DatumGetInt32(limitc->constvalue); /* - * The constant can legally be either 0 ("ALL") or a - * positive integer. If it is not ALL, we also need - * to consider the OFFSET part of LIMIT. + * A NULL-constant LIMIT represents "LIMIT ALL", + * which we treat the same as no limit (ie, + * expect to retrieve all the tuples). */ - if (count > 0) + if (!limitc->constisnull && count > 0) { tuple_fraction = (double) count; + /* We must also consider the OFFSET, if present */ if (parse->limitOffset != NULL) { if (IsA(parse->limitOffset, Const)) { - int offset; + int32 offset; limitc = (Const *) parse->limitOffset; - offset = (int) (limitc->constvalue); - if (offset > 0) + offset = DatumGetInt32(limitc->constvalue); + if (!limitc->constisnull && offset > 0) tuple_fraction += (double) offset; } else @@ -850,14 +851,14 @@ union_planner(Query *parse, } /* - * Check for a retrieve-into-portal, ie DECLARE CURSOR. + * If no LIMIT, check for retrieve-into-portal, ie DECLARE CURSOR. * * We have no real idea how many tuples the user will ultimately * FETCH from a cursor, but it seems a good bet that he * doesn't want 'em all. Optimize for 10% retrieval (you * gotta better number?) */ - if (parse->isPortal) + else if (parse->isPortal) tuple_fraction = 0.10; } diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index b86dab4bea..45e81aec60 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.162 2000/11/04 18:29:09 momjian Exp $ + * $Id: analyze.c,v 1.163 2000/11/05 00:15:54 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -46,8 +46,8 @@ 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 *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt); +static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt); static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt); static Query *transformCreateStmt(ParseState *pstate, CreateStmt *stmt); static Query *transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt); @@ -257,11 +257,12 @@ transformStmt(ParseState *pstate, Node *parseTree) break; case T_SelectStmt: - result = transformSelectStmt(pstate, (SelectStmt *) parseTree); - break; - - case T_SetOperationStmt: - result = transformSetOperationStmt(pstate, (SetOperationStmt *) parseTree); + if (((SelectStmt *) parseTree)->op == SETOP_NONE) + result = transformSelectStmt(pstate, + (SelectStmt *) parseTree); + else + result = transformSetOperationStmt(pstate, + (SelectStmt *) parseTree); break; default: @@ -1173,7 +1174,7 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt) found=1; } if (!found) - elog(ERROR, "columns in foreign key table of constraint not found."); + elog(ERROR, "columns referenced in foreign key constraint not found."); } /* @@ -1772,29 +1773,30 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) /* * transformSetOperationsStmt - - * transforms a SetOperations Statement + * transforms a set-operations tree * - * SetOperations is actually just a SELECT, but with UNION/INTERSECT/EXCEPT + * A set-operation tree is 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. + * The tree of set operations is converted into the setOperations field of + * the top-level Query. */ static Query * -transformSetOperationStmt(ParseState *pstate, SetOperationStmt *stmt) +transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) { Query *qry = makeNode(Query); - Node *node; SelectStmt *leftmostSelect; Query *leftmostQuery; + SetOperationStmt *sostmt; char *into; + bool istemp; char *portalname; bool binary; - bool istemp; List *sortClause; Node *limitOffset; Node *limitCount; List *forUpdate; + Node *node; List *lefttl, *dtlist; int tllen; @@ -1802,50 +1804,55 @@ transformSetOperationStmt(ParseState *pstate, SetOperationStmt *stmt) qry->commandType = CMD_SELECT; /* - * Find leftmost leaf SelectStmt and extract the one-time-only items - * from it. + * Find leftmost leaf SelectStmt; extract the one-time-only items + * from it and from the top-level node. */ - node = stmt->larg; - while (node && IsA(node, SetOperationStmt)) - node = ((SetOperationStmt *) node)->larg; - Assert(node && IsA(node, SelectStmt)); - leftmostSelect = (SelectStmt *) node; - + leftmostSelect = stmt->larg; + while (leftmostSelect && leftmostSelect->op != SETOP_NONE) + leftmostSelect = leftmostSelect->larg; + Assert(leftmostSelect && IsA(leftmostSelect, SelectStmt) && + leftmostSelect->larg == NULL); into = leftmostSelect->into; - portalname = leftmostSelect->portalname; - binary = leftmostSelect->binary; istemp = leftmostSelect->istemp; - sortClause = leftmostSelect->sortClause; - limitOffset = leftmostSelect->limitOffset; - limitCount = leftmostSelect->limitCount; - forUpdate = leftmostSelect->forUpdate; + portalname = stmt->portalname; + binary = stmt->binary; /* 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; + stmt->portalname = NULL; + stmt->binary = false; - /* We don't actually support forUpdate with set ops at the moment. */ + /* + * These are not one-time, exactly, but we want to process them here + * and not let transformSetOperationTree() see them --- else it'll just + * recurse right back here! + */ + sortClause = stmt->sortClause; + limitOffset = stmt->limitOffset; + limitCount = stmt->limitCount; + forUpdate = stmt->forUpdate; + + stmt->sortClause = NIL; + stmt->limitOffset = NULL; + stmt->limitCount = NULL; + stmt->forUpdate = NIL; + + /* We don't support forUpdate with set ops at the moment. */ if (forUpdate) elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT"); /* * Recursively transform the components of the tree. */ - stmt = (SetOperationStmt *) - transformSetOperationTree(pstate, (Node *) stmt); - Assert(stmt && IsA(stmt, SetOperationStmt)); - qry->setOperations = (Node *) stmt; + sostmt = (SetOperationStmt *) transformSetOperationTree(pstate, stmt); + Assert(sostmt && IsA(sostmt, SetOperationStmt)); + qry->setOperations = (Node *) sostmt; /* * Re-find leftmost SELECT (now it's a sub-query in rangetable) */ - node = stmt->larg; + node = sostmt->larg; while (node && IsA(node, SetOperationStmt)) node = ((SetOperationStmt *) node)->larg; Assert(node && IsA(node, RangeTblRef)); @@ -1858,7 +1865,7 @@ transformSetOperationStmt(ParseState *pstate, SetOperationStmt *stmt) */ qry->targetList = NIL; lefttl = leftmostQuery->targetList; - foreach(dtlist, stmt->colTypes) + foreach(dtlist, sostmt->colTypes) { Oid colType = (Oid) lfirsti(dtlist); char *colName = ((TargetEntry *) lfirst(lefttl))->resdom->resname; @@ -1953,11 +1960,47 @@ transformSetOperationStmt(ParseState *pstate, SetOperationStmt *stmt) * Recursively transform leaves and internal nodes of a set-op tree */ static Node * -transformSetOperationTree(ParseState *pstate, Node *node) +transformSetOperationTree(ParseState *pstate, SelectStmt *stmt) { - if (IsA(node, SelectStmt)) + bool isLeaf; + + Assert(stmt && IsA(stmt, SelectStmt)); + + /* + * Validity-check both leaf and internal SELECTs for disallowed ops. + */ + if (stmt->into) + elog(ERROR, "INTO is only allowed on first SELECT of UNION/INTERSECT/EXCEPT"); + if (stmt->portalname) /* should not happen */ + elog(ERROR, "Portal may not appear in UNION/INTERSECT/EXCEPT"); + /* We don't support forUpdate with set ops at the moment. */ + if (stmt->forUpdate) + elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT"); + + /* + * If an internal node of a set-op tree has ORDER BY, UPDATE, or LIMIT + * clauses attached, we need to treat it like a leaf node to generate + * an independent sub-Query tree. Otherwise, it can be represented by + * a SetOperationStmt node underneath the parent Query. + */ + if (stmt->op == SETOP_NONE) { - SelectStmt *stmt = (SelectStmt *) node; + Assert(stmt->larg == NULL && stmt->rarg == NULL); + isLeaf = true; + } + else + { + Assert(stmt->larg != NULL && stmt->rarg != NULL); + if (stmt->sortClause || stmt->limitOffset || stmt->limitCount || + stmt->forUpdate) + isLeaf = true; + else + isLeaf = false; + } + + if (isLeaf) + { + /* Process leaf SELECT */ List *save_rtable; List *selectList; Query *selectQuery; @@ -1965,20 +2008,6 @@ transformSetOperationTree(ParseState *pstate, Node *node) 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 @@ -2011,21 +2040,26 @@ transformSetOperationTree(ParseState *pstate, Node *node) Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable)); return (Node *) rtr; } - else if (IsA(node, SetOperationStmt)) + else { - SetOperationStmt *op = (SetOperationStmt *) node; + /* Process an internal node (set operation node) */ + SetOperationStmt *op = makeNode(SetOperationStmt); List *lcoltypes; List *rcoltypes; const char *context; - context = (op->op == SETOP_UNION ? "UNION" : - (op->op == SETOP_INTERSECT ? "INTERSECT" : + context = (stmt->op == SETOP_UNION ? "UNION" : + (stmt->op == SETOP_INTERSECT ? "INTERSECT" : "EXCEPT")); + + op->op = stmt->op; + op->all = stmt->all; + /* * Recursively transform the child nodes. */ - op->larg = transformSetOperationTree(pstate, op->larg); - op->rarg = transformSetOperationTree(pstate, op->rarg); + op->larg = transformSetOperationTree(pstate, stmt->larg); + op->rarg = transformSetOperationTree(pstate, stmt->rarg); /* * Verify that the two children have the same number of non-junk * columns, and determine the types of the merged output columns. @@ -2048,14 +2082,9 @@ transformSetOperationTree(ParseState *pstate, Node *node) lcoltypes = lnext(lcoltypes); rcoltypes = lnext(rcoltypes); } + return (Node *) op; } - else - { - elog(ERROR, "transformSetOperationTree: unexpected node %d", - (int) nodeTag(node)); - return NULL; /* keep compiler quiet */ - } } /* diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index f6859429f2..2fbfa853dd 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.203 2000/11/04 21:04:55 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.204 2000/11/05 00:15:54 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -80,7 +80,11 @@ 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 SelectStmt *findLeftmostSelect(SelectStmt *node); +static void insertSelectOptions(SelectStmt *stmt, + List *sortClause, List *forUpdate, + Node *limitOffset, Node *limitCount); +static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg); static bool exprIsNullConstant(Node *arg); static Node *doNegate(Node *n); static void doNegateFloat(Value *v); @@ -134,7 +138,7 @@ static void doNegateFloat(Value *v); UnlistenStmt, UpdateStmt, VacuumStmt, VariableResetStmt, VariableSetStmt, VariableShowStmt, ViewStmt -%type select_clause, select_subclause +%type select_no_parens, select_clause, simple_select %type SessionList %type SessionClause @@ -174,8 +178,8 @@ static void doNegateFloat(Value *v); %type operation, TriggerOneEvent %type stmtblock, stmtmulti, - result, OptTempTableName, relation_name_list, OptTableElementList, - OptUnder, OptInherit, definition, opt_distinct, + into_clause, OptTempTableName, relation_name_list, + OptTableElementList, OptUnder, OptInherit, definition, opt_distinct, opt_with, func_args, func_args_list, func_as, oper_argtypes, RuleActionList, RuleActionMulti, opt_column_list, columnList, opt_va_list, va_list, @@ -183,13 +187,13 @@ static void doNegateFloat(Value *v); from_clause, from_list, opt_array_bounds, expr_list, attrs, target_list, update_target_list, def_list, opt_indirection, group_clause, TriggerFuncArgs, - opt_select_limit + select_limit, opt_select_limit %type func_arg, func_return, aggr_argtype %type opt_arg, TriggerForOpt, TriggerForType, OptTemp -%type for_update_clause, update_list +%type for_update_clause, opt_for_update_clause, update_list %type opt_all %type opt_table %type opt_chain, opt_trans @@ -385,6 +389,7 @@ static void doNegateFloat(Value *v); %right UMINUS %left '.' %left '[' ']' +%left '(' ')' %left TYPECAST %% @@ -444,6 +449,7 @@ stmt : AlterSchemaStmt | ListenStmt | UnlistenStmt | LockStmt + | NotifyStmt | ProcedureStmt | ReindexStmt | RemoveAggrStmt @@ -1527,7 +1533,14 @@ OptInherit: INHERITS '(' relation_name_list ')' { $$ = $3; } CreateAsStmt: CREATE OptTemp TABLE relation_name OptUnder OptCreateAs AS SelectStmt { - SelectStmt *n = findLeftmostSelect($8); + /* + * When the SelectStmt is a set-operation tree, we must + * stuff the INTO information into the leftmost component + * Select, because that's where analyze.c will expect + * to find it. Similarly, the output column names must + * be attached to that Select's target list. + */ + SelectStmt *n = findLeftmostSelect((SelectStmt *) $8); if (n->into != NULL) elog(ERROR,"CREATE TABLE/AS SELECT may not specify INTO"); n->istemp = $2; @@ -1541,7 +1554,7 @@ CreateAsStmt: CREATE OptTemp TABLE relation_name OptUnder OptCreateAs AS Select ; OptCreateAs: '(' CreateAsList ')' { $$ = $2; } - | /*EMPTY*/ { $$ = NULL; } + | /*EMPTY*/ { $$ = NIL; } ; CreateAsList: CreateAsList ',' CreateAsElement { $$ = lappend($1, $3); } @@ -2682,7 +2695,6 @@ RuleStmt: CREATE RULE name AS ; RuleActionList: NOTHING { $$ = NIL; } - | SelectStmt { $$ = makeList1($1); } | RuleActionStmt { $$ = makeList1($1); } | '[' RuleActionMulti ']' { $$ = $2; } | '(' RuleActionMulti ')' { $$ = $2; } @@ -2703,7 +2715,17 @@ RuleActionMulti: RuleActionMulti ';' RuleActionStmtOrEmpty } ; -RuleActionStmt: InsertStmt +/* + * Allowing RuleActionStmt to be a SelectStmt creates an ambiguity: + * is the RuleActionList "((SELECT foo))" a standalone RuleActionStmt, + * or a one-entry RuleActionMulti list? We don't really care, but yacc + * wants to know. We use operator precedence to resolve the ambiguity: + * giving this rule a higher precedence than ')' will force a reduce + * rather than shift decision, causing the one-entry-list interpretation + * to be chosen. + */ +RuleActionStmt: SelectStmt %prec TYPECAST + | InsertStmt | UpdateStmt | DeleteStmt | NotifyStmt @@ -3070,7 +3092,6 @@ OptimizableStmt: SelectStmt | CursorStmt | UpdateStmt | InsertStmt - | NotifyStmt | DeleteStmt /* by default all are $$=$1 */ ; @@ -3225,7 +3246,7 @@ UpdateStmt: UPDATE opt_only relation_name *****************************************************************************/ CursorStmt: DECLARE name opt_cursor CURSOR FOR SelectStmt { - SelectStmt *n = findLeftmostSelect($6); + SelectStmt *n = (SelectStmt *)$6; n->portalname = $2; n->binary = $3; $$ = $6; @@ -3246,55 +3267,99 @@ 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 either a SelectStmt node or a SetOperationStmt tree. - * One-time clauses are attached to the leftmost SelectStmt leaf. +/* A complete SELECT statement looks like this. * - * NOTE: only the leftmost SelectStmt leaf should have INTO, either. - * However, this is not checked by the grammar; parse analysis must check it. + * The rule returns either a single SelectStmt node or a tree of them, + * representing a set-operation tree. + * + * To avoid ambiguity problems with nested parentheses, we have to define + * a "select_no_parens" nonterminal in which there are no parentheses + * at the outermost level. This is used in the production + * c_expr: '(' select_no_parens ')' + * This gives a unique parsing of constructs where a subselect is nested + * in an expression with extra parentheses: the parentheses are not part + * of the subselect but of the outer expression. yacc is not quite bright + * enough to handle the situation completely, however. To prevent a shift/ + * reduce conflict, we also have to attach a precedence to the + * SelectStmt: select_no_parens + * rule that is higher than the precedence of ')'. This means that when + * "((SELECT foo" has been parsed in an expression context, and the + * next token is ')', the parser will follow the '(' SelectStmt ')' reduction + * path rather than '(' select_no_parens ')'. The upshot is that excess + * parens don't work in this context: SELECT ((SELECT foo)) will give a + * parse error, whereas SELECT ((SELECT foo) UNION (SELECT bar)) is OK. + * This is ugly, but it beats not allowing excess parens anywhere... + * + * In all other contexts, we can use SelectStmt which allows outer parens. */ -SelectStmt: select_clause sort_clause for_update_clause opt_select_limit +SelectStmt: select_no_parens %prec TYPECAST { - SelectStmt *n = findLeftmostSelect($1); + $$ = $1; + } + | '(' SelectStmt ')' + { + $$ = $2; + } + ; - n->sortClause = $2; - n->forUpdate = $3; - n->limitOffset = nth(0, $4); - n->limitCount = nth(1, $4); +select_no_parens: simple_select + { + $$ = $1; + } + | select_clause sort_clause opt_for_update_clause opt_select_limit + { + insertSelectOptions((SelectStmt *) $1, $2, $3, + nth(0, $4), nth(1, $4)); + $$ = $1; + } + | select_clause for_update_clause opt_select_limit + { + insertSelectOptions((SelectStmt *) $1, NIL, $2, + nth(0, $3), nth(1, $3)); + $$ = $1; + } + | select_clause select_limit + { + insertSelectOptions((SelectStmt *) $1, NIL, NIL, + nth(0, $2), nth(1, $2)); $$ = $1; } ; -/* This rule parses Select statements that can appear within set operations, +select_clause: simple_select + { + $$ = $1; + } + | '(' SelectStmt ')' + { + $$ = $2; + } + ; + +/* + * This rule parses SELECT statements that can appear within set operations, * including UNION, INTERSECT and EXCEPT. '(' and ')' can be used to specify * 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. + * As with select_no_parens, simple_select cannot have outer parentheses, + * but can have parenthesized subclauses. * - * 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. + * Note that sort clauses cannot be included at this level --- SQL92 requires + * SELECT foo UNION SELECT bar ORDER BY baz + * to be parsed as + * (SELECT foo UNION SELECT bar) ORDER BY baz + * not + * SELECT foo UNION (SELECT bar ORDER BY baz) + * Likewise FOR UPDATE and LIMIT. This does not limit functionality, + * because you can reintroduce sort and limit clauses inside parentheses. + * + * NOTE: only the leftmost component SelectStmt should have INTO. + * However, this is not checked by the grammar; parse analysis must check it. */ -select_clause: '(' select_subclause ')' - { - $$ = $2; - } - | select_subclause - { - $$ = $1; - } - ; - -select_subclause: SELECT opt_distinct target_list - result from_clause where_clause +simple_select: SELECT opt_distinct target_list + into_clause from_clause where_clause group_clause having_clause { SelectStmt *n = makeNode(SelectStmt); @@ -3310,35 +3375,20 @@ select_subclause: SELECT opt_distinct target_list } | 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; + $$ = makeSetOp(SETOP_UNION, $3, $1, $4); } | 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; + $$ = makeSetOp(SETOP_INTERSECT, $3, $1, $4); } | 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; + $$ = makeSetOp(SETOP_EXCEPT, $3, $1, $4); } ; /* easy way to return two values. Can someone improve this? bjm */ -result: INTO OptTempTableName { $$ = $2; } +into_clause: INTO OptTempTableName { $$ = $2; } | /*EMPTY*/ { $$ = lcons(makeInteger(FALSE), NIL); } ; @@ -3391,7 +3441,6 @@ opt_distinct: DISTINCT { $$ = makeList1(NIL); } ; sort_clause: ORDER BY sortby_list { $$ = $3; } - | /*EMPTY*/ { $$ = NIL; } ; sortby_list: sortby { $$ = makeList1($1); } @@ -3413,7 +3462,7 @@ OptUseOp: USING all_Op { $$ = $2; } ; -opt_select_limit: LIMIT select_limit_value ',' select_offset_value +select_limit: LIMIT select_limit_value ',' select_offset_value { $$ = makeList2($4, $2); } | LIMIT select_limit_value OFFSET select_offset_value { $$ = makeList2($4, $2); } @@ -3423,20 +3472,22 @@ opt_select_limit: LIMIT select_limit_value ',' select_offset_value { $$ = makeList2($2, $4); } | OFFSET select_offset_value { $$ = makeList2($2, NULL); } - | /* EMPTY */ - { $$ = makeList2(NULL, NULL); } + ; + +opt_select_limit: select_limit { $$ = $1; } + | /* EMPTY */ { $$ = makeList2(NULL,NULL); } ; select_limit_value: Iconst { Const *n = makeNode(Const); - if ($1 < 1) - elog(ERROR, "Selection limit must be ALL or a positive integer value"); + if ($1 < 0) + elog(ERROR, "LIMIT must not be negative"); n->consttype = INT4OID; n->constlen = sizeof(int4); - n->constvalue = (Datum)$1; + n->constvalue = Int32GetDatum($1); n->constisnull = FALSE; n->constbyval = TRUE; n->constisset = FALSE; @@ -3445,12 +3496,13 @@ select_limit_value: Iconst } | ALL { + /* LIMIT ALL is represented as a NULL constant */ Const *n = makeNode(Const); n->consttype = INT4OID; n->constlen = sizeof(int4); - n->constvalue = (Datum)0; - n->constisnull = FALSE; + n->constvalue = (Datum) 0; + n->constisnull = TRUE; n->constbyval = TRUE; n->constisset = FALSE; n->constiscast = FALSE; @@ -3471,9 +3523,12 @@ select_offset_value: Iconst { Const *n = makeNode(Const); + if ($1 < 0) + elog(ERROR, "OFFSET must not be negative"); + n->consttype = INT4OID; n->constlen = sizeof(int4); - n->constvalue = (Datum)$1; + n->constvalue = Int32GetDatum($1); n->constisnull = FALSE; n->constbyval = TRUE; n->constisset = FALSE; @@ -3490,6 +3545,7 @@ select_offset_value: Iconst $$ = (Node *)n; } ; + /* * jimmy bell-style recursive queries aren't supported in the * current system. @@ -3522,6 +3578,9 @@ having_clause: HAVING a_expr for_update_clause: FOR UPDATE update_list { $$ = $3; } | FOR READ ONLY { $$ = NULL; } + ; + +opt_for_update_clause: for_update_clause { $$ = $1; } | /* EMPTY */ { $$ = NULL; } ; @@ -3565,7 +3624,7 @@ table_ref: relation_expr $1->name = $2; $$ = (Node *) $1; } - | '(' select_subclause ')' alias_clause + | '(' SelectStmt ')' alias_clause { RangeSubselect *n = makeNode(RangeSubselect); n->subquery = $2; @@ -4101,7 +4160,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 '(' select_subclause ')' +row_expr: '(' row_descriptor ')' IN '(' SelectStmt ')' { SubLink *n = makeNode(SubLink); n->lefthand = $2; @@ -4111,7 +4170,7 @@ row_expr: '(' row_descriptor ')' IN '(' select_subclause ')' n->subselect = $6; $$ = (Node *)n; } - | '(' row_descriptor ')' NOT IN '(' select_subclause ')' + | '(' row_descriptor ')' NOT IN '(' SelectStmt ')' { SubLink *n = makeNode(SubLink); n->lefthand = $2; @@ -4121,7 +4180,7 @@ row_expr: '(' row_descriptor ')' IN '(' select_subclause ')' n->subselect = $7; $$ = (Node *)n; } - | '(' row_descriptor ')' all_Op sub_type '(' select_subclause ')' + | '(' row_descriptor ')' all_Op sub_type '(' SelectStmt ')' { SubLink *n = makeNode(SubLink); n->lefthand = $2; @@ -4134,7 +4193,7 @@ row_expr: '(' row_descriptor ')' IN '(' select_subclause ')' n->subselect = $7; $$ = (Node *)n; } - | '(' row_descriptor ')' all_Op '(' select_subclause ')' + | '(' row_descriptor ')' all_Op '(' SelectStmt ')' { SubLink *n = makeNode(SubLink); n->lefthand = $2; @@ -4458,7 +4517,7 @@ a_expr: c_expr $$ = n; } } - | a_expr all_Op sub_type '(' select_subclause ')' + | a_expr all_Op sub_type '(' SelectStmt ')' { SubLink *n = makeNode(SubLink); n->lefthand = makeList1($1); @@ -4851,7 +4910,7 @@ c_expr: attr n->agg_distinct = FALSE; $$ = (Node *)n; } - | '(' select_subclause ')' + | '(' select_no_parens ')' { SubLink *n = makeNode(SubLink); n->lefthand = NIL; @@ -4861,7 +4920,7 @@ c_expr: attr n->subselect = $2; $$ = (Node *)n; } - | EXISTS '(' select_subclause ')' + | EXISTS '(' SelectStmt ')' { SubLink *n = makeNode(SubLink); n->lefthand = NIL; @@ -4960,7 +5019,7 @@ trim_list: a_expr FROM expr_list { $$ = $1; } ; -in_expr: select_subclause +in_expr: SelectStmt { SubLink *n = makeNode(SubLink); n->subselect = $1; @@ -5688,20 +5747,71 @@ mapTargetColumns(List *src, List *dst) src = lnext(src); dst = lnext(dst); } - return; } /* mapTargetColumns() */ /* findLeftmostSelect() - * Find the leftmost SelectStmt in a SetOperationStmt parsetree. + * Find the leftmost component SelectStmt in a set-operation parsetree. */ static SelectStmt * -findLeftmostSelect(Node *node) +findLeftmostSelect(SelectStmt *node) { - while (node && IsA(node, SetOperationStmt)) - node = ((SetOperationStmt *) node)->larg; - Assert(node && IsA(node, SelectStmt)); - return (SelectStmt *) node; + while (node && node->op != SETOP_NONE) + node = node->larg; + Assert(node && IsA(node, SelectStmt) && node->larg == NULL); + return node; +} + +/* insertSelectOptions() + * Insert ORDER BY, etc into an already-constructed SelectStmt. + * + * This routine is just to avoid duplicating code in SelectStmt productions. + */ +static void +insertSelectOptions(SelectStmt *stmt, + List *sortClause, List *forUpdate, + Node *limitOffset, Node *limitCount) +{ + /* + * Tests here are to reject constructs like + * (SELECT foo ORDER BY bar) ORDER BY baz + */ + if (sortClause) + { + if (stmt->sortClause) + elog(ERROR, "Multiple ORDER BY clauses not allowed"); + stmt->sortClause = sortClause; + } + if (forUpdate) + { + if (stmt->forUpdate) + elog(ERROR, "Multiple FOR UPDATE clauses not allowed"); + stmt->forUpdate = forUpdate; + } + if (limitOffset) + { + if (stmt->limitOffset) + elog(ERROR, "Multiple OFFSET clauses not allowed"); + stmt->limitOffset = limitOffset; + } + if (limitCount) + { + if (stmt->limitCount) + elog(ERROR, "Multiple LIMIT clauses not allowed"); + stmt->limitCount = limitCount; + } +} + +static Node * +makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg) +{ + SelectStmt *n = makeNode(SelectStmt); + + n->op = op; + n->all = all; + n->larg = (SelectStmt *) larg; + n->rarg = (SelectStmt *) rarg; + return (Node *) n; } diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 70dfe9706b..cc25a5a026 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -3,7 +3,7 @@ * back to source text * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.67 2000/10/26 21:37:45 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.68 2000/11/05 00:15:53 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -941,7 +941,11 @@ get_select_query_def(Query *query, deparse_context *context) if (query->limitCount != NULL) { appendStringInfo(buf, " LIMIT "); - get_rule_expr(query->limitCount, context); + if (IsA(query->limitCount, Const) && + ((Const *) query->limitCount)->constisnull) + appendStringInfo(buf, "ALL"); + else + get_rule_expr(query->limitCount, context); } } diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 3913d88ce9..6ac6d0be4d 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2000, PostgreSQL, Inc * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: parsenodes.h,v 1.117 2000/10/18 16:16:10 momjian Exp $ + * $Id: parsenodes.h,v 1.118 2000/11/05 00:15:53 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -782,7 +782,7 @@ typedef struct InsertStmt /* * An INSERT statement has *either* VALUES or SELECT, never both. * If VALUES, a targetList is supplied (empty for DEFAULT VALUES). - * If SELECT, a complete SelectStmt (or SetOperation tree) is supplied. + * If SELECT, a complete SelectStmt (or set-operation tree) is supplied. */ List *targetList; /* the target list (of ResTarget) */ Node *selectStmt; /* the source SELECT */ @@ -816,51 +816,71 @@ typedef struct UpdateStmt /* ---------------------- * Select Statement - * ---------------------- - */ -typedef struct SelectStmt -{ - NodeTag type; - List *distinctClause; /* NULL, list of DISTINCT ON exprs, or - * lcons(NIL,NIL) for all (SELECT - * DISTINCT) */ - char *into; /* name of table (for select into table) */ - List *targetList; /* the target list (of ResTarget) */ - List *fromClause; /* the from clause */ - Node *whereClause; /* qualifications */ - List *groupClause; /* GROUP BY clauses */ - Node *havingClause; /* having conditional-expression */ - List *sortClause; /* sort clause (a list of SortGroupBy's) */ - char *portalname; /* the portal (cursor) to create */ - bool binary; /* a binary (internal) portal? */ - bool istemp; /* into is a temp table */ - Node *limitOffset; /* # of result tuples to skip */ - Node *limitCount; /* # of result tuples to return */ - List *forUpdate; /* FOR UPDATE clause */ -} SelectStmt; - -/* ---------------------- - * Select Statement with Set Operations * - * UNION/INTERSECT/EXCEPT operations are represented in the output of gram.y - * as a tree whose leaves are SelectStmts and internal nodes are - * SetOperationStmts. The statement-wide info (ORDER BY, etc clauses) - * is placed in the leftmost SelectStmt leaf. - * - * After parse analysis, there is a top-level Query node containing the leaf - * SELECTs as subqueries in its range table. Its setOperations field is the - * SetOperationStmt tree with leaf SelectStmt nodes replaced by RangeTblRef - * nodes. The statement-wide options such as ORDER BY are attached to this - * top-level Query. + * A "simple" SELECT is represented in the output of gram.y by a single + * SelectStmt node. A SELECT construct containing set operators (UNION, + * INTERSECT, EXCEPT) is represented by a tree of SelectStmt nodes, in + * which the leaf nodes are component SELECTs and the internal nodes + * represent UNION, INTERSECT, or EXCEPT operators. Using the same node + * type for both leaf and internal nodes allows gram.y to stick ORDER BY, + * LIMIT, etc, clause values into a SELECT statement without worrying + * whether it is a simple or compound SELECT. * ---------------------- */ typedef enum SetOperation { + SETOP_NONE = 0, SETOP_UNION, SETOP_INTERSECT, SETOP_EXCEPT } SetOperation; +typedef struct SelectStmt +{ + NodeTag type; + /* + * These fields are used only in "leaf" SelectStmts. + */ + List *distinctClause; /* NULL, list of DISTINCT ON exprs, or + * lcons(NIL,NIL) for all (SELECT + * DISTINCT) */ + char *into; /* name of table (for select into table) */ + bool istemp; /* into is a temp table? */ + List *targetList; /* the target list (of ResTarget) */ + List *fromClause; /* the FROM clause */ + Node *whereClause; /* WHERE qualification */ + List *groupClause; /* GROUP BY clauses */ + Node *havingClause; /* HAVING conditional-expression */ + /* + * These fields are used in both "leaf" SelectStmts and upper-level + * SelectStmts. portalname/binary may only be set at the top level. + */ + List *sortClause; /* sort clause (a list of SortGroupBy's) */ + char *portalname; /* the portal (cursor) to create */ + bool binary; /* a binary (internal) portal? */ + Node *limitOffset; /* # of result tuples to skip */ + Node *limitCount; /* # of result tuples to return */ + List *forUpdate; /* FOR UPDATE clause */ + /* + * These fields are used only in upper-level SelectStmts. + */ + SetOperation op; /* type of set op */ + bool all; /* ALL specified? */ + struct SelectStmt *larg; /* left child */ + struct SelectStmt *rarg; /* right child */ + /* Eventually add fields for CORRESPONDING spec here */ +} SelectStmt; + +/* ---------------------- + * Set Operation node for post-analysis query trees + * + * After parse analysis, a SELECT with set operations is represented by a + * top-level Query node containing the leaf SELECTs as subqueries in its + * range table. Its setOperations field shows the tree of set operations, + * with leaf SelectStmt nodes replaced by RangeTblRef nodes, and internal + * nodes replaced by SetOperationStmt nodes. + * ---------------------- + */ typedef struct SetOperationStmt { NodeTag type; @@ -870,7 +890,7 @@ typedef struct SetOperationStmt Node *rarg; /* right child */ /* Eventually add fields for CORRESPONDING spec here */ - /* This field is filled in during parse analysis: */ + /* Fields derived during parse analysis: */ List *colTypes; /* integer list of OIDs of output column types */ } SetOperationStmt;