Commit 0d115dde authored by Tom Lane's avatar Tom Lane

Extend CTE patch to support recursive UNION (ie, without ALL). The

implementation uses an in-memory hash table, so it will poop out for very
large recursive results ... but the performance characteristics of a
sort-based implementation would be pretty unpleasant too.
parent 059349be
<!-- $PostgreSQL: pgsql/doc/src/sgml/queries.sgml,v 1.46 2008/10/04 21:56:52 tgl Exp $ --> <!-- $PostgreSQL: pgsql/doc/src/sgml/queries.sgml,v 1.47 2008/10/07 19:27:03 tgl Exp $ -->
<chapter id="queries"> <chapter id="queries">
<title>Queries</title> <title>Queries</title>
...@@ -1519,7 +1519,8 @@ SELECT sum(n) FROM t; ...@@ -1519,7 +1519,8 @@ SELECT sum(n) FROM t;
</programlisting> </programlisting>
The general form of a recursive <literal>WITH</> query is always a The general form of a recursive <literal>WITH</> query is always a
<firstterm>non-recursive term</>, then <literal>UNION ALL</>, then a <firstterm>non-recursive term</>, then <literal>UNION</> (or
<literal>UNION ALL</>), then a
<firstterm>recursive term</>, where only the recursive term can contain <firstterm>recursive term</>, where only the recursive term can contain
a reference to the query's own output. Such a query is executed as a reference to the query's own output. Such a query is executed as
follows: follows:
...@@ -1530,9 +1531,10 @@ SELECT sum(n) FROM t; ...@@ -1530,9 +1531,10 @@ SELECT sum(n) FROM t;
<step performance="required"> <step performance="required">
<para> <para>
Evaluate the non-recursive term. Include all its output rows in the Evaluate the non-recursive term. For <literal>UNION</> (but not
result of the recursive query, and also place them in a temporary <literal>UNION ALL</>), discard duplicate rows. Include all remaining
<firstterm>working table</>. rows in the result of the recursive query, and also place them in a
temporary <firstterm>working table</>.
</para> </para>
</step> </step>
...@@ -1544,9 +1546,11 @@ SELECT sum(n) FROM t; ...@@ -1544,9 +1546,11 @@ SELECT sum(n) FROM t;
<step performance="required"> <step performance="required">
<para> <para>
Evaluate the recursive term, substituting the current contents of Evaluate the recursive term, substituting the current contents of
the working table for the recursive self-reference. Include all its the working table for the recursive self-reference.
output rows in the result of the recursive query, and also place them For <literal>UNION</> (but not <literal>UNION ALL</>), discard
in a temporary <firstterm>intermediate table</>. duplicate rows and rows that duplicate any previous result row.
Include all remaining rows in the result of the recursive query, and
also place them in a temporary <firstterm>intermediate table</>.
</para> </para>
</step> </step>
...@@ -1598,10 +1602,13 @@ GROUP BY sub_part ...@@ -1598,10 +1602,13 @@ GROUP BY sub_part
<para> <para>
When working with recursive queries it is important to be sure that When working with recursive queries it is important to be sure that
the recursive part of the query will eventually return no tuples, the recursive part of the query will eventually return no tuples,
or else the query will loop indefinitely. A useful trick for or else the query will loop indefinitely. Sometimes, using
development purposes is to place a <literal>LIMIT</> in the parent <literal>UNION</> instead of <literal>UNION ALL</> can accomplish this
query. For example, this query would loop forever without the by discarding rows that duplicate previous output rows; this catches
<literal>LIMIT</>: cycles that would otherwise repeat. A useful trick for testing queries
when you are not certain if they might loop is to place a <literal>LIMIT</>
in the parent query. For example, this query would loop forever without
the <literal>LIMIT</>:
<programlisting> <programlisting>
WITH RECURSIVE t(n) AS ( WITH RECURSIVE t(n) AS (
...@@ -1614,7 +1621,7 @@ SELECT n FROM t LIMIT 100; ...@@ -1614,7 +1621,7 @@ SELECT n FROM t LIMIT 100;
This works because <productname>PostgreSQL</productname>'s implementation This works because <productname>PostgreSQL</productname>'s implementation
evaluates only as many rows of a <literal>WITH</> query as are actually evaluates only as many rows of a <literal>WITH</> query as are actually
demanded by the parent query. Using this trick in production is not fetched by the parent query. Using this trick in production is not
recommended, because other systems might work differently. recommended, because other systems might work differently.
</para> </para>
......
<!-- <!--
$PostgreSQL: pgsql/doc/src/sgml/ref/select.sgml,v 1.105 2008/10/04 21:56:52 tgl Exp $ $PostgreSQL: pgsql/doc/src/sgml/ref/select.sgml,v 1.106 2008/10/07 19:27:04 tgl Exp $
PostgreSQL documentation PostgreSQL documentation
--> -->
...@@ -202,10 +202,10 @@ and <replaceable class="parameter">with_query</replaceable> is: ...@@ -202,10 +202,10 @@ and <replaceable class="parameter">with_query</replaceable> is:
subquery to reference itself by name. Such a subquery must have subquery to reference itself by name. Such a subquery must have
the form the form
<synopsis> <synopsis>
<replaceable class="parameter">non_recursive_term</replaceable> UNION ALL <replaceable class="parameter">recursive_term</replaceable> <replaceable class="parameter">non_recursive_term</replaceable> UNION [ ALL ] <replaceable class="parameter">recursive_term</replaceable>
</synopsis> </synopsis>
where the recursive self-reference must appear on the right-hand where the recursive self-reference must appear on the right-hand
side of <literal>UNION ALL</>. Only one recursive self-reference side of the <literal>UNION</>. Only one recursive self-reference
is permitted per query. is permitted per query.
</para> </para>
...@@ -1234,7 +1234,7 @@ SELECT distance, employee_name FROM employee_recursive; ...@@ -1234,7 +1234,7 @@ SELECT distance, employee_name FROM employee_recursive;
</programlisting> </programlisting>
Notice the typical form of recursive queries: Notice the typical form of recursive queries:
an initial condition, followed by <literal>UNION ALL</literal>, an initial condition, followed by <literal>UNION</literal>,
followed by the recursive part of the query. Be sure that the followed by the recursive part of the query. Be sure that the
recursive part of the query will eventually return no tuples, or recursive part of the query will eventually return no tuples, or
else the query will loop indefinitely. (See <xref linkend="queries-with"> else the query will loop indefinitely. (See <xref linkend="queries-with">
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/nodeRecursiveunion.c,v 1.1 2008/10/04 21:56:53 tgl Exp $ * $PostgreSQL: pgsql/src/backend/executor/nodeRecursiveunion.c,v 1.2 2008/10/07 19:27:04 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -17,6 +17,41 @@ ...@@ -17,6 +17,41 @@
#include "executor/execdebug.h" #include "executor/execdebug.h"
#include "executor/nodeRecursiveunion.h" #include "executor/nodeRecursiveunion.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "utils/memutils.h"
/*
* To implement UNION (without ALL), we need a hashtable that stores tuples
* already seen. The hash key is computed from the grouping columns.
*/
typedef struct RUHashEntryData *RUHashEntry;
typedef struct RUHashEntryData
{
TupleHashEntryData shared; /* common header for hash table entries */
} RUHashEntryData;
/*
* Initialize the hash table to empty.
*/
static void
build_hash_table(RecursiveUnionState *rustate)
{
RecursiveUnion *node = (RecursiveUnion *) rustate->ps.plan;
Assert(node->numCols > 0);
Assert(node->numGroups > 0);
rustate->hashtable = BuildTupleHashTable(node->numCols,
node->dupColIdx,
rustate->eqfunctions,
rustate->hashfunctions,
node->numGroups,
sizeof(RUHashEntryData),
rustate->tableContext,
rustate->tempContext);
}
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
...@@ -44,49 +79,85 @@ ExecRecursiveUnion(RecursiveUnionState *node) ...@@ -44,49 +79,85 @@ ExecRecursiveUnion(RecursiveUnionState *node)
PlanState *innerPlan = innerPlanState(node); PlanState *innerPlan = innerPlanState(node);
RecursiveUnion *plan = (RecursiveUnion *) node->ps.plan; RecursiveUnion *plan = (RecursiveUnion *) node->ps.plan;
TupleTableSlot *slot; TupleTableSlot *slot;
RUHashEntry entry;
bool isnew;
/* 1. Evaluate non-recursive term */ /* 1. Evaluate non-recursive term */
if (!node->recursing) if (!node->recursing)
{ {
slot = ExecProcNode(outerPlan); for (;;)
if (!TupIsNull(slot))
{ {
slot = ExecProcNode(outerPlan);
if (TupIsNull(slot))
break;
if (plan->numCols > 0)
{
/* Find or build hashtable entry for this tuple's group */
entry = (RUHashEntry)
LookupTupleHashEntry(node->hashtable, slot, &isnew);
/* Must reset temp context after each hashtable lookup */
MemoryContextReset(node->tempContext);
/* Ignore tuple if already seen */
if (!isnew)
continue;
}
/* Each non-duplicate tuple goes to the working table ... */
tuplestore_puttupleslot(node->working_table, slot); tuplestore_puttupleslot(node->working_table, slot);
/* ... and to the caller */
return slot; return slot;
} }
node->recursing = true; node->recursing = true;
} }
retry:
/* 2. Execute recursive term */ /* 2. Execute recursive term */
slot = ExecProcNode(innerPlan); for (;;)
if (TupIsNull(slot))
{ {
if (node->intermediate_empty) slot = ExecProcNode(innerPlan);
return NULL; if (TupIsNull(slot))
{
/* Done if there's nothing in the intermediate table */
if (node->intermediate_empty)
break;
/* done with old working table ... */ /* done with old working table ... */
tuplestore_end(node->working_table); tuplestore_end(node->working_table);
/* intermediate table becomes working table */ /* intermediate table becomes working table */
node->working_table = node->intermediate_table; node->working_table = node->intermediate_table;
/* create new empty intermediate table */ /* create new empty intermediate table */
node->intermediate_table = tuplestore_begin_heap(false, false, work_mem); node->intermediate_table = tuplestore_begin_heap(false, false,
node->intermediate_empty = true; work_mem);
node->intermediate_empty = true;
/* and reset the inner plan */ /* reset the recursive term */
innerPlan->chgParam = bms_add_member(innerPlan->chgParam, innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
plan->wtParam); plan->wtParam);
goto retry;
} /* and continue fetching from recursive term */
else continue;
{ }
if (plan->numCols > 0)
{
/* Find or build hashtable entry for this tuple's group */
entry = (RUHashEntry)
LookupTupleHashEntry(node->hashtable, slot, &isnew);
/* Must reset temp context after each hashtable lookup */
MemoryContextReset(node->tempContext);
/* Ignore tuple if already seen */
if (!isnew)
continue;
}
/* Else, tuple is good; stash it in intermediate table ... */
node->intermediate_empty = false; node->intermediate_empty = false;
tuplestore_puttupleslot(node->intermediate_table, slot); tuplestore_puttupleslot(node->intermediate_table, slot);
} /* ... and return it */
return slot;
}
return slot; return NULL;
} }
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
...@@ -109,12 +180,40 @@ ExecInitRecursiveUnion(RecursiveUnion *node, EState *estate, int eflags) ...@@ -109,12 +180,40 @@ ExecInitRecursiveUnion(RecursiveUnion *node, EState *estate, int eflags)
rustate->ps.plan = (Plan *) node; rustate->ps.plan = (Plan *) node;
rustate->ps.state = estate; rustate->ps.state = estate;
rustate->eqfunctions = NULL;
rustate->hashfunctions = NULL;
rustate->hashtable = NULL;
rustate->tempContext = NULL;
rustate->tableContext = NULL;
/* initialize processing state */ /* initialize processing state */
rustate->recursing = false; rustate->recursing = false;
rustate->intermediate_empty = true; rustate->intermediate_empty = true;
rustate->working_table = tuplestore_begin_heap(false, false, work_mem); rustate->working_table = tuplestore_begin_heap(false, false, work_mem);
rustate->intermediate_table = tuplestore_begin_heap(false, false, work_mem); rustate->intermediate_table = tuplestore_begin_heap(false, false, work_mem);
/*
* If hashing, we need a per-tuple memory context for comparisons, and a
* longer-lived context to store the hash table. The table can't just be
* kept in the per-query context because we want to be able to throw it
* away when rescanning.
*/
if (node->numCols > 0)
{
rustate->tempContext =
AllocSetContextCreate(CurrentMemoryContext,
"RecursiveUnion",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
rustate->tableContext =
AllocSetContextCreate(CurrentMemoryContext,
"RecursiveUnion hash table",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
}
/* /*
* Make the state structure available to descendant WorkTableScan nodes * Make the state structure available to descendant WorkTableScan nodes
* via the Param slot reserved for it. * via the Param slot reserved for it.
...@@ -154,6 +253,19 @@ ExecInitRecursiveUnion(RecursiveUnion *node, EState *estate, int eflags) ...@@ -154,6 +253,19 @@ ExecInitRecursiveUnion(RecursiveUnion *node, EState *estate, int eflags)
outerPlanState(rustate) = ExecInitNode(outerPlan(node), estate, eflags); outerPlanState(rustate) = ExecInitNode(outerPlan(node), estate, eflags);
innerPlanState(rustate) = ExecInitNode(innerPlan(node), estate, eflags); innerPlanState(rustate) = ExecInitNode(innerPlan(node), estate, eflags);
/*
* If hashing, precompute fmgr lookup data for inner loop, and create
* the hash table.
*/
if (node->numCols > 0)
{
execTuplesHashPrepare(node->numCols,
node->dupOperators,
&rustate->eqfunctions,
&rustate->hashfunctions);
build_hash_table(rustate);
}
return rustate; return rustate;
} }
...@@ -178,6 +290,12 @@ ExecEndRecursiveUnion(RecursiveUnionState *node) ...@@ -178,6 +290,12 @@ ExecEndRecursiveUnion(RecursiveUnionState *node)
tuplestore_end(node->working_table); tuplestore_end(node->working_table);
tuplestore_end(node->intermediate_table); tuplestore_end(node->intermediate_table);
/* free subsidiary stuff including hashtable */
if (node->tempContext)
MemoryContextDelete(node->tempContext);
if (node->tableContext)
MemoryContextDelete(node->tableContext);
/* /*
* clean out the upper tuple table * clean out the upper tuple table
*/ */
...@@ -217,6 +335,14 @@ ExecRecursiveUnionReScan(RecursiveUnionState *node, ExprContext *exprCtxt) ...@@ -217,6 +335,14 @@ ExecRecursiveUnionReScan(RecursiveUnionState *node, ExprContext *exprCtxt)
if (outerPlan->chgParam == NULL) if (outerPlan->chgParam == NULL)
ExecReScan(outerPlan, exprCtxt); ExecReScan(outerPlan, exprCtxt);
/* Release any hashtable storage */
if (node->tableContext)
MemoryContextResetAndDeleteChildren(node->tableContext);
/* And rebuild empty hashtable if needed */
if (plan->numCols > 0)
build_hash_table(node);
/* reset processing state */ /* reset processing state */
node->recursing = false; node->recursing = false;
node->intermediate_empty = true; node->intermediate_empty = true;
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.407 2008/10/06 17:39:25 tgl Exp $ * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.408 2008/10/07 19:27:04 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -193,6 +193,13 @@ _copyRecursiveUnion(RecursiveUnion *from) ...@@ -193,6 +193,13 @@ _copyRecursiveUnion(RecursiveUnion *from)
* copy remainder of node * copy remainder of node
*/ */
COPY_SCALAR_FIELD(wtParam); COPY_SCALAR_FIELD(wtParam);
COPY_SCALAR_FIELD(numCols);
if (from->numCols > 0)
{
COPY_POINTER_FIELD(dupColIdx, from->numCols * sizeof(AttrNumber));
COPY_POINTER_FIELD(dupOperators, from->numCols * sizeof(Oid));
}
COPY_SCALAR_FIELD(numGroups);
return newnode; return newnode;
} }
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.341 2008/10/06 17:39:26 tgl Exp $ * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.342 2008/10/07 19:27:04 tgl Exp $
* *
* NOTES * NOTES
* Every node type that can appear in stored rules' parsetrees *must* * Every node type that can appear in stored rules' parsetrees *must*
...@@ -334,11 +334,24 @@ _outAppend(StringInfo str, Append *node) ...@@ -334,11 +334,24 @@ _outAppend(StringInfo str, Append *node)
static void static void
_outRecursiveUnion(StringInfo str, RecursiveUnion *node) _outRecursiveUnion(StringInfo str, RecursiveUnion *node)
{ {
int i;
WRITE_NODE_TYPE("RECURSIVEUNION"); WRITE_NODE_TYPE("RECURSIVEUNION");
_outPlanInfo(str, (Plan *) node); _outPlanInfo(str, (Plan *) node);
WRITE_INT_FIELD(wtParam); WRITE_INT_FIELD(wtParam);
WRITE_INT_FIELD(numCols);
appendStringInfo(str, " :dupColIdx");
for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %d", node->dupColIdx[i]);
appendStringInfo(str, " :dupOperators");
for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %u", node->dupOperators[i]);
WRITE_LONG_FIELD(numGroups);
} }
static void static void
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.249 2008/10/04 21:56:53 tgl Exp $ * $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.250 2008/10/07 19:27:04 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -2545,10 +2545,13 @@ RecursiveUnion * ...@@ -2545,10 +2545,13 @@ RecursiveUnion *
make_recursive_union(List *tlist, make_recursive_union(List *tlist,
Plan *lefttree, Plan *lefttree,
Plan *righttree, Plan *righttree,
int wtParam) int wtParam,
List *distinctList,
long numGroups)
{ {
RecursiveUnion *node = makeNode(RecursiveUnion); RecursiveUnion *node = makeNode(RecursiveUnion);
Plan *plan = &node->plan; Plan *plan = &node->plan;
int numCols = list_length(distinctList);
cost_recursive_union(plan, lefttree, righttree); cost_recursive_union(plan, lefttree, righttree);
...@@ -2558,6 +2561,37 @@ make_recursive_union(List *tlist, ...@@ -2558,6 +2561,37 @@ make_recursive_union(List *tlist,
plan->righttree = righttree; plan->righttree = righttree;
node->wtParam = wtParam; node->wtParam = wtParam;
/*
* convert SortGroupClause list into arrays of attr indexes and equality
* operators, as wanted by executor
*/
node->numCols = numCols;
if (numCols > 0)
{
int keyno = 0;
AttrNumber *dupColIdx;
Oid *dupOperators;
ListCell *slitem;
dupColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numCols);
dupOperators = (Oid *) palloc(sizeof(Oid) * numCols);
foreach(slitem, distinctList)
{
SortGroupClause *sortcl = (SortGroupClause *) lfirst(slitem);
TargetEntry *tle = get_sortgroupclause_tle(sortcl,
plan->targetlist);
dupColIdx[keyno] = tle->resno;
dupOperators[keyno] = sortcl->eqop;
Assert(OidIsValid(dupOperators[keyno]));
keyno++;
}
node->dupColIdx = dupColIdx;
node->dupOperators = dupOperators;
}
node->numGroups = numGroups;
return node; return node;
} }
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.157 2008/10/06 17:39:26 tgl Exp $ * $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.158 2008/10/07 19:27:04 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -318,10 +318,12 @@ generate_recursion_plan(SetOperationStmt *setOp, PlannerInfo *root, ...@@ -318,10 +318,12 @@ generate_recursion_plan(SetOperationStmt *setOp, PlannerInfo *root,
Plan *lplan; Plan *lplan;
Plan *rplan; Plan *rplan;
List *tlist; List *tlist;
List *groupList;
long numGroups;
/* Parser should have rejected other cases */ /* Parser should have rejected other cases */
if (setOp->op != SETOP_UNION || !setOp->all) if (setOp->op != SETOP_UNION)
elog(ERROR, "only UNION ALL queries can be recursive"); elog(ERROR, "only UNION queries can be recursive");
/* Worktable ID should be assigned */ /* Worktable ID should be assigned */
Assert(root->wt_param_id >= 0); Assert(root->wt_param_id >= 0);
...@@ -346,13 +348,46 @@ generate_recursion_plan(SetOperationStmt *setOp, PlannerInfo *root, ...@@ -346,13 +348,46 @@ generate_recursion_plan(SetOperationStmt *setOp, PlannerInfo *root,
list_make2(lplan, rplan), list_make2(lplan, rplan),
refnames_tlist); refnames_tlist);
/*
* If UNION, identify the grouping operators
*/
if (setOp->all)
{
groupList = NIL;
numGroups = 0;
}
else
{
double dNumGroups;
/* Identify the grouping semantics */
groupList = generate_setop_grouplist(setOp, tlist);
/* We only support hashing here */
if (!grouping_is_hashable(groupList))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("could not implement recursive UNION"),
errdetail("All column datatypes must be hashable.")));
/*
* For the moment, take the number of distinct groups as equal to
* the total input size, ie, the worst case.
*/
dNumGroups = lplan->plan_rows + rplan->plan_rows * 10;
/* Also convert to long int --- but 'ware overflow! */
numGroups = (long) Min(dNumGroups, (double) LONG_MAX);
}
/* /*
* And make the plan node. * And make the plan node.
*/ */
plan = (Plan *) make_recursive_union(tlist, lplan, rplan, plan = (Plan *) make_recursive_union(tlist, lplan, rplan,
root->wt_param_id); root->wt_param_id,
groupList, numGroups);
*sortClauses = NIL; /* result of UNION ALL is always unsorted */ *sortClauses = NIL; /* RecursiveUnion result is always unsorted */
return plan; return plan;
} }
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/parse_cte.c,v 2.2 2008/10/05 22:50:55 tgl Exp $ * $PostgreSQL: pgsql/src/backend/parser/parse_cte.c,v 2.3 2008/10/07 19:27:04 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -601,11 +601,11 @@ checkWellFormedRecursion(CteState *cstate) ...@@ -601,11 +601,11 @@ checkWellFormedRecursion(CteState *cstate)
if (!cte->cterecursive) if (!cte->cterecursive)
continue; continue;
/* Must have top-level UNION ALL */ /* Must have top-level UNION */
if (stmt->op != SETOP_UNION || !stmt->all) if (stmt->op != SETOP_UNION)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_RECURSION), (errcode(ERRCODE_INVALID_RECURSION),
errmsg("recursive query \"%s\" does not have the form non-recursive-term UNION ALL recursive-term", errmsg("recursive query \"%s\" does not have the form non-recursive-term UNION [ALL] recursive-term",
cte->ctename), cte->ctename),
parser_errposition(cstate->pstate, cte->location))); parser_errposition(cstate->pstate, cte->location)));
...@@ -628,7 +628,7 @@ checkWellFormedRecursion(CteState *cstate) ...@@ -628,7 +628,7 @@ checkWellFormedRecursion(CteState *cstate)
elog(ERROR, "missing recursive reference"); elog(ERROR, "missing recursive reference");
/* /*
* Disallow ORDER BY and similar decoration atop the UNION ALL. * Disallow ORDER BY and similar decoration atop the UNION.
* These don't make sense because it's impossible to figure out what * These don't make sense because it's impossible to figure out what
* they mean when we have only part of the recursive query's results. * they mean when we have only part of the recursive query's results.
* (If we did allow them, we'd have to check for recursive references * (If we did allow them, we'd have to check for recursive references
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.189 2008/10/04 21:56:55 tgl Exp $ * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.190 2008/10/07 19:27:04 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -964,6 +964,12 @@ typedef struct RecursiveUnionState ...@@ -964,6 +964,12 @@ typedef struct RecursiveUnionState
bool intermediate_empty; bool intermediate_empty;
Tuplestorestate *working_table; Tuplestorestate *working_table;
Tuplestorestate *intermediate_table; Tuplestorestate *intermediate_table;
/* Remaining fields are unused in UNION ALL case */
FmgrInfo *eqfunctions; /* per-grouping-field equality fns */
FmgrInfo *hashfunctions; /* per-grouping-field hash fns */
MemoryContext tempContext; /* short-term context for comparisons */
TupleHashTable hashtable; /* hash table for tuples already seen */
MemoryContext tableContext; /* memory context containing hash table */
} RecursiveUnionState; } RecursiveUnionState;
/* ---------------- /* ----------------
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.104 2008/10/04 21:56:55 tgl Exp $ * $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.105 2008/10/07 19:27:04 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -194,6 +194,12 @@ typedef struct RecursiveUnion ...@@ -194,6 +194,12 @@ typedef struct RecursiveUnion
{ {
Plan plan; Plan plan;
int wtParam; /* ID of Param representing work table */ int wtParam; /* ID of Param representing work table */
/* Remaining fields are zero/null in UNION ALL case */
int numCols; /* number of columns to check for
* duplicate-ness */
AttrNumber *dupColIdx; /* their indexes in the target list */
Oid *dupOperators; /* equality operators to compare with */
long numGroups; /* estimated number of groups in input */
} RecursiveUnion; } RecursiveUnion;
/* ---------------- /* ----------------
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.113 2008/10/04 21:56:55 tgl Exp $ * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.114 2008/10/07 19:27:04 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -43,7 +43,8 @@ extern SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual, ...@@ -43,7 +43,8 @@ extern SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual,
Index scanrelid, Plan *subplan, List *subrtable); Index scanrelid, Plan *subplan, List *subrtable);
extern Append *make_append(List *appendplans, bool isTarget, List *tlist); extern Append *make_append(List *appendplans, bool isTarget, List *tlist);
extern RecursiveUnion *make_recursive_union(List *tlist, extern RecursiveUnion *make_recursive_union(List *tlist,
Plan *lefttree, Plan *righttree, int wtParam); Plan *lefttree, Plan *righttree, int wtParam,
List *distinctList, long numGroups);
extern Sort *make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, extern Sort *make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree,
List *pathkeys, double limit_tuples); List *pathkeys, double limit_tuples);
extern Sort *make_sort_from_sortclauses(PlannerInfo *root, List *sortcls, extern Sort *make_sort_from_sortclauses(PlannerInfo *root, List *sortcls,
......
...@@ -49,6 +49,18 @@ SELECT * FROM t; ...@@ -49,6 +49,18 @@ SELECT * FROM t;
5 5
(5 rows) (5 rows)
-- This is an infinite loop with UNION ALL, but not with UNION
WITH RECURSIVE t(n) AS (
SELECT 1
UNION
SELECT 10-n FROM t)
SELECT * FROM t;
n
---
1
9
(2 rows)
-- This'd be an infinite loop, but outside query reads only as much as needed -- This'd be an infinite loop, but outside query reads only as much as needed
WITH RECURSIVE t(n) AS ( WITH RECURSIVE t(n) AS (
VALUES (1) VALUES (1)
...@@ -69,6 +81,26 @@ SELECT * FROM t LIMIT 10; ...@@ -69,6 +81,26 @@ SELECT * FROM t LIMIT 10;
10 10
(10 rows) (10 rows)
-- UNION case should have same property
WITH RECURSIVE t(n) AS (
SELECT 1
UNION
SELECT n+1 FROM t)
SELECT * FROM t LIMIT 10;
n
----
1
2
3
4
5
6
7
8
9
10
(10 rows)
-- Test behavior with an unknown-type literal in the WITH -- Test behavior with an unknown-type literal in the WITH
WITH q AS (SELECT 'foo' AS x) WITH q AS (SELECT 'foo' AS x)
SELECT x, x IS OF (unknown) as is_unknown FROM q; SELECT x, x IS OF (unknown) as is_unknown FROM q;
...@@ -510,38 +542,32 @@ WITH RECURSIVE ...@@ -510,38 +542,32 @@ WITH RECURSIVE
-- --
-- error cases -- error cases
-- --
-- UNION (should be supported someday)
WITH RECURSIVE x(n) AS (SELECT 1 UNION SELECT n+1 FROM x)
SELECT * FROM x;
ERROR: recursive query "x" does not have the form non-recursive-term UNION ALL recursive-term
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 UNION SELECT n+1 FROM x)
^
-- INTERSECT -- INTERSECT
WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x) WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x)
SELECT * FROM x; SELECT * FROM x;
ERROR: recursive query "x" does not have the form non-recursive-term UNION ALL recursive-term ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x... LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x...
^ ^
WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FROM x) WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FROM x)
SELECT * FROM x; SELECT * FROM x;
ERROR: recursive query "x" does not have the form non-recursive-term UNION ALL recursive-term ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FR... LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FR...
^ ^
-- EXCEPT -- EXCEPT
WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x) WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x)
SELECT * FROM x; SELECT * FROM x;
ERROR: recursive query "x" does not have the form non-recursive-term UNION ALL recursive-term ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x) LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x)
^ ^
WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM x) WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM x)
SELECT * FROM x; SELECT * FROM x;
ERROR: recursive query "x" does not have the form non-recursive-term UNION ALL recursive-term ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM ... LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM ...
^ ^
-- no non-recursive term -- no non-recursive term
WITH RECURSIVE x(n) AS (SELECT n FROM x) WITH RECURSIVE x(n) AS (SELECT n FROM x)
SELECT * FROM x; SELECT * FROM x;
ERROR: recursive query "x" does not have the form non-recursive-term UNION ALL recursive-term ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
LINE 1: WITH RECURSIVE x(n) AS (SELECT n FROM x) LINE 1: WITH RECURSIVE x(n) AS (SELECT n FROM x)
^ ^
-- recursive term in the left hand side (strictly speaking, should allow this) -- recursive term in the left hand side (strictly speaking, should allow this)
......
...@@ -31,6 +31,13 @@ UNION ALL ...@@ -31,6 +31,13 @@ UNION ALL
) )
SELECT * FROM t; SELECT * FROM t;
-- This is an infinite loop with UNION ALL, but not with UNION
WITH RECURSIVE t(n) AS (
SELECT 1
UNION
SELECT 10-n FROM t)
SELECT * FROM t;
-- This'd be an infinite loop, but outside query reads only as much as needed -- This'd be an infinite loop, but outside query reads only as much as needed
WITH RECURSIVE t(n) AS ( WITH RECURSIVE t(n) AS (
VALUES (1) VALUES (1)
...@@ -38,6 +45,13 @@ UNION ALL ...@@ -38,6 +45,13 @@ UNION ALL
SELECT n+1 FROM t) SELECT n+1 FROM t)
SELECT * FROM t LIMIT 10; SELECT * FROM t LIMIT 10;
-- UNION case should have same property
WITH RECURSIVE t(n) AS (
SELECT 1
UNION
SELECT n+1 FROM t)
SELECT * FROM t LIMIT 10;
-- Test behavior with an unknown-type literal in the WITH -- Test behavior with an unknown-type literal in the WITH
WITH q AS (SELECT 'foo' AS x) WITH q AS (SELECT 'foo' AS x)
SELECT x, x IS OF (unknown) as is_unknown FROM q; SELECT x, x IS OF (unknown) as is_unknown FROM q;
...@@ -265,10 +279,6 @@ WITH RECURSIVE ...@@ -265,10 +279,6 @@ WITH RECURSIVE
-- error cases -- error cases
-- --
-- UNION (should be supported someday)
WITH RECURSIVE x(n) AS (SELECT 1 UNION SELECT n+1 FROM x)
SELECT * FROM x;
-- INTERSECT -- INTERSECT
WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x) WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x)
SELECT * FROM x; SELECT * FROM x;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment