Implement SQL-standard WITH clauses, including WITH RECURSIVE.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 4 Oct 2008 21:56:55 +0000 (21:56 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 4 Oct 2008 21:56:55 +0000 (21:56 +0000)
There are some unimplemented aspects: recursive queries must use UNION ALL
(should allow UNION too), and we don't have SEARCH or CYCLE clauses.
These might or might not get done for 8.4, but even without them it's a
pretty useful feature.

There are also a couple of small loose ends and definitional quibbles,
which I'll send a memo about to pgsql-hackers shortly.  But let's land
the patch now so we can get on with other development.

Yoshiyuki Asaba, with lots of help from Tatsuo Ishii and Tom Lane

77 files changed:
doc/src/sgml/errcodes.sgml
doc/src/sgml/queries.sgml
doc/src/sgml/ref/select.sgml
doc/src/sgml/ref/select_into.sgml
src/backend/catalog/dependency.c
src/backend/commands/explain.c
src/backend/executor/Makefile
src/backend/executor/execAmi.c
src/backend/executor/execProcnode.c
src/backend/executor/nodeCtescan.c [new file with mode: 0644]
src/backend/executor/nodeRecursiveunion.c [new file with mode: 0644]
src/backend/executor/nodeSubplan.c
src/backend/executor/nodeWorktablescan.c [new file with mode: 0644]
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/nodeFuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/print.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/path/allpaths.c
src/backend/optimizer/path/clausesel.c
src/backend/optimizer/path/costsize.c
src/backend/optimizer/path/joinpath.c
src/backend/optimizer/plan/createplan.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/plan/setrefs.c
src/backend/optimizer/plan/subselect.c
src/backend/optimizer/prep/prepjointree.c
src/backend/optimizer/prep/prepunion.c
src/backend/optimizer/util/clauses.c
src/backend/optimizer/util/pathnode.c
src/backend/optimizer/util/plancat.c
src/backend/optimizer/util/relnode.c
src/backend/parser/Makefile
src/backend/parser/analyze.c
src/backend/parser/gram.y
src/backend/parser/keywords.c
src/backend/parser/parse_agg.c
src/backend/parser/parse_clause.c
src/backend/parser/parse_cte.c [new file with mode: 0644]
src/backend/parser/parse_relation.c
src/backend/parser/parse_target.c
src/backend/parser/parse_type.c
src/backend/rewrite/rewriteDefine.c
src/backend/rewrite/rewriteHandler.c
src/backend/rewrite/rewriteManip.c
src/backend/utils/adt/ruleutils.c
src/backend/utils/cache/plancache.c
src/backend/utils/sort/tuplestore.c
src/bin/psql/tab-complete.c
src/include/catalog/catversion.h
src/include/executor/nodeCtescan.h [new file with mode: 0644]
src/include/executor/nodeRecursiveunion.h [new file with mode: 0644]
src/include/executor/nodeWorktablescan.h [new file with mode: 0644]
src/include/nodes/execnodes.h
src/include/nodes/nodeFuncs.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/nodes/plannodes.h
src/include/nodes/primnodes.h
src/include/nodes/relation.h
src/include/optimizer/cost.h
src/include/optimizer/pathnode.h
src/include/optimizer/planmain.h
src/include/optimizer/planner.h
src/include/optimizer/subselect.h
src/include/parser/parse_cte.h [new file with mode: 0644]
src/include/parser/parse_node.h
src/include/parser/parse_relation.h
src/include/utils/errcodes.h
src/include/utils/tuplestore.h
src/interfaces/ecpg/preproc/preproc.y
src/pl/plpgsql/src/plerrcodes.h
src/test/regress/expected/with.out [new file with mode: 0644]
src/test/regress/parallel_schedule
src/test/regress/serial_schedule
src/test/regress/sql/with.sql [new file with mode: 0644]

index 474c0ca8da711eb96f446898b1f41dcdb11c5f7b..574e7f5fbad75cb5db8adebdbdc92f62e71cba31 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/errcodes.sgml,v 1.24 2008/05/15 22:39:48 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/errcodes.sgml,v 1.25 2008/10/04 21:56:52 tgl Exp $ -->
 
 <appendix id="errcodes-appendix">
  <title><productname>PostgreSQL</productname> Error Codes</title>
 <entry>grouping_error</entry>
 </row>
 
+<row>
+<entry><literal>42P19</literal></entry>
+<entry>INVALID RECURSION</entry>
+<entry>invalid_recursion</entry>
+</row>
+
 <row>
 <entry><literal>42830</literal></entry>
 <entry>INVALID FOREIGN KEY</entry>
index e3b6be4d97b277f8009f7cb04512699d07fd430c..b3d72ceb7f8d5237b5c8f2348c7a6ba395aa5d8c 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/queries.sgml,v 1.45 2008/02/15 22:17:06 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/queries.sgml,v 1.46 2008/10/04 21:56:52 tgl Exp $ -->
 
 <chapter id="queries">
  <title>Queries</title>
    used to specify queries.  The general syntax of the
    <command>SELECT</command> command is
 <synopsis>
-SELECT <replaceable>select_list</replaceable> FROM <replaceable>table_expression</replaceable> <optional><replaceable>sort_specification</replaceable></optional>
+<optional>WITH <replaceable>with_queries</replaceable></optional> SELECT <replaceable>select_list</replaceable> FROM <replaceable>table_expression</replaceable> <optional><replaceable>sort_specification</replaceable></optional>
 </synopsis>
    The following sections describe the details of the select list, the
-   table expression, and the sort specification.
+   table expression, and the sort specification.  <literal>WITH</>
+   queries are treated last since they are an advanced feature.
   </para>
 
   <para>
@@ -107,7 +108,7 @@ SELECT random();
 
   <sect2 id="queries-from">
    <title>The <literal>FROM</literal> Clause</title>
+
    <para>
     The <xref linkend="sql-from" endterm="sql-from-title"> derives a
     table from one or more other tables given in a comma-separated
@@ -211,7 +212,7 @@ FROM <replaceable>table_reference</replaceable> <optional>, <replaceable>table_r
 <replaceable>T1</replaceable> { <optional>INNER</optional> | { LEFT | RIGHT | FULL } <optional>OUTER</optional> } JOIN <replaceable>T2</replaceable> USING ( <replaceable>join column list</replaceable> )
 <replaceable>T1</replaceable> NATURAL { <optional>INNER</optional> | { LEFT | RIGHT | FULL } <optional>OUTER</optional> } JOIN <replaceable>T2</replaceable>
 </synopsis>
-        
+
        <para>
         The words <literal>INNER</literal> and
         <literal>OUTER</literal> are optional in all forms.
@@ -303,7 +304,7 @@ FROM <replaceable>table_reference</replaceable> <optional>, <replaceable>table_r
           </para>
          </listitem>
         </varlistentry>
-         
+
         <varlistentry>
          <term><literal>RIGHT OUTER JOIN</></term>
 
@@ -326,7 +327,7 @@ FROM <replaceable>table_reference</replaceable> <optional>, <replaceable>table_r
           </para>
          </listitem>
         </varlistentry>
-         
+
         <varlistentry>
          <term><literal>FULL OUTER JOIN</></term>
 
@@ -1042,7 +1043,7 @@ SELECT a AS value, b + c AS sum FROM ...
    <para>
     If no output column name is specified using <literal>AS</>,
     the system assigns a default column name.  For simple column references,
-    this is the name of the referenced column.  For function 
+    this is the name of the referenced column.  For function
     calls, this is the name of the function.  For complex expressions,
     the system will generate a generic name.
    </para>
@@ -1302,7 +1303,7 @@ SELECT a, max(b) FROM table1 GROUP BY a ORDER BY 1;
 <programlisting>
 SELECT a + b AS sum, c FROM table1 ORDER BY sum + c;          -- wrong
 </programlisting>
-   This restriction is made to reduce ambiguity.  There is still 
+   This restriction is made to reduce ambiguity.  There is still
    ambiguity if an <literal>ORDER BY</> item is a simple name that
    could match either an output column name or a column from the table
    expression.  The output column is used in such cases.  This would
@@ -1455,4 +1456,185 @@ SELECT <replaceable>select_list</replaceable> FROM <replaceable>table_expression
 
  </sect1>
 
+
+ <sect1 id="queries-with">
+  <title><literal>WITH</literal> Queries</title>
+
+  <indexterm zone="queries-with">
+   <primary>WITH</primary>
+   <secondary>in SELECT</secondary>
+  </indexterm>
+
+  <indexterm>
+   <primary>common table expression</primary>
+   <see>WITH</see>
+  </indexterm>
+
+  <para>
+   <literal>WITH</> provides a way to write subqueries for use in a larger
+   <literal>SELECT</> query.  The subqueries can be thought of as defining
+   temporary tables that exist just for this query.  One use of this feature
+   is to break down complicated queries into simpler parts.  An example is:
+
+<programlisting>
+WITH regional_sales AS (
+        SELECT region, SUM(amount) AS total_sales
+        FROM orders
+        GROUP BY region
+     ), top_regions AS (
+        SELECT region
+        FROM regional_sales
+        WHERE total_sales &gt; (SELECT SUM(total_sales)/10 FROM regional_sales)
+     )
+SELECT region,
+       product,
+       SUM(quantity) AS product_units,
+       SUM(amount) AS product_sales
+FROM orders
+WHERE region IN (SELECT region FROM top_regions)
+GROUP BY region, product;
+</programlisting>
+
+   which displays per-product sales totals in only the top sales regions.
+   This example could have been written without <literal>WITH</>,
+   but we'd have needed two levels of nested sub-SELECTs.  It's a bit
+   easier to follow this way.
+  </para>
+
+  <para>
+   The optional <literal>RECURSIVE</> modifier changes <literal>WITH</>
+   from a mere syntactic convenience into a feature that accomplishes
+   things not otherwise possible in standard SQL.  Using
+   <literal>RECURSIVE</>, a <literal>WITH</> query can refer to its own
+   output.  A very simple example is this query to sum the integers from 1
+   through 100:
+
+<programlisting>
+WITH RECURSIVE t(n) AS (
+    VALUES (1)
+  UNION ALL
+    SELECT n+1 FROM t WHERE n &lt; 100
+)
+SELECT sum(n) FROM t;
+</programlisting>
+
+   The general form of a recursive <literal>WITH</> query is always a
+   <firstterm>non-recursive term</>, then <literal>UNION ALL</>, then a
+   <firstterm>recursive term</>, where only the recursive term can contain
+   a reference to the query's own output.  Such a query is executed as
+   follows:
+  </para>
+
+  <procedure>
+   <title>Recursive Query Evaluation</title>
+
+   <step performance="required">
+    <para>
+     Evaluate the non-recursive term.  Include all its output rows in the
+     result of the recursive query, and also place them in a temporary
+     <firstterm>working table</>.
+    </para>
+   </step>
+
+   <step performance="required">
+    <para>
+     So long as the working table is not empty, repeat these steps:
+    </para>
+    <substeps>
+     <step performance="required">
+      <para>
+       Evaluate the recursive term, substituting the current contents of
+       the working table for the recursive self-reference.  Include all its
+       output rows in the result of the recursive query, and also place them
+       in a temporary <firstterm>intermediate table</>.
+      </para>
+     </step>
+
+     <step performance="required">
+      <para>
+       Replace the contents of the working table with the contents of the
+       intermediate table, then empty the intermediate table.
+      </para>
+     </step>
+    </substeps>
+   </step>
+  </procedure>
+
+  <note>
+   <para>
+    Strictly speaking, this process is iteration not recursion, but
+    <literal>RECURSIVE</> is the terminology chosen by the SQL standards
+    committee.
+   </para>
+  </note>
+
+  <para>
+   In the example above, the working table has just a single row in each step,
+   and it takes on the values from 1 through 100 in successive steps.  In
+   the 100th step, there is no output because of the <literal>WHERE</>
+   clause, and so the query terminates.
+  </para>
+
+  <para>
+   Recursive queries are typically used to deal with hierarchical or
+   tree-structured data.  A useful example is this query to find all the
+   direct and indirect sub-parts of a product, given only a table that
+   shows immediate inclusions:
+
+<programlisting>
+WITH RECURSIVE included_parts(sub_part, part, quantity) AS (
+    SELECT sub_part, part, quantity FROM parts WHERE part = 'our_product'
+  UNION ALL
+    SELECT p.sub_part, p.part, p.quantity
+    FROM included_parts pr, parts p
+    WHERE p.part = pr.sub_part
+  )
+SELECT sub_part, SUM(quantity) as total_quantity
+FROM included_parts
+GROUP BY sub_part
+</programlisting>
+  </para>
+
+  <para>
+   When working with recursive queries it is important to be sure that
+   the recursive part of the query will eventually return no tuples,
+   or else the query will loop indefinitely.  A useful trick for
+   development purposes is to place a <literal>LIMIT</> in the parent
+   query.  For example, this query would loop forever without the
+   <literal>LIMIT</>:
+
+<programlisting>
+WITH RECURSIVE t(n) AS (
+    SELECT 1
+  UNION ALL
+    SELECT n+1 FROM t
+)
+SELECT n FROM t LIMIT 100;
+</programlisting>
+
+   This works because <productname>PostgreSQL</productname>'s implementation
+   evaluates only as many rows of a <literal>WITH</> query as are actually
+   demanded by the parent query.  Using this trick in production is not
+   recommended, because other systems might work differently.
+  </para>
+
+  <para>
+   A useful property of <literal>WITH</> queries is that they are evaluated
+   only once per execution of the parent query, even if they are referred to
+   more than once by the parent query or sibling <literal>WITH</> queries.
+   Thus, expensive calculations that are needed in multiple places can be
+   placed within a <literal>WITH</> query to avoid redundant work.  Another
+   possible application is to prevent unwanted multiple evaluations of
+   functions with side-effects.
+   However, the other side of this coin is that the optimizer is less able to
+   push restrictions from the parent query down into a <literal>WITH</> query
+   than an ordinary sub-query.  The <literal>WITH</> query will generally be
+   evaluated as stated, without suppression of rows that the parent query
+   might discard afterwards.  (But, as mentioned above, evaluation might stop
+   early if the reference(s) to the query demand only a limited number of
+   rows.)
+  </para>
+
+ </sect1>
+
 </chapter>
index d8ed7aef9c6c66befece74bcab494b1239529f59..e72d9c126f6d838647cf3b1c92c97afc9803d4e2 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/select.sgml,v 1.104 2008/09/23 09:20:35 heikki Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/select.sgml,v 1.105 2008/10/04 21:56:52 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -20,6 +20,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
+[ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
 SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replaceable> [, ...] ) ] ]
     * | <replaceable class="parameter">expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...]
     [ FROM <replaceable class="parameter">from_item</replaceable> [, ...] ]
@@ -36,9 +37,14 @@ where <replaceable class="parameter">from_item</replaceable> can be one of:
 
     [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
     ( <replaceable class="parameter">select</replaceable> ) [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ]
+    <replaceable class="parameter">with_query_name</replaceable> [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
     <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] | <replaceable class="parameter">column_definition</replaceable> [, ...] ) ]
     <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] )
     <replaceable class="parameter">from_item</replaceable> [ NATURAL ] <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> [ ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) ]
+
+and <replaceable class="parameter">with_query</replaceable> is:
+
+    <replaceable class="parameter">with_query_name</replaceable> [ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ] AS ( <replaceable class="parameter">select</replaceable> )
 </synopsis>
 
  </refsynopsisdiv>
@@ -51,6 +57,17 @@ where <replaceable class="parameter">from_item</replaceable> can be one of:
    The general processing of <command>SELECT</command> is as follows:
 
    <orderedlist>
+    <listitem>
+     <para>
+      All queries in the <literal>WITH</literal> list are computed.
+      These effectively serve as temporary tables that can be referenced
+      in the <literal>FROM</literal> list.  A <literal>WITH</literal> query
+      that is referenced more than once in <literal>FROM</literal> is
+      computed only once.
+      (See <xref linkend="sql-with" endterm="sql-with-title"> below.)
+     </para>
+    </listitem>
+
     <listitem>
      <para>
       All elements in the <literal>FROM</literal> list are computed.
@@ -163,6 +180,56 @@ where <replaceable class="parameter">from_item</replaceable> can be one of:
  <refsect1>
   <title>Parameters</title>
 
+  <refsect2 id="SQL-WITH">
+   <title id="sql-with-title"><literal>WITH</literal> Clause</title>
+
+   <para>
+    The <literal>WITH</literal> clause allows you to specify one or more
+    subqueries that can be referenced by name in the primary query.
+    The subqueries effectively act as temporary tables or views
+    for the duration of the primary query.
+   </para>
+
+   <para>
+    A name (without schema qualification) must be specified for each
+    <literal>WITH</literal> query.  Optionally, a list of column names
+    can be specified; if this is omitted,
+    the column names are inferred from the subquery.
+   </para>
+
+   <para>
+    If <literal>RECURSIVE</literal> is specified, it allows a
+    subquery to reference itself by name.  Such a subquery must have
+    the form
+<synopsis>
+<replaceable class="parameter">non_recursive_term</replaceable> UNION ALL <replaceable class="parameter">recursive_term</replaceable>
+</synopsis>
+    where the recursive self-reference must appear on the right-hand
+    side of <literal>UNION ALL</>.  Only one recursive self-reference
+    is permitted per query.
+   </para>
+
+   <para>
+    Another effect of <literal>RECURSIVE</literal> is that
+    <literal>WITH</literal> queries need not be ordered: a query
+    can reference another one that is later in the list.  (However,
+    circular references, or mutual recursion, are not implemented.)
+    Without <literal>RECURSIVE</literal>, <literal>WITH</literal> queries
+    can only reference sibling <literal>WITH</literal> queries
+    that are earlier in the <literal>WITH</literal> list.
+   </para>
+
+   <para>
+    A useful property of <literal>WITH</literal> queries is that they
+    are evaluated only once per execution of the primary query,
+    even if the primary query refers to them more than once.
+   </para>
+
+   <para>
+    See <xref linkend="queries-with"> for additional information.
+   </para>
+  </refsect2>
+
   <refsect2 id="SQL-FROM">
    <title id="sql-from-title"><literal>FROM</literal> Clause</title>
 
@@ -197,7 +264,7 @@ where <replaceable class="parameter">from_item</replaceable> can be one of:
        </para>
       </listitem>
      </varlistentry>
-     
+
      <varlistentry>
       <term><replaceable class="parameter">alias</replaceable></term>
       <listitem>
@@ -215,7 +282,7 @@ where <replaceable class="parameter">from_item</replaceable> can be one of:
        </para>
       </listitem>
      </varlistentry>
-     
+
      <varlistentry>
       <term><replaceable class="parameter">select</replaceable></term>
       <listitem>
@@ -233,6 +300,21 @@ where <replaceable class="parameter">from_item</replaceable> can be one of:
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="parameter">with_query_name</replaceable></term>
+      <listitem>
+       <para>
+        A <literal>WITH</> query is referenced by writing its name,
+        just as though the query's name were a table name.  (In fact,
+        the <literal>WITH</> query hides any real table of the same name
+        for the purposes of the primary query.  If necessary, you can
+        refer to a real table of the same name by schema-qualifying
+        the table's name.)
+        An alias can be provided in the same way as for a table.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="parameter">function_name</replaceable></term>
       <listitem>
@@ -256,7 +338,7 @@ where <replaceable class="parameter">from_item</replaceable> can be one of:
        </para>
       </listitem>
      </varlistentry>
-     
+
      <varlistentry>
       <term><replaceable class="parameter">join_type</replaceable></term>
       <listitem>
@@ -339,7 +421,7 @@ where <replaceable class="parameter">from_item</replaceable> can be one of:
        </para>
       </listitem>
      </varlistentry>
-     
+
      <varlistentry>
       <term><literal>ON <replaceable class="parameter">join_condition</replaceable></literal></term>
       <listitem>
@@ -352,7 +434,7 @@ where <replaceable class="parameter">from_item</replaceable> can be one of:
        </para>
       </listitem>
      </varlistentry>
-     
+
      <varlistentry>
       <term><literal>USING ( <replaceable class="parameter">join_column</replaceable> [, ...] )</literal></term>
       <listitem>
@@ -380,7 +462,7 @@ where <replaceable class="parameter">from_item</replaceable> can be one of:
     </variablelist>
    </para>
   </refsect2>
-   
+
   <refsect2 id="SQL-WHERE">
    <title id="sql-where-title"><literal>WHERE</literal> Clause</title>
 
@@ -397,7 +479,7 @@ WHERE <replaceable class="parameter">condition</replaceable>
     substituted for any variable references.
    </para>
   </refsect2>
-  
+
   <refsect2 id="SQL-GROUPBY">
    <title id="sql-groupby-title"><literal>GROUP BY</literal> Clause</title>
 
@@ -444,7 +526,7 @@ HAVING <replaceable class="parameter">condition</replaceable>
     where <replaceable class="parameter">condition</replaceable> is
     the same as specified for the <literal>WHERE</literal> clause.
    </para>
-    
+
    <para>
     <literal>HAVING</literal> eliminates group rows that do not
     satisfy the condition.  <literal>HAVING</literal> is different
@@ -456,7 +538,7 @@ HAVING <replaceable class="parameter">condition</replaceable>
     unambiguously reference a grouping column, unless the reference
     appears within an aggregate function.
    </para>
-    
+
    <para>
     The presence of <literal>HAVING</literal> turns a query into a grouped
     query even if there is no <literal>GROUP BY</> clause.  This is the
@@ -518,7 +600,7 @@ HAVING <replaceable class="parameter">condition</replaceable>
     the output column names will be the same as the table columns' names.
    </para>
   </refsect2>
-  
+
   <refsect2 id="SQL-UNION">
    <title id="sql-union-title"><literal>UNION</literal> Clause</title>
 
@@ -537,7 +619,7 @@ HAVING <replaceable class="parameter">condition</replaceable>
     the <literal>UNION</literal>, not to its right-hand input
     expression.)
    </para>
-    
+
    <para>
     The <literal>UNION</literal> operator computes the set union of
     the rows returned by the involved <command>SELECT</command>
@@ -548,7 +630,7 @@ HAVING <replaceable class="parameter">condition</replaceable>
     number of columns, and corresponding columns must be of compatible
     data types.
    </para>
-    
+
    <para>
     The result of <literal>UNION</> does not contain any duplicate
     rows unless the <literal>ALL</> option is specified.
@@ -556,13 +638,13 @@ HAVING <replaceable class="parameter">condition</replaceable>
     <literal>UNION ALL</> is usually significantly quicker than
     <literal>UNION</>; use <literal>ALL</> when you can.)
    </para>
-    
+
    <para>
     Multiple <literal>UNION</> operators in the same
     <command>SELECT</command> statement are evaluated left to right,
     unless otherwise indicated by parentheses.
    </para>
-    
+
    <para>
     Currently, <literal>FOR UPDATE</> and <literal>FOR SHARE</> cannot be
     specified either for a <literal>UNION</> result or for any input of a
@@ -590,7 +672,7 @@ HAVING <replaceable class="parameter">condition</replaceable>
     <command>SELECT</command> statements.  A row is in the
     intersection of two result sets if it appears in both result sets.
    </para>
-    
+
    <para>
     The result of <literal>INTERSECT</literal> does not contain any
     duplicate rows unless the <literal>ALL</> option is specified.
@@ -598,7 +680,7 @@ HAVING <replaceable class="parameter">condition</replaceable>
     left table and <replaceable>n</> duplicates in the right table will appear
     min(<replaceable>m</>,<replaceable>n</>) times in the result set.
    </para>
-    
+
    <para>
     Multiple <literal>INTERSECT</literal> operators in the same
     <command>SELECT</command> statement are evaluated left to right,
@@ -608,7 +690,7 @@ HAVING <replaceable class="parameter">condition</replaceable>
     C</literal> will be read as <literal>A UNION (B INTERSECT
     C)</literal>.
    </para>
-    
+
    <para>
     Currently, <literal>FOR UPDATE</> and <literal>FOR SHARE</> cannot be
     specified either for an <literal>INTERSECT</> result or for any input of
@@ -635,7 +717,7 @@ HAVING <replaceable class="parameter">condition</replaceable>
     that are in the result of the left <command>SELECT</command>
     statement but not in the result of the right one.
    </para>
-    
+
    <para>
     The result of <literal>EXCEPT</literal> does not contain any
     duplicate rows unless the <literal>ALL</> option is specified.
@@ -643,14 +725,14 @@ HAVING <replaceable class="parameter">condition</replaceable>
     left table and <replaceable>n</> duplicates in the right table will appear
     max(<replaceable>m</>-<replaceable>n</>,0) times in the result set.
    </para>
-    
+
    <para>
     Multiple <literal>EXCEPT</literal> operators in the same
     <command>SELECT</command> statement are evaluated left to right,
     unless parentheses dictate otherwise.  <literal>EXCEPT</> binds at
     the same level as <literal>UNION</>.
    </para>
-    
+
    <para>
     Currently, <literal>FOR UPDATE</> and <literal>FOR SHARE</> cannot be
     specified either for an <literal>EXCEPT</> result or for any input of
@@ -689,7 +771,7 @@ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC |
     possible to assign a name to an output column using the
     <literal>AS</> clause.
    </para>
-    
+
    <para>
     It is also possible to use arbitrary expressions in the
     <literal>ORDER BY</literal> clause, including columns that do not
@@ -712,7 +794,7 @@ SELECT name FROM distributors ORDER BY code;
     make in the same situation.  This inconsistency is made to be
     compatible with the SQL standard.
    </para>
-    
+
    <para>
     Optionally one can add the key word <literal>ASC</> (ascending) or
     <literal>DESC</> (descending) after any expression in the
@@ -789,7 +871,7 @@ SELECT DISTINCT ON (location) location, time, report
     desired precedence of rows within each <literal>DISTINCT ON</> group.
    </para>
   </refsect2>
-  
+
   <refsect2 id="SQL-LIMIT">
    <title id="sql-limit-title"><literal>LIMIT</literal> Clause</title>
 
@@ -1106,8 +1188,60 @@ SELECT * FROM distributors_2(111) AS (f1 int, f2 text);
  111 | Walt Disney
 </programlisting>
   </para>
+
+  <para>
+   This example shows how to use a simple <literal>WITH</> clause:
+
+<programlisting>
+WITH t AS (
+    SELECT random() as x FROM generate_series(1, 3)
+  )
+SELECT * FROM t
+UNION ALL
+SELECT * FROM t
+
+         x          
+--------------------
+  0.534150459803641
+  0.520092216785997
+ 0.0735620250925422
+  0.534150459803641
+  0.520092216785997
+ 0.0735620250925422
+</programlisting>
+
+   Notice that the <literal>WITH</> query was evaluated only once,
+   so that we got two sets of the same three random values.
+  </para>
+
+  <para>
+   This example uses <literal>WITH RECURSIVE</literal> to find all
+   subordinates (direct or indirect) of the employee Mary, and their
+   level of indirectness, from a table that shows only direct
+   subordinates:
+
+<programlisting>
+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_recursive er, employee e
+    WHERE er.employee_name = e.manager_name
+  )
+SELECT distance, employee_name FROM employee_recursive;
+</programlisting>
+
+   Notice the typical form of recursive queries:
+   an initial condition, followed by <literal>UNION ALL</literal>,
+   followed by 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 loop indefinitely.  (See <xref linkend="queries-with">
+   for more examples.)
+  </para>
  </refsect1>
+
  <refsect1>
   <title>Compatibility</title>
 
@@ -1116,7 +1250,7 @@ SELECT * FROM distributors_2(111) AS (f1 int, f2 text);
    with the SQL standard.  But there are some extensions and some
    missing features.
   </para>
-  
+
   <refsect2>
    <title>Omitted <literal>FROM</literal> Clauses</title>
 
@@ -1196,7 +1330,7 @@ SELECT distributors.* WHERE distributors.name = 'Westward';
 
    <para>
     SQL:1999 and later use a slightly different definition which is not
-    entirely upward compatible with SQL-92.  
+    entirely upward compatible with SQL-92.
     In most cases, however, <productname>PostgreSQL</productname>
     will interpret an <literal>ORDER BY</literal> or <literal>GROUP
     BY</literal> expression the same way SQL:1999 does.
index 915e859ea988ce87c2daee540594561362b33ed4..de9a86a878c87f66dc3aeabd132b8ad04d291e39 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/select_into.sgml,v 1.40 2008/02/15 22:17:06 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/select_into.sgml,v 1.41 2008/10/04 21:56:52 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -20,17 +20,18 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-SELECT [ ALL | DISTINCT [ ON ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) ] ]
-    * | <replaceable class="PARAMETER">expression</replaceable> [ [ AS ] <replaceable class="PARAMETER">output_name</replaceable> ] [, ...]
-    INTO [ TEMPORARY | TEMP ] [ TABLE ] <replaceable class="PARAMETER">new_table</replaceable>
-    [ FROM <replaceable class="PARAMETER">from_item</replaceable> [, ...] ]
-    [ WHERE <replaceable class="PARAMETER">condition</replaceable> ]
-    [ GROUP BY <replaceable class="PARAMETER">expression</replaceable> [, ...] ]
-    [ HAVING <replaceable class="PARAMETER">condition</replaceable> [, ...] ]
-    [ { UNION | INTERSECT | EXCEPT } [ ALL ] <replaceable class="PARAMETER">select</replaceable> ]
+[ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
+SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replaceable> [, ...] ) ] ]
+    * | <replaceable class="parameter">expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...]
+    INTO [ TEMPORARY | TEMP ] [ TABLE ] <replaceable class="parameter">new_table</replaceable>
+    [ FROM <replaceable class="parameter">from_item</replaceable> [, ...] ]
+    [ WHERE <replaceable class="parameter">condition</replaceable> ]
+    [ GROUP BY <replaceable class="parameter">expression</replaceable> [, ...] ]
+    [ HAVING <replaceable class="parameter">condition</replaceable> [, ...] ]
+    [ { UNION | INTERSECT | EXCEPT } [ ALL ] <replaceable class="parameter">select</replaceable> ]
     [ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
-    [ LIMIT { <replaceable class="PARAMETER">count</replaceable> | ALL } ]
-    [ OFFSET <replaceable class="PARAMETER">start</replaceable> ]
+    [ LIMIT { <replaceable class="parameter">count</replaceable> | ALL } ]
+    [ OFFSET <replaceable class="parameter">start</replaceable> ]
     [ FOR { UPDATE | SHARE } [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ] [ NOWAIT ] [...] ]
 </synopsis>
  </refsynopsisdiv>
@@ -46,7 +47,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="PARAMETER">expression</replac
    output columns of the <command>SELECT</command>.
   </para>
  </refsect1>
-  
+
  <refsect1>
   <title>Parameters</title>
 
index 3521f5ec95228a3f6b1f595e703d430ebda7df0c..b830d668426d89c7c9b47dd960d4208466ce5c41 100644 (file)
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.80 2008/08/25 22:42:32 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.81 2008/10/04 21:56:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1557,7 +1557,7 @@ find_expr_references_walker(Node *node,
                 * Add whole-relation refs for each plain relation mentioned in the
                 * subquery's rtable, as well as datatype refs for any datatypes used
                 * as a RECORD function's output.  (Note: query_tree_walker takes care
-                * of recursing into RTE_FUNCTION and RTE_SUBQUERY RTEs, so no need to
+                * of recursing into RTE_FUNCTION RTEs, subqueries, etc, so no need to
                 * do that here.  But keep it from looking at join alias lists.)
                 */
                foreach(rtable, query->rtable)
index 81f1dd0c31fcbf30b63c3178d080450eef160437..48334d1947bf55cb3d984023b911a17b8b47f550 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994-5, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.178 2008/08/19 18:30:04 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.179 2008/10/04 21:56:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -429,6 +429,9 @@ explain_outNode(StringInfo str,
                case T_Append:
                        pname = "Append";
                        break;
+               case T_RecursiveUnion:
+                       pname = "Recursive Union";
+                       break;
                case T_BitmapAnd:
                        pname = "BitmapAnd";
                        break;
@@ -537,6 +540,12 @@ explain_outNode(StringInfo str,
                case T_ValuesScan:
                        pname = "Values Scan";
                        break;
+               case T_CteScan:
+                       pname = "CTE Scan";
+                       break;
+               case T_WorkTableScan:
+                       pname = "WorkTable Scan";
+                       break;
                case T_Material:
                        pname = "Materialize";
                        break;
@@ -721,6 +730,40 @@ explain_outNode(StringInfo str,
                                                                 quote_identifier(valsname));
                        }
                        break;
+               case T_CteScan:
+                       if (((Scan *) plan)->scanrelid > 0)
+                       {
+                               RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid,
+                                                                                         es->rtable);
+
+                               /* Assert it's on a non-self-reference CTE */
+                               Assert(rte->rtekind == RTE_CTE);
+                               Assert(!rte->self_reference);
+
+                               appendStringInfo(str, " on %s",
+                                                                quote_identifier(rte->ctename));
+                               if (strcmp(rte->eref->aliasname, rte->ctename) != 0)
+                                       appendStringInfo(str, " %s",
+                                                                        quote_identifier(rte->eref->aliasname));
+                       }
+                       break;
+               case T_WorkTableScan:
+                       if (((Scan *) plan)->scanrelid > 0)
+                       {
+                               RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid,
+                                                                                         es->rtable);
+
+                               /* Assert it's on a self-reference CTE */
+                               Assert(rte->rtekind == RTE_CTE);
+                               Assert(rte->self_reference);
+
+                               appendStringInfo(str, " on %s",
+                                                                quote_identifier(rte->ctename));
+                               if (strcmp(rte->eref->aliasname, rte->ctename) != 0)
+                                       appendStringInfo(str, " %s",
+                                                                        quote_identifier(rte->eref->aliasname));
+                       }
+                       break;
                default:
                        break;
        }
@@ -787,6 +830,8 @@ explain_outNode(StringInfo str,
                case T_SeqScan:
                case T_FunctionScan:
                case T_ValuesScan:
+               case T_CteScan:
+               case T_WorkTableScan:
                        show_scan_qual(plan->qual,
                                                   "Filter",
                                                   ((Scan *) plan)->scanrelid,
@@ -1071,6 +1116,9 @@ show_plan_tlist(Plan *plan,
        /* The tlist of an Append isn't real helpful, so suppress it */
        if (IsA(plan, Append))
                return;
+       /* Likewise for RecursiveUnion */
+       if (IsA(plan, RecursiveUnion))
+               return;
 
        /* Set up deparsing context */
        context = deparse_context_for_plan((Node *) outerPlan(plan),
index 38364a9cd1388519cc1de175d5ec0cd2d790100a..b4a0492751c40d6676bee935747dce320463205e 100644 (file)
@@ -4,7 +4,7 @@
 #    Makefile for executor
 #
 # IDENTIFICATION
-#    $PostgreSQL: pgsql/src/backend/executor/Makefile,v 1.27 2008/02/19 10:30:07 petere Exp $
+#    $PostgreSQL: pgsql/src/backend/executor/Makefile,v 1.28 2008/10/04 21:56:52 tgl Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -18,9 +18,10 @@ OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \
        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
+       nodeNestloop.o nodeFunctionscan.o nodeRecursiveunion.o nodeResult.o \
+       nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
+       nodeValuesscan.o nodeCtescan.o nodeWorktablescan.o \
+       nodeLimit.o nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
+       tstoreReceiver.o spi.o
 
 include $(top_srcdir)/src/backend/common.mk
index afddf4d39200c69aa86768cfc053b74fb9274efd..1de3f5a778efedac7a6daf329a19ba53039f579e 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *     $PostgreSQL: pgsql/src/backend/executor/execAmi.c,v 1.98 2008/10/01 19:51:49 tgl Exp $
+ *     $PostgreSQL: pgsql/src/backend/executor/execAmi.c,v 1.99 2008/10/04 21:56:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -30,6 +30,7 @@
 #include "executor/nodeMaterial.h"
 #include "executor/nodeMergejoin.h"
 #include "executor/nodeNestloop.h"
+#include "executor/nodeRecursiveunion.h"
 #include "executor/nodeResult.h"
 #include "executor/nodeSeqscan.h"
 #include "executor/nodeSetOp.h"
@@ -39,6 +40,8 @@
 #include "executor/nodeTidscan.h"
 #include "executor/nodeUnique.h"
 #include "executor/nodeValuesscan.h"
+#include "executor/nodeCtescan.h"
+#include "executor/nodeWorktablescan.h"
 
 
 /*
@@ -121,6 +124,10 @@ ExecReScan(PlanState *node, ExprContext *exprCtxt)
                        ExecReScanAppend((AppendState *) node, exprCtxt);
                        break;
 
+               case T_RecursiveUnionState:
+                       ExecRecursiveUnionReScan((RecursiveUnionState *) node, exprCtxt);
+                       break;
+
                case T_BitmapAndState:
                        ExecReScanBitmapAnd((BitmapAndState *) node, exprCtxt);
                        break;
@@ -161,6 +168,14 @@ ExecReScan(PlanState *node, ExprContext *exprCtxt)
                        ExecValuesReScan((ValuesScanState *) node, exprCtxt);
                        break;
 
+               case T_CteScanState:
+                       ExecCteScanReScan((CteScanState *) node, exprCtxt);
+                       break;
+
+               case T_WorkTableScanState:
+                       ExecWorkTableScanReScan((WorkTableScanState *) node, exprCtxt);
+                       break;
+
                case T_NestLoopState:
                        ExecReScanNestLoop((NestLoopState *) node, exprCtxt);
                        break;
@@ -396,6 +411,8 @@ ExecSupportsBackwardScan(Plan *node)
                case T_TidScan:
                case T_FunctionScan:
                case T_ValuesScan:
+               case T_CteScan:
+               case T_WorkTableScan:
                        return true;
 
                case T_SubqueryScan:
index 385c415974baf6ef134ec8f223905d5aee6631f4..e689ec00f8c4669e3b153c5af557d76fcfd40556 100644 (file)
@@ -12,7 +12,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/execProcnode.c,v 1.62 2008/01/01 19:45:49 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/execProcnode.c,v 1.63 2008/10/04 21:56:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -94,6 +94,7 @@
 #include "executor/nodeMaterial.h"
 #include "executor/nodeMergejoin.h"
 #include "executor/nodeNestloop.h"
+#include "executor/nodeRecursiveunion.h"
 #include "executor/nodeResult.h"
 #include "executor/nodeSeqscan.h"
 #include "executor/nodeSetOp.h"
 #include "executor/nodeTidscan.h"
 #include "executor/nodeUnique.h"
 #include "executor/nodeValuesscan.h"
+#include "executor/nodeCtescan.h"
+#include "executor/nodeWorktablescan.h"
 #include "miscadmin.h"
 
+
 /* ------------------------------------------------------------------------
  *             ExecInitNode
  *
@@ -147,6 +151,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
                                                                                                  estate, eflags);
                        break;
 
+               case T_RecursiveUnion:
+                       result = (PlanState *) ExecInitRecursiveUnion((RecursiveUnion *) node,
+                                                                                                                 estate, eflags);
+                       break;
+
                case T_BitmapAnd:
                        result = (PlanState *) ExecInitBitmapAnd((BitmapAnd *) node,
                                                                                                         estate, eflags);
@@ -200,6 +209,16 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
                                                                                                          estate, eflags);
                        break;
 
+               case T_CteScan:
+                       result = (PlanState *) ExecInitCteScan((CteScan *) node,
+                                                                                                  estate, eflags);
+                       break;
+
+               case T_WorkTableScan:
+                       result = (PlanState *) ExecInitWorkTableScan((WorkTableScan *) node,
+                                                                                                                estate, eflags);
+                       break;
+
                        /*
                         * join nodes
                         */
@@ -323,6 +342,10 @@ ExecProcNode(PlanState *node)
                        result = ExecAppend((AppendState *) node);
                        break;
 
+               case T_RecursiveUnionState:
+                       result = ExecRecursiveUnion((RecursiveUnionState *) node);
+                       break;
+
                        /* BitmapAndState does not yield tuples */
 
                        /* BitmapOrState does not yield tuples */
@@ -360,6 +383,14 @@ ExecProcNode(PlanState *node)
                        result = ExecValuesScan((ValuesScanState *) node);
                        break;
 
+               case T_CteScanState:
+                       result = ExecCteScan((CteScanState *) node);
+                       break;
+
+               case T_WorkTableScanState:
+                       result = ExecWorkTableScan((WorkTableScanState *) node);
+                       break;
+
                        /*
                         * join nodes
                         */
@@ -501,6 +532,9 @@ ExecCountSlotsNode(Plan *node)
                case T_Append:
                        return ExecCountSlotsAppend((Append *) node);
 
+               case T_RecursiveUnion:
+                       return ExecCountSlotsRecursiveUnion((RecursiveUnion *) node);
+
                case T_BitmapAnd:
                        return ExecCountSlotsBitmapAnd((BitmapAnd *) node);
 
@@ -534,6 +568,12 @@ ExecCountSlotsNode(Plan *node)
                case T_ValuesScan:
                        return ExecCountSlotsValuesScan((ValuesScan *) node);
 
+               case T_CteScan:
+                       return ExecCountSlotsCteScan((CteScan *) node);
+
+               case T_WorkTableScan:
+                       return ExecCountSlotsWorkTableScan((WorkTableScan *) node);
+
                        /*
                         * join nodes
                         */
@@ -620,6 +660,10 @@ ExecEndNode(PlanState *node)
                        ExecEndAppend((AppendState *) node);
                        break;
 
+               case T_RecursiveUnionState:
+                       ExecEndRecursiveUnion((RecursiveUnionState *) node);
+                       break;
+
                case T_BitmapAndState:
                        ExecEndBitmapAnd((BitmapAndState *) node);
                        break;
@@ -663,6 +707,14 @@ ExecEndNode(PlanState *node)
                        ExecEndValuesScan((ValuesScanState *) node);
                        break;
 
+               case T_CteScanState:
+                       ExecEndCteScan((CteScanState *) node);
+                       break;
+
+               case T_WorkTableScanState:
+                       ExecEndWorkTableScan((WorkTableScanState *) node);
+                       break;
+
                        /*
                         * join nodes
                         */
diff --git a/src/backend/executor/nodeCtescan.c b/src/backend/executor/nodeCtescan.c
new file mode 100644 (file)
index 0000000..b4ae27d
--- /dev/null
@@ -0,0 +1,335 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeCtescan.c
+ *       routines to handle CteScan nodes.
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *       $PostgreSQL: pgsql/src/backend/executor/nodeCtescan.c,v 1.1 2008/10/04 21:56:53 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "executor/execdebug.h"
+#include "executor/nodeCtescan.h"
+#include "miscadmin.h"
+
+static TupleTableSlot *CteScanNext(CteScanState *node);
+
+/* ----------------------------------------------------------------
+ *             CteScanNext
+ *
+ *             This is a workhorse for ExecCteScan
+ * ----------------------------------------------------------------
+ */
+static TupleTableSlot *
+CteScanNext(CteScanState *node)
+{
+       EState     *estate;
+       ScanDirection dir;
+       bool            forward;
+       Tuplestorestate *tuplestorestate;
+       bool            eof_tuplestore;
+       TupleTableSlot *slot;
+
+       /*
+        * get state info from node
+        */
+       estate = node->ss.ps.state;
+       dir = estate->es_direction;
+       forward = ScanDirectionIsForward(dir);
+       tuplestorestate = node->leader->cte_table;
+       tuplestore_select_read_pointer(tuplestorestate, node->readptr);
+       slot = node->ss.ss_ScanTupleSlot;
+
+       /*
+        * If we are not at the end of the tuplestore, or are going backwards, try
+        * to fetch a tuple from tuplestore.
+        */
+       eof_tuplestore = tuplestore_ateof(tuplestorestate);
+
+       if (!forward && eof_tuplestore)
+       {
+               if (!node->leader->eof_cte)
+               {
+                       /*
+                        * When reversing direction at tuplestore EOF, the first
+                        * gettupleslot call will fetch the last-added tuple; but we want
+                        * to return the one before that, if possible. So do an extra
+                        * fetch.
+                        */
+                       if (!tuplestore_advance(tuplestorestate, forward))
+                               return NULL;    /* the tuplestore must be empty */
+               }
+               eof_tuplestore = false;
+       }
+
+       /*
+        * If we can fetch another tuple from the tuplestore, return it.
+        */
+       if (!eof_tuplestore)
+       {
+               if (tuplestore_gettupleslot(tuplestorestate, forward, slot))
+                       return slot;
+               if (forward)
+                       eof_tuplestore = true;
+       }
+
+       /*
+        * If necessary, try to fetch another row from the CTE query.
+        *
+        * Note: the eof_cte state variable exists to short-circuit further calls
+        * of the CTE plan.  It's not optional, unfortunately, because some plan
+        * node types are not robust about being called again when they've already
+        * returned NULL.
+        */
+       if (eof_tuplestore && !node->leader->eof_cte)
+       {
+               TupleTableSlot *cteslot;
+
+               /*
+                * We can only get here with forward==true, so no need to worry about
+                * which direction the subplan will go.
+                */
+               cteslot = ExecProcNode(node->cteplanstate);
+               if (TupIsNull(cteslot))
+               {
+                       node->leader->eof_cte = true;
+                       return NULL;
+               }
+
+               /*
+                * Append a copy of the returned tuple to tuplestore.  NOTE: because
+                * our read pointer is certainly in EOF state, its read position will
+                * move forward over the added tuple.  This is what we want.  Also,
+                * any other readers will *not* move past the new tuple, which is
+                * what they want.
+                */
+               tuplestore_puttupleslot(tuplestorestate, cteslot);
+
+               /*
+                * We MUST copy the CTE query's output tuple into our own slot.
+                * This is because other CteScan nodes might advance the CTE query
+                * before we are called again, and our output tuple must stay
+                * stable over that.
+                */
+               return ExecCopySlot(slot, cteslot);
+       }
+
+       /*
+        * Nothing left ...
+        */
+       return ExecClearTuple(slot);
+}
+
+/* ----------------------------------------------------------------
+ *             ExecCteScan(node)
+ *
+ *             Scans the CTE sequentially and returns the next qualifying tuple.
+ *             It calls the ExecScan() routine and passes it the access method
+ *             which retrieves tuples sequentially.
+ * ----------------------------------------------------------------
+ */
+TupleTableSlot *
+ExecCteScan(CteScanState *node)
+{
+       /*
+        * use CteScanNext as access method
+        */
+       return ExecScan(&node->ss, (ExecScanAccessMtd) CteScanNext);
+}
+
+
+/* ----------------------------------------------------------------
+ *             ExecInitCteScan
+ * ----------------------------------------------------------------
+ */
+CteScanState *
+ExecInitCteScan(CteScan *node, EState *estate, int eflags)
+{
+       CteScanState *scanstate;
+       ParamExecData *prmdata;
+
+       /* check for unsupported flags */
+       Assert(!(eflags & EXEC_FLAG_MARK));
+
+       /*
+        * For the moment we have to force the tuplestore to allow REWIND, because
+        * we might be asked to rescan the CTE even though upper levels didn't
+        * tell us to be prepared to do it efficiently.  Annoying, since this
+        * prevents truncation of the tuplestore.  XXX FIXME
+        */
+       eflags |= EXEC_FLAG_REWIND;
+
+       /*
+        * CteScan should not have any children.
+        */
+       Assert(outerPlan(node) == NULL);
+       Assert(innerPlan(node) == NULL);
+
+       /*
+        * create new CteScanState for node
+        */
+       scanstate = makeNode(CteScanState);
+       scanstate->ss.ps.plan = (Plan *) node;
+       scanstate->ss.ps.state = estate;
+       scanstate->eflags = eflags;
+       scanstate->cte_table = NULL;
+       scanstate->eof_cte = false;
+
+       /*
+        * Find the already-initialized plan for the CTE query.
+        */
+       scanstate->cteplanstate = (PlanState *) list_nth(estate->es_subplanstates,
+                                                                                                        node->ctePlanId - 1);
+
+       /*
+        * The Param slot associated with the CTE query is used to hold a
+        * pointer to the CteState of the first CteScan node that initializes
+        * for this CTE.  This node will be the one that holds the shared
+        * state for all the CTEs.
+        */
+       prmdata = &(estate->es_param_exec_vals[node->cteParam]);
+       Assert(prmdata->execPlan == NULL);
+       Assert(!prmdata->isnull);
+       scanstate->leader = (CteScanState *) DatumGetPointer(prmdata->value);
+       if (scanstate->leader == NULL)
+       {
+               /* I am the leader */
+               prmdata->value = PointerGetDatum(scanstate);
+               scanstate->leader = scanstate;
+               scanstate->cte_table = tuplestore_begin_heap(true, false, work_mem);
+               tuplestore_set_eflags(scanstate->cte_table, scanstate->eflags);
+               scanstate->readptr = 0;
+       }
+       else
+       {
+               /* Not the leader */
+               Assert(IsA(scanstate->leader, CteScanState));
+               scanstate->readptr =
+                       tuplestore_alloc_read_pointer(scanstate->leader->cte_table,
+                                                                                 scanstate->eflags);
+       }
+
+       /*
+        * 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 CTESCAN_NSLOTS 2
+
+       /*
+        * tuple table initialization
+        */
+       ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
+       ExecInitScanTupleSlot(estate, &scanstate->ss);
+
+       /*
+        * The scan tuple type (ie, the rowtype we expect to find in the work
+        * table) is the same as the result rowtype of the CTE query.
+        */
+       ExecAssignScanType(&scanstate->ss,
+                                          ExecGetResultType(scanstate->cteplanstate));
+
+       /*
+        * Initialize result tuple type and projection info.
+        */
+       ExecAssignResultTypeFromTL(&scanstate->ss.ps);
+       ExecAssignScanProjectionInfo(&scanstate->ss);
+
+       scanstate->ss.ps.ps_TupFromTlist = false;
+
+       return scanstate;
+}
+
+int
+ExecCountSlotsCteScan(CteScan *node)
+{
+       return ExecCountSlotsNode(outerPlan(node)) +
+               ExecCountSlotsNode(innerPlan(node)) +
+               CTESCAN_NSLOTS;
+}
+
+/* ----------------------------------------------------------------
+ *             ExecEndCteScan
+ *
+ *             frees any storage allocated through C routines.
+ * ----------------------------------------------------------------
+ */
+void
+ExecEndCteScan(CteScanState *node)
+{
+       /*
+        * Free exprcontext
+        */
+       ExecFreeExprContext(&node->ss.ps);
+
+       /*
+        * clean out the tuple table
+        */
+       ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+       ExecClearTuple(node->ss.ss_ScanTupleSlot);
+
+       /*
+        * If I am the leader, free the tuplestore.
+        */
+       if (node->leader == node)
+               tuplestore_end(node->cte_table);
+}
+
+/* ----------------------------------------------------------------
+ *             ExecCteScanReScan
+ *
+ *             Rescans the relation.
+ * ----------------------------------------------------------------
+ */
+void
+ExecCteScanReScan(CteScanState *node, ExprContext *exprCtxt)
+{
+       Tuplestorestate *tuplestorestate;
+
+       tuplestorestate = node->leader->cte_table;
+
+       ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+
+       if (node->leader == node)
+       {
+               /*
+                * The leader is responsible for clearing the tuplestore if a new
+                * scan of the underlying CTE is required.
+                */
+               if (node->cteplanstate->chgParam != NULL)
+               {
+                       tuplestore_clear(tuplestorestate);
+                       node->eof_cte = false;
+               }
+               else
+               {
+                       tuplestore_select_read_pointer(tuplestorestate, node->readptr);
+                       tuplestore_rescan(tuplestorestate);
+               }
+       }
+       else
+       {
+               /* Not leader, so just rewind my own pointer */
+               tuplestore_select_read_pointer(tuplestorestate, node->readptr);
+               tuplestore_rescan(tuplestorestate);
+       }
+}
diff --git a/src/backend/executor/nodeRecursiveunion.c b/src/backend/executor/nodeRecursiveunion.c
new file mode 100644 (file)
index 0000000..7136a62
--- /dev/null
@@ -0,0 +1,225 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeRecursiveunion.c
+ *       routines to handle RecursiveUnion nodes.
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *       $PostgreSQL: pgsql/src/backend/executor/nodeRecursiveunion.c,v 1.1 2008/10/04 21:56:53 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "executor/execdebug.h"
+#include "executor/nodeRecursiveunion.h"
+#include "miscadmin.h"
+
+
+/* ----------------------------------------------------------------
+ *             ExecRecursiveUnion(node)
+ *
+ *             Scans the recursive query sequentially and returns the next
+ *      qualifying tuple.
+ *
+ * 1. evaluate non recursive term 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
+ * ----------------------------------------------------------------
+ */
+TupleTableSlot *
+ExecRecursiveUnion(RecursiveUnionState *node)
+{
+       PlanState  *outerPlan = outerPlanState(node);
+       PlanState  *innerPlan = innerPlanState(node);
+       RecursiveUnion *plan = (RecursiveUnion *) node->ps.plan;
+       TupleTableSlot *slot;
+
+       /* 1. Evaluate non-recursive term */
+       if (!node->recursing)
+       {
+               slot = ExecProcNode(outerPlan);
+               if (!TupIsNull(slot))
+               {
+                       tuplestore_puttupleslot(node->working_table, slot);
+                       return slot;
+               }
+               node->recursing = true;
+       }
+
+retry:
+       /* 2. Execute recursive term */
+       slot = ExecProcNode(innerPlan);
+       if (TupIsNull(slot))
+       {
+               if (node->intermediate_empty)
+                       return NULL;
+
+               /* done with old working table ... */
+               tuplestore_end(node->working_table);
+
+               /* intermediate table becomes working table */
+               node->working_table = node->intermediate_table;
+
+               /* create new empty intermediate table */
+               node->intermediate_table = tuplestore_begin_heap(false, false, work_mem);
+               node->intermediate_empty = true;
+
+               /* and reset the inner plan */
+               innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
+                                                                                        plan->wtParam);
+               goto retry;
+       }
+       else
+       {
+               node->intermediate_empty = false;
+               tuplestore_puttupleslot(node->intermediate_table, slot);
+       }
+
+       return slot;
+}
+
+/* ----------------------------------------------------------------
+ *             ExecInitRecursiveUnionScan
+ * ----------------------------------------------------------------
+ */
+RecursiveUnionState *
+ExecInitRecursiveUnion(RecursiveUnion *node, EState *estate, int eflags)
+{
+       RecursiveUnionState *rustate;
+       ParamExecData *prmdata;
+
+       /* check for unsupported flags */
+       Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
+
+       /*
+        * create state structure
+        */
+       rustate = makeNode(RecursiveUnionState);
+       rustate->ps.plan = (Plan *) node;
+       rustate->ps.state = estate;
+
+       /* initialize processing state */
+       rustate->recursing = false;
+       rustate->intermediate_empty = true;
+       rustate->working_table = tuplestore_begin_heap(false, false, work_mem);
+       rustate->intermediate_table = tuplestore_begin_heap(false, false, work_mem);
+
+       /*
+        * Make the state structure available to descendant WorkTableScan nodes
+        * via the Param slot reserved for it.
+        */
+       prmdata = &(estate->es_param_exec_vals[node->wtParam]);
+       Assert(prmdata->execPlan == NULL);
+       prmdata->value = PointerGetDatum(rustate);
+       prmdata->isnull = false;
+
+       /*
+        * Miscellaneous initialization
+        *
+        * RecursiveUnion plans don't have expression contexts because they never
+        * call ExecQual or ExecProject.
+        */
+       Assert(node->plan.qual == NIL);
+
+#define RECURSIVEUNION_NSLOTS 1
+
+       /*
+        * RecursiveUnion nodes still have Result slots, which hold pointers to
+        * tuples, so we have to initialize them.
+        */
+       ExecInitResultTupleSlot(estate, &rustate->ps);
+
+       /*
+        * Initialize result tuple type and projection info.  (Note: we have
+        * to set up the result type before initializing child nodes, because
+        * nodeWorktablescan.c expects it to be valid.)
+        */
+       ExecAssignResultTypeFromTL(&rustate->ps);
+       rustate->ps.ps_ProjInfo = NULL;
+
+       /*
+        * initialize child nodes
+        */
+       outerPlanState(rustate) = ExecInitNode(outerPlan(node), estate, eflags);
+       innerPlanState(rustate) = ExecInitNode(innerPlan(node), estate, eflags);
+
+       return rustate;
+}
+
+int
+ExecCountSlotsRecursiveUnion(RecursiveUnion *node)
+{
+       return ExecCountSlotsNode(outerPlan(node)) +
+               ExecCountSlotsNode(innerPlan(node)) +
+               RECURSIVEUNION_NSLOTS;
+}
+
+/* ----------------------------------------------------------------
+ *             ExecEndRecursiveUnionScan
+ *
+ *             frees any storage allocated through C routines.
+ * ----------------------------------------------------------------
+ */
+void
+ExecEndRecursiveUnion(RecursiveUnionState *node)
+{
+       /* Release tuplestores */
+       tuplestore_end(node->working_table);
+       tuplestore_end(node->intermediate_table);
+
+       /*
+        * clean out the upper tuple table
+        */
+       ExecClearTuple(node->ps.ps_ResultTupleSlot);
+
+       /*
+        * close down subplans
+        */
+       ExecEndNode(outerPlanState(node));
+       ExecEndNode(innerPlanState(node));
+}
+
+/* ----------------------------------------------------------------
+ *             ExecRecursiveUnionReScan
+ *
+ *             Rescans the relation.
+ * ----------------------------------------------------------------
+ */
+void
+ExecRecursiveUnionReScan(RecursiveUnionState *node, ExprContext *exprCtxt)
+{
+       PlanState  *outerPlan = outerPlanState(node);
+       PlanState  *innerPlan = innerPlanState(node);
+       RecursiveUnion *plan = (RecursiveUnion *) node->ps.plan;
+
+       /*
+        * Set recursive term's chgParam to tell it that we'll modify the
+        * working table and therefore it has to rescan.
+        */
+       innerPlan->chgParam = bms_add_member(innerPlan->chgParam, plan->wtParam);
+
+       /*
+        * if chgParam of subnode is not null then plan will be re-scanned by
+        * first ExecProcNode.  Because of above, we only have to do this to
+        * the non-recursive term.
+        */
+       if (outerPlan->chgParam == NULL)
+               ExecReScan(outerPlan, exprCtxt);
+
+       /* reset processing state */
+       node->recursing = false;
+       node->intermediate_empty = true;
+       tuplestore_clear(node->working_table);
+       tuplestore_clear(node->intermediate_table);
+}
index 3ebb045f922190a362f1830a935105ef3a06b840..338d94e7608949dc7d5954435f0e52121965e085 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/nodeSubplan.c,v 1.94 2008/08/22 00:16:03 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/nodeSubplan.c,v 1.95 2008/10/04 21:56:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -66,9 +66,13 @@ ExecSubPlan(SubPlanState *node,
        if (isDone)
                *isDone = ExprSingleResult;
 
+       /* Sanity checks */
+       if (subplan->subLinkType == CTE_SUBLINK)
+               elog(ERROR, "CTE subplans should not be executed via ExecSubPlan");
        if (subplan->setParam != NIL)
                elog(ERROR, "cannot set parent params from subquery");
 
+       /* Select appropriate evaluation strategy */
        if (subplan->useHashTable)
                return ExecHashSubPlan(node, econtext, isNull);
        else
@@ -688,11 +692,14 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
         * If this plan is un-correlated or undirect correlated one and want to
         * set params for parent plan then mark parameters as needing evaluation.
         *
+        * A CTE subplan's output parameter is never to be evaluated in the normal
+        * way, so skip this in that case.
+        *
         * Note that in the case of un-correlated subqueries we don't care about
         * setting parent->chgParam here: indices take care about it, for others -
         * it doesn't matter...
         */
-       if (subplan->setParam != NIL)
+       if (subplan->setParam != NIL && subplan->subLinkType != CTE_SUBLINK)
        {
                ListCell   *lst;
 
@@ -907,22 +914,21 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
        bool            found = false;
        ArrayBuildState *astate = NULL;
 
-       /*
-        * Must switch to per-query memory context.
-        */
-       oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-
        if (subLinkType == ANY_SUBLINK ||
                subLinkType == ALL_SUBLINK)
                elog(ERROR, "ANY/ALL subselect unsupported as initplan");
+       if (subLinkType == CTE_SUBLINK)
+               elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan");
 
        /*
-        * By definition, an initplan has no parameters from our query level, but
-        * it could have some from an outer level.      Rescan it if needed.
+        * Must switch to per-query memory context.
         */
-       if (planstate->chgParam != NULL)
-               ExecReScan(planstate, NULL);
+       oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
 
+       /*
+        * Run the plan.  (If it needs to be rescanned, the first ExecProcNode
+        * call will take care of that.)
+        */
        for (slot = ExecProcNode(planstate);
                 !TupIsNull(slot);
                 slot = ExecProcNode(planstate))
@@ -932,7 +938,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 
                if (subLinkType == EXISTS_SUBLINK)
                {
-                       /* There can be only one param... */
+                       /* There can be only one setParam... */
                        int                     paramid = linitial_int(subplan->setParam);
                        ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
 
@@ -994,7 +1000,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 
        if (subLinkType == ARRAY_SUBLINK)
        {
-               /* There can be only one param... */
+               /* There can be only one setParam... */
                int                     paramid = linitial_int(subplan->setParam);
                ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
 
@@ -1014,7 +1020,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
        {
                if (subLinkType == EXISTS_SUBLINK)
                {
-                       /* There can be only one param... */
+                       /* There can be only one setParam... */
                        int                     paramid = linitial_int(subplan->setParam);
                        ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
 
@@ -1059,18 +1065,25 @@ ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent)
                elog(ERROR, "extParam set of initplan is empty");
 
        /*
-        * Don't actually re-scan: ExecSetParamPlan does it if needed.
+        * Don't actually re-scan: it'll happen inside ExecSetParamPlan if needed.
         */
 
        /*
-        * Mark this subplan's output parameters as needing recalculation
+        * Mark this subplan's output parameters as needing recalculation.
+        *
+        * CTE subplans are never executed via parameter recalculation; instead
+        * they get run when called by nodeCtescan.c.  So don't mark the output
+        * parameter of a CTE subplan as dirty, but do set the chgParam bit
+        * for it so that dependent plan nodes will get told to rescan.
         */
        foreach(l, subplan->setParam)
        {
                int                     paramid = lfirst_int(l);
                ParamExecData *prm = &(estate->es_param_exec_vals[paramid]);
 
-               prm->execPlan = node;
+               if (subplan->subLinkType != CTE_SUBLINK)
+                       prm->execPlan = node;
+
                parent->chgParam = bms_add_member(parent->chgParam, paramid);
        }
 }
diff --git a/src/backend/executor/nodeWorktablescan.c b/src/backend/executor/nodeWorktablescan.c
new file mode 100644 (file)
index 0000000..2f09c5e
--- /dev/null
@@ -0,0 +1,194 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeWorktablescan.c
+ *       routines to handle WorkTableScan nodes.
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *       $PostgreSQL: pgsql/src/backend/executor/nodeWorktablescan.c,v 1.1 2008/10/04 21:56:53 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "executor/execdebug.h"
+#include "executor/nodeWorktablescan.h"
+
+static TupleTableSlot *WorkTableScanNext(WorkTableScanState *node);
+
+/* ----------------------------------------------------------------
+ *             WorkTableScanNext
+ *
+ *             This is a workhorse for ExecWorkTableScan
+ * ----------------------------------------------------------------
+ */
+static TupleTableSlot *
+WorkTableScanNext(WorkTableScanState *node)
+{
+       TupleTableSlot *slot;
+       EState     *estate;
+       ScanDirection direction;
+       Tuplestorestate *tuplestorestate;
+
+       /*
+        * get information from the estate and scan state
+        */
+       estate = node->ss.ps.state;
+       direction = estate->es_direction;
+
+       tuplestorestate = node->rustate->working_table;
+
+       /*
+        * Get the next tuple from tuplestore. Return NULL if no more tuples.
+        */
+       slot = node->ss.ss_ScanTupleSlot;
+       (void) tuplestore_gettupleslot(tuplestorestate,
+                                                                  ScanDirectionIsForward(direction),
+                                                                  slot);
+       return slot;
+}
+
+/* ----------------------------------------------------------------
+ *             ExecWorkTableScan(node)
+ *
+ *             Scans the worktable sequentially and returns the next qualifying tuple.
+ *             It calls the ExecScan() routine and passes it the access method
+ *             which retrieves tuples sequentially.
+ * ----------------------------------------------------------------
+ */
+TupleTableSlot *
+ExecWorkTableScan(WorkTableScanState *node)
+{
+       /*
+        * use WorkTableScanNext as access method
+        */
+       return ExecScan(&node->ss, (ExecScanAccessMtd) WorkTableScanNext);
+}
+
+
+/* ----------------------------------------------------------------
+ *             ExecInitWorkTableScan
+ * ----------------------------------------------------------------
+ */
+WorkTableScanState *
+ExecInitWorkTableScan(WorkTableScan *node, EState *estate, int eflags)
+{
+       WorkTableScanState *scanstate;
+       ParamExecData *prmdata;
+
+       /* check for unsupported flags */
+       Assert(!(eflags & EXEC_FLAG_MARK));
+
+       /*
+        * WorkTableScan should not have any children.
+        */
+       Assert(outerPlan(node) == NULL);
+       Assert(innerPlan(node) == NULL);
+
+       /*
+        * create new WorkTableScanState for node
+        */
+       scanstate = makeNode(WorkTableScanState);
+       scanstate->ss.ps.plan = (Plan *) node;
+       scanstate->ss.ps.state = estate;
+
+       /*
+        * Find the ancestor RecursiveUnion's state
+        * via the Param slot reserved for it.
+        */
+       prmdata = &(estate->es_param_exec_vals[node->wtParam]);
+       Assert(prmdata->execPlan == NULL);
+       Assert(!prmdata->isnull);
+       scanstate->rustate = (RecursiveUnionState *) DatumGetPointer(prmdata->value);
+       Assert(scanstate->rustate && IsA(scanstate->rustate, RecursiveUnionState));
+
+       /*
+        * 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 WORKTABLESCAN_NSLOTS 2
+
+       /*
+        * tuple table initialization
+        */
+       ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
+       ExecInitScanTupleSlot(estate, &scanstate->ss);
+
+       /*
+        * The scan tuple type (ie, the rowtype we expect to find in the work
+        * table) is the same as the result rowtype of the ancestor RecursiveUnion
+        * node.  Note this depends on the assumption that RecursiveUnion doesn't
+        * allow projection.
+        */
+       ExecAssignScanType(&scanstate->ss,
+                                          ExecGetResultType(&scanstate->rustate->ps));
+
+       /*
+        * Initialize result tuple type and projection info.
+        */
+       ExecAssignResultTypeFromTL(&scanstate->ss.ps);
+       ExecAssignScanProjectionInfo(&scanstate->ss);
+
+       scanstate->ss.ps.ps_TupFromTlist = false;
+
+       return scanstate;
+}
+
+int
+ExecCountSlotsWorkTableScan(WorkTableScan *node)
+{
+       return ExecCountSlotsNode(outerPlan(node)) +
+               ExecCountSlotsNode(innerPlan(node)) +
+               WORKTABLESCAN_NSLOTS;
+}
+
+/* ----------------------------------------------------------------
+ *             ExecEndWorkTableScan
+ *
+ *             frees any storage allocated through C routines.
+ * ----------------------------------------------------------------
+ */
+void
+ExecEndWorkTableScan(WorkTableScanState *node)
+{
+       /*
+        * Free exprcontext
+        */
+       ExecFreeExprContext(&node->ss.ps);
+
+       /*
+        * clean out the tuple table
+        */
+       ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+       ExecClearTuple(node->ss.ss_ScanTupleSlot);
+}
+
+/* ----------------------------------------------------------------
+ *             ExecWorkTableScanReScan
+ *
+ *             Rescans the relation.
+ * ----------------------------------------------------------------
+ */
+void
+ExecWorkTableScanReScan(WorkTableScanState *node, ExprContext *exprCtxt)
+{
+       ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+       tuplestore_rescan(node->rustate->working_table);
+}
index 9cfa1700f436e8d918ca1c4926b2a82f6f838d3c..57ef558c404c4304256fcd18a43720909cf2ba26 100644 (file)
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.405 2008/09/09 18:58:08 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.406 2008/10/04 21:56:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -176,6 +176,27 @@ _copyAppend(Append *from)
        return newnode;
 }
 
+/*
+ * _copyRecursiveUnion
+ */
+static RecursiveUnion *
+_copyRecursiveUnion(RecursiveUnion *from)
+{
+       RecursiveUnion     *newnode = makeNode(RecursiveUnion);
+
+       /*
+        * copy node superclass fields
+        */
+       CopyPlanFields((Plan *) from, (Plan *) newnode);
+
+       /*
+        * copy remainder of node
+        */
+       COPY_SCALAR_FIELD(wtParam);
+
+       return newnode;
+}
+
 /*
  * _copyBitmapAnd
  */
@@ -421,6 +442,49 @@ _copyValuesScan(ValuesScan *from)
        return newnode;
 }
 
+/*
+ * _copyCteScan
+ */
+static CteScan *
+_copyCteScan(CteScan *from)
+{
+       CteScan *newnode = makeNode(CteScan);
+
+       /*
+        * copy node superclass fields
+        */
+       CopyScanFields((Scan *) from, (Scan *) newnode);
+
+       /*
+        * copy remainder of node
+        */
+       COPY_SCALAR_FIELD(ctePlanId);
+       COPY_SCALAR_FIELD(cteParam);
+
+       return newnode;
+}
+
+/*
+ * _copyWorkTableScan
+ */
+static WorkTableScan *
+_copyWorkTableScan(WorkTableScan *from)
+{
+       WorkTableScan *newnode = makeNode(WorkTableScan);
+
+       /*
+        * copy node superclass fields
+        */
+       CopyScanFields((Scan *) from, (Scan *) newnode);
+
+       /*
+        * copy remainder of node
+        */
+       COPY_SCALAR_FIELD(wtParam);
+
+       return newnode;
+}
+
 /*
  * CopyJoinFields
  *
@@ -1572,12 +1636,17 @@ _copyRangeTblEntry(RangeTblEntry *from)
        COPY_SCALAR_FIELD(rtekind);
        COPY_SCALAR_FIELD(relid);
        COPY_NODE_FIELD(subquery);
+       COPY_SCALAR_FIELD(jointype);
+       COPY_NODE_FIELD(joinaliasvars);
        COPY_NODE_FIELD(funcexpr);
        COPY_NODE_FIELD(funccoltypes);
        COPY_NODE_FIELD(funccoltypmods);
        COPY_NODE_FIELD(values_lists);
-       COPY_SCALAR_FIELD(jointype);
-       COPY_NODE_FIELD(joinaliasvars);
+       COPY_STRING_FIELD(ctename);
+       COPY_SCALAR_FIELD(ctelevelsup);
+       COPY_SCALAR_FIELD(self_reference);
+       COPY_NODE_FIELD(ctecoltypes);
+       COPY_NODE_FIELD(ctecoltypmods);
        COPY_NODE_FIELD(alias);
        COPY_NODE_FIELD(eref);
        COPY_SCALAR_FIELD(inh);
@@ -1632,6 +1701,36 @@ _copyRowMarkClause(RowMarkClause *from)
        return newnode;
 }
 
+static WithClause *
+_copyWithClause(WithClause *from)
+{
+       WithClause *newnode = makeNode(WithClause);
+
+       COPY_NODE_FIELD(ctes);
+       COPY_SCALAR_FIELD(recursive);
+       COPY_LOCATION_FIELD(location);
+
+       return newnode;
+}
+
+static CommonTableExpr *
+_copyCommonTableExpr(CommonTableExpr *from)
+{
+       CommonTableExpr *newnode = makeNode(CommonTableExpr);
+
+       COPY_STRING_FIELD(ctename);
+       COPY_NODE_FIELD(aliascolnames);
+       COPY_NODE_FIELD(ctequery);
+       COPY_LOCATION_FIELD(location);
+       COPY_SCALAR_FIELD(cterecursive);
+       COPY_SCALAR_FIELD(cterefcount);
+       COPY_NODE_FIELD(ctecolnames);
+       COPY_NODE_FIELD(ctecoltypes);
+       COPY_NODE_FIELD(ctecoltypmods);
+
+       return newnode;
+}
+
 static A_Expr *
 _copyAExpr(A_Expr *from)
 {
@@ -1931,6 +2030,8 @@ _copyQuery(Query *from)
        COPY_SCALAR_FIELD(hasAggs);
        COPY_SCALAR_FIELD(hasSubLinks);
        COPY_SCALAR_FIELD(hasDistinctOn);
+       COPY_SCALAR_FIELD(hasRecursive);
+       COPY_NODE_FIELD(cteList);
        COPY_NODE_FIELD(rtable);
        COPY_NODE_FIELD(jointree);
        COPY_NODE_FIELD(targetList);
@@ -1999,6 +2100,7 @@ _copySelectStmt(SelectStmt *from)
        COPY_NODE_FIELD(whereClause);
        COPY_NODE_FIELD(groupClause);
        COPY_NODE_FIELD(havingClause);
+       COPY_NODE_FIELD(withClause);
        COPY_NODE_FIELD(valuesLists);
        COPY_NODE_FIELD(sortClause);
        COPY_NODE_FIELD(limitOffset);
@@ -3104,6 +3206,9 @@ copyObject(void *from)
                case T_Append:
                        retval = _copyAppend(from);
                        break;
+               case T_RecursiveUnion:
+                       retval = _copyRecursiveUnion(from);
+                       break;
                case T_BitmapAnd:
                        retval = _copyBitmapAnd(from);
                        break;
@@ -3137,6 +3242,12 @@ copyObject(void *from)
                case T_ValuesScan:
                        retval = _copyValuesScan(from);
                        break;
+               case T_CteScan:
+                       retval = _copyCteScan(from);
+                       break;
+               case T_WorkTableScan:
+                       retval = _copyWorkTableScan(from);
+                       break;
                case T_Join:
                        retval = _copyJoin(from);
                        break;
@@ -3672,6 +3783,12 @@ copyObject(void *from)
                case T_RowMarkClause:
                        retval = _copyRowMarkClause(from);
                        break;
+               case T_WithClause:
+                       retval = _copyWithClause(from);
+                       break;
+               case T_CommonTableExpr:
+                       retval = _copyCommonTableExpr(from);
+                       break;
                case T_FkConstraint:
                        retval = _copyFkConstraint(from);
                        break;
index f0939b4777df9d3000666b1788bdf5a905eb536a..f01ebe33e87775342bf39970e83e11dc03dd7446 100644 (file)
@@ -22,7 +22,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.331 2008/09/01 20:42:44 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.332 2008/10/04 21:56:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -808,6 +808,8 @@ _equalQuery(Query *a, Query *b)
        COMPARE_SCALAR_FIELD(hasAggs);
        COMPARE_SCALAR_FIELD(hasSubLinks);
        COMPARE_SCALAR_FIELD(hasDistinctOn);
+       COMPARE_SCALAR_FIELD(hasRecursive);
+       COMPARE_NODE_FIELD(cteList);
        COMPARE_NODE_FIELD(rtable);
        COMPARE_NODE_FIELD(jointree);
        COMPARE_NODE_FIELD(targetList);
@@ -868,6 +870,7 @@ _equalSelectStmt(SelectStmt *a, SelectStmt *b)
        COMPARE_NODE_FIELD(whereClause);
        COMPARE_NODE_FIELD(groupClause);
        COMPARE_NODE_FIELD(havingClause);
+       COMPARE_NODE_FIELD(withClause);
        COMPARE_NODE_FIELD(valuesLists);
        COMPARE_NODE_FIELD(sortClause);
        COMPARE_NODE_FIELD(limitOffset);
@@ -1932,12 +1935,17 @@ _equalRangeTblEntry(RangeTblEntry *a, RangeTblEntry *b)
        COMPARE_SCALAR_FIELD(rtekind);
        COMPARE_SCALAR_FIELD(relid);
        COMPARE_NODE_FIELD(subquery);
+       COMPARE_SCALAR_FIELD(jointype);
+       COMPARE_NODE_FIELD(joinaliasvars);
        COMPARE_NODE_FIELD(funcexpr);
        COMPARE_NODE_FIELD(funccoltypes);
        COMPARE_NODE_FIELD(funccoltypmods);
        COMPARE_NODE_FIELD(values_lists);
-       COMPARE_SCALAR_FIELD(jointype);
-       COMPARE_NODE_FIELD(joinaliasvars);
+       COMPARE_STRING_FIELD(ctename);
+       COMPARE_SCALAR_FIELD(ctelevelsup);
+       COMPARE_SCALAR_FIELD(self_reference);
+       COMPARE_NODE_FIELD(ctecoltypes);
+       COMPARE_NODE_FIELD(ctecoltypmods);
        COMPARE_NODE_FIELD(alias);
        COMPARE_NODE_FIELD(eref);
        COMPARE_SCALAR_FIELD(inh);
@@ -1969,6 +1977,32 @@ _equalRowMarkClause(RowMarkClause *a, RowMarkClause *b)
        return true;
 }
 
+static bool
+_equalWithClause(WithClause *a, WithClause *b)
+{
+       COMPARE_NODE_FIELD(ctes);
+       COMPARE_SCALAR_FIELD(recursive);
+       COMPARE_LOCATION_FIELD(location);
+
+       return true;
+}
+
+static bool
+_equalCommonTableExpr(CommonTableExpr *a, CommonTableExpr *b)
+{
+       COMPARE_STRING_FIELD(ctename);
+       COMPARE_NODE_FIELD(aliascolnames);
+       COMPARE_NODE_FIELD(ctequery);
+       COMPARE_LOCATION_FIELD(location);
+       COMPARE_SCALAR_FIELD(cterecursive);
+       COMPARE_SCALAR_FIELD(cterefcount);
+       COMPARE_NODE_FIELD(ctecolnames);
+       COMPARE_NODE_FIELD(ctecoltypes);
+       COMPARE_NODE_FIELD(ctecoltypmods);
+
+       return true;
+}
+
 static bool
 _equalFkConstraint(FkConstraint *a, FkConstraint *b)
 {
@@ -2593,6 +2627,12 @@ equal(void *a, void *b)
                case T_RowMarkClause:
                        retval = _equalRowMarkClause(a, b);
                        break;
+               case T_WithClause:
+                       retval = _equalWithClause(a, b);
+                       break;
+               case T_CommonTableExpr:
+                       retval = _equalCommonTableExpr(a, b);
+                       break;
                case T_FkConstraint:
                        retval = _equalFkConstraint(a, b);
                        break;
index a06164156665b827059db2f89d54910549fc8f0b..0127c6e14b2be1319fa8bbbb262a2221b42ac2b7 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/nodeFuncs.c,v 1.32 2008/09/01 20:42:44 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/nodeFuncs.c,v 1.33 2008/10/04 21:56:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -870,6 +870,12 @@ exprLocation(Node *expr)
                        /* XMLSERIALIZE keyword should always be the first thing */
                        loc = ((XmlSerialize *) expr)->location;
                        break;
+               case T_WithClause:
+                       loc = ((WithClause *) expr)->location;
+                       break;
+               case T_CommonTableExpr:
+                       loc = ((CommonTableExpr *) expr)->location;
+                       break;
                default:
                        /* for any other node type it's just unknown... */
                        loc = -1;
@@ -1205,6 +1211,17 @@ expression_tree_walker(Node *node,
                case T_Query:
                        /* Do nothing with a sub-Query, per discussion above */
                        break;
+               case T_CommonTableExpr:
+                       {
+                               CommonTableExpr *cte = (CommonTableExpr *) node;
+
+                               /*
+                                * Invoke the walker on the CTE's Query node, so it
+                                * can recurse into the sub-query if it wants to.
+                                */
+                               return walker(cte->ctequery, context);
+                       }
+                       break;
                case T_List:
                        foreach(temp, (List *) node)
                        {
@@ -1313,6 +1330,11 @@ query_tree_walker(Query *query,
                return true;
        if (walker(query->limitCount, context))
                return true;
+       if (!(flags & QTW_IGNORE_CTE_SUBQUERIES))
+       {
+               if (walker((Node *) query->cteList, context))
+                       return true;
+       }
        if (range_table_walker(query->rtable, walker, context, flags))
                return true;
        return false;
@@ -1335,10 +1357,16 @@ range_table_walker(List *rtable,
        {
                RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
 
+               /* For historical reasons, visiting RTEs is not the default */
+               if (flags & QTW_EXAMINE_RTES)
+                       if (walker(rte, context))
+                               return true;
+
                switch (rte->rtekind)
                {
                        case RTE_RELATION:
                        case RTE_SPECIAL:
+                       case RTE_CTE:
                                /* nothing to do */
                                break;
                        case RTE_SUBQUERY:
@@ -1806,6 +1834,21 @@ expression_tree_mutator(Node *node,
                case T_Query:
                        /* Do nothing with a sub-Query, per discussion above */
                        return node;
+               case T_CommonTableExpr:
+                       {
+                               CommonTableExpr *cte = (CommonTableExpr *) node;
+                               CommonTableExpr *newnode;
+
+                               FLATCOPY(newnode, cte, CommonTableExpr);
+
+                               /*
+                                * Also invoke the mutator on the CTE's Query node, so it
+                                * can recurse into the sub-query if it wants to.
+                                */
+                               MUTATE(newnode->ctequery, cte->ctequery, Node *);
+                               return (Node *) newnode;
+                       }
+                       break;
                case T_List:
                        {
                                /*
@@ -1935,6 +1978,10 @@ query_tree_mutator(Query *query,
        MUTATE(query->havingQual, query->havingQual, Node *);
        MUTATE(query->limitOffset, query->limitOffset, Node *);
        MUTATE(query->limitCount, query->limitCount, Node *);
+       if (!(flags & QTW_IGNORE_CTE_SUBQUERIES))
+               MUTATE(query->cteList, query->cteList, List *);
+       else                                            /* else copy CTE list as-is */
+               query->cteList = copyObject(query->cteList);
        query->rtable = range_table_mutator(query->rtable,
                                                                                mutator, context, flags);
        return query;
@@ -1964,6 +2011,7 @@ range_table_mutator(List *rtable,
                {
                        case RTE_RELATION:
                        case RTE_SPECIAL:
+                       case RTE_CTE:
                                /* we don't bother to copy eref, aliases, etc; OK? */
                                break;
                        case RTE_SUBQUERY:
@@ -2044,3 +2092,304 @@ query_or_expression_tree_mutator(Node *node,
        else
                return mutator(node, context);
 }
+
+
+/*
+ * raw_expression_tree_walker --- walk raw parse trees
+ *
+ * This has exactly the same API as expression_tree_walker, but instead of
+ * walking post-analysis parse trees, it knows how to walk the node types
+ * found in raw grammar output.  (There is not currently any need for a
+ * combined walker, so we keep them separate in the name of efficiency.)
+ * Unlike expression_tree_walker, there is no special rule about query
+ * boundaries: we descend to everything that's possibly interesting.
+ *
+ * Currently, the node type coverage extends to SelectStmt and everything
+ * that could appear under it, but not other statement types.
+ */
+bool
+raw_expression_tree_walker(Node *node, bool (*walker) (), void *context)
+{
+       ListCell   *temp;
+
+       /*
+        * The walker has already visited the current node, and so we need only
+        * recurse into any sub-nodes it has.
+        */
+       if (node == NULL)
+               return false;
+
+       /* Guard against stack overflow due to overly complex expressions */
+       check_stack_depth();
+
+       switch (nodeTag(node))
+       {
+               case T_SetToDefault:
+               case T_CurrentOfExpr:
+               case T_Integer:
+               case T_Float:
+               case T_String:
+               case T_BitString:
+               case T_Null:
+               case T_ParamRef:
+               case T_A_Const:
+               case T_A_Star:
+                       /* primitive node types with no subnodes */
+                       break;
+               case T_Alias:
+                       /* we assume the colnames list isn't interesting */
+                       break;
+               case T_RangeVar:
+                       return walker(((RangeVar *) node)->alias, context);
+               case T_SubLink:
+                       {
+                               SubLink    *sublink = (SubLink *) node;
+
+                               if (walker(sublink->testexpr, context))
+                                       return true;
+                               /* we assume the operName is not interesting */
+                               if (walker(sublink->subselect, context))
+                                       return true;
+                       }
+                       break;
+               case T_CaseExpr:
+                       {
+                               CaseExpr   *caseexpr = (CaseExpr *) node;
+
+                               if (walker(caseexpr->arg, context))
+                                       return true;
+                               /* we assume walker doesn't care about CaseWhens, either */
+                               foreach(temp, caseexpr->args)
+                               {
+                                       CaseWhen   *when = (CaseWhen *) lfirst(temp);
+
+                                       Assert(IsA(when, CaseWhen));
+                                       if (walker(when->expr, context))
+                                               return true;
+                                       if (walker(when->result, context))
+                                               return true;
+                               }
+                               if (walker(caseexpr->defresult, context))
+                                       return true;
+                       }
+                       break;
+               case T_RowExpr:
+                       return walker(((RowExpr *) node)->args, context);
+               case T_CoalesceExpr:
+                       return walker(((CoalesceExpr *) node)->args, context);
+               case T_MinMaxExpr:
+                       return walker(((MinMaxExpr *) node)->args, context);
+               case T_XmlExpr:
+                       {
+                               XmlExpr    *xexpr = (XmlExpr *) node;
+
+                               if (walker(xexpr->named_args, context))
+                                       return true;
+                               /* we assume walker doesn't care about arg_names */
+                               if (walker(xexpr->args, context))
+                                       return true;
+                       }
+                       break;
+               case T_NullTest:
+                       return walker(((NullTest *) node)->arg, context);
+               case T_BooleanTest:
+                       return walker(((BooleanTest *) node)->arg, context);
+               case T_JoinExpr:
+                       {
+                               JoinExpr   *join = (JoinExpr *) node;
+
+                               if (walker(join->larg, context))
+                                       return true;
+                               if (walker(join->rarg, context))
+                                       return true;
+                               if (walker(join->quals, context))
+                                       return true;
+                               if (walker(join->alias, context))
+                                       return true;
+                               /* using list is deemed uninteresting */
+                       }
+                       break;
+               case T_IntoClause:
+                       {
+                               IntoClause *into = (IntoClause *) node;
+
+                               if (walker(into->rel, context))
+                                       return true;
+                               /* colNames, options are deemed uninteresting */
+                       }
+                       break;
+               case T_List:
+                       foreach(temp, (List *) node)
+                       {
+                               if (walker((Node *) lfirst(temp), context))
+                                       return true;
+                       }
+                       break;
+               case T_SelectStmt:
+                       {
+                               SelectStmt *stmt = (SelectStmt *) node;
+
+                               if (walker(stmt->distinctClause, context))
+                                       return true;
+                               if (walker(stmt->intoClause, context))
+                                       return true;
+                               if (walker(stmt->targetList, context))
+                                       return true;
+                               if (walker(stmt->fromClause, context))
+                                       return true;
+                               if (walker(stmt->whereClause, context))
+                                       return true;
+                               if (walker(stmt->groupClause, context))
+                                       return true;
+                               if (walker(stmt->havingClause, context))
+                                       return true;
+                               if (walker(stmt->withClause, context))
+                                       return true;
+                               if (walker(stmt->valuesLists, context))
+                                       return true;
+                               if (walker(stmt->sortClause, context))
+                                       return true;
+                               if (walker(stmt->limitOffset, context))
+                                       return true;
+                               if (walker(stmt->limitCount, context))
+                                       return true;
+                               if (walker(stmt->lockingClause, context))
+                                       return true;
+                               if (walker(stmt->larg, context))
+                                       return true;
+                               if (walker(stmt->rarg, context))
+                                       return true;
+                       }
+                       break;
+               case T_A_Expr:
+                       {
+                               A_Expr *expr = (A_Expr *) node;
+
+                               if (walker(expr->lexpr, context))
+                                       return true;
+                               if (walker(expr->rexpr, context))
+                                       return true;
+                               /* operator name is deemed uninteresting */
+                       }
+                       break;
+               case T_ColumnRef:
+                       /* we assume the fields contain nothing interesting */
+                       break;
+               case T_FuncCall:
+                       {
+                               FuncCall *fcall = (FuncCall *) node;
+
+                               if (walker(fcall->args, context))
+                                       return true;
+                               /* function name is deemed uninteresting */
+                       }
+                       break;
+               case T_A_Indices:
+                       {
+                               A_Indices *indices = (A_Indices *) node;
+
+                               if (walker(indices->lidx, context))
+                                       return true;
+                               if (walker(indices->uidx, context))
+                                       return true;
+                       }
+                       break;
+               case T_A_Indirection:
+                       {
+                               A_Indirection *indir = (A_Indirection *) node;
+
+                               if (walker(indir->arg, context))
+                                       return true;
+                               if (walker(indir->indirection, context))
+                                       return true;
+                       }
+                       break;
+               case T_A_ArrayExpr:
+                       return walker(((A_ArrayExpr *) node)->elements, context);
+               case T_ResTarget:
+                       {
+                               ResTarget *rt = (ResTarget *) node;
+
+                               if (walker(rt->indirection, context))
+                                       return true;
+                               if (walker(rt->val, context))
+                                       return true;
+                       }
+                       break;
+               case T_TypeCast:
+                       {
+                               TypeCast *tc = (TypeCast *) node;
+
+                               if (walker(tc->arg, context))
+                                       return true;
+                               if (walker(tc->typename, context))
+                                       return true;
+                       }
+                       break;
+               case T_SortBy:
+                       return walker(((SortBy *) node)->node, context);
+               case T_RangeSubselect:
+                       {
+                               RangeSubselect *rs = (RangeSubselect *) node;
+
+                               if (walker(rs->subquery, context))
+                                       return true;
+                               if (walker(rs->alias, context))
+                                       return true;
+                       }
+                       break;
+               case T_RangeFunction:
+                       {
+                               RangeFunction *rf = (RangeFunction *) node;
+
+                               if (walker(rf->funccallnode, context))
+                                       return true;
+                               if (walker(rf->alias, context))
+                                       return true;
+                       }
+                       break;
+               case T_TypeName:
+                       {
+                               TypeName *tn = (TypeName *) node;
+
+                               if (walker(tn->typmods, context))
+                                       return true;
+                               if (walker(tn->arrayBounds, context))
+                                       return true;
+                               /* type name itself is deemed uninteresting */
+                       }
+                       break;
+               case T_ColumnDef:
+                       {
+                               ColumnDef *coldef = (ColumnDef *) node;
+
+                               if (walker(coldef->typename, context))
+                                       return true;
+                               if (walker(coldef->raw_default, context))
+                                       return true;
+                               /* for now, constraints are ignored */
+                       }
+                       break;
+               case T_LockingClause:
+                       return walker(((LockingClause *) node)->lockedRels, context);
+               case T_XmlSerialize:
+                       {
+                               XmlSerialize *xs = (XmlSerialize *) node;
+
+                               if (walker(xs->expr, context))
+                                       return true;
+                               if (walker(xs->typename, context))
+                                       return true;
+                       }
+                       break;
+               case T_WithClause:
+                       return walker(((WithClause *) node)->ctes, context);
+               case T_CommonTableExpr:
+                       return walker(((CommonTableExpr *) node)->ctequery, context);
+               default:
+                       elog(ERROR, "unrecognized node type: %d",
+                                (int) nodeTag(node));
+                       break;
+       }
+       return false;
+}
index 0b74b3a063d4bdd7666322844e35bc53930f13fd..6d39a51a9839351e79e955500b67d1983cc2a49c 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.339 2008/09/09 18:58:08 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.340 2008/10/04 21:56:53 tgl Exp $
  *
  * NOTES
  *       Every node type that can appear in stored rules' parsetrees *must*
@@ -331,6 +331,16 @@ _outAppend(StringInfo str, Append *node)
        WRITE_BOOL_FIELD(isTarget);
 }
 
+static void
+_outRecursiveUnion(StringInfo str, RecursiveUnion *node)
+{
+       WRITE_NODE_TYPE("RECURSIVEUNION");
+
+       _outPlanInfo(str, (Plan *) node);
+
+       WRITE_INT_FIELD(wtParam);
+}
+
 static void
 _outBitmapAnd(StringInfo str, BitmapAnd *node)
 {
@@ -446,6 +456,27 @@ _outValuesScan(StringInfo str, ValuesScan *node)
        WRITE_NODE_FIELD(values_lists);
 }
 
+static void
+_outCteScan(StringInfo str, CteScan *node)
+{
+       WRITE_NODE_TYPE("CTESCAN");
+
+       _outScanInfo(str, (Scan *) node);
+
+       WRITE_INT_FIELD(ctePlanId);
+       WRITE_INT_FIELD(cteParam);
+}
+
+static void
+_outWorkTableScan(StringInfo str, WorkTableScan *node)
+{
+       WRITE_NODE_TYPE("WORKTABLESCAN");
+
+       _outScanInfo(str, (Scan *) node);
+
+       WRITE_INT_FIELD(wtParam);
+}
+
 static void
 _outJoin(StringInfo str, Join *node)
 {
@@ -1382,6 +1413,7 @@ _outPlannerInfo(StringInfo str, PlannerInfo *node)
        WRITE_NODE_FIELD(resultRelations);
        WRITE_NODE_FIELD(returningLists);
        WRITE_NODE_FIELD(init_plans);
+       WRITE_NODE_FIELD(cte_plan_ids);
        WRITE_NODE_FIELD(eq_classes);
        WRITE_NODE_FIELD(canon_pathkeys);
        WRITE_NODE_FIELD(left_join_clauses);
@@ -1398,6 +1430,8 @@ _outPlannerInfo(StringInfo str, PlannerInfo *node)
        WRITE_BOOL_FIELD(hasJoinRTEs);
        WRITE_BOOL_FIELD(hasHavingQual);
        WRITE_BOOL_FIELD(hasPseudoConstantQuals);
+       WRITE_BOOL_FIELD(hasRecursion);
+       WRITE_INT_FIELD(wt_param_id);
 }
 
 static void
@@ -1648,6 +1682,7 @@ _outSelectStmt(StringInfo str, SelectStmt *node)
        WRITE_NODE_FIELD(whereClause);
        WRITE_NODE_FIELD(groupClause);
        WRITE_NODE_FIELD(havingClause);
+       WRITE_NODE_FIELD(withClause);
        WRITE_NODE_FIELD(valuesLists);
        WRITE_NODE_FIELD(sortClause);
        WRITE_NODE_FIELD(limitOffset);
@@ -1793,6 +1828,8 @@ _outQuery(StringInfo str, Query *node)
        WRITE_BOOL_FIELD(hasAggs);
        WRITE_BOOL_FIELD(hasSubLinks);
        WRITE_BOOL_FIELD(hasDistinctOn);
+       WRITE_BOOL_FIELD(hasRecursive);
+       WRITE_NODE_FIELD(cteList);
        WRITE_NODE_FIELD(rtable);
        WRITE_NODE_FIELD(jointree);
        WRITE_NODE_FIELD(targetList);
@@ -1828,6 +1865,32 @@ _outRowMarkClause(StringInfo str, RowMarkClause *node)
        WRITE_BOOL_FIELD(noWait);
 }
 
+static void
+_outWithClause(StringInfo str, WithClause *node)
+{
+       WRITE_NODE_TYPE("WITHCLAUSE");
+
+       WRITE_NODE_FIELD(ctes);
+       WRITE_BOOL_FIELD(recursive);
+       WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outCommonTableExpr(StringInfo str, CommonTableExpr *node)
+{
+       WRITE_NODE_TYPE("COMMONTABLEEXPR");
+
+       WRITE_STRING_FIELD(ctename);
+       WRITE_NODE_FIELD(aliascolnames);
+       WRITE_NODE_FIELD(ctequery);
+       WRITE_LOCATION_FIELD(location);
+       WRITE_BOOL_FIELD(cterecursive);
+       WRITE_INT_FIELD(cterefcount);
+       WRITE_NODE_FIELD(ctecolnames);
+       WRITE_NODE_FIELD(ctecoltypes);
+       WRITE_NODE_FIELD(ctecoltypmods);
+}
+
 static void
 _outSetOperationStmt(StringInfo str, SetOperationStmt *node)
 {
@@ -1861,6 +1924,10 @@ _outRangeTblEntry(StringInfo str, RangeTblEntry *node)
                case RTE_SUBQUERY:
                        WRITE_NODE_FIELD(subquery);
                        break;
+               case RTE_JOIN:
+                       WRITE_ENUM_FIELD(jointype, JoinType);
+                       WRITE_NODE_FIELD(joinaliasvars);
+                       break;
                case RTE_FUNCTION:
                        WRITE_NODE_FIELD(funcexpr);
                        WRITE_NODE_FIELD(funccoltypes);
@@ -1869,9 +1936,12 @@ _outRangeTblEntry(StringInfo str, RangeTblEntry *node)
                case RTE_VALUES:
                        WRITE_NODE_FIELD(values_lists);
                        break;
-               case RTE_JOIN:
-                       WRITE_ENUM_FIELD(jointype, JoinType);
-                       WRITE_NODE_FIELD(joinaliasvars);
+               case RTE_CTE:
+                       WRITE_STRING_FIELD(ctename);
+                       WRITE_UINT_FIELD(ctelevelsup);
+                       WRITE_BOOL_FIELD(self_reference);
+                       WRITE_NODE_FIELD(ctecoltypes);
+                       WRITE_NODE_FIELD(ctecoltypmods);
                        break;
                default:
                        elog(ERROR, "unrecognized RTE kind: %d", (int) node->rtekind);
@@ -2059,6 +2129,25 @@ _outSortBy(StringInfo str, SortBy *node)
        WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outRangeSubselect(StringInfo str, RangeSubselect *node)
+{
+       WRITE_NODE_TYPE("RANGESUBSELECT");
+
+       WRITE_NODE_FIELD(subquery);
+       WRITE_NODE_FIELD(alias);
+}
+
+static void
+_outRangeFunction(StringInfo str, RangeFunction *node)
+{
+       WRITE_NODE_TYPE("RANGEFUNCTION");
+
+       WRITE_NODE_FIELD(funccallnode);
+       WRITE_NODE_FIELD(alias);
+       WRITE_NODE_FIELD(coldeflist);
+}
+
 static void
 _outConstraint(StringInfo str, Constraint *node)
 {
@@ -2159,6 +2248,9 @@ _outNode(StringInfo str, void *obj)
                        case T_Append:
                                _outAppend(str, obj);
                                break;
+                       case T_RecursiveUnion:
+                               _outRecursiveUnion(str, obj);
+                               break;
                        case T_BitmapAnd:
                                _outBitmapAnd(str, obj);
                                break;
@@ -2192,6 +2284,12 @@ _outNode(StringInfo str, void *obj)
                        case T_ValuesScan:
                                _outValuesScan(str, obj);
                                break;
+                       case T_CteScan:
+                               _outCteScan(str, obj);
+                               break;
+                       case T_WorkTableScan:
+                               _outWorkTableScan(str, obj);
+                               break;
                        case T_Join:
                                _outJoin(str, obj);
                                break;
@@ -2473,6 +2571,12 @@ _outNode(StringInfo str, void *obj)
                        case T_RowMarkClause:
                                _outRowMarkClause(str, obj);
                                break;
+                       case T_WithClause:
+                               _outWithClause(str, obj);
+                               break;
+                       case T_CommonTableExpr:
+                               _outCommonTableExpr(str, obj);
+                               break;
                        case T_SetOperationStmt:
                                _outSetOperationStmt(str, obj);
                                break;
@@ -2509,6 +2613,12 @@ _outNode(StringInfo str, void *obj)
                        case T_SortBy:
                                _outSortBy(str, obj);
                                break;
+                       case T_RangeSubselect:
+                               _outRangeSubselect(str, obj);
+                               break;
+                       case T_RangeFunction:
+                               _outRangeFunction(str, obj);
+                               break;
                        case T_Constraint:
                                _outConstraint(str, obj);
                                break;
index 58a7495d37ef2ace3b600b689eeaffce761a4c75..3338e1b83027ddc472132f8e7c316aa5b2be52df 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/print.c,v 1.89 2008/06/19 00:46:04 alvherre Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/print.c,v 1.90 2008/10/04 21:56:53 tgl Exp $
  *
  * HISTORY
  *       AUTHOR                        DATE                    MAJOR EVENT
@@ -272,6 +272,14 @@ print_rt(List *rtable)
                                printf("%d\t%s\t[subquery]",
                                           i, rte->eref->aliasname);
                                break;
+                       case RTE_JOIN:
+                               printf("%d\t%s\t[join]",
+                                          i, rte->eref->aliasname);
+                               break;
+                       case RTE_SPECIAL:
+                               printf("%d\t%s\t[special]",
+                                          i, rte->eref->aliasname);
+                               break;
                        case RTE_FUNCTION:
                                printf("%d\t%s\t[rangefunction]",
                                           i, rte->eref->aliasname);
@@ -280,12 +288,8 @@ print_rt(List *rtable)
                                printf("%d\t%s\t[values list]",
                                           i, rte->eref->aliasname);
                                break;
-                       case RTE_JOIN:
-                               printf("%d\t%s\t[join]",
-                                          i, rte->eref->aliasname);
-                               break;
-                       case RTE_SPECIAL:
-                               printf("%d\t%s\t[special]",
+                       case RTE_CTE:
+                               printf("%d\t%s\t[cte]",
                                           i, rte->eref->aliasname);
                                break;
                        default:
index 1eca85d0b60b91f8d8df6c9e9dabd6a05692116f..3f7ce455df230439fbbf147f8209ee38dca53ab6 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.214 2008/09/01 20:42:44 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.215 2008/10/04 21:56:53 tgl Exp $
  *
  * NOTES
  *       Path and Plan nodes do not have any readfuncs support, because we
@@ -155,6 +155,8 @@ _readQuery(void)
        READ_BOOL_FIELD(hasAggs);
        READ_BOOL_FIELD(hasSubLinks);
        READ_BOOL_FIELD(hasDistinctOn);
+       READ_BOOL_FIELD(hasRecursive);
+       READ_NODE_FIELD(cteList);
        READ_NODE_FIELD(rtable);
        READ_NODE_FIELD(jointree);
        READ_NODE_FIELD(targetList);
@@ -230,6 +232,27 @@ _readRowMarkClause(void)
        READ_DONE();
 }
 
+/*
+ * _readCommonTableExpr
+ */
+static CommonTableExpr *
+_readCommonTableExpr(void)
+{
+       READ_LOCALS(CommonTableExpr);
+
+       READ_STRING_FIELD(ctename);
+       READ_NODE_FIELD(aliascolnames);
+       READ_NODE_FIELD(ctequery);
+       READ_LOCATION_FIELD(location);
+       READ_BOOL_FIELD(cterecursive);
+       READ_INT_FIELD(cterefcount);
+       READ_NODE_FIELD(ctecolnames);
+       READ_NODE_FIELD(ctecoltypes);
+       READ_NODE_FIELD(ctecoltypmods);
+
+       READ_DONE();
+}
+
 /*
  * _readSetOperationStmt
  */
@@ -1007,6 +1030,10 @@ _readRangeTblEntry(void)
                case RTE_SUBQUERY:
                        READ_NODE_FIELD(subquery);
                        break;
+               case RTE_JOIN:
+                       READ_ENUM_FIELD(jointype, JoinType);
+                       READ_NODE_FIELD(joinaliasvars);
+                       break;
                case RTE_FUNCTION:
                        READ_NODE_FIELD(funcexpr);
                        READ_NODE_FIELD(funccoltypes);
@@ -1015,9 +1042,12 @@ _readRangeTblEntry(void)
                case RTE_VALUES:
                        READ_NODE_FIELD(values_lists);
                        break;
-               case RTE_JOIN:
-                       READ_ENUM_FIELD(jointype, JoinType);
-                       READ_NODE_FIELD(joinaliasvars);
+               case RTE_CTE:
+                       READ_STRING_FIELD(ctename);
+                       READ_UINT_FIELD(ctelevelsup);
+                       READ_BOOL_FIELD(self_reference);
+                       READ_NODE_FIELD(ctecoltypes);
+                       READ_NODE_FIELD(ctecoltypmods);
                        break;
                default:
                        elog(ERROR, "unrecognized RTE kind: %d",
@@ -1060,6 +1090,8 @@ parseNodeString(void)
                return_value = _readSortGroupClause();
        else if (MATCH("ROWMARKCLAUSE", 13))
                return_value = _readRowMarkClause();
+       else if (MATCH("COMMONTABLEEXPR", 15))
+               return_value = _readCommonTableExpr();
        else if (MATCH("SETOPERATIONSTMT", 16))
                return_value = _readSetOperationStmt();
        else if (MATCH("ALIAS", 5))
index 3dc41c6807323e1836a3d333384f4ffe0fd051fa..a942249fd09ab3cc257fb2f6f93bbfcc0912e87d 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.173 2008/08/25 22:42:32 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.174 2008/10/04 21:56:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -57,6 +57,10 @@ static void set_function_pathlist(PlannerInfo *root, RelOptInfo *rel,
                                          RangeTblEntry *rte);
 static void set_values_pathlist(PlannerInfo *root, RelOptInfo *rel,
                                        RangeTblEntry *rte);
+static void set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel,
+                                                        RangeTblEntry *rte);
+static void set_worktable_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);
@@ -173,14 +177,22 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
        }
        else if (rel->rtekind == RTE_FUNCTION)
        {
-               /* RangeFunction --- generate a separate plan for it */
+               /* RangeFunction --- generate a suitable path for it */
                set_function_pathlist(root, rel, rte);
        }
        else if (rel->rtekind == RTE_VALUES)
        {
-               /* Values list --- generate a separate plan for it */
+               /* Values list --- generate a suitable path for it */
                set_values_pathlist(root, rel, rte);
        }
+       else if (rel->rtekind == RTE_CTE)
+       {
+               /* CTE reference --- generate a suitable path for it */
+               if (rte->self_reference)
+                       set_worktable_pathlist(root, rel, rte);
+               else
+                       set_cte_pathlist(root, rel, rte);
+       }
        else
        {
                /* Plain relation */
@@ -585,8 +597,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 
        /* Generate the plan for the subquery */
        rel->subplan = subquery_planner(root->glob, subquery,
-                                                                       root->query_level + 1,
-                                                                       tuple_fraction,
+                                                                       root,
+                                                                       false, tuple_fraction,
                                                                        &subroot);
        rel->subrtable = subroot->parse->rtable;
 
@@ -640,6 +652,104 @@ set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
        set_cheapest(rel);
 }
 
+/*
+ * set_cte_pathlist
+ *             Build the (single) access path for a non-self-reference CTE RTE
+ */
+static void
+set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
+{
+       Plan       *cteplan;
+       PlannerInfo *cteroot;
+       Index           levelsup;
+       int                     ndx;
+       ListCell   *lc;
+       int                     plan_id;
+
+       /*
+        * Find the referenced CTE, and locate the plan previously made for it.
+        */
+       levelsup = rte->ctelevelsup;
+       cteroot = root;
+       while (levelsup-- > 0)
+       {
+               cteroot = cteroot->parent_root;
+               if (!cteroot)                   /* shouldn't happen */
+                       elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename);
+       }
+       /*
+        * Note: cte_plan_ids can be shorter than cteList, if we are still working
+        * on planning the CTEs (ie, this is a side-reference from another CTE).
+        * So we mustn't use forboth here.
+        */
+       ndx = 0;
+       foreach(lc, cteroot->parse->cteList)
+       {
+               CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+
+               if (strcmp(cte->ctename, rte->ctename) == 0)
+                       break;
+               ndx++;
+       }
+       if (lc == NULL)                         /* shouldn't happen */
+               elog(ERROR, "could not find CTE \"%s\"", rte->ctename);
+       if (ndx >= list_length(cteroot->cte_plan_ids))
+               elog(ERROR, "could not find plan for CTE \"%s\"", rte->ctename);
+       plan_id = list_nth_int(cteroot->cte_plan_ids, ndx);
+       Assert(plan_id > 0);
+       cteplan = (Plan *) list_nth(root->glob->subplans, plan_id - 1);
+
+       /* Mark rel with estimated output rows, width, etc */
+       set_cte_size_estimates(root, rel, cteplan);
+
+       /* Generate appropriate path */
+       add_path(rel, create_ctescan_path(root, rel));
+
+       /* Select cheapest path (pretty easy in this case...) */
+       set_cheapest(rel);
+}
+
+/*
+ * set_worktable_pathlist
+ *             Build the (single) access path for a self-reference CTE RTE
+ */
+static void
+set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
+{
+       Plan       *cteplan;
+       PlannerInfo *cteroot;
+       Index           levelsup;
+
+       /*
+        * We need to find the non-recursive term's plan, which is in the plan
+        * level that's processing the recursive UNION, which is one level
+        * *below* where the CTE comes from.
+        */
+       levelsup = rte->ctelevelsup;
+       if (levelsup == 0)                      /* shouldn't happen */
+               elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename);
+       levelsup--;
+       cteroot = root;
+       while (levelsup-- > 0)
+       {
+               cteroot = cteroot->parent_root;
+               if (!cteroot)                   /* shouldn't happen */
+                       elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename);
+       }
+       cteplan = cteroot->non_recursive_plan;
+       if (!cteplan)                           /* shouldn't happen */
+               elog(ERROR, "could not find plan for CTE \"%s\"", rte->ctename);
+
+       /* Mark rel with estimated output rows, width, etc */
+       set_cte_size_estimates(root, rel, cteplan);
+
+       /* Generate appropriate path */
+       add_path(rel, create_worktablescan_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.
index 9ffdc32e775b63a816f00f17473969cf2b6f3c42..e3e4e9f02c015e86cddd97fee08aa9580dfda9de 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/path/clausesel.c,v 1.93 2008/08/22 00:16:03 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/path/clausesel.c,v 1.94 2008/10/04 21:56:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -550,29 +550,17 @@ clause_selectivity(PlannerInfo *root,
                if (var->varlevelsup == 0 &&
                        (varRelid == 0 || varRelid == (int) var->varno))
                {
-                       RangeTblEntry *rte = planner_rt_fetch(var->varno, root);
-
-                       if (rte->rtekind == RTE_SUBQUERY)
-                       {
-                               /*
-                                * XXX not smart about subquery references... any way to do
-                                * better than default?
-                                */
-                       }
-                       else
-                       {
-                               /*
-                                * A Var at the top of a clause must be a bool Var. This is
-                                * equivalent to the clause reln.attribute = 't', so we
-                                * compute the selectivity as if that is what we have.
-                                */
-                               s1 = restriction_selectivity(root,
-                                                                                        BooleanEqualOperator,
-                                                                                        list_make2(var,
-                                                                                                               makeBoolConst(true,
-                                                                                                                                         false)),
-                                                                                        varRelid);
-                       }
+                       /*
+                        * A Var at the top of a clause must be a bool Var. This is
+                        * equivalent to the clause reln.attribute = 't', so we
+                        * compute the selectivity as if that is what we have.
+                        */
+                       s1 = restriction_selectivity(root,
+                                                                                BooleanEqualOperator,
+                                                                                list_make2(var,
+                                                                                                       makeBoolConst(true,
+                                                                                                                                 false)),
+                                                                                varRelid);
                }
        }
        else if (IsA(clause, Const))
index bbc77db4e25b5b6a325a0b628213b657fffb4b5b..7bfc66cac3a82b6da7022618cfe4b7e19e9e7a5c 100644 (file)
@@ -54,7 +54,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.197 2008/09/05 21:07:29 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.198 2008/10/04 21:56:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -933,6 +933,84 @@ cost_valuesscan(Path *path, PlannerInfo *root, RelOptInfo *baserel)
        path->total_cost = startup_cost + run_cost;
 }
 
+/*
+ * cost_ctescan
+ *       Determines and returns the cost of scanning a CTE RTE.
+ *
+ * Note: this is used for both self-reference and regular CTEs; the
+ * possible cost differences are below the threshold of what we could
+ * estimate accurately anyway.  Note that the costs of evaluating the
+ * referenced CTE query are added into the final plan as initplan costs,
+ * and should NOT be counted here.
+ */
+void
+cost_ctescan(Path *path, PlannerInfo *root, RelOptInfo *baserel)
+{
+       Cost            startup_cost = 0;
+       Cost            run_cost = 0;
+       Cost            cpu_per_tuple;
+
+       /* Should only be applied to base relations that are CTEs */
+       Assert(baserel->relid > 0);
+       Assert(baserel->rtekind == RTE_CTE);
+
+       /* Charge one CPU tuple cost per row for tuplestore manipulation */
+       cpu_per_tuple = cpu_tuple_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_recursive_union
+ *       Determines and returns the cost of performing a recursive union,
+ *       and also the estimated output size.
+ *
+ * We are given Plans for the nonrecursive and recursive terms.
+ *
+ * Note that the arguments and output are Plans, not Paths as in most of
+ * the rest of this module.  That's because we don't bother setting up a
+ * Path representation for recursive union --- we have only one way to do it.
+ */
+void
+cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm)
+{
+       Cost            startup_cost;
+       Cost            total_cost;
+       double          total_rows;
+
+       /* We probably have decent estimates for the non-recursive term */
+       startup_cost = nrterm->startup_cost;
+       total_cost = nrterm->total_cost;
+       total_rows = nrterm->plan_rows;
+
+       /*
+        * We arbitrarily assume that about 10 recursive iterations will be
+        * needed, and that we've managed to get a good fix on the cost and
+        * output size of each one of them.  These are mighty shaky assumptions
+        * but it's hard to see how to do better.
+        */
+       total_cost += 10 * rterm->total_cost;
+       total_rows += 10 * rterm->plan_rows;
+
+       /*
+        * Also charge cpu_tuple_cost per row to account for the costs of
+        * manipulating the tuplestores.  (We don't worry about possible
+        * spill-to-disk costs.)
+        */
+       total_cost += cpu_tuple_cost * total_rows;
+
+       runion->startup_cost = startup_cost;
+       runion->total_cost = total_cost;
+       runion->plan_rows = total_rows;
+       runion->plan_width = Max(nrterm->plan_width, rterm->plan_width);
+}
+
 /*
  * cost_sort
  *       Determines and returns the cost of sorting a relation, including
@@ -2475,6 +2553,44 @@ set_values_size_estimates(PlannerInfo *root, RelOptInfo *rel)
        set_baserel_size_estimates(root, rel);
 }
 
+/*
+ * set_cte_size_estimates
+ *             Set the size estimates for a base relation that is a CTE reference.
+ *
+ * The rel's targetlist and restrictinfo list must have been constructed
+ * already, and we need the completed plan for the CTE (if a regular CTE)
+ * or the non-recursive term (if a self-reference).
+ *
+ * We set the same fields as set_baserel_size_estimates.
+ */
+void
+set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, Plan *cteplan)
+{
+       RangeTblEntry *rte;
+
+       /* Should only be applied to base relations that are CTE references */
+       Assert(rel->relid > 0);
+       rte = planner_rt_fetch(rel->relid, root);
+       Assert(rte->rtekind == RTE_CTE);
+
+       if (rte->self_reference)
+       {
+               /*
+                * In a self-reference, arbitrarily assume the average worktable
+                * size is about 10 times the nonrecursive term's size.
+                */
+               rel->tuples = 10 * cteplan->plan_rows;
+       }
+       else
+       {
+               /* Otherwise just believe the CTE plan's output estimate */
+               rel->tuples = cteplan->plan_rows;
+       }
+
+       /* Now estimate number of output rows, etc */
+       set_baserel_size_estimates(root, rel);
+}
+
 
 /*
  * set_rel_width
index 845f429a4b4865fc55a7f7e24c0b03c10c9dd4b3..eaf0ffa4276e87a1a28d3eb126597b56ff14cf31 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/path/joinpath.c,v 1.117 2008/08/14 18:47:59 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/path/joinpath.c,v 1.118 2008/10/04 21:56:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -408,10 +408,15 @@ match_unsorted_outer(PlannerInfo *root,
                 * If the cheapest inner path is a join or seqscan, we should consider
                 * materializing it.  (This is a heuristic: we could consider it
                 * always, but for inner indexscans it's probably a waste of time.)
+                * Also skip it if the inner path materializes its output anyway.
                 */
-               if (!(IsA(inner_cheapest_total, IndexPath) ||
-                         IsA(inner_cheapest_total, BitmapHeapPath) ||
-                         IsA(inner_cheapest_total, TidPath)))
+               if (!(inner_cheapest_total->pathtype == T_IndexScan ||
+                         inner_cheapest_total->pathtype == T_BitmapHeapScan ||
+                         inner_cheapest_total->pathtype == T_TidScan ||
+                         inner_cheapest_total->pathtype == T_Material ||
+                         inner_cheapest_total->pathtype == T_FunctionScan ||
+                         inner_cheapest_total->pathtype == T_CteScan ||
+                         inner_cheapest_total->pathtype == T_WorkTableScan))
                        matpath = (Path *)
                                create_material_path(innerrel, inner_cheapest_total);
 
index 1195024b5fc45c83f427c0b073c7a948043159d9..aabbf64a755e1d4902ab4242d3fb4f9e3c96699d 100644 (file)
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.248 2008/09/05 21:07:29 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.249 2008/10/04 21:56:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -62,6 +62,10 @@ static FunctionScan *create_functionscan_plan(PlannerInfo *root, Path *best_path
                                                 List *tlist, List *scan_clauses);
 static ValuesScan *create_valuesscan_plan(PlannerInfo *root, Path *best_path,
                                           List *tlist, List *scan_clauses);
+static CteScan *create_ctescan_plan(PlannerInfo *root, Path *best_path,
+                                                                       List *tlist, List *scan_clauses);
+static WorkTableScan *create_worktablescan_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,6 +97,10 @@ static FunctionScan *make_functionscan(List *qptlist, List *qpqual,
                                  List *funccoltypes, List *funccoltypmods);
 static ValuesScan *make_valuesscan(List *qptlist, List *qpqual,
                                Index scanrelid, List *values_lists);
+static CteScan *make_ctescan(List *qptlist, List *qpqual,
+                                                        Index scanrelid, int ctePlanId, int cteParam);
+static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
+                                                                                Index scanrelid, int wtParam);
 static BitmapAnd *make_bitmap_and(List *bitmapplans);
 static BitmapOr *make_bitmap_or(List *bitmapplans);
 static NestLoop *make_nestloop(List *tlist,
@@ -148,6 +156,8 @@ create_plan(PlannerInfo *root, Path *best_path)
                case T_SubqueryScan:
                case T_FunctionScan:
                case T_ValuesScan:
+               case T_CteScan:
+               case T_WorkTableScan:
                        plan = create_scan_plan(root, best_path);
                        break;
                case T_HashJoin:
@@ -270,6 +280,20 @@ create_scan_plan(PlannerInfo *root, Path *best_path)
                                                                                                   scan_clauses);
                        break;
 
+               case T_CteScan:
+                       plan = (Plan *) create_ctescan_plan(root,
+                                                                                               best_path,
+                                                                                               tlist,
+                                                                                               scan_clauses);
+                       break;
+
+               case T_WorkTableScan:
+                       plan = (Plan *) create_worktablescan_plan(root,
+                                                                                                         best_path,
+                                                                                                         tlist,
+                                                                                                         scan_clauses);
+                       break;
+
                default:
                        elog(ERROR, "unrecognized node type: %d",
                                 (int) best_path->pathtype);
@@ -324,12 +348,13 @@ use_physical_tlist(RelOptInfo *rel)
 
        /*
         * We can do this for real relation scans, subquery scans, function scans,
-        * and values scans (but not for, eg, joins).
+        * values scans, and CTE scans (but not for, eg, joins).
         */
        if (rel->rtekind != RTE_RELATION &&
                rel->rtekind != RTE_SUBQUERY &&
                rel->rtekind != RTE_FUNCTION &&
-               rel->rtekind != RTE_VALUES)
+               rel->rtekind != RTE_VALUES &&
+               rel->rtekind != RTE_CTE)
                return false;
 
        /*
@@ -375,6 +400,8 @@ disuse_physical_tlist(Plan *plan, Path *path)
                case T_SubqueryScan:
                case T_FunctionScan:
                case T_ValuesScan:
+               case T_CteScan:
+               case T_WorkTableScan:
                        plan->targetlist = build_relation_tlist(path->parent);
                        break;
                default:
@@ -1335,6 +1362,145 @@ create_valuesscan_plan(PlannerInfo *root, Path *best_path,
        return scan_plan;
 }
 
+/*
+ * create_ctescan_plan
+ *      Returns a ctescan plan for the base relation scanned by 'best_path'
+ *      with restriction clauses 'scan_clauses' and targetlist 'tlist'.
+ */
+static CteScan *
+create_ctescan_plan(PlannerInfo *root, Path *best_path,
+                                       List *tlist, List *scan_clauses)
+{
+       CteScan    *scan_plan;
+       Index           scan_relid = best_path->parent->relid;
+       RangeTblEntry *rte;
+       SubPlan    *ctesplan = NULL;
+       int                     plan_id;
+       int                     cte_param_id;
+       PlannerInfo *cteroot;
+       Index           levelsup;
+       int                     ndx;
+       ListCell   *lc;
+
+       Assert(scan_relid > 0);
+       rte = planner_rt_fetch(scan_relid, root);
+       Assert(rte->rtekind == RTE_CTE);
+       Assert(!rte->self_reference);
+
+       /*
+        * Find the referenced CTE, and locate the SubPlan previously made for it.
+        */
+       levelsup = rte->ctelevelsup;
+       cteroot = root;
+       while (levelsup-- > 0)
+       {
+               cteroot = cteroot->parent_root;
+               if (!cteroot)                   /* shouldn't happen */
+                       elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename);
+       }
+       /*
+        * Note: cte_plan_ids can be shorter than cteList, if we are still working
+        * on planning the CTEs (ie, this is a side-reference from another CTE).
+        * So we mustn't use forboth here.
+        */
+       ndx = 0;
+       foreach(lc, cteroot->parse->cteList)
+       {
+               CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+
+               if (strcmp(cte->ctename, rte->ctename) == 0)
+                       break;
+               ndx++;
+       }
+       if (lc == NULL)                         /* shouldn't happen */
+               elog(ERROR, "could not find CTE \"%s\"", rte->ctename);
+       if (ndx >= list_length(cteroot->cte_plan_ids))
+               elog(ERROR, "could not find plan for CTE \"%s\"", rte->ctename);
+       plan_id = list_nth_int(cteroot->cte_plan_ids, ndx);
+       Assert(plan_id > 0);
+       foreach(lc, cteroot->init_plans)
+       {
+               ctesplan = (SubPlan *) lfirst(lc);
+               if (ctesplan->plan_id == plan_id)
+                       break;
+       }
+       if (lc == NULL)                         /* shouldn't happen */
+               elog(ERROR, "could not find plan for CTE \"%s\"", rte->ctename);
+
+       /*
+        * We need the CTE param ID, which is the sole member of the
+        * SubPlan's setParam list.
+        */
+       cte_param_id = linitial_int(ctesplan->setParam);
+
+       /* 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_ctescan(tlist, scan_clauses, scan_relid,
+                                                        plan_id, cte_param_id);
+
+       copy_path_costsize(&scan_plan->scan.plan, best_path);
+
+       return scan_plan;
+}
+
+/*
+ * create_worktablescan_plan
+ *      Returns a worktablescan plan for the base relation scanned by 'best_path'
+ *      with restriction clauses 'scan_clauses' and targetlist 'tlist'.
+ */
+static WorkTableScan *
+create_worktablescan_plan(PlannerInfo *root, Path *best_path,
+                                                 List *tlist, List *scan_clauses)
+{
+       WorkTableScan *scan_plan;
+       Index           scan_relid = best_path->parent->relid;
+       RangeTblEntry *rte;
+       Index           levelsup;
+       PlannerInfo *cteroot;
+
+       Assert(scan_relid > 0);
+       rte = planner_rt_fetch(scan_relid, root);
+       Assert(rte->rtekind == RTE_CTE);
+       Assert(rte->self_reference);
+
+       /*
+        * We need to find the worktable param ID, which is in the plan level
+        * that's processing the recursive UNION, which is one level *below*
+        * where the CTE comes from.
+        */
+       levelsup = rte->ctelevelsup;
+       if (levelsup == 0)                      /* shouldn't happen */
+                       elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename);
+       levelsup--;
+       cteroot = root;
+       while (levelsup-- > 0)
+       {
+               cteroot = cteroot->parent_root;
+               if (!cteroot)                   /* shouldn't happen */
+                       elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename);
+       }
+       if (cteroot->wt_param_id < 0)   /* shouldn't happen */
+               elog(ERROR, "could not find param ID for CTE \"%s\"", rte->ctename);
+
+       /* 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_worktablescan(tlist, scan_clauses, scan_relid,
+                                                                  cteroot->wt_param_id);
+
+       copy_path_costsize(&scan_plan->scan.plan, best_path);
+
+       return scan_plan;
+}
+
+
 /*****************************************************************************
  *
  *     JOIN METHODS
@@ -2291,6 +2457,48 @@ make_valuesscan(List *qptlist,
        return node;
 }
 
+static CteScan *
+make_ctescan(List *qptlist,
+                        List *qpqual,
+                        Index scanrelid,
+                        int ctePlanId,
+                        int cteParam)
+{
+       CteScan *node = makeNode(CteScan);
+       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;
+       node->ctePlanId = ctePlanId;
+       node->cteParam = cteParam;
+
+       return node;
+}
+
+static WorkTableScan *
+make_worktablescan(List *qptlist,
+                                  List *qpqual,
+                                  Index scanrelid,
+                                  int wtParam)
+{
+       WorkTableScan *node = makeNode(WorkTableScan);
+       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;
+       node->wtParam = wtParam;
+
+       return node;
+}
+
 Append *
 make_append(List *appendplans, bool isTarget, List *tlist)
 {
@@ -2333,6 +2541,26 @@ make_append(List *appendplans, bool isTarget, List *tlist)
        return node;
 }
 
+RecursiveUnion *
+make_recursive_union(List *tlist,
+                                        Plan *lefttree,
+                                        Plan *righttree,
+                                        int wtParam)
+{
+       RecursiveUnion *node = makeNode(RecursiveUnion);
+       Plan       *plan = &node->plan;
+
+       cost_recursive_union(plan, lefttree, righttree);
+
+       plan->targetlist = tlist;
+       plan->qual = NIL;
+       plan->lefttree = lefttree;
+       plan->righttree = righttree;
+       node->wtParam = wtParam;
+
+       return node;
+}
+
 static BitmapAnd *
 make_bitmap_and(List *bitmapplans)
 {
@@ -3284,6 +3512,7 @@ is_projection_capable_plan(Plan *plan)
                case T_SetOp:
                case T_Limit:
                case T_Append:
+               case T_RecursiveUnion:
                        return false;
                default:
                        break;
index ec2b0f794a05b4424ae9f714399d6a3ceb012995..0a704dc65452a3d1908653f9a4fae96fada69d26 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.243 2008/09/09 18:58:08 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.244 2008/10/04 21:56:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -173,7 +173,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
        }
 
        /* primary planning entry point (may recurse for subqueries) */
-       top_plan = subquery_planner(glob, parse, 1, tuple_fraction, &root);
+       top_plan = subquery_planner(glob, parse, NULL,
+                                                               false, tuple_fraction, &root);
 
        /*
         * If creating a plan for a scrollable cursor, make sure it can run
@@ -228,7 +229,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
  *
  * glob is the global state for the current planner run.
  * parse is the querytree produced by the parser & rewriter.
- * level is the current recursion depth (1 at the top-level Query).
+ * parent_root is the immediate parent Query's info (NULL at the top level).
+ * hasRecursion is true if this is a recursive WITH query.
  * tuple_fraction is the fraction of tuples we expect will be retrieved.
  * tuple_fraction is interpreted as explained for grouping_planner, below.
  *
@@ -249,7 +251,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
  */
 Plan *
 subquery_planner(PlannerGlobal *glob, Query *parse,
-                                Index level, double tuple_fraction,
+                                PlannerInfo *parent_root,
+                                bool hasRecursion, double tuple_fraction,
                                 PlannerInfo **subroot)
 {
        int                     num_old_subplans = list_length(glob->subplans);
@@ -263,12 +266,28 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
        root = makeNode(PlannerInfo);
        root->parse = parse;
        root->glob = glob;
-       root->query_level = level;
+       root->query_level = parent_root ? parent_root->query_level + 1 : 1;
+       root->parent_root = parent_root;
        root->planner_cxt = CurrentMemoryContext;
        root->init_plans = NIL;
+       root->cte_plan_ids = NIL;
        root->eq_classes = NIL;
        root->append_rel_list = NIL;
 
+       root->hasRecursion = hasRecursion;
+       if (hasRecursion)
+               root->wt_param_id = SS_assign_worktable_param(root);
+       else
+               root->wt_param_id = -1;
+       root->non_recursive_plan = NULL;
+
+       /*
+        * If there is a WITH list, process each WITH query and build an
+        * initplan SubPlan structure for it.
+        */
+       if (parse->cteList)
+               SS_process_ctes(root);
+
        /*
         * Look for ANY and EXISTS SubLinks in WHERE and JOIN/ON clauses, and try
         * to transform them into joins.  Note that this step does not descend
@@ -776,7 +795,9 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 
                /*
                 * Construct the plan for set operations.  The result will not need
-                * any work except perhaps a top-level sort and/or LIMIT.
+                * any work except perhaps a top-level sort and/or LIMIT.  Note that
+                * any special work for recursive unions is the responsibility of
+                * plan_set_operations.
                 */
                result_plan = plan_set_operations(root, tuple_fraction,
                                                                                  &set_sortclauses);
@@ -838,6 +859,9 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 
                MemSet(&agg_counts, 0, sizeof(AggClauseCounts));
 
+               /* A recursive query should always have setOperations */
+               Assert(!root->hasRecursion);
+
                /* Preprocess GROUP BY clause, if any */
                if (parse->groupClause)
                        preprocess_groupclause(root);
index 18362628727865b1fd75e614ee5ae1a11ff4d76a..6d7ec283b347bb04e35566130f066f8c96f34f6c 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.144 2008/09/09 18:58:08 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.145 2008/10/04 21:56:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -201,11 +201,13 @@ set_plan_references(PlannerGlobal *glob, Plan *plan, List *rtable)
 
                /* zap unneeded sub-structure */
                newrte->subquery = NULL;
+               newrte->joinaliasvars = NIL;
                newrte->funcexpr = NULL;
                newrte->funccoltypes = NIL;
                newrte->funccoltypmods = NIL;
                newrte->values_lists = NIL;
-               newrte->joinaliasvars = NIL;
+               newrte->ctecoltypes = NIL;
+               newrte->ctecoltypmods = NIL;
 
                glob->finalrtable = lappend(glob->finalrtable, newrte);
 
@@ -343,7 +345,28 @@ set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
                                        fix_scan_list(glob, splan->values_lists, rtoffset);
                        }
                        break;
+               case T_CteScan:
+                       {
+                               CteScan *splan = (CteScan *) 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_WorkTableScan:
+                       {
+                               WorkTableScan *splan = (WorkTableScan *) 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_NestLoop:
                case T_MergeJoin:
                case T_HashJoin:
@@ -434,6 +457,11 @@ set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
                                }
                        }
                        break;
+               case T_RecursiveUnion:
+                       /* This doesn't evaluate targetlist or check quals either */
+                       set_dummy_tlist_references(plan, rtoffset);
+                       Assert(plan->qual == NIL);
+                       break;
                case T_BitmapAnd:
                        {
                                BitmapAnd  *splan = (BitmapAnd *) plan;
index fe8be5b1bbb5764ea1f3e55992a347109fdc5650..00d84d56823c33cec74c8bc1e98efead6e48a5e6 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.140 2008/08/28 23:09:46 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.141 2008/10/04 21:56:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -212,6 +212,20 @@ generate_new_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod)
        return retval;
 }
 
+/*
+ * Assign a (nonnegative) PARAM_EXEC ID for a recursive query's worktable.
+ */
+int
+SS_assign_worktable_param(PlannerInfo *root)
+{
+       Param      *param;
+
+       /* We generate a Param of datatype INTERNAL */
+       param = generate_new_param(root, INTERNALOID, -1);
+       /* ... but the caller only cares about its ID */
+       return param->paramid;
+}
+
 /*
  * Get the datatype of the first column of the plan's output.
  *
@@ -308,8 +322,8 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType,
         * Generate the plan for the subquery.
         */
        plan = subquery_planner(root->glob, subquery,
-                                                       root->query_level + 1,
-                                                       tuple_fraction,
+                                                       root,
+                                                       false, tuple_fraction,
                                                        &subroot);
 
        /* And convert to SubPlan or InitPlan format. */
@@ -342,8 +356,8 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType,
                {
                        /* Generate the plan for the ANY subquery; we'll need all rows */
                        plan = subquery_planner(root->glob, subquery,
-                                                                       root->query_level + 1,
-                                                                       0.0,
+                                                                       root,
+                                                                       false, 0.0,
                                                                        &subroot);
 
                        /* Now we can check if it'll fit in work_mem */
@@ -549,6 +563,8 @@ build_subplan(PlannerInfo *root, Plan *plan, List *rtable,
                        {
                                case T_Material:
                                case T_FunctionScan:
+                               case T_CteScan:
+                               case T_WorkTableScan:
                                case T_Sort:
                                        use_material = false;
                                        break;
@@ -798,6 +814,123 @@ hash_ok_operator(OpExpr *expr)
        return true;
 }
 
+
+/*
+ * SS_process_ctes: process a query's WITH list
+ *
+ * We plan each interesting WITH item and convert it to an initplan.
+ * A side effect is to fill in root->cte_plan_ids with a list that
+ * parallels root->parse->cteList and provides the subplan ID for
+ * each CTE's initplan.
+ */
+void
+SS_process_ctes(PlannerInfo *root)
+{
+       ListCell   *lc;
+
+       Assert(root->cte_plan_ids == NIL);
+
+       foreach(lc, root->parse->cteList)
+       {
+               CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+               Query      *subquery;
+               Plan       *plan;
+               PlannerInfo *subroot;
+               SubPlan    *splan;
+               Bitmapset  *tmpset;
+               int                     paramid;
+               Param      *prm;
+
+               /*
+                * Ignore CTEs that are not actually referenced anywhere.
+                */
+               if (cte->cterefcount == 0)
+               {
+                       /* Make a dummy entry in cte_plan_ids */
+                       root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
+                       continue;
+               }
+
+               /*
+                * Copy the source Query node.  Probably not necessary, but let's
+                * keep this similar to make_subplan.
+                */
+               subquery = (Query *) copyObject(cte->ctequery);
+
+               /*
+                * Generate the plan for the CTE query.  Always plan for full
+                * retrieval --- we don't have enough info to predict otherwise.
+                */
+               plan = subquery_planner(root->glob, subquery,
+                                                               root,
+                                                               cte->cterecursive, 0.0,
+                                                               &subroot);
+
+               /*
+                * Make a SubPlan node for it.  This is just enough unlike
+                * build_subplan that we can't share code.
+                *
+                * Note plan_id isn't set till further down, likewise the cost fields.
+                */
+               splan = makeNode(SubPlan);
+               splan->subLinkType = CTE_SUBLINK;
+               splan->testexpr = NULL;
+               splan->paramIds = NIL;
+               splan->firstColType = get_first_col_type(plan);
+               splan->useHashTable = false;
+               splan->unknownEqFalse = false;
+               splan->setParam = NIL;
+               splan->parParam = NIL;
+               splan->args = NIL;
+
+               /*
+                * Make parParam and args lists of param IDs and expressions that
+                * current query level will pass to this child plan.  Even though
+                * this is an initplan, there could be side-references to earlier
+                * initplan's outputs, specifically their CTE output parameters.
+                */
+               tmpset = bms_copy(plan->extParam);
+               while ((paramid = bms_first_member(tmpset)) >= 0)
+               {
+                       PlannerParamItem *pitem = list_nth(root->glob->paramlist, paramid);
+
+                       if (pitem->abslevel == root->query_level)
+                       {
+                               prm = (Param *) pitem->item;
+                               if (!IsA(prm, Param) ||
+                                       prm->paramtype != INTERNALOID)
+                                       elog(ERROR, "bogus local parameter passed to WITH query");
+
+                               splan->parParam = lappend_int(splan->parParam, paramid);
+                               splan->args = lappend(splan->args, copyObject(prm));
+                       }
+               }
+               bms_free(tmpset);
+
+               /*
+                * Assign a param to represent the query output.  We only really
+                * care about reserving a parameter ID number.
+                */
+               prm = generate_new_param(root, INTERNALOID, -1);
+               splan->setParam = list_make1_int(prm->paramid);
+
+               /*
+                * Add the subplan and its rtable to the global lists.
+                */
+               root->glob->subplans = lappend(root->glob->subplans, plan);
+               root->glob->subrtables = lappend(root->glob->subrtables,
+                                                                                subroot->parse->rtable);
+               splan->plan_id = list_length(root->glob->subplans);
+
+               root->init_plans = lappend(root->init_plans, splan);
+
+               root->cte_plan_ids = lappend_int(root->cte_plan_ids, splan->plan_id);
+
+               /* Lastly, fill in the cost estimates for use later */
+               cost_subplan(root, splan, plan);
+       }
+}
+
 /*
  * convert_ANY_sublink_to_join: can we convert an ANY SubLink to a join?
  *
@@ -1589,6 +1722,9 @@ SS_finalize_plan(PlannerInfo *root, Plan *plan, bool attach_initplans)
 
                paramid++;
        }
+       /* Also include the recursion working table, if any */
+       if (root->wt_param_id >= 0)
+               valid_params = bms_add_member(valid_params, root->wt_param_id);
 
        /*
         * Now recurse through plan tree.
@@ -1719,6 +1855,18 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params)
                                                          &context);
                        break;
 
+               case T_CteScan:
+                       context.paramids =
+                               bms_add_member(context.paramids,
+                                                          ((CteScan *) plan)->cteParam);
+                       break;
+
+               case T_WorkTableScan:
+                       context.paramids =
+                               bms_add_member(context.paramids,
+                                                          ((WorkTableScan *) plan)->wtParam);
+                       break;
+
                case T_Append:
                        {
                                ListCell   *l;
@@ -1790,6 +1938,7 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params)
                                                          &context);
                        break;
 
+               case T_RecursiveUnion:
                case T_Hash:
                case T_Agg:
                case T_SeqScan:
@@ -1816,6 +1965,15 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params)
                                                                                                         plan->righttree,
                                                                                                         valid_params));
 
+       /*
+        * RecursiveUnion *generates* its worktable param, so don't bubble that up
+        */
+       if (IsA(plan, RecursiveUnion))
+       {
+               context.paramids = bms_del_member(context.paramids,
+                                                                                 ((RecursiveUnion *) plan)->wtParam);
+       }
+
        /* Now we have all the paramids */
 
        if (!bms_is_subset(context.paramids, valid_params))
index 965856385d77a2fae252284e2a04470d930819fe..21bd8332f4a25cd2a61697a294a1c30fe5f9f9a2 100644 (file)
@@ -16,7 +16,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.54 2008/08/25 22:42:33 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.55 2008/10/04 21:56:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -567,10 +567,18 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
        subroot->parse = subquery;
        subroot->glob = root->glob;
        subroot->query_level = root->query_level;
+       subroot->parent_root = root->parent_root;
        subroot->planner_cxt = CurrentMemoryContext;
        subroot->init_plans = NIL;
+       subroot->cte_plan_ids = NIL;
        subroot->eq_classes = NIL;
        subroot->append_rel_list = NIL;
+       subroot->hasRecursion = false;
+       subroot->wt_param_id = -1;
+       subroot->non_recursive_plan = NULL;
+
+       /* No CTEs to worry about */
+       Assert(subquery->cteList == NIL);
 
        /*
         * Pull up any SubLinks within the subquery's quals, so that we don't
@@ -916,8 +924,8 @@ is_simple_subquery(Query *subquery)
                return false;
 
        /*
-        * Can't pull up a subquery involving grouping, aggregation, sorting, or
-        * limiting.
+        * Can't pull up a subquery involving grouping, aggregation, sorting,
+        * limiting, or WITH.  (XXX WITH could possibly be allowed later)
         */
        if (subquery->hasAggs ||
                subquery->groupClause ||
@@ -925,7 +933,8 @@ is_simple_subquery(Query *subquery)
                subquery->sortClause ||
                subquery->distinctClause ||
                subquery->limitOffset ||
-               subquery->limitCount)
+               subquery->limitCount ||
+               subquery->cteList)
                return false;
 
        /*
@@ -985,11 +994,12 @@ is_simple_union_all(Query *subquery)
                return false;
        Assert(IsA(topop, SetOperationStmt));
 
-       /* Can't handle ORDER BY, LIMIT/OFFSET, or locking */
+       /* Can't handle ORDER BY, LIMIT/OFFSET, locking, or WITH */
        if (subquery->sortClause ||
                subquery->limitOffset ||
                subquery->limitCount ||
-               subquery->rowMarks)
+               subquery->rowMarks ||
+               subquery->cteList)
                return false;
 
        /* Recursively check the tree of set operations */
index 7dc274797e5cc21b9af557bf86adf118b63f5c31..0836fde517b40e3af28753347fada355a2c06101 100644 (file)
@@ -22,7 +22,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.155 2008/08/28 23:09:46 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.156 2008/10/04 21:56:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -56,6 +56,10 @@ static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root,
                                           List *colTypes, bool junkOK,
                                           int flag, List *refnames_tlist,
                                           List **sortClauses, double *pNumGroups);
+static Plan *generate_recursion_plan(SetOperationStmt *setOp,
+                                               PlannerInfo *root, double tuple_fraction,
+                                               List *refnames_tlist,
+                                               List **sortClauses);
 static Plan *generate_union_plan(SetOperationStmt *op, PlannerInfo *root,
                                        double tuple_fraction,
                                        List *refnames_tlist,
@@ -147,6 +151,14 @@ plan_set_operations(PlannerInfo *root, double tuple_fraction,
                                                         parse->rtable)->subquery;
        Assert(leftmostQuery != NULL);
 
+       /*
+        * If the topmost node is a recursive union, it needs special processing.
+        */
+       if (root->hasRecursion)
+               return generate_recursion_plan(topop, root, tuple_fraction,
+                                                                          leftmostQuery->targetList,
+                                                                          sortClauses);
+
        /*
         * Recurse on setOperations tree to generate plans for set ops. The final
         * output plan should have just the column types shown as the output from
@@ -200,8 +212,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
                 * Generate plan for primitive subquery
                 */
                subplan = subquery_planner(root->glob, subquery,
-                                                                  root->query_level + 1,
-                                                                  tuple_fraction,
+                                                                  root,
+                                                                  false, tuple_fraction,
                                                                   &subroot);
 
                /*
@@ -293,6 +305,58 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
        }
 }
 
+/*
+ * Generate plan for a recursive UNION node
+ */
+static Plan *
+generate_recursion_plan(SetOperationStmt *setOp, PlannerInfo *root,
+                                               double tuple_fraction,
+                                               List *refnames_tlist,
+                                               List **sortClauses)
+{
+       Plan       *plan;
+       Plan       *lplan;
+       Plan       *rplan;
+       List       *tlist;
+
+       /* Parser should have rejected other cases */
+       if (setOp->op != SETOP_UNION || !setOp->all)
+               elog(ERROR, "only UNION ALL queries can be recursive");
+       /* Worktable ID should be assigned */
+       Assert(root->wt_param_id >= 0);
+
+       /*
+        * Unlike a regular UNION node, process the left and right inputs
+        * separately without any intention of combining them into one Append.
+        */
+       lplan = recurse_set_operations(setOp->larg, root, tuple_fraction,
+                                                                  setOp->colTypes, false, -1,
+                                                                  refnames_tlist, sortClauses, NULL);
+       /* The right plan will want to look at the left one ... */
+       root->non_recursive_plan = lplan;
+       rplan = recurse_set_operations(setOp->rarg, root, tuple_fraction,
+                                                                  setOp->colTypes, false, -1,
+                                                                  refnames_tlist, sortClauses, NULL);
+       root->non_recursive_plan = NULL;
+
+       /*
+        * Generate tlist for RecursiveUnion plan node --- same as in Append cases
+        */
+       tlist = generate_append_tlist(setOp->colTypes, false,
+                                                                 list_make2(lplan, rplan),
+                                                                 refnames_tlist);
+
+       /*
+        * And make the plan node.
+        */
+       plan = (Plan *) make_recursive_union(tlist, lplan, rplan,
+                                                                                root->wt_param_id);
+
+       *sortClauses = NIL;                     /* result of UNION ALL is always unsorted */
+
+       return plan;
+}
+
 /*
  * Generate plan for a UNION or UNION ALL node
  */
@@ -1346,7 +1410,7 @@ adjust_appendrel_attrs(Node *node, AppendRelInfo *appinfo)
                newnode = query_tree_mutator((Query *) node,
                                                                         adjust_appendrel_attrs_mutator,
                                                                         (void *) appinfo,
-                                                                        QTW_IGNORE_RT_SUBQUERIES);
+                                                                        QTW_IGNORE_RC_SUBQUERIES);
                if (newnode->resultRelation == appinfo->parent_relid)
                {
                        newnode->resultRelation = appinfo->child_relid;
index 097105a89a290b25448db343da39e15a2fa1c458..bd0192d829e265b1c20fb87b32936aeb5298035d 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.267 2008/09/09 18:58:08 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.268 2008/10/04 21:56:53 tgl Exp $
  *
  * HISTORY
  *       AUTHOR                        DATE                    MAJOR EVENT
@@ -3361,6 +3361,7 @@ inline_function(Oid funcid, Oid result_type, List *args,
                querytree->intoClause ||
                querytree->hasAggs ||
                querytree->hasSubLinks ||
+               querytree->cteList ||
                querytree->rtable ||
                querytree->jointree->fromlist ||
                querytree->jointree->quals ||
index 8e32895fb27624539762dbdd127d147bfad1b18f..a922abcee8a00a5103f33ecff542a39be0397d01 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/util/pathnode.c,v 1.147 2008/09/05 21:07:29 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/util/pathnode.c,v 1.148 2008/10/04 21:56:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1219,6 +1219,45 @@ create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel)
        return pathnode;
 }
 
+/*
+ * create_ctescan_path
+ *       Creates a path corresponding to a scan of a non-self-reference CTE,
+ *       returning the pathnode.
+ */
+Path *
+create_ctescan_path(PlannerInfo *root, RelOptInfo *rel)
+{
+       Path       *pathnode = makeNode(Path);
+
+       pathnode->pathtype = T_CteScan;
+       pathnode->parent = rel;
+       pathnode->pathkeys = NIL;       /* XXX for now, result is always unordered */
+
+       cost_ctescan(pathnode, root, rel);
+
+       return pathnode;
+}
+
+/*
+ * create_worktablescan_path
+ *       Creates a path corresponding to a scan of a self-reference CTE,
+ *       returning the pathnode.
+ */
+Path *
+create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel)
+{
+       Path       *pathnode = makeNode(Path);
+
+       pathnode->pathtype = T_WorkTableScan;
+       pathnode->parent = rel;
+       pathnode->pathkeys = NIL;       /* result is always unordered */
+
+       /* Cost is the same as for a regular CTE scan */
+       cost_ctescan(pathnode, root, rel);
+
+       return pathnode;
+}
+
 /*
  * create_nestloop_path
  *       Creates a pathnode corresponding to a nestloop join between two
index e6cd2f26b959053334885482f7d00de6ce8a78d0..044cb8bbe68426c12fd10f540bf4268e969c8757 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/util/plancat.c,v 1.151 2008/09/01 20:42:44 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/util/plancat.c,v 1.152 2008/10/04 21:56:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -657,8 +657,8 @@ relation_excluded_by_constraints(PlannerInfo *root,
  * dropped cols.
  *
  * We also support building a "physical" tlist for subqueries, functions,
- * and values lists, since the same optimization can occur in SubqueryScan,
- * FunctionScan, and ValuesScan nodes.
+ * values lists, and CTEs, since the same optimization can occur in
+ * SubqueryScan, FunctionScan, ValuesScan, CteScan, and WorkTableScan nodes.
  */
 List *
 build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
@@ -733,6 +733,9 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
                        break;
 
                case RTE_FUNCTION:
+               case RTE_VALUES:
+               case RTE_CTE:
+                       /* Not all of these can have dropped cols, but share code anyway */
                        expandRTE(rte, varno, 0, -1, true /* include dropped */ ,
                                          NULL, &colvars);
                        foreach(l, colvars)
@@ -757,21 +760,6 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
                        }
                        break;
 
-               case RTE_VALUES:
-                       expandRTE(rte, varno, 0, -1, false /* dropped not applicable */ ,
-                                         NULL, &colvars);
-                       foreach(l, colvars)
-                       {
-                               var = (Var *) lfirst(l);
-
-                               tlist = lappend(tlist,
-                                                               makeTargetEntry((Expr *) var,
-                                                                                               var->varattno,
-                                                                                               NULL,
-                                                                                               false));
-                       }
-                       break;
-
                default:
                        /* caller error */
                        elog(ERROR, "unsupported RTE kind %d in build_physical_tlist",
index f5592d17bb92ad81690384dfecc8f85f3a1f21fc..2fb6cd2efe646fa2da8f27879d59c8dfd88759f5 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.90 2008/08/14 18:47:59 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.91 2008/10/04 21:56:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -101,6 +101,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
                case RTE_SUBQUERY:
                case RTE_FUNCTION:
                case RTE_VALUES:
+               case RTE_CTE:
 
                        /*
                         * Subquery, function, or values list --- set up attr range and
index 04b9ed61f4445707bd1c7cdb3588c2148d1c4256..8cefea14ac09327106a3a22853084101cdce882c 100644 (file)
@@ -2,7 +2,7 @@
 #
 # Makefile for parser
 #
-# $PostgreSQL: pgsql/src/backend/parser/Makefile,v 1.47 2008/08/29 13:02:32 petere Exp $
+# $PostgreSQL: pgsql/src/backend/parser/Makefile,v 1.48 2008/10/04 21:56:54 tgl Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -12,7 +12,7 @@ include $(top_builddir)/src/Makefile.global
 
 override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
 
-OBJS= analyze.o gram.o keywords.o parser.o parse_agg.o parse_clause.o \
+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
 
index 18585b860b4335aa96ea6bf795d646b11e9b2dbe..8934c04bb244675da9e8d1e6cffdde1436d5b763 100644 (file)
@@ -17,7 +17,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *     $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.379 2008/09/01 20:42:44 tgl Exp $
+ *     $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.380 2008/10/04 21:56:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -32,6 +32,7 @@
 #include "parser/parse_agg.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
+#include "parser/parse_cte.h"
 #include "parser/parse_oper.h"
 #include "parser/parse_relation.h"
 #include "parser/parse_target.h"
@@ -309,6 +310,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
                pstate->p_relnamespace = NIL;
                sub_varnamespace = pstate->p_varnamespace;
                pstate->p_varnamespace = NIL;
+               /* There can't be any outer WITH to worry about */
+               Assert(pstate->p_ctenamespace == NIL);
        }
        else
        {
@@ -447,6 +450,13 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
                List       *exprsLists = NIL;
                int                     sublist_length = -1;
 
+               /* process the WITH clause */
+               if (selectStmt->withClause)
+               {
+                       qry->hasRecursive = selectStmt->withClause->recursive;
+                       qry->cteList = transformWithClause(pstate, selectStmt->withClause);
+               }
+
                foreach(lc, selectStmt->valuesLists)
                {
                        List       *sublist = (List *) lfirst(lc);
@@ -540,6 +550,13 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
                Assert(list_length(valuesLists) == 1);
 
+               /* process the WITH clause */
+               if (selectStmt->withClause)
+               {
+                       qry->hasRecursive = selectStmt->withClause->recursive;
+                       qry->cteList = transformWithClause(pstate, selectStmt->withClause);
+               }
+
                /* Do basic expression transformation (same as a ROW() expr) */
                exprList = transformExpressionList(pstate,
                                                                                   (List *) linitial(valuesLists));
@@ -694,6 +711,13 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
        /* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */
        pstate->p_locking_clause = stmt->lockingClause;
 
+       /* process the WITH clause */
+       if (stmt->withClause)
+       {
+               qry->hasRecursive = stmt->withClause->recursive;
+               qry->cteList = transformWithClause(pstate, stmt->withClause);
+       }
+
        /* process the FROM clause */
        transformFromClause(pstate, stmt->fromClause);
 
@@ -814,6 +838,13 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
        Assert(stmt->havingClause == NULL);
        Assert(stmt->op == SETOP_NONE);
 
+       /* process the WITH clause */
+       if (stmt->withClause)
+       {
+               qry->hasRecursive = stmt->withClause->recursive;
+               qry->cteList = transformWithClause(pstate, stmt->withClause);
+       }
+
        /*
         * For each row of VALUES, transform the raw expressions and gather type
         * information.  This is also a handy place to reject DEFAULT nodes, which
@@ -1059,6 +1090,13 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                 errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
 
+       /* process the WITH clause */
+       if (stmt->withClause)
+       {
+               qry->hasRecursive = stmt->withClause->recursive;
+               qry->cteList = transformWithClause(pstate, stmt->withClause);
+       }
+
        /*
         * Recursively transform the components of the tree.
         */
@@ -1101,21 +1139,22 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
                TargetEntry *lefttle = (TargetEntry *) lfirst(left_tlist);
                char       *colName;
                TargetEntry *tle;
-               Expr       *expr;
+               Var                *var;
 
                Assert(!lefttle->resjunk);
                colName = pstrdup(lefttle->resname);
-               expr = (Expr *) makeVar(leftmostRTI,
-                                                               lefttle->resno,
-                                                               colType,
-                                                               colTypmod,
-                                                               0);
-               tle = makeTargetEntry(expr,
+               var = makeVar(leftmostRTI,
+                                         lefttle->resno,
+                                         colType,
+                                         colTypmod,
+                                         0);
+               var->location = exprLocation((Node *) lefttle->expr);
+               tle = makeTargetEntry((Expr *) var,
                                                          (AttrNumber) pstate->p_next_resno++,
                                                          colName,
                                                          false);
                qry->targetList = lappend(qry->targetList, tle);
-               targetvars = lappend(targetvars, expr);
+               targetvars = lappend(targetvars, var);
                targetnames = lappend(targetnames, makeString(colName));
                left_tlist = lnext(left_tlist);
        }
@@ -1841,6 +1880,30 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc)
                                         */
                                        transformLockingClause(pstate, rte->subquery, allrels);
                                        break;
+                               case RTE_CTE:
+                                       {
+                                               /*
+                                                * We allow FOR UPDATE/SHARE of a WITH query to be
+                                                * propagated into the WITH, but it doesn't seem
+                                                * very sane to allow this for a reference to an
+                                                * outer-level WITH.  And it definitely wouldn't
+                                                * work for a self-reference, since we're not done
+                                                * analyzing the CTE anyway.
+                                                */
+                                               CommonTableExpr *cte;
+
+                                               if (rte->ctelevelsup > 0 || rte->self_reference)
+                                                       ereport(ERROR,
+                                                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                                        errmsg("SELECT FOR UPDATE/SHARE cannot be applied to an outer-level WITH query")));
+                                               cte = GetCTEForRTE(pstate, rte);
+                                               /* should be analyzed by now */
+                                               Assert(IsA(cte->ctequery, Query));
+                                               transformLockingClause(pstate,
+                                                                                          (Query *) cte->ctequery,
+                                                                                          allrels);
+                                       }
+                                       break;
                                default:
                                        /* ignore JOIN, SPECIAL, FUNCTION RTEs */
                                        break;
@@ -1908,6 +1971,32 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc)
                                                                         errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES"),
                                                                         parser_errposition(pstate, thisrel->location)));
                                                        break;
+                                               case RTE_CTE:
+                                                       {
+                                                               /*
+                                                                * We allow FOR UPDATE/SHARE of a WITH query
+                                                                * to be propagated into the WITH, but it
+                                                                * doesn't seem very sane to allow this for a
+                                                                * reference to an outer-level WITH.  And it
+                                                                * definitely wouldn't work for a
+                                                                * self-reference, since we're not done
+                                                                * analyzing the CTE anyway.
+                                                                */
+                                                               CommonTableExpr *cte;
+
+                                                               if (rte->ctelevelsup > 0 || rte->self_reference)
+                                                                       ereport(ERROR,
+                                                                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                                                        errmsg("SELECT FOR UPDATE/SHARE cannot be applied to an outer-level WITH query"),
+                                                                                        parser_errposition(pstate, thisrel->location)));
+                                                               cte = GetCTEForRTE(pstate, rte);
+                                                               /* should be analyzed by now */
+                                                               Assert(IsA(cte->ctequery, Query));
+                                                               transformLockingClause(pstate,
+                                                                                                          (Query *) cte->ctequery,
+                                                                                                          allrels);
+                                                       }
+                                                       break;
                                                default:
                                                        elog(ERROR, "unrecognized RTE type: %d",
                                                                 (int) rte->rtekind);
index d487e59fd72654305d3b1120d6a2aba0117c6c75..5ee06996241ecada5fb9b7bc48144045d05c5b64 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.624 2008/09/23 09:20:35 heikki Exp $
+ *       $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.625 2008/10/04 21:56:54 tgl Exp $
  *
  * HISTORY
  *       AUTHOR                        DATE                    MAJOR EVENT
@@ -119,7 +119,8 @@ static List *extractArgTypes(List *parameters);
 static SelectStmt *findLeftmostSelect(SelectStmt *node);
 static void insertSelectOptions(SelectStmt *stmt,
                                                                List *sortClause, List *lockingClause,
-                                                               Node *limitOffset, Node *limitCount);
+                                                               Node *limitOffset, Node *limitCount,
+                                                               WithClause *withClause);
 static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
@@ -160,6 +161,7 @@ static TypeName *TableFuncTypeName(List *columns);
        Alias                           *alias;
        RangeVar                        *range;
        IntoClause                      *into;
+       WithClause                      *with;
        A_Indices                       *aind;
        ResTarget                       *target;
        PrivTarget                      *privtarget;
@@ -377,6 +379,10 @@ static TypeName *TableFuncTypeName(List *columns);
 %type <ival>   document_or_content
 %type <boolean> xml_whitespace_option
 
+%type <node>   common_table_expr
+%type <with>   with_clause
+%type <list>   cte_list
+
 
 /*
  * If you make any token changes, update the keyword table in
@@ -443,9 +449,9 @@ static TypeName *TableFuncTypeName(List *columns);
 
        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
+       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
@@ -6276,6 +6282,10 @@ select_with_parens:
                ;
 
 /*
+ * This rule parses the equivalent of the standard's <query expression>.
+ * The duplicative productions are annoying, but hard to get rid of without
+ * creating shift/reduce conflicts.
+ *
  *     FOR UPDATE/SHARE may be before or after LIMIT/OFFSET.
  *     In <=7.2.X, LIMIT/OFFSET had to be after FOR UPDATE
  *     We now support both orderings, but prefer LIMIT/OFFSET before FOR UPDATE/SHARE
@@ -6286,21 +6296,51 @@ select_no_parens:
                        | select_clause sort_clause
                                {
                                        insertSelectOptions((SelectStmt *) $1, $2, NIL,
-                                                                               NULL, NULL);
+                                                                               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));
+                                                                               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));
+                                                                               list_nth($3, 0), list_nth($3, 1),
+                                                                               NULL);
                                        $$ = $1;
                                }
+                       | with_clause simple_select
+                               {
+                                       insertSelectOptions((SelectStmt *) $2, NULL, NIL,
+                                                                               NULL, NULL,
+                                                                               $1);
+                                       $$ = $2;
+                               }
+                       | with_clause select_clause sort_clause
+                               {
+                                       insertSelectOptions((SelectStmt *) $2, $3, NIL,
+                                                                               NULL, NULL,
+                                                                               $1);
+                                       $$ = $2;
+                               }
+                       | with_clause select_clause opt_sort_clause for_locking_clause opt_select_limit
+                               {
+                                       insertSelectOptions((SelectStmt *) $2, $3, $4,
+                                                                               list_nth($5, 0), list_nth($5, 1),
+                                                                               $1);
+                                       $$ = $2;
+                               }
+                       | with_clause select_clause opt_sort_clause select_limit opt_for_locking_clause
+                               {
+                                       insertSelectOptions((SelectStmt *) $2, $3, $5,
+                                                                               list_nth($4, 0), list_nth($4, 1),
+                                                                               $1);
+                                       $$ = $2;
+                               }
                ;
 
 select_clause:
@@ -6323,10 +6363,10 @@ select_clause:
  *             (SELECT foo UNION SELECT bar) ORDER BY baz
  * not
  *             SELECT foo UNION (SELECT bar ORDER BY baz)
- * Likewise FOR UPDATE and LIMIT.  Therefore, those clauses are described
- * as part of the select_no_parens production, not simple_select.
- * This does not limit functionality, because you can reintroduce sort and
- * limit clauses inside parentheses.
+ * Likewise for WITH, FOR UPDATE and LIMIT.  Therefore, those clauses are
+ * described as part of the select_no_parens production, not simple_select.
+ * This does not limit functionality, because you can reintroduce these
+ * clauses inside parentheses.
  *
  * NOTE: only the leftmost component SelectStmt should have INTO.
  * However, this is not checked by the grammar; parse analysis must check it.
@@ -6361,6 +6401,47 @@ simple_select:
                                }
                ;
 
+/*
+ * SQL standard WITH clause looks like:
+ *
+ * WITH [ RECURSIVE ] <query name> [ (<column>,...) ]
+ *             AS (query) [ SEARCH or CYCLE clause ]
+ *
+ * We don't currently support the SEARCH or CYCLE clause.
+ */
+with_clause:
+               WITH cte_list
+                       {
+                               $$ = makeNode(WithClause);
+                               $$->ctes = $2;
+                               $$->recursive = false;
+                               $$->location = @1;
+                       }
+               | WITH RECURSIVE cte_list
+                       {
+                               $$ = makeNode(WithClause);
+                               $$->ctes = $3;
+                               $$->recursive = true;
+                               $$->location = @1;
+                       }
+               ;
+
+cte_list:
+               common_table_expr                                               { $$ = list_make1($1); }
+               | cte_list ',' common_table_expr                { $$ = lappend($1, $3); }
+               ;
+
+common_table_expr:  name opt_name_list AS select_with_parens
+                       {
+                               CommonTableExpr *n = makeNode(CommonTableExpr);
+                               n->ctename = $1;
+                               n->aliascolnames = $2;
+                               n->ctequery = $4;
+                               n->location = @1;
+                               $$ = (Node *) n;
+                       }
+               ;
+
 into_clause:
                        INTO OptTempTableName
                                {
@@ -9349,6 +9430,7 @@ unreserved_keyword:
                        | READ
                        | REASSIGN
                        | RECHECK
+                       | RECURSIVE
                        | REINDEX
                        | RELATIVE_P
                        | RELEASE
@@ -9416,7 +9498,6 @@ unreserved_keyword:
                        | VIEW
                        | VOLATILE
                        | WHITESPACE_P
-                       | WITH
                        | WITHOUT
                        | WORK
                        | WRITE
@@ -9599,6 +9680,7 @@ reserved_keyword:
                        | VARIADIC
                        | WHEN
                        | WHERE
+                       | WITH
                ;
 
 
@@ -9922,8 +10004,11 @@ findLeftmostSelect(SelectStmt *node)
 static void
 insertSelectOptions(SelectStmt *stmt,
                                        List *sortClause, List *lockingClause,
-                                       Node *limitOffset, Node *limitCount)
+                                       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
@@ -9957,6 +10042,15 @@ insertSelectOptions(SelectStmt *stmt,
                                         scanner_errposition(exprLocation(limitCount))));
                stmt->limitCount = limitCount;
        }
+       if (withClause)
+       {
+               if (stmt->withClause)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("multiple WITH clauses not allowed"),
+                                        scanner_errposition(exprLocation((Node *) withClause))));
+               stmt->withClause = withClause;
+       }
 }
 
 static Node *
index 0f17aa131ebc702ca1bc39c1a563e09473fabda4..c55bfd8b5cde512d33c8ccf5186cb7a093df6229 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.201 2008/09/23 09:20:36 heikki Exp $
+ *       $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.202 2008/10/04 21:56:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -305,6 +305,7 @@ const ScanKeyword ScanKeywords[] = {
        {"real", REAL, COL_NAME_KEYWORD},
        {"reassign", REASSIGN, UNRESERVED_KEYWORD},
        {"recheck", RECHECK, UNRESERVED_KEYWORD},
+       {"recursive", RECURSIVE, UNRESERVED_KEYWORD},
        {"references", REFERENCES, RESERVED_KEYWORD},
        {"reindex", REINDEX, UNRESERVED_KEYWORD},
        {"relative", RELATIVE_P, UNRESERVED_KEYWORD},
@@ -403,15 +404,6 @@ const ScanKeyword ScanKeywords[] = {
        {"when", WHEN, RESERVED_KEYWORD},
        {"where", WHERE, RESERVED_KEYWORD},
        {"whitespace", WHITESPACE_P, UNRESERVED_KEYWORD},
-
-       /*
-        * XXX we mark WITH as reserved to force it to be quoted in dumps, even
-        * though it is currently unreserved according to gram.y.  This is because
-        * we expect we'll have to make it reserved to implement SQL WITH clauses.
-        * If that patch manages to do without reserving WITH, adjust this entry
-        * at that time; in any case this should be back in sync with gram.y after
-        * WITH clauses are implemented.
-        */
        {"with", WITH, RESERVED_KEYWORD},
        {"without", WITHOUT, UNRESERVED_KEYWORD},
        {"work", WORK, UNRESERVED_KEYWORD},
index d85f64c7abc5dc0b4e32d846a5878f18c15a81aa..e2645462d57887ec693c6cce165c69930beabc7b 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/parser/parse_agg.c,v 1.83 2008/09/01 20:42:44 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/parser/parse_agg.c,v 1.84 2008/10/04 21:56:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -102,12 +102,28 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
        bool            have_non_var_grouping;
        ListCell   *l;
        bool            hasJoinRTEs;
+       bool            hasSelfRefRTEs;
        PlannerInfo *root;
        Node       *clause;
 
        /* This should only be called if we found aggregates or grouping */
        Assert(pstate->p_hasAggs || qry->groupClause || qry->havingQual);
 
+       /*
+        * Scan the range table to see if there are JOIN or self-reference CTE
+        * entries.  We'll need this info below.
+        */
+       hasJoinRTEs = hasSelfRefRTEs = false;
+       foreach(l, pstate->p_rtable)
+       {
+               RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+
+               if (rte->rtekind == RTE_JOIN)
+                       hasJoinRTEs = true;
+               else if (rte->rtekind == RTE_CTE && rte->self_reference)
+                       hasSelfRefRTEs = true;
+       }
+
        /*
         * Aggregates must never appear in WHERE or JOIN/ON clauses.
         *
@@ -157,20 +173,6 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
         * underlying vars, so that aliased and unaliased vars will be correctly
         * taken as equal.      We can skip the expense of doing this if no rangetable
         * entries are RTE_JOIN kind.
-        */
-       hasJoinRTEs = false;
-       foreach(l, pstate->p_rtable)
-       {
-               RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-               if (rte->rtekind == RTE_JOIN)
-               {
-                       hasJoinRTEs = true;
-                       break;
-               }
-       }
-
-       /*
         * We use the planner's flatten_join_alias_vars routine to do the
         * flattening; it wants a PlannerInfo root node, which fortunately can be
         * mostly dummy.
@@ -217,6 +219,16 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
                clause = flatten_join_alias_vars(root, clause);
        check_ungrouped_columns(clause, pstate,
                                                        groupClauses, have_non_var_grouping);
+
+       /*
+        * Per spec, aggregates can't appear in a recursive term.
+        */
+       if (pstate->p_hasAggs && hasSelfRefRTEs)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_RECURSION),
+                                errmsg("aggregates not allowed in a recursive query's recursive term"),
+                                parser_errposition(pstate,
+                                                                       locate_agg_of_level((Node *) qry, 0))));
 }
 
 
index 2f547f29be66ed2662af11a67fb51d093add3d53..dbf17759617210ec4fc1f6885fddc44646228736 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.179 2008/09/01 20:42:44 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.180 2008/10/04 21:56:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -54,6 +54,8 @@ static Node *transformJoinOnClause(ParseState *pstate, JoinExpr *j,
                                          List *relnamespace,
                                          Relids containedRels);
 static RangeTblEntry *transformTableEntry(ParseState *pstate, RangeVar *r);
+static RangeTblEntry *transformCTEReference(ParseState *pstate, RangeVar *r,
+                                         CommonTableExpr *cte, Index levelsup);
 static RangeTblEntry *transformRangeSubselect(ParseState *pstate,
                                                RangeSubselect *r);
 static RangeTblEntry *transformRangeFunction(ParseState *pstate,
@@ -422,6 +424,20 @@ transformTableEntry(ParseState *pstate, RangeVar *r)
        return rte;
 }
 
+/*
+ * transformCTEReference --- transform a RangeVar that references a common
+ * table expression (ie, a sub-SELECT defined in a WITH clause)
+ */
+static RangeTblEntry *
+transformCTEReference(ParseState *pstate, RangeVar *r,
+                                         CommonTableExpr *cte, Index levelsup)
+{
+       RangeTblEntry *rte;
+
+       rte = addRangeTableEntryForCTE(pstate, cte, levelsup, r->alias, true);
+
+       return rte;
+}
 
 /*
  * transformRangeSubselect --- transform a sub-SELECT appearing in FROM
@@ -609,12 +625,46 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 {
        if (IsA(n, RangeVar))
        {
-               /* Plain relation reference */
+               /* Plain relation reference, or perhaps a CTE reference */
+               RangeVar *rv = (RangeVar *) n;