aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/executor/nodeLimit.c14
-rw-r--r--src/backend/nodes/copyfuncs.c8
-rw-r--r--src/backend/nodes/equalfuncs.c14
-rw-r--r--src/backend/optimizer/plan/planner.c23
-rw-r--r--src/backend/parser/analyze.c173
-rw-r--r--src/backend/parser/gram.y296
-rw-r--r--src/backend/utils/adt/ruleutils.c8
-rw-r--r--src/include/nodes/parsenodes.h70
8 files changed, 388 insertions, 218 deletions
diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c
index c7bc666c2f9..9e0cef44ab1 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 9435a396f07..406e85ce623 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 8519fd6115e..08087136a10 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 f9c70f7137d..5951e8f0c9b 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 b86dab4bea0..45e81aec608 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;
+
+ /*
+ * 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 actually support forUpdate with set ops at the moment. */
+ /* 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;
@@ -1966,20 +2009,6 @@ transformSetOperationTree(ParseState *pstate, Node *node)
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
@@ -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 f6859429f2a..2fbfa853dd3 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 <node> select_clause, select_subclause
+%type <node> select_no_parens, select_clause, simple_select
%type <list> SessionList
%type <node> SessionClause
@@ -174,8 +178,8 @@ static void doNegateFloat(Value *v);
%type <chr> operation, TriggerOneEvent
%type <list> 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 <typnam> func_arg, func_return, aggr_argtype
%type <boolean> opt_arg, TriggerForOpt, TriggerForType, OptTemp
-%type <list> for_update_clause, update_list
+%type <list> for_update_clause, opt_for_update_clause, update_list
%type <boolean> opt_all
%type <boolean> opt_table
%type <boolean> 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.
- *
- * NOTE: only the leftmost SelectStmt leaf should have INTO, either.
- * However, this is not checked by the grammar; parse analysis must check it.
+/* A complete SELECT statement looks like this.
+ *
+ * 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,
- * 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.
- *
- * 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.
- */
-select_clause: '(' select_subclause ')'
+select_clause: simple_select
{
- $$ = $2;
+ $$ = $1;
}
- | select_subclause
+ | '(' SelectStmt ')'
{
- $$ = $1;
+ $$ = $2;
}
;
-select_subclause: SELECT opt_distinct target_list
- result from_clause where_clause
+/*
+ * 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.
+ *
+ * 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 --- 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.
+ */
+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 70dfe9706bc..cc25a5a026a 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 3913d88ce91..6ac6d0be4da 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
+ *
+ * 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; /* qualifications */
+ List *fromClause; /* the FROM clause */
+ Node *whereClause; /* WHERE qualification */
List *groupClause; /* GROUP BY clauses */
- Node *havingClause; /* having conditional-expression */
+ 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? */
- 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 */
+ /*
+ * 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;
/* ----------------------
- * 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.
+ * Set Operation node for post-analysis query trees
*
- * 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.
+ * 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 enum SetOperation
-{
- SETOP_UNION,
- SETOP_INTERSECT,
- SETOP_EXCEPT
-} SetOperation;
-
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;