*** pgsql/doc/src/sgml/ref/select.sgml 2008-02-16 07:17:06.000000000 +0900
--- pgsql.patched/doc/src/sgml/ref/select.sgml 2008-08-18 15:59:23.000000000 +0900
***************
*** 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;
+
+
+
*** pgsql/src/backend/commands/explain.c 2008-08-15 03:47:58.000000000 +0900
--- pgsql.patched/src/backend/commands/explain.c 2008-08-18 07:47:48.000000000 +0900
***************
*** 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_RECURSIVE);
+
+ 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)
{
*** pgsql/src/backend/executor/Makefile 2008-02-19 19:30:07.000000000 +0900
--- pgsql.patched/src/backend/executor/Makefile 2008-08-18 07:47:48.000000000 +0900
***************
*** 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
*** pgsql/src/backend/executor/execAmi.c 2008-08-06 06:28:29.000000000 +0900
--- pgsql.patched/src/backend/executor/execAmi.c 2008-08-18 07:47:48.000000000 +0900
***************
*** 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;
***************
*** 344,349 ****
--- 362,368 ----
case T_TidScan:
case T_FunctionScan:
case T_ValuesScan:
+ case T_RecursiveScan:
case T_Material:
case T_Sort:
return true;
***************
*** 405,410 ****
--- 424,430 ----
case T_TidScan:
case T_FunctionScan:
case T_ValuesScan:
+ case T_RecursiveScan:
return true;
case T_SubqueryScan:
*** pgsql/src/backend/executor/execProcnode.c 2008-01-02 04:45:49.000000000 +0900
--- pgsql.patched/src/backend/executor/execProcnode.c 2008-08-18 07:47:48.000000000 +0900
***************
*** 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
*/
*** pgsql/src/backend/executor/nodeAgg.c 2008-08-03 06:31:59.000000000 +0900
--- pgsql.patched/src/backend/executor/nodeAgg.c 2008-08-18 14:04:07.000000000 +0900
***************
*** 1232,1237 ****
--- 1232,1238 ----
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.
***************
*** 1617,1627 ****
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;
--- 1618,1630 ----
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;
*** pgsql/src/backend/executor/nodeAppend.c 2008-01-02 04:45:49.000000000 +0900
--- pgsql.patched/src/backend/executor/nodeAppend.c 2008-08-18 07:47:48.000000000 +0900
***************
*** 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;
***************
*** 213,218 ****
--- 211,220 ----
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]);
}
/*
*** pgsql/src/backend/executor/nodeHash.c 2008-01-02 04:45:49.000000000 +0900
--- pgsql.patched/src/backend/executor/nodeHash.c 2008-08-18 07:47:48.000000000 +0900
***************
*** 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
*** pgsql/src/backend/executor/nodeHashjoin.c 2008-08-16 04:20:42.000000000 +0900
--- pgsql.patched/src/backend/executor/nodeHashjoin.c 2008-08-18 07:47:48.000000000 +0900
***************
*** 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.
*** pgsql/src/backend/executor/nodeMergejoin.c 2008-08-16 04:20:42.000000000 +0900
--- pgsql.patched/src/backend/executor/nodeMergejoin.c 2008-08-18 07:47:48.000000000 +0900
***************
*** 420,425 ****
--- 420,433 ----
ResetExprContext(econtext);
+ #if 0
+ /*
+ * Do not store a tuple into the recursive intermediate table.
+ * Because a recursive query does not stop loops.
+ */
+ node->js.ps.state->es_disallow_tuplestore = true;
+ #endif
+
econtext->ecxt_outertuple = node->mj_OuterTupleSlot;
econtext->ecxt_innertuple = node->mj_NullInnerTupleSlot;
*** pgsql/src/backend/executor/nodeNestloop.c 2008-08-16 04:20:42.000000000 +0900
--- pgsql.patched/src/backend/executor/nodeNestloop.c 2008-08-18 07:47:48.000000000 +0900
***************
*** 179,184 ****
--- 179,192 ----
*/
econtext->ecxt_innertuple = node->nl_NullInnerTupleSlot;
+ #if 0
+ /*
+ * Do not store a tuple into the recursive intermediate table.
+ * Because a recursive query does not stop loops.
+ */
+ node->js.ps.state->es_disallow_tuplestore = true;
+ #endif
+
ENL1_printf("testing qualification for outer-join tuple");
if (otherqual == NIL || ExecQual(otherqual, econtext, false))
*** pgsql/src/backend/executor/nodeRecursion.c 1970-01-01 09:00:00.000000000 +0900
--- pgsql.patched/src/backend/executor/nodeRecursion.c 2008-08-18 07:47:48.000000000 +0900
***************
*** 0 ****
--- 1,280 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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;
+ node->ss.ps.state->es_disallow_tuplestore = false;
+
+ /* 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 if (!node->ss.ps.state->es_disallow_tuplestore)
+ {
+ node->recursive_empty = false;
+ tuplestore_puttupleslot(node->intermediate_tuplestorestate, slot);
+ }
+
+ /* restore flag */
+ node->ss.ps.state->es_disallow_tuplestore = false;
+
+ 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));
+ estate->es_rscan_tupledesc = 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;
+ }
*** pgsql/src/backend/executor/nodeRecursivescan.c 1970-01-01 09:00:00.000000000 +0900
--- pgsql.patched/src/backend/executor/nodeRecursivescan.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 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);
+ }
*** pgsql/src/backend/nodes/copyfuncs.c 2008-08-15 03:47:58.000000000 +0900
--- pgsql.patched/src/backend/nodes/copyfuncs.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 417,422 ****
--- 417,460 ----
}
/*
+ * _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
***************
*** 740,745 ****
--- 778,797 ----
}
/*
+ * _copyWithClause
+ */
+ static WithClause *
+ _copyWithClause(WithClause *from)
+ {
+ WithClause *newnode = makeNode(WithClause);
+
+ COPY_NODE_FIELD(subquery);
+ COPY_SCALAR_FIELD(recursive);
+
+ return newnode;
+ }
+
+ /*
* We don't need a _copyExpr because Expr is an abstract supertype which
* should never actually get instantiated. Also, since it has no common
* fields except NodeTag, there's no need for a helper routine to factor
***************
*** 1515,1520 ****
--- 1567,1574 ----
COPY_NODE_FIELD(funccoltypes);
COPY_NODE_FIELD(funccoltypmods);
COPY_NODE_FIELD(values_lists);
+ COPY_SCALAR_FIELD(self_reference);
+ COPY_NODE_FIELD(non_recursive_query);
COPY_SCALAR_FIELD(jointype);
COPY_NODE_FIELD(joinaliasvars);
COPY_NODE_FIELD(alias);
***************
*** 1867,1872 ****
--- 1921,1927 ----
COPY_NODE_FIELD(limitCount);
COPY_NODE_FIELD(rowMarks);
COPY_NODE_FIELD(setOperations);
+ COPY_SCALAR_FIELD(recursive);
return newnode;
}
***************
*** 1928,1933 ****
--- 1983,1989 ----
COPY_NODE_FIELD(limitOffset);
COPY_NODE_FIELD(limitCount);
COPY_NODE_FIELD(lockingClause);
+ COPY_NODE_FIELD(withClause);
COPY_SCALAR_FIELD(op);
COPY_SCALAR_FIELD(all);
COPY_NODE_FIELD(larg);
***************
*** 3028,3033 ****
--- 3084,3092 ----
case T_Append:
retval = _copyAppend(from);
break;
+ case T_Recursion:
+ retval = _copyRecursion(from);
+ break;
case T_BitmapAnd:
retval = _copyBitmapAnd(from);
break;
***************
*** 3061,3066 ****
--- 3120,3128 ----
case T_ValuesScan:
retval = _copyValuesScan(from);
break;
+ case T_RecursiveScan:
+ retval = _copyRecursiveScan(from);
+ break;
case T_Join:
retval = _copyJoin(from);
break;
***************
*** 3110,3115 ****
--- 3172,3180 ----
case T_IntoClause:
retval = _copyIntoClause(from);
break;
+ case T_WithClause:
+ retval = _copyWithClause(from);
+ break;
case T_Var:
retval = _copyVar(from);
break;
*** pgsql/src/backend/nodes/equalfuncs.c 2008-08-15 03:47:58.000000000 +0900
--- pgsql.patched/src/backend/nodes/equalfuncs.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 114,119 ****
--- 114,128 ----
return true;
}
+ static bool
+ _equalWithClause(WithClause *a, WithClause *b)
+ {
+ COMPARE_NODE_FIELD(subquery);
+ COMPARE_SCALAR_FIELD(recursive);
+
+ return true;
+ }
+
/*
* We don't need an _equalExpr because Expr is an abstract supertype which
* should never actually get instantiated. Also, since it has no common
***************
*** 823,828 ****
--- 832,838 ----
COMPARE_NODE_FIELD(limitOffset);
COMPARE_NODE_FIELD(limitCount);
COMPARE_NODE_FIELD(lockingClause);
+ COMPARE_NODE_FIELD(withClause);
COMPARE_SCALAR_FIELD(op);
COMPARE_SCALAR_FIELD(all);
COMPARE_NODE_FIELD(larg);
***************
*** 1875,1880 ****
--- 1885,1892 ----
COMPARE_NODE_FIELD(funccoltypes);
COMPARE_NODE_FIELD(funccoltypmods);
COMPARE_NODE_FIELD(values_lists);
+ COMPARE_SCALAR_FIELD(self_reference);
+ COMPARE_NODE_FIELD(non_recursive_query);
COMPARE_SCALAR_FIELD(jointype);
COMPARE_NODE_FIELD(joinaliasvars);
COMPARE_NODE_FIELD(alias);
***************
*** 2062,2067 ****
--- 2074,2082 ----
case T_IntoClause:
retval = _equalIntoClause(a, b);
break;
+ case T_WithClause:
+ retval = _equalWithClause(a, b);
+ break;
case T_Var:
retval = _equalVar(a, b);
break;
*** pgsql/src/backend/nodes/outfuncs.c 2008-08-15 03:47:58.000000000 +0900
--- pgsql.patched/src/backend/nodes/outfuncs.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 442,447 ****
--- 442,466 ----
}
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");
***************
*** 678,683 ****
--- 697,711 ----
}
static void
+ _outWithClause(StringInfo str, WithClause *node)
+ {
+ WRITE_NODE_TYPE("WITHCLAUSE");
+
+ WRITE_NODE_FIELD(subquery);
+ WRITE_BOOL_FIELD(recursive);
+ }
+
+ static void
_outVar(StringInfo str, Var *node)
{
WRITE_NODE_TYPE("VAR");
***************
*** 1600,1605 ****
--- 1628,1634 ----
WRITE_NODE_FIELD(limitOffset);
WRITE_NODE_FIELD(limitCount);
WRITE_NODE_FIELD(lockingClause);
+ WRITE_NODE_FIELD(withClause);
WRITE_ENUM_FIELD(op, SetOperation);
WRITE_BOOL_FIELD(all);
WRITE_NODE_FIELD(larg);
***************
*** 1629,1634 ****
--- 1658,1694 ----
}
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
+ _outRangeRecursive(StringInfo str, RangeRecursive *node)
+ {
+ WRITE_NODE_TYPE("RANGERECURSIVE");
+
+ WRITE_NODE_FIELD(subquery);
+ WRITE_NODE_FIELD(alias);
+ WRITE_NODE_FIELD(coldeflist);
+ WRITE_NODE_FIELD(non_recursive_term);
+ WRITE_BOOL_FIELD(recursive);
+ }
+
+ static void
_outLockingClause(StringInfo str, LockingClause *node)
{
WRITE_NODE_TYPE("LOCKINGCLAUSE");
***************
*** 1750,1755 ****
--- 1810,1816 ----
WRITE_NODE_FIELD(limitCount);
WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(setOperations);
+ WRITE_BOOL_FIELD(recursive);
}
static void
***************
*** 1814,1819 ****
--- 1875,1885 ----
case RTE_VALUES:
WRITE_NODE_FIELD(values_lists);
break;
+ case RTE_RECURSIVE:
+ WRITE_NODE_FIELD(subquery);
+ WRITE_BOOL_FIELD(self_reference);
+ WRITE_NODE_FIELD(non_recursive_query);
+ break;
case RTE_JOIN:
WRITE_ENUM_FIELD(jointype, JoinType);
WRITE_NODE_FIELD(joinaliasvars);
***************
*** 2127,2132 ****
--- 2193,2204 ----
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;
***************
*** 2172,2177 ****
--- 2244,2252 ----
case T_IntoClause:
_outIntoClause(str, obj);
break;
+ case T_WithClause:
+ _outWithClause(str, obj);
+ break;
case T_Var:
_outVar(str, obj);
break;
***************
*** 2444,2449 ****
--- 2519,2533 ----
case T_FuncCall:
_outFuncCall(str, obj);
break;
+ case T_RangeSubselect:
+ _outRangeSubselect(str, obj);
+ break;
+ case T_RangeFunction:
+ _outRangeFunction(str, obj);
+ break;
+ case T_RangeRecursive:
+ _outRangeRecursive(str, obj);
+ break;
case T_DefElem:
_outDefElem(str, obj);
break;
*** pgsql/src/backend/nodes/readfuncs.c 2008-08-07 10:11:49.000000000 +0900
--- pgsql.patched/src/backend/nodes/readfuncs.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 155,160 ****
--- 155,161 ----
READ_NODE_FIELD(limitCount);
READ_NODE_FIELD(rowMarks);
READ_NODE_FIELD(setOperations);
+ READ_BOOL_FIELD(recursive);
READ_DONE();
}
***************
*** 977,982 ****
--- 978,988 ----
case RTE_VALUES:
READ_NODE_FIELD(values_lists);
break;
+ case RTE_RECURSIVE:
+ READ_NODE_FIELD(subquery);
+ READ_BOOL_FIELD(self_reference);
+ READ_NODE_FIELD(non_recursive_query);
+ break;
case RTE_JOIN:
READ_ENUM_FIELD(jointype, JoinType);
READ_NODE_FIELD(joinaliasvars);
*** pgsql/src/backend/optimizer/path/allpaths.c 2008-08-03 06:31:59.000000000 +0900
--- pgsql.patched/src/backend/optimizer/path/allpaths.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 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_RECURSIVE)
+ {
+ if (rte->self_reference)
+ set_recursivescan_pathlist(root, rel, rte);
+ else
+ set_recursion_pathlist(root, rel, rti, rte);
+ }
else
{
/* Plain relation */
***************
*** 641,646 ****
--- 652,781 ----
}
/*
+ * 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;
+ Query *subquery = rte->subquery;
+ bool *differentTypes;
+ double tuple_fraction;
+ PlannerInfo *subroot;
+ List *pathkeys;
+
+ /* We need a workspace for keeping track of set-op type coercions */
+ differentTypes = (bool *)
+ palloc0((list_length(subquery->targetList) + 1) * sizeof(bool));
+
+ /*
+ * If there are any restriction clauses that have been attached to the
+ * subquery relation, consider pushing them down to become WHERE or HAVING
+ * quals of the subquery itself. This transformation is useful because it
+ * may allow us to generate a better plan for the subquery than evaluating
+ * all the subquery output rows and then filtering them.
+ *
+ * There are several cases where we cannot push down clauses. Restrictions
+ * involving the subquery are checked by subquery_is_pushdown_safe().
+ * Restrictions on individual clauses are checked by
+ * qual_is_pushdown_safe(). Also, we don't want to push down
+ * pseudoconstant clauses; better to have the gating node above the
+ * subquery.
+ *
+ * Non-pushed-down clauses will get evaluated as qpquals of the
+ * SubqueryScan node.
+ *
+ * XXX Are there any cases where we want to make a policy decision not to
+ * push down a pushable qual, because it'd result in a worse plan?
+ */
+ if (rel->baserestrictinfo != NIL &&
+ subquery_is_pushdown_safe(subquery, subquery, differentTypes))
+ {
+ /* OK to consider pushing down individual quals */
+ List *upperrestrictlist = NIL;
+ ListCell *l;
+
+ foreach(l, rel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
+ Node *clause = (Node *) rinfo->clause;
+
+ if (!rinfo->pseudoconstant &&
+ qual_is_pushdown_safe(subquery, rti, clause, differentTypes))
+ {
+ /* Push it down */
+ subquery_push_qual(subquery, rte, rti, clause);
+ }
+ else
+ {
+ /* Keep it in the upper query */
+ upperrestrictlist = lappend(upperrestrictlist, rinfo);
+ }
+ }
+ rel->baserestrictinfo = upperrestrictlist;
+ }
+
+ pfree(differentTypes);
+
+ /*
+ * 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 plan for the subquery */
+ rel->subplan = subquery_planner(root->glob, subquery,
+ root->query_level + 1,
+ 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 */
+ 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.
*
***************
*** 950,967 ****
*
* Conditions checked here:
*
! * 1. The qual must not contain any subselects (mainly because I'm not sure
* it will work correctly: sublinks will already have been transformed into
* subplans in the qual, but not in the subquery).
*
! * 2. The qual must not refer to the whole-row output of the subquery
* (since there is no easy way to name that within the subquery itself).
*
! * 3. The qual must not refer to any subquery output columns that were
* found to have inconsistent types across a set operation tree by
* subquery_is_pushdown_safe().
*
! * 4. If the subquery uses DISTINCT ON, we must not push down any quals that
* refer to non-DISTINCT output columns, because that could change the set
* of rows returned. (This condition is vacuous for DISTINCT, because then
* there are no non-DISTINCT output columns, so we needn't check. But note
--- 1085,1104 ----
*
* Conditions checked here:
*
! * 1. If the subquery is recursively, we must not push down any quals.
! *
! * 2. The qual must not contain any subselects (mainly because I'm not sure
* it will work correctly: sublinks will already have been transformed into
* subplans in the qual, but not in the subquery).
*
! * 3. The qual must not refer to the whole-row output of the subquery
* (since there is no easy way to name that within the subquery itself).
*
! * 4. The qual must not refer to any subquery output columns that were
* found to have inconsistent types across a set operation tree by
* subquery_is_pushdown_safe().
*
! * 5. If the subquery uses DISTINCT ON, we must not push down any quals that
* refer to non-DISTINCT output columns, because that could change the set
* of rows returned. (This condition is vacuous for DISTINCT, because then
* there are no non-DISTINCT output columns, so we needn't check. But note
***************
*** 970,980 ****
* for the case, and it's unlikely enough that we shouldn't refuse the
* optimization just because it could theoretically happen.)
*
! * 5. We must not push down any quals that refer to subselect outputs that
* return sets, else we'd introduce functions-returning-sets into the
* subquery's WHERE/HAVING quals.
*
! * 6. We must not push down any quals that refer to subselect outputs that
* contain volatile functions, for fear of introducing strange results due
* to multiple evaluation of a volatile function.
*/
--- 1107,1117 ----
* for the case, and it's unlikely enough that we shouldn't refuse the
* optimization just because it could theoretically happen.)
*
! * 6. We must not push down any quals that refer to subselect outputs that
* return sets, else we'd introduce functions-returning-sets into the
* subquery's WHERE/HAVING quals.
*
! * 7. We must not push down any quals that refer to subselect outputs that
* contain volatile functions, for fear of introducing strange results due
* to multiple evaluation of a volatile function.
*/
***************
*** 987,993 ****
ListCell *vl;
Bitmapset *tested = NULL;
! /* Refuse subselects (point 1) */
if (contain_subplans(qual))
return false;
--- 1124,1134 ----
ListCell *vl;
Bitmapset *tested = NULL;
! /* Recursive query (point 1) */
! if (subquery->recursive == true)
! return false;
!
! /* Refuse subselects (point 2) */
if (contain_subplans(qual))
return false;
***************
*** 1003,1009 ****
Assert(var->varno == rti);
! /* Check point 2 */
if (var->varattno == 0)
{
safe = false;
--- 1144,1150 ----
Assert(var->varno == rti);
! /* Check point 3 */
if (var->varattno == 0)
{
safe = false;
***************
*** 1019,1025 ****
continue;
tested = bms_add_member(tested, var->varattno);
! /* Check point 3 */
if (differentTypes[var->varattno])
{
safe = false;
--- 1160,1166 ----
continue;
tested = bms_add_member(tested, var->varattno);
! /* Check point 4 */
if (differentTypes[var->varattno])
{
safe = false;
***************
*** 1040,1053 ****
break;
}
! /* Refuse functions returning sets (point 5) */
if (expression_returns_set((Node *) tle->expr))
{
safe = false;
break;
}
! /* Refuse volatile functions (point 6) */
if (contain_volatile_functions((Node *) tle->expr))
{
safe = false;
--- 1181,1194 ----
break;
}
! /* Refuse functions returning sets (point 6) */
if (expression_returns_set((Node *) tle->expr))
{
safe = false;
break;
}
! /* Refuse volatile functions (point 7) */
if (contain_volatile_functions((Node *) tle->expr))
{
safe = false;
***************
*** 1230,1235 ****
--- 1371,1379 ----
ptype = "HashJoin";
join = true;
break;
+ case T_Recursion:
+ ptype = "Recursion";
+ break;
default:
ptype = "???Path";
break;
*** pgsql/src/backend/optimizer/path/costsize.c 2008-08-16 09:01:36.000000000 +0900
--- pgsql.patched/src/backend/optimizer/path/costsize.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 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_RECURSIVE);
+
+ /*
+ * 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_RECURSIVE);
+
+ /*
+ * 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.
***************
*** 2461,2466 ****
--- 2521,2548 ----
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_RECURSIVE);
+
+ /* Now estimate number of output rows, etc */
+ set_baserel_size_estimates(root, rel);
+ }
+
/*
* set_rel_width
*** pgsql/src/backend/optimizer/plan/createplan.c 2008-08-15 03:47:59.000000000 +0900
--- pgsql.patched/src/backend/optimizer/plan/createplan.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 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);
***************
*** 329,335 ****
if (rel->rtekind != RTE_RELATION &&
rel->rtekind != RTE_SUBQUERY &&
rel->rtekind != RTE_FUNCTION &&
! rel->rtekind != RTE_VALUES)
return false;
/*
--- 356,363 ----
if (rel->rtekind != RTE_RELATION &&
rel->rtekind != RTE_SUBQUERY &&
rel->rtekind != RTE_FUNCTION &&
! rel->rtekind != RTE_VALUES &&
! rel->rtekind != RTE_RECURSIVE)
return false;
/*
***************
*** 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_RECURSIVE);
+
+ /* 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_RECURSIVE);
+
+ /* 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
***************
*** 2289,2294 ****
--- 2380,2432 ----
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)
{
*** pgsql/src/backend/optimizer/plan/setrefs.c 2008-06-17 23:51:32.000000000 +0900
--- pgsql.patched/src/backend/optimizer/plan/setrefs.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 336,342 ****
--- 336,369 ----
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:
*** pgsql/src/backend/optimizer/plan/subselect.c 2008-08-17 11:19:19.000000000 +0900
--- pgsql.patched/src/backend/optimizer/plan/subselect.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 1485,1490 ****
--- 1485,1492 ----
case T_Unique:
case T_SetOp:
case T_Group:
+ case T_Recursion:
+ case T_RecursiveScan:
break;
default:
*** pgsql/src/backend/optimizer/prep/prepunion.c 2008-08-15 03:47:59.000000000 +0900
--- pgsql.patched/src/backend/optimizer/prep/prepunion.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 1084,1090 ****
/* Ignore any already-expanded UNION ALL nodes */
if (rte->rtekind != RTE_RELATION)
{
! Assert(rte->rtekind == RTE_SUBQUERY);
return;
}
/* Fast path for common case of childless table */
--- 1084,1090 ----
/* Ignore any already-expanded UNION ALL nodes */
if (rte->rtekind != RTE_RELATION)
{
! Assert(rte->rtekind == RTE_SUBQUERY || rte->rtekind == RTE_RECURSIVE);
return;
}
/* Fast path for common case of childless table */
*** pgsql/src/backend/optimizer/util/clauses.c 2008-08-15 03:47:59.000000000 +0900
--- pgsql.patched/src/backend/optimizer/util/clauses.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 4373,4378 ****
--- 4373,4379 ----
{
case RTE_RELATION:
case RTE_SPECIAL:
+ case RTE_RECURSIVE:
/* nothing to do */
break;
case RTE_SUBQUERY:
***************
*** 4989,4994 ****
--- 4990,4996 ----
{
case RTE_RELATION:
case RTE_SPECIAL:
+ case RTE_RECURSIVE:
/* we don't bother to copy eref, aliases, etc; OK? */
break;
case RTE_SUBQUERY:
*** pgsql/src/backend/optimizer/util/pathnode.c 2008-08-15 03:47:59.000000000 +0900
--- pgsql.patched/src/backend/optimizer/util/pathnode.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 1220,1225 ****
--- 1220,1264 ----
}
/*
+ * create_subqueryscan_path
+ * Creates a path corresponding to a sequential scan of a subquery,
+ * 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.
*** pgsql/src/backend/optimizer/util/plancat.c 2008-08-16 09:01:36.000000000 +0900
--- pgsql.patched/src/backend/optimizer/util/plancat.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 772,777 ****
--- 772,801 ----
}
break;
+ case RTE_RECURSIVE:
+ subquery = rte->non_recursive_query;
+ foreach(l, subquery->targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ /*
+ * A resjunk column of the subquery can be reflected as
+ * resjunk in the physical tlist; we need not punt.
+ */
+ var = makeVar(varno,
+ tle->resno,
+ exprType((Node *) tle->expr),
+ exprTypmod((Node *) tle->expr),
+ 0);
+
+ tlist = lappend(tlist,
+ makeTargetEntry((Expr *) var,
+ tle->resno,
+ NULL,
+ tle->resjunk));
+ }
+ break;
+
default:
/* caller error */
elog(ERROR, "unsupported RTE kind %d in build_physical_tlist",
*** pgsql/src/backend/optimizer/util/relnode.c 2008-08-15 03:47:59.000000000 +0900
--- pgsql.patched/src/backend/optimizer/util/relnode.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 101,106 ****
--- 101,107 ----
case RTE_SUBQUERY:
case RTE_FUNCTION:
case RTE_VALUES:
+ case RTE_RECURSIVE:
/*
* Subquery, function, or values list --- set up attr range and
*** pgsql/src/backend/parser/Makefile 2008-02-19 19:30:07.000000000 +0900
--- pgsql.patched/src/backend/parser/Makefile 2008-08-18 07:47:49.000000000 +0900
***************
*** 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
*** pgsql/src/backend/parser/analyze.c 2008-08-07 10:11:51.000000000 +0900
--- pgsql.patched/src/backend/parser/analyze.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 691,696 ****
--- 691,700 ----
/* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */
pstate->p_locking_clause = stmt->lockingClause;
+ /* process the WITH clause (pull CTEs into the pstate's ctenamespace) */
+ if (stmt->withClause)
+ transformWithClause(pstate, stmt->withClause);
+
/* process the FROM clause */
transformFromClause(pstate, stmt->fromClause);
***************
*** 1045,1050 ****
--- 1049,1058 ----
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
+ /* process the WITH clause (pull CTEs into the pstate's ctenamespace) */
+ if (stmt->withClause)
+ transformWithClause(pstate, stmt->withClause);
+
/*
* Recursively transform the components of the tree.
*/
*** pgsql/src/backend/parser/gram.y 2008-07-18 12:32:52.000000000 +0900
--- pgsql.patched/src/backend/parser/gram.y 2008-08-18 07:47:49.000000000 +0900
***************
*** 106,112 ****
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);
--- 106,113 ----
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);
***************
*** 146,151 ****
--- 147,153 ----
Alias *alias;
RangeVar *range;
IntoClause *into;
+ WithClause *with;
A_Indices *aind;
ResTarget *target;
PrivTarget *privtarget;
***************
*** 363,368 ****
--- 365,374 ----
%type document_or_content
%type xml_whitespace_option
+ %type common_table_expression
+ %type with_cte_list
+ %type cte_list
+
/*
* If you make any token changes, update the keyword table in
***************
*** 429,437 ****
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
--- 435,443 ----
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
***************
*** 6243,6263 ****
| 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:
--- 6249,6300 ----
| 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_cte_list simple_select
+ {
+ insertSelectOptions((SelectStmt *) $2,
+ NULL, NIL,
+ NULL, NULL,
+ $1);
+ $$ = $2;
+ }
+ | with_cte_list select_clause sort_clause
+ {
+ insertSelectOptions((SelectStmt *) $2, $3, NIL,
+ NULL, NULL,
+ $1);
+ $$ = $2;
+ }
+ | with_cte_list 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_cte_list 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:
***************
*** 6318,6323 ****
--- 6355,6401 ----
}
;
+ /*
+ * ANSI standard WITH clause looks like:
+ *
+ * WITH [ RECURSIVE ] [ (,...) ]
+ * AS (query) [ SEARCH or CYCLE clause ]
+ *
+ * We don't currently support the SEARCH or CYCLE clause.
+ */
+ with_cte_list:
+ WITH cte_list
+ {
+ $$ = makeNode(WithClause);
+ $$->recursive = false;
+ $$->subquery = $2;
+ }
+ | WITH RECURSIVE cte_list
+ {
+ $$ = makeNode(WithClause);
+ $$->recursive = true;
+ $$->subquery = $3;
+ }
+ ;
+
+ cte_list:
+ common_table_expression { $$ = list_make1($1); }
+ | cte_list ',' common_table_expression { $$ = lappend($1, $3); }
+ ;
+
+ common_table_expression: name opt_name_list AS select_with_parens
+ {
+ RangeSubselect *n = makeNode(RangeSubselect);
+
+ n->subquery = $4;
+ n->alias = makeNode(Alias);
+ n->alias->aliasname = $1;
+ n->alias->colnames = $2;
+
+ $$ = (Node *) n;
+ }
+ ;
+
into_clause:
INTO OptTempTableName
{
***************
*** 9269,9275 ****
| VIEW
| VOLATILE
| WHITESPACE_P
- | WITH
| WITHOUT
| WORK
| WRITE
--- 9347,9352 ----
***************
*** 9452,9457 ****
--- 9529,9535 ----
| VARIADIC
| WHEN
| WHERE
+ | WITH
;
***************
*** 9740,9747 ****
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
--- 9818,9828 ----
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
***************
*** 9772,9777 ****
--- 9853,9866 ----
errmsg("multiple LIMIT clauses not allowed")));
stmt->limitCount = limitCount;
}
+ if (withClause)
+ {
+ if (stmt->withClause)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("multiple WITH clauses not allowed")));
+ stmt->withClause = withClause;
+ }
}
static Node *
*** pgsql/src/backend/parser/keywords.c 2008-07-16 10:30:22.000000000 +0900
--- pgsql.patched/src/backend/parser/keywords.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 304,309 ****
--- 304,310 ----
{"real", REAL, COL_NAME_KEYWORD},
{"reassign", REASSIGN, UNRESERVED_KEYWORD},
{"recheck", RECHECK, UNRESERVED_KEYWORD},
+ {"recursive", RECURSIVE, RESERVED_KEYWORD},
{"references", REFERENCES, RESERVED_KEYWORD},
{"reindex", REINDEX, UNRESERVED_KEYWORD},
{"relative", RELATIVE_P, UNRESERVED_KEYWORD},
*** pgsql/src/backend/parser/parse_clause.c 2008-08-07 10:11:51.000000000 +0900
--- pgsql.patched/src/backend/parser/parse_clause.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 27,32 ****
--- 27,33 ----
#include "parser/parsetree.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"
***************
*** 58,63 ****
--- 59,66 ----
RangeSubselect *r);
static RangeTblEntry *transformRangeFunction(ParseState *pstate,
RangeFunction *r);
+ static RangeTblEntry *transformRangeRecursive(ParseState *pstate,
+ RangeVar *n, RangeRecursive *r);
static Node *transformFromClauseItem(ParseState *pstate, Node *n,
RangeTblEntry **top_rte, int *top_rti,
List **relnamespace,
***************
*** 76,81 ****
--- 79,164 ----
/*
+ * transformWithClause -
+ * Transform the list of WITH clause "common table expressions" into
+ * Query nodes.
+ *
+ * We need to add the name of the common table expression to a list that is
+ * used later to find them. But we do _not_ add the table itself to the current
+ * namespace because that would implicitly join all of them which isn't right.
+ */
+ void
+ transformWithClause(ParseState *pstate, WithClause *withClause)
+ {
+ ListCell *lc;
+ RangeRecursive *new_cte;
+
+ if (withClause->recursive)
+ {
+ /* register recursive cte names */
+ foreach(lc, withClause->subquery)
+ {
+ RangeSubselect *cte = lfirst(lc);
+
+ new_cte = makeNode(RangeRecursive);
+ new_cte->subquery = (Node *)cte->subquery;
+ new_cte->alias = copyObject(cte->alias);
+
+ /*
+ * Check if CTE name has already registered.
+ */
+ checkCteNameSpaceConflicts(pstate->p_recursive_namespace, cte);
+
+ pstate->p_recursive_namespace = lappend(pstate->p_recursive_namespace, new_cte);
+ }
+
+ /*
+ * Check well-formed subquery.
+ */
+ checkWellFormedCte(pstate, withClause);
+
+ foreach(lc, pstate->p_recursive_namespace)
+ {
+ RangeRecursive *cte = lfirst(lc);
+ if (IsA(cte->subquery, SelectStmt))
+ cte->subquery = (Node*) parse_sub_analyze(cte->subquery, pstate);
+ }
+ return;
+ }
+
+ foreach(lc, withClause->subquery)
+ {
+ RangeSubselect *cte = lfirst(lc);
+ RangeSubselect *new_cte;
+ Query *query;
+
+ query = parse_sub_analyze(cte->subquery, pstate);
+
+ /* 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")));
+
+ new_cte = makeNode(RangeSubselect);
+ new_cte->subquery = (Node*) query;
+ new_cte->alias = copyObject(cte->alias);
+
+ /*
+ * Check if CTE name has already registered.
+ */
+ if (!withClause->recursive)
+ {
+ checkCteNameSpaceConflicts(pstate->p_ctenamespace, new_cte);
+ pstate->p_ctenamespace = lappend(pstate->p_ctenamespace, new_cte);
+ }
+ }
+ }
+
+ /*
* transformFromClause -
* Process the FROM clause and add items to the query's range table,
* joinlist, and namespaces.
***************
*** 418,423 ****
--- 501,555 ----
return rte;
}
+ /*
+ * transformRangeCTE --- transform a RangeVar which references a common table
+ * expression (ie, a sub-SELECT defined in a WITH clause)
+ */
+ static RangeTblEntry *
+ transformRangeCTE(ParseState *pstate, RangeVar *n, RangeSubselect *r)
+ {
+ RangeTblEntry *rte;
+
+ /*
+ * Unlike transformRangeSubselect we do not have to worry about:
+ *
+ * . checking for an alias because the grammar for WITH always gives us an
+ * alias
+ *
+ * . transforming the subquery as transformWithClause has already done that
+ * and the RangeSubselect contains the query tree, not the raw parse tree
+ *
+ * . checking for lateral references since WITH subqueries have their own
+ * scope. Since they were transformed prior to any range table entries
+ * being created in our pstate they were all planned with a fresh copy of
+ * our empty pstate (unless we're in a subquery already of course).
+ */
+
+ /*
+ * This is a kluge for now. Effectively we're inlining all the WITH
+ * clauses which isn't what we want to do
+ */
+
+ /*
+ * One tricky bit. We potentially have two aliases here. The WITH clause
+ * always specifies a relation alias and may or may not specify column
+ * aliases. The rangevar also may or may not specify a relation alias
+ * and may or may not specify column aliases.
+ */
+
+ Alias *a = copyObject(r->alias);
+ if (n->alias && n->alias->aliasname)
+ a->aliasname = n->alias->aliasname;
+ if (n->alias && n->alias->colnames)
+ a->colnames = n->alias->colnames;
+
+ /*
+ * OK, build an RTE for the subquery.
+ */
+ rte = addRangeTableEntryForSubquery(pstate, (Query*) r->subquery, a, true);
+
+ return rte;
+ }
/*
* transformRangeSubselect --- transform a sub-SELECT appearing in FROM
***************
*** 563,568 ****
--- 695,726 ----
}
+ static RangeTblEntry *
+ transformRangeRecursive(ParseState *pstate, RangeVar *n, RangeRecursive *r)
+ {
+ RangeTblEntry *rte;
+ Alias *a = copyObject(r->alias);
+
+ Assert(r->alias != NULL);
+
+ if (n->alias && n->alias->aliasname)
+ a->aliasname = n->alias->aliasname;
+ if (n->alias && n->alias->colnames)
+ a->colnames = n->alias->colnames;
+
+ /*
+ * OK, build an RTE for the subquery.
+ */
+
+ if (r->recursive == true)
+ rte = addRangeTableEntryForRecursive(pstate, (Query *) r->subquery, (Query *) r->non_recursive_term, a, true);
+ else
+ rte = addRangeTableEntryForSubquery(pstate, (Query *) r->subquery, a, true);
+
+ return rte;
+ }
+
+
/*
* transformFromClauseItem -
* Transform a FROM-clause item, adding any required entries to the
***************
*** 598,608 ****
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));
--- 756,806 ----
if (IsA(n, RangeVar))
{
/* Plain relation reference */
+ RangeVar *rv = (RangeVar *) n;
RangeTblRef *rtr;
! RangeTblEntry *rte = NULL;
int rtindex;
! if (!rv->schemaname)
! {
! /*
! * We have to check if this is a reference to a common table
! * expression (ie subquery defined in the WITH clause). Either
! * in this query or any parent query.
! */
! ParseState *ps;
! ListCell *lc;
! bool found = false;
!
! for (ps = pstate; ps; ps = ps->parentParseState)
! {
! foreach(lc, ps->p_ctenamespace)
! {
! RangeSubselect *r = (RangeSubselect *) lfirst(lc);
! if (strcmp(rv->relname, r->alias->aliasname) == 0)
! {
! rte = transformRangeCTE(pstate, rv, r);
! found = true;
! break;
! }
! }
!
! foreach(lc, ps->p_recursive_namespace)
! {
! RangeRecursive *r = (RangeRecursive *) lfirst(lc);
! if (strcmp(rv->relname, r->alias->aliasname) == 0)
! {
! rte = transformRangeRecursive(pstate, rv, r);
! found = true;
! break;
! }
! }
! }
! }
!
! 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));
*** pgsql/src/backend/parser/parse_cte.c 1970-01-01 09:00:00.000000000 +0900
--- pgsql.patched/src/backend/parser/parse_cte.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 0 ****
--- 1,674 ----
+ /*-------------------------------------------------------------------------
+ *
+ * parse_cte.c
+ * handle cte(common table expression) 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/makefuncs.h"
+ #include "parser/analyze.h"
+ #include "parser/parsetree.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"
+
+ typedef struct CteNode CteNode;
+ struct CteNode {
+ int id;
+ RangeRecursive *subquery;
+ List *depend_list;
+ };
+
+ typedef enum RecursiveType
+ {
+ NON_RECURSIVE,
+ RECURSIVE_SELF,
+ RECURSIVE_OTHER
+ } RecursiveType;
+
+ /* Dependency collector functions */
+ static void makeDependFromCteName(ParseState *pstate, CteNode *node, int index, char *name, char *myname);
+ static void makeDependFromTargetList(ParseState *pstate, CteNode *node, int index, List *targetList, char *name);
+ static void makeDependFromFromClause(ParseState *pstate, CteNode *node, int index, List *fromClause, char *name);
+ static void makeDependFromSelectStmt(ParseState *pstate, CteNode *node, int index, SelectStmt *stmt, char *name);
+ static void TopologicalSort(CteNode *nodes, int len);
+
+ /* Checker function */
+ static RecursiveType checkCteSelectStmt(ParseState *pstate, SelectStmt *n, char *myname);
+ static RecursiveType checkCteTargetList(ParseState *pstate, List *targetList, char *myname);
+ static RecursiveType checkCteTargetListItem(ParseState *pstate, Node *target, char *myname);
+ static RecursiveType checkCteFromClause(ParseState *pstate, List *fromClause, char *myname);
+ static RecursiveType checkWhereClause(ParseState *pstate, Node *n, char *myname);
+ static RecursiveType checkCteFromClauseItem(ParseState *pstate, Node *n, char *myname);
+ static RecursiveType duplicateCteName(List *namespace, char *name, char *myname, bool check_non_recursive);
+
+ static Query *non_recursive_term;
+
+ static void makeDependFromCteName(ParseState *pstate, CteNode *node, int index, char *name, char *myname)
+ {
+ ListCell *l1;
+ int i = 0, j, len = list_length(pstate->p_recursive_namespace);
+
+ foreach(l1, pstate->p_recursive_namespace)
+ {
+ RangeSubselect *cte = (RangeSubselect *) lfirst(l1);
+ const char *aliasname1 = cte->alias->aliasname;
+
+ if (strcmp(name, aliasname1) != 0)
+ continue; /* definitely no conflict */
+
+ /* Add dependency */
+ if (strcmp(name, myname) != 0)
+ {
+ for (j = 0; j < len; j++)
+ if (strcmp(node[j].subquery->alias->aliasname, myname) == 0)
+ break;
+
+ /* Check if name has already registered */
+ if (!list_member_int(node[j].depend_list, i))
+ node[j].depend_list = lappend_int(node[j].depend_list, i);
+ elog(DEBUG1, "Cte name dependency: %s(%d) -> %s(%d)", myname, j, name, i);
+ break;
+ }
+
+ i++;
+ }
+ }
+
+ static void makeDependFromTargetList(ParseState *pstate, CteNode *node, int index, List *targetList, char *name)
+ {
+ ListCell *lc;
+
+ foreach (lc, targetList)
+ {
+ Node *n = (Node *)lfirst(lc);
+
+ 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 makeDependFromSelectStmt(pstate, node, index, (SelectStmt *) sl->subselect, name);
+ }
+ }
+ }
+ }
+
+ static void makeDependFromFromClauseItem(ParseState *pstate, CteNode *node, int index, Node *n, char *name)
+ {
+ if (IsA(n, RangeVar)) /* table reference */
+ {
+ RangeVar *rv = (RangeVar *)n;
+ if (!rv->schemaname)
+ makeDependFromCteName(pstate, node, index, rv->relname, name);
+ }
+ else if (IsA(n, RangeSubselect))
+ {
+ RangeSubselect *rs = (RangeSubselect *)n;
+
+ makeDependFromSelectStmt(pstate, node, index, (SelectStmt *)rs->subquery, name);
+ }
+ else if (IsA(n, RangeFunction))
+ {
+ /* do nothing */
+ }
+ else if (IsA(n, JoinExpr))
+ {
+ JoinExpr *je = (JoinExpr *) n;
+
+ makeDependFromFromClauseItem(pstate, node, index, je->larg, name);
+ makeDependFromFromClauseItem(pstate, node, index, je->rarg, name);
+ }
+
+ else
+ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(n));
+ }
+
+ static void makeDependFromFromClause(ParseState *pstate, CteNode *node, int index, List *fromClause, char *name)
+ {
+ ListCell *lc;
+
+ foreach(lc, fromClause)
+ makeDependFromFromClauseItem(pstate, node, index, (Node *)lfirst(lc), name);
+ }
+
+ static void makeDependFromSelectStmt(ParseState *pstate, CteNode *node, int index, SelectStmt *stmt, char *name)
+ {
+ if (stmt->op == SETOP_NONE)
+ {
+ if (stmt->targetList)
+ makeDependFromTargetList(pstate, node, index, stmt->targetList, name);
+
+ if (stmt->fromClause)
+ makeDependFromFromClause(pstate, node, index, stmt->fromClause, name);
+
+ if (stmt->whereClause)
+ ;
+ }
+ else
+ {
+ SelectStmt *larg = (SelectStmt *)stmt->larg;
+ SelectStmt *rarg = (SelectStmt *)stmt->rarg;
+
+ makeDependFromSelectStmt(pstate, node, index, larg, name);
+ makeDependFromSelectStmt(pstate, node, index, rarg, name);
+ }
+ }
+
+ static List *makeDependencyGraph(ParseState *pstate, CteNode *nodes, List *recursive_namespace)
+ {
+ List *list;
+ ListCell *lc;
+ int i = 0;
+
+ /* initialize */
+ i = 0;
+ foreach(lc, recursive_namespace)
+ {
+ nodes[i].subquery = (RangeRecursive *)lfirst(lc);
+ i++;
+ }
+
+ i = 0;
+ foreach(lc, recursive_namespace)
+ {
+ RangeRecursive *rr = (RangeRecursive *)lfirst(lc);
+
+ makeDependFromSelectStmt(pstate, nodes, i, (SelectStmt *)rr->subquery, rr->alias->aliasname);
+ }
+
+ TopologicalSort(nodes, list_length(pstate->p_recursive_namespace));
+
+ /* Reconstruct pstate->p_recursive_namespace order by topological sort. */
+ list = list_make1(nodes[0].subquery);
+ for (i = 1; i < list_length(pstate->p_recursive_namespace); i++)
+ {
+ list = lappend(list, nodes[i].subquery);
+ }
+
+ return list;
+ }
+
+ /* Sort by dependency */
+ static void TopologicalSort(CteNode *nodes, int len)
+ {
+ int i, j, k;
+ CteNode tmp;
+
+ for (i = 0; i < len; i++)
+ {
+ for (j = i; j < len; j++)
+ {
+ if (nodes[j].depend_list == NULL ||
+ list_length(nodes[j].depend_list) == 0)
+ {
+ for (k = 0; k < len; k++)
+ nodes[k].depend_list = list_delete_int(nodes[k].depend_list, i);
+
+ elog(DEBUG1, "swap %d <--> %d", i, j);
+
+ /* swap */
+ if (i == j)
+ break;
+
+ memcpy(&tmp, &nodes[i], sizeof(CteNode));
+ memcpy(&nodes[i], &nodes[j], sizeof(CteNode));
+ memcpy(&nodes[j], &tmp, sizeof(CteNode));
+
+ break;
+ }
+
+ }
+
+ /* If this condition is true, the dependency graph has cycle */
+ if (j == len)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("mutual recursive call is not supported")));
+ }
+ }
+
+ void checkWellFormedCte(ParseState *pstate, WithClause *withClause)
+ {
+ ListCell *lc;
+ CteNode *nodes;
+ int i;
+ Size size;
+
+ if (list_length(pstate->p_recursive_namespace) > 1)
+ {
+ /* initialize dependency graph */
+ size = list_length(pstate->p_recursive_namespace);
+ nodes = palloc(size * sizeof(CteNode));
+ MemSet(nodes, 0, size * sizeof(CteNode));
+ for (i = 0; i < size; i++)
+ nodes[i].id = i;
+
+ pstate->p_recursive_namespace = makeDependencyGraph(pstate, nodes, pstate->p_recursive_namespace);
+ }
+
+ i = 0;
+ foreach(lc, pstate->p_recursive_namespace)
+ {
+ RangeRecursive *cte = lfirst(lc);
+ SelectStmt *stmt = (SelectStmt *)cte->subquery;
+
+ if (stmt->op == SETOP_NONE)
+ {
+ RecursiveType r = NON_RECURSIVE;
+
+ r = checkCteSelectStmt(pstate, stmt, cte->alias->aliasname);
+ if (r != NON_RECURSIVE)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("recursive query must have a non-recursive term")));
+
+ if (non_recursive_term)
+ cte->non_recursive_term = copyObject(non_recursive_term);
+ else
+ {
+ cte->subquery = (Node *)parse_sub_analyze(cte->subquery, pstate);
+ cte->recursive = false;
+ }
+ non_recursive_term = NULL;
+ }
+ else
+ {
+ SelectStmt *larg = (SelectStmt *)stmt->larg;
+ SelectStmt *rarg = (SelectStmt *)stmt->rarg;
+ RecursiveType lresult = NON_RECURSIVE, rresult = NON_RECURSIVE;
+
+ lresult = checkCteSelectStmt(pstate, larg, cte->alias->aliasname);
+ rresult = checkCteSelectStmt(pstate, rarg, cte->alias->aliasname);
+
+ /* Check if a query is a recursive query */
+ if (lresult == NON_RECURSIVE && rresult == NON_RECURSIVE)
+ {
+ if (non_recursive_term)
+ {
+ cte->non_recursive_term = copyObject(non_recursive_term);
+ }
+ else
+ {
+ cte->subquery = (Node *)parse_sub_analyze(cte->subquery, pstate);
+ cte->recursive = false;
+ }
+ non_recursive_term = NULL;
+ }
+ else if (lresult == RECURSIVE_OTHER || rresult == RECURSIVE_OTHER)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("mutual recursive call is not supported")));
+ /*
+ * 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)
+ {
+ ListCell *lc;
+
+ 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")));
+
+ /* check AGGREGATE(*) */
+ foreach (lc, rarg->targetList)
+ {
+ Node *n = lfirst(lc);
+
+ if (IsA(n, ResTarget))
+ {
+ ResTarget *rt = (ResTarget *) n;
+ if (rt->val && IsA(rt->val, FuncCall))
+ {
+ FuncCall *fc = (FuncCall *) lfirst(lc);
+ if (fc->agg_star)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("aggregate functions in a recursive term not allowed")));
+ }
+ }
+ }
+
+ /*
+ * Analyze non_recursive_term.
+ */
+ if (non_recursive_term)
+ cte->non_recursive_term = copyObject(non_recursive_term);
+ else
+ cte->non_recursive_term = (Node *) parse_sub_analyze(copyObject(larg), pstate);
+ cte->recursive = true;
+ non_recursive_term = NULL;
+ }
+
+ else
+ elog(ERROR, "unknown error in checkWellFormedCte");
+ }
+ i++;
+ }
+ }
+
+ static RecursiveType checkCteSelectStmt(ParseState *pstate, SelectStmt *n, char *myname)
+ {
+ RecursiveType r = NON_RECURSIVE;
+
+ if (n->op == SETOP_UNION)
+ {
+ RecursiveType r1, r2;
+
+ r1 = checkCteSelectStmt(pstate, n->larg, myname);
+ r2 = checkCteSelectStmt(pstate, n->rarg, myname);
+
+ 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(pstate, n->targetList, myname) != 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(pstate, n->fromClause, myname);
+
+ if (n->whereClause)
+ {
+ /* Cannot contain any recursive names in WHERE-clause */
+ if (checkWhereClause(pstate, n->whereClause, myname) != 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(ParseState *pstate, List *targetList, char *myname)
+ {
+ ListCell *lc;
+ RecursiveType result = NON_RECURSIVE;
+
+ foreach(lc, targetList)
+ {
+ RecursiveType r;
+ r = checkCteTargetListItem(pstate, lfirst(lc), myname);
+ 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(ParseState *pstate, Node *n, char *myname)
+ {
+ 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(pstate, (SelectStmt *) sl->subselect, myname);
+ }
+ }
+
+ return NON_RECURSIVE;
+ }
+
+ static RecursiveType checkWhereClause(ParseState *pstate, Node *n, char *myname)
+ {
+ if (IsA(n, SubLink))
+ return checkCteSelectStmt(pstate, (SelectStmt *) ((SubLink *) n)->subselect, myname);
+ else if (IsA(n, A_Expr))
+ {
+ RecursiveType result;
+ A_Expr *expr = (A_Expr *)n;
+
+ if (expr->lexpr && IsA(expr->lexpr, SubLink))
+ {
+ result = checkWhereClause(pstate, (Node *) expr->lexpr, myname);
+ if (result != NON_RECURSIVE)
+ return result;
+ }
+ if (expr->rexpr && IsA(expr->rexpr, SubLink))
+ {
+ result = checkWhereClause(pstate, (Node *) expr->rexpr, myname);
+ if (result != NON_RECURSIVE)
+ return result;
+ }
+ }
+
+ return NON_RECURSIVE;
+ }
+
+ /*
+ * checkCteFromClause
+ */
+ static RecursiveType checkCteFromClause(ParseState *pstate, List *fromClause, char *myname)
+ {
+ ListCell *lc;
+ RecursiveType result = NON_RECURSIVE;
+
+ foreach(lc, fromClause)
+ {
+ RecursiveType r;
+ r = checkCteFromClauseItem(pstate, lfirst(lc), myname);
+ if (result == NON_RECURSIVE)
+ result = r;
+ else if (result == RECURSIVE_SELF && r == RECURSIVE_OTHER)
+ result = r;
+ }
+ return result;
+ }
+
+ static RecursiveType checkCteFromClauseItem(ParseState *pstate, Node *n, char *myname)
+ {
+ if (IsA(n, RangeVar)) /* table reference */
+ {
+ RangeVar *rv = (RangeVar *)n;
+ if (rv->schemaname)
+ return NON_RECURSIVE;
+
+ return duplicateCteName(pstate->p_recursive_namespace, rv->relname, myname, true);
+ }
+ else if (IsA(n, RangeSubselect))
+ {
+ RangeSubselect *rs = (RangeSubselect *)n;
+
+ return checkCteSelectStmt(pstate, (SelectStmt *)rs->subquery, myname);
+ }
+ 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(pstate, je->larg, myname);
+ rarg = checkCteFromClauseItem(pstate, je->rarg, myname);
+
+ /* 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;
+ }
+
+ /*
+ * Check for common-table-expression-name conflicts between
+ * ctenamespace lists and new cte item. Raise an error if any is found.
+ */
+ void
+ checkCteNameSpaceConflicts(List *namespace, RangeSubselect *new_cte)
+ {
+ if (duplicateCteName(namespace, new_cte->alias->aliasname, new_cte->alias->aliasname, false) != NON_RECURSIVE)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_ALIAS),
+ errmsg("common table expression name \"%s\" specified more than once",
+ new_cte->alias->aliasname)));
+ }
+
+ static RecursiveType
+ duplicateCteName(List *namespace, char *name, char *myname, bool check_non_recursive)
+ {
+ ListCell *l1;
+
+ foreach(l1, namespace)
+ {
+ RangeSubselect *cte = (RangeSubselect *) lfirst(l1);
+ const char *aliasname1 = cte->alias->aliasname;
+
+ if (strcmp(name, aliasname1) != 0)
+ continue; /* definitely no conflict */
+
+ if (strcmp(name, myname) == 0)
+ return RECURSIVE_SELF;
+
+ if (check_non_recursive)
+ {
+ RangeRecursive *rte = (RangeRecursive *)cte;
+
+ /*
+ * If the RTE has a non-recursive term, we can handle it as non-recursive query.
+ */
+ if (rte->non_recursive_term)
+ {
+ if (non_recursive_term == NULL)
+ non_recursive_term = (Query *) rte->non_recursive_term;
+ return NON_RECURSIVE;
+ }
+
+ if (rte->recursive == false)
+ return NON_RECURSIVE;
+ }
+
+ return RECURSIVE_OTHER;
+ }
+ return NON_RECURSIVE;
+ }
*** pgsql/src/backend/parser/parse_relation.c 2008-05-12 09:00:50.000000000 +0900
--- pgsql.patched/src/backend/parser/parse_relation.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 1079,1084 ****
--- 1079,1173 ----
}
/*
+ * Add an entry for a WITH RECURSIVE query to the pstate's range table (p_rtable).
+ *
+ * This is just like addRangeTableEntry() except that it makes a recursive RTE.
+ * Note that an alias clause *must* be supplied.
+ */
+ RangeTblEntry *
+ addRangeTableEntryForRecursive(ParseState *pstate,
+ Query *query,
+ Query *non_recursive_term,
+ Alias *alias,
+ bool inFromCl)
+
+ {
+ RangeTblEntry *rte = makeNode(RangeTblEntry);
+ char *refname = alias->aliasname;
+ Alias *eref;
+ int numaliases;
+ int varattno;
+ ListCell *tlistitem;
+
+ rte->rtekind = RTE_RECURSIVE;
+ rte->relid = InvalidOid;
+ if (IsA(query, Query))
+ {
+ query->recursive = true;
+ rte->subquery = copyObject(query);
+ }
+ else
+ rte->self_reference = true;
+ rte->alias = alias;
+ rte->non_recursive_query = non_recursive_term;
+
+ eref = copyObject(alias);
+ numaliases = list_length(eref->colnames);
+
+ /* fill in any unspecified alias columns */
+ varattno = 0;
+ foreach(tlistitem, non_recursive_term->targetList)
+ {
+ TargetEntry *te = (TargetEntry *) lfirst(tlistitem);
+
+ if (te->resjunk)
+ continue;
+ varattno++;
+ Assert(varattno == te->resno);
+ if (varattno > numaliases)
+ {
+ char *attrname;
+
+ attrname = pstrdup(te->resname);
+ eref->colnames = lappend(eref->colnames, makeString(attrname));
+ }
+ }
+ 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.
***************
*** 1407,1412 ****
--- 1496,1540 ----
}
}
break;
+ case RTE_RECURSIVE:
+ {
+ /* Recursive RTE */
+ ListCell *aliasp_item = list_head(rte->eref->colnames);
+ ListCell *tlistitem;
+
+ varattno = 0;
+ foreach(tlistitem, rte->non_recursive_query->targetList)
+ {
+ TargetEntry *te = (TargetEntry *) lfirst(tlistitem);
+
+ if (te->resjunk)
+ continue;
+ varattno++;
+ Assert(varattno == te->resno);
+
+ if (colnames)
+ {
+ /* Assume there is one alias per target item */
+ 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,
+ exprType((Node *) te->expr),
+ exprTypmod((Node *) te->expr),
+ sublevels_up);
+
+ *colvars = lappend(*colvars, varnode);
+ }
+ }
+ }
+ break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
}
***************
*** 1711,1716 ****
--- 1839,1857 ----
*vartypmod = exprTypmod(aliasvar);
}
break;
+ case RTE_RECURSIVE:
+ {
+ /* Subselect RTE --- get type info from subselect's tlist */
+ TargetEntry *te = get_tle_by_resno(rte->non_recursive_query->targetList,
+ attnum);
+
+ if (te == NULL || te->resjunk)
+ elog(ERROR, "subquery %s does not have attribute %d",
+ rte->eref->aliasname, attnum);
+ *vartype = exprType((Node *) te->expr);
+ *vartypmod = exprTypmod((Node *) te->expr);
+ }
+ break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
}
***************
*** 1749,1754 ****
--- 1890,1896 ----
break;
case RTE_SUBQUERY:
case RTE_VALUES:
+ case RTE_RECURSIVE:
/* Subselect and Values RTEs never have dropped columns */
result = false;
break;
*** pgsql/src/backend/parser/parse_target.c 2008-04-29 23:59:17.000000000 +0900
--- pgsql.patched/src/backend/parser/parse_target.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 297,302 ****
--- 297,303 ----
case RTE_SPECIAL:
case RTE_FUNCTION:
case RTE_VALUES:
+ case RTE_RECURSIVE:
/* not a simple relation, leave it unmarked */
break;
}
***************
*** 1114,1119 ****
--- 1115,1121 ----
case RTE_RELATION:
case RTE_SPECIAL:
case RTE_VALUES:
+ case RTE_RECURSIVE:
/*
* This case should not occur: a column of a table or values list
*** pgsql/src/backend/rewrite/rewriteHandler.c 2008-01-02 04:45:51.000000000 +0900
--- pgsql.patched/src/backend/rewrite/rewriteHandler.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 1274,1284 ****
rte = rt_fetch(rt_index, parsetree->rtable);
/*
! * A subquery RTE can't have associated rules, so there's nothing to
! * do to this level of the query, but we must recurse into the
! * subquery to expand any rule references in it.
*/
! if (rte->rtekind == RTE_SUBQUERY)
{
rte->subquery = fireRIRrules(rte->subquery, activeRIRs);
continue;
--- 1274,1286 ----
rte = rt_fetch(rt_index, parsetree->rtable);
/*
! * A subquery RTE and recursive RTE can't have associated
! * rules, so there's nothing to do to this level of the query,
! * but we must recurse into the subquery to expand any rule
! * references in it.
*/
! if (rte->rtekind == RTE_SUBQUERY ||
! (rte->rtekind == RTE_RECURSIVE && !rte->self_reference))
{
rte->subquery = fireRIRrules(rte->subquery, activeRIRs);
continue;
*** pgsql/src/backend/utils/adt/ruleutils.c 2008-08-03 06:32:00.000000000 +0900
--- pgsql.patched/src/backend/utils/adt/ruleutils.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 144,149 ****
--- 144,150 ----
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_recursive_def(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);
***************
*** 2019,2024 ****
--- 2020,2062 ----
}
/* ----------
+ * get_with_recursive_def - Parse back a RECURSIVE clause
+ * ----------
+ */
+ static void
+ get_with_recursive_def(Query *query, deparse_context *context)
+ {
+ StringInfo buf = context->buf;
+ char *sep;
+ ListCell *l;
+
+ sep = "WITH RECURSIVE ";
+ foreach (l, query->jointree->fromlist)
+ {
+ RangeTblRef *rtr = (RangeTblRef *) lfirst(l);
+ if (IsA(rtr, RangeTblRef))
+ {
+ RangeTblEntry *rte = rt_fetch(rtr->rtindex, query->rtable);
+
+ if (rte->rtekind == RTE_RECURSIVE)
+ {
+ appendStringInfo(buf, "%s", sep);
+ appendStringInfo(buf, "%s", quote_identifier(rte->alias->aliasname));
+ get_from_clause_alias(rte->alias,
+ rt_fetch(rtr->rtindex, query->rtable),
+ context);
+ appendStringInfo(buf, " AS (");
+ get_query_def(rte->subquery, buf, context->namespaces, NULL,
+ context->prettyFlags, context->indentLevel);
+ appendStringInfoChar(buf, ')');
+ sep = ", ";
+ }
+ }
+ }
+
+ }
+
+ /* ----------
* get_select_query_def - Parse back a SELECT parsetree
* ----------
*/
***************
*** 2171,2176 ****
--- 2209,2219 ----
}
/*
+ * Add the WITH RECURSIVE clause if given
+ */
+ get_with_recursive_def(query, context);
+
+ /*
* Build up the query string - first we say SELECT
*/
appendStringInfo(buf, "SELECT");
***************
*** 3074,3079 ****
--- 3117,3123 ----
case RTE_RELATION:
case RTE_SPECIAL:
case RTE_VALUES:
+ case RTE_RECURSIVE:
/*
* This case should not occur: a column of a table or values list
***************
*** 4923,4928 ****
--- 4967,4976 ----
context->prettyFlags, context->indentLevel);
appendStringInfoChar(buf, ')');
break;
+ case RTE_RECURSIVE:
+ /* RECURSIVE RTE */
+ /* Do not build RTE name because RECURSIVE RTE always has alias name.*/
+ break;
case RTE_FUNCTION:
/* Function RTE */
get_rule_expr(rte->funcexpr, context, true);
***************
*** 4990,4996 ****
get_from_clause_alias(rte->eref, rte, context);
}
}
! else
{
/*
* For non-function RTEs, just report whatever the user originally
--- 5038,5044 ----
get_from_clause_alias(rte->eref, rte, context);
}
}
! else if (rte->rtekind != RTE_RECURSIVE)
{
/*
* For non-function RTEs, just report whatever the user originally
*** pgsql/src/include/executor/nodeAppend.h 2008-01-02 04:45:57.000000000 +0900
--- pgsql.patched/src/include/executor/nodeAppend.h 2008-08-18 07:47:49.000000000 +0900
***************
*** 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 */
*** pgsql/src/include/executor/nodeRecursion.h 1970-01-01 09:00:00.000000000 +0900
--- pgsql.patched/src/include/executor/nodeRecursion.h 2008-08-18 07:47:49.000000000 +0900
***************
*** 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 */
*** pgsql/src/include/executor/nodeRecursivescan.h 1970-01-01 09:00:00.000000000 +0900
--- pgsql.patched/src/include/executor/nodeRecursivescan.h 2008-08-18 07:47:49.000000000 +0900
***************
*** 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 */
*** pgsql/src/include/nodes/execnodes.h 2008-08-07 12:04:03.000000000 +0900
--- pgsql.patched/src/include/nodes/execnodes.h 2008-08-18 07:47:49.000000000 +0900
***************
*** 349,354 ****
--- 349,358 ----
List *es_subplanstates; /* List of PlanState for SubPlans */
+ Tuplestorestate **es_tuplestorestate; /* Stuff used for recursive query */
+ TupleDesc es_rscan_tupledesc;
+ bool es_disallow_tuplestore;
+
/*
* this ExprContext is for per-output-tuple operations, such as constraint
* checks and index-value computations. It will be reset for each output
***************
*** 881,886 ****
--- 885,891 ----
* 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.
***************
*** 1168,1173 ****
--- 1173,1205 ----
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
* ----------------------------------------------------------------
*** pgsql/src/include/nodes/nodes.h 2008-08-15 03:48:00.000000000 +0900
--- pgsql.patched/src/include/nodes/nodes.h 2008-08-18 07:47:49.000000000 +0900
***************
*** 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,
***************
*** 76,81 ****
--- 78,84 ----
T_PlanState = 200,
T_ResultState,
T_AppendState,
+ T_RecursionState,
T_BitmapAndState,
T_BitmapOrState,
T_ScanState,
***************
*** 87,92 ****
--- 90,96 ----
T_SubqueryScanState,
T_FunctionScanState,
T_ValuesScanState,
+ T_RecursiveScanState,
T_JoinState,
T_NestLoopState,
T_MergeJoinState,
***************
*** 145,150 ****
--- 149,155 ----
T_JoinExpr,
T_FromExpr,
T_IntoClause,
+ T_WithClause,
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
***************
*** 330,335 ****
--- 335,341 ----
T_SortBy,
T_RangeSubselect,
T_RangeFunction,
+ T_RangeRecursive,
T_TypeName,
T_ColumnDef,
T_IndexElem,
*** pgsql/src/include/nodes/parsenodes.h 2008-08-07 10:11:51.000000000 +0900
--- pgsql.patched/src/include/nodes/parsenodes.h 2008-08-18 07:47:49.000000000 +0900
***************
*** 132,137 ****
--- 132,139 ----
Node *setOperations; /* set-operation tree if this is top level of
* a UNION/INTERSECT/EXCEPT query */
+
+ bool recursive;
} Query;
***************
*** 369,374 ****
--- 371,390 ----
} RangeFunction;
/*
+ * RangeRecursion - recursive call appearing in a FROM clause
+ */
+ typedef struct RangeRecursive
+ {
+ NodeTag type;
+ Node *subquery;
+ Alias *alias; /* table alias & optional column aliases */
+ List *coldeflist; /* list of ColumnDef nodes to describe result
+ * of function returning RECORD */
+ Node *non_recursive_term;
+ bool recursive;
+ } RangeRecursive;
+
+ /*
* ColumnDef - column definition (used in various creates)
*
* If the column has a default value, we may have the value expression
***************
*** 541,547 ****
RTE_JOIN, /* join */
RTE_SPECIAL, /* special rule relation (NEW or OLD) */
RTE_FUNCTION, /* function in FROM */
! RTE_VALUES /* VALUES (), (), ... */
} RTEKind;
typedef struct RangeTblEntry
--- 557,564 ----
RTE_JOIN, /* join */
RTE_SPECIAL, /* special rule relation (NEW or OLD) */
RTE_FUNCTION, /* function in FROM */
! RTE_VALUES, /* VALUES (), (), ... */
! RTE_RECURSIVE /* WITH RECURSIVE */
} RTEKind;
typedef struct RangeTblEntry
***************
*** 583,588 ****
--- 600,611 ----
List *values_lists; /* list of expression lists */
/*
+ * Fields valid for a RECURSIVE CTE (else NIL)
+ */
+ bool self_reference; /* delay analyzing SelectStmt */
+ Query *non_recursive_query; /* non-recursive term */
+
+ /*
* Fields valid for a join RTE (else NULL/zero):
*
* joinaliasvars is a list of Vars or COALESCE expressions corresponding
***************
*** 675,680 ****
--- 698,714 ----
bool noWait; /* NOWAIT option */
} RowMarkClause;
+ /*
+ * WithClause -
+ * reprensentation of WITH clause
+ */
+ typedef struct WithClause
+ {
+ NodeTag type;
+ List *subquery; /* query list */
+ bool recursive; /* true = WITH RECURSIVE */
+ } WithClause;
+
/*****************************************************************************
* Optimizable Statements
*****************************************************************************/
***************
*** 759,764 ****
--- 793,799 ----
Node *whereClause; /* WHERE qualification */
List *groupClause; /* GROUP BY clauses */
Node *havingClause; /* HAVING conditional-expression */
+ WithClause *withClause; /* WITH [RECURSIVE] clause */
/*
* In a "leaf" node representing a VALUES list, the above fields are all
*** pgsql/src/include/nodes/plannodes.h 2008-08-08 04:35:02.000000000 +0900
--- pgsql.patched/src/include/nodes/plannodes.h 2008-08-18 07:47:49.000000000 +0900
***************
*** 355,360 ****
--- 355,381 ----
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
*** pgsql/src/include/optimizer/cost.h 2008-08-15 03:48:00.000000000 +0900
--- pgsql.patched/src/include/optimizer/cost.h 2008-08-18 07:47:49.000000000 +0900
***************
*** 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
*** pgsql/src/include/optimizer/pathnode.h 2008-08-15 03:48:00.000000000 +0900
--- pgsql.patched/src/include/optimizer/pathnode.h 2008-08-18 07:47:49.000000000 +0900
***************
*** 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,
*** pgsql/src/include/parser/parse_clause.h 2008-08-07 10:11:52.000000000 +0900
--- pgsql.patched/src/include/parser/parse_clause.h 2008-08-18 07:47:49.000000000 +0900
***************
*** 16,21 ****
--- 16,22 ----
#include "parser/parse_node.h"
+ extern void transformWithClause(ParseState *pstate, WithClause *withClause);
extern void transformFromClause(ParseState *pstate, List *frmList);
extern int setTargetTable(ParseState *pstate, RangeVar *relation,
bool inh, bool alsoSource, AclMode requiredPerms);
*** pgsql/src/include/parser/parse_cte.h 1970-01-01 09:00:00.000000000 +0900
--- pgsql.patched/src/include/parser/parse_cte.h 2008-08-18 07:47:49.000000000 +0900
***************
*** 0 ****
--- 1,22 ----
+ /*-------------------------------------------------------------------------
+ *
+ * parse_cte.h
+ * handle cte(common table expression) 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 void checkCteNameSpaceConflicts(List *namespace, RangeSubselect *new_cte);
+ extern void checkWellFormedCte(ParseState *pstate, WithClause *withClause);
+
+ #endif /* PARSE_CTE_H */
*** pgsql/src/include/parser/parse_node.h 2008-06-19 09:46:06.000000000 +0900
--- pgsql.patched/src/include/parser/parse_node.h 2008-08-18 07:47:49.000000000 +0900
***************
*** 58,63 ****
--- 58,71 ----
* of ParseStates, only the topmost ParseState contains paramtype info; but
* we copy the p_variableparams flag down to the child nodes for speed in
* coerce_type.
+ *
+ * [1] Note that p_ctenamespace is a namespace for "relations" but distinct
+ * from p_relnamespace. p_ctenamespace is a list of relations that can be
+ * referred to in a FROM or JOIN clause (in addition to normal tables and
+ * views). p_relnamespace is the list of relations which already have been
+ * listed in such clauses and therefore can be referred to in qualified
+ * variable references. Also, note that p_ctenamespace is a list of
+ * RangeSubselects, not a list of range table entries.
*/
typedef struct ParseState
{
***************
*** 68,73 ****
--- 76,83 ----
* 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 expressions [1] */
+ List *p_recursive_namespace; /* current namespace for recursive common table expressions */
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 */
***************
*** 78,83 ****
--- 88,94 ----
bool p_hasSubLinks;
bool p_is_insert;
bool p_is_update;
+ bool p_in_with_clause;
Relation p_target_relation;
RangeTblEntry *p_target_rangetblentry;
} ParseState;
*** pgsql/src/include/parser/parse_relation.h 2008-01-02 04:45:58.000000000 +0900
--- pgsql.patched/src/include/parser/parse_relation.h 2008-08-18 07:47:49.000000000 +0900
***************
*** 63,68 ****
--- 63,73 ----
List *exprs,
Alias *alias,
bool inFromCl);
+ extern RangeTblEntry *addRangeTableEntryForRecursive(ParseState *pstate,
+ Query *query,
+ Query *non_recursive_term,
+ Alias *alias,
+ bool inFromCl);
extern RangeTblEntry *addRangeTableEntryForJoin(ParseState *pstate,
List *colnames,
JoinType jointype,
*** pgsql/src/interfaces/ecpg/preproc/preproc.y 2008-07-16 10:30:23.000000000 +0900
--- pgsql.patched/src/interfaces/ecpg/preproc/preproc.y 2008-08-18 07:47:49.000000000 +0900
***************
*** 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
*** pgsql/src/test/regress/parallel_schedule 2008-04-11 07:25:26.000000000 +0900
--- pgsql.patched/src/test/regress/parallel_schedule 2008-08-18 07:47:49.000000000 +0900
***************
*** 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
*** pgsql/src/test/regress/serial_schedule 2008-04-11 07:25:26.000000000 +0900
--- pgsql.patched/src/test/regress/serial_schedule 2008-08-18 07:47:49.000000000 +0900
***************
*** 114,118 ****
--- 114,119 ----
test: returning
test: largeobject
test: xml
+ test: recursive
test: stats
test: tablespace
*** pgsql/src/test/regress/sql/recursive.sql 1970-01-01 09:00:00.000000000 +0900
--- pgsql.patched/src/test/regress/sql/recursive.sql 2008-08-18 07:47:49.000000000 +0900
***************
*** 0 ****
--- 1,319 ----
+ 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;
+
+ -- DISTINCT
+ WITH RECURSIVE t(n) AS (
+ SELECT 1
+
+ UNION ALL
+
+ SELECT DISTINCT n+1 FROM t WHERE n < 10
+ )
+ 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(*) 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 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;
*** pgsql/src/test/regress/expected/recursive.out 1970-01-01 09:00:00.000000000 +0900
--- pgsql.patched/src/test/regress/expected/recursive.out 2008-08-18 07:47:49.000000000 +0900
***************
*** 0 ****
--- 1,517 ----
+ 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)
+
+ -- DISTINCT
+ WITH RECURSIVE t(n) AS (
+ SELECT 1
+
+ UNION ALL
+
+ SELECT DISTINCT n+1 FROM t WHERE n < 10
+ )
+ SELECT * FROM t;
+ n
+ ----
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ (10 rows)
+
+ 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
+ -- 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(*) 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 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 recursive call is not supported
*** pgsql/src/bin/psql/tab-complete.c 2008-08-16 10:36:35.000000000 +0900
--- pgsql.patched/src/bin/psql/tab-complete.c 2008-08-18 07:47:49.000000000 +0900
***************
*** 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)