*** doc/src/sgml/ref/select.sgml.orig Fri Feb 15 17:17:06 2008
--- doc/src/sgml/ref/select.sgml Mon Sep 22 11:16:32 2008
***************
*** 20,25 ****
--- 20,26 ----
+ [WITH [RECURSIVE] with_query [, ...] ]
SELECT [ ALL | DISTINCT [ ON ( expression [, ...] ) ] ]
* | expression [ [ AS ] output_name ] [, ...]
[ FROM from_item [, ...] ]
***************
*** 39,44 ****
--- 40,49 ----
function_name ( [ argument [, ...] ] ) [ AS ] alias [ ( column_alias [, ...] | column_definition [, ...] ) ]
function_name ( [ argument [, ...] ] ) AS ( column_definition [, ...] )
from_item [ NATURAL ] join_type from_item [ ON join_condition | USING ( join_column [, ...] ) ]
+
+ and with_query is:
+
+ query_name[(column_name[, ...])] AS ( select )
***************
*** 842,847 ****
--- 847,876 ----
+
+ WITH [RECURSIVE] Clause
+
+
+ The WITH clause allows you to specify a
+ subquery that can be referenced by name in the primary query,
+ similar to how you might use a predefined view.
+
+
+
+ The column list specifies the names of the columns, or if omitted,
+ the column names are inferred from the subquery.
+
+
+
+ If RECURSIVE is specified, it allows the
+ subquery to reference itself by name. Using
+ RECURSIVE allows you to perform queries that
+ might not otherwise be possible, and are not guaranteed to
+ complete (that is, it's possible to write a query that will
+ recurse infinitely).
+
+
+
FOR UPDATE/FOR SHARE Clause
***************
*** 1107,1112 ****
--- 1136,1194 ----
111 | Walt Disney
+
+
+ This example shows how to use a simple WITH clause.
+
+ WITH t(relkind, relcount) AS (
+ SELECT relkind, count(*)
+ FROM
+ pg_catalog.pg_class
+ GROUP BY relkind
+ ORDER BY relkind
+ )
+ SELECT relkind, relcount FROM t;
+ relkind | relcount
+ ---------+----------
+ i | 90
+ r | 47
+ t | 16
+ v | 74
+
+
+
+
+
+ This example shows how to use WITH RECURSIVE.
+
+ Assume you have a table employee with columns
+ employee_name and
+ manager_name. This recursive query will find all
+ subordinates (direct or indirect) of the employee Mary, and the
+ level of indirectness. Note the typical form of recursive queries:
+ an initial condition, followed by a UNION ALL,
+ followed by a the recursive part of the query. Be sure that the
+ recursive part of the query will eventually return no tuples, or
+ else the query will recurse infinitely. It's not necessary to
+ follow this form, but often useful.
+
+
+ WITH RECURSIVE employee_recursive(distance, employee_name, manager_name) AS (
+ SELECT 1, employee_name, manager_name FROM employee WHERE manager_name = 'Mary'
+ UNION ALL
+ SELECT
+ er.distance + 1,
+ e.employee_name,
+ e.manager_name
+ FROM
+ employee e,
+ employee_recursive er
+ WHERE e.manager_name = er.employee_name
+ )
+ SELECT distance, employee_name FROM employee_recursive;
+
+
+
*** src/backend/catalog/dependency.c.orig Mon Aug 25 18:42:32 2008
--- src/backend/catalog/dependency.c Mon Sep 22 15:32:57 2008
***************
*** 1557,1563 ****
* Add whole-relation refs for each plain relation mentioned in the
* subquery's rtable, as well as datatype refs for any datatypes used
* as a RECORD function's output. (Note: query_tree_walker takes care
! * of recursing into RTE_FUNCTION and RTE_SUBQUERY RTEs, so no need to
* do that here. But keep it from looking at join alias lists.)
*/
foreach(rtable, query->rtable)
--- 1557,1563 ----
* Add whole-relation refs for each plain relation mentioned in the
* subquery's rtable, as well as datatype refs for any datatypes used
* as a RECORD function's output. (Note: query_tree_walker takes care
! * of recursing into RTE_FUNCTION RTEs, subqueries, etc, so no need to
* do that here. But keep it from looking at join alias lists.)
*/
foreach(rtable, query->rtable)
*** src/backend/commands/explain.c.orig Tue Aug 19 14:30:04 2008
--- src/backend/commands/explain.c Mon Sep 22 19:54:38 2008
***************
*** 429,434 ****
--- 429,437 ----
case T_Append:
pname = "Append";
break;
+ case T_Recursion:
+ pname = "Recursion";
+ break;
case T_BitmapAnd:
pname = "BitmapAnd";
break;
***************
*** 537,542 ****
--- 540,548 ----
case T_ValuesScan:
pname = "Values Scan";
break;
+ case T_RecursiveScan:
+ pname = "Recursive Scan";
+ break;
case T_Material:
pname = "Materialize";
break;
***************
*** 721,726 ****
--- 727,749 ----
quote_identifier(valsname));
}
break;
+ case T_Recursion:
+ case T_RecursiveScan:
+ if (((Scan *) plan)->scanrelid > 0)
+ {
+ RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid,
+ es->rtable);
+ char *valsname;
+
+ /* Assert it's on a recursive rte */
+ Assert(rte->rtekind == RTE_CTE);
+
+ valsname = rte->eref->aliasname;
+
+ appendStringInfo(str, " on %s",
+ quote_identifier(valsname));
+ }
+ break;
default:
break;
}
***************
*** 787,792 ****
--- 810,817 ----
case T_SeqScan:
case T_FunctionScan:
case T_ValuesScan:
+ case T_RecursiveScan:
+ case T_Recursion:
show_scan_qual(plan->qual,
"Filter",
((Scan *) plan)->scanrelid,
***************
*** 1028,1033 ****
--- 1053,1074 ----
indent + 3, es);
}
+ if (IsA(plan, Recursion))
+ {
+ Recursion *subqueryscan = (Recursion *) plan;
+ RecursionState *subquerystate = (RecursionState *) planstate;
+ Plan *subnode = subqueryscan->subplan;
+
+ for (i = 0; i < indent; i++)
+ appendStringInfo(str, " ");
+ appendStringInfo(str, " -> ");
+
+ explain_outNode(str, subnode,
+ subquerystate->subplan,
+ NULL,
+ indent + 3, es);
+ }
+
/* subPlan-s */
if (planstate->subPlan)
{
*** src/backend/executor/Makefile.orig Tue Feb 19 05:30:07 2008
--- src/backend/executor/Makefile Mon Sep 22 11:16:32 2008
***************
*** 18,25 ****
nodeBitmapAnd.o nodeBitmapOr.o \
nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \
nodeHashjoin.o nodeIndexscan.o nodeMaterial.o nodeMergejoin.o \
! nodeNestloop.o nodeFunctionscan.o nodeResult.o nodeSeqscan.o \
! nodeSetOp.o nodeSort.o nodeUnique.o \
nodeValuesscan.o nodeLimit.o nodeGroup.o \
nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o tstoreReceiver.o spi.o
--- 18,25 ----
nodeBitmapAnd.o nodeBitmapOr.o \
nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \
nodeHashjoin.o nodeIndexscan.o nodeMaterial.o nodeMergejoin.o \
! nodeNestloop.o nodeFunctionscan.o nodeResult.o nodeRecursion.o \
! nodeRecursivescan.o nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
nodeValuesscan.o nodeLimit.o nodeGroup.o \
nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o tstoreReceiver.o spi.o
*** src/backend/executor/execAmi.c.orig Tue Aug 5 17:28:29 2008
--- src/backend/executor/execAmi.c Mon Sep 22 11:16:32 2008
***************
*** 30,35 ****
--- 30,37 ----
#include "executor/nodeMaterial.h"
#include "executor/nodeMergejoin.h"
#include "executor/nodeNestloop.h"
+ #include "executor/nodeRecursion.h"
+ #include "executor/nodeRecursivescan.h"
#include "executor/nodeResult.h"
#include "executor/nodeSeqscan.h"
#include "executor/nodeSetOp.h"
***************
*** 161,166 ****
--- 163,176 ----
ExecValuesReScan((ValuesScanState *) node, exprCtxt);
break;
+ case T_RecursionState:
+ ExecRecursionReScan((RecursionState *) node, exprCtxt);
+ break;
+
+ case T_RecursiveScanState:
+ ExecRecursiveScanReScan((RecursiveScanState *) node, exprCtxt);
+ break;
+
case T_NestLoopState:
ExecReScanNestLoop((NestLoopState *) node, exprCtxt);
break;
***************
*** 247,252 ****
--- 257,266 ----
ExecValuesMarkPos((ValuesScanState *) node);
break;
+ case T_RecursiveScanState:
+ ExecRecursiveScanMarkPos((RecursiveScanState *) node);
+ break;
+
case T_MaterialState:
ExecMaterialMarkPos((MaterialState *) node);
break;
***************
*** 304,309 ****
--- 318,327 ----
ExecValuesRestrPos((ValuesScanState *) node);
break;
+ case T_RecursiveScanState:
+ ExecRecursiveScanRestrPos((RecursiveScanState *) node);
+ break;
+
case T_MaterialState:
ExecMaterialRestrPos((MaterialState *) node);
break;
*** src/backend/executor/execProcnode.c.orig Tue Jan 1 14:45:49 2008
--- src/backend/executor/execProcnode.c Mon Sep 22 11:16:32 2008
***************
*** 94,99 ****
--- 94,101 ----
#include "executor/nodeMaterial.h"
#include "executor/nodeMergejoin.h"
#include "executor/nodeNestloop.h"
+ #include "executor/nodeRecursion.h"
+ #include "executor/nodeRecursivescan.h"
#include "executor/nodeResult.h"
#include "executor/nodeSeqscan.h"
#include "executor/nodeSetOp.h"
***************
*** 147,152 ****
--- 149,159 ----
estate, eflags);
break;
+ case T_Recursion:
+ result = (PlanState *) ExecInitRecursion((Recursion *) node,
+ estate, eflags);
+ break;
+
case T_BitmapAnd:
result = (PlanState *) ExecInitBitmapAnd((BitmapAnd *) node,
estate, eflags);
***************
*** 200,205 ****
--- 207,217 ----
estate, eflags);
break;
+ case T_RecursiveScan:
+ result = (PlanState *) ExecInitRecursiveScan((RecursiveScan *) node,
+ estate, eflags);
+ break;
+
/*
* join nodes
*/
***************
*** 323,328 ****
--- 335,344 ----
result = ExecAppend((AppendState *) node);
break;
+ case T_RecursionState:
+ result = ExecRecursion((RecursionState *) node);
+ break;
+
/* BitmapAndState does not yield tuples */
/* BitmapOrState does not yield tuples */
***************
*** 360,365 ****
--- 376,385 ----
result = ExecValuesScan((ValuesScanState *) node);
break;
+ case T_RecursiveScanState:
+ result = ExecRecursiveScan((RecursiveScanState *) node);
+ break;
+
/*
* join nodes
*/
***************
*** 507,512 ****
--- 527,535 ----
case T_BitmapOr:
return ExecCountSlotsBitmapOr((BitmapOr *) node);
+ case T_Recursion:
+ return ExecCountSlotsRecursion((Recursion *) node);
+
/*
* scan nodes
*/
***************
*** 534,539 ****
--- 557,565 ----
case T_ValuesScan:
return ExecCountSlotsValuesScan((ValuesScan *) node);
+ case T_RecursiveScan:
+ return ExecCountSlotsRecursiveScan((RecursiveScan *) node);
+
/*
* join nodes
*/
***************
*** 620,625 ****
--- 646,655 ----
ExecEndAppend((AppendState *) node);
break;
+ case T_RecursionState:
+ ExecEndRecursion((RecursionState *) node);
+ break;
+
case T_BitmapAndState:
ExecEndBitmapAnd((BitmapAndState *) node);
break;
***************
*** 663,668 ****
--- 693,702 ----
ExecEndValuesScan((ValuesScanState *) node);
break;
+ case T_RecursiveScanState:
+ ExecEndRecursiveScan((RecursiveScanState *) node);
+ break;
+
/*
* join nodes
*/
*** src/backend/executor/nodeAgg.c.orig Sun Sep 7 20:22:55 2008
--- src/backend/executor/nodeAgg.c Mon Sep 22 11:16:32 2008
***************
*** 1263,1268 ****
--- 1263,1269 ----
eflags &= ~EXEC_FLAG_REWIND;
outerPlan = outerPlan(node);
outerPlanState(aggstate) = ExecInitNode(outerPlan, estate, eflags);
+ aggstate->ss.ps.has_recursivescan = (outerPlanState(aggstate))->has_recursivescan;
/*
* initialize source tuple type.
***************
*** 1648,1658 ****
return;
/*
! * If we do have the hash table and the subplan does not have any
! * parameter changes, then we can just rescan the existing hash table;
! * no need to build it again.
*/
! if (((PlanState *) node)->lefttree->chgParam == NULL)
{
ResetTupleHashIterator(node->hashtable, &node->hashiter);
return;
--- 1649,1661 ----
return;
/*
! * If we do have the hash table and the subplan does not have
! * any parameter changes nor have Recursive Scan plan, then we
! * can just rescan the existing hash table; no need to build
! * it again.
*/
! if (((PlanState *) node)->lefttree->chgParam == NULL &&
! ((PlanState *) node)->lefttree->has_recursivescan == false)
{
ResetTupleHashIterator(node->hashtable, &node->hashiter);
return;
*** src/backend/executor/nodeAppend.c.orig Tue Jan 1 14:45:49 2008
--- src/backend/executor/nodeAppend.c Mon Sep 22 11:16:32 2008
***************
*** 60,67 ****
#include "executor/execdebug.h"
#include "executor/nodeAppend.h"
- static bool exec_append_initialize_next(AppendState *appendstate);
-
/* ----------------------------------------------------------------
* exec_append_initialize_next
--- 60,65 ----
***************
*** 71,77 ****
* Returns t iff there is a "next" scan to process.
* ----------------------------------------------------------------
*/
! static bool
exec_append_initialize_next(AppendState *appendstate)
{
EState *estate;
--- 69,75 ----
* Returns t iff there is a "next" scan to process.
* ----------------------------------------------------------------
*/
! bool
exec_append_initialize_next(AppendState *appendstate)
{
EState *estate;
***************
*** 145,154 ****
--- 143,156 ----
int nplans;
int i;
Plan *initNode;
+ TupleDesc save_tupledesc;
/* check for unsupported flags */
Assert(!(eflags & EXEC_FLAG_MARK));
+ /* Save tuple desc */
+ save_tupledesc = estate->es_rscan_tupledesc;
+
/*
* Set up empty vector of subplan states
*/
***************
*** 213,218 ****
--- 215,224 ----
initNode = (Plan *) list_nth(node->appendplans, i);
appendplanstates[i] = ExecInitNode(initNode, estate, eflags);
+
+ /* save result type for RecursiveScan plan node. */
+ if (i == appendstate->as_firstplan)
+ estate->es_rscan_tupledesc = ExecGetResultType(appendplanstates[i]);
}
/*
***************
*** 230,235 ****
--- 236,244 ----
appendstate->as_whichplan = appendstate->as_firstplan;
exec_append_initialize_next(appendstate);
+ /* Restore tuple desc */
+ estate->es_rscan_tupledesc = save_tupledesc;
+
return appendstate;
}
*** src/backend/executor/nodeHash.c.orig Tue Jan 1 14:45:49 2008
--- src/backend/executor/nodeHash.c Mon Sep 22 11:16:32 2008
***************
*** 169,174 ****
--- 169,175 ----
* initialize child nodes
*/
outerPlanState(hashstate) = ExecInitNode(outerPlan(node), estate, eflags);
+ hashstate->ps.has_recursivescan = (outerPlanState(hashstate))->has_recursivescan;
/*
* initialize tuple type. no need to initialize projection info because
*** src/backend/executor/nodeHashjoin.c.orig Fri Aug 15 15:20:42 2008
--- src/backend/executor/nodeHashjoin.c Mon Sep 22 11:16:32 2008
***************
*** 848,854 ****
if (node->hj_HashTable != NULL)
{
if (node->hj_HashTable->nbatch == 1 &&
! ((PlanState *) node)->righttree->chgParam == NULL)
{
/*
* okay to reuse the hash table; needn't rescan inner, either.
--- 848,855 ----
if (node->hj_HashTable != NULL)
{
if (node->hj_HashTable->nbatch == 1 &&
! ((PlanState *) node)->righttree->chgParam == NULL &&
! ((PlanState *) node)->righttree->has_recursivescan == false)
{
/*
* okay to reuse the hash table; needn't rescan inner, either.
*** src/backend/executor/nodeRecursion.c.orig Mon Sep 22 11:16:32 2008
--- src/backend/executor/nodeRecursion.c Mon Sep 22 11:16:32 2008
***************
*** 0 ****
--- 1,276 ----
+ /*-------------------------------------------------------------------------
+ *
+ * nodeRecursion.c
+ * routines to handle Recursion nodes.
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+
+ #include "postgres.h"
+
+ #include "executor/execdebug.h"
+ #include "executor/instrument.h"
+ #include "executor/nodeAppend.h"
+ #include "executor/nodeRecursion.h"
+ #include "miscadmin.h"
+
+ /* ----------------------------------------------------------------
+ * RecursionNext
+ *
+ * This is a workhorse for ExecRecursion
+ * ----------------------------------------------------------------
+ * *
+ * 1. evaluate non recursive term or partition depending on other
+ * partitions and assign the result to RT
+ *
+ * 2. execute recursive terms
+ *
+ * 2.1 WT := RT
+ * 2.2 while WT is not empty repeat 2.3 to 2.6. if WT is empty returns RT
+ * 2.3 replace the name of recursive term with WT
+ * 2.4 evaluate the recursive term and store into WT
+ * 2.5 append WT to RT
+ * 2.6 go back to 2.2
+ */
+ static TupleTableSlot *RecursionNext(RecursionState *node)
+ {
+ TupleTableSlot *slot;
+ AppendState *appendstate;
+ Tuplestorestate *tmp;
+
+ Assert(node->subplan->type == T_AppendState);
+
+ appendstate = (AppendState *) node->subplan;
+
+ /* 1. Evaluate non recursive plan */
+ while (appendstate->as_whichplan != appendstate->as_lastplan)
+ {
+ slot = ExecProcNode(appendstate->appendplans[appendstate->as_whichplan]);
+ if (!TupIsNull(slot))
+ {
+ tuplestore_puttupleslot(node->working_table, slot);
+ return slot;
+ }
+ appendstate->as_whichplan++;
+ if (!exec_append_initialize_next(appendstate))
+ return ExecClearTuple(appendstate->ps.ps_ResultTupleSlot);
+ }
+
+ retry:
+ /* 2. Execute recursive plan */
+ slot = ExecProcNode(appendstate->appendplans[appendstate->as_whichplan]);
+ if (TupIsNull(slot))
+ {
+ if (node->recursive_empty == false)
+ {
+ tmp = node->working_table;
+ node->working_table = node->intermediate_tuplestorestate;
+ node->intermediate_tuplestorestate = tmp;
+
+ /* Re-create intermediate table */
+ tuplestore_end(node->intermediate_tuplestorestate);
+ node->intermediate_tuplestorestate = tuplestore_begin_heap(true, false, 0);
+
+ node->recursive_empty = true;
+ ExecReScan(appendstate->appendplans[appendstate->as_whichplan], NULL);
+ goto retry;
+ }
+ else if (!exec_append_initialize_next(appendstate))
+ return ExecClearTuple(appendstate->ps.ps_ResultTupleSlot);
+ }
+
+ else
+ {
+ node->recursive_empty = false;
+ tuplestore_puttupleslot(node->intermediate_tuplestorestate, slot);
+ }
+
+ return slot;
+ }
+
+ /* ----------------------------------------------------------------
+ * ExecRecursion(node)
+ *
+ * Scans the recursive query sequentially and returns the next
+ * qualifying tuple.
+ * It calls the ExecScan() routine and passes it the access method
+ * which retrieve tuples sequentially.
+ *
+ */
+ TupleTableSlot *
+ ExecRecursion(RecursionState *node)
+ {
+ /*
+ * use RecursivescanNext as access method
+ */
+ return ExecScan(&node->ss, (ExecScanAccessMtd) RecursionNext);
+ }
+
+ /* ----------------------------------------------------------------
+ * ExecInitRecursionScan
+ * ----------------------------------------------------------------
+ */
+ RecursionState *
+ ExecInitRecursion(Recursion *node, EState *estate, int eflags)
+ {
+ RecursionState *recursionstate;
+ Tuplestorestate **save_tuplestorestate;
+
+ /* check for unsupported flags */
+ Assert(!(eflags & EXEC_FLAG_MARK));
+
+ /*
+ * SubqueryScan should not have any "normal" children. Also, if planner
+ * left anything in subrtable, it's fishy.
+ */
+ Assert(outerPlan(node) == NULL);
+ Assert(innerPlan(node) == NULL);
+ Assert(node->subrtable == NIL);
+
+ /*
+ * create state structure
+ */
+ recursionstate = makeNode(RecursionState);
+ recursionstate->ss.ps.plan = (Plan *) node;
+ recursionstate->ss.ps.state = estate;
+ recursionstate->recursive_empty = true;
+ recursionstate->working_table = tuplestore_begin_heap(true, false, work_mem);
+ recursionstate->intermediate_tuplestorestate = tuplestore_begin_heap(true, false, work_mem);
+
+ /*
+ * Save the reference for the working table to share
+ */
+ save_tuplestorestate = estate->es_tuplestorestate;
+ estate->es_tuplestorestate = &recursionstate->working_table;
+
+ /*
+ * Miscellaneous initialization
+ *
+ * create expression context for node
+ */
+ ExecAssignExprContext(estate, &recursionstate->ss.ps);
+
+ /*
+ * initialize child expressions
+ */
+ recursionstate->ss.ps.targetlist = (List *)
+ ExecInitExpr((Expr *) node->scan.plan.targetlist,
+ (PlanState *) recursionstate);
+ recursionstate->ss.ps.qual = (List *)
+ ExecInitExpr((Expr *) node->scan.plan.qual,
+ (PlanState *) recursionstate);
+
+ #define RECURSION_NSLOTS 2
+
+ /*
+ * tuple table initialization
+ */
+ ExecInitResultTupleSlot(estate, &recursionstate->ss.ps);
+ ExecInitScanTupleSlot(estate, &recursionstate->ss);
+
+ /*
+ * initialize subquery
+ */
+ recursionstate->subplan = ExecInitNode(node->subplan, estate, eflags);
+
+ recursionstate->ss.ps.ps_TupFromTlist = false;
+
+ /*
+ * Initialize scan tuple type (needed by ExecAssignScanProjectionInfo)
+ */
+ ExecAssignScanType(&recursionstate->ss,
+ ExecGetResultType(recursionstate->subplan));
+
+ /*
+ * Initialize result tuple type and projection info.
+ */
+ ExecAssignResultTypeFromTL(&recursionstate->ss.ps);
+ ExecAssignScanProjectionInfo(&recursionstate->ss);
+
+ /* Restore tuplestore */
+ estate->es_tuplestorestate = save_tuplestorestate;
+
+ return recursionstate;
+ }
+
+ int
+ ExecCountSlotsRecursion(Recursion *node)
+ {
+ Assert(outerPlan(node) == NULL);
+ Assert(innerPlan(node) == NULL);
+ return ExecCountSlotsNode(node->subplan) +
+ RECURSION_NSLOTS;
+ }
+
+ /* ----------------------------------------------------------------
+ * ExecEndRecursionScan
+ *
+ * frees any storage allocated through C routines.
+ * ----------------------------------------------------------------
+ */
+ void
+ ExecEndRecursion(RecursionState *node)
+ {
+ if (node->intermediate_tuplestorestate != NULL)
+ tuplestore_end(node->intermediate_tuplestorestate);
+ node->intermediate_tuplestorestate = NULL;
+
+ if (node->working_table != NULL)
+ tuplestore_end(node->working_table);
+ node->working_table = NULL;
+
+ /*
+ * Free the exprcontext
+ */
+ ExecFreeExprContext(&node->ss.ps);
+
+ /*
+ * clean out the upper tuple table
+ */
+ ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+ node->ss.ss_ScanTupleSlot = NULL; /* not ours to clear */
+
+ /*
+ * close down subquery
+ */
+ ExecEndNode(node->subplan);
+ }
+
+ /* ----------------------------------------------------------------
+ * ExecRecursionReScan
+ *
+ * Rescans the relation.
+ * ----------------------------------------------------------------
+ */
+ void
+ ExecRecursionReScan(RecursionState *node, ExprContext *exprCtxt)
+ {
+ EState *estate;
+
+ estate = node->ss.ps.state;
+
+ /*
+ * ExecReScan doesn't know about my subplan, so I have to do
+ * changed-parameter signaling myself. This is just as well, because the
+ * subplan has its own memory context in which its chgParam state lives.
+ */
+ if (node->ss.ps.chgParam != NULL)
+ UpdateChangedParamSet(node->subplan, node->ss.ps.chgParam);
+
+ /*
+ * if chgParam of subnode is not null then plan will be re-scanned by
+ * first ExecProcNode.
+ */
+ if (node->subplan->chgParam == NULL)
+ ExecReScan(node->subplan, NULL);
+
+ node->ss.ss_ScanTupleSlot = NULL;
+ node->ss.ps.ps_TupFromTlist = false;
+ }
*** src/backend/executor/nodeRecursivescan.c.orig Mon Sep 22 11:16:32 2008
--- src/backend/executor/nodeRecursivescan.c Mon Sep 22 11:16:32 2008
***************
*** 0 ****
--- 1,194 ----
+ /*-------------------------------------------------------------------------
+ *
+ * nodeRecursivescan.c
+ * routines to handle RecursiveScan nodes.
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+
+ #include "postgres.h"
+
+ #include "executor/execdebug.h"
+ #include "executor/executor.h"
+ #include "executor/instrument.h"
+ #include "executor/nodeRecursivescan.h"
+ #include "miscadmin.h"
+ #include "utils/memutils.h"
+
+ static TupleTableSlot *RecursivescanNext(RecursiveScanState *node);
+
+ static TupleTableSlot *
+ RecursivescanNext(RecursiveScanState *node)
+ {
+ Tuplestorestate *tuplestorestate;
+ TupleTableSlot *slot;
+
+ tuplestorestate = *node->working_table;
+
+ slot = node->ss.ss_ScanTupleSlot;
+ if (tuplestore_gettupleslot(tuplestorestate, true, slot))
+ return slot;
+
+ return NULL;
+ }
+
+ TupleTableSlot *
+ ExecRecursiveScan(RecursiveScanState *node)
+ {
+ /*
+ * use RecursivescanNext as access method
+ */
+ return ExecScan(&node->ss, (ExecScanAccessMtd) RecursivescanNext);
+ }
+
+
+ /* ----------------------------------------------------------------
+ * ExecInitRecursiveScan
+ * ----------------------------------------------------------------
+ */
+ RecursiveScanState *
+ ExecInitRecursiveScan(RecursiveScan *node, EState *estate, int eflags)
+ {
+ RecursiveScanState *scanstate;
+
+ /*
+ * create new RecursiveScanState for node
+ */
+ scanstate = makeNode(RecursiveScanState);
+ scanstate->ss.ps.plan = (Plan *) node;
+ scanstate->ss.ps.state = estate;
+ scanstate->ss.ps.has_recursivescan = true;
+ scanstate->working_table = estate->es_tuplestorestate;
+
+ /*
+ * Miscellaneous initialization
+ *
+ * create expression context for node
+ */
+ ExecAssignExprContext(estate, &scanstate->ss.ps);
+
+ /*
+ * initialize child expressions
+ */
+ scanstate->ss.ps.targetlist = (List *)
+ ExecInitExpr((Expr *) node->scan.plan.targetlist,
+ (PlanState *) scanstate);
+ scanstate->ss.ps.qual = (List *)
+ ExecInitExpr((Expr *) node->scan.plan.qual,
+ (PlanState *) scanstate);
+
+ #define RECURSIVESCAN_NSLOTS 2
+
+ /*
+ * tuple table initialization
+ */
+ ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
+ ExecInitScanTupleSlot(estate, &scanstate->ss);
+
+ scanstate->ss.ps.ps_TupFromTlist = false;
+
+ /*
+ * Do not initialize scan tuple type, result tuple type and
+ * projection info in ExecInitRecursivescan. These types are
+ * initialized after initializing Recursion node.
+ */
+ ExecAssignScanType(&scanstate->ss,
+ estate->es_rscan_tupledesc);
+
+ /*
+ * Initialize result tuple type and projection info.
+ */
+ ExecAssignResultTypeFromTL(&scanstate->ss.ps);
+ ExecAssignScanProjectionInfo(&scanstate->ss);
+
+ return scanstate;
+ }
+
+ int
+ ExecCountSlotsRecursiveScan(RecursiveScan *node)
+ {
+ return ExecCountSlotsNode(outerPlan(node)) +
+ ExecCountSlotsNode(innerPlan(node)) +
+ RECURSIVESCAN_NSLOTS;
+ }
+
+ /* ----------------------------------------------------------------
+ * ExecEndRecursiveScan
+ *
+ * frees any storage allocated through C routines.
+ * ----------------------------------------------------------------
+ */
+ void
+ ExecEndRecursiveScan(RecursiveScanState *node)
+ {
+ /*
+ * Free both exprcontexts
+ */
+ ExecFreeExprContext(&node->ss.ps);
+ ExecFreeExprContext(&node->ss.ps);
+
+ /*
+ * clean out the tuple table
+ */
+ ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+ ExecClearTuple(node->ss.ss_ScanTupleSlot);
+ }
+
+ /* ----------------------------------------------------------------
+ * ExecRecursiveScanMarkPos
+ *
+ * Marks scan position.
+ * ----------------------------------------------------------------
+ */
+ void
+ ExecRecursiveScanMarkPos(RecursiveScanState *node)
+ {
+ /*
+ * if we haven't materialized yet, just return.
+ */
+ if (!(*node->working_table))
+ return;
+
+ tuplestore_markpos(*node->working_table);
+ }
+
+ /* ----------------------------------------------------------------
+ * ExecRecursiveScanRestrPos
+ *
+ * Restores scan position.
+ * ----------------------------------------------------------------
+ */
+ void
+ ExecRecursiveScanRestrPos(RecursiveScanState *node)
+ {
+ /*
+ * if we haven't materialized yet, just return.
+ */
+ if (!(*node->working_table))
+ return;
+
+ /*
+ * restore the scan to the previously marked position
+ */
+ tuplestore_restorepos(*node->working_table);
+ }
+
+ /* ----------------------------------------------------------------
+ * ExecRecursiveScanReScan
+ *
+ * Rescans the relation.
+ * ----------------------------------------------------------------
+ */
+ void
+ ExecRecursiveScanReScan(RecursiveScanState *node, ExprContext *exprCtxt)
+ {
+ ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+ tuplestore_rescan(*node->working_table);
+ }
*** src/backend/nodes/copyfuncs.c.orig Tue Sep 9 14:58:08 2008
--- src/backend/nodes/copyfuncs.c Tue Sep 23 12:03:15 2008
***************
*** 422,427 ****
--- 422,465 ----
}
/*
+ * _copyRecursion
+ */
+ static Recursion *
+ _copyRecursion(Recursion *from)
+ {
+ Recursion *newnode = makeNode(Recursion);
+
+ /*
+ * copy node superclass fields
+ */
+ CopyScanFields((Scan *) from, (Scan *) newnode);
+
+ /*
+ * copy remainder of node
+ */
+ COPY_NODE_FIELD(subplan);
+ COPY_NODE_FIELD(subrtable);
+
+ return newnode;
+ }
+
+ /*
+ * _copyRecursiveScan
+ */
+ static RecursiveScan *
+ _copyRecursiveScan(RecursiveScan *from)
+ {
+ RecursiveScan *newnode = makeNode(RecursiveScan);
+
+ /*
+ * copy node superclass fields
+ */
+ CopyScanFields((Scan *) from, (Scan *) newnode);
+
+ return newnode;
+ }
+
+ /*
* CopyJoinFields
*
* This function copies the fields of the Join node. It is used by
***************
*** 1572,1583 ****
COPY_SCALAR_FIELD(rtekind);
COPY_SCALAR_FIELD(relid);
COPY_NODE_FIELD(subquery);
COPY_NODE_FIELD(funcexpr);
COPY_NODE_FIELD(funccoltypes);
COPY_NODE_FIELD(funccoltypmods);
COPY_NODE_FIELD(values_lists);
! COPY_SCALAR_FIELD(jointype);
! COPY_NODE_FIELD(joinaliasvars);
COPY_NODE_FIELD(alias);
COPY_NODE_FIELD(eref);
COPY_SCALAR_FIELD(inh);
--- 1610,1626 ----
COPY_SCALAR_FIELD(rtekind);
COPY_SCALAR_FIELD(relid);
COPY_NODE_FIELD(subquery);
+ COPY_SCALAR_FIELD(jointype);
+ COPY_NODE_FIELD(joinaliasvars);
COPY_NODE_FIELD(funcexpr);
COPY_NODE_FIELD(funccoltypes);
COPY_NODE_FIELD(funccoltypmods);
COPY_NODE_FIELD(values_lists);
! COPY_STRING_FIELD(ctename);
! COPY_SCALAR_FIELD(ctelevelsup);
! COPY_SCALAR_FIELD(self_reference);
! COPY_NODE_FIELD(ctecoltypes);
! COPY_NODE_FIELD(ctecoltypmods);
COPY_NODE_FIELD(alias);
COPY_NODE_FIELD(eref);
COPY_SCALAR_FIELD(inh);
***************
*** 1632,1637 ****
--- 1675,1709 ----
return newnode;
}
+ static WithClause *
+ _copyWithClause(WithClause *from)
+ {
+ WithClause *newnode = makeNode(WithClause);
+
+ COPY_NODE_FIELD(ctes);
+ COPY_SCALAR_FIELD(recursive);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+ }
+
+ static CommonTableExpr *
+ _copyCommonTableExpr(CommonTableExpr *from)
+ {
+ CommonTableExpr *newnode = makeNode(CommonTableExpr);
+
+ COPY_STRING_FIELD(ctename);
+ COPY_NODE_FIELD(aliascolnames);
+ COPY_NODE_FIELD(ctequery);
+ COPY_LOCATION_FIELD(location);
+ COPY_SCALAR_FIELD(cterecursive);
+ COPY_NODE_FIELD(ctecolnames);
+ COPY_NODE_FIELD(ctecoltypes);
+ COPY_NODE_FIELD(ctecoltypmods);
+
+ return newnode;
+ }
+
static A_Expr *
_copyAExpr(A_Expr *from)
{
***************
*** 1931,1936 ****
--- 2003,2010 ----
COPY_SCALAR_FIELD(hasAggs);
COPY_SCALAR_FIELD(hasSubLinks);
COPY_SCALAR_FIELD(hasDistinctOn);
+ COPY_SCALAR_FIELD(hasRecursive);
+ COPY_NODE_FIELD(cteList);
COPY_NODE_FIELD(rtable);
COPY_NODE_FIELD(jointree);
COPY_NODE_FIELD(targetList);
***************
*** 1999,2004 ****
--- 2073,2079 ----
COPY_NODE_FIELD(whereClause);
COPY_NODE_FIELD(groupClause);
COPY_NODE_FIELD(havingClause);
+ COPY_NODE_FIELD(withClause);
COPY_NODE_FIELD(valuesLists);
COPY_NODE_FIELD(sortClause);
COPY_NODE_FIELD(limitOffset);
***************
*** 3104,3109 ****
--- 3179,3187 ----
case T_Append:
retval = _copyAppend(from);
break;
+ case T_Recursion:
+ retval = _copyRecursion(from);
+ break;
case T_BitmapAnd:
retval = _copyBitmapAnd(from);
break;
***************
*** 3137,3142 ****
--- 3215,3223 ----
case T_ValuesScan:
retval = _copyValuesScan(from);
break;
+ case T_RecursiveScan:
+ retval = _copyRecursiveScan(from);
+ break;
case T_Join:
retval = _copyJoin(from);
break;
***************
*** 3672,3677 ****
--- 3753,3764 ----
case T_RowMarkClause:
retval = _copyRowMarkClause(from);
break;
+ case T_WithClause:
+ retval = _copyWithClause(from);
+ break;
+ case T_CommonTableExpr:
+ retval = _copyCommonTableExpr(from);
+ break;
case T_FkConstraint:
retval = _copyFkConstraint(from);
break;
*** src/backend/nodes/equalfuncs.c.orig Mon Sep 1 16:42:44 2008
--- src/backend/nodes/equalfuncs.c Tue Sep 23 12:03:15 2008
***************
*** 808,813 ****
--- 808,815 ----
COMPARE_SCALAR_FIELD(hasAggs);
COMPARE_SCALAR_FIELD(hasSubLinks);
COMPARE_SCALAR_FIELD(hasDistinctOn);
+ COMPARE_SCALAR_FIELD(hasRecursive);
+ COMPARE_NODE_FIELD(cteList);
COMPARE_NODE_FIELD(rtable);
COMPARE_NODE_FIELD(jointree);
COMPARE_NODE_FIELD(targetList);
***************
*** 868,873 ****
--- 870,876 ----
COMPARE_NODE_FIELD(whereClause);
COMPARE_NODE_FIELD(groupClause);
COMPARE_NODE_FIELD(havingClause);
+ COMPARE_NODE_FIELD(withClause);
COMPARE_NODE_FIELD(valuesLists);
COMPARE_NODE_FIELD(sortClause);
COMPARE_NODE_FIELD(limitOffset);
***************
*** 1932,1943 ****
COMPARE_SCALAR_FIELD(rtekind);
COMPARE_SCALAR_FIELD(relid);
COMPARE_NODE_FIELD(subquery);
COMPARE_NODE_FIELD(funcexpr);
COMPARE_NODE_FIELD(funccoltypes);
COMPARE_NODE_FIELD(funccoltypmods);
COMPARE_NODE_FIELD(values_lists);
! COMPARE_SCALAR_FIELD(jointype);
! COMPARE_NODE_FIELD(joinaliasvars);
COMPARE_NODE_FIELD(alias);
COMPARE_NODE_FIELD(eref);
COMPARE_SCALAR_FIELD(inh);
--- 1935,1951 ----
COMPARE_SCALAR_FIELD(rtekind);
COMPARE_SCALAR_FIELD(relid);
COMPARE_NODE_FIELD(subquery);
+ COMPARE_SCALAR_FIELD(jointype);
+ COMPARE_NODE_FIELD(joinaliasvars);
COMPARE_NODE_FIELD(funcexpr);
COMPARE_NODE_FIELD(funccoltypes);
COMPARE_NODE_FIELD(funccoltypmods);
COMPARE_NODE_FIELD(values_lists);
! COMPARE_STRING_FIELD(ctename);
! COMPARE_SCALAR_FIELD(ctelevelsup);
! COMPARE_SCALAR_FIELD(self_reference);
! COMPARE_NODE_FIELD(ctecoltypes);
! COMPARE_NODE_FIELD(ctecoltypmods);
COMPARE_NODE_FIELD(alias);
COMPARE_NODE_FIELD(eref);
COMPARE_SCALAR_FIELD(inh);
***************
*** 1970,1975 ****
--- 1978,2008 ----
}
static bool
+ _equalWithClause(WithClause *a, WithClause *b)
+ {
+ COMPARE_NODE_FIELD(ctes);
+ COMPARE_SCALAR_FIELD(recursive);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+ }
+
+ static bool
+ _equalCommonTableExpr(CommonTableExpr *a, CommonTableExpr *b)
+ {
+ COMPARE_STRING_FIELD(ctename);
+ COMPARE_NODE_FIELD(aliascolnames);
+ COMPARE_NODE_FIELD(ctequery);
+ COMPARE_LOCATION_FIELD(location);
+ COMPARE_SCALAR_FIELD(cterecursive);
+ COMPARE_NODE_FIELD(ctecolnames);
+ COMPARE_NODE_FIELD(ctecoltypes);
+ COMPARE_NODE_FIELD(ctecoltypmods);
+
+ return true;
+ }
+
+ static bool
_equalFkConstraint(FkConstraint *a, FkConstraint *b)
{
COMPARE_STRING_FIELD(constr_name);
***************
*** 2593,2598 ****
--- 2626,2637 ----
case T_RowMarkClause:
retval = _equalRowMarkClause(a, b);
break;
+ case T_WithClause:
+ retval = _equalWithClause(a, b);
+ break;
+ case T_CommonTableExpr:
+ retval = _equalCommonTableExpr(a, b);
+ break;
case T_FkConstraint:
retval = _equalFkConstraint(a, b);
break;
*** src/backend/nodes/nodeFuncs.c.orig Mon Sep 1 16:42:44 2008
--- src/backend/nodes/nodeFuncs.c Tue Sep 23 20:00:39 2008
***************
*** 870,875 ****
--- 870,881 ----
/* XMLSERIALIZE keyword should always be the first thing */
loc = ((XmlSerialize *) expr)->location;
break;
+ case T_WithClause:
+ loc = ((WithClause *) expr)->location;
+ break;
+ case T_CommonTableExpr:
+ loc = ((CommonTableExpr *) expr)->location;
+ break;
default:
/* for any other node type it's just unknown... */
loc = -1;
***************
*** 1205,1210 ****
--- 1211,1227 ----
case T_Query:
/* Do nothing with a sub-Query, per discussion above */
break;
+ case T_CommonTableExpr:
+ {
+ CommonTableExpr *cte = (CommonTableExpr *) node;
+
+ /*
+ * Invoke the walker on the CTE's Query node, so it
+ * can recurse into the sub-query if it wants to.
+ */
+ return walker(cte->ctequery, context);
+ }
+ break;
case T_List:
foreach(temp, (List *) node)
{
***************
*** 1313,1318 ****
--- 1330,1340 ----
return true;
if (walker(query->limitCount, context))
return true;
+ if (!(flags & QTW_IGNORE_CTE_SUBQUERIES))
+ {
+ if (walker((Node *) query->cteList, context))
+ return true;
+ }
if (range_table_walker(query->rtable, walker, context, flags))
return true;
return false;
***************
*** 1335,1344 ****
--- 1357,1372 ----
{
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
+ /* For historical reasons, visiting RTEs is not the default */
+ if (flags & QTW_EXAMINE_RTES)
+ if (walker(rte, context))
+ return true;
+
switch (rte->rtekind)
{
case RTE_RELATION:
case RTE_SPECIAL:
+ case RTE_CTE:
/* nothing to do */
break;
case RTE_SUBQUERY:
***************
*** 1806,1811 ****
--- 1834,1854 ----
case T_Query:
/* Do nothing with a sub-Query, per discussion above */
return node;
+ case T_CommonTableExpr:
+ {
+ CommonTableExpr *cte = (CommonTableExpr *) node;
+ CommonTableExpr *newnode;
+
+ FLATCOPY(newnode, cte, CommonTableExpr);
+
+ /*
+ * Also invoke the mutator on the CTE's Query node, so it
+ * can recurse into the sub-query if it wants to.
+ */
+ MUTATE(newnode->ctequery, cte->ctequery, Node *);
+ return (Node *) newnode;
+ }
+ break;
case T_List:
{
/*
***************
*** 1935,1940 ****
--- 1978,1987 ----
MUTATE(query->havingQual, query->havingQual, Node *);
MUTATE(query->limitOffset, query->limitOffset, Node *);
MUTATE(query->limitCount, query->limitCount, Node *);
+ if (!(flags & QTW_IGNORE_CTE_SUBQUERIES))
+ MUTATE(query->cteList, query->cteList, List *);
+ else /* else copy CTE list as-is */
+ query->cteList = copyObject(query->cteList);
query->rtable = range_table_mutator(query->rtable,
mutator, context, flags);
return query;
***************
*** 1964,1969 ****
--- 2011,2017 ----
{
case RTE_RELATION:
case RTE_SPECIAL:
+ case RTE_CTE:
/* we don't bother to copy eref, aliases, etc; OK? */
break;
case RTE_SUBQUERY:
***************
*** 2044,2046 ****
--- 2092,2395 ----
else
return mutator(node, context);
}
+
+
+ /*
+ * raw_expression_tree_walker --- walk raw parse trees
+ *
+ * This has exactly the same API as expression_tree_walker, but instead of
+ * walking post-analysis parse trees, it knows how to walk the node types
+ * found in raw grammar output. (There is not currently any need for a
+ * combined walker, so we keep them separate in the name of efficiency.)
+ * Unlike expression_tree_walker, there is no special rule about query
+ * boundaries: we descend to everything that's possibly interesting.
+ *
+ * Currently, the node type coverage extends to SelectStmt and everything
+ * that could appear under it, but not other statement types.
+ */
+ bool
+ raw_expression_tree_walker(Node *node, bool (*walker) (), void *context)
+ {
+ ListCell *temp;
+
+ /*
+ * The walker has already visited the current node, and so we need only
+ * recurse into any sub-nodes it has.
+ */
+ if (node == NULL)
+ return false;
+
+ /* Guard against stack overflow due to overly complex expressions */
+ check_stack_depth();
+
+ switch (nodeTag(node))
+ {
+ case T_SetToDefault:
+ case T_CurrentOfExpr:
+ case T_Integer:
+ case T_Float:
+ case T_String:
+ case T_BitString:
+ case T_Null:
+ case T_ParamRef:
+ case T_A_Const:
+ case T_A_Star:
+ /* primitive node types with no subnodes */
+ break;
+ case T_Alias:
+ /* we assume the colnames list isn't interesting */
+ break;
+ case T_RangeVar:
+ return walker(((RangeVar *) node)->alias, context);
+ case T_SubLink:
+ {
+ SubLink *sublink = (SubLink *) node;
+
+ if (walker(sublink->testexpr, context))
+ return true;
+ /* we assume the operName is not interesting */
+ if (walker(sublink->subselect, context))
+ return true;
+ }
+ break;
+ case T_CaseExpr:
+ {
+ CaseExpr *caseexpr = (CaseExpr *) node;
+
+ if (walker(caseexpr->arg, context))
+ return true;
+ /* we assume walker doesn't care about CaseWhens, either */
+ foreach(temp, caseexpr->args)
+ {
+ CaseWhen *when = (CaseWhen *) lfirst(temp);
+
+ Assert(IsA(when, CaseWhen));
+ if (walker(when->expr, context))
+ return true;
+ if (walker(when->result, context))
+ return true;
+ }
+ if (walker(caseexpr->defresult, context))
+ return true;
+ }
+ break;
+ case T_RowExpr:
+ return walker(((RowExpr *) node)->args, context);
+ case T_CoalesceExpr:
+ return walker(((CoalesceExpr *) node)->args, context);
+ case T_MinMaxExpr:
+ return walker(((MinMaxExpr *) node)->args, context);
+ case T_XmlExpr:
+ {
+ XmlExpr *xexpr = (XmlExpr *) node;
+
+ if (walker(xexpr->named_args, context))
+ return true;
+ /* we assume walker doesn't care about arg_names */
+ if (walker(xexpr->args, context))
+ return true;
+ }
+ break;
+ case T_NullTest:
+ return walker(((NullTest *) node)->arg, context);
+ case T_BooleanTest:
+ return walker(((BooleanTest *) node)->arg, context);
+ case T_JoinExpr:
+ {
+ JoinExpr *join = (JoinExpr *) node;
+
+ if (walker(join->larg, context))
+ return true;
+ if (walker(join->rarg, context))
+ return true;
+ if (walker(join->quals, context))
+ return true;
+ if (walker(join->alias, context))
+ return true;
+ /* using list is deemed uninteresting */
+ }
+ break;
+ case T_IntoClause:
+ {
+ IntoClause *into = (IntoClause *) node;
+
+ if (walker(into->rel, context))
+ return true;
+ /* colNames, options are deemed uninteresting */
+ }
+ break;
+ case T_List:
+ foreach(temp, (List *) node)
+ {
+ if (walker((Node *) lfirst(temp), context))
+ return true;
+ }
+ break;
+ case T_SelectStmt:
+ {
+ SelectStmt *stmt = (SelectStmt *) node;
+
+ if (walker(stmt->distinctClause, context))
+ return true;
+ if (walker(stmt->intoClause, context))
+ return true;
+ if (walker(stmt->targetList, context))
+ return true;
+ if (walker(stmt->fromClause, context))
+ return true;
+ if (walker(stmt->whereClause, context))
+ return true;
+ if (walker(stmt->groupClause, context))
+ return true;
+ if (walker(stmt->havingClause, context))
+ return true;
+ if (walker(stmt->withClause, context))
+ return true;
+ if (walker(stmt->valuesLists, context))
+ return true;
+ if (walker(stmt->sortClause, context))
+ return true;
+ if (walker(stmt->limitOffset, context))
+ return true;
+ if (walker(stmt->limitCount, context))
+ return true;
+ if (walker(stmt->lockingClause, context))
+ return true;
+ if (walker(stmt->larg, context))
+ return true;
+ if (walker(stmt->rarg, context))
+ return true;
+ }
+ break;
+ case T_A_Expr:
+ {
+ A_Expr *expr = (A_Expr *) node;
+
+ if (walker(expr->lexpr, context))
+ return true;
+ if (walker(expr->rexpr, context))
+ return true;
+ /* operator name is deemed uninteresting */
+ }
+ break;
+ case T_ColumnRef:
+ /* we assume the fields contain nothing interesting */
+ break;
+ case T_FuncCall:
+ {
+ FuncCall *fcall = (FuncCall *) node;
+
+ if (walker(fcall->args, context))
+ return true;
+ /* function name is deemed uninteresting */
+ }
+ break;
+ case T_A_Indices:
+ {
+ A_Indices *indices = (A_Indices *) node;
+
+ if (walker(indices->lidx, context))
+ return true;
+ if (walker(indices->uidx, context))
+ return true;
+ }
+ break;
+ case T_A_Indirection:
+ {
+ A_Indirection *indir = (A_Indirection *) node;
+
+ if (walker(indir->arg, context))
+ return true;
+ if (walker(indir->indirection, context))
+ return true;
+ }
+ break;
+ case T_A_ArrayExpr:
+ return walker(((A_ArrayExpr *) node)->elements, context);
+ case T_ResTarget:
+ {
+ ResTarget *rt = (ResTarget *) node;
+
+ if (walker(rt->indirection, context))
+ return true;
+ if (walker(rt->val, context))
+ return true;
+ }
+ break;
+ case T_TypeCast:
+ {
+ TypeCast *tc = (TypeCast *) node;
+
+ if (walker(tc->arg, context))
+ return true;
+ if (walker(tc->typename, context))
+ return true;
+ }
+ break;
+ case T_SortBy:
+ return walker(((SortBy *) node)->node, context);
+ case T_RangeSubselect:
+ {
+ RangeSubselect *rs = (RangeSubselect *) node;
+
+ if (walker(rs->subquery, context))
+ return true;
+ if (walker(rs->alias, context))
+ return true;
+ }
+ break;
+ case T_RangeFunction:
+ {
+ RangeFunction *rf = (RangeFunction *) node;
+
+ if (walker(rf->funccallnode, context))
+ return true;
+ if (walker(rf->alias, context))
+ return true;
+ }
+ break;
+ case T_TypeName:
+ {
+ TypeName *tn = (TypeName *) node;
+
+ if (walker(tn->typmods, context))
+ return true;
+ if (walker(tn->arrayBounds, context))
+ return true;
+ /* type name itself is deemed uninteresting */
+ }
+ break;
+ case T_ColumnDef:
+ {
+ ColumnDef *coldef = (ColumnDef *) node;
+
+ if (walker(coldef->typename, context))
+ return true;
+ if (walker(coldef->raw_default, context))
+ return true;
+ /* for now, constraints are ignored */
+ }
+ break;
+ case T_LockingClause:
+ return walker(((LockingClause *) node)->lockedRels, context);
+ case T_XmlSerialize:
+ {
+ XmlSerialize *xs = (XmlSerialize *) node;
+
+ if (walker(xs->expr, context))
+ return true;
+ if (walker(xs->typename, context))
+ return true;
+ }
+ break;
+ case T_WithClause:
+ return walker(((WithClause *) node)->ctes, context);
+ case T_CommonTableExpr:
+ return walker(((CommonTableExpr *) node)->ctequery, context);
+ default:
+ elog(ERROR, "unrecognized node type: %d",
+ (int) nodeTag(node));
+ break;
+ }
+ return false;
+ }
*** src/backend/nodes/outfuncs.c.orig Tue Sep 9 14:58:08 2008
--- src/backend/nodes/outfuncs.c Tue Sep 23 12:03:16 2008
***************
*** 447,452 ****
--- 447,471 ----
}
static void
+ _outRecursion(StringInfo str, Recursion *node)
+ {
+ WRITE_NODE_TYPE("RECURSION");
+
+ _outScanInfo(str, (Scan *) node);
+
+ WRITE_NODE_FIELD(subplan);
+ WRITE_NODE_FIELD(subrtable);
+ }
+
+ static void
+ _outRecursiveScan(StringInfo str, RecursiveScan *node)
+ {
+ WRITE_NODE_TYPE("RECURSIVESCAN");
+
+ _outScanInfo(str, (Scan *) node);
+ }
+
+ static void
_outJoin(StringInfo str, Join *node)
{
WRITE_NODE_TYPE("JOIN");
***************
*** 1648,1653 ****
--- 1667,1673 ----
WRITE_NODE_FIELD(whereClause);
WRITE_NODE_FIELD(groupClause);
WRITE_NODE_FIELD(havingClause);
+ WRITE_NODE_FIELD(withClause);
WRITE_NODE_FIELD(valuesLists);
WRITE_NODE_FIELD(sortClause);
WRITE_NODE_FIELD(limitOffset);
***************
*** 1793,1798 ****
--- 1813,1820 ----
WRITE_BOOL_FIELD(hasAggs);
WRITE_BOOL_FIELD(hasSubLinks);
WRITE_BOOL_FIELD(hasDistinctOn);
+ WRITE_BOOL_FIELD(hasRecursive);
+ WRITE_NODE_FIELD(cteList);
WRITE_NODE_FIELD(rtable);
WRITE_NODE_FIELD(jointree);
WRITE_NODE_FIELD(targetList);
***************
*** 1829,1834 ****
--- 1851,1881 ----
}
static void
+ _outWithClause(StringInfo str, WithClause *node)
+ {
+ WRITE_NODE_TYPE("WITHCLAUSE");
+
+ WRITE_NODE_FIELD(ctes);
+ WRITE_BOOL_FIELD(recursive);
+ WRITE_LOCATION_FIELD(location);
+ }
+
+ static void
+ _outCommonTableExpr(StringInfo str, CommonTableExpr *node)
+ {
+ WRITE_NODE_TYPE("COMMONTABLEEXPR");
+
+ WRITE_STRING_FIELD(ctename);
+ WRITE_NODE_FIELD(aliascolnames);
+ WRITE_NODE_FIELD(ctequery);
+ WRITE_LOCATION_FIELD(location);
+ WRITE_BOOL_FIELD(cterecursive);
+ WRITE_NODE_FIELD(ctecolnames);
+ WRITE_NODE_FIELD(ctecoltypes);
+ WRITE_NODE_FIELD(ctecoltypmods);
+ }
+
+ static void
_outSetOperationStmt(StringInfo str, SetOperationStmt *node)
{
WRITE_NODE_TYPE("SETOPERATIONSTMT");
***************
*** 1861,1866 ****
--- 1908,1917 ----
case RTE_SUBQUERY:
WRITE_NODE_FIELD(subquery);
break;
+ case RTE_JOIN:
+ WRITE_ENUM_FIELD(jointype, JoinType);
+ WRITE_NODE_FIELD(joinaliasvars);
+ break;
case RTE_FUNCTION:
WRITE_NODE_FIELD(funcexpr);
WRITE_NODE_FIELD(funccoltypes);
***************
*** 1869,1877 ****
case RTE_VALUES:
WRITE_NODE_FIELD(values_lists);
break;
! case RTE_JOIN:
! WRITE_ENUM_FIELD(jointype, JoinType);
! WRITE_NODE_FIELD(joinaliasvars);
break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) node->rtekind);
--- 1920,1931 ----
case RTE_VALUES:
WRITE_NODE_FIELD(values_lists);
break;
! case RTE_CTE:
! WRITE_STRING_FIELD(ctename);
! WRITE_UINT_FIELD(ctelevelsup);
! WRITE_BOOL_FIELD(self_reference);
! WRITE_NODE_FIELD(ctecoltypes);
! WRITE_NODE_FIELD(ctecoltypmods);
break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) node->rtekind);
***************
*** 2060,2065 ****
--- 2114,2138 ----
}
static void
+ _outRangeSubselect(StringInfo str, RangeSubselect *node)
+ {
+ WRITE_NODE_TYPE("RANGESUBSELECT");
+
+ WRITE_NODE_FIELD(subquery);
+ WRITE_NODE_FIELD(alias);
+ }
+
+ static void
+ _outRangeFunction(StringInfo str, RangeFunction *node)
+ {
+ WRITE_NODE_TYPE("RANGEFUNCTION");
+
+ WRITE_NODE_FIELD(funccallnode);
+ WRITE_NODE_FIELD(alias);
+ WRITE_NODE_FIELD(coldeflist);
+ }
+
+ static void
_outConstraint(StringInfo str, Constraint *node)
{
WRITE_NODE_TYPE("CONSTRAINT");
***************
*** 2192,2197 ****
--- 2265,2276 ----
case T_ValuesScan:
_outValuesScan(str, obj);
break;
+ case T_Recursion:
+ _outRecursion(str, obj);
+ break;
+ case T_RecursiveScan:
+ _outRecursiveScan(str, obj);
+ break;
case T_Join:
_outJoin(str, obj);
break;
***************
*** 2473,2478 ****
--- 2552,2563 ----
case T_RowMarkClause:
_outRowMarkClause(str, obj);
break;
+ case T_WithClause:
+ _outWithClause(str, obj);
+ break;
+ case T_CommonTableExpr:
+ _outCommonTableExpr(str, obj);
+ break;
case T_SetOperationStmt:
_outSetOperationStmt(str, obj);
break;
***************
*** 2509,2514 ****
--- 2594,2605 ----
case T_SortBy:
_outSortBy(str, obj);
break;
+ case T_RangeSubselect:
+ _outRangeSubselect(str, obj);
+ break;
+ case T_RangeFunction:
+ _outRangeFunction(str, obj);
+ break;
case T_Constraint:
_outConstraint(str, obj);
break;
*** src/backend/nodes/print.c.orig Wed Jun 18 20:46:04 2008
--- src/backend/nodes/print.c Mon Sep 22 15:03:48 2008
***************
*** 272,277 ****
--- 272,285 ----
printf("%d\t%s\t[subquery]",
i, rte->eref->aliasname);
break;
+ case RTE_JOIN:
+ printf("%d\t%s\t[join]",
+ i, rte->eref->aliasname);
+ break;
+ case RTE_SPECIAL:
+ printf("%d\t%s\t[special]",
+ i, rte->eref->aliasname);
+ break;
case RTE_FUNCTION:
printf("%d\t%s\t[rangefunction]",
i, rte->eref->aliasname);
***************
*** 280,291 ****
printf("%d\t%s\t[values list]",
i, rte->eref->aliasname);
break;
! case RTE_JOIN:
! printf("%d\t%s\t[join]",
! i, rte->eref->aliasname);
! break;
! case RTE_SPECIAL:
! printf("%d\t%s\t[special]",
i, rte->eref->aliasname);
break;
default:
--- 288,295 ----
printf("%d\t%s\t[values list]",
i, rte->eref->aliasname);
break;
! case RTE_CTE:
! printf("%d\t%s\t[cte]",
i, rte->eref->aliasname);
break;
default:
*** src/backend/nodes/readfuncs.c.orig Mon Sep 1 16:42:44 2008
--- src/backend/nodes/readfuncs.c Tue Sep 23 12:03:16 2008
***************
*** 155,160 ****
--- 155,162 ----
READ_BOOL_FIELD(hasAggs);
READ_BOOL_FIELD(hasSubLinks);
READ_BOOL_FIELD(hasDistinctOn);
+ READ_BOOL_FIELD(hasRecursive);
+ READ_NODE_FIELD(cteList);
READ_NODE_FIELD(rtable);
READ_NODE_FIELD(jointree);
READ_NODE_FIELD(targetList);
***************
*** 231,236 ****
--- 233,258 ----
}
/*
+ * _readCommonTableExpr
+ */
+ static CommonTableExpr *
+ _readCommonTableExpr(void)
+ {
+ READ_LOCALS(CommonTableExpr);
+
+ READ_STRING_FIELD(ctename);
+ READ_NODE_FIELD(aliascolnames);
+ READ_NODE_FIELD(ctequery);
+ READ_LOCATION_FIELD(location);
+ READ_BOOL_FIELD(cterecursive);
+ READ_NODE_FIELD(ctecolnames);
+ READ_NODE_FIELD(ctecoltypes);
+ READ_NODE_FIELD(ctecoltypmods);
+
+ READ_DONE();
+ }
+
+ /*
* _readSetOperationStmt
*/
static SetOperationStmt *
***************
*** 1007,1012 ****
--- 1029,1038 ----
case RTE_SUBQUERY:
READ_NODE_FIELD(subquery);
break;
+ case RTE_JOIN:
+ READ_ENUM_FIELD(jointype, JoinType);
+ READ_NODE_FIELD(joinaliasvars);
+ break;
case RTE_FUNCTION:
READ_NODE_FIELD(funcexpr);
READ_NODE_FIELD(funccoltypes);
***************
*** 1015,1023 ****
case RTE_VALUES:
READ_NODE_FIELD(values_lists);
break;
! case RTE_JOIN:
! READ_ENUM_FIELD(jointype, JoinType);
! READ_NODE_FIELD(joinaliasvars);
break;
default:
elog(ERROR, "unrecognized RTE kind: %d",
--- 1041,1052 ----
case RTE_VALUES:
READ_NODE_FIELD(values_lists);
break;
! case RTE_CTE:
! READ_STRING_FIELD(ctename);
! READ_UINT_FIELD(ctelevelsup);
! READ_BOOL_FIELD(self_reference);
! READ_NODE_FIELD(ctecoltypes);
! READ_NODE_FIELD(ctecoltypmods);
break;
default:
elog(ERROR, "unrecognized RTE kind: %d",
***************
*** 1060,1065 ****
--- 1089,1096 ----
return_value = _readSortGroupClause();
else if (MATCH("ROWMARKCLAUSE", 13))
return_value = _readRowMarkClause();
+ else if (MATCH("COMMONTABLEEXPR", 15))
+ return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
else if (MATCH("ALIAS", 5))
*** src/backend/optimizer/path/allpaths.c.orig Mon Aug 25 18:42:32 2008
--- src/backend/optimizer/path/allpaths.c Tue Sep 23 16:55:29 2008
***************
*** 57,62 ****
--- 57,66 ----
RangeTblEntry *rte);
static void set_values_pathlist(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
+ static void set_recursion_pathlist(PlannerInfo *root, RelOptInfo *rel,
+ Index rti, RangeTblEntry *rte);
+ static void set_recursivescan_pathlist(PlannerInfo *root, RelOptInfo *rel,
+ RangeTblEntry *rte);
static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist);
static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery,
bool *differentTypes);
***************
*** 181,186 ****
--- 185,197 ----
/* Values list --- generate a separate plan for it */
set_values_pathlist(root, rel, rte);
}
+ else if (rel->rtekind == RTE_CTE)
+ {
+ if (rte->self_reference)
+ set_recursivescan_pathlist(root, rel, rte);
+ else
+ set_recursion_pathlist(root, rel, rti, rte);
+ }
else
{
/* Plain relation */
***************
*** 550,556 ****
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
Node *clause = (Node *) rinfo->clause;
! if (!rinfo->pseudoconstant &&
qual_is_pushdown_safe(subquery, rti, clause, differentTypes))
{
/* Push it down */
--- 561,567 ----
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
Node *clause = (Node *) rinfo->clause;
! if (rte->rtekind != RTE_CTE && !rinfo->pseudoconstant &&
qual_is_pushdown_safe(subquery, rti, clause, differentTypes))
{
/* Push it down */
***************
*** 585,591 ****
/* Generate the plan for the subquery */
rel->subplan = subquery_planner(root->glob, subquery,
! root->query_level + 1,
tuple_fraction,
&subroot);
rel->subrtable = subroot->parse->rtable;
--- 596,602 ----
/* Generate the plan for the subquery */
rel->subplan = subquery_planner(root->glob, subquery,
! root,
tuple_fraction,
&subroot);
rel->subrtable = subroot->parse->rtable;
***************
*** 641,646 ****
--- 652,768 ----
}
/*
+ * set_recursion_pathlist
+ * Build the (single) access path for a recursive RTE
+ */
+ static void
+ set_recursion_pathlist(PlannerInfo *root, RelOptInfo *rel,
+ Index rti, RangeTblEntry *rte)
+ {
+ Query *parse = root->parse;
+ CommonTableExpr *cte = NULL;
+ Query *subquery;
+ double tuple_fraction;
+ PlannerInfo *cteroot;
+ PlannerInfo *subroot;
+ List *pathkeys;
+ Index levelsup;
+ ListCell *lc;
+
+ /*
+ * Find the referenced CTE.
+ */
+ levelsup = rte->ctelevelsup;
+ cteroot = root;
+ while (levelsup-- > 0)
+ {
+ cteroot = cteroot->parent_root;
+ if (!cteroot) /* shouldn't happen */
+ elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename);
+ }
+ foreach(lc, cteroot->parse->cteList)
+ {
+ cte = (CommonTableExpr *) lfirst(lc);
+ if (strcmp(cte->ctename, rte->ctename) == 0)
+ break;
+ }
+ if (lc == NULL) /* shouldn't happen */
+ elog(ERROR, "could not find CTE \"%s\"", rte->ctename);
+
+ /*
+ * Kluge: copy the subquery and adjust any outer refs it has to match our
+ * own query level. We should instead be referencing a single evaluation
+ * of the CTE ...
+ */
+ subquery = copyObject(cte->ctequery);
+ if (rte->ctelevelsup > 0)
+ IncrementVarSublevelsUp((Node *) subquery, rte->ctelevelsup, 1);
+
+ /*
+ * We can safely pass the outer tuple_fraction down to the subquery if the
+ * outer level has no joining, aggregation, or sorting to do. Otherwise
+ * we'd better tell the subquery to plan for full retrieval. (XXX This
+ * could probably be made more intelligent ...)
+ */
+ if (parse->hasAggs ||
+ parse->groupClause ||
+ parse->havingQual ||
+ parse->distinctClause ||
+ parse->sortClause ||
+ has_multiple_baserels(root))
+ tuple_fraction = 0.0; /* default case */
+ else
+ tuple_fraction = root->tuple_fraction;
+
+ /*
+ * Generate the subplan for the recursion.
+ * this is always "Append" as for now.
+ */
+ rel->subplan = subquery_planner(root->glob, subquery,
+ root,
+ tuple_fraction,
+ &subroot);
+ rel->subrtable = subroot->parse->rtable;
+
+ /* Copy number of output rows from subplan */
+ rel->tuples = rel->subplan->plan_rows;
+
+ /* Mark rel with estimated output rows, width, etc */
+ set_baserel_size_estimates(root, rel);
+
+ /*
+ * Convert subquery pathkeys to outer representation. For now
+ * this is useless since the subplan is always Append. Someday we
+ * implement "non_recursive_term UNION recursive_term" type CTE,
+ * and this will make sense...
+ */
+ pathkeys = convert_subquery_pathkeys(root, rel, subroot->query_pathkeys);
+
+ /* Generate appropriate path */
+ add_path(rel, create_recursion_path(rel, pathkeys));
+
+ /* Select cheapest path (pretty easy in this case...) */
+ set_cheapest(rel);
+ }
+
+ /*
+ * set_recursivescan_pathlist
+ * Build the (single) access path for a recursive RTE
+ */
+ static void
+ set_recursivescan_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
+ {
+ /* Mark rel with estimated output rows, width, etc */
+ set_recursivescan_size_estimates(root, rel);
+
+ /* Generate appropriate path */
+ add_path(rel, create_recursivescan_path(root, rel));
+
+ /* Select cheapest path (pretty easy in this case...) */
+ set_cheapest(rel);
+ }
+
+ /*
* make_rel_from_joinlist
* Build access paths using a "joinlist" to guide the join path search.
*
***************
*** 1230,1235 ****
--- 1352,1360 ----
ptype = "HashJoin";
join = true;
break;
+ case T_Recursion:
+ ptype = "Recursion";
+ break;
default:
ptype = "???Path";
break;
*** src/backend/optimizer/path/clausesel.c.orig Thu Aug 21 20:16:03 2008
--- src/backend/optimizer/path/clausesel.c Mon Sep 22 15:33:09 2008
***************
*** 552,558 ****
{
RangeTblEntry *rte = planner_rt_fetch(var->varno, root);
! if (rte->rtekind == RTE_SUBQUERY)
{
/*
* XXX not smart about subquery references... any way to do
--- 552,559 ----
{
RangeTblEntry *rte = planner_rt_fetch(var->varno, root);
! if (rte->rtekind == RTE_SUBQUERY ||
! rte->rtekind == RTE_CTE)
{
/*
* XXX not smart about subquery references... any way to do
*** src/backend/optimizer/path/costsize.c.orig Fri Sep 5 17:07:29 2008
--- src/backend/optimizer/path/costsize.c Mon Sep 22 19:55:02 2008
***************
*** 934,939 ****
--- 934,999 ----
}
/*
+ * cost_recursion
+ * Determines and returns the cost of scanning a recursion RTE.
+ */
+ void
+ cost_recursion(Path *path, RelOptInfo *baserel)
+ {
+ Cost startup_cost;
+ Cost run_cost;
+ Cost cpu_per_tuple;
+
+ /* Should only be applied to base relations that are subqueries */
+ Assert(baserel->relid > 0);
+ Assert(baserel->rtekind == RTE_CTE);
+
+ /*
+ * Cost of path is cost of evaluating the subplan, plus cost of evaluating
+ * any restriction clauses that will be attached to the SubqueryScan node,
+ * plus cpu_tuple_cost to account for selection and projection overhead.
+ */
+ path->startup_cost = baserel->subplan->startup_cost;
+ path->total_cost = baserel->subplan->total_cost;
+
+ startup_cost = baserel->baserestrictcost.startup;
+ cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple;
+ run_cost = cpu_per_tuple * baserel->tuples;
+
+ path->startup_cost += startup_cost;
+ path->total_cost += startup_cost + run_cost;
+ }
+
+ /*
+ * cost_recursivescan
+ * Determines and returns the cost of scanning a WITH RECURSIVE RTE.
+ */
+ void
+ cost_recursivescan(Path *path, PlannerInfo *root, RelOptInfo *baserel)
+ {
+ Cost startup_cost = 0;
+ Cost run_cost = 0;
+ Cost cpu_per_tuple;
+
+ Assert(baserel->relid > 0);
+ Assert(baserel->rtekind == RTE_CTE);
+
+ /*
+ * For now, estimate list evaluation cost at one operator eval per list
+ * (probably pretty bogus, but is it worth being smarter?)
+ */
+ cpu_per_tuple = cpu_operator_cost;
+
+ /* Add scanning CPU costs */
+ startup_cost += baserel->baserestrictcost.startup;
+ cpu_per_tuple += cpu_tuple_cost + baserel->baserestrictcost.per_tuple;
+ run_cost += cpu_per_tuple * baserel->tuples;
+
+ path->startup_cost = startup_cost;
+ path->total_cost = startup_cost + run_cost;
+ }
+
+ /*
* cost_sort
* Determines and returns the cost of sorting a relation, including
* the cost of reading the input data.
***************
*** 2475,2480 ****
--- 2535,2562 ----
set_baserel_size_estimates(root, rel);
}
+ /*
+ * set_recursivescan_size_estimates
+ * Set the size estimates for a recursivescan.
+ *
+ * The rel's targetlist and restrictinfo list must have been constructed
+ * already.
+ *
+ * We set the same fields as set_baserel_size_estimates.
+ */
+ void
+ set_recursivescan_size_estimates(PlannerInfo *root, RelOptInfo *rel)
+ {
+ RangeTblEntry *rte;
+
+ Assert(rel->relid > 0);
+ rte = planner_rt_fetch(rel->relid, root);
+ Assert(rte->rtekind == RTE_CTE);
+
+ /* Now estimate number of output rows, etc */
+ set_baserel_size_estimates(root, rel);
+ }
+
/*
* set_rel_width
*** src/backend/optimizer/plan/createplan.c.orig Fri Sep 5 17:07:29 2008
--- src/backend/optimizer/plan/createplan.c Mon Sep 22 19:55:25 2008
***************
*** 62,67 ****
--- 62,71 ----
List *tlist, List *scan_clauses);
static ValuesScan *create_valuesscan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses);
+ static RecursiveScan *create_recursivescan_plan(PlannerInfo *root, Path *best_path,
+ List *tlist, List *scan_clauses);
+ static Recursion *create_recursion_plan(PlannerInfo *root, Path *best_path,
+ List *tlist, List *scan_clauses);
static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path,
Plan *outer_plan, Plan *inner_plan);
static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path,
***************
*** 93,98 ****
--- 97,109 ----
List *funccoltypes, List *funccoltypmods);
static ValuesScan *make_valuesscan(List *qptlist, List *qpqual,
Index scanrelid, List *values_lists);
+ static RecursiveScan *make_recursivescan(List *qptlist, List *qpqual,
+ Index scanrelid);
+ static Recursion *make_recursion(List *qptlist,
+ List *qpqual,
+ Index scanrelid,
+ Plan *subplan,
+ List *subrtable);
static BitmapAnd *make_bitmap_and(List *bitmapplans);
static BitmapOr *make_bitmap_or(List *bitmapplans);
static NestLoop *make_nestloop(List *tlist,
***************
*** 148,153 ****
--- 159,166 ----
case T_SubqueryScan:
case T_FunctionScan:
case T_ValuesScan:
+ case T_RecursiveScan:
+ case T_Recursion:
plan = create_scan_plan(root, best_path);
break;
case T_HashJoin:
***************
*** 270,275 ****
--- 283,302 ----
scan_clauses);
break;
+ case T_RecursiveScan:
+ plan = (Plan *) create_recursivescan_plan(root,
+ best_path,
+ tlist,
+ scan_clauses);
+ break;
+
+ case T_Recursion:
+ plan = (Plan *) create_recursion_plan(root,
+ best_path,
+ tlist,
+ scan_clauses);
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d",
(int) best_path->pathtype);
***************
*** 375,380 ****
--- 402,408 ----
case T_SubqueryScan:
case T_FunctionScan:
case T_ValuesScan:
+ case T_RecursiveScan:
plan->targetlist = build_relation_tlist(path->parent);
break;
default:
***************
*** 1335,1340 ****
--- 1363,1431 ----
return scan_plan;
}
+ /*
+ * create_recursivescan_plan
+ */
+ static RecursiveScan *
+ create_recursivescan_plan(PlannerInfo *root, Path *best_path,
+ List *tlist, List *scan_clauses)
+ {
+ RecursiveScan *scan_plan;
+ Index scan_relid = best_path->parent->relid;
+ RangeTblEntry *rte;
+
+ Assert(scan_relid > 0);
+ rte = planner_rt_fetch(scan_relid, root);
+ Assert(rte->rtekind == RTE_CTE);
+
+ /* Sort clauses into best execution order */
+ scan_clauses = order_qual_clauses(root, scan_clauses);
+
+ /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+ scan_clauses = extract_actual_clauses(scan_clauses, false);
+
+ scan_plan = make_recursivescan(tlist, scan_clauses, scan_relid);
+
+ copy_path_costsize(&scan_plan->scan.plan, best_path);
+
+ return scan_plan;
+ }
+
+ /*
+ * create_subqueryscan_plan
+ * Returns a subqueryscan plan for the base relation scanned by 'best_path'
+ * with restriction clauses 'scan_clauses' and targetlist 'tlist'.
+ */
+ static Recursion *
+ create_recursion_plan(PlannerInfo *root, Path *best_path,
+ List *tlist, List *scan_clauses)
+ {
+ Recursion *recursion_plan;
+ Index scan_relid = best_path->parent->relid;
+
+ /* it should be a subquery base rel... */
+ Assert(scan_relid > 0);
+ Assert(best_path->parent->rtekind == RTE_CTE);
+
+ /* Sort clauses into best execution order */
+ scan_clauses = order_qual_clauses(root, scan_clauses);
+
+ /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+ scan_clauses = extract_actual_clauses(scan_clauses, false);
+
+ recursion_plan = make_recursion(tlist,
+ scan_clauses,
+ scan_relid,
+ best_path->parent->subplan,
+ best_path->parent->subrtable);
+
+ copy_path_costsize(&recursion_plan->scan.plan, best_path);
+
+ return recursion_plan;
+ }
+
+
+
/*****************************************************************************
*
* JOIN METHODS
***************
*** 2291,2296 ****
--- 2382,2434 ----
return node;
}
+ static RecursiveScan *
+ make_recursivescan(List *qptlist,
+ List *qpqual,
+ Index scanrelid)
+ {
+ RecursiveScan *node = makeNode(RecursiveScan);
+ Plan *plan = &node->scan.plan;
+
+ /* cost should be inserted by caller */
+ plan->targetlist = qptlist;
+ plan->qual = qpqual;
+ plan->lefttree = NULL;
+ plan->righttree = NULL;
+ node->scan.scanrelid = scanrelid;
+
+ return node;
+ }
+
+ static Recursion *
+ make_recursion(List *qptlist,
+ List *qpqual,
+ Index scanrelid,
+ Plan *subplan,
+ List *subrtable)
+ {
+ Recursion *node = makeNode(Recursion);
+ Plan *plan = &node->scan.plan;
+
+ /*
+ * Cost is figured here for the convenience of prepunion.c. Note this is
+ * only correct for the case where qpqual is empty; otherwise caller
+ * should overwrite cost with a better estimate.
+ */
+ copy_plan_costsize(plan, subplan);
+ plan->total_cost += cpu_tuple_cost * subplan->plan_rows;
+
+ plan->targetlist = qptlist;
+ plan->qual = qpqual;
+ plan->lefttree = NULL;
+ plan->righttree = NULL;
+ node->scan.scanrelid = scanrelid;
+ node->subplan = subplan;
+ node->subrtable = subrtable;
+
+ return node;
+ }
+
Append *
make_append(List *appendplans, bool isTarget, List *tlist)
{
*** src/backend/optimizer/plan/planner.c.orig Tue Sep 9 14:58:08 2008
--- src/backend/optimizer/plan/planner.c Tue Sep 23 16:33:55 2008
***************
*** 173,179 ****
}
/* primary planning entry point (may recurse for subqueries) */
! top_plan = subquery_planner(glob, parse, 1, tuple_fraction, &root);
/*
* If creating a plan for a scrollable cursor, make sure it can run
--- 173,179 ----
}
/* primary planning entry point (may recurse for subqueries) */
! top_plan = subquery_planner(glob, parse, NULL, tuple_fraction, &root);
/*
* If creating a plan for a scrollable cursor, make sure it can run
***************
*** 228,234 ****
*
* glob is the global state for the current planner run.
* parse is the querytree produced by the parser & rewriter.
! * level is the current recursion depth (1 at the top-level Query).
* tuple_fraction is the fraction of tuples we expect will be retrieved.
* tuple_fraction is interpreted as explained for grouping_planner, below.
*
--- 228,234 ----
*
* glob is the global state for the current planner run.
* parse is the querytree produced by the parser & rewriter.
! * parent_root is the immediate parent Query's info (NULL at the top level).
* tuple_fraction is the fraction of tuples we expect will be retrieved.
* tuple_fraction is interpreted as explained for grouping_planner, below.
*
***************
*** 249,255 ****
*/
Plan *
subquery_planner(PlannerGlobal *glob, Query *parse,
! Index level, double tuple_fraction,
PlannerInfo **subroot)
{
int num_old_subplans = list_length(glob->subplans);
--- 249,255 ----
*/
Plan *
subquery_planner(PlannerGlobal *glob, Query *parse,
! PlannerInfo *parent_root, double tuple_fraction,
PlannerInfo **subroot)
{
int num_old_subplans = list_length(glob->subplans);
***************
*** 263,269 ****
root = makeNode(PlannerInfo);
root->parse = parse;
root->glob = glob;
! root->query_level = level;
root->planner_cxt = CurrentMemoryContext;
root->init_plans = NIL;
root->eq_classes = NIL;
--- 263,270 ----
root = makeNode(PlannerInfo);
root->parse = parse;
root->glob = glob;
! root->query_level = parent_root ? parent_root->query_level + 1 : 1;
! root->parent_root = parent_root;
root->planner_cxt = CurrentMemoryContext;
root->init_plans = NIL;
root->eq_classes = NIL;
*** src/backend/optimizer/plan/setrefs.c.orig Tue Sep 9 14:58:08 2008
--- src/backend/optimizer/plan/setrefs.c Mon Sep 22 21:42:53 2008
***************
*** 201,211 ****
/* zap unneeded sub-structure */
newrte->subquery = NULL;
newrte->funcexpr = NULL;
newrte->funccoltypes = NIL;
newrte->funccoltypmods = NIL;
newrte->values_lists = NIL;
! newrte->joinaliasvars = NIL;
glob->finalrtable = lappend(glob->finalrtable, newrte);
--- 201,214 ----
/* zap unneeded sub-structure */
newrte->subquery = NULL;
+ newrte->joinaliasvars = NIL;
newrte->funcexpr = NULL;
newrte->funccoltypes = NIL;
newrte->funccoltypmods = NIL;
newrte->values_lists = NIL;
! newrte->ctename = NULL;
! newrte->ctecoltypes = NIL;
! newrte->ctecoltypmods = NIL;
glob->finalrtable = lappend(glob->finalrtable, newrte);
***************
*** 343,349 ****
--- 346,379 ----
fix_scan_list(glob, splan->values_lists, rtoffset);
}
break;
+ case T_RecursiveScan:
+ {
+ RecursiveScan *splan = (RecursiveScan *) plan;
+
+ splan->scan.scanrelid += rtoffset;
+ splan->scan.plan.targetlist =
+ fix_scan_list(glob, splan->scan.plan.targetlist, rtoffset);
+ splan->scan.plan.qual =
+ fix_scan_list(glob, splan->scan.plan.qual, rtoffset);
+ }
+ break;
+ case T_Recursion:
+ {
+ Recursion *rplan = (Recursion *) plan;
+ /* First, recursively process the subplan */
+ rplan->subplan = set_plan_references(glob, rplan->subplan, rplan->subrtable);
+
+ /* subrtable is no longer needed in the plan tree */
+ rplan->subrtable = NIL;
+
+ rplan->scan.scanrelid += rtoffset;
+ rplan->scan.plan.targetlist =
+ fix_scan_list(glob, rplan->scan.plan.targetlist, rtoffset);
+ rplan->scan.plan.qual =
+ fix_scan_list(glob, rplan->scan.plan.qual, rtoffset);
+ }
+ break;
case T_NestLoop:
case T_MergeJoin:
case T_HashJoin:
*** src/backend/optimizer/plan/subselect.c.orig Thu Aug 28 19:09:46 2008
--- src/backend/optimizer/plan/subselect.c Tue Sep 23 16:36:50 2008
***************
*** 308,314 ****
* Generate the plan for the subquery.
*/
plan = subquery_planner(root->glob, subquery,
! root->query_level + 1,
tuple_fraction,
&subroot);
--- 308,314 ----
* Generate the plan for the subquery.
*/
plan = subquery_planner(root->glob, subquery,
! root,
tuple_fraction,
&subroot);
***************
*** 342,348 ****
{
/* Generate the plan for the ANY subquery; we'll need all rows */
plan = subquery_planner(root->glob, subquery,
! root->query_level + 1,
0.0,
&subroot);
--- 342,348 ----
{
/* Generate the plan for the ANY subquery; we'll need all rows */
plan = subquery_planner(root->glob, subquery,
! root,
0.0,
&subroot);
***************
*** 1798,1803 ****
--- 1798,1805 ----
case T_Unique:
case T_SetOp:
case T_Group:
+ case T_Recursion:
+ case T_RecursiveScan:
break;
default:
*** src/backend/optimizer/prep/prepjointree.c.orig Mon Aug 25 18:42:33 2008
--- src/backend/optimizer/prep/prepjointree.c Tue Sep 23 16:37:13 2008
***************
*** 567,572 ****
--- 567,573 ----
subroot->parse = subquery;
subroot->glob = root->glob;
subroot->query_level = root->query_level;
+ subroot->parent_root = root->parent_root;
subroot->planner_cxt = CurrentMemoryContext;
subroot->init_plans = NIL;
subroot->eq_classes = NIL;
***************
*** 916,923 ****
return false;
/*
! * Can't pull up a subquery involving grouping, aggregation, sorting, or
! * limiting.
*/
if (subquery->hasAggs ||
subquery->groupClause ||
--- 917,924 ----
return false;
/*
! * Can't pull up a subquery involving grouping, aggregation, sorting,
! * limiting, or WITH. (XXX WITH could possibly be allowed later)
*/
if (subquery->hasAggs ||
subquery->groupClause ||
***************
*** 925,931 ****
subquery->sortClause ||
subquery->distinctClause ||
subquery->limitOffset ||
! subquery->limitCount)
return false;
/*
--- 926,933 ----
subquery->sortClause ||
subquery->distinctClause ||
subquery->limitOffset ||
! subquery->limitCount ||
! subquery->cteList)
return false;
/*
***************
*** 985,995 ****
return false;
Assert(IsA(topop, SetOperationStmt));
! /* Can't handle ORDER BY, LIMIT/OFFSET, or locking */
if (subquery->sortClause ||
subquery->limitOffset ||
subquery->limitCount ||
! subquery->rowMarks)
return false;
/* Recursively check the tree of set operations */
--- 987,998 ----
return false;
Assert(IsA(topop, SetOperationStmt));
! /* Can't handle ORDER BY, LIMIT/OFFSET, locking, or WITH */
if (subquery->sortClause ||
subquery->limitOffset ||
subquery->limitCount ||
! subquery->rowMarks ||
! subquery->cteList)
return false;
/* Recursively check the tree of set operations */
*** src/backend/optimizer/prep/prepunion.c.orig Thu Aug 28 19:09:46 2008
--- src/backend/optimizer/prep/prepunion.c Tue Sep 23 16:37:13 2008
***************
*** 200,206 ****
* Generate plan for primitive subquery
*/
subplan = subquery_planner(root->glob, subquery,
! root->query_level + 1,
tuple_fraction,
&subroot);
--- 200,206 ----
* Generate plan for primitive subquery
*/
subplan = subquery_planner(root->glob, subquery,
! root,
tuple_fraction,
&subroot);
***************
*** 1346,1352 ****
newnode = query_tree_mutator((Query *) node,
adjust_appendrel_attrs_mutator,
(void *) appinfo,
! QTW_IGNORE_RT_SUBQUERIES);
if (newnode->resultRelation == appinfo->parent_relid)
{
newnode->resultRelation = appinfo->child_relid;
--- 1346,1352 ----
newnode = query_tree_mutator((Query *) node,
adjust_appendrel_attrs_mutator,
(void *) appinfo,
! QTW_IGNORE_RC_SUBQUERIES);
if (newnode->resultRelation == appinfo->parent_relid)
{
newnode->resultRelation = appinfo->child_relid;
*** src/backend/optimizer/util/clauses.c.orig Tue Sep 9 14:58:08 2008
--- src/backend/optimizer/util/clauses.c Mon Sep 22 14:07:15 2008
***************
*** 3361,3366 ****
--- 3361,3367 ----
querytree->intoClause ||
querytree->hasAggs ||
querytree->hasSubLinks ||
+ querytree->cteList ||
querytree->rtable ||
querytree->jointree->fromlist ||
querytree->jointree->quals ||
*** src/backend/optimizer/util/pathnode.c.orig Fri Sep 5 17:07:29 2008
--- src/backend/optimizer/util/pathnode.c Mon Sep 22 11:16:32 2008
***************
*** 1220,1225 ****
--- 1220,1264 ----
}
/*
+ * create_recursion_path
+ * Creates a path corresponding to a scan of recursion,
+ * returning the pathnode.
+ */
+ Path *
+ create_recursion_path(RelOptInfo *rel, List *pathkeys)
+ {
+ Path *pathnode = makeNode(Path);
+
+ pathnode->pathtype = T_Recursion;
+ pathnode->parent = rel;
+ pathnode->pathkeys = pathkeys;
+
+ cost_recursion(pathnode, rel);
+
+ return pathnode;
+ }
+
+
+ /*
+ * create_recursivescan_path
+ * Creates a path corresponding to a scan of recursive,
+ * returning the pathnode.
+ */
+ Path *
+ create_recursivescan_path(PlannerInfo *root, RelOptInfo *rel)
+ {
+ Path *pathnode = makeNode(Path);
+
+ pathnode->pathtype = T_RecursiveScan;
+ pathnode->parent = rel;
+ pathnode->pathkeys = NIL; /* result is always unordered */
+
+ cost_recursivescan(pathnode, root, rel);
+
+ return pathnode;
+ }
+
+ /*
* create_nestloop_path
* Creates a pathnode corresponding to a nestloop join between two
* relations.
*** src/backend/optimizer/util/relnode.c.orig Thu Aug 14 14:47:59 2008
--- src/backend/optimizer/util/relnode.c Mon Sep 22 14:07:16 2008
***************
*** 101,106 ****
--- 101,107 ----
case RTE_SUBQUERY:
case RTE_FUNCTION:
case RTE_VALUES:
+ case RTE_CTE:
/*
* Subquery, function, or values list --- set up attr range and
*** src/backend/parser/Makefile.orig Fri Aug 29 09:02:32 2008
--- src/backend/parser/Makefile Mon Sep 22 11:16:32 2008
***************
*** 12,18 ****
override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
! OBJS= analyze.o gram.o keywords.o parser.o parse_agg.o parse_clause.o \
parse_expr.o parse_func.o parse_node.o parse_oper.o parse_relation.o \
parse_type.o parse_coerce.o parse_target.o parse_utilcmd.o scansup.o
--- 12,18 ----
override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
! OBJS= analyze.o gram.o keywords.o parser.o parse_agg.o parse_cte.o parse_clause.o \
parse_expr.o parse_func.o parse_node.o parse_oper.o parse_relation.o \
parse_type.o parse_coerce.o parse_target.o parse_utilcmd.o scansup.o
*** src/backend/parser/analyze.c.orig Mon Sep 1 16:42:44 2008
--- src/backend/parser/analyze.c Tue Sep 23 12:24:06 2008
***************
*** 32,37 ****
--- 32,38 ----
#include "parser/parse_agg.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
+ #include "parser/parse_cte.h"
#include "parser/parse_oper.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
***************
*** 309,314 ****
--- 310,317 ----
pstate->p_relnamespace = NIL;
sub_varnamespace = pstate->p_varnamespace;
pstate->p_varnamespace = NIL;
+ /* There can't be any outer WITH to worry about */
+ Assert(pstate->p_ctenamespace == NIL);
}
else
{
***************
*** 694,699 ****
--- 697,709 ----
/* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */
pstate->p_locking_clause = stmt->lockingClause;
+ /* process the WITH clause */
+ if (stmt->withClause)
+ {
+ qry->hasRecursive = stmt->withClause->recursive;
+ qry->cteList = transformWithClause(pstate, stmt->withClause);
+ }
+
/* process the FROM clause */
transformFromClause(pstate, stmt->fromClause);
***************
*** 812,817 ****
--- 822,828 ----
Assert(stmt->whereClause == NULL);
Assert(stmt->groupClause == NIL);
Assert(stmt->havingClause == NULL);
+ Assert(stmt->withClause == NULL);
Assert(stmt->op == SETOP_NONE);
/*
***************
*** 1059,1064 ****
--- 1070,1082 ----
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
+ /* process the WITH clause */
+ if (stmt->withClause)
+ {
+ qry->hasRecursive = stmt->withClause->recursive;
+ qry->cteList = transformWithClause(pstate, stmt->withClause);
+ }
+
/*
* Recursively transform the components of the tree.
*/
***************
*** 1841,1846 ****
--- 1859,1868 ----
*/
transformLockingClause(pstate, rte->subquery, allrels);
break;
+ case RTE_CTE:
+ /* XXX fixme */
+ elog(ERROR, "FOR UPDATE of CTE not supported");
+ break;
default:
/* ignore JOIN, SPECIAL, FUNCTION RTEs */
break;
***************
*** 1908,1913 ****
--- 1930,1937 ----
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES"),
parser_errposition(pstate, thisrel->location)));
break;
+ case RTE_CTE:
+ /* XXX fixme */
default:
elog(ERROR, "unrecognized RTE type: %d",
(int) rte->rtekind);
*** src/backend/parser/gram.y.orig Thu Sep 11 11:27:30 2008
--- src/backend/parser/gram.y Tue Sep 23 11:04:44 2008
***************
*** 119,125 ****
static SelectStmt *findLeftmostSelect(SelectStmt *node);
static void insertSelectOptions(SelectStmt *stmt,
List *sortClause, List *lockingClause,
! Node *limitOffset, Node *limitCount);
static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
static Node *doNegate(Node *n, int location);
static void doNegateFloat(Value *v);
--- 119,126 ----
static SelectStmt *findLeftmostSelect(SelectStmt *node);
static void insertSelectOptions(SelectStmt *stmt,
List *sortClause, List *lockingClause,
! Node *limitOffset, Node *limitCount,
! WithClause *withClause);
static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
static Node *doNegate(Node *n, int location);
static void doNegateFloat(Value *v);
***************
*** 160,165 ****
--- 161,167 ----
Alias *alias;
RangeVar *range;
IntoClause *into;
+ WithClause *with;
A_Indices *aind;
ResTarget *target;
PrivTarget *privtarget;
***************
*** 377,382 ****
--- 379,388 ----
%type document_or_content
%type xml_whitespace_option
+ %type common_table_expr
+ %type with_clause
+ %type cte_list
+
/*
* If you make any token changes, update the keyword table in
***************
*** 443,451 ****
QUOTE
! READ REAL REASSIGN RECHECK REFERENCES REINDEX RELATIVE_P RELEASE RENAME
! REPEATABLE REPLACE REPLICA RESET RESTART RESTRICT RETURNING RETURNS REVOKE
! RIGHT ROLE ROLLBACK ROW ROWS RULE
SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE
SERIALIZABLE SESSION SESSION_USER SET SETOF SHARE
--- 449,457 ----
QUOTE
! READ REAL REASSIGN RECHECK RECURSIVE REFERENCES REINDEX RELATIVE_P RELEASE
! RENAME REPEATABLE REPLACE REPLICA RESET RESTART RESTRICT RETURNING RETURNS
! REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE
SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE
SERIALIZABLE SESSION SESSION_USER SET SETOF SHARE
***************
*** 6260,6265 ****
--- 6266,6275 ----
;
/*
+ * This rule parses the equivalent of the standard's .
+ * The duplicative productions are annoying, but hard to get rid of without
+ * creating shift/reduce conflicts.
+ *
* FOR UPDATE/SHARE may be before or after LIMIT/OFFSET.
* In <=7.2.X, LIMIT/OFFSET had to be after FOR UPDATE
* We now support both orderings, but prefer LIMIT/OFFSET before FOR UPDATE/SHARE
***************
*** 6270,6290 ****
| select_clause sort_clause
{
insertSelectOptions((SelectStmt *) $1, $2, NIL,
! NULL, NULL);
$$ = $1;
}
| select_clause opt_sort_clause for_locking_clause opt_select_limit
{
insertSelectOptions((SelectStmt *) $1, $2, $3,
! list_nth($4, 0), list_nth($4, 1));
$$ = $1;
}
| select_clause opt_sort_clause select_limit opt_for_locking_clause
{
insertSelectOptions((SelectStmt *) $1, $2, $4,
! list_nth($3, 0), list_nth($3, 1));
$$ = $1;
}
;
select_clause:
--- 6280,6330 ----
| select_clause sort_clause
{
insertSelectOptions((SelectStmt *) $1, $2, NIL,
! NULL, NULL, NULL);
$$ = $1;
}
| select_clause opt_sort_clause for_locking_clause opt_select_limit
{
insertSelectOptions((SelectStmt *) $1, $2, $3,
! list_nth($4, 0), list_nth($4, 1),
! NULL);
$$ = $1;
}
| select_clause opt_sort_clause select_limit opt_for_locking_clause
{
insertSelectOptions((SelectStmt *) $1, $2, $4,
! list_nth($3, 0), list_nth($3, 1),
! NULL);
$$ = $1;
}
+ | with_clause simple_select
+ {
+ insertSelectOptions((SelectStmt *) $2, NULL, NIL,
+ NULL, NULL,
+ $1);
+ $$ = $2;
+ }
+ | with_clause select_clause sort_clause
+ {
+ insertSelectOptions((SelectStmt *) $2, $3, NIL,
+ NULL, NULL,
+ $1);
+ $$ = $2;
+ }
+ | with_clause select_clause opt_sort_clause for_locking_clause opt_select_limit
+ {
+ insertSelectOptions((SelectStmt *) $2, $3, $4,
+ list_nth($5, 0), list_nth($5, 1),
+ $1);
+ $$ = $2;
+ }
+ | with_clause select_clause opt_sort_clause select_limit opt_for_locking_clause
+ {
+ insertSelectOptions((SelectStmt *) $2, $3, $5,
+ list_nth($4, 0), list_nth($4, 1),
+ $1);
+ $$ = $2;
+ }
;
select_clause:
***************
*** 6345,6350 ****
--- 6385,6431 ----
}
;
+ /*
+ * SQL standard WITH clause looks like:
+ *
+ * WITH [ RECURSIVE ] [ (,...) ]
+ * AS (query) [ SEARCH or CYCLE clause ]
+ *
+ * We don't currently support the SEARCH or CYCLE clause.
+ */
+ with_clause:
+ WITH cte_list
+ {
+ $$ = makeNode(WithClause);
+ $$->ctes = $2;
+ $$->recursive = false;
+ $$->location = @1;
+ }
+ | WITH RECURSIVE cte_list
+ {
+ $$ = makeNode(WithClause);
+ $$->ctes = $3;
+ $$->recursive = true;
+ $$->location = @1;
+ }
+ ;
+
+ cte_list:
+ common_table_expr { $$ = list_make1($1); }
+ | cte_list ',' common_table_expr { $$ = lappend($1, $3); }
+ ;
+
+ common_table_expr: name opt_name_list AS select_with_parens
+ {
+ CommonTableExpr *n = makeNode(CommonTableExpr);
+ n->ctename = $1;
+ n->aliascolnames = $2;
+ n->ctequery = $4;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
into_clause:
INTO OptTempTableName
{
***************
*** 9332,9337 ****
--- 9413,9419 ----
| READ
| REASSIGN
| RECHECK
+ | RECURSIVE
| REINDEX
| RELATIVE_P
| RELEASE
***************
*** 9399,9405 ****
| VIEW
| VOLATILE
| WHITESPACE_P
- | WITH
| WITHOUT
| WORK
| WRITE
--- 9481,9486 ----
***************
*** 9582,9587 ****
--- 9663,9669 ----
| VARIADIC
| WHEN
| WHERE
+ | WITH
;
***************
*** 9905,9912 ****
static void
insertSelectOptions(SelectStmt *stmt,
List *sortClause, List *lockingClause,
! Node *limitOffset, Node *limitCount)
{
/*
* Tests here are to reject constructs like
* (SELECT foo ORDER BY bar) ORDER BY baz
--- 9987,9997 ----
static void
insertSelectOptions(SelectStmt *stmt,
List *sortClause, List *lockingClause,
! Node *limitOffset, Node *limitCount,
! WithClause *withClause)
{
+ Assert(IsA(stmt, SelectStmt));
+
/*
* Tests here are to reject constructs like
* (SELECT foo ORDER BY bar) ORDER BY baz
***************
*** 9940,9945 ****
--- 10025,10039 ----
scanner_errposition(exprLocation(limitCount))));
stmt->limitCount = limitCount;
}
+ if (withClause)
+ {
+ if (stmt->withClause)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("multiple WITH clauses not allowed"),
+ scanner_errposition(exprLocation((Node *) withClause))));
+ stmt->withClause = withClause;
+ }
}
static Node *
*** src/backend/parser/keywords.c.orig Fri Aug 29 09:02:32 2008
--- src/backend/parser/keywords.c Mon Sep 22 13:33:32 2008
***************
*** 304,309 ****
--- 304,310 ----
{"real", REAL, COL_NAME_KEYWORD},
{"reassign", REASSIGN, UNRESERVED_KEYWORD},
{"recheck", RECHECK, UNRESERVED_KEYWORD},
+ {"recursive", RECURSIVE, UNRESERVED_KEYWORD},
{"references", REFERENCES, RESERVED_KEYWORD},
{"reindex", REINDEX, UNRESERVED_KEYWORD},
{"relative", RELATIVE_P, UNRESERVED_KEYWORD},
***************
*** 402,416 ****
{"when", WHEN, RESERVED_KEYWORD},
{"where", WHERE, RESERVED_KEYWORD},
{"whitespace", WHITESPACE_P, UNRESERVED_KEYWORD},
-
- /*
- * XXX we mark WITH as reserved to force it to be quoted in dumps, even
- * though it is currently unreserved according to gram.y. This is because
- * we expect we'll have to make it reserved to implement SQL WITH clauses.
- * If that patch manages to do without reserving WITH, adjust this entry
- * at that time; in any case this should be back in sync with gram.y after
- * WITH clauses are implemented.
- */
{"with", WITH, RESERVED_KEYWORD},
{"without", WITHOUT, UNRESERVED_KEYWORD},
{"work", WORK, UNRESERVED_KEYWORD},
--- 403,408 ----
*** src/backend/parser/parse_agg.c.orig Mon Sep 1 16:42:44 2008
--- src/backend/parser/parse_agg.c Mon Sep 22 19:55:40 2008
***************
*** 217,222 ****
--- 217,238 ----
clause = flatten_join_alias_vars(root, clause);
check_ungrouped_columns(clause, pstate,
groupClauses, have_non_var_grouping);
+
+ /*
+ * Check if there's aggregate function in a recursive term.
+ */
+ foreach(l, qry->rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+
+ if (qry->hasAggs && rte->rtekind == RTE_CTE &&
+ rte->self_reference)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("aggregate functions in a recursive term not allowed")));
+ }
+ }
}
*** src/backend/parser/parse_clause.c.orig Mon Sep 1 16:42:44 2008
--- src/backend/parser/parse_clause.c Tue Sep 23 17:42:43 2008
***************
*** 54,59 ****
--- 54,61 ----
List *relnamespace,
Relids containedRels);
static RangeTblEntry *transformTableEntry(ParseState *pstate, RangeVar *r);
+ static RangeTblEntry *transformCTEReference(ParseState *pstate, RangeVar *r,
+ CommonTableExpr *cte, Index levelsup);
static RangeTblEntry *transformRangeSubselect(ParseState *pstate,
RangeSubselect *r);
static RangeTblEntry *transformRangeFunction(ParseState *pstate,
***************
*** 422,427 ****
--- 424,470 ----
return rte;
}
+ /*
+ * transformCTEReference --- transform a RangeVar that references a common
+ * table expression (ie, a sub-SELECT defined in a WITH clause)
+ */
+ static RangeTblEntry *
+ transformCTEReference(ParseState *pstate, RangeVar *r,
+ CommonTableExpr *cte, Index levelsup)
+ {
+ RangeTblEntry *rte;
+
+ /*
+ * This is a kluge for now. Effectively we're inlining non-recursive WITH
+ * clauses which isn't what we want to do. But the planner currently
+ * thinks that all CTE RTEs are recursive, and that has to be fixed
+ * before this can be cleaned up. Note that we may also get the column
+ * names wrong when there are column aliases at both sites.
+ */
+ if (cte->cterecursive)
+ {
+ rte = addRangeTableEntryForCTE(pstate, cte, levelsup, r->alias, true);
+ }
+ else
+ {
+ Alias *a;
+ Query *q;
+
+ if (r->alias)
+ a = r->alias;
+ else
+ a = makeAlias(cte->ctename, cte->aliascolnames);
+
+ Assert(IsA(cte->ctequery, Query));
+ q = copyObject(cte->ctequery);
+ /* must adjust any outer references when copying down in level */
+ if (levelsup > 0)
+ IncrementVarSublevelsUp((Node *) q, levelsup, 1);
+ rte = addRangeTableEntryForSubquery(pstate, q, a, true);
+ }
+
+ return rte;
+ }
/*
* transformRangeSubselect --- transform a sub-SELECT appearing in FROM
***************
*** 609,620 ****
{
if (IsA(n, RangeVar))
{
! /* Plain relation reference */
RangeTblRef *rtr;
! RangeTblEntry *rte;
int rtindex;
! rte = transformTableEntry(pstate, (RangeVar *) n);
/* assume new rte is at end */
rtindex = list_length(pstate->p_rtable);
Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
--- 652,697 ----
{
if (IsA(n, RangeVar))
{
! /* Plain relation reference, or perhaps a CTE reference */
! RangeVar *rv = (RangeVar *) n;
RangeTblRef *rtr;
! RangeTblEntry *rte = NULL;
int rtindex;
! /*
! * If it is an unqualified name, it might be a reference to some
! * CTE visible in this or a parent query.
! */
! if (!rv->schemaname)
! {
! ParseState *ps;
! Index levelsup;
!
! for (ps = pstate, levelsup = 0;
! ps != NULL;
! ps = ps->parentParseState, levelsup++)
! {
! ListCell *lc;
!
! foreach(lc, ps->p_ctenamespace)
! {
! CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
!
! if (strcmp(rv->relname, cte->ctename) == 0)
! {
! rte = transformCTEReference(pstate, rv, cte, levelsup);
! break;
! }
! }
! if (rte)
! break;
! }
! }
!
! /* if not found as a CTE, must be a table reference */
! if (!rte)
! rte = transformTableEntry(pstate, rv);
!
/* assume new rte is at end */
rtindex = list_length(pstate->p_rtable);
Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
*** src/backend/parser/parse_cte.c.orig Mon Sep 22 11:16:32 2008
--- src/backend/parser/parse_cte.c Tue Sep 23 21:22:49 2008
***************
*** 0 ****
--- 1,882 ----
+ /*-------------------------------------------------------------------------
+ *
+ * parse_cte.c
+ * handle CTEs (common table expressions) in parser
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+ #include "postgres.h"
+
+ #include "access/heapam.h"
+ #include "catalog/heap.h"
+ #include "catalog/pg_type.h"
+ #include "commands/defrem.h"
+ #include "nodes/bitmapset.h"
+ #include "nodes/makefuncs.h"
+ #include "nodes/nodeFuncs.h"
+ #include "optimizer/clauses.h"
+ #include "parser/analyze.h"
+ #include "parser/parse_clause.h"
+ #include "parser/parse_coerce.h"
+ #include "parser/parse_cte.h"
+ #include "parser/parse_expr.h"
+ #include "parser/parse_oper.h"
+ #include "parser/parse_relation.h"
+ #include "parser/parse_target.h"
+ #include "parser/parsetree.h"
+
+
+ /*
+ * For WITH RECURSIVE, we have to find an ordering of the clause members
+ * with no forward references, and determine which members are recursive
+ * (i.e., self-referential). It is convenient to do this with an array
+ * of CteItems instead of a list of CommonTableExprs.
+ */
+ typedef struct CteItem
+ {
+ CommonTableExpr *cte; /* One CTE to examine */
+ int id; /* Its ID number for dependencies */
+ Node *non_recursive_term; /* Its nonrecursive part, if identified */
+ Bitmapset *depends_on; /* CTEs depended on (not including self) */
+ } CteItem;
+
+ /* CteState is what we need to pass around in the tree walkers */
+ typedef struct CteState
+ {
+ ParseState *pstate; /* global parse state */
+ CteItem *items; /* array of CTEs and extra data */
+ int numitems; /* number of CTEs */
+ int curitem; /* index of item currently being examined */
+ List *innerwiths; /* list of lists of CommonTableExpr */
+ } CteState;
+
+ typedef enum RecursiveType
+ {
+ NON_RECURSIVE,
+ RECURSIVE_SELF,
+ RECURSIVE_OTHER
+ } RecursiveType;
+
+
+ /* UGH FIXME */
+ static Node *non_recursive_term;
+
+
+ static void analyzeCTE(ParseState *pstate, CommonTableExpr *cte);
+ static void analyzeCTETargetList(ParseState *pstate, CommonTableExpr *cte, List *tlist);
+
+ /* Dependency collector functions */
+ static void makeDependencyGraph(CteState *cstate);
+ static bool makeDependencyGraphWalker(Node *node, CteState *cstate);
+ static void TopologicalSort(ParseState *pstate, CteItem *items, int numitems);
+
+ /* Checker functions */
+ static void checkWellFormedCte(CteState *cstate);
+ static RecursiveType checkCteSelectStmt(CteState *cstate, SelectStmt *n, int myindex);
+ static RecursiveType checkCteTargetList(CteState *cstate, List *targetList, int myindex);
+ static RecursiveType checkCteTargetListItem(CteState *cstate, Node *target, int myindex);
+ static RecursiveType checkCteFromClause(CteState *cstate, List *fromClause, int myindex);
+ static RecursiveType checkCteWhereClause(CteState *cstate, Node *n, int myindex);
+ static RecursiveType checkCteFromClauseItem(CteState *cstate, Node *n, int myindex);
+ static RecursiveType findCteName(CteState *cstate, char *name, int myindex);
+
+
+ /*
+ * transformWithClause -
+ * Transform the list of WITH clause "common table expressions" into
+ * Query nodes.
+ *
+ * The result is the list of transformed CTEs to be put into the output
+ * Query. (This is in fact the same as the ending value of p_ctenamespace,
+ * but it seems cleaner to not expose that in the function's API.)
+ */
+ List *
+ transformWithClause(ParseState *pstate, WithClause *withClause)
+ {
+ ListCell *lc;
+
+ /* Only one WITH clause per query level */
+ Assert(pstate->p_ctenamespace == NIL);
+
+ /*
+ * For either type of WITH, there must not be duplicate CTE names in
+ * the list. Check this right away so we needn't worry later.
+ *
+ * Also, tentatively mark each CTE as non-recursive.
+ */
+ foreach(lc, withClause->ctes)
+ {
+ CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+ ListCell *rest;
+
+ for_each_cell(rest, lnext(lc))
+ {
+ CommonTableExpr *cte2 = (CommonTableExpr *) lfirst(rest);
+
+ if (strcmp(cte->ctename, cte2->ctename) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_ALIAS),
+ errmsg("common table expression name \"%s\" specified more than once",
+ cte2->ctename),
+ parser_errposition(pstate, cte2->location)));
+ }
+
+ cte->cterecursive = false;
+ }
+
+ if (withClause->recursive)
+ {
+ /*
+ * For WITH RECURSIVE, we rearrange the list elements if needed
+ * to eliminate forward references. First, build a work array
+ * and set up the data structure needed by the tree walkers.
+ */
+ CteState cstate;
+ int i;
+
+ cstate.pstate = pstate;
+ cstate.numitems = list_length(withClause->ctes);
+ cstate.items = (CteItem *) palloc0(cstate.numitems * sizeof(CteItem));
+ i = 0;
+ foreach(lc, withClause->ctes)
+ {
+ cstate.items[i].cte = (CommonTableExpr *) lfirst(lc);
+ cstate.items[i].id = i;
+ i++;
+ }
+ cstate.curitem = 0;
+ cstate.innerwiths = NIL;
+
+ /*
+ * Check well-formed subquery.
+ */
+ checkWellFormedCte(&cstate);
+
+ /*
+ * Set up the ctenamespace for parse analysis. Per spec, all
+ * the WITH items are visible to all others, so it's just the
+ * same as the original WITH list. (We keep the original order
+ * since this will affect display of the query by ruleutils.c.)
+ */
+ pstate->p_ctenamespace = withClause->ctes;
+
+ /*
+ * Do parse analysis in the order determined by the topological sort.
+ */
+ for (i = 0; i < cstate.numitems; i++)
+ {
+ CommonTableExpr *cte = cstate.items[i].cte;
+
+ /*
+ * If it's recursive, we have to do a throwaway parse analysis
+ * of the non-recursive term in order to determine the set of
+ * output columns for the recursive CTE.
+ */
+ if (cte->cterecursive)
+ {
+ Node *nrt;
+ Query *nrq;
+
+ if (!cstate.items[i].non_recursive_term)
+ elog(ERROR, "could not find non-recursive term for %s",
+ cte->ctename);
+ /* copy the term to be sure we don't modify original query */
+ nrt = copyObject(cstate.items[i].non_recursive_term);
+ nrq = parse_sub_analyze(nrt, pstate);
+ analyzeCTETargetList(pstate, cte, nrq->targetList);
+ }
+
+ analyzeCTE(pstate, cte);
+ }
+ }
+ else
+ {
+ /*
+ * For non-recursive WITH, just analyze each CTE in sequence and then
+ * add it to the ctenamespace. This corresponds to the spec's
+ * definition of the scope of each WITH name.
+ */
+ foreach(lc, withClause->ctes)
+ {
+ CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+
+ analyzeCTE(pstate, cte);
+ pstate->p_ctenamespace = lappend(pstate->p_ctenamespace, cte);
+ }
+ }
+
+ return pstate->p_ctenamespace;
+ }
+
+
+ /*
+ * Perform the actual parse analysis transformation of one CTE. All
+ * CTEs it is allowed to depend on have already been loaded into
+ * pstate->p_ctenamespace.
+ */
+ static void
+ analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
+ {
+ Query *query;
+
+ /* Analysis not done already */
+ Assert(IsA(cte->ctequery, SelectStmt));
+
+ query = parse_sub_analyze(cte->ctequery, pstate);
+ cte->ctequery = (Node *) query;
+
+ /* Same checks that FROM does on subqueries XXX refactor? */
+ if (query->commandType != CMD_SELECT ||
+ query->utilityStmt != NULL)
+ elog(ERROR, "expected SELECT query from subquery in WITH");
+ if (query->intoClause)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("subquery in WITH cannot have SELECT INTO")));
+
+ /* Compute the derived fields if not done yet */
+ if (!cte->cterecursive)
+ analyzeCTETargetList(pstate, cte, query->targetList);
+ }
+
+ /*
+ * Compute derived fields of a CTE, given the transformed output targetlist
+ */
+ static void
+ analyzeCTETargetList(ParseState *pstate, CommonTableExpr *cte, List *tlist)
+ {
+ int numaliases;
+ int varattno;
+ ListCell *tlistitem;
+
+ /*
+ * We need to determine column names and types. The alias column names
+ * override anything coming from the query itself. (Note: the SQL spec
+ * says that the alias list must be empty or exactly as long as the
+ * output column set; but we allow it to be shorter for consistency
+ * with Alias handling.)
+ */
+ cte->ctecolnames = copyObject(cte->aliascolnames);
+ cte->ctecoltypes = cte->ctecoltypmods = NIL;
+ numaliases = list_length(cte->aliascolnames);
+ varattno = 0;
+ foreach(tlistitem, tlist)
+ {
+ TargetEntry *te = (TargetEntry *) lfirst(tlistitem);
+
+ if (te->resjunk)
+ continue;
+ varattno++;
+ Assert(varattno == te->resno);
+ if (varattno > numaliases)
+ {
+ char *attrname;
+
+ attrname = pstrdup(te->resname);
+ cte->ctecolnames = lappend(cte->ctecolnames, makeString(attrname));
+ }
+ cte->ctecoltypes = lappend_oid(cte->ctecoltypes,
+ exprType((Node *) te->expr));
+ cte->ctecoltypmods = lappend_int(cte->ctecoltypmods,
+ exprTypmod((Node *) te->expr));
+ }
+ if (varattno < numaliases)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg("WITH item \"%s\" has %d columns available but %d columns specified",
+ cte->ctename, varattno, numaliases),
+ parser_errposition(pstate, cte->location)));
+ }
+
+
+ /*
+ * Identify the cross-references of a list of WITH RECURSIVE items,
+ * and sort into an order that has no forward references.
+ */
+ static void
+ makeDependencyGraph(CteState *cstate)
+ {
+ int i;
+
+ for (i = 0; i < cstate->numitems; i++)
+ {
+ CommonTableExpr *cte = cstate->items[i].cte;
+
+ cstate->curitem = i;
+ cstate->innerwiths = NIL;
+ makeDependencyGraphWalker((Node *) cte->ctequery, cstate);
+ Assert(cstate->innerwiths == NIL);
+ }
+
+ TopologicalSort(cstate->pstate, cstate->items, cstate->numitems);
+ }
+
+ /*
+ * Tree walker function to detect cross-references and self-references of the
+ * CTEs in a WITH RECURSIVE list.
+ */
+ static bool
+ makeDependencyGraphWalker(Node *node, CteState *cstate)
+ {
+ if (node == NULL)
+ return false;
+ if (IsA(node, RangeVar))
+ {
+ RangeVar *rv = (RangeVar *) node;
+
+ /* If unqualified name, might be a CTE reference */
+ if (!rv->schemaname)
+ {
+ ListCell *lc;
+ int i;
+
+ /* ... but first see if it's captured by an inner WITH */
+ foreach(lc, cstate->innerwiths)
+ {
+ List *withlist = (List *) lfirst(lc);
+ ListCell *lc2;
+
+ foreach(lc2, withlist)
+ {
+ CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc2);
+
+ if (strcmp(rv->relname, cte->ctename) == 0)
+ return false; /* yes, so bail out */
+ }
+ }
+
+ /* No, could be a reference to the query level we are working on */
+ for (i = 0; i < cstate->numitems; i++)
+ {
+ CommonTableExpr *cte = cstate->items[i].cte;
+
+ if (strcmp(rv->relname, cte->ctename) == 0)
+ {
+ int myindex = cstate->curitem;
+
+ if (i != myindex)
+ {
+ /* Add cross-item dependency */
+ cstate->items[myindex].depends_on =
+ bms_add_member(cstate->items[myindex].depends_on,
+ cstate->items[i].id);
+ elog(DEBUG1, "CTE name dependency: %s(%d) -> %s(%d)",
+ cstate->items[myindex].cte->ctename,
+ cstate->items[myindex].id,
+ cte->ctename, cstate->items[i].id);
+ }
+ else
+ {
+ /* Found out this one is self-referential */
+ cte->cterecursive = true;
+ }
+ break;
+ }
+ }
+ }
+ return false;
+ }
+ if (IsA(node, SelectStmt))
+ {
+ SelectStmt *stmt = (SelectStmt *) node;
+ ListCell *lc;
+
+ if (stmt->withClause)
+ {
+ if (stmt->withClause->recursive)
+ {
+ /*
+ * In the RECURSIVE case, all query names of the WITH are
+ * visible to all WITH items as well as the main query.
+ * So push them all on, process, pop them all off.
+ */
+ cstate->innerwiths = lcons(stmt->withClause->ctes,
+ cstate->innerwiths);
+ foreach(lc, stmt->withClause->ctes)
+ {
+ CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+
+ (void) makeDependencyGraphWalker(cte->ctequery, cstate);
+ }
+ (void) raw_expression_tree_walker(node,
+ makeDependencyGraphWalker,
+ (void *) cstate);
+ cstate->innerwiths = list_delete_first(cstate->innerwiths);
+ }
+ else
+ {
+ /*
+ * In the non-RECURSIVE case, query names are visible to
+ * the WITH items after them and to the main query.
+ */
+ ListCell *cell1;
+
+ cstate->innerwiths = lcons(NIL, cstate->innerwiths);
+ cell1 = list_head(cstate->innerwiths);
+ foreach(lc, stmt->withClause->ctes)
+ {
+ CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+
+ (void) makeDependencyGraphWalker(cte->ctequery, cstate);
+ lfirst(cell1) = lappend((List *) lfirst(cell1), cte);
+ }
+ (void) raw_expression_tree_walker(node,
+ makeDependencyGraphWalker,
+ (void *) cstate);
+ cstate->innerwiths = list_delete_first(cstate->innerwiths);
+ }
+ /* We're done examining the SelectStmt */
+ return false;
+ }
+ /* if no WITH clause, just fall through for normal processing */
+ }
+ if (IsA(node, WithClause))
+ {
+ /*
+ * Prevent raw_expression_tree_walker from recursing directly into
+ * a WITH clause. We need that to happen only under the control
+ * of the code above.
+ */
+ return false;
+ }
+ return raw_expression_tree_walker(node,
+ makeDependencyGraphWalker,
+ (void *) cstate);
+ }
+
+ /*
+ * Sort by dependencies, using a standard topological sort operation
+ */
+ static void
+ TopologicalSort(ParseState *pstate, CteItem *items, int numitems)
+ {
+ int i, j, k;
+
+ /* for each position in sequence ... */
+ for (i = 0; i < numitems; i++)
+ {
+ /* ... scan the remaining items to find one that has no dependencies */
+ for (j = i; j < numitems; j++)
+ {
+ if (!bms_is_empty(items[j].depends_on))
+ continue;
+
+ /*
+ * Found one. Move it to front and remove it from every other
+ * item's dependencies.
+ *
+ * Items before i are known to have no dependencies left,
+ * so we can skip them in this loop.
+ */
+ for (k = i; k < numitems; k++)
+ {
+ items[k].depends_on = bms_del_member(items[k].depends_on,
+ items[j].id);
+ }
+ /* swap */
+ if (i != j)
+ {
+ CteItem tmp;
+
+ tmp = items[i];
+ items[i] = items[j];
+ items[j] = tmp;
+ }
+ break;
+ }
+
+ /* if we didn't find one, the dependency graph has a cycle */
+ if (j >= numitems)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("mutual recursion between WITH items is not supported"),
+ parser_errposition(pstate, items[i].cte->location)));
+ }
+ }
+
+
+ static void
+ checkWellFormedCte(CteState *cstate)
+ {
+ int i;
+
+ non_recursive_term = NULL;
+
+ /* Find all the dependencies and sort into a safe processing order */
+ makeDependencyGraph(cstate);
+
+ for (i = 0; i < cstate->numitems; i++)
+ {
+ CommonTableExpr *cte = cstate->items[i].cte;
+ SelectStmt *stmt = (SelectStmt *) cte->ctequery;
+
+ if (stmt->op == SETOP_NONE)
+ {
+ RecursiveType r;
+
+ r = checkCteSelectStmt(cstate, stmt, i);
+ if (r != NON_RECURSIVE)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("recursive query must have a non-recursive term"),
+ parser_errposition(cstate->pstate, cte->location)));
+
+ if (non_recursive_term)
+ cstate->items[i].non_recursive_term = non_recursive_term;
+ non_recursive_term = NULL;
+ }
+ else
+ {
+ SelectStmt *larg = (SelectStmt *)stmt->larg;
+ SelectStmt *rarg = (SelectStmt *)stmt->rarg;
+ RecursiveType lresult, rresult;
+
+ lresult = checkCteSelectStmt(cstate, larg, i);
+ rresult = checkCteSelectStmt(cstate, rarg, i);
+
+ /* Check if a query is a recursive query */
+ if (lresult == NON_RECURSIVE && rresult == NON_RECURSIVE)
+ {
+ if (non_recursive_term)
+ cstate->items[i].non_recursive_term = non_recursive_term;
+ non_recursive_term = NULL;
+ }
+ else if (lresult == RECURSIVE_OTHER || rresult == RECURSIVE_OTHER)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("mutual recursion between WITH items is not supported"),
+ parser_errposition(cstate->pstate, cte->location)));
+
+ /*
+ * Check non-recursive-term UNION ALL recursive-term
+ */
+ else if (stmt->op == SETOP_UNION && stmt->all == true &&
+ lresult != NON_RECURSIVE)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("Left hand side of UNION ALL must be a non-recursive term in a recursive query")));
+
+ else if (stmt->op == SETOP_INTERSECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("non-recursive term and recursive term must not be combined with INTERSECT")));
+
+ else if (stmt->op == SETOP_EXCEPT)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("non-recursive term and recursive term must not be combined with EXCEPT")));
+
+ else if (stmt->op == SETOP_UNION && stmt->all != true)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("non-recursive term and recursive term must be combined with UNION ALL")));
+
+ else if (stmt->op == SETOP_UNION && stmt->all == true &&
+ rarg->op == SETOP_UNION)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("Right hand side of UNION ALL must not contain UNION operation")));
+
+ else if (stmt->sortClause)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("ORDER BY in a recursive query not allowed")));
+
+ else if (stmt->limitOffset || stmt->limitCount)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("LIMIT OFFSET in a recursive query not allowed")));
+
+ else if (stmt->lockingClause)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR UPDATE in a recursive query not allowed")));
+
+ else if (lresult == NON_RECURSIVE && rresult == RECURSIVE_SELF)
+ {
+ if (larg->distinctClause)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("DISTINCT in a non recursive term not allowed")));
+
+ if (rarg->distinctClause)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("DISTINCT in a recursive term not allowed")));
+
+ if (rarg->groupClause)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("GROUP BY in a recursive term not allowed")));
+
+ if (rarg->havingClause)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("HAVING in a recursive term not allowed")));
+
+ /*
+ * Save non_recursive_term.
+ */
+ if (non_recursive_term)
+ cstate->items[i].non_recursive_term = non_recursive_term;
+ else
+ cstate->items[i].non_recursive_term = (Node *) larg;
+ cte->cterecursive = true;
+ non_recursive_term = NULL;
+ }
+ else
+ elog(ERROR, "unknown error in checkWellFormedCte");
+ }
+ }
+ }
+
+ static RecursiveType checkCteSelectStmt(CteState *cstate, SelectStmt *n, int myindex)
+ {
+ RecursiveType r = NON_RECURSIVE;
+
+ if (n->op != SETOP_NONE)
+ {
+ RecursiveType r1, r2;
+
+ r1 = checkCteSelectStmt(cstate, n->larg, myindex);
+ r2 = checkCteSelectStmt(cstate, n->rarg, myindex);
+
+ /* Non-linear recursion is not allowed */
+ if (r1 == RECURSIVE_SELF && r2 == RECURSIVE_SELF)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ (errmsg("non-linear recursion not allowed"))));
+
+ else if (r1 == RECURSIVE_OTHER || r2 == RECURSIVE_OTHER)
+ return RECURSIVE_OTHER;
+ else if (r1 == RECURSIVE_SELF || r2 == RECURSIVE_SELF)
+ return RECURSIVE_SELF;
+ else
+ return NON_RECURSIVE;
+ }
+
+ if (n->targetList)
+ {
+ /* Cannot contain any recursive term in target list */
+ if (checkCteTargetList(cstate, n->targetList, myindex) != NON_RECURSIVE)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ (errmsg("target list having subquery which uses recursive name in a recursive term not allowed"))));
+ }
+
+ if (n->fromClause)
+ r = checkCteFromClause(cstate, n->fromClause, myindex);
+
+ if (n->whereClause)
+ {
+ /* Cannot contain any recursive names in WHERE-clause */
+ if (checkCteWhereClause(cstate, n->whereClause, myindex) != NON_RECURSIVE)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ (errmsg("WHERE clause having subqury which uses recursive name in a recursive term not allowed"))));
+
+ }
+
+ return r;
+ }
+
+ static RecursiveType checkCteTargetList(CteState *cstate, List *targetList, int myindex)
+ {
+ ListCell *lc;
+ RecursiveType result = NON_RECURSIVE;
+
+ foreach(lc, targetList)
+ {
+ RecursiveType r;
+ r = checkCteTargetListItem(cstate, lfirst(lc), myindex);
+ if (result == NON_RECURSIVE)
+ result = r;
+ else if (result == RECURSIVE_SELF && r == RECURSIVE_OTHER)
+ result = r;
+ }
+ return result;
+ }
+
+ /*
+ * Check if a target list item has a subquery which uses recursive name.
+ */
+ static RecursiveType checkCteTargetListItem(CteState *cstate, Node *n, int myindex)
+ {
+ if (IsA(n, ResTarget))
+ {
+ ResTarget *rs = (ResTarget *)n;
+
+ if (rs->val && IsA(rs->val, SubLink))
+ {
+ SubLink *sl = (SubLink *)rs->val;
+
+ if (sl->subselect && IsA(sl->subselect, SelectStmt))
+ return checkCteSelectStmt(cstate, (SelectStmt *) sl->subselect, myindex);
+ }
+ }
+
+ return NON_RECURSIVE;
+ }
+
+ static RecursiveType checkCteWhereClause(CteState *cstate, Node *n, int myindex)
+ {
+ if (IsA(n, SubLink))
+ return checkCteSelectStmt(cstate, (SelectStmt *) ((SubLink *) n)->subselect, myindex);
+ else if (IsA(n, A_Expr))
+ {
+ RecursiveType result;
+ A_Expr *expr = (A_Expr *)n;
+
+ if (expr->lexpr && IsA(expr->lexpr, SubLink))
+ {
+ result = checkCteWhereClause(cstate, (Node *) expr->lexpr, myindex);
+ if (result != NON_RECURSIVE)
+ return result;
+ }
+ if (expr->rexpr && IsA(expr->rexpr, SubLink))
+ {
+ result = checkCteWhereClause(cstate, (Node *) expr->rexpr, myindex);
+ if (result != NON_RECURSIVE)
+ return result;
+ }
+ }
+
+ return NON_RECURSIVE;
+ }
+
+ /*
+ * checkCteFromClause
+ */
+ static RecursiveType checkCteFromClause(CteState *cstate, List *fromClause, int myindex)
+ {
+ ListCell *lc;
+ RecursiveType result = NON_RECURSIVE;
+
+ foreach(lc, fromClause)
+ {
+ RecursiveType r;
+
+ r = checkCteFromClauseItem(cstate, lfirst(lc), myindex);
+ if (result == NON_RECURSIVE)
+ result = r;
+ else if (result == RECURSIVE_SELF && r == RECURSIVE_OTHER)
+ result = r;
+ }
+ return result;
+ }
+
+ static RecursiveType checkCteFromClauseItem(CteState *cstate, Node *n, int myindex)
+ {
+ if (IsA(n, RangeVar)) /* table reference */
+ {
+ RangeVar *rv = (RangeVar *)n;
+ if (rv->schemaname)
+ return NON_RECURSIVE;
+
+ return findCteName(cstate, rv->relname, myindex);
+ }
+ else if (IsA(n, RangeSubselect))
+ {
+ RangeSubselect *rs = (RangeSubselect *)n;
+
+ return checkCteSelectStmt(cstate, (SelectStmt *)rs->subquery, myindex);
+ }
+ else if (IsA(n, RangeFunction))
+ {
+ /* do nothing */
+ return NON_RECURSIVE;
+ }
+
+ /*
+ * SQL standard says that the following join type should not contain.
+ *
+ * 1. query including recursive query name and FULL OUTER JOIN is not allowed.
+ * 2. outer join query is not allowed if the right hand side of LEFT OUTER JOIN
+ * has recursive query name.
+ * 3. outer join query is not allowed if the left hand side of RIGHT OUTER JOIN
+ * has recursive query name.
+ *
+ */
+ else if (IsA(n, JoinExpr))
+ {
+ JoinExpr *je = (JoinExpr *) n;
+ RecursiveType larg, rarg;
+
+ larg = checkCteFromClauseItem(cstate, je->larg, myindex);
+ rarg = checkCteFromClauseItem(cstate, je->rarg, myindex);
+
+ /* We must not allow mutually recursion. */
+ if (larg == RECURSIVE_OTHER || rarg == RECURSIVE_OTHER)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot refer other recursive names")));
+
+ /* Condition #1 */
+ if (je->jointype == JOIN_FULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FULL JOIN in a recursive term not allowed")));
+ /* Condition #2 */
+ else if (je->jointype == JOIN_LEFT)
+ {
+ if (rarg != NON_RECURSIVE)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("LEFT JOIN in the right hand side of recursive term not allowed")));
+ }
+ /* Condition #3 */
+ else if (je->jointype == JOIN_RIGHT)
+ {
+ if (larg != NON_RECURSIVE)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RIGHT JOIN in the left hand side of recursive term not allowed")));
+ }
+
+ return RECURSIVE_SELF;
+ }
+ else
+ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(n));
+
+ return true;
+ }
+
+ static RecursiveType
+ findCteName(CteState *cstate, char *name, int myindex)
+ {
+ int i;
+
+ for (i = 0; i < cstate->numitems; i++)
+ {
+ CommonTableExpr *cte = cstate->items[i].cte;
+
+ if (strcmp(name, cte->ctename) != 0)
+ continue; /* not the right one */
+
+ if (i == myindex)
+ return RECURSIVE_SELF;
+
+ /*
+ * If the CTE has a non-recursive term, we can handle it as non-recursive query.
+ */
+ if (cstate->items[i].non_recursive_term)
+ {
+ if (non_recursive_term == NULL)
+ non_recursive_term = cstate->items[i].non_recursive_term;
+ return NON_RECURSIVE;
+ }
+
+ if (!cte->cterecursive)
+ return NON_RECURSIVE;
+
+ return RECURSIVE_OTHER;
+ }
+ return NON_RECURSIVE;
+ }
*** src/backend/parser/parse_relation.c.orig Mon Sep 1 16:42:44 2008
--- src/backend/parser/parse_relation.c Tue Sep 23 13:00:07 2008
***************
*** 1109,1114 ****
--- 1109,1193 ----
}
/*
+ * Add an entry for a CTE reference to the pstate's range table (p_rtable).
+ *
+ * This is much like addRangeTableEntry() except that it makes a CTE RTE.
+ */
+ RangeTblEntry *
+ addRangeTableEntryForCTE(ParseState *pstate,
+ CommonTableExpr *cte,
+ Index levelsup,
+ Alias *alias,
+ bool inFromCl)
+ {
+ RangeTblEntry *rte = makeNode(RangeTblEntry);
+ char *refname = alias ? alias->aliasname : cte->ctename;
+ Alias *eref;
+ int numaliases;
+ int varattno;
+ ListCell *lc;
+
+ rte->rtekind = RTE_CTE;
+ rte->ctename = cte->ctename;
+ rte->ctelevelsup = levelsup;
+
+ /* Self-reference if and only if CTE's parse analysis isn't completed */
+ rte->self_reference = !IsA(cte->ctequery, Query);
+ Assert(cte->cterecursive || !rte->self_reference);
+
+ rte->ctecoltypes = cte->ctecoltypes;
+ rte->ctecoltypmods = cte->ctecoltypmods;
+
+ rte->alias = alias;
+ if (alias)
+ eref = copyObject(alias);
+ else
+ eref = makeAlias(refname, NIL);
+ numaliases = list_length(eref->colnames);
+
+ /* fill in any unspecified alias columns */
+ varattno = 0;
+ foreach(lc, cte->ctecolnames)
+ {
+ varattno++;
+ if (varattno > numaliases)
+ eref->colnames = lappend(eref->colnames, lfirst(lc));
+ }
+ if (varattno < numaliases)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg("table \"%s\" has %d columns available but %d columns specified",
+ refname, varattno, numaliases)));
+
+ rte->eref = eref;
+
+ /*----------
+ * Flags:
+ * - this RTE should be expanded to include descendant tables,
+ * - this RTE is in the FROM clause,
+ * - this RTE should be checked for appropriate access rights.
+ *
+ * Subqueries are never checked for access rights.
+ *----------
+ */
+ rte->inh = false; /* never true for subqueries */
+ rte->inFromCl = inFromCl;
+
+ rte->requiredPerms = 0;
+ rte->checkAsUser = InvalidOid;
+
+ /*
+ * Add completed RTE to pstate's range table list, but not to join list
+ * nor namespace --- caller must do that if appropriate.
+ */
+ if (pstate != NULL)
+ pstate->p_rtable = lappend(pstate->p_rtable, rte);
+
+ return rte;
+ }
+
+
+ /*
* Has the specified refname been selected FOR UPDATE/FOR SHARE?
*
* Note: we pay no attention to whether it's FOR UPDATE vs FOR SHARE.
***************
*** 1444,1449 ****
--- 1523,1563 ----
}
}
break;
+ case RTE_CTE:
+ {
+ ListCell *aliasp_item = list_head(rte->eref->colnames);
+ ListCell *lct;
+ ListCell *lcm;
+
+ varattno = 0;
+ forboth(lct, rte->ctecoltypes, lcm, rte->ctecoltypmods)
+ {
+ Oid coltype = lfirst_oid(lct);
+ int32 coltypmod = lfirst_int(lcm);
+
+ varattno++;
+
+ if (colnames)
+ {
+ /* Assume there is one alias per output column */
+ char *label = strVal(lfirst(aliasp_item));
+
+ *colnames = lappend(*colnames, makeString(pstrdup(label)));
+ aliasp_item = lnext(aliasp_item);
+ }
+
+ if (colvars)
+ {
+ Var *varnode;
+
+ varnode = makeVar(rtindex, varattno,
+ coltype, coltypmod,
+ sublevels_up);
+ *colvars = lappend(*colvars, varnode);
+ }
+ }
+ }
+ break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
}
***************
*** 1750,1755 ****
--- 1864,1877 ----
*vartypmod = exprTypmod(aliasvar);
}
break;
+ case RTE_CTE:
+ {
+ /* CTE RTE --- get type info from lists in the RTE */
+ Assert(attnum > 0 && attnum <= list_length(rte->ctecoltypes));
+ *vartype = list_nth_oid(rte->ctecoltypes, attnum - 1);
+ *vartypmod = list_nth_int(rte->ctecoltypmods, attnum - 1);
+ }
+ break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
}
***************
*** 1788,1793 ****
--- 1910,1916 ----
break;
case RTE_SUBQUERY:
case RTE_VALUES:
+ case RTE_CTE:
/* Subselect and Values RTEs never have dropped columns */
result = false;
break;
*** src/backend/parser/parse_target.c.orig Mon Sep 1 16:42:44 2008
--- src/backend/parser/parse_target.c Mon Sep 22 19:21:52 2008
***************
*** 296,301 ****
--- 296,304 ----
case RTE_VALUES:
/* not a simple relation, leave it unmarked */
break;
+ case RTE_CTE:
+ /* XXX fixme */
+ break;
}
}
***************
*** 1176,1181 ****
--- 1179,1187 ----
* its result columns as RECORD, which is not allowed.
*/
break;
+ case RTE_CTE:
+ /* XXX fixme */
+ break;
}
/*
*** src/backend/parser/parse_type.c.orig Mon Sep 1 16:42:45 2008
--- src/backend/parser/parse_type.c Mon Sep 22 11:16:32 2008
***************
*** 611,616 ****
--- 611,617 ----
stmt->whereClause != NULL ||
stmt->groupClause != NIL ||
stmt->havingClause != NULL ||
+ stmt->withClause != NULL ||
stmt->valuesLists != NIL ||
stmt->sortClause != NIL ||
stmt->limitOffset != NULL ||
*** src/backend/rewrite/rewriteDefine.c.orig Mon Aug 25 18:42:34 2008
--- src/backend/rewrite/rewriteDefine.c Mon Sep 22 15:30:59 2008
***************
*** 635,651 ****
rte->checkAsUser = userid;
}
/* If there are sublinks, search for them and process their RTEs */
- /* ignore subqueries in rtable because we already processed them */
if (qry->hasSubLinks)
query_tree_walker(qry, setRuleCheckAsUser_walker, (void *) &userid,
! QTW_IGNORE_RT_SUBQUERIES);
}
/*
* Change the firing semantics of an existing rule.
- *
*/
void
EnableDisableRule(Relation rel, const char *rulename,
--- 635,657 ----
rte->checkAsUser = userid;
}
+ /* Recurse into subquery-in-WITH */
+ foreach(l, qry->cteList)
+ {
+ CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
+
+ setRuleCheckAsUser_Query((Query *) cte->ctequery, userid);
+ }
+
/* If there are sublinks, search for them and process their RTEs */
if (qry->hasSubLinks)
query_tree_walker(qry, setRuleCheckAsUser_walker, (void *) &userid,
! QTW_IGNORE_RC_SUBQUERIES);
}
/*
* Change the firing semantics of an existing rule.
*/
void
EnableDisableRule(Relation rel, const char *rulename,
*** src/backend/rewrite/rewriteHandler.c.orig Thu Aug 28 19:09:48 2008
--- src/backend/rewrite/rewriteHandler.c Mon Sep 22 15:31:00 2008
***************
*** 215,227 ****
}
}
/*
* Recurse into sublink subqueries, too. But we already did the ones in
! * the rtable.
*/
if (parsetree->hasSubLinks)
query_tree_walker(parsetree, acquireLocksOnSubLinks, NULL,
! QTW_IGNORE_RT_SUBQUERIES);
}
/*
--- 215,235 ----
}
}
+ /* Recurse into subqueries in WITH */
+ foreach(l, parsetree->cteList)
+ {
+ CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
+
+ AcquireRewriteLocks((Query *) cte->ctequery);
+ }
+
/*
* Recurse into sublink subqueries, too. But we already did the ones in
! * the rtable and cteList.
*/
if (parsetree->hasSubLinks)
query_tree_walker(parsetree, acquireLocksOnSubLinks, NULL,
! QTW_IGNORE_RC_SUBQUERIES);
}
/*
***************
*** 1256,1261 ****
--- 1264,1270 ----
fireRIRrules(Query *parsetree, List *activeRIRs)
{
int rt_index;
+ ListCell *lc;
/*
* don't try to convert this into a foreach loop, because rtable list can
***************
*** 1368,1380 ****
heap_close(rel, NoLock);
}
/*
* Recurse into sublink subqueries, too. But we already did the ones in
! * the rtable.
*/
if (parsetree->hasSubLinks)
query_tree_walker(parsetree, fireRIRonSubLink, (void *) activeRIRs,
! QTW_IGNORE_RT_SUBQUERIES);
return parsetree;
}
--- 1377,1398 ----
heap_close(rel, NoLock);
}
+ /* Recurse into subqueries in WITH */
+ foreach(lc, parsetree->cteList)
+ {
+ CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+
+ cte->ctequery = (Node *)
+ fireRIRrules((Query *) cte->ctequery, activeRIRs);
+ }
+
/*
* Recurse into sublink subqueries, too. But we already did the ones in
! * the rtable and cteList.
*/
if (parsetree->hasSubLinks)
query_tree_walker(parsetree, fireRIRonSubLink, (void *) activeRIRs,
! QTW_IGNORE_RC_SUBQUERIES);
return parsetree;
}
*** src/backend/rewrite/rewriteManip.c.orig Mon Sep 1 16:42:45 2008
--- src/backend/rewrite/rewriteManip.c Tue Sep 23 17:21:19 2008
***************
*** 183,195 ****
checkExprHasSubLink(Node *node)
{
/*
! * If a Query is passed, examine it --- but we need not recurse into
! * sub-Queries.
*/
return query_or_expression_tree_walker(node,
checkExprHasSubLink_walker,
NULL,
! QTW_IGNORE_RT_SUBQUERIES);
}
static bool
--- 183,195 ----
checkExprHasSubLink(Node *node)
{
/*
! * If a Query is passed, examine it --- but we should not recurse into
! * sub-Queries that are in its rangetable or CTE list.
*/
return query_or_expression_tree_walker(node,
checkExprHasSubLink_walker,
NULL,
! QTW_IGNORE_RC_SUBQUERIES);
}
static bool
***************
*** 543,549 ****
* that sublink are not affected, only outer references to vars that belong
* to the expression's original query level or parents thereof.
*
! * Aggref nodes are adjusted similarly.
*
* NOTE: although this has the form of a walker, we cheat and modify the
* Var nodes in-place. The given expression tree should have been copied
--- 543,549 ----
* that sublink are not affected, only outer references to vars that belong
* to the expression's original query level or parents thereof.
*
! * Likewise for other nodes containing levelsup fields, such as Aggref.
*
* NOTE: although this has the form of a walker, we cheat and modify the
* Var nodes in-place. The given expression tree should have been copied
***************
*** 585,590 ****
--- 585,601 ----
agg->agglevelsup += context->delta_sublevels_up;
/* fall through to recurse into argument */
}
+ if (IsA(node, RangeTblEntry))
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) node;
+
+ if (rte->rtekind == RTE_CTE)
+ {
+ if (rte->ctelevelsup >= context->min_sublevels_up)
+ rte->ctelevelsup += context->delta_sublevels_up;
+ }
+ return false; /* allow range_table_walker to continue */
+ }
if (IsA(node, Query))
{
/* Recurse into subselects */
***************
*** 593,599 ****
context->min_sublevels_up++;
result = query_tree_walker((Query *) node,
IncrementVarSublevelsUp_walker,
! (void *) context, 0);
context->min_sublevels_up--;
return result;
}
--- 604,611 ----
context->min_sublevels_up++;
result = query_tree_walker((Query *) node,
IncrementVarSublevelsUp_walker,
! (void *) context,
! QTW_EXAMINE_RTES);
context->min_sublevels_up--;
return result;
}
***************
*** 617,623 ****
query_or_expression_tree_walker(node,
IncrementVarSublevelsUp_walker,
(void *) &context,
! 0);
}
/*
--- 629,635 ----
query_or_expression_tree_walker(node,
IncrementVarSublevelsUp_walker,
(void *) &context,
! QTW_EXAMINE_RTES);
}
/*
***************
*** 636,642 ****
range_table_walker(rtable,
IncrementVarSublevelsUp_walker,
(void *) &context,
! 0);
}
--- 648,654 ----
range_table_walker(rtable,
IncrementVarSublevelsUp_walker,
(void *) &context,
! QTW_EXAMINE_RTES);
}
*** src/backend/utils/adt/ruleutils.c.orig Sat Sep 6 16:18:08 2008
--- src/backend/utils/adt/ruleutils.c Tue Sep 23 12:03:46 2008
***************
*** 145,150 ****
--- 145,151 ----
static void get_query_def(Query *query, StringInfo buf, List *parentnamespace,
TupleDesc resultDesc, int prettyFlags, int startIndent);
static void get_values_def(List *values_lists, deparse_context *context);
+ static void get_with_clause(Query *query, deparse_context *context);
static void get_select_query_def(Query *query, deparse_context *context,
TupleDesc resultDesc);
static void get_insert_query_def(Query *query, deparse_context *context);
***************
*** 2205,2210 ****
--- 2206,2261 ----
}
/* ----------
+ * get_with_clause - Parse back a WITH clause
+ * ----------
+ */
+ static void
+ get_with_clause(Query *query, deparse_context *context)
+ {
+ StringInfo buf = context->buf;
+ const char *sep;
+ ListCell *l;
+
+ if (query->cteList == NIL)
+ return;
+
+ if (query->hasRecursive)
+ sep = "WITH RECURSIVE ";
+ else
+ sep = "WITH ";
+ foreach(l, query->cteList)
+ {
+ CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
+
+ appendStringInfoString(buf, sep);
+ appendStringInfoString(buf, quote_identifier(cte->ctename));
+ if (cte->aliascolnames)
+ {
+ bool first = true;
+ ListCell *col;
+
+ appendStringInfoChar(buf, '(');
+ foreach(col, cte->aliascolnames)
+ {
+ if (first)
+ first = false;
+ else
+ appendStringInfoString(buf, ", ");
+ appendStringInfoString(buf,
+ quote_identifier(strVal(lfirst(col))));
+ }
+ appendStringInfoChar(buf, ')');
+ }
+ appendStringInfoString(buf, " AS (");
+ get_query_def((Query *) cte->ctequery, buf, context->namespaces, NULL,
+ context->prettyFlags, context->indentLevel);
+ appendStringInfoChar(buf, ')');
+ sep = ", ";
+ }
+ appendStringInfoChar(buf, ' ');
+ }
+
+ /* ----------
* get_select_query_def - Parse back a SELECT parsetree
* ----------
*/
***************
*** 2357,2362 ****
--- 2408,2418 ----
}
/*
+ * Insert the WITH clause if given
+ */
+ get_with_clause(query, context);
+
+ /*
* Build up the query string - first we say SELECT
*/
appendStringInfo(buf, "SELECT");
***************
*** 3259,3264 ****
--- 3315,3321 ----
case RTE_RELATION:
case RTE_SPECIAL:
case RTE_VALUES:
+ case RTE_CTE: /* XXX fixme */
/*
* This case should not occur: a column of a table or values list
***************
*** 5130,5135 ****
--- 5187,5197 ----
/* Values list RTE */
get_values_def(rte->values_lists, context);
break;
+ case RTE_CTE:
+ appendStringInfo(buf, "%s%s",
+ only_marker(rte),
+ rte->ctename);
+ break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
break;
*** src/backend/utils/cache/plancache.c.orig Mon Sep 15 19:37:39 2008
--- src/backend/utils/cache/plancache.c Mon Sep 22 15:31:46 2008
***************
*** 728,742 ****
}
}
/*
* Recurse into sublink subqueries, too. But we already did the ones in
! * the rtable.
*/
if (parsetree->hasSubLinks)
{
query_tree_walker(parsetree, ScanQueryWalker,
(void *) &acquire,
! QTW_IGNORE_RT_SUBQUERIES);
}
}
--- 728,750 ----
}
}
+ /* Recurse into subquery-in-WITH */
+ foreach(lc, parsetree->cteList)
+ {
+ CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+
+ ScanQueryForLocks((Query *) cte->ctequery, acquire);
+ }
+
/*
* Recurse into sublink subqueries, too. But we already did the ones in
! * the rtable and cteList.
*/
if (parsetree->hasSubLinks)
{
query_tree_walker(parsetree, ScanQueryWalker,
(void *) &acquire,
! QTW_IGNORE_RC_SUBQUERIES);
}
}
*** src/bin/psql/tab-complete.c.orig Sun Sep 7 20:47:40 2008
--- src/bin/psql/tab-complete.c Mon Sep 22 11:16:32 2008
***************
*** 613,621 ****
"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
"DELETE FROM", "DISCARD", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
! "REASSIGN", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK",
"SAVEPOINT", "SELECT", "SET", "SHOW", "START", "TRUNCATE", "UNLISTEN",
! "UPDATE", "VACUUM", "VALUES", NULL
};
static const char *const backslash_commands[] = {
--- 613,621 ----
"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
"DELETE FROM", "DISCARD", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
! "REASSIGN", "RECURSIVE", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK",
"SAVEPOINT", "SELECT", "SET", "SHOW", "START", "TRUNCATE", "UNLISTEN",
! "UPDATE", "VACUUM", "VALUES", "WITH", NULL
};
static const char *const backslash_commands[] = {
***************
*** 2044,2049 ****
--- 2044,2058 ----
pg_strcasecmp(prev2_wd, "ANALYZE") == 0))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+ /* WITH [RECURSIVE] */
+ else if (pg_strcasecmp(prev_wd, "WITH") == 0)
+ {
+ static const char *const list_WITH[] =
+ {"RECURSIVE", NULL};
+
+ COMPLETE_WITH_LIST(list_WITH);
+ }
+
/* ANALYZE */
/* If the previous word is ANALYZE, produce list of tables */
else if (pg_strcasecmp(prev_wd, "ANALYZE") == 0)
*** src/include/executor/nodeAppend.h.orig Tue Jan 1 14:45:57 2008
--- src/include/executor/nodeAppend.h Mon Sep 22 11:16:32 2008
***************
*** 21,25 ****
--- 21,26 ----
extern TupleTableSlot *ExecAppend(AppendState *node);
extern void ExecEndAppend(AppendState *node);
extern void ExecReScanAppend(AppendState *node, ExprContext *exprCtxt);
+ extern bool exec_append_initialize_next(AppendState *appendstate);
#endif /* NODEAPPEND_H */
*** src/include/executor/nodeRecursion.h.orig Mon Sep 22 11:16:32 2008
--- src/include/executor/nodeRecursion.h Mon Sep 22 11:16:32 2008
***************
*** 0 ****
--- 1,25 ----
+ /*-------------------------------------------------------------------------
+ *
+ * nodeRecursion.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef NODERECURSION_H
+ #define NODERECURSION_H
+
+ #include "nodes/execnodes.h"
+
+ extern int ExecCountSlotsRecursion(Recursion *node);
+ extern RecursionState *ExecInitRecursion(Recursion *node, EState *estate, int eflags);
+ extern TupleTableSlot *ExecRecursion(RecursionState *node);
+ extern void ExecEndRecursion(RecursionState *node);
+ extern void ExecRecursionReScan(RecursionState *node, ExprContext *exprCtxt);
+
+ #endif /* NODESUBQUERYSCAN_H */
*** src/include/executor/nodeRecursivescan.h.orig Mon Sep 22 11:16:32 2008
--- src/include/executor/nodeRecursivescan.h Mon Sep 22 11:16:32 2008
***************
*** 0 ****
--- 1,27 ----
+ /*-------------------------------------------------------------------------
+ *
+ * nodeRecursivescan.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef NODERECURSIVESCAN_H
+ #define NODERECURSIVESCAN_H
+
+ #include "nodes/execnodes.h"
+
+ extern int ExecCountSlotsRecursiveScan(RecursiveScan *node);
+ extern RecursiveScanState *ExecInitRecursiveScan(RecursiveScan *node, EState *estate, int eflags);
+ extern TupleTableSlot *ExecRecursiveScan(RecursiveScanState *node);
+ extern void ExecEndRecursiveScan(RecursiveScanState *node);
+ extern void ExecRecursiveScanMarkPos(RecursiveScanState *node);
+ extern void ExecRecursiveScanRestrPos(RecursiveScanState *node);
+ extern void ExecRecursiveScanReScan(RecursiveScanState *node, ExprContext *exprCtxt);
+
+ #endif /* NODERECURSIVESCAN_H */
*** src/include/nodes/execnodes.h.orig Thu Aug 21 20:16:04 2008
--- src/include/nodes/execnodes.h Mon Sep 22 11:16:32 2008
***************
*** 349,354 ****
--- 349,357 ----
List *es_subplanstates; /* List of PlanState for SubPlans */
+ Tuplestorestate **es_tuplestorestate; /* Stuff used for recursive query */
+ TupleDesc es_rscan_tupledesc;
+
/*
* this ExprContext is for per-output-tuple operations, such as constraint
* checks and index-value computations. It will be reset for each output
***************
*** 892,897 ****
--- 895,901 ----
* State for management of parameter-change-driven rescanning
*/
Bitmapset *chgParam; /* set of IDs of changed Params */
+ bool has_recursivescan;
/*
* Other run-time state needed by most if not all node types.
***************
*** 1179,1184 ****
--- 1183,1215 ----
int marked_idx;
} ValuesScanState;
+ /* ----------------
+ * RecursionState information
+ *
+ * RecursionState is used for scanning a recursive query.
+ * ----------------
+ */
+ typedef struct RecursionState
+ {
+ ScanState ss; /* its first field is NodeTag */
+ PlanState *subplan;
+ bool recursive_empty;
+ Tuplestorestate *intermediate_tuplestorestate;
+ Tuplestorestate *working_table;
+ bool init_done;
+ } RecursionState;
+
+ /* ----------------
+ * RecursiveScanState information
+ * ----------------
+ */
+ typedef struct RecursiveScanState
+ {
+ ScanState ss; /* its first field is NodeTag */
+ TupleDesc tupdesc;
+ Tuplestorestate **working_table;
+ } RecursiveScanState;
+
/* ----------------------------------------------------------------
* Join State Information
* ----------------------------------------------------------------
*** src/include/nodes/nodeFuncs.h.orig Thu Aug 28 19:09:48 2008
--- src/include/nodes/nodeFuncs.h Tue Sep 23 19:58:05 2008
***************
*** 18,25 ****
/* flags bits for query_tree_walker and query_tree_mutator */
#define QTW_IGNORE_RT_SUBQUERIES 0x01 /* subqueries in rtable */
! #define QTW_IGNORE_JOINALIASES 0x02 /* JOIN alias var lists */
! #define QTW_DONT_COPY_QUERY 0x04 /* do not copy top Query */
extern Oid exprType(Node *expr);
--- 18,28 ----
/* flags bits for query_tree_walker and query_tree_mutator */
#define QTW_IGNORE_RT_SUBQUERIES 0x01 /* subqueries in rtable */
! #define QTW_IGNORE_CTE_SUBQUERIES 0x02 /* subqueries in cteList */
! #define QTW_IGNORE_RC_SUBQUERIES 0x03 /* both of above */
! #define QTW_IGNORE_JOINALIASES 0x04 /* JOIN alias var lists */
! #define QTW_EXAMINE_RTES 0x08 /* examine RTEs */
! #define QTW_DONT_COPY_QUERY 0x10 /* do not copy top Query */
extern Oid exprType(Node *expr);
***************
*** 49,52 ****
--- 52,58 ----
extern Node *query_or_expression_tree_mutator(Node *node, Node *(*mutator) (),
void *context, int flags);
+ extern bool raw_expression_tree_walker(Node *node, bool (*walker) (),
+ void *context);
+
#endif /* NODEFUNCS_H */
*** src/include/nodes/nodes.h.orig Tue Sep 9 14:58:08 2008
--- src/include/nodes/nodes.h Mon Sep 22 13:32:47 2008
***************
*** 55,65 ****
--- 55,67 ----
T_SubqueryScan,
T_FunctionScan,
T_ValuesScan,
+ T_RecursiveScan,
T_Join,
T_NestLoop,
T_MergeJoin,
T_HashJoin,
T_Material,
+ T_Recursion,
T_Sort,
T_Group,
T_Agg,
***************
*** 89,99 ****
--- 91,103 ----
T_SubqueryScanState,
T_FunctionScanState,
T_ValuesScanState,
+ T_RecursiveScanState,
T_JoinState,
T_NestLoopState,
T_MergeJoinState,
T_HashJoinState,
T_MaterialState,
+ T_RecursionState,
T_SortState,
T_GroupState,
T_AggState,
***************
*** 352,357 ****
--- 356,363 ----
T_LockingClause,
T_RowMarkClause,
T_XmlSerialize,
+ T_WithClause,
+ T_CommonTableExpr,
/*
* TAGS FOR RANDOM OTHER STUFF
*** src/include/nodes/parsenodes.h.orig Sun Sep 7 20:47:41 2008
--- src/include/nodes/parsenodes.h Tue Sep 23 12:02:48 2008
***************
*** 115,120 ****
--- 115,123 ----
bool hasAggs; /* has aggregates in tlist or havingQual */
bool hasSubLinks; /* has subquery SubLink */
bool hasDistinctOn; /* distinctClause is from DISTINCT ON */
+ bool hasRecursive; /* WITH RECURSIVE was specified */
+
+ List *cteList; /* WITH list (of CommonTableExpr's) */
List *rtable; /* list of range table entries */
FromExpr *jointree; /* table join tree (FROM and WHERE clauses) */
***************
*** 563,569 ****
RTE_JOIN, /* join */
RTE_SPECIAL, /* special rule relation (NEW or OLD) */
RTE_FUNCTION, /* function in FROM */
! RTE_VALUES /* VALUES (), (), ... */
} RTEKind;
typedef struct RangeTblEntry
--- 566,573 ----
RTE_JOIN, /* join */
RTE_SPECIAL, /* special rule relation (NEW or OLD) */
RTE_FUNCTION, /* function in FROM */
! RTE_VALUES, /* VALUES (), (), ... */
! RTE_CTE /* common table expr (WITH list element) */
} RTEKind;
typedef struct RangeTblEntry
***************
*** 589,594 ****
--- 593,612 ----
Query *subquery; /* the sub-query */
/*
+ * Fields valid for a join RTE (else NULL/zero):
+ *
+ * joinaliasvars is a list of Vars or COALESCE expressions corresponding
+ * to the columns of the join result. An alias Var referencing column K
+ * of the join result can be replaced by the K'th element of joinaliasvars
+ * --- but to simplify the task of reverse-listing aliases correctly, we
+ * do not do that until planning time. In a Query loaded from a stored
+ * rule, it is also possible for joinaliasvars items to be NULL Consts,
+ * denoting columns dropped since the rule was made.
+ */
+ JoinType jointype; /* type of join */
+ List *joinaliasvars; /* list of alias-var expansions */
+
+ /*
* Fields valid for a function RTE (else NULL):
*
* If the function returns RECORD, funccoltypes lists the column types
***************
*** 605,622 ****
List *values_lists; /* list of expression lists */
/*
! * Fields valid for a join RTE (else NULL/zero):
! *
! * joinaliasvars is a list of Vars or COALESCE expressions corresponding
! * to the columns of the join result. An alias Var referencing column K
! * of the join result can be replaced by the K'th element of joinaliasvars
! * --- but to simplify the task of reverse-listing aliases correctly, we
! * do not do that until planning time. In a Query loaded from a stored
! * rule, it is also possible for joinaliasvars items to be NULL Consts,
! * denoting columns dropped since the rule was made.
*/
! JoinType jointype; /* type of join */
! List *joinaliasvars; /* list of alias-var expansions */
/*
* Fields valid in all RTEs:
--- 623,635 ----
List *values_lists; /* list of expression lists */
/*
! * Fields valid for a CTE RTE (else NULL/zero):
*/
! char *ctename; /* name of the WITH list item */
! Index ctelevelsup; /* number of query levels up */
! bool self_reference; /* is this a recursive self-reference? */
! List *ctecoltypes; /* OID list of column type OIDs */
! List *ctecoltypmods; /* integer list of column typmods */
/*
* Fields valid in all RTEs:
***************
*** 697,702 ****
--- 710,750 ----
bool noWait; /* NOWAIT option */
} RowMarkClause;
+ /*
+ * WithClause -
+ * representation of WITH clause
+ *
+ * Note: WithClause does not propagate into the Query representation;
+ * but CommonTableExpr does.
+ */
+ typedef struct WithClause
+ {
+ NodeTag type;
+ List *ctes; /* list of CommonTableExprs */
+ bool recursive; /* true = WITH RECURSIVE */
+ int location; /* token location, or -1 if unknown */
+ } WithClause;
+
+ /*
+ * CommonTableExpr -
+ * representation of WITH list element
+ *
+ * We don't currently support the SEARCH or CYCLE clause.
+ */
+ typedef struct CommonTableExpr
+ {
+ NodeTag type;
+ char *ctename; /* query name (never qualified) */
+ List *aliascolnames; /* optional list of column names */
+ Node *ctequery; /* subquery (SelectStmt or Query) */
+ int location; /* token location, or -1 if unknown */
+ /* These fields are set during parse analysis: */
+ bool cterecursive; /* is this CTE actually recursive? */
+ List *ctecolnames; /* list of output column names */
+ List *ctecoltypes; /* OID list of output column type OIDs */
+ List *ctecoltypmods; /* integer list of output column typmods */
+ } CommonTableExpr;
+
/*****************************************************************************
* Optimizable Statements
*****************************************************************************/
***************
*** 781,786 ****
--- 829,835 ----
Node *whereClause; /* WHERE qualification */
List *groupClause; /* GROUP BY clauses */
Node *havingClause; /* HAVING conditional-expression */
+ WithClause *withClause; /* WITH clause */
/*
* In a "leaf" node representing a VALUES list, the above fields are all
*** src/include/nodes/plannodes.h.orig Tue Sep 9 14:58:08 2008
--- src/include/nodes/plannodes.h Mon Sep 22 11:16:32 2008
***************
*** 358,363 ****
--- 358,384 ----
List *values_lists; /* list of expression lists */
} ValuesScan;
+ /* ----------------
+ * RecursiveScan node
+ * ----------------
+ */
+ typedef struct RecursiveScan
+ {
+ Scan scan;
+ } RecursiveScan;
+
+ /* ----------------
+ * Recursion node
+ * ----------------
+ */
+ typedef struct Recursion
+ {
+ Scan scan;
+ Plan *subplan;
+ List *subrtable; /* temporary workspace for planner */
+ } Recursion;
+
+
/*
* ==========
* Join nodes
*** src/include/nodes/relation.h.orig Tue Sep 9 14:58:08 2008
--- src/include/nodes/relation.h Tue Sep 23 16:29:39 2008
***************
*** 104,109 ****
--- 104,111 ----
Index query_level; /* 1 at the outermost Query */
+ struct PlannerInfo *parent_root; /* NULL at outermost Query */
+
/*
* simple_rel_array holds pointers to "base rels" and "other rels" (see
* comments for RelOptInfo for more info). It is indexed by rangetable
*** src/include/optimizer/cost.h.orig Thu Aug 21 20:16:04 2008
--- src/include/optimizer/cost.h Mon Sep 22 11:16:32 2008
***************
*** 72,77 ****
--- 72,80 ----
RelOptInfo *baserel);
extern void cost_valuesscan(Path *path, PlannerInfo *root,
RelOptInfo *baserel);
+ extern void cost_recursion(Path *path, RelOptInfo *baserel);
+ extern void cost_recursivescan(Path *path, PlannerInfo *root,
+ RelOptInfo *baserel);
extern void cost_sort(Path *path, PlannerInfo *root,
List *pathkeys, Cost input_cost, double tuples, int width,
double limit_tuples);
***************
*** 104,109 ****
--- 107,113 ----
List *restrictlist);
extern void set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel);
extern void set_values_size_estimates(PlannerInfo *root, RelOptInfo *rel);
+ extern void set_recursivescan_size_estimates(PlannerInfo *root, RelOptInfo *rel);
/*
* prototypes for clausesel.c
*** src/include/optimizer/pathnode.h.orig Thu Aug 14 14:48:00 2008
--- src/include/optimizer/pathnode.h Mon Sep 22 11:16:32 2008
***************
*** 54,59 ****
--- 54,61 ----
extern Path *create_subqueryscan_path(RelOptInfo *rel, List *pathkeys);
extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel);
extern Path *create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel);
+ extern Path *create_recursion_path(RelOptInfo *rel, List *pathkeys);
+ extern Path *create_recursivescan_path(PlannerInfo *root, RelOptInfo *rel);
extern NestPath *create_nestloop_path(PlannerInfo *root,
RelOptInfo *joinrel,
*** src/include/optimizer/planner.h.orig Tue Jan 1 14:45:58 2008
--- src/include/optimizer/planner.h Tue Sep 23 16:33:47 2008
***************
*** 30,36 ****
extern PlannedStmt *standard_planner(Query *parse, int cursorOptions,
ParamListInfo boundParams);
extern Plan *subquery_planner(PlannerGlobal *glob, Query *parse,
! Index level, double tuple_fraction,
PlannerInfo **subroot);
#endif /* PLANNER_H */
--- 30,36 ----
extern PlannedStmt *standard_planner(Query *parse, int cursorOptions,
ParamListInfo boundParams);
extern Plan *subquery_planner(PlannerGlobal *glob, Query *parse,
! PlannerInfo *parent_root, double tuple_fraction,
PlannerInfo **subroot);
#endif /* PLANNER_H */
*** src/include/parser/parse_cte.h.orig Mon Sep 22 11:16:32 2008
--- src/include/parser/parse_cte.h Tue Sep 23 12:23:54 2008
***************
*** 0 ****
--- 1,21 ----
+ /*-------------------------------------------------------------------------
+ *
+ * parse_cte.h
+ * handle CTEs (common table expressions) in parser
+ *
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef PARSE_CTE_H
+ #define PARSE_CTE_H
+
+ #include "parser/parse_node.h"
+
+ extern List *transformWithClause(ParseState *pstate, WithClause *withClause);
+
+ #endif /* PARSE_CTE_H */
*** src/include/parser/parse_node.h.orig Mon Sep 1 16:42:45 2008
--- src/include/parser/parse_node.h Tue Sep 23 14:02:46 2008
***************
*** 50,55 ****
--- 50,59 ----
* implicit RTEs into p_relnamespace but not p_varnamespace, so that they
* do not affect the set of columns available for unqualified references.
*
+ * p_ctenamespace: list of CommonTableExprs (WITH items) that are visible
+ * at the moment. This is different from p_relnamespace because you have
+ * to make an RTE before you can access a CTE.
+ *
* p_paramtypes: an array of p_numparams type OIDs for $n parameter symbols
* (zeroth entry in array corresponds to $1). If p_variableparams is true, the
* set of param types is not predetermined; in that case, a zero array entry
***************
*** 68,73 ****
--- 72,78 ----
* node's fromlist) */
List *p_relnamespace; /* current namespace for relations */
List *p_varnamespace; /* current namespace for columns */
+ List *p_ctenamespace; /* current namespace for common table exprs */
Oid *p_paramtypes; /* OIDs of types for $n parameter symbols */
int p_numparams; /* allocated size of p_paramtypes[] */
int p_next_resno; /* next targetlist resno to assign */
*** src/include/parser/parse_relation.h.orig Mon Sep 1 16:42:45 2008
--- src/include/parser/parse_relation.h Tue Sep 23 10:55:36 2008
***************
*** 72,77 ****
--- 72,82 ----
List *aliasvars,
Alias *alias,
bool inFromCl);
+ extern RangeTblEntry *addRangeTableEntryForCTE(ParseState *pstate,
+ CommonTableExpr *cte,
+ Index levelsup,
+ Alias *alias,
+ bool inFromCl);
extern void addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
bool addToJoinList,
bool addToRelNameSpace, bool addToVarNameSpace);
*** src/interfaces/ecpg/preproc/preproc.y.orig Wed Aug 20 10:09:16 2008
--- src/interfaces/ecpg/preproc/preproc.y Mon Sep 22 11:16:32 2008
***************
*** 473,479 ****
QUOTE
! READ REAL REASSIGN RECHECK REFERENCES REINDEX RELATIVE_P RELEASE RENAME
REPEATABLE REPLACE REPLICA RESET RESTART RESTRICT RETURNING RETURNS REVOKE
RIGHT ROLE ROLLBACK ROW ROWS RULE
--- 473,479 ----
QUOTE
! READ REAL REASSIGN RECHECK RECURSIVE REFERENCES REINDEX RELATIVE_P RELEASE RENAME
REPEATABLE REPLACE REPLICA RESET RESTART RESTRICT RETURNING RETURNS REVOKE
RIGHT ROLE ROLLBACK ROW ROWS RULE
*** src/test/regress/expected/recursive.out.orig Mon Sep 22 11:16:32 2008
--- src/test/regress/expected/recursive.out Tue Sep 23 21:23:28 2008
***************
*** 0 ****
--- 1,557 ----
+ CREATE TEMP TABLE department (
+ id INTEGER PRIMARY KEY, -- department ID
+ parent_department INTEGER REFERENCES department, -- upper department ID
+ name TEXT -- department name
+ );
+ NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "department_pkey" for table "department"
+ INSERT INTO department VALUES (0, NULL, 'ROOT');
+ INSERT INTO department VALUES (1, 0, 'A');
+ INSERT INTO department VALUES (2, 1, 'B');
+ INSERT INTO department VALUES (3, 2, 'C');
+ INSERT INTO department VALUES (4, 2, 'D');
+ INSERT INTO department VALUES (5, 0, 'E');
+ INSERT INTO department VALUES (6, 4, 'F');
+ INSERT INTO department VALUES (7, 5, 'G');
+ -- department structure represented here is as follows:
+ --
+ -- ROOT-+->A-+->B-+->C
+ -- | |
+ -- | +->D-+->F
+ -- +->E-+->G
+ -- extract all departments under 'A'. Result should be A, B, C, D and F
+ WITH RECURSIVE subdepartment AS
+ (
+ -- non recursive term
+ SELECT * FROM department WHERE name = 'A'
+ UNION ALL
+ -- recursive term
+ SELECT d.* FROM department AS d, subdepartment AS sd
+ WHERE d.parent_department = sd.id
+ )
+ SELECT * FROM subdepartment ORDER BY name;
+ id | parent_department | name
+ ----+-------------------+------
+ 1 | 0 | A
+ 2 | 1 | B
+ 3 | 2 | C
+ 4 | 2 | D
+ 6 | 4 | F
+ (5 rows)
+
+ -- extract all departments under 'A' with "level" number
+ WITH RECURSIVE subdepartment(level, id, parent_department, name) AS
+ (
+ -- non recursive term
+ SELECT 1, * FROM department WHERE name = 'A'
+ UNION ALL
+ -- recursive term
+ SELECT sd.level + 1, d.* FROM department AS d, subdepartment AS sd
+ WHERE d.parent_department = sd.id
+ )
+ SELECT * FROM subdepartment ORDER BY name;
+ level | id | parent_department | name
+ -------+----+-------------------+------
+ 1 | 1 | 0 | A
+ 2 | 2 | 1 | B
+ 3 | 3 | 2 | C
+ 3 | 4 | 2 | D
+ 4 | 6 | 4 | F
+ (5 rows)
+
+ -- extract all departments under 'A' with "level" number.
+ -- Only shows 2 level or more
+ WITH RECURSIVE subdepartment(level, id, parent_department, name) AS
+ (
+ -- non recursive term
+ SELECT 1, * FROM department WHERE name = 'A'
+ UNION ALL
+ -- recursive term
+ SELECT sd.level + 1, d.* FROM department AS d, subdepartment AS sd
+ WHERE d.parent_department = sd.id
+ )
+ SELECT * FROM subdepartment WHERE level >= 2 ORDER BY name;
+ level | id | parent_department | name
+ -------+----+-------------------+------
+ 2 | 2 | 1 | B
+ 3 | 3 | 2 | C
+ 3 | 4 | 2 | D
+ 4 | 6 | 4 | F
+ (4 rows)
+
+ -- sum of 1..100
+ WITH RECURSIVE t(n) AS (
+ VALUES (1)
+ UNION ALL
+ SELECT n+1
+ FROM t
+ WHERE n < 100
+ )
+ SELECT sum(n) FROM t;
+ sum
+ ------
+ 5050
+ (1 row)
+
+ -- non recursive term only case: should return 1 row
+ WITH RECURSIVE subdepartment AS
+ (
+ -- non recursive term
+ SELECT * FROM department WHERE name = 'A'
+ )
+ SELECT * FROM subdepartment ORDER BY name;
+ id | parent_department | name
+ ----+-------------------+------
+ 1 | 0 | A
+ (1 row)
+
+ -- subquery
+ SELECT count(*) FROM (
+ WITH RECURSIVE t(n) AS (
+ SELECT 1 UNION ALL SELECT n + 1 FROM t WHERE n < 500
+ )
+ SELECT * FROM t) AS t WHERE n < (
+ SELECT count(*) FROM (
+ WITH RECURSIVE t(n) AS (
+ SELECT 1 UNION ALL SELECT n + 1 FROM t WHERE n < 100
+ )
+ SELECT * FROM t WHERE n < 50000) as t WHERE n < 100);
+ count
+ -------
+ 98
+ (1 row)
+
+ -- VIEW
+ CREATE TEMPORARY VIEW vsubdepartment AS
+ WITH RECURSIVE subdepartment AS
+ (
+ -- non recursive term
+ SELECT * FROM department WHERE name = 'A'
+ UNION ALL
+ -- recursive term
+ SELECT d.* FROM department AS d, subdepartment AS sd
+ WHERE d.parent_department = sd.id
+ )
+ SELECT * FROM subdepartment;
+ SELECT * FROM vsubdepartment ORDER BY name;
+ id | parent_department | name
+ ----+-------------------+------
+ 1 | 0 | A
+ 2 | 1 | B
+ 3 | 2 | C
+ 4 | 2 | D
+ 6 | 4 | F
+ (5 rows)
+
+ -- recursive term has UNION
+ WITH RECURSIVE t(i,j) AS (
+ VALUES (1,2)
+ UNION ALL
+ SELECT t2.i, t.j FROM (SELECT 2 AS i UNION ALL SELECT 3 AS i) AS t2
+ JOIN t ON (t2.i = t.i))
+ SELECT * FROM t;
+ i | j
+ ---+---
+ 1 | 2
+ (1 row)
+
+ CREATE TEMPORARY TABLE tree(
+ id INTEGER PRIMARY KEY,
+ parent_id INTEGER REFERENCES tree(id)
+ );
+ NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "tree_pkey" for table "tree"
+ INSERT INTO tree
+ VALUES (1, NULL), (2, 1), (3,1), (4,2), (5,2), (6,2), (7,3), (8,3),
+ (9,4), (10,4), (11,7), (12,7), (13,7), (14, 9), (15,11), (16,11);
+ --
+ -- get all path from the "second level" node to leaf nodes
+ --
+ WITH RECURSIVE t(id, path) AS (
+ VALUES(1,ARRAY[NULL::integer])
+ UNION ALL
+ SELECT tree.id, t.path || tree.id
+ FROM tree JOIN t ON (tree.parent_id = t.id)
+ )
+ SELECT t1.*, t2.* FROM t AS t1 JOIN t AS t2 ON
+ (t1.path[1:2] = t2.path[1:2] AND
+ array_upper(t1.path,1) = 2 AND
+ array_upper(t2.path,1) > 2);
+ id | path | id | path
+ ----+----------+----+------------------
+ 2 | {NULL,2} | 4 | {NULL,2,4}
+ 2 | {NULL,2} | 5 | {NULL,2,5}
+ 2 | {NULL,2} | 6 | {NULL,2,6}
+ 2 | {NULL,2} | 9 | {NULL,2,4,9}
+ 2 | {NULL,2} | 10 | {NULL,2,4,10}
+ 2 | {NULL,2} | 14 | {NULL,2,4,9,14}
+ 3 | {NULL,3} | 7 | {NULL,3,7}
+ 3 | {NULL,3} | 8 | {NULL,3,8}
+ 3 | {NULL,3} | 11 | {NULL,3,7,11}
+ 3 | {NULL,3} | 12 | {NULL,3,7,12}
+ 3 | {NULL,3} | 13 | {NULL,3,7,13}
+ 3 | {NULL,3} | 15 | {NULL,3,7,11,15}
+ 3 | {NULL,3} | 16 | {NULL,3,7,11,16}
+ (13 rows)
+
+ WITH RECURSIVE t(id, path) AS (
+ VALUES(1,ARRAY[NULL::integer])
+ UNION ALL
+ SELECT tree.id, t.path || tree.id
+ FROM tree JOIN t ON (tree.parent_id = t.id)
+ )
+ SELECT t1.id, count(t2.*) FROM t AS t1 JOIN t AS t2 ON
+ (t1.path[1:2] = t2.path[1:2] AND
+ array_upper(t1.path,1) = 2 AND
+ array_upper(t2.path,1) > 2)
+ GROUP BY t1.id;
+ id | count
+ ----+-------
+ 3 | 7
+ 2 | 6
+ (2 rows)
+
+ --
+ -- target list has a subquery
+ --
+ WITH RECURSIVE t(id) AS (
+ SELECT (VALUES(1))
+ UNION ALL
+ SELECT id+1 FROM t WHERE id < 5
+ )
+ SELECT * FROM t;
+ id
+ ----
+ 1
+ 2
+ 3
+ 4
+ 5
+ (5 rows)
+
+ --
+ -- multiple query names
+ --
+ WITH RECURSIVE
+ y (id) AS (VALUES (1)),
+ x (id) AS (SELECT * FROM y UNION ALL SELECT id+1 FROM x WHERE id < 5)
+ SELECT * FROM x;
+ id
+ ----
+ 1
+ 2
+ 3
+ 4
+ 5
+ (5 rows)
+
+ WITH RECURSIVE
+ x(id) AS (SELECT * FROM y UNION ALL SELECT id+1 FROM x WHERE id < 5),
+ y(id) AS (values (1))
+ SELECT * FROM x;
+ id
+ ----
+ 1
+ 2
+ 3
+ 4
+ 5
+ (5 rows)
+
+ WITH RECURSIVE
+ x(id) AS
+ (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 5),
+ y(id) AS
+ (VALUES (1) UNION ALL SELECT id+1 FROM y WHERE id < 10)
+ SELECT y.*, x.* FROM y LEFT JOIN x USING (id);
+ id | id
+ ----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+ 5 | 5
+ 6 |
+ 7 |
+ 8 |
+ 9 |
+ 10 |
+ (10 rows)
+
+ WITH RECURSIVE
+ x(id) AS
+ (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 5),
+ y(id) AS
+ (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 10)
+ SELECT y.*, x.* FROM y LEFT JOIN x USING (id);
+ id | id
+ ----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+ 5 | 5
+ 6 |
+ (6 rows)
+
+ WITH RECURSIVE
+ x(id) AS
+ (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 3 ),
+ y(id) AS
+ (SELECT * FROM x UNION ALL SELECT * FROM x),
+ z(id) AS
+ (SELECT * FROM x UNION ALL SELECT id+1 FROM z WHERE id < 10)
+ SELECT * FROM z;
+ id
+ ----
+ 1
+ 2
+ 3
+ 2
+ 3
+ 4
+ 3
+ 4
+ 5
+ 4
+ 5
+ 6
+ 5
+ 6
+ 7
+ 6
+ 7
+ 8
+ 7
+ 8
+ 9
+ 8
+ 9
+ 10
+ 9
+ 10
+ 10
+ (27 rows)
+
+ WITH RECURSIVE
+ x(id) AS
+ (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 3 ),
+ y(id) AS
+ (SELECT * FROM x UNION ALL SELECT * FROM x),
+ z(id) AS
+ (SELECT * FROM y UNION ALL SELECT id+1 FROM z WHERE id < 10)
+ SELECT * FROM z;
+ id
+ ----
+ 1
+ 2
+ 3
+ 1
+ 2
+ 3
+ 2
+ 3
+ 4
+ 2
+ 3
+ 4
+ 3
+ 4
+ 5
+ 3
+ 4
+ 5
+ 4
+ 5
+ 6
+ 4
+ 5
+ 6
+ 5
+ 6
+ 7
+ 5
+ 6
+ 7
+ 6
+ 7
+ 8
+ 6
+ 7
+ 8
+ 7
+ 8
+ 9
+ 7
+ 8
+ 9
+ 8
+ 9
+ 10
+ 8
+ 9
+ 10
+ 9
+ 10
+ 9
+ 10
+ 10
+ 10
+ (54 rows)
+
+ --
+ -- error cases
+ --
+ -- UNION
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION SELECT n+1 FROM x)
+ SELECT * FROM x;
+ ERROR: non-recursive term and recursive term must be combined with UNION ALL
+ -- INTERSECT
+ WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x)
+ SELECT * FROM x;
+ ERROR: non-recursive term and recursive term must not be combined with INTERSECT
+ WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FROM x)
+ SELECT * FROM x;
+ ERROR: non-recursive term and recursive term must not be combined with INTERSECT
+ -- EXCEPT
+ WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x)
+ SELECT * FROM x;
+ ERROR: non-recursive term and recursive term must not be combined with EXCEPT
+ WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM x)
+ SELECT * FROM x;
+ ERROR: non-recursive term and recursive term must not be combined with EXCEPT
+ -- no non-recursive term
+ WITH RECURSIVE x(n) AS (SELECT n FROM x)
+ SELECT * FROM x;
+ ERROR: recursive query must have a non-recursive term
+ LINE 1: WITH RECURSIVE x(n) AS (SELECT n FROM x)
+ ^
+ -- recursive term in the left hand side
+ WITH RECURSIVE x(n) AS (SELECT n FROM x UNION ALL SELECT 1)
+ SELECT * FROM x;
+ ERROR: Left hand side of UNION ALL must be a non-recursive term in a recursive query
+ CREATE TEMPORARY TABLE y (a INTEGER);
+ INSERT INTO y SELECT generate_series(1, 10);
+ -- LEFT JOIN
+ WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1
+ UNION ALL
+ SELECT x.n+1 FROM y LEFT JOIN x ON x.n = y.a WHERE n < 10)
+ SELECT * FROM x;
+ ERROR: LEFT JOIN in the right hand side of recursive term not allowed
+ -- RIGHT JOIN
+ WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1
+ UNION ALL
+ SELECT x.n+1 FROM x RIGHT JOIN y ON x.n = y.a WHERE n < 10)
+ SELECT * FROM x;
+ ERROR: RIGHT JOIN in the left hand side of recursive term not allowed
+ -- FULL JOIN
+ WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1
+ UNION ALL
+ SELECT x.n+1 FROM x FULL JOIN y ON x.n = y.a WHERE n < 10)
+ SELECT * FROM x;
+ ERROR: FULL JOIN in a recursive term not allowed
+ -- subquery
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x
+ WHERE n IN (SELECT * FROM x))
+ SELECT * FROM x;
+ ERROR: WHERE clause having subqury which uses recursive name in a recursive term not allowed
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x
+ WHERE n = 1 AND n IN (SELECT * FROM x))
+ SELECT * FROM x;
+ ERROR: WHERE clause having subqury which uses recursive name in a recursive term not allowed
+ -- GROUP BY
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x GROUP BY n)
+ SELECT * FROM x;
+ ERROR: GROUP BY in a recursive term not allowed
+ -- HAVING
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x HAVING n < 10)
+ SELECT * FROM x;
+ ERROR: HAVING in a recursive term not allowed
+ -- aggregate functions
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT count(*) FROM x)
+ SELECT * FROM x;
+ ERROR: aggregate functions in a recursive term not allowed
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT sum(n) FROM x)
+ SELECT * FROM x;
+ ERROR: aggregate functions in a recursive term not allowed
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT max(n) FROM x)
+ SELECT * FROM x;
+ ERROR: aggregate functions in a recursive term not allowed
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT max(n)+1 FROM x)
+ SELECT * FROM x;
+ ERROR: aggregate functions in a recursive term not allowed
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT min(n) FROM x)
+ SELECT * FROM x;
+ ERROR: aggregate functions in a recursive term not allowed
+ -- ORDER BY
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x ORDER BY 1)
+ SELECT * FROM x;
+ ERROR: ORDER BY in a recursive query not allowed
+ -- LIMIT/OFFSET
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x LIMIT 10 OFFSET 1)
+ SELECT * FROM x;
+ ERROR: LIMIT OFFSET in a recursive query not allowed
+ -- FOR UPDATE
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x FOR UPDATE)
+ SELECT * FROM x;
+ ERROR: FOR UPDATE in a recursive query not allowed
+ -- target list has a recursive query name
+ WITH RECURSIVE x(id) AS (values (1)
+ UNION ALL
+ SELECT (SELECT * FROM x) FROM x WHERE id < 5
+ ) SELECT * FROM x;
+ ERROR: target list having subquery which uses recursive name in a recursive term not allowed
+ -- mutual recursive query
+ WITH RECURSIVE
+ x (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM y WHERE id < 5),
+ y (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 5)
+ SELECT * FROM x;
+ ERROR: mutual recursion between WITH items is not supported
+ LINE 2: x (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM y WHERE id ...
+ ^
+ -- non-linear recursion is not allowed
+ WITH RECURSIVE foo(i) AS
+ (values (1)
+ UNION ALL
+ (SELECT i+1 FROM foo WHERE i < 10
+ UNION ALL
+ SELECT i+1 FROM foo WHERE i < 5)
+ ) SELECT * FROM foo;
+ ERROR: non-linear recursion not allowed
+ WITH RECURSIVE foo(i) AS
+ (values (1)
+ UNION ALL
+ SELECT * FROM
+ (SELECT i+1 FROM foo WHERE i < 10
+ UNION ALL
+ SELECT i+1 FROM foo WHERE i < 5) AS t
+ ) SELECT * FROM foo;
+ ERROR: non-linear recursion not allowed
+ WITH RECURSIVE foo(i) AS
+ (values (1)
+ UNION ALL
+ (SELECT i+1 FROM foo WHERE i < 10
+ EXCEPT
+ SELECT i+1 FROM foo WHERE i < 5)
+ ) SELECT * FROM foo;
+ ERROR: non-linear recursion not allowed
+ WITH RECURSIVE foo(i) AS
+ (values (1)
+ UNION ALL
+ (SELECT i+1 FROM foo WHERE i < 10
+ INTERSECT
+ SELECT i+1 FROM foo WHERE i < 5)
+ ) SELECT * FROM foo;
+ ERROR: non-linear recursion not allowed
+ -- DISTINCT
+ WITH RECURSIVE t(n) AS (
+ SELECT 1
+ UNION ALL
+ SELECT DISTINCT n+1 FROM t WHERE n < 10
+ )
+ SELECT * FROM t;
+ ERROR: DISTINCT in a recursive term not allowed
+ WITH RECURSIVE foo(i) AS
+ (SELECT DISTINCT * FROM (VALUES(1),(2)) t
+ UNION ALL
+ SELECT DISTINCT i+1 FROM foo WHERE i < 10)
+ SELECT * FROM foo;
+ ERROR: DISTINCT in a non recursive term not allowed
*** src/test/regress/parallel_schedule.orig Thu Apr 10 18:25:26 2008
--- src/test/regress/parallel_schedule Mon Sep 22 11:16:32 2008
***************
*** 83,89 ****
# Another group of parallel tests
# ----------
# "plpgsql" cannot run concurrently with "rules", nor can "plancache"
! test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject xml
# run stats by itself because its delay may be insufficient under heavy load
test: stats
--- 83,89 ----
# Another group of parallel tests
# ----------
# "plpgsql" cannot run concurrently with "rules", nor can "plancache"
! test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject xml recursive
# run stats by itself because its delay may be insufficient under heavy load
test: stats
*** src/test/regress/serial_schedule.orig Thu Apr 10 18:25:26 2008
--- src/test/regress/serial_schedule Mon Sep 22 11:16:32 2008
***************
*** 114,118 ****
--- 114,119 ----
test: returning
test: largeobject
test: xml
+ test: recursive
test: stats
test: tablespace
*** src/test/regress/sql/recursive.sql.orig Mon Sep 22 11:16:32 2008
--- src/test/regress/sql/recursive.sql Mon Sep 22 11:16:32 2008
***************
*** 0 ****
--- 1,367 ----
+ CREATE TEMP TABLE department (
+ id INTEGER PRIMARY KEY, -- department ID
+ parent_department INTEGER REFERENCES department, -- upper department ID
+ name TEXT -- department name
+ );
+
+ INSERT INTO department VALUES (0, NULL, 'ROOT');
+ INSERT INTO department VALUES (1, 0, 'A');
+ INSERT INTO department VALUES (2, 1, 'B');
+ INSERT INTO department VALUES (3, 2, 'C');
+ INSERT INTO department VALUES (4, 2, 'D');
+ INSERT INTO department VALUES (5, 0, 'E');
+ INSERT INTO department VALUES (6, 4, 'F');
+ INSERT INTO department VALUES (7, 5, 'G');
+
+ -- department structure represented here is as follows:
+ --
+ -- ROOT-+->A-+->B-+->C
+ -- | |
+ -- | +->D-+->F
+ -- +->E-+->G
+
+ -- extract all departments under 'A'. Result should be A, B, C, D and F
+ WITH RECURSIVE subdepartment AS
+ (
+ -- non recursive term
+ SELECT * FROM department WHERE name = 'A'
+
+ UNION ALL
+
+ -- recursive term
+ SELECT d.* FROM department AS d, subdepartment AS sd
+ WHERE d.parent_department = sd.id
+ )
+ SELECT * FROM subdepartment ORDER BY name;
+
+ -- extract all departments under 'A' with "level" number
+ WITH RECURSIVE subdepartment(level, id, parent_department, name) AS
+ (
+ -- non recursive term
+ SELECT 1, * FROM department WHERE name = 'A'
+
+ UNION ALL
+
+ -- recursive term
+ SELECT sd.level + 1, d.* FROM department AS d, subdepartment AS sd
+ WHERE d.parent_department = sd.id
+ )
+ SELECT * FROM subdepartment ORDER BY name;
+
+ -- extract all departments under 'A' with "level" number.
+ -- Only shows 2 level or more
+ WITH RECURSIVE subdepartment(level, id, parent_department, name) AS
+ (
+ -- non recursive term
+ SELECT 1, * FROM department WHERE name = 'A'
+
+ UNION ALL
+
+ -- recursive term
+ SELECT sd.level + 1, d.* FROM department AS d, subdepartment AS sd
+ WHERE d.parent_department = sd.id
+ )
+ SELECT * FROM subdepartment WHERE level >= 2 ORDER BY name;
+
+ -- sum of 1..100
+ WITH RECURSIVE t(n) AS (
+ VALUES (1)
+ UNION ALL
+ SELECT n+1
+ FROM t
+ WHERE n < 100
+ )
+ SELECT sum(n) FROM t;
+
+ -- non recursive term only case: should return 1 row
+ WITH RECURSIVE subdepartment AS
+ (
+ -- non recursive term
+ SELECT * FROM department WHERE name = 'A'
+ )
+ SELECT * FROM subdepartment ORDER BY name;
+
+ -- subquery
+ SELECT count(*) FROM (
+ WITH RECURSIVE t(n) AS (
+ SELECT 1 UNION ALL SELECT n + 1 FROM t WHERE n < 500
+ )
+ SELECT * FROM t) AS t WHERE n < (
+ SELECT count(*) FROM (
+ WITH RECURSIVE t(n) AS (
+ SELECT 1 UNION ALL SELECT n + 1 FROM t WHERE n < 100
+ )
+ SELECT * FROM t WHERE n < 50000) as t WHERE n < 100);
+
+ -- VIEW
+ CREATE TEMPORARY VIEW vsubdepartment AS
+ WITH RECURSIVE subdepartment AS
+ (
+ -- non recursive term
+ SELECT * FROM department WHERE name = 'A'
+ UNION ALL
+ -- recursive term
+ SELECT d.* FROM department AS d, subdepartment AS sd
+ WHERE d.parent_department = sd.id
+ )
+ SELECT * FROM subdepartment;
+
+ SELECT * FROM vsubdepartment ORDER BY name;
+
+ -- recursive term has UNION
+ WITH RECURSIVE t(i,j) AS (
+ VALUES (1,2)
+
+ UNION ALL
+
+ SELECT t2.i, t.j FROM (SELECT 2 AS i UNION ALL SELECT 3 AS i) AS t2
+ JOIN t ON (t2.i = t.i))
+
+ SELECT * FROM t;
+
+ CREATE TEMPORARY TABLE tree(
+ id INTEGER PRIMARY KEY,
+ parent_id INTEGER REFERENCES tree(id)
+ );
+
+ INSERT INTO tree
+ VALUES (1, NULL), (2, 1), (3,1), (4,2), (5,2), (6,2), (7,3), (8,3),
+ (9,4), (10,4), (11,7), (12,7), (13,7), (14, 9), (15,11), (16,11);
+
+ --
+ -- get all path from the "second level" node to leaf nodes
+ --
+ WITH RECURSIVE t(id, path) AS (
+ VALUES(1,ARRAY[NULL::integer])
+ UNION ALL
+ SELECT tree.id, t.path || tree.id
+ FROM tree JOIN t ON (tree.parent_id = t.id)
+ )
+ SELECT t1.*, t2.* FROM t AS t1 JOIN t AS t2 ON
+ (t1.path[1:2] = t2.path[1:2] AND
+ array_upper(t1.path,1) = 2 AND
+ array_upper(t2.path,1) > 2);
+
+ WITH RECURSIVE t(id, path) AS (
+ VALUES(1,ARRAY[NULL::integer])
+ UNION ALL
+ SELECT tree.id, t.path || tree.id
+ FROM tree JOIN t ON (tree.parent_id = t.id)
+ )
+ SELECT t1.id, count(t2.*) FROM t AS t1 JOIN t AS t2 ON
+ (t1.path[1:2] = t2.path[1:2] AND
+ array_upper(t1.path,1) = 2 AND
+ array_upper(t2.path,1) > 2)
+ GROUP BY t1.id;
+
+ --
+ -- target list has a subquery
+ --
+ WITH RECURSIVE t(id) AS (
+ SELECT (VALUES(1))
+ UNION ALL
+ SELECT id+1 FROM t WHERE id < 5
+ )
+ SELECT * FROM t;
+
+ --
+ -- multiple query names
+ --
+ WITH RECURSIVE
+ y (id) AS (VALUES (1)),
+ x (id) AS (SELECT * FROM y UNION ALL SELECT id+1 FROM x WHERE id < 5)
+ SELECT * FROM x;
+
+ WITH RECURSIVE
+ x(id) AS (SELECT * FROM y UNION ALL SELECT id+1 FROM x WHERE id < 5),
+ y(id) AS (values (1))
+ SELECT * FROM x;
+
+ WITH RECURSIVE
+ x(id) AS
+ (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 5),
+ y(id) AS
+ (VALUES (1) UNION ALL SELECT id+1 FROM y WHERE id < 10)
+ SELECT y.*, x.* FROM y LEFT JOIN x USING (id);
+
+ WITH RECURSIVE
+ x(id) AS
+ (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 5),
+ y(id) AS
+ (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 10)
+ SELECT y.*, x.* FROM y LEFT JOIN x USING (id);
+
+ WITH RECURSIVE
+ x(id) AS
+ (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 3 ),
+ y(id) AS
+ (SELECT * FROM x UNION ALL SELECT * FROM x),
+ z(id) AS
+ (SELECT * FROM x UNION ALL SELECT id+1 FROM z WHERE id < 10)
+ SELECT * FROM z;
+
+ WITH RECURSIVE
+ x(id) AS
+ (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 3 ),
+ y(id) AS
+ (SELECT * FROM x UNION ALL SELECT * FROM x),
+ z(id) AS
+ (SELECT * FROM y UNION ALL SELECT id+1 FROM z WHERE id < 10)
+ SELECT * FROM z;
+
+ --
+ -- error cases
+ --
+
+ -- UNION
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION SELECT n+1 FROM x)
+ SELECT * FROM x;
+
+ -- INTERSECT
+ WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x)
+ SELECT * FROM x;
+
+ WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FROM x)
+ SELECT * FROM x;
+
+ -- EXCEPT
+ WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x)
+ SELECT * FROM x;
+
+ WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM x)
+ SELECT * FROM x;
+
+ -- no non-recursive term
+ WITH RECURSIVE x(n) AS (SELECT n FROM x)
+ SELECT * FROM x;
+
+ -- recursive term in the left hand side
+ WITH RECURSIVE x(n) AS (SELECT n FROM x UNION ALL SELECT 1)
+ SELECT * FROM x;
+
+ CREATE TEMPORARY TABLE y (a INTEGER);
+ INSERT INTO y SELECT generate_series(1, 10);
+
+ -- LEFT JOIN
+
+ WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1
+ UNION ALL
+ SELECT x.n+1 FROM y LEFT JOIN x ON x.n = y.a WHERE n < 10)
+ SELECT * FROM x;
+
+ -- RIGHT JOIN
+ WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1
+ UNION ALL
+ SELECT x.n+1 FROM x RIGHT JOIN y ON x.n = y.a WHERE n < 10)
+ SELECT * FROM x;
+
+ -- FULL JOIN
+ WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1
+ UNION ALL
+ SELECT x.n+1 FROM x FULL JOIN y ON x.n = y.a WHERE n < 10)
+ SELECT * FROM x;
+
+ -- subquery
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x
+ WHERE n IN (SELECT * FROM x))
+ SELECT * FROM x;
+
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x
+ WHERE n = 1 AND n IN (SELECT * FROM x))
+ SELECT * FROM x;
+
+ -- GROUP BY
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x GROUP BY n)
+ SELECT * FROM x;
+
+ -- HAVING
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x HAVING n < 10)
+ SELECT * FROM x;
+
+ -- aggregate functions
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT count(*) FROM x)
+ SELECT * FROM x;
+
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT sum(n) FROM x)
+ SELECT * FROM x;
+
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT max(n) FROM x)
+ SELECT * FROM x;
+
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT max(n)+1 FROM x)
+ SELECT * FROM x;
+
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT min(n) FROM x)
+ SELECT * FROM x;
+
+ -- ORDER BY
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x ORDER BY 1)
+ SELECT * FROM x;
+
+ -- LIMIT/OFFSET
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x LIMIT 10 OFFSET 1)
+ SELECT * FROM x;
+
+ -- FOR UPDATE
+ WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x FOR UPDATE)
+ SELECT * FROM x;
+
+ -- target list has a recursive query name
+ WITH RECURSIVE x(id) AS (values (1)
+ UNION ALL
+ SELECT (SELECT * FROM x) FROM x WHERE id < 5
+ ) SELECT * FROM x;
+
+ -- mutual recursive query
+ WITH RECURSIVE
+ x (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM y WHERE id < 5),
+ y (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 5)
+ SELECT * FROM x;
+
+ -- non-linear recursion is not allowed
+ WITH RECURSIVE foo(i) AS
+ (values (1)
+ UNION ALL
+ (SELECT i+1 FROM foo WHERE i < 10
+ UNION ALL
+ SELECT i+1 FROM foo WHERE i < 5)
+ ) SELECT * FROM foo;
+
+ WITH RECURSIVE foo(i) AS
+ (values (1)
+ UNION ALL
+ SELECT * FROM
+ (SELECT i+1 FROM foo WHERE i < 10
+ UNION ALL
+ SELECT i+1 FROM foo WHERE i < 5) AS t
+ ) SELECT * FROM foo;
+
+ WITH RECURSIVE foo(i) AS
+ (values (1)
+ UNION ALL
+ (SELECT i+1 FROM foo WHERE i < 10
+ EXCEPT
+ SELECT i+1 FROM foo WHERE i < 5)
+ ) SELECT * FROM foo;
+
+ WITH RECURSIVE foo(i) AS
+ (values (1)
+ UNION ALL
+ (SELECT i+1 FROM foo WHERE i < 10
+ INTERSECT
+ SELECT i+1 FROM foo WHERE i < 5)
+ ) SELECT * FROM foo;
+
+ -- DISTINCT
+ WITH RECURSIVE t(n) AS (
+ SELECT 1
+ UNION ALL
+ SELECT DISTINCT n+1 FROM t WHERE n < 10
+ )
+ SELECT * FROM t;
+
+ WITH RECURSIVE foo(i) AS
+ (SELECT DISTINCT * FROM (VALUES(1),(2)) t
+ UNION ALL
+ SELECT DISTINCT i+1 FROM foo WHERE i < 10)
+ SELECT * FROM foo;