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 ed4b9d9f775b92f41a6448dc042b1d44127991e4..368d5bd25991f2987ee45eaabfd0469fe9e14914 100644 (file)
 <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 0d237abc7e2ea2e087811bfa05400b1b9e95a290..79b14316c3c5182cb7dab825a56ecc5aa54c4480 100644 (file)
    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 a1be7cb786b0b8bdb4a4dc6668c9a7c6862c7b40..18ae4321ec385a076799b49116a46e200bc66be7 100644 (file)
@@ -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 2458d6fd3136a84a9c335d472167aff5f96df558..0508d91fcaf5ac0fc2daebe21199dfda2651da15 100644 (file)
@@ -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 0e4e63483d5b998ba6280e3b1ba6b44f4854480d..7e7435b58d10c8911db1f150a82a622ddf8f09b9 100644 (file)
@@ -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 7fd39e7f33d7eb2d924e86f18a33e2b589590567..73da5ec0e49c2c242df6591d4e99ca3f9f37cc10 100644 (file)
@@ -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 e48e1f4707016db4fed9393f6385981cffc03a8b..d812c5aec5cbc01e44441e8732cc2f3351fef32e 100644 (file)
@@ -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 64e44758b71c16529a43ee4557842742d7dc44c6..ab5a90474680ae64ade20c5498db3d6498291182 100644 (file)
@@ -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 6e83d16724af2374f37ef89e10a65eeff11c353e..86b815874883b700f231038314fe7b52fe85e6dd 100644 (file)
@@ -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..fdcb6f4
--- /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$
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#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..28e6b43
--- /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$
+ *
+ *-------------------------------------------------------------------------
+ */
+#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 6b0c888ed70e44c88fe90fe08cf449f21197d2db..d52919786a4e8a4065125ec70c4575c55b793f53 100644 (file)
@@ -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..f2c6273
--- /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$
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#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 0586eb3dff0b2c85157c31e381fd5b903ee10566..bb5072118f68442ac4d3afb2d5c92fb180aef9ef 100644 (file)
@@ -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 6763086067b12ddfcff1dd5deb52499be9eb6cdb..7385fd52c4a7acaae3e316f690072fee72d42c6a 100644 (file)
@@ -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 60ad41e730d9ba38454bc7cb5c04632be63281eb..da8d4fd00df74529f90c8ec8351e1a2105c6e532 100644 (file)
@@ -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 b7e5186585ad541266eab0ad76e43c50663fdd5a..948741652934572234d5d5225deeb86c2a5a53ca 100644 (file)
@@ -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 5c35a5b68756c6a2433e261628bc26d34ef6325f..d49c97867b8ea3ec884e607f1148b81148493de9 100644 (file)
@@ -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 58a173aedefdce4049c92bc2af0457dfa33aced9..531665ecd9104915d336343148def98510eb851c 100644 (file)
@@ -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 93bd42c291bc49f254e535c6346e6948a8412b4a..d096386c86e5c1bf594361f99ef15a377d4b6c3c 100644 (file)
@@ -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 d774b09187b7150c32b0367766c2ff41a0dce065..28e6fd1b7048a8b67909081e69ee40aa21cf93d1 100644 (file)
@@ -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 ed38ed91c37b64fbeca440658a80d5a78862d07d..694ef8b5dd4bae9a126050a84f28a81814042053 100644 (file)
@@ -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 8db8b812809c0ba75d37f545cdf141fbaad45525..447534a5c5afd182f32411b56c07391730303eb9 100644 (file)
@@ -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 f53f482138ee8c713dd9202ea0c185b8889b0441..b97f2882fd6ee44c2ace36d9716153316d3b00e7 100644 (file)
@@ -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 99894b7b8803b05f87e8c9dba9569a3eacce7a00..def3fd2ef3b361293c777bc668707a056484d351 100644 (file)
@@ -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 d5e4af36344e5e817af9ea3705a23b7b901e228f..2772e2b07e26f539d7197f843c89dad4ae43e4ca 100644 (file)
@@ -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 d28c66e23a9eca76383ff1b8b312bfc5eb15feb6..d6ebe481e972f19c4b5b07ee9df71398402092bd 100644 (file)
@@ -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 455957520007cb89d77147184d07107e6e12dfdd..3b074fbeddd1fc7fad069c39a3249a98d492f330 100644 (file)
@@ -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 36b08fad2b1f08d47f51ac269418a1d3bf4bd0e2..840d28bad2eb0fa5ba0b3dd107353b388e83f927 100644 (file)
@@ -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 b6be274a44597c1660d5c1ad29af11dd7cf96dfd..1179e3a3137ed97d064b748f1db5ce17af1c7429 100644 (file)
@@ -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 853ae4b91333c2700e58cdebe94739e50de70a0f..e59420e9461cd60992f91ba362e986f4c0d8d051 100644 (file)
@@ -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 0b00d3e525b07f2003e098bfab82743f4198a3f9..a6cf33c0ad3ef3cc68efb2786c5627238381b00d 100644 (file)
@@ -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 cebccf84ca487be0089153c35a5f6d90e985c0d6..32be4d169858fb49f32093ef5e494e8aa43fcfa3 100644 (file)
@@ -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 2d6eccf4c6b31add994cd29a9f08a39dc877092d..1e5ac944bf9ddcf997413fddb854fa90dd2a47a0 100644 (file)
@@ -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 b606a5225a7cb4c7957d20830a8f478107754aee..c3fad8d728b860e283499050c7a12c0ae5b08555 100644 (file)
@@ -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 0831a9472590dc4a1f57824d2140ae12b86aa201..4fcdb05ff40dcdd87ef52167fff086293e9dfa05 100644 (file)
@@ -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 e2fe4bf0003835412c4d79a1fdc1d91a1185a9fd..ec68fb8d1391d2f4cedf0d1a3557f759e72d880e 100644 (file)
@@ -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 47748bb50b0b53351d99ac678b62d6399dd9a470..8a628bfc305076c3a38bf996d33dadeccb96a2fc 100644 (file)
@@ -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 6b81378f4a97c9e9b27bb66ad2c6b4216d43efc2..a1a79bf6c81fc588ee5371fd6ca37b3fdb9832eb 100644 (file)
@@ -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;
                RangeTblRef *rtr;
-               RangeTblEntry *rte;
+               RangeTblEntry *rte = NULL;
                int                     rtindex;
 
-               rte = transformTableEntry(pstate, (RangeVar *) n);
+               /*
+                * If it is an unqualified name, it might be a reference to some
+                * CTE visible in this or a parent query.
+                */
+               if (!rv->schemaname)
+               {
+                       ParseState *ps;
+                       Index   levelsup;
+
+                       for (ps = pstate, levelsup = 0;
+                                ps != NULL;
+                                ps = ps->parentParseState, levelsup++)
+                       {
+                               ListCell *lc;
+
+                               foreach(lc, ps->p_ctenamespace)
+                               {
+                                       CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+
+                                       if (strcmp(rv->relname, cte->ctename) == 0)
+                                       {
+                                               rte = transformCTEReference(pstate, rv, cte, levelsup);
+                                               break;
+                                       }
+                               }
+                               if (rte)
+                                       break;
+                       }
+               }
+
+               /* if not found as a CTE, must be a table reference */
+               if (!rte)
+                       rte = transformTableEntry(pstate, rv);
+
                /* assume new rte is at end */
                rtindex = list_length(pstate->p_rtable);
                Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
diff --git a/src/backend/parser/parse_cte.c b/src/backend/parser/parse_cte.c
new file mode 100644 (file)
index 0000000..b38441b
--- /dev/null
@@ -0,0 +1,899 @@
+/*-------------------------------------------------------------------------
+ *
+ * parse_cte.c
+ *       handle CTEs (common table expressions) in parser
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *       $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/nodeFuncs.h"
+#include "parser/analyze.h"
+#include "parser/parse_cte.h"
+#include "utils/builtins.h"
+
+
+/* Enumeration of contexts in which a self-reference is disallowed */
+typedef enum
+{
+       RECURSION_OK,
+       RECURSION_NONRECURSIVETERM,     /* inside the left-hand term */
+       RECURSION_SUBLINK,                      /* inside a sublink */
+       RECURSION_OUTERJOIN,            /* inside nullable side of an outer join */
+       RECURSION_INTERSECT,            /* underneath INTERSECT (ALL) */
+       RECURSION_EXCEPT                        /* underneath EXCEPT (ALL) */
+} RecursionContext;
+
+/* Associated error messages --- each must have one %s for CTE name */
+static const char * const recursion_errormsgs[] = {
+       /* RECURSION_OK */
+       NULL,
+       /* RECURSION_NONRECURSIVETERM */
+       gettext_noop("recursive reference to query \"%s\" must not appear within its non-recursive term"),
+       /* RECURSION_SUBLINK */
+       gettext_noop("recursive reference to query \"%s\" must not appear within a subquery"),
+       /* RECURSION_OUTERJOIN */
+       gettext_noop("recursive reference to query \"%s\" must not appear within an outer join"),
+       /* RECURSION_INTERSECT */
+       gettext_noop("recursive reference to query \"%s\" must not appear within INTERSECT"),
+       /* RECURSION_EXCEPT */
+       gettext_noop("recursive reference to query \"%s\" must not appear within EXCEPT")
+};
+
+/*
+ * For WITH RECURSIVE, we have to find an ordering of the clause members
+ * with no forward references, and determine which members are recursive
+ * (i.e., self-referential).  It is convenient to do this with an array
+ * of CteItems instead of a list of CommonTableExprs.
+ */
+typedef struct CteItem
+{
+       CommonTableExpr *cte;                   /* One CTE to examine */
+       int                     id;                                     /* Its ID number for dependencies */
+       Node       *non_recursive_term; /* Its nonrecursive part, if identified */
+       Bitmapset  *depends_on;                 /* CTEs depended on (not including self) */
+} CteItem;
+
+/* CteState is what we need to pass around in the tree walkers */
+typedef struct CteState
+{
+       /* global state: */
+       ParseState *pstate;                     /* global parse state */
+       CteItem    *items;                      /* array of CTEs and extra data */
+       int                     numitems;               /* number of CTEs */
+       /* working state during a tree walk: */
+       int                     curitem;                /* index of item currently being examined */
+       List       *innerwiths;         /* list of lists of CommonTableExpr */
+       /* working state for checkWellFormedRecursion walk only: */
+       int                     selfrefcount;   /* number of self-references detected */
+       RecursionContext context;       /* context to allow or disallow self-ref */
+} CteState;
+
+
+static void analyzeCTE(ParseState *pstate, CommonTableExpr *cte);
+static void analyzeCTETargetList(ParseState *pstate, CommonTableExpr *cte, List *tlist);
+
+/* Dependency processing functions */
+static void makeDependencyGraph(CteState *cstate);
+static bool makeDependencyGraphWalker(Node *node, CteState *cstate);
+static void TopologicalSort(ParseState *pstate, CteItem *items, int numitems);
+
+/* Recursion validity checker functions */
+static void checkWellFormedRecursion(CteState *cstate);
+static bool checkWellFormedRecursionWalker(Node *node, CteState *cstate);
+static void checkWellFormedSelectStmt(SelectStmt *stmt, CteState *cstate);
+
+
+/*
+ * transformWithClause -
+ *    Transform the list of WITH clause "common table expressions" into
+ *    Query nodes.
+ *
+ * The result is the list of transformed CTEs to be put into the output
+ * Query.  (This is in fact the same as the ending value of p_ctenamespace,
+ * but it seems cleaner to not expose that in the function's API.)
+ */
+List *
+transformWithClause(ParseState *pstate, WithClause *withClause)
+{
+       ListCell   *lc;
+
+       /* Only one WITH clause per query level */
+       Assert(pstate->p_ctenamespace == NIL);
+
+       /*
+        * For either type of WITH, there must not be duplicate CTE names in
+        * the list.  Check this right away so we needn't worry later.
+        *
+        * Also, tentatively mark each CTE as non-recursive, and initialize
+        * its reference count to zero.
+        */
+       foreach(lc, withClause->ctes)
+       {
+               CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+               ListCell   *rest;
+
+               for_each_cell(rest, lnext(lc))
+               {
+                       CommonTableExpr *cte2 = (CommonTableExpr *) lfirst(rest);
+
+                       if (strcmp(cte->ctename, cte2->ctename) == 0)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_DUPLICATE_ALIAS),
+                                                errmsg("WITH query name \"%s\" specified more than once",
+                                                               cte2->ctename),
+                                                parser_errposition(pstate, cte2->location)));
+               }
+
+               cte->cterecursive = false;
+               cte->cterefcount = 0;
+       }
+
+       if (withClause->recursive)
+       {
+               /*
+                * For WITH RECURSIVE, we rearrange the list elements if needed
+                * to eliminate forward references.  First, build a work array
+                * and set up the data structure needed by the tree walkers.
+                */
+               CteState cstate;
+               int             i;
+
+               cstate.pstate = pstate;
+               cstate.numitems = list_length(withClause->ctes);
+               cstate.items = (CteItem *) palloc0(cstate.numitems * sizeof(CteItem));
+               i = 0;
+               foreach(lc, withClause->ctes)
+               {
+                       cstate.items[i].cte = (CommonTableExpr *) lfirst(lc);
+                       cstate.items[i].id = i;
+                       i++;
+               }
+
+               /*
+                * Find all the dependencies and sort the CteItems into a safe
+                * processing order.  Also, mark CTEs that contain self-references.
+                */
+               makeDependencyGraph(&cstate);
+
+               /*
+                * Check that recursive queries are well-formed.
+                */
+               checkWellFormedRecursion(&cstate);
+
+               /*
+                * Set up the ctenamespace for parse analysis.  Per spec, all
+                * the WITH items are visible to all others, so stuff them all in
+                * before parse analysis.  We build the list in safe processing
+                * order so that the planner can process the queries in sequence.
+                */
+               for (i = 0; i < cstate.numitems; i++)
+               {
+                       CommonTableExpr *cte = cstate.items[i].cte;
+
+                       pstate->p_ctenamespace = lappend(pstate->p_ctenamespace, cte);
+               }
+
+               /*
+                * Do parse analysis in the order determined by the topological sort.
+                */
+               for (i = 0; i < cstate.numitems; i++)
+               {
+                       CommonTableExpr *cte = cstate.items[i].cte;
+
+                       /*
+                        * If it's recursive, we have to do a throwaway parse analysis
+                        * of the non-recursive term in order to determine the set of
+                        * output columns for the recursive CTE.
+                        */
+                       if (cte->cterecursive)
+                       {
+                               Node   *nrt;
+                               Query  *nrq;
+
+                               if (!cstate.items[i].non_recursive_term)
+                                       elog(ERROR, "could not find non-recursive term for %s",
+                                                cte->ctename);
+                               /* copy the term to be sure we don't modify original query */
+                               nrt = copyObject(cstate.items[i].non_recursive_term);
+                               nrq = parse_sub_analyze(nrt, pstate);
+                               analyzeCTETargetList(pstate, cte, nrq->targetList);
+                       }
+
+                       analyzeCTE(pstate, cte);
+               }
+       }
+       else
+       {
+               /*
+                * For non-recursive WITH, just analyze each CTE in sequence and then
+                * add it to the ctenamespace.  This corresponds to the spec's
+                * definition of the scope of each WITH name.
+                */
+               foreach(lc, withClause->ctes)
+               {
+                       CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+
+                       analyzeCTE(pstate, cte);
+                       pstate->p_ctenamespace = lappend(pstate->p_ctenamespace, cte);
+               }
+       }
+
+       return pstate->p_ctenamespace;
+}
+
+
+/*
+ * Perform the actual parse analysis transformation of one CTE.  All
+ * CTEs it depends on have already been loaded into pstate->p_ctenamespace,
+ * and have been marked with the correct output column names/types.
+ */
+static void
+analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
+{
+       Query  *query;
+
+       /* Analysis not done already */
+       Assert(IsA(cte->ctequery, SelectStmt));
+
+       query = parse_sub_analyze(cte->ctequery, pstate);
+       cte->ctequery = (Node *) query;
+
+       /*
+        * Check that we got something reasonable.      Many of these conditions are
+        * impossible given restrictions of the grammar, but check 'em anyway.
+        * (These are the same checks as in transformRangeSubselect.)
+        */
+       if (!IsA(query, Query) ||
+               query->commandType != CMD_SELECT ||
+               query->utilityStmt != NULL)
+               elog(ERROR, "unexpected non-SELECT command in subquery in WITH");
+       if (query->intoClause)
+               ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg("subquery in WITH cannot have SELECT INTO"),
+                                parser_errposition(pstate,
+                                                                       exprLocation((Node *) query->intoClause))));
+
+       if (!cte->cterecursive)
+       {
+               /* Compute the output column names/types if not done yet */
+               analyzeCTETargetList(pstate, cte, query->targetList);
+       }
+       else
+       {
+               /*
+                * Verify that the previously determined output column types match
+                * what the query really produced.  We have to check this because
+                * the recursive term could have overridden the non-recursive term,
+                * and we don't have any easy way to fix that.
+                */
+               ListCell   *lctlist,
+                                  *lctyp,
+                                  *lctypmod;
+               int                     varattno;
+
+               lctyp = list_head(cte->ctecoltypes);
+               lctypmod = list_head(cte->ctecoltypmods);
+               varattno = 0;
+               foreach(lctlist, query->targetList)
+               {
+                       TargetEntry *te = (TargetEntry *) lfirst(lctlist);
+                       Node   *texpr;
+
+                       if (te->resjunk)
+                               continue;
+                       varattno++;
+                       Assert(varattno == te->resno);
+                       if (lctyp == NULL || lctypmod == NULL)          /* shouldn't happen */
+                               elog(ERROR, "wrong number of output columns in WITH");
+                       texpr = (Node *) te->expr;
+                       if (exprType(texpr) != lfirst_oid(lctyp) ||
+                               exprTypmod(texpr) != lfirst_int(lctypmod))
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                errmsg("recursive query \"%s\" column %d has type %s in non-recursive term but type %s overall",
+                                                               cte->ctename, varattno,
+                                                               format_type_with_typemod(lfirst_oid(lctyp),
+                                                                                                                lfirst_int(lctypmod)),
+                                                               format_type_with_typemod(exprType(texpr),
+                                                                                                                exprTypmod(texpr))),
+                                                errhint("Cast the output of the non-recursive term to the correct type."),
+                                                parser_errposition(pstate, exprLocation(texpr))));
+                       lctyp = lnext(lctyp);
+                       lctypmod = lnext(lctypmod);
+               }
+               if (lctyp != NULL || lctypmod != NULL)          /* shouldn't happen */
+                       elog(ERROR, "wrong number of output columns in WITH");
+       }
+}
+
+/*
+ * Compute derived fields of a CTE, given the transformed output targetlist
+ */
+static void
+analyzeCTETargetList(ParseState *pstate, CommonTableExpr *cte, List *tlist)
+{
+       int                     numaliases;
+       int                     varattno;
+       ListCell   *tlistitem;
+
+       /*
+        * We need to determine column names and types.  The alias column names
+        * override anything coming from the query itself.  (Note: the SQL spec
+        * says that the alias list must be empty or exactly as long as the
+        * output column set; but we allow it to be shorter for consistency
+        * with Alias handling.)
+        */
+       cte->ctecolnames = copyObject(cte->aliascolnames);
+       cte->ctecoltypes = cte->ctecoltypmods = NIL;
+       numaliases = list_length(cte->aliascolnames);
+       varattno = 0;
+       foreach(tlistitem, tlist)
+       {
+               TargetEntry *te = (TargetEntry *) lfirst(tlistitem);
+
+               if (te->resjunk)
+                       continue;
+               varattno++;
+               Assert(varattno == te->resno);
+               if (varattno > numaliases)
+               {
+                       char       *attrname;
+
+                       attrname = pstrdup(te->resname);
+                       cte->ctecolnames = lappend(cte->ctecolnames, makeString(attrname));
+               }
+               cte->ctecoltypes = lappend_oid(cte->ctecoltypes,
+                                                                          exprType((Node *) te->expr));
+               cte->ctecoltypmods = lappend_int(cte->ctecoltypmods,
+                                                                                exprTypmod((Node *) te->expr));
+       }
+       if (varattno < numaliases)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+                                errmsg("WITH query \"%s\" has %d columns available but %d columns specified",
+                                               cte->ctename, varattno, numaliases),
+                                parser_errposition(pstate, cte->location)));
+}
+
+
+/*
+ * Identify the cross-references of a list of WITH RECURSIVE items,
+ * and sort into an order that has no forward references.
+ */
+static void
+makeDependencyGraph(CteState *cstate)
+{
+       int                     i;
+
+       for (i = 0; i < cstate->numitems; i++)
+       {
+               CommonTableExpr *cte = cstate->items[i].cte;
+
+               cstate->curitem = i;
+               cstate->innerwiths = NIL;
+               makeDependencyGraphWalker((Node *) cte->ctequery, cstate);
+               Assert(cstate->innerwiths == NIL);
+       }
+
+       TopologicalSort(cstate->pstate, cstate->items, cstate->numitems);
+}
+
+/*
+ * Tree walker function to detect cross-references and self-references of the
+ * CTEs in a WITH RECURSIVE list.
+ */
+static bool
+makeDependencyGraphWalker(Node *node, CteState *cstate)
+{
+       if (node == NULL)
+               return false;
+       if (IsA(node, RangeVar))
+       {
+               RangeVar   *rv = (RangeVar *) node;
+
+               /* If unqualified name, might be a CTE reference */
+               if (!rv->schemaname)
+               {
+                       ListCell *lc;
+                       int             i;
+
+                       /* ... but first see if it's captured by an inner WITH */
+                       foreach(lc, cstate->innerwiths)
+                       {
+                               List   *withlist = (List *) lfirst(lc);
+                               ListCell *lc2;
+
+                               foreach(lc2, withlist)
+                               {
+                                       CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc2);
+
+                                       if (strcmp(rv->relname, cte->ctename) == 0)
+                                               return false;                           /* yes, so bail out */
+                               }
+                       }
+
+                       /* No, could be a reference to the query level we are working on */
+                       for (i = 0; i < cstate->numitems; i++)
+                       {
+                               CommonTableExpr *cte = cstate->items[i].cte;
+
+                               if (strcmp(rv->relname, cte->ctename) == 0)
+                               {
+                                       int             myindex = cstate->curitem;
+
+                                       if (i != myindex)
+                                       {
+                                               /* Add cross-item dependency */
+                                               cstate->items[myindex].depends_on =
+                                                       bms_add_member(cstate->items[myindex].depends_on,
+                                                                                  cstate->items[i].id);
+                                       }
+                                       else
+                                       {
+                                               /* Found out this one is self-referential */
+                                               cte->cterecursive = true;
+                                       }
+                                       break;
+                               }
+                       }
+               }
+               return false;
+       }
+       if (IsA(node, SelectStmt))
+       {
+               SelectStmt *stmt = (SelectStmt *) node;
+               ListCell *lc;
+
+               if (stmt->withClause)
+               {
+                       if (stmt->withClause->recursive)
+                       {
+                               /*
+                                * In the RECURSIVE case, all query names of the WITH are
+                                * visible to all WITH items as well as the main query.
+                                * So push them all on, process, pop them all off.
+                                */
+                               cstate->innerwiths = lcons(stmt->withClause->ctes,
+                                                                                  cstate->innerwiths);
+                               foreach(lc, stmt->withClause->ctes)
+                               {
+                                       CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+
+                                       (void) makeDependencyGraphWalker(cte->ctequery, cstate);
+                               }
+                               (void) raw_expression_tree_walker(node,
+                                                                                                 makeDependencyGraphWalker,
+                                                                                                 (void *) cstate);
+                               cstate->innerwiths = list_delete_first(cstate->innerwiths);
+                       }
+                       else
+                       {
+                               /*
+                                * In the non-RECURSIVE case, query names are visible to
+                                * the WITH items after them and to the main query.
+                                */
+                               ListCell   *cell1;
+
+                               cstate->innerwiths = lcons(NIL, cstate->innerwiths);
+                               cell1 = list_head(cstate->innerwiths);
+                               foreach(lc, stmt->withClause->ctes)
+                               {
+                                       CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+
+                                       (void) makeDependencyGraphWalker(cte->ctequery, cstate);
+                                       lfirst(cell1) = lappend((List *) lfirst(cell1), cte);
+                               }
+                               (void) raw_expression_tree_walker(node,
+                                                                                                 makeDependencyGraphWalker,
+                                                                                                 (void *) cstate);
+                               cstate->innerwiths = list_delete_first(cstate->innerwiths);
+                       }
+                       /* We're done examining the SelectStmt */
+                       return false;
+               }
+               /* if no WITH clause, just fall through for normal processing */
+       }
+       if (IsA(node, WithClause))
+       {
+               /*
+                * Prevent raw_expression_tree_walker from recursing directly into
+                * a WITH clause.  We need that to happen only under the control
+                * of the code above.
+                */
+               return false;
+       }
+       return raw_expression_tree_walker(node,
+                                                                         makeDependencyGraphWalker,
+                                                                         (void *) cstate);
+}
+
+/*
+ * Sort by dependencies, using a standard topological sort operation
+ */
+static void
+TopologicalSort(ParseState *pstate, CteItem *items, int numitems)
+{
+       int i, j;
+
+       /* for each position in sequence ... */
+       for (i = 0; i < numitems; i++)
+       {
+               /* ... scan the remaining items to find one that has no dependencies */
+               for (j = i; j < numitems; j++)
+               {
+                       if (bms_is_empty(items[j].depends_on))
+                               break;
+               }
+
+               /* if we didn't find one, the dependency graph has a cycle */
+               if (j >= numitems)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("mutual recursion between WITH items is not implemented"),
+                                        parser_errposition(pstate, items[i].cte->location)));
+
+               /*
+                * Found one.  Move it to front and remove it from every other
+                * item's dependencies.
+                */
+               if (i != j)
+               {
+                       CteItem tmp;
+                               
+                       tmp = items[i];
+                       items[i] = items[j];
+                       items[j] = tmp;
+               }
+               /*
+                * Items up through i are known to have no dependencies left,
+                * so we can skip them in this loop.
+                */
+               for (j = i + 1; j < numitems; j++)
+               {
+                       items[j].depends_on = bms_del_member(items[j].depends_on,
+                                                                                                items[i].id);
+               }
+       }
+}
+
+
+/*
+ * Check that recursive queries are well-formed.
+ */
+static void
+checkWellFormedRecursion(CteState *cstate)
+{
+       int                     i;
+
+       for (i = 0; i < cstate->numitems; i++)
+       {
+               CommonTableExpr *cte = cstate->items[i].cte;
+               SelectStmt              *stmt = (SelectStmt *) cte->ctequery;
+
+               Assert(IsA(stmt, SelectStmt));                          /* not analyzed yet */
+
+               /* Ignore items that weren't found to be recursive */
+               if (!cte->cterecursive)
+                       continue;
+
+               /* Must have top-level UNION ALL */
+               if (stmt->op != SETOP_UNION || !stmt->all)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_RECURSION),
+                                        errmsg("recursive query \"%s\" does not have the form non-recursive-term UNION ALL recursive-term",
+                                                       cte->ctename),
+                                        parser_errposition(cstate->pstate, cte->location)));
+
+               /* The left-hand operand mustn't contain self-reference at all */
+               cstate->curitem = i;
+               cstate->innerwiths = NIL;
+               cstate->selfrefcount = 0;
+               cstate->context = RECURSION_NONRECURSIVETERM;
+               checkWellFormedRecursionWalker((Node *) stmt->larg, cstate);
+               Assert(cstate->innerwiths == NIL);
+
+               /* Right-hand operand should contain one reference in a valid place */
+               cstate->curitem = i;
+               cstate->innerwiths = NIL;
+               cstate->selfrefcount = 0;
+               cstate->context = RECURSION_OK;
+               checkWellFormedRecursionWalker((Node *) stmt->rarg, cstate);
+               Assert(cstate->innerwiths == NIL);
+               if (cstate->selfrefcount != 1)                  /* shouldn't happen */
+                       elog(ERROR, "missing recursive reference");
+
+               /*
+                * Disallow ORDER BY and similar decoration atop the UNION ALL.
+                * These don't make sense because it's impossible to figure out what
+                * they mean when we have only part of the recursive query's results.
+                * (If we did allow them, we'd have to check for recursive references
+                * inside these subtrees.)
+                */
+               if (stmt->sortClause)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("ORDER BY in a recursive query is not implemented"),
+                                        parser_errposition(cstate->pstate,
+                                                                               exprLocation((Node *) stmt->sortClause))));
+               if (stmt->limitOffset)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("OFFSET in a recursive query is not implemented"),
+                                        parser_errposition(cstate->pstate,
+                                                                               exprLocation(stmt->limitOffset))));
+               if (stmt->limitCount)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("LIMIT in a recursive query is not implemented"),
+                                        parser_errposition(cstate->pstate,
+                                                                               exprLocation(stmt->limitCount))));
+               if (stmt->lockingClause)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("FOR UPDATE/SHARE in a recursive query is not implemented"),
+                                        parser_errposition(cstate->pstate,
+                                                                               exprLocation((Node *) stmt->lockingClause))));
+
+               /*
+                * Save non_recursive_term.
+                */
+               cstate->items[i].non_recursive_term = (Node *) stmt->larg;
+       }
+}
+
+/*
+ * Tree walker function to detect invalid self-references in a recursive query.
+ */
+static bool
+checkWellFormedRecursionWalker(Node *node, CteState *cstate)
+{
+       RecursionContext save_context = cstate->context;
+
+       if (node == NULL)
+               return false;
+       if (IsA(node, RangeVar))
+       {
+               RangeVar   *rv = (RangeVar *) node;
+
+               /* If unqualified name, might be a CTE reference */
+               if (!rv->schemaname)
+               {
+                       ListCell *lc;
+                       CommonTableExpr *mycte;
+
+                       /* ... but first see if it's captured by an inner WITH */
+                       foreach(lc, cstate->innerwiths)
+                       {
+                               List   *withlist = (List *) lfirst(lc);
+                               ListCell *lc2;
+
+                               foreach(lc2, withlist)
+                               {
+                                       CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc2);
+
+                                       if (strcmp(rv->relname, cte->ctename) == 0)
+                                               return false;                           /* yes, so bail out */
+                               }
+                       }
+
+                       /* No, could be a reference to the query level we are working on */
+                       mycte = cstate->items[cstate->curitem].cte;
+                       if (strcmp(rv->relname, mycte->ctename) == 0)
+                       {
+                               /* Found a recursive reference to the active query */
+                               if (cstate->context != RECURSION_OK)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_INVALID_RECURSION),
+                                                        errmsg(recursion_errormsgs[cstate->context],
+                                                                       mycte->ctename),
+                                                        parser_errposition(cstate->pstate,
+                                                                                               rv->location)));
+                               /* Count references */
+                               if (++(cstate->selfrefcount) > 1)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_INVALID_RECURSION),
+                                                        errmsg("recursive reference to query \"%s\" must not appear more than once",
+                                                                       mycte->ctename),
+                                                        parser_errposition(cstate->pstate,
+                                                                                               rv->location)));
+                       }
+               }
+               return false;
+       }
+       if (IsA(node, SelectStmt))
+       {
+               SelectStmt *stmt = (SelectStmt *) node;
+               ListCell *lc;
+
+               if (stmt->withClause)
+               {
+                       if (stmt->withClause->recursive)
+                       {
+                               /*
+                                * In the RECURSIVE case, all query names of the WITH are
+                                * visible to all WITH items as well as the main query.
+                                * So push them all on, process, pop them all off.
+                                */
+                               cstate->innerwiths = lcons(stmt->withClause->ctes,
+                                                                                  cstate->innerwiths);
+                               foreach(lc, stmt->withClause->ctes)
+                               {
+                                       CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+
+                                       (void) checkWellFormedRecursionWalker(cte->ctequery, cstate);
+                               }
+                               checkWellFormedSelectStmt(stmt, cstate);
+                               cstate->innerwiths = list_delete_first(cstate->innerwiths);
+                       }
+                       else
+                       {
+                               /*
+                                * In the non-RECURSIVE case, query names are visible to
+                                * the WITH items after them and to the main query.
+                                */
+                               ListCell   *cell1;
+
+                               cstate->innerwiths = lcons(NIL, cstate->innerwiths);
+                               cell1 = list_head(cstate->innerwiths);
+                               foreach(lc, stmt->withClause->ctes)
+                               {
+                                       CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+
+                                       (void) checkWellFormedRecursionWalker(cte->ctequery, cstate);
+                                       lfirst(cell1) = lappend((List *) lfirst(cell1), cte);
+                               }
+                               checkWellFormedSelectStmt(stmt, cstate);
+                               cstate->innerwiths = list_delete_first(cstate->innerwiths);
+                       }
+               }
+               else
+                               checkWellFormedSelectStmt(stmt, cstate);
+               /* We're done examining the SelectStmt */
+               return false;
+       }
+       if (IsA(node, WithClause))
+       {
+               /*
+                * Prevent raw_expression_tree_walker from recursing directly into
+                * a WITH clause.  We need that to happen only under the control
+                * of the code above.
+                */
+               return false;
+       }
+       if (IsA(node, JoinExpr))
+       {
+               JoinExpr *j = (JoinExpr *) node;
+
+               switch (j->jointype)
+               {
+                       case JOIN_INNER:
+                               checkWellFormedRecursionWalker(j->larg, cstate);
+                               checkWellFormedRecursionWalker(j->rarg, cstate);
+                               checkWellFormedRecursionWalker(j->quals, cstate);
+                               break;
+                       case JOIN_LEFT:
+                               checkWellFormedRecursionWalker(j->larg, cstate);
+                               if (save_context == RECURSION_OK)
+                                       cstate->context = RECURSION_OUTERJOIN;
+                               checkWellFormedRecursionWalker(j->rarg, cstate);
+                               cstate->context = save_context;
+                               checkWellFormedRecursionWalker(j->quals, cstate);
+                               break;
+                       case JOIN_FULL:
+                               if (save_context == RECURSION_OK)
+                                       cstate->context = RECURSION_OUTERJOIN;
+                               checkWellFormedRecursionWalker(j->larg, cstate);
+                               checkWellFormedRecursionWalker(j->rarg, cstate);
+                               cstate->context = save_context;
+                               checkWellFormedRecursionWalker(j->quals, cstate);
+                               break;
+                       case JOIN_RIGHT:
+                               if (save_context == RECURSION_OK)
+                                       cstate->context = RECURSION_OUTERJOIN;
+                               checkWellFormedRecursionWalker(j->larg, cstate);
+                               cstate->context = save_context;
+                               checkWellFormedRecursionWalker(j->rarg, cstate);
+                               checkWellFormedRecursionWalker(j->quals, cstate);
+                               break;
+                       default:
+                               elog(ERROR, "unrecognized join type: %d",
+                                        (int) j->jointype);
+               }
+               return false;
+       }
+       if (IsA(node, SubLink))
+       {
+               SubLink *sl = (SubLink *) node;
+
+               /*
+                * we intentionally override outer context, since subquery is
+                * independent
+                */
+               cstate->context = RECURSION_SUBLINK;
+               checkWellFormedRecursionWalker(sl->subselect, cstate);
+               cstate->context = save_context;
+               checkWellFormedRecursionWalker(sl->testexpr, cstate);
+               return false;
+       }
+       return raw_expression_tree_walker(node,
+                                                                         checkWellFormedRecursionWalker,
+                                                                         (void *) cstate);
+}
+
+/*
+ * subroutine for checkWellFormedRecursionWalker: process a SelectStmt
+ * without worrying about its WITH clause
+ */
+static void
+checkWellFormedSelectStmt(SelectStmt *stmt, CteState *cstate)
+{
+       RecursionContext save_context = cstate->context;
+
+       if (save_context != RECURSION_OK)
+       {
+               /* just recurse without changing state */
+               raw_expression_tree_walker((Node *) stmt,
+                                                                  checkWellFormedRecursionWalker,
+                                                                  (void *) cstate);
+       }
+       else
+       {
+               switch (stmt->op)
+               {
+                       case SETOP_NONE:
+                       case SETOP_UNION:
+                               raw_expression_tree_walker((Node *) stmt,
+                                                                                  checkWellFormedRecursionWalker,
+                                                                                  (void *) cstate);
+                               break;
+                       case SETOP_INTERSECT:
+                               if (stmt->all)
+                                       cstate->context = RECURSION_INTERSECT;
+                               checkWellFormedRecursionWalker((Node *) stmt->larg,
+                                                                                          cstate);
+                               checkWellFormedRecursionWalker((Node *) stmt->rarg,
+                                                                                          cstate);
+                               cstate->context = save_context;
+                               checkWellFormedRecursionWalker((Node *) stmt->sortClause,
+                                                                                          cstate);
+                               checkWellFormedRecursionWalker((Node *) stmt->limitOffset,
+                                                                                          cstate);
+                               checkWellFormedRecursionWalker((Node *) stmt->limitCount,
+                                                                                          cstate);
+                               checkWellFormedRecursionWalker((Node *) stmt->lockingClause,
+                                                                                          cstate);
+                               break;
+                               break;
+                       case SETOP_EXCEPT:
+                               if (stmt->all)
+                                       cstate->context = RECURSION_EXCEPT;
+                               checkWellFormedRecursionWalker((Node *) stmt->larg,
+                                                                                          cstate);
+                               cstate->context = RECURSION_EXCEPT;
+                               checkWellFormedRecursionWalker((Node *) stmt->rarg,
+                                                                                          cstate);
+                               cstate->context = save_context;
+                               checkWellFormedRecursionWalker((Node *) stmt->sortClause,
+                                                                                          cstate);
+                               checkWellFormedRecursionWalker((Node *) stmt->limitOffset,
+                                                                                          cstate);
+                               checkWellFormedRecursionWalker((Node *) stmt->limitCount,
+                                                                                          cstate);
+                               checkWellFormedRecursionWalker((Node *) stmt->lockingClause,
+                                                                                          cstate);
+                               break;
+                       default:
+                               elog(ERROR, "unrecognized set op: %d",
+                                        (int) stmt->op);
+               }
+       }
+}
index 9141ea187cf81e1e29182ae3a81c0639bc1bb729..16a7cb74203a3653cec7e228d6186387e21fa727 100644 (file)
@@ -318,6 +318,35 @@ GetRTEByRangeTablePosn(ParseState *pstate,
        return rt_fetch(varno, pstate->p_rtable);
 }
 
+/*
+ * Fetch the CTE for a CTE-reference RTE.
+ */
+CommonTableExpr *
+GetCTEForRTE(ParseState *pstate, RangeTblEntry *rte)
+{
+       Index           levelsup;
+       ListCell   *lc;
+
+       Assert(rte->rtekind == RTE_CTE);
+       levelsup = rte->ctelevelsup;
+       while (levelsup-- > 0)
+       {
+               pstate = pstate->parentParseState;
+               if (!pstate)                    /* shouldn't happen */
+                       elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename);
+       }
+       foreach(lc, pstate->p_ctenamespace)
+       {
+               CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+
+               if (strcmp(cte->ctename, rte->ctename) == 0)
+                       return cte;
+       }
+       /* shouldn't happen */
+       elog(ERROR, "could not find CTE \"%s\"", rte->ctename);
+       return NULL;                            /* keep compiler quiet */
+}
+
 /*
  * scanRTEForColumn
  *       Search the column names of a single RTE for the given name.
@@ -1108,6 +1137,88 @@ addRangeTableEntryForJoin(ParseState *pstate,
        return rte;
 }
 
+/*
+ * Add an entry for a CTE reference to the pstate's range table (p_rtable).
+ *
+ * This is much like addRangeTableEntry() except that it makes a CTE RTE.
+ */
+RangeTblEntry *
+addRangeTableEntryForCTE(ParseState *pstate,
+                                                CommonTableExpr *cte,
+                                                Index levelsup,
+                                                Alias *alias,
+                                                bool inFromCl)
+{
+       RangeTblEntry *rte = makeNode(RangeTblEntry);
+       char       *refname = alias ? alias->aliasname : cte->ctename;
+       Alias      *eref;
+       int                     numaliases;
+       int                     varattno;
+       ListCell   *lc;
+
+       rte->rtekind = RTE_CTE;
+       rte->ctename = cte->ctename;
+       rte->ctelevelsup = levelsup;
+
+       /* Self-reference if and only if CTE's parse analysis isn't completed */
+       rte->self_reference = !IsA(cte->ctequery, Query);
+       Assert(cte->cterecursive || !rte->self_reference);
+       /* Bump the CTE's refcount if this isn't a self-reference */
+       if (!rte->self_reference)
+               cte->cterefcount++;
+
+       rte->ctecoltypes = cte->ctecoltypes;
+       rte->ctecoltypmods = cte->ctecoltypmods;
+
+       rte->alias = alias;
+       if (alias)
+               eref = copyObject(alias);
+       else
+               eref = makeAlias(refname, NIL);
+       numaliases = list_length(eref->colnames);
+
+       /* fill in any unspecified alias columns */
+       varattno = 0;
+       foreach(lc, cte->ctecolnames)
+       {
+               varattno++;
+               if (varattno > numaliases)
+                       eref->colnames = lappend(eref->colnames, lfirst(lc));
+       }
+       if (varattno < numaliases)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+                                errmsg("table \"%s\" has %d columns available but %d columns specified",
+                                               refname, varattno, numaliases)));
+
+       rte->eref = eref;
+
+       /*----------
+        * Flags:
+        * - this RTE should be expanded to include descendant tables,
+        * - this RTE is in the FROM clause,
+        * - this RTE should be checked for appropriate access rights.
+        *
+        * Subqueries are never checked for access rights.
+        *----------
+        */
+       rte->inh = false;                       /* never true for subqueries */
+       rte->inFromCl = inFromCl;
+
+       rte->requiredPerms = 0;
+       rte->checkAsUser = InvalidOid;
+
+       /*
+        * Add completed RTE to pstate's range table list, but not to join list
+        * nor namespace --- caller must do that if appropriate.
+        */
+       if (pstate != NULL)
+               pstate->p_rtable = lappend(pstate->p_rtable, rte);
+
+       return rte;
+}
+
+
 /*
  * Has the specified refname been selected FOR UPDATE/FOR SHARE?
  *
@@ -1444,6 +1555,41 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
                                }
                        }
                        break;
+               case RTE_CTE:
+                       {
+                               ListCell   *aliasp_item = list_head(rte->eref->colnames);
+                               ListCell   *lct;
+                               ListCell   *lcm;
+
+                               varattno = 0;
+                               forboth(lct, rte->ctecoltypes, lcm, rte->ctecoltypmods)
+                               {
+                                       Oid             coltype = lfirst_oid(lct);
+                                       int32   coltypmod = lfirst_int(lcm);
+
+                                       varattno++;
+
+                                       if (colnames)
+                                       {
+                                               /* Assume there is one alias per output column */
+                                               char       *label = strVal(lfirst(aliasp_item));
+
+                                               *colnames = lappend(*colnames, makeString(pstrdup(label)));
+                                               aliasp_item = lnext(aliasp_item);
+                                       }
+
+                                       if (colvars)
+                                       {
+                                               Var                *varnode;
+
+                                               varnode = makeVar(rtindex, varattno,
+                                                                                 coltype, coltypmod,
+                                                                                 sublevels_up);
+                                               *colvars = lappend(*colvars, varnode);
+                                       }
+                               }
+                       }
+                       break;
                default:
                        elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
        }
@@ -1750,6 +1896,14 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
                                *vartypmod = exprTypmod(aliasvar);
                        }
                        break;
+               case RTE_CTE:
+                       {
+                               /* CTE RTE --- get type info from lists in the RTE */
+                               Assert(attnum > 0 && attnum <= list_length(rte->ctecoltypes));
+                               *vartype = list_nth_oid(rte->ctecoltypes, attnum - 1);
+                               *vartypmod = list_nth_int(rte->ctecoltypmods, attnum - 1);
+                       }
+                       break;
                default:
                        elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
        }
@@ -1788,7 +1942,8 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
                        break;
                case RTE_SUBQUERY:
                case RTE_VALUES:
-                       /* Subselect and Values RTEs never have dropped columns */
+               case RTE_CTE:
+                       /* Subselect, Values, CTE RTEs never have dropped columns */
                        result = false;
                        break;
                case RTE_JOIN:
index 4f5c46da820aee316890b8c579f7af0889abe384..5b8f5177a05a72b59f8ea449aeea498f156d6328 100644 (file)
@@ -296,6 +296,24 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
                case RTE_VALUES:
                        /* not a simple relation, leave it unmarked */
                        break;
+               case RTE_CTE:
+                       /* CTE reference: copy up from the subquery */
+                       if (attnum != InvalidAttrNumber)
+                       {
+                               CommonTableExpr *cte = GetCTEForRTE(pstate, rte);
+                               TargetEntry *ste;
+
+                               /* should be analyzed by now */
+                               Assert(IsA(cte->ctequery, Query));
+                               ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList,
+                                                                          attnum);
+                               if (ste == NULL || ste->resjunk)
+                                       elog(ERROR, "subquery %s does not have attribute %d",
+                                                rte->eref->aliasname, attnum);
+                               tle->resorigtbl = ste->resorigtbl;
+                               tle->resorigcol = ste->resorigcol;
+                       }
+                       break;
        }
 }
 
@@ -1176,6 +1194,44 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
                         * its result columns as RECORD, which is not allowed.
                         */
                        break;
+               case RTE_CTE:
+                       {
+                               /* CTE reference: examine subquery's output expr */
+                               CommonTableExpr *cte = GetCTEForRTE(pstate, rte);
+                               TargetEntry *ste;
+
+                               /* should be analyzed by now */
+                               Assert(IsA(cte->ctequery, Query));
+                               ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList,
+                                                                          attnum);
+                               if (ste == NULL || ste->resjunk)
+                                       elog(ERROR, "subquery %s does not have attribute %d",
+                                                rte->eref->aliasname, attnum);
+                               expr = (Node *) ste->expr;
+                               if (IsA(expr, Var))
+                               {
+                                       /*
+                                        * Recurse into the CTE to see what its Var refers to. We
+                                        * have to build an additional level of ParseState to keep
+                                        * in step with varlevelsup in the CTE; furthermore it
+                                        * could be an outer CTE.
+                                        */
+                                       ParseState      mypstate;
+                                       Index           levelsup;
+
+                                       MemSet(&mypstate, 0, sizeof(mypstate));
+                                       /* this loop must work, since GetCTEForRTE did */
+                                       for (levelsup = 0; levelsup < rte->ctelevelsup; levelsup++)
+                                               pstate = pstate->parentParseState;
+                                       mypstate.parentParseState = pstate;
+                                       mypstate.p_rtable = ((Query *) cte->ctequery)->rtable;
+                                       /* don't bother filling the rest of the fake pstate */
+
+                                       return expandRecordVariable(&mypstate, (Var *) expr, 0);
+                               }
+                               /* else fall through to inspect the expression */
+                       }
+                       break;
        }
 
        /*
index 26e53f543d843408459742b233fc2adff9a9e909..463fee715d04969d6e2c6db7ccbf7178e78fcea2 100644 (file)
@@ -611,6 +611,7 @@ parseTypeString(const char *str, Oid *type_id, int32 *typmod_p)
                stmt->whereClause != NULL ||
                stmt->groupClause != NIL ||
                stmt->havingClause != NULL ||
+               stmt->withClause != NULL ||
                stmt->valuesLists != NIL ||
                stmt->sortClause != NIL ||
                stmt->limitOffset != NULL ||
index 7f4a764ecf96e0221677f08cc2ce66cf5d1aff42..d254f5330bf644e49b839ffc163b10ba3a99d9aa 100644 (file)
@@ -635,17 +635,23 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
                        rte->checkAsUser = userid;
        }
 
+       /* Recurse into subquery-in-WITH */
+       foreach(l, qry->cteList)
+       {
+               CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
+
+               setRuleCheckAsUser_Query((Query *) cte->ctequery, userid);
+       }
+
        /* If there are sublinks, search for them and process their RTEs */
-       /* ignore subqueries in rtable because we already processed them */
        if (qry->hasSubLinks)
                query_tree_walker(qry, setRuleCheckAsUser_walker, (void *) &userid,
-                                                 QTW_IGNORE_RT_SUBQUERIES);
+                                                 QTW_IGNORE_RC_SUBQUERIES);
 }
 
 
 /*
  * Change the firing semantics of an existing rule.
- *
  */
 void
 EnableDisableRule(Relation rel, const char *rulename,
index 6369932c708f7dcfb416665fc5a9ecc817ca8314..7dd23a8ae6320c84c213a943551d9876027a57aa 100644 (file)
@@ -215,13 +215,21 @@ AcquireRewriteLocks(Query *parsetree)
                }
        }
 
+       /* Recurse into subqueries in WITH */
+       foreach(l, parsetree->cteList)
+       {
+               CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
+
+               AcquireRewriteLocks((Query *) cte->ctequery);
+       }
+
        /*
         * Recurse into sublink subqueries, too.  But we already did the ones in
-        * the rtable.
+        * the rtable and cteList.
         */
        if (parsetree->hasSubLinks)
                query_tree_walker(parsetree, acquireLocksOnSubLinks, NULL,
-                                                 QTW_IGNORE_RT_SUBQUERIES);
+                                                 QTW_IGNORE_RC_SUBQUERIES);
 }
 
 /*
@@ -1228,6 +1236,35 @@ markQueryForLocking(Query *qry, Node *jtnode, bool forUpdate, bool noWait)
                        markQueryForLocking(rte->subquery, (Node *) rte->subquery->jointree,
                                                                forUpdate, noWait);
                }
+               else if (rte->rtekind == 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 (compare
+                        * transformLockingClause).  Which simplifies life here.
+                        */
+                       CommonTableExpr *cte = NULL;
+                       ListCell   *lc;
+
+                       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")));
+                       foreach(lc, qry->cteList)
+                       {
+                               cte = (CommonTableExpr *) lfirst(lc);
+                               if (strcmp(cte->ctename, rte->ctename) == 0)
+                                       break;
+                       }
+                       if (lc == NULL)                         /* shouldn't happen */
+                               elog(ERROR, "could not find CTE \"%s\"", rte->ctename);
+                       /* should be analyzed by now */
+                       Assert(IsA(cte->ctequery, Query));
+                       markQueryForLocking((Query *) cte->ctequery,
+                                                               (Node *) ((Query *) cte->ctequery)->jointree,
+                                                               forUpdate, noWait);
+               }
        }
        else if (IsA(jtnode, FromExpr))
        {
@@ -1295,6 +1332,7 @@ static Query *
 fireRIRrules(Query *parsetree, List *activeRIRs)
 {
        int                     rt_index;
+       ListCell   *lc;
 
        /*
         * don't try to convert this into a foreach loop, because rtable list can
@@ -1407,13 +1445,22 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
                heap_close(rel, NoLock);
        }
 
+       /* Recurse into subqueries in WITH */
+       foreach(lc, parsetree->cteList)
+       {
+               CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+
+               cte->ctequery = (Node *)
+                       fireRIRrules((Query *) cte->ctequery, activeRIRs);
+       }
+
        /*
         * Recurse into sublink subqueries, too.  But we already did the ones in
-        * the rtable.
+        * the rtable and cteList.
         */
        if (parsetree->hasSubLinks)
                query_tree_walker(parsetree, fireRIRonSubLink, (void *) activeRIRs,
-                                                 QTW_IGNORE_RT_SUBQUERIES);
+                                                 QTW_IGNORE_RC_SUBQUERIES);
 
        return parsetree;
 }
index 8bb2b9d251d210e166431c4586dd0299f83149f2..75735c4bca13c67471f47ae4a8233834bca87463 100644 (file)
@@ -183,13 +183,13 @@ bool
 checkExprHasSubLink(Node *node)
 {
        /*
-        * If a Query is passed, examine it --- but we need not recurse into
-        * sub-Queries.
+        * If a Query is passed, examine it --- but we should not recurse into
+        * sub-Queries that are in its rangetable or CTE list.
         */
        return query_or_expression_tree_walker(node,
                                                                                   checkExprHasSubLink_walker,
                                                                                   NULL,
-                                                                                  QTW_IGNORE_RT_SUBQUERIES);
+                                                                                  QTW_IGNORE_RC_SUBQUERIES);
 }
 
 static bool
@@ -543,7 +543,7 @@ adjust_relid_set(Relids relids, int oldrelid, int newrelid)
  * that sublink are not affected, only outer references to vars that belong
  * to the expression's original query level or parents thereof.
  *
- * Aggref nodes are adjusted similarly.
+ * Likewise for other nodes containing levelsup fields, such as Aggref.
  *
  * NOTE: although this has the form of a walker, we cheat and modify the
  * Var nodes in-place. The given expression tree should have been copied
@@ -585,6 +585,17 @@ IncrementVarSublevelsUp_walker(Node *node,
                        agg->agglevelsup += context->delta_sublevels_up;
                /* fall through to recurse into argument */
        }
+       if (IsA(node, RangeTblEntry))
+       {
+               RangeTblEntry *rte = (RangeTblEntry *) node;
+
+               if (rte->rtekind == RTE_CTE)
+               {
+                       if (rte->ctelevelsup >= context->min_sublevels_up)
+                               rte->ctelevelsup += context->delta_sublevels_up;
+               }
+               return false;                   /* allow range_table_walker to continue */
+       }
        if (IsA(node, Query))
        {
                /* Recurse into subselects */
@@ -593,7 +604,8 @@ IncrementVarSublevelsUp_walker(Node *node,
                context->min_sublevels_up++;
                result = query_tree_walker((Query *) node,
                                                                   IncrementVarSublevelsUp_walker,
-                                                                  (void *) context, 0);
+                                                                  (void *) context,
+                                                                  QTW_EXAMINE_RTES);
                context->min_sublevels_up--;
                return result;
        }
@@ -617,7 +629,7 @@ IncrementVarSublevelsUp(Node *node, int delta_sublevels_up,
        query_or_expression_tree_walker(node,
                                                                        IncrementVarSublevelsUp_walker,
                                                                        (void *) &context,
-                                                                       0);
+                                                                       QTW_EXAMINE_RTES);
 }
 
 /*
@@ -636,7 +648,7 @@ IncrementVarSublevelsUp_rtable(List *rtable, int delta_sublevels_up,
        range_table_walker(rtable,
                                           IncrementVarSublevelsUp_walker,
                                           (void *) &context,
-                                          0);
+                                          QTW_EXAMINE_RTES);
 }
 
 
index 2da3d3aa211316012bc5a7dac35f5edde679fc6c..f756b786d9e4662f80ab3faa2c629f7c6d659d6b 100644 (file)
@@ -145,6 +145,7 @@ static void make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
 static void get_query_def(Query *query, StringInfo buf, List *parentnamespace,
                          TupleDesc resultDesc, int prettyFlags, int startIndent);
 static void get_values_def(List *values_lists, deparse_context *context);
+static void get_with_clause(Query *query, deparse_context *context);
 static void get_select_query_def(Query *query, deparse_context *context,
                                         TupleDesc resultDesc);
 static void get_insert_query_def(Query *query, deparse_context *context);
@@ -2204,6 +2205,73 @@ get_values_def(List *values_lists, deparse_context *context)
        }
 }
 
+/* ----------
+ * get_with_clause                     - Parse back a WITH clause
+ * ----------
+ */
+static void
+get_with_clause(Query *query, deparse_context *context)
+{
+       StringInfo      buf = context->buf;
+       const char      *sep;
+       ListCell        *l;
+
+       if (query->cteList == NIL)
+               return;
+
+       if (PRETTY_INDENT(context))
+       {
+               context->indentLevel += PRETTYINDENT_STD;
+               appendStringInfoChar(buf, ' ');
+       }
+
+       if (query->hasRecursive)
+               sep = "WITH RECURSIVE ";
+       else
+               sep = "WITH ";
+       foreach(l, query->cteList)
+       {
+               CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
+
+               appendStringInfoString(buf, sep);
+               appendStringInfoString(buf, quote_identifier(cte->ctename));
+               if (cte->aliascolnames)
+               {
+                       bool            first = true;
+                       ListCell   *col;
+
+                       appendStringInfoChar(buf, '(');
+                       foreach(col, cte->aliascolnames)
+                       {
+                               if (first)
+                                       first = false;
+                               else
+                                       appendStringInfoString(buf, ", ");
+                               appendStringInfoString(buf,
+                                                                          quote_identifier(strVal(lfirst(col))));
+                       }
+                       appendStringInfoChar(buf, ')');
+               }
+               appendStringInfoString(buf, " AS (");
+               if (PRETTY_INDENT(context))
+                       appendContextKeyword(context, "", 0, 0, 0);
+               get_query_def((Query *) cte->ctequery, buf, context->namespaces, NULL,
+                                         context->prettyFlags, context->indentLevel);
+               if (PRETTY_INDENT(context))
+                       appendContextKeyword(context, "", 0, 0, 0);
+               appendStringInfoChar(buf, ')');
+               sep = ", ";
+       }
+
+       if (PRETTY_INDENT(context))
+       {
+               context->indentLevel -= PRETTYINDENT_STD;
+               appendContextKeyword(context, "", 0, 0, 0);
+       }
+       else
+               appendStringInfoChar(buf, ' ');
+}
+
 /* ----------
  * get_select_query_def                        - Parse back a SELECT parsetree
  * ----------
@@ -2214,13 +2282,16 @@ get_select_query_def(Query *query, deparse_context *context,
 {
        StringInfo      buf = context->buf;
        bool            force_colno;
-       char       *sep;
+       const char *sep;
        ListCell   *l;
 
+       /* Insert the WITH clause if given */
+       get_with_clause(query, context);
+
        /*
         * If the Query node has a setOperations tree, then it's the top level of
-        * a UNION/INTERSECT/EXCEPT query; only the ORDER BY and LIMIT fields are
-        * interesting in the top query itself.
+        * a UNION/INTERSECT/EXCEPT query; only the WITH, ORDER BY and LIMIT
+        * fields are interesting in the top query itself.
         */
        if (query->setOperations)
        {
@@ -2507,8 +2578,9 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
 
                Assert(subquery != NULL);
                Assert(subquery->setOperations == NULL);
-               /* Need parens if ORDER BY, FOR UPDATE, or LIMIT; see gram.y */
-               need_paren = (subquery->sortClause ||
+               /* Need parens if WITH, ORDER BY, FOR UPDATE, or LIMIT; see gram.y */
+               need_paren = (subquery->cteList ||
+                                         subquery->sortClause ||
                                          subquery->rowMarks ||
                                          subquery->limitOffset ||
                                          subquery->limitCount);
@@ -2523,6 +2595,12 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
        {
                SetOperationStmt *op = (SetOperationStmt *) setOp;
 
+               if (PRETTY_INDENT(context))
+               {
+                       context->indentLevel += PRETTYINDENT_STD;
+                       appendStringInfoSpaces(buf, PRETTYINDENT_STD);
+               }
+
                /*
                 * We force parens whenever nesting two SetOperationStmts. There are
                 * some cases in which parens are needed around a leaf query too, but
@@ -2570,6 +2648,9 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
                get_setop_query(op->rarg, query, context, resultDesc);
                if (need_paren)
                        appendStringInfoChar(buf, ')');
+
+               if (PRETTY_INDENT(context))
+                       context->indentLevel -= PRETTYINDENT_STD;
        }
        else
        {
@@ -2730,11 +2811,15 @@ get_insert_query_def(Query *query, deparse_context *context)
        }
        else if (values_rte)
        {
+               /* A WITH clause is possible here */
+               get_with_clause(query, context);
                /* Add the multi-VALUES expression lists */
                get_values_def(values_rte->values_lists, context);
        }
        else
        {
+               /* A WITH clause is possible here */
+               get_with_clause(query, context);
                /* Add the single-VALUES expression list */
                appendContextKeyword(context, "VALUES (",
                                                         -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
@@ -3360,6 +3445,13 @@ get_name_for_var_field(Var *var, int fieldno,
                         * its result columns as RECORD, which is not allowed.
                         */
                        break;
+               case RTE_CTE:
+                       /*
+                        * XXX not implemented yet, we need more infrastructure in
+                        * deparse_namespace and explain.c.
+                        */
+                       elog(ERROR, "deparsing field references to whole-row vars from WITH queries not implemented yet");
+                       break;
        }
 
        /*
@@ -5029,6 +5121,7 @@ get_sublink_expr(SubLink *sublink, deparse_context *context)
                        need_paren = false;
                        break;
 
+               case CTE_SUBLINK:               /* shouldn't occur in a SubLink */
                default:
                        elog(ERROR, "unrecognized sublink type: %d",
                                 (int) sublink->subLinkType);
@@ -5130,6 +5223,9 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
                                /* Values list RTE */
                                get_values_def(rte->values_lists, context);
                                break;
+                       case RTE_CTE:
+                               appendStringInfoString(buf, quote_identifier(rte->ctename));
+                               break;
                        default:
                                elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
                                break;
index c8cad02a0993af439bca2073b976a25f13c70fc0..2e840d958c70028fd57ff79048512df8dadefb69 100644 (file)
@@ -728,15 +728,23 @@ ScanQueryForLocks(Query *parsetree, bool acquire)
                }
        }
 
+       /* Recurse into subquery-in-WITH */
+       foreach(lc, parsetree->cteList)
+       {
+               CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+
+               ScanQueryForLocks((Query *) cte->ctequery, acquire);
+       }
+
        /*
         * Recurse into sublink subqueries, too.  But we already did the ones in
-        * the rtable.
+        * the rtable and cteList.
         */
        if (parsetree->hasSubLinks)
        {
                query_tree_walker(parsetree, ScanQueryWalker,
                                                  (void *) &acquire,
-                                                 QTW_IGNORE_RT_SUBQUERIES);
+                                                 QTW_IGNORE_RC_SUBQUERIES);
        }
 }
 
index c927618540f9ff74384df69e915070d6cf0425c5..9e9ffec53e08bd410c154e67a5a7a19dd57c19ac 100644 (file)
@@ -86,7 +86,7 @@ typedef enum
 typedef struct
 {
        int                     eflags;                 /* capability flags */
-       bool            eof_reached;    /* read reached EOF */
+       bool            eof_reached;    /* read has reached EOF */
        int                     current;                /* next array index to read */
        int                     file;                   /* temp file# */
        off_t           offset;                 /* byte offset in file */
@@ -373,6 +373,39 @@ tuplestore_alloc_read_pointer(Tuplestorestate *state, int eflags)
        return state->readptrcount++;
 }
 
+/*
+ * tuplestore_clear
+ *
+ *     Delete all the contents of a tuplestore, and reset its read pointers
+ *     to the start.
+ */
+void
+tuplestore_clear(Tuplestorestate *state)
+{
+       int                     i;
+       TSReadPointer *readptr;
+
+       if (state->myfile)
+               BufFileClose(state->myfile);
+       state->myfile = NULL;
+       if (state->memtuples)
+       {
+               for (i = 0; i < state->memtupcount; i++)
+               {
+                       FREEMEM(state, GetMemoryChunkSpace(state->memtuples[i]));
+                       pfree(state->memtuples[i]);
+               }
+       }
+       state->status = TSS_INMEM;
+       state->memtupcount = 0;
+       readptr = state->readptrs;
+       for (i = 0; i < state->readptrcount; readptr++, i++)
+       {
+               readptr->eof_reached = false;
+               readptr->current = 0;
+       }
+}
+
 /*
  * tuplestore_end
  *
@@ -463,9 +496,13 @@ tuplestore_ateof(Tuplestorestate *state)
  *
  * Note that the input tuple is always copied; the caller need not save it.
  *
- * Any read pointer that is currently "AT EOF" remains so (the read pointer
- * implicitly advances along with the write pointer); otherwise the read
- * pointer is unchanged.  This is for the convenience of nodeMaterial.c.
+ * If the active read pointer is currently "at EOF", it remains so (the read
+ * pointer implicitly advances along with the write pointer); otherwise the
+ * read pointer is unchanged.  Non-active read pointers do not move, which
+ * means they are certain to not be "at EOF" immediately after puttuple.
+ * This curious-seeming behavior is for the convenience of nodeMaterial.c and
+ * nodeCtescan.c, which would otherwise need to do extra pointer repositioning
+ * steps.
  *
  * tuplestore_puttupleslot() is a convenience routine to collect data from
  * a TupleTableSlot without an extra copy operation.
@@ -519,10 +556,26 @@ tuplestore_putvalues(Tuplestorestate *state, TupleDesc tdesc,
 static void
 tuplestore_puttuple_common(Tuplestorestate *state, void *tuple)
 {
+       TSReadPointer *readptr;
+       int                     i;
+
        switch (state->status)
        {
                case TSS_INMEM:
 
+                       /*
+                        * Update read pointers as needed; see API spec above.
+                        */
+                       readptr = state->readptrs;
+                       for (i = 0; i < state->readptrcount; readptr++, i++)
+                       {
+                               if (readptr->eof_reached && i != state->activeptr)
+                               {
+                                       readptr->eof_reached = false;
+                                       readptr->current = state->memtupcount;
+                               }
+                       }
+
                        /*
                         * Grow the array as needed.  Note that we try to grow the array
                         * when there is still one free slot remaining --- if we fail,
@@ -572,6 +625,24 @@ tuplestore_puttuple_common(Tuplestorestate *state, void *tuple)
                        dumptuples(state);
                        break;
                case TSS_WRITEFILE:
+
+                       /*
+                        * Update read pointers as needed; see API spec above.
+                        * Note: BufFileTell is quite cheap, so not worth trying
+                        * to avoid multiple calls.
+                        */
+                       readptr = state->readptrs;
+                       for (i = 0; i < state->readptrcount; readptr++, i++)
+                       {
+                               if (readptr->eof_reached && i != state->activeptr)
+                               {
+                                       readptr->eof_reached = false;
+                                       BufFileTell(state->myfile,
+                                                               &readptr->file,
+                                                               &readptr->offset);
+                               }
+                       }
+
                        WRITETUP(state, tuple);
                        break;
                case TSS_READFILE:
@@ -588,6 +659,21 @@ tuplestore_puttuple_common(Tuplestorestate *state, void *tuple)
                                                        SEEK_SET) != 0)
                                elog(ERROR, "tuplestore seek to EOF failed");
                        state->status = TSS_WRITEFILE;
+
+                       /*
+                        * Update read pointers as needed; see API spec above.
+                        */
+                       readptr = state->readptrs;
+                       for (i = 0; i < state->readptrcount; readptr++, i++)
+                       {
+                               if (readptr->eof_reached && i != state->activeptr)
+                               {
+                                       readptr->eof_reached = false;
+                                       readptr->file = state->writepos_file;
+                                       readptr->offset = state->writepos_offset;
+                               }
+                       }
+
                        WRITETUP(state, tuple);
                        break;
                default:
index 7bb2d164b51925d531a986bccc9954ceb5735612..add5a40d272735188589d738409fb5306d68893b 100644 (file)
@@ -615,7 +615,7 @@ psql_completion(char *text, int start, int end)
                "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
                "REASSIGN", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK",
                "SAVEPOINT", "SELECT", "SET", "SHOW", "START", "TRUNCATE", "UNLISTEN",
-               "UPDATE", "VACUUM", "VALUES", NULL
+               "UPDATE", "VACUUM", "VALUES", "WITH", NULL
        };
 
        static const char *const backslash_commands[] = {
@@ -2044,6 +2044,10 @@ psql_completion(char *text, int start, int end)
                          pg_strcasecmp(prev2_wd, "ANALYZE") == 0))
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
+/* WITH [RECURSIVE] */
+       else if (pg_strcasecmp(prev_wd, "WITH") == 0)
+               COMPLETE_WITH_CONST("RECURSIVE");
+
 /* ANALYZE */
        /* If the previous word is ANALYZE, produce list of tables */
        else if (pg_strcasecmp(prev_wd, "ANALYZE") == 0)
index 0ca115843a3cacdca50a20de131322bb7b0c2c9f..b453ab08fe1d9347d52fa689a7fcfa173f22b474 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     200810031
+#define CATALOG_VERSION_NO     200810041
 
 #endif
diff --git a/src/include/executor/nodeCtescan.h b/src/include/executor/nodeCtescan.h
new file mode 100644 (file)
index 0000000..6f9e382
--- /dev/null
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeCtescan.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODECTESCAN_H
+#define NODECTESCAN_H
+
+#include "nodes/execnodes.h"
+
+extern int     ExecCountSlotsCteScan(CteScan *node);
+extern CteScanState *ExecInitCteScan(CteScan *node, EState *estate, int eflags);
+extern TupleTableSlot *ExecCteScan(CteScanState *node);
+extern void ExecEndCteScan(CteScanState *node);
+extern void ExecCteScanReScan(CteScanState *node, ExprContext *exprCtxt);
+
+#endif   /* NODECTESCAN_H */
diff --git a/src/include/executor/nodeRecursiveunion.h b/src/include/executor/nodeRecursiveunion.h
new file mode 100644 (file)
index 0000000..e9af902
--- /dev/null
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeRecursiveunion.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODERECURSIVEUNION_H
+#define NODERECURSIVEUNION_H
+
+#include "nodes/execnodes.h"
+
+extern int     ExecCountSlotsRecursiveUnion(RecursiveUnion *node);
+extern RecursiveUnionState *ExecInitRecursiveUnion(RecursiveUnion *node, EState *estate, int eflags);
+extern TupleTableSlot *ExecRecursiveUnion(RecursiveUnionState *node);
+extern void ExecEndRecursiveUnion(RecursiveUnionState *node);
+extern void ExecRecursiveUnionReScan(RecursiveUnionState *node, ExprContext *exprCtxt);
+
+#endif   /* NODERECURSIVEUNION_H */
diff --git a/src/include/executor/nodeWorktablescan.h b/src/include/executor/nodeWorktablescan.h
new file mode 100644 (file)
index 0000000..04cd63a
--- /dev/null
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeWorktablescan.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODEWORKTABLESCAN_H
+#define NODEWORKTABLESCAN_H
+
+#include "nodes/execnodes.h"
+
+extern int     ExecCountSlotsWorkTableScan(WorkTableScan *node);
+extern WorkTableScanState *ExecInitWorkTableScan(WorkTableScan *node, EState *estate, int eflags);
+extern TupleTableSlot *ExecWorkTableScan(WorkTableScanState *node);
+extern void ExecEndWorkTableScan(WorkTableScanState *node);
+extern void ExecWorkTableScanReScan(WorkTableScanState *node, ExprContext *exprCtxt);
+
+#endif   /* NODEWORKTABLESCAN_H */
index 7c0d723ab2991340f3c6cc3e081faba7cec9a638..c391fbb71710cb74786395e071fdd46d4fdbd485 100644 (file)
@@ -946,6 +946,26 @@ typedef struct AppendState
        int                     as_lastplan;
 } AppendState;
 
+/* ----------------
+ *      RecursiveUnionState information
+ *
+ *             RecursiveUnionState is used for performing a recursive union.
+ *
+ *             recursing                       T when we're done scanning the non-recursive term
+ *             intermediate_empty      T if intermediate_table is currently empty
+ *             working_table           working table (to be scanned by recursive term)
+ *             intermediate_table      current recursive output (next generation of WT)
+ * ----------------
+ */
+typedef struct RecursiveUnionState
+{
+       PlanState       ps;                             /* its first field is NodeTag */
+       bool            recursing;
+       bool            intermediate_empty;
+       Tuplestorestate *working_table;
+       Tuplestorestate *intermediate_table;
+} RecursiveUnionState;
+
 /* ----------------
  *      BitmapAndState information
  * ----------------
@@ -1179,6 +1199,43 @@ typedef struct ValuesScanState
        int                     marked_idx;
 } ValuesScanState;
 
+/* ----------------
+ *      CteScanState information
+ *
+ *             CteScan nodes are used to scan a CommonTableExpr query.
+ *
+ * Multiple CteScan nodes can read out from the same CTE query.  We use
+ * a tuplestore to hold rows that have been read from the CTE query but
+ * not yet consumed by all readers.
+ * ----------------
+ */
+typedef struct CteScanState
+{
+       ScanState       ss;                             /* its first field is NodeTag */
+       int                     eflags;                 /* capability flags to pass to tuplestore */
+       int                     readptr;                /* index of my tuplestore read pointer */
+       PlanState  *cteplanstate;       /* PlanState for the CTE query itself */
+       /* Link to the "leader" CteScanState (possibly this same node) */
+       struct CteScanState *leader;
+       /* The remaining fields are only valid in the "leader" CteScanState */
+       Tuplestorestate *cte_table;     /* rows already read from the CTE query */
+       bool            eof_cte;                /* reached end of CTE query? */
+} CteScanState;
+
+/* ----------------
+ *      WorkTableScanState information
+ *
+ *             WorkTableScan nodes are used to scan the work table created by
+ *             a RecursiveUnion node.  We locate the RecursiveUnion node
+ *             during executor startup.
+ * ----------------
+ */
+typedef struct WorkTableScanState
+{
+       ScanState       ss;                             /* its first field is NodeTag */
+       RecursiveUnionState *rustate;
+} WorkTableScanState;
+
 /* ----------------------------------------------------------------
  *                              Join State Information
  * ----------------------------------------------------------------
index 7fe13d1032285a6bd2ff49bcf0ca7033a89b667c..c3d78fde9d99722f29e0cd2d8c70900b69e2c0a5 100644 (file)
 
 /* flags bits for query_tree_walker and query_tree_mutator */
 #define QTW_IGNORE_RT_SUBQUERIES       0x01            /* subqueries in rtable */
-#define QTW_IGNORE_JOINALIASES         0x02            /* JOIN alias var lists */
-#define QTW_DONT_COPY_QUERY                    0x04            /* do not copy top Query */
+#define QTW_IGNORE_CTE_SUBQUERIES      0x02            /* subqueries in cteList */
+#define QTW_IGNORE_RC_SUBQUERIES       0x03            /* both of above */
+#define QTW_IGNORE_JOINALIASES         0x04            /* JOIN alias var lists */
+#define QTW_EXAMINE_RTES                       0x08            /* examine RTEs */
+#define QTW_DONT_COPY_QUERY                    0x10            /* do not copy top Query */
 
 
 extern Oid     exprType(Node *expr);
@@ -49,4 +52,7 @@ extern bool query_or_expression_tree_walker(Node *node, bool (*walker) (),
 extern Node *query_or_expression_tree_mutator(Node *node, Node *(*mutator) (),
                                                                                                   void *context, int flags);
 
+extern bool raw_expression_tree_walker(Node *node, bool (*walker) (),
+                                                                          void *context);
+
 #endif   /* NODEFUNCS_H */
index d51a26353bdcea34d4465851422d7f5d1ba75364..abbea7c563e4c6c3bd6fe44de496667b75031872 100644 (file)
@@ -44,6 +44,7 @@ typedef enum NodeTag
        T_Plan = 100,
        T_Result,
        T_Append,
+       T_RecursiveUnion,
        T_BitmapAnd,
        T_BitmapOr,
        T_Scan,
@@ -55,6 +56,8 @@ typedef enum NodeTag
        T_SubqueryScan,
        T_FunctionScan,
        T_ValuesScan,
+       T_CteScan,
+       T_WorkTableScan,
        T_Join,
        T_NestLoop,
        T_MergeJoin,
@@ -78,6 +81,7 @@ typedef enum NodeTag
        T_PlanState = 200,
        T_ResultState,
        T_AppendState,
+       T_RecursiveUnionState,
        T_BitmapAndState,
        T_BitmapOrState,
        T_ScanState,
@@ -89,6 +93,8 @@ typedef enum NodeTag
        T_SubqueryScanState,
        T_FunctionScanState,
        T_ValuesScanState,
+       T_CteScanState,
+       T_WorkTableScanState,
        T_JoinState,
        T_NestLoopState,
        T_MergeJoinState,
@@ -352,6 +358,8 @@ typedef enum NodeTag
        T_LockingClause,
        T_RowMarkClause,
        T_XmlSerialize,
+       T_WithClause,
+       T_CommonTableExpr,
 
        /*
         * TAGS FOR RANDOM OTHER STUFF
index b9b396eef4e962ab1631058c9483a47ef352e6d2..28508174849845818e831879c97993cc1dfc8281 100644 (file)
@@ -115,6 +115,9 @@ typedef struct Query
        bool            hasAggs;                /* has aggregates in tlist or havingQual */
        bool            hasSubLinks;    /* has subquery SubLink */
        bool            hasDistinctOn;  /* distinctClause is from DISTINCT ON */
+       bool            hasRecursive;   /* WITH RECURSIVE was specified */
+
+       List       *cteList;            /* WITH list (of CommonTableExpr's) */
 
        List       *rtable;                     /* list of range table entries */
        FromExpr   *jointree;           /* table join tree (FROM and WHERE clauses) */
@@ -563,7 +566,8 @@ typedef enum RTEKind
        RTE_JOIN,                                       /* join */
        RTE_SPECIAL,                            /* special rule relation (NEW or OLD) */
        RTE_FUNCTION,                           /* function in FROM */
-       RTE_VALUES                                      /* VALUES (<exprlist>), (<exprlist>), ... */
+       RTE_VALUES,                                     /* VALUES (<exprlist>), (<exprlist>), ... */
+       RTE_CTE                                         /* common table expr (WITH list element) */
 } RTEKind;
 
 typedef struct RangeTblEntry
@@ -588,6 +592,20 @@ typedef struct RangeTblEntry
         */
        Query      *subquery;           /* the sub-query */
 
+       /*
+        * Fields valid for a join RTE (else NULL/zero):
+        *
+        * joinaliasvars is a list of Vars or COALESCE expressions corresponding
+        * to the columns of the join result.  An alias Var referencing column K
+        * of the join result can be replaced by the K'th element of joinaliasvars
+        * --- but to simplify the task of reverse-listing aliases correctly, we
+        * do not do that until planning time.  In a Query loaded from a stored
+        * rule, it is also possible for joinaliasvars items to be NULL Consts,
+        * denoting columns dropped since the rule was made.
+        */
+       JoinType        jointype;               /* type of join */
+       List       *joinaliasvars;      /* list of alias-var expansions */
+
        /*
         * Fields valid for a function RTE (else NULL):
         *
@@ -605,18 +623,13 @@ typedef struct RangeTblEntry
        List       *values_lists;       /* list of expression lists */
 
        /*
-        * Fields valid for a join RTE (else NULL/zero):
-        *
-        * joinaliasvars is a list of Vars or COALESCE expressions corresponding
-        * to the columns of the join result.  An alias Var referencing column K
-        * of the join result can be replaced by the K'th element of joinaliasvars
-        * --- but to simplify the task of reverse-listing aliases correctly, we
-        * do not do that until planning time.  In a Query loaded from a stored
-        * rule, it is also possible for joinaliasvars items to be NULL Consts,
-        * denoting columns dropped since the rule was made.
+        * Fields valid for a CTE RTE (else NULL/zero):
         */
-       JoinType        jointype;               /* type of join */
-       List       *joinaliasvars;      /* list of alias-var expansions */
+       char       *ctename;            /* name of the WITH list item */
+       Index           ctelevelsup;    /* number of query levels up */
+       bool            self_reference; /* is this a recursive self-reference? */
+       List       *ctecoltypes;        /* OID list of column type OIDs */
+       List       *ctecoltypmods;      /* integer list of column typmods */
 
        /*
         * Fields valid in all RTEs:
@@ -697,6 +710,43 @@ typedef struct RowMarkClause
        bool            noWait;                 /* NOWAIT option */
 } RowMarkClause;
 
+/*
+ * WithClause -
+ *     representation of WITH clause
+ *
+ * Note: WithClause does not propagate into the Query representation;
+ * but CommonTableExpr does.
+ */
+typedef struct WithClause
+{
+       NodeTag         type;
+       List       *ctes;                       /* list of CommonTableExprs */
+       bool            recursive;              /* true = WITH RECURSIVE */
+       int                     location;               /* token location, or -1 if unknown */
+} WithClause;
+
+/*
+ * CommonTableExpr -
+ *     representation of WITH list element
+ *
+ * We don't currently support the SEARCH or CYCLE clause.
+ */
+typedef struct CommonTableExpr
+{
+       NodeTag         type;
+       char       *ctename;            /* query name (never qualified) */
+       List       *aliascolnames;      /* optional list of column names */
+       Node       *ctequery;           /* subquery (SelectStmt or Query) */
+       int                     location;               /* token location, or -1 if unknown */
+       /* These fields are set during parse analysis: */
+       bool            cterecursive;   /* is this CTE actually recursive? */
+       int                     cterefcount;    /* number of RTEs referencing this CTE
+                                                                * (excluding internal self-references) */
+       List       *ctecolnames;        /* list of output column names */
+       List       *ctecoltypes;        /* OID list of output column type OIDs */
+       List       *ctecoltypmods;      /* integer list of output column typmods */
+} CommonTableExpr;
+
 /*****************************************************************************
  *             Optimizable Statements
  *****************************************************************************/
@@ -781,6 +831,7 @@ typedef struct SelectStmt
        Node       *whereClause;        /* WHERE qualification */
        List       *groupClause;        /* GROUP BY clauses */
        Node       *havingClause;       /* HAVING conditional-expression */
+       WithClause *withClause;         /* WITH clause */
 
        /*
         * In a "leaf" node representing a VALUES list, the above fields are all
index 2a831ad838789e96fdd538d26c1c48c743f30c00..36dd513067160c0b02ef84ebe3090281745548c3 100644 (file)
@@ -182,6 +182,20 @@ typedef struct Append
        bool            isTarget;
 } Append;
 
+/* ----------------
+ *     RecursiveUnion node -
+ *             Generate a recursive union of two subplans.
+ *
+ * The "outer" subplan is always the non-recursive term, and the "inner"
+ * subplan is the recursive term.
+ * ----------------
+ */
+typedef struct RecursiveUnion
+{
+       Plan            plan;
+       int                     wtParam;                /* ID of Param representing work table */
+} RecursiveUnion;
+
 /* ----------------
  *      BitmapAnd node -
  *             Generate the intersection of the results of sub-plans.
@@ -358,6 +372,28 @@ typedef struct ValuesScan
        List       *values_lists;       /* list of expression lists */
 } ValuesScan;
 
+/* ----------------
+ *             CteScan node
+ * ----------------
+ */
+typedef struct CteScan
+{
+       Scan            scan;
+       int                     ctePlanId;              /* ID of init SubPlan for CTE */
+       int                     cteParam;               /* ID of Param representing CTE output */
+} CteScan;
+
+/* ----------------
+ *             WorkTableScan node
+ * ----------------
+ */
+typedef struct WorkTableScan
+{
+       Scan            scan;
+       int                     wtParam;                /* ID of Param representing work table */
+} WorkTableScan;
+
+
 /*
  * ==========
  * Join nodes
index 7eeb4c86f53b63efcb3aeaf64417132fe33664d3..67f651218256d1fa26788f46bd1b189c915fa9f6 100644 (file)
@@ -386,6 +386,7 @@ typedef struct BoolExpr
  *     ROWCOMPARE_SUBLINK      (lefthand) op (SELECT ...)
  *     EXPR_SUBLINK            (SELECT with single targetlist item ...)
  *     ARRAY_SUBLINK           ARRAY(SELECT with single targetlist item ...)
+ *     CTE_SUBLINK                     WITH query (never actually part of an expression)
  * For ALL, ANY, and ROWCOMPARE, the lefthand is a list of expressions of the
  * same length as the subselect's targetlist.  ROWCOMPARE will *always* have
  * a list with more than one entry; if the subselect has just one target
@@ -412,6 +413,9 @@ typedef struct BoolExpr
  *
  * In EXISTS, EXPR, and ARRAY SubLinks, testexpr and operName are unused and
  * are always null.
+ *
+ * The CTE_SUBLINK case never occurs in actual SubLink nodes, but it is used
+ * in SubPlans generated for WITH subqueries.
  */
 typedef enum SubLinkType
 {
@@ -420,7 +424,8 @@ typedef enum SubLinkType
        ANY_SUBLINK,
        ROWCOMPARE_SUBLINK,
        EXPR_SUBLINK,
-       ARRAY_SUBLINK
+       ARRAY_SUBLINK,
+       CTE_SUBLINK                                     /* for SubPlans only */
 } SubLinkType;
 
 
index deef37fff01ec8e6680948430b1ef0b694ee18a1..9dc0184da19c71eea818c0617f06413a3af43913 100644 (file)
@@ -104,6 +104,8 @@ typedef struct PlannerInfo
 
        Index           query_level;    /* 1 at the outermost Query */
 
+       struct PlannerInfo *parent_root; /* NULL at outermost Query */
+
        /*
         * simple_rel_array holds pointers to "base rels" and "other rels" (see
         * comments for RelOptInfo for more info).      It is indexed by rangetable
@@ -138,7 +140,9 @@ typedef struct PlannerInfo
 
        List       *returningLists; /* list of lists of TargetEntry, or NIL */
 
-       List       *init_plans;         /* init subplans for query */
+       List       *init_plans;         /* init SubPlans for query */
+
+       List       *cte_plan_ids;       /* per-CTE-item list of subplan IDs */
 
        List       *eq_classes;         /* list of active EquivalenceClasses */
 
@@ -178,6 +182,11 @@ typedef struct PlannerInfo
        bool            hasHavingQual;  /* true if havingQual was non-null */
        bool            hasPseudoConstantQuals; /* true if any RestrictInfo has
                                                                                 * pseudoconstant = true */
+       bool            hasRecursion;   /* true if planning a recursive WITH item */
+
+       /* These fields are used only when hasRecursion is true: */
+       int                     wt_param_id;                    /* PARAM_EXEC ID for the work table */
+       struct Plan *non_recursive_plan;        /* plan for non-recursive term */
 } PlannerInfo;
 
 
@@ -542,8 +551,9 @@ typedef struct PathKey
 } PathKey;
 
 /*
- * Type "Path" is used as-is for sequential-scan paths.  For other
- * path types it is the first component of a larger struct.
+ * Type "Path" is used as-is for sequential-scan paths, as well as some other
+ * simple plan types that we don't need any extra information in the path for.
+ * For other path types it is the first component of a larger struct.
  *
  * Note: "pathtype" is the NodeTag of the Plan node we could build from this
  * Path.  It is partially redundant with the Path's NodeTag, but allows us
index 41b7a3dc8e17ac1bc30e9d3a0a4338fccbd12510..da11d5822e86ab436ce989d0c1fac82c617058a9 100644 (file)
@@ -72,6 +72,8 @@ extern void cost_functionscan(Path *path, PlannerInfo *root,
                                  RelOptInfo *baserel);
 extern void cost_valuesscan(Path *path, PlannerInfo *root,
                                RelOptInfo *baserel);
+extern void cost_ctescan(Path *path, PlannerInfo *root, RelOptInfo *baserel);
+extern void cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm);
 extern void cost_sort(Path *path, PlannerInfo *root,
                  List *pathkeys, Cost input_cost, double tuples, int width,
                  double limit_tuples);
@@ -104,6 +106,8 @@ extern void set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel,
                                                   List *restrictlist);
 extern void set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern void set_values_size_estimates(PlannerInfo *root, RelOptInfo *rel);
+extern void set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel,
+                                                                  Plan *cteplan);
 
 /*
  * prototypes for clausesel.c
index fa003b46d9d4eabd97059c1b46af5e3b8e49fe16..e0ac7d88402aa588cc3a7c3d270efb3a71df9449 100644 (file)
@@ -54,6 +54,8 @@ extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel,
 extern Path *create_subqueryscan_path(RelOptInfo *rel, List *pathkeys);
 extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel);
 extern Path *create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel);
+extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel);
+extern Path *create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel);
 
 extern NestPath *create_nestloop_path(PlannerInfo *root,
                                         RelOptInfo *joinrel,
index 7faf5c5262f3480cf49e7cacb1a5450a7cb02f30..5e23930d91b5b8563dabbca08e61e7c309d075a4 100644 (file)
@@ -42,6 +42,8 @@ extern Plan *create_plan(PlannerInfo *root, Path *best_path);
 extern SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual,
                                  Index scanrelid, Plan *subplan, List *subrtable);
 extern Append *make_append(List *appendplans, bool isTarget, List *tlist);
+extern RecursiveUnion *make_recursive_union(List *tlist,
+                          Plan *lefttree, Plan *righttree, int wtParam);
 extern Sort *make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree,
                                                List *pathkeys, double limit_tuples);
 extern Sort *make_sort_from_sortclauses(PlannerInfo *root, List *sortcls,
index 30fe094fcdf287ea02273cbb686558b715d43d4d..9dbeb6e1b0f562ee7b7e397ccafd046e0930ecf0 100644 (file)
@@ -30,7 +30,8 @@ extern PlannedStmt *planner(Query *parse, int cursorOptions,
 extern PlannedStmt *standard_planner(Query *parse, int cursorOptions,
                                 ParamListInfo boundParams);
 extern Plan *subquery_planner(PlannerGlobal *glob, Query *parse,
-                                Index level, double tuple_fraction,
+                                PlannerInfo *parent_root,
+                                bool hasRecursion, double tuple_fraction,
                                 PlannerInfo **subroot);
 
 #endif   /* PLANNER_H */
index 2319bb49bc78f14a22a0bfe34406c7bf38a22172..f4087c03e8aab0737947635fa461d9326bbf7b39 100644 (file)
@@ -15,6 +15,7 @@
 #include "nodes/plannodes.h"
 #include "nodes/relation.h"
 
+extern void SS_process_ctes(PlannerInfo *root);
 extern bool convert_ANY_sublink_to_join(PlannerInfo *root, SubLink *sublink,
                                                                                Relids available_rels,
                                                                                Node **new_qual, List **fromlist);
@@ -28,5 +29,6 @@ extern void SS_finalize_plan(PlannerInfo *root, Plan *plan,
                                                         bool attach_initplans);
 extern Param *SS_make_initplan_from_plan(PlannerInfo *root, Plan *plan,
                                                   Oid resulttype, int32 resulttypmod);
+extern int     SS_assign_worktable_param(PlannerInfo *root);
 
 #endif   /* SUBSELECT_H */
diff --git a/src/include/parser/parse_cte.h b/src/include/parser/parse_cte.h
new file mode 100644 (file)
index 0000000..a8f5e67
--- /dev/null
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * parse_cte.h
+ *       handle CTEs (common table expressions) in parser
+ *
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARSE_CTE_H
+#define PARSE_CTE_H
+
+#include "parser/parse_node.h"
+
+extern List *transformWithClause(ParseState *pstate, WithClause *withClause);
+
+#endif   /* PARSE_CTE_H */
index e3061d0c075865002a7bc51576533b817b9f69f2..18f57b90adfaa27f418dc2488b0b3dd51b826115 100644 (file)
  * implicit RTEs into p_relnamespace but not p_varnamespace, so that they
  * do not affect the set of columns available for unqualified references.
  *
+ * p_ctenamespace: list of CommonTableExprs (WITH items) that are visible
+ * at the moment.  This is different from p_relnamespace because you have
+ * to make an RTE before you can access a CTE.
+ *
  * p_paramtypes: an array of p_numparams type OIDs for $n parameter symbols
  * (zeroth entry in array corresponds to $1).  If p_variableparams is true, the
  * set of param types is not predetermined; in that case, a zero array entry
@@ -68,6 +72,7 @@ typedef struct ParseState
                                                                 * node's fromlist) */
        List       *p_relnamespace; /* current namespace for relations */
        List       *p_varnamespace; /* current namespace for columns */
+       List       *p_ctenamespace; /* current namespace for common table exprs */
        Oid                *p_paramtypes;       /* OIDs of types for $n parameter symbols */
        int                     p_numparams;    /* allocated size of p_paramtypes[] */
        int                     p_next_resno;   /* next targetlist resno to assign */
index 9d840c95620987d020c94c595134098bfba01636..34b99f3388a30767ba3505d0c23b7d9da008fadd 100644 (file)
@@ -31,6 +31,7 @@ extern int RTERangeTablePosn(ParseState *pstate,
 extern RangeTblEntry *GetRTEByRangeTablePosn(ParseState *pstate,
                                           int varno,
                                           int sublevels_up);
+extern CommonTableExpr *GetCTEForRTE(ParseState *pstate, RangeTblEntry *rte);
 extern Node *scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte,
                                 char *colname, int location);
 extern Node *colNameToVar(ParseState *pstate, char *colname, bool localonly,
@@ -72,6 +73,11 @@ extern RangeTblEntry *addRangeTableEntryForJoin(ParseState *pstate,
                                                  List *aliasvars,
                                                  Alias *alias,
                                                  bool inFromCl);
+extern RangeTblEntry *addRangeTableEntryForCTE(ParseState *pstate,
+                                                CommonTableExpr *cte,
+                                                Index levelsup,
+                                                Alias *alias,
+                                                bool inFromCl);
 extern void addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
                          bool addToJoinList,
                          bool addToRelNameSpace, bool addToVarNameSpace);
index 37e85545609b9ee3f6375ab320fd356dfb02d9a7..5790af32fa519a892ce6b58f9233b73f496ce02a 100644 (file)
 #define ERRCODE_INSUFFICIENT_PRIVILEGE         MAKE_SQLSTATE('4','2', '5','0','1')
 #define ERRCODE_CANNOT_COERCE                          MAKE_SQLSTATE('4','2', '8','4','6')
 #define ERRCODE_GROUPING_ERROR                         MAKE_SQLSTATE('4','2', '8','0','3')
+#define ERRCODE_INVALID_RECURSION                      MAKE_SQLSTATE('4','2', 'P','1','9')
 #define ERRCODE_INVALID_FOREIGN_KEY                    MAKE_SQLSTATE('4','2', '8','3','0')
 #define ERRCODE_INVALID_NAME                           MAKE_SQLSTATE('4','2', '6','0','2')
 #define ERRCODE_NAME_TOO_LONG                          MAKE_SQLSTATE('4','2', '6','2','2')
index 37a5231e58ac86e9f4d0dd220c66054dc99eeaaa..7996fd1b76338118cf2fcf86aa08d828620f3bbe 100644 (file)
@@ -74,6 +74,8 @@ extern bool tuplestore_ateof(Tuplestorestate *state);
 
 extern void tuplestore_rescan(Tuplestorestate *state);
 
+extern void tuplestore_clear(Tuplestorestate *state);
+
 extern void tuplestore_end(Tuplestorestate *state);
 
 #endif   /* TUPLESTORE_H */
index 949e76bf334c58dc859eeacd9b527f4aa0163738..8cf9a513dd381b0e4dfbf7bd9f2362b17d7a2a79 100644 (file)
@@ -473,7 +473,7 @@ add_typedef(char *name, char * dimension, char * length, enum ECPGttype type_enu
 
        QUOTE
 
-       READ REAL REASSIGN RECHECK REFERENCES REINDEX RELATIVE_P RELEASE RENAME
+       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
 
index acb7d07c13d948242741f3a1d0173a759e9adc7d..c61c9fa09d9d81068bbfb6a2639da913c6a2dc06 100644 (file)
        "grouping_error", ERRCODE_GROUPING_ERROR
 },
 
+{
+       "invalid_recursion", ERRCODE_INVALID_RECURSION
+},
+
 {
        "invalid_foreign_key", ERRCODE_INVALID_FOREIGN_KEY
 },
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
new file mode 100644 (file)
index 0000000..ac91642
--- /dev/null
@@ -0,0 +1,666 @@
+--
+-- Tests for common table expressions (WITH query, ... SELECT ...)
+--
+-- Basic WITH
+WITH q1(x,y) AS (SELECT 1,2)
+SELECT * FROM q1, q1 AS q2;
+ x | y | x | y 
+---+---+---+---
+ 1 | 2 | 1 | 2
+(1 row)
+
+-- Multiple uses are evaluated only once
+SELECT count(*) FROM (
+  WITH q1(x) AS (SELECT random() FROM generate_series(1, 5))
+    SELECT * FROM q1
+  UNION
+    SELECT * FROM q1
+) ss;
+ count 
+-------
+     5
+(1 row)
+
+-- WITH RECURSIVE
+-- sum of 1..100
+WITH RECURSIVE t(n) AS (
+    VALUES (1)
+UNION ALL
+    SELECT n+1 FROM t WHERE n < 100
+)
+SELECT sum(n) FROM t;
+ sum  
+------
+ 5050
+(1 row)
+
+WITH RECURSIVE t(n) AS (
+    SELECT (VALUES(1))
+UNION ALL
+    SELECT n+1 FROM t WHERE n < 5
+)
+SELECT * FROM t;
+ n 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+-- This'd be an infinite loop, but outside query reads only as much as needed
+WITH RECURSIVE t(n) AS (
+    VALUES (1)
+UNION ALL
+    SELECT n+1 FROM t)
+SELECT * FROM t LIMIT 10;
+ n  
+----
+  1
+  2
+  3
+  4
+  5
+  6
+  7
+  8
+  9
+ 10
+(10 rows)
+
+--
+-- Some examples with a tree
+--
+-- department structure represented here is as follows:
+--
+-- ROOT-+->A-+->B-+->C
+--      |         |
+--      |         +->D-+->F
+--      +->E-+->G
+CREATE TEMP TABLE department (
+       id INTEGER PRIMARY KEY,  -- department ID
+       parent_department INTEGER REFERENCES department, -- upper department ID
+       name TEXT -- department name
+);
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "department_pkey" for table "department"
+INSERT INTO department VALUES (0, NULL, 'ROOT');
+INSERT INTO department VALUES (1, 0, 'A');
+INSERT INTO department VALUES (2, 1, 'B');
+INSERT INTO department VALUES (3, 2, 'C');
+INSERT INTO department VALUES (4, 2, 'D');
+INSERT INTO department VALUES (5, 0, 'E');
+INSERT INTO department VALUES (6, 4, 'F');
+INSERT INTO department VALUES (7, 5, 'G');
+-- extract all departments under 'A'. Result should be A, B, C, D and F
+WITH RECURSIVE subdepartment AS
+(
+       -- non recursive term
+       SELECT * FROM department WHERE name = 'A'
+       UNION ALL
+       -- recursive term
+       SELECT d.* FROM department AS d, subdepartment AS sd
+               WHERE d.parent_department = sd.id
+)
+SELECT * FROM subdepartment ORDER BY name;
+ id | parent_department | name 
+----+-------------------+------
+  1 |                 0 | A
+  2 |                 1 | B
+  3 |                 2 | C
+  4 |                 2 | D
+  6 |                 4 | F
+(5 rows)
+
+-- extract all departments under 'A' with "level" number
+WITH RECURSIVE subdepartment(level, id, parent_department, name) AS
+(
+       -- non recursive term
+       SELECT 1, * FROM department WHERE name = 'A'
+       UNION ALL
+       -- recursive term
+       SELECT sd.level + 1, d.* FROM department AS d, subdepartment AS sd
+               WHERE d.parent_department = sd.id
+)
+SELECT * FROM subdepartment ORDER BY name;
+ level | id | parent_department | name 
+-------+----+-------------------+------
+     1 |  1 |                 0 | A
+     2 |  2 |                 1 | B
+     3 |  3 |                 2 | C
+     3 |  4 |                 2 | D
+     4 |  6 |                 4 | F
+(5 rows)
+
+-- extract all departments under 'A' with "level" number.
+-- Only shows level 2 or more
+WITH RECURSIVE subdepartment(level, id, parent_department, name) AS
+(
+       -- non recursive term
+       SELECT 1, * FROM department WHERE name = 'A'
+       UNION ALL
+       -- recursive term
+       SELECT sd.level + 1, d.* FROM department AS d, subdepartment AS sd
+               WHERE d.parent_department = sd.id
+)
+SELECT * FROM subdepartment WHERE level >= 2 ORDER BY name;
+ level | id | parent_department | name 
+-------+----+-------------------+------
+     2 |  2 |                 1 | B
+     3 |  3 |                 2 | C
+     3 |  4 |                 2 | D
+     4 |  6 |                 4 | F
+(4 rows)
+
+-- "RECURSIVE" is ignored if the query has no self-reference
+WITH RECURSIVE subdepartment AS
+(
+       -- note lack of recursive UNION structure
+       SELECT * FROM department WHERE name = 'A'
+)
+SELECT * FROM subdepartment ORDER BY name;
+ id | parent_department | name 
+----+-------------------+------
+  1 |                 0 | A
+(1 row)
+
+-- inside subqueries
+SELECT count(*) FROM (
+    WITH RECURSIVE t(n) AS (
+        SELECT 1 UNION ALL SELECT n + 1 FROM t WHERE n < 500
+    )
+    SELECT * FROM t) AS t WHERE n < (
+        SELECT count(*) FROM (
+            WITH RECURSIVE t(n) AS (
+                   SELECT 1 UNION ALL SELECT n + 1 FROM t WHERE n < 100
+                )
+            SELECT * FROM t WHERE n < 50000
+         ) AS t WHERE n < 100);
+ count 
+-------
+    98
+(1 row)
+
+-- use same CTE twice at different subquery levels
+WITH q1(x,y) AS (
+    SELECT hundred, sum(ten) FROM tenk1 GROUP BY hundred
+  )
+SELECT count(*) FROM q1 WHERE y > (SELECT sum(y)/100 FROM q1 qsub);
+ count 
+-------
+    50
+(1 row)
+
+-- via a VIEW
+CREATE TEMPORARY VIEW vsubdepartment AS
+       WITH RECURSIVE subdepartment AS
+       (
+                -- non recursive term
+               SELECT * FROM department WHERE name = 'A'
+               UNION ALL
+               -- recursive term
+               SELECT d.* FROM department AS d, subdepartment AS sd
+                       WHERE d.parent_department = sd.id
+       )
+       SELECT * FROM subdepartment;
+SELECT * FROM vsubdepartment ORDER BY name;
+ id | parent_department | name 
+----+-------------------+------
+  1 |                 0 | A
+  2 |                 1 | B
+  3 |                 2 | C
+  4 |                 2 | D
+  6 |                 4 | F
+(5 rows)
+
+-- Check reverse listing
+SELECT pg_get_viewdef('vsubdepartment'::regclass);
+                                                                                                                                                                                    pg_get_viewdef                                                                                                                                                                                     
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ WITH RECURSIVE subdepartment AS (SELECT department.id, department.parent_department, department.name FROM department WHERE (department.name = 'A'::text) UNION ALL SELECT d.id, d.parent_department, d.name FROM department d, subdepartment sd WHERE (d.parent_department = sd.id)) SELECT subdepartment.id, subdepartment.parent_department, subdepartment.name FROM subdepartment;
+(1 row)
+
+SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
+                                    pg_get_viewdef                                    
+--------------------------------------------------------------------------------------
+  WITH RECURSIVE subdepartment AS (
+                  SELECT department.id, department.parent_department, department.name
+                    FROM department
+                   WHERE department.name = 'A'::text
+         UNION ALL 
+                  SELECT d.id, d.parent_department, d.name
+                    FROM department d, subdepartment sd
+                   WHERE d.parent_department = sd.id
+         )
+  SELECT subdepartment.id, subdepartment.parent_department, subdepartment.name
+    FROM subdepartment;
+(1 row)
+
+-- recursive term has sub-UNION
+WITH RECURSIVE t(i,j) AS (
+       VALUES (1,2)
+       UNION ALL
+       SELECT t2.i, t.j+1 FROM
+               (SELECT 2 AS i UNION ALL SELECT 3 AS i) AS t2
+               JOIN t ON (t2.i = t.i+1))
+       SELECT * FROM t;
+ i | j 
+---+---
+ 1 | 2
+ 2 | 3
+ 3 | 4
+(3 rows)
+
+--
+-- different tree example
+--
+CREATE TEMPORARY TABLE tree(
+    id INTEGER PRIMARY KEY,
+    parent_id INTEGER REFERENCES tree(id)
+);
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "tree_pkey" for table "tree"
+INSERT INTO tree
+VALUES (1, NULL), (2, 1), (3,1), (4,2), (5,2), (6,2), (7,3), (8,3),
+       (9,4), (10,4), (11,7), (12,7), (13,7), (14, 9), (15,11), (16,11);
+--
+-- get all paths from "second level" nodes to leaf nodes
+--
+WITH RECURSIVE t(id, path) AS (
+    VALUES(1,ARRAY[]::integer[])
+UNION ALL
+    SELECT tree.id, t.path || tree.id
+    FROM tree JOIN t ON (tree.parent_id = t.id)
+)
+SELECT t1.*, t2.* FROM t AS t1 JOIN t AS t2 ON
+       (t1.path[1] = t2.path[1] AND
+       array_upper(t1.path,1) = 1 AND
+       array_upper(t2.path,1) > 1)
+       ORDER BY t1.id, t2.id;
+ id | path | id |    path     
+----+------+----+-------------
+  2 | {2}  |  4 | {2,4}
+  2 | {2}  |  5 | {2,5}
+  2 | {2}  |  6 | {2,6}
+  2 | {2}  |  9 | {2,4,9}
+  2 | {2}  | 10 | {2,4,10}
+  2 | {2}  | 14 | {2,4,9,14}
+  3 | {3}  |  7 | {3,7}
+  3 | {3}  |  8 | {3,8}
+  3 | {3}  | 11 | {3,7,11}
+  3 | {3}  | 12 | {3,7,12}
+  3 | {3}  | 13 | {3,7,13}
+  3 | {3}  | 15 | {3,7,11,15}
+  3 | {3}  | 16 | {3,7,11,16}
+(13 rows)
+
+-- just count 'em
+WITH RECURSIVE t(id, path) AS (
+    VALUES(1,ARRAY[]::integer[])
+UNION ALL
+    SELECT tree.id, t.path || tree.id
+    FROM tree JOIN t ON (tree.parent_id = t.id)
+)
+SELECT t1.id, count(t2.*) FROM t AS t1 JOIN t AS t2 ON
+       (t1.path[1] = t2.path[1] AND
+       array_upper(t1.path,1) = 1 AND
+       array_upper(t2.path,1) > 1)
+       GROUP BY t1.id
+       ORDER BY t1.id;
+ id | count 
+----+-------
+  2 |     6
+  3 |     7
+(2 rows)
+
+--
+-- test multiple WITH queries
+--
+WITH RECURSIVE
+  y (id) AS (VALUES (1)),
+  x (id) AS (SELECT * FROM y UNION ALL SELECT id+1 FROM x WHERE id < 5)
+SELECT * FROM x;
+ id 
+----
+  1
+  2
+  3
+  4
+  5
+(5 rows)
+
+-- forward reference OK
+WITH RECURSIVE
+    x(id) AS (SELECT * FROM y UNION ALL SELECT id+1 FROM x WHERE id < 5),
+    y(id) AS (values (1))
+ SELECT * FROM x;
+ id 
+----
+  1
+  2
+  3
+  4
+  5
+(5 rows)
+
+WITH RECURSIVE
+   x(id) AS
+     (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 5),
+   y(id) AS
+     (VALUES (1) UNION ALL SELECT id+1 FROM y WHERE id < 10)
+ SELECT y.*, x.* FROM y LEFT JOIN x USING (id);
+ id | id 
+----+----
+  1 |  1
+  2 |  2
+  3 |  3
+  4 |  4
+  5 |  5
+  6 |   
+  7 |   
+  8 |   
+  9 |   
+ 10 |   
+(10 rows)
+
+WITH RECURSIVE
+   x(id) AS
+     (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 5),
+   y(id) AS
+     (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 10)
+ SELECT y.*, x.* FROM y LEFT JOIN x USING (id);
+ id | id 
+----+----
+  1 |  1
+  2 |  2
+  3 |  3
+  4 |  4
+  5 |  5
+  6 |   
+(6 rows)
+
+WITH RECURSIVE
+   x(id) AS
+     (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 3 ),
+   y(id) AS
+     (SELECT * FROM x UNION ALL SELECT * FROM x),
+   z(id) AS
+     (SELECT * FROM x UNION ALL SELECT id+1 FROM z WHERE id < 10)
+ SELECT * FROM z;
+ id 
+----
+  1
+  2
+  3
+  2
+  3
+  4
+  3
+  4
+  5
+  4
+  5
+  6
+  5
+  6
+  7
+  6
+  7
+  8
+  7
+  8
+  9
+  8
+  9
+ 10
+  9
+ 10
+ 10
+(27 rows)
+
+WITH RECURSIVE
+   x(id) AS
+     (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 3 ),
+   y(id) AS
+     (SELECT * FROM x UNION ALL SELECT * FROM x),
+   z(id) AS
+     (SELECT * FROM y UNION ALL SELECT id+1 FROM z WHERE id < 10)
+ SELECT * FROM z;
+ id 
+----
+  1
+  2
+  3
+  1
+  2
+  3
+  2
+  3
+  4
+  2
+  3
+  4
+  3
+  4
+  5
+  3
+  4
+  5
+  4
+  5
+  6
+  4
+  5
+  6
+  5
+  6
+  7
+  5
+  6
+  7
+  6
+  7
+  8
+  6
+  7
+  8
+  7
+  8
+  9
+  7
+  8
+  9
+  8
+  9
+ 10
+  8
+  9
+ 10
+  9
+ 10
+  9
+ 10
+ 10
+ 10
+(54 rows)
+
+--
+-- error cases
+--
+-- UNION (should be supported someday)
+WITH RECURSIVE x(n) AS (SELECT 1 UNION SELECT n+1 FROM x)
+       SELECT * FROM x;
+ERROR:  recursive query "x" does not have the form non-recursive-term UNION ALL recursive-term
+LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 UNION SELECT n+1 FROM x)
+                       ^
+-- INTERSECT
+WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x)
+       SELECT * FROM x;
+ERROR:  recursive query "x" does not have the form non-recursive-term UNION ALL recursive-term
+LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x...
+                       ^
+WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FROM x)
+       SELECT * FROM x;
+ERROR:  recursive query "x" does not have the form non-recursive-term UNION ALL recursive-term
+LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FR...
+                       ^
+-- EXCEPT
+WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x)
+       SELECT * FROM x;
+ERROR:  recursive query "x" does not have the form non-recursive-term UNION ALL recursive-term
+LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x)
+                       ^
+WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM x)
+       SELECT * FROM x;
+ERROR:  recursive query "x" does not have the form non-recursive-term UNION ALL recursive-term
+LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM ...
+                       ^
+-- no non-recursive term
+WITH RECURSIVE x(n) AS (SELECT n FROM x)
+       SELECT * FROM x;
+ERROR:  recursive query "x" does not have the form non-recursive-term UNION ALL recursive-term
+LINE 1: WITH RECURSIVE x(n) AS (SELECT n FROM x)
+                       ^
+-- recursive term in the left hand side (strictly speaking, should allow this)
+WITH RECURSIVE x(n) AS (SELECT n FROM x UNION ALL SELECT 1)
+       SELECT * FROM x;
+ERROR:  recursive reference to query "x" must not appear within its non-recursive term
+LINE 1: WITH RECURSIVE x(n) AS (SELECT n FROM x UNION ALL SELECT 1)
+                                              ^
+CREATE TEMPORARY TABLE y (a INTEGER);
+INSERT INTO y SELECT generate_series(1, 10);
+-- LEFT JOIN
+WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1
+       UNION ALL
+       SELECT x.n+1 FROM y LEFT JOIN x ON x.n = y.a WHERE n < 10)
+SELECT * FROM x;
+ERROR:  recursive reference to query "x" must not appear within an outer join
+LINE 3:  SELECT x.n+1 FROM y LEFT JOIN x ON x.n = y.a WHERE n < 10)
+                                       ^
+-- RIGHT JOIN
+WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1
+       UNION ALL
+       SELECT x.n+1 FROM x RIGHT JOIN y ON x.n = y.a WHERE n < 10)
+SELECT * FROM x;
+ERROR:  recursive reference to query "x" must not appear within an outer join
+LINE 3:  SELECT x.n+1 FROM x RIGHT JOIN y ON x.n = y.a WHERE n < 10)
+                           ^
+-- FULL JOIN
+WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1
+       UNION ALL
+       SELECT x.n+1 FROM x FULL JOIN y ON x.n = y.a WHERE n < 10)
+SELECT * FROM x;
+ERROR:  recursive reference to query "x" must not appear within an outer join
+LINE 3:  SELECT x.n+1 FROM x FULL JOIN y ON x.n = y.a WHERE n < 10)
+                           ^
+-- subquery
+WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x
+                          WHERE n IN (SELECT * FROM x))
+  SELECT * FROM x;
+ERROR:  recursive reference to query "x" must not appear within a subquery
+LINE 2:                           WHERE n IN (SELECT * FROM x))
+                                                            ^
+-- aggregate functions
+WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT count(*) FROM x)
+  SELECT * FROM x;
+ERROR:  aggregates not allowed in a recursive query's recursive term
+LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT count(*) F...
+                                                          ^
+WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT sum(n) FROM x)
+  SELECT * FROM x;
+ERROR:  aggregates not allowed in a recursive query's recursive term
+LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT sum(n) FRO...
+                                                          ^
+-- ORDER BY
+WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x ORDER BY 1)
+  SELECT * FROM x;
+ERROR:  ORDER BY in a recursive query is not implemented
+LINE 1: ...VE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x ORDER BY 1)
+                                                                     ^
+-- LIMIT/OFFSET
+WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x LIMIT 10 OFFSET 1)
+  SELECT * FROM x;
+ERROR:  OFFSET in a recursive query is not implemented
+LINE 1: ... AS (SELECT 1 UNION ALL SELECT n+1 FROM x LIMIT 10 OFFSET 1)
+                                                                     ^
+-- FOR UPDATE
+WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x FOR UPDATE)
+  SELECT * FROM x;
+ERROR:  FOR UPDATE/SHARE in a recursive query is not implemented
+-- target list has a recursive query name
+WITH RECURSIVE x(id) AS (values (1)
+    UNION ALL
+    SELECT (SELECT * FROM x) FROM x WHERE id < 5
+) SELECT * FROM x;
+ERROR:  recursive reference to query "x" must not appear within a subquery
+LINE 3:     SELECT (SELECT * FROM x) FROM x WHERE id < 5
+                                  ^
+-- mutual recursive query (not implemented)
+WITH RECURSIVE
+  x (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM y WHERE id < 5),
+  y (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 5)
+SELECT * FROM x;
+ERROR:  mutual recursion between WITH items is not implemented
+LINE 2:   x (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM y WHERE id ...
+          ^
+-- non-linear recursion is not allowed
+WITH RECURSIVE foo(i) AS
+    (values (1)
+    UNION ALL
+       (SELECT i+1 FROM foo WHERE i < 10
+          UNION ALL
+       SELECT i+1 FROM foo WHERE i < 5)
+) SELECT * FROM foo;
+ERROR:  recursive reference to query "foo" must not appear more than once
+LINE 6:        SELECT i+1 FROM foo WHERE i < 5)
+                               ^
+WITH RECURSIVE foo(i) AS
+    (values (1)
+    UNION ALL
+          SELECT * FROM
+       (SELECT i+1 FROM foo WHERE i < 10
+          UNION ALL
+       SELECT i+1 FROM foo WHERE i < 5) AS t
+) SELECT * FROM foo;
+ERROR:  recursive reference to query "foo" must not appear more than once
+LINE 7:        SELECT i+1 FROM foo WHERE i < 5) AS t
+                               ^
+WITH RECURSIVE foo(i) AS
+    (values (1)
+    UNION ALL
+       (SELECT i+1 FROM foo WHERE i < 10
+          EXCEPT
+       SELECT i+1 FROM foo WHERE i < 5)
+) SELECT * FROM foo;
+ERROR:  recursive reference to query "foo" must not appear within EXCEPT
+LINE 6:        SELECT i+1 FROM foo WHERE i < 5)
+                               ^
+WITH RECURSIVE foo(i) AS
+    (values (1)
+    UNION ALL
+       (SELECT i+1 FROM foo WHERE i < 10
+          INTERSECT
+       SELECT i+1 FROM foo WHERE i < 5)
+) SELECT * FROM foo;
+ERROR:  recursive reference to query "foo" must not appear more than once
+LINE 6:        SELECT i+1 FROM foo WHERE i < 5)
+                               ^
+-- Wrong type induced from non-recursive term
+WITH RECURSIVE foo(i) AS
+   (SELECT i FROM (VALUES(1),(2)) t(i)
+   UNION ALL
+   SELECT (i+1)::numeric(10,0) FROM foo WHERE i < 10)
+SELECT * FROM foo;
+ERROR:  recursive query "foo" column 1 has type integer in non-recursive term but type numeric overall
+LINE 2:    (SELECT i FROM (VALUES(1),(2)) t(i)
+                   ^
+HINT:  Cast the output of the non-recursive term to the correct type.
+-- rejects different typmod, too (should we allow this?)
+WITH RECURSIVE foo(i) AS
+   (SELECT i::numeric(3,0) FROM (VALUES(1),(2)) t(i)
+   UNION ALL
+   SELECT (i+1)::numeric(10,0) FROM foo WHERE i < 10)
+SELECT * FROM foo;
+ERROR:  recursive query "foo" column 1 has type numeric(3,0) in non-recursive term but type numeric overall
+LINE 2:    (SELECT i::numeric(3,0) FROM (VALUES(1),(2)) t(i)
+                   ^
+HINT:  Cast the output of the non-recursive term to the correct type.
index ca5107bd5fa123f83c1689bb08fac13c07e8573b..3e0a465cf1e02c4539746300844b6aebb5e35a2d 100644 (file)
@@ -83,7 +83,7 @@ test: select_views portals_p2 rules foreign_key cluster dependency guc bitmapops
 # Another group of parallel tests
 # ----------
 # "plpgsql" cannot run concurrently with "rules", nor can "plancache"
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
index 61d82e4c76bc6c12e8fbecff0ed6123cd4c22af1..998a0ae0a9c323fa28b556abcaf96d508429aad7 100644 (file)
@@ -114,6 +114,7 @@ test: polymorphism
 test: rowtypes
 test: returning
 test: largeobject
+test: with
 test: xml
 test: stats
 test: tablespace
diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql
new file mode 100644 (file)
index 0000000..0ad59ab
--- /dev/null
@@ -0,0 +1,387 @@
+--
+-- Tests for common table expressions (WITH query, ... SELECT ...)
+--
+
+-- Basic WITH
+WITH q1(x,y) AS (SELECT 1,2)
+SELECT * FROM q1, q1 AS q2;
+
+-- Multiple uses are evaluated only once
+SELECT count(*) FROM (
+  WITH q1(x) AS (SELECT random() FROM generate_series(1, 5))
+    SELECT * FROM q1
+  UNION
+    SELECT * FROM q1
+) ss;
+
+-- WITH RECURSIVE
+
+-- sum of 1..100
+WITH RECURSIVE t(n) AS (
+    VALUES (1)
+UNION ALL
+    SELECT n+1 FROM t WHERE n < 100
+)
+SELECT sum(n) FROM t;
+
+WITH RECURSIVE t(n) AS (
+    SELECT (VALUES(1))
+UNION ALL
+    SELECT n+1 FROM t WHERE n < 5
+)
+SELECT * FROM t;
+
+-- This'd be an infinite loop, but outside query reads only as much as needed
+WITH RECURSIVE t(n) AS (
+    VALUES (1)
+UNION ALL
+    SELECT n+1 FROM t)
+SELECT * FROM t LIMIT 10;
+
+--
+-- Some examples with a tree
+--
+-- department structure represented here is as follows:
+--
+-- ROOT-+->A-+->B-+->C
+--      |         |
+--      |         +->D-+->F
+--      +->E-+->G
+
+CREATE TEMP TABLE department (
+       id INTEGER PRIMARY KEY,  -- department ID
+       parent_department INTEGER REFERENCES department, -- upper department ID
+       name TEXT -- department name
+);
+
+INSERT INTO department VALUES (0, NULL, 'ROOT');
+INSERT INTO department VALUES (1, 0, 'A');
+INSERT INTO department VALUES (2, 1, 'B');
+INSERT INTO department VALUES (3, 2, 'C');
+INSERT INTO department VALUES (4, 2, 'D');
+INSERT INTO department VALUES (5, 0, 'E');
+INSERT INTO department VALUES (6, 4, 'F');
+INSERT INTO department VALUES (7, 5, 'G');
+
+
+-- extract all departments under 'A'. Result should be A, B, C, D and F
+WITH RECURSIVE subdepartment AS
+(
+       -- non recursive term
+       SELECT * FROM department WHERE name = 'A'
+
+       UNION ALL
+
+       -- recursive term
+       SELECT d.* FROM department AS d, subdepartment AS sd
+               WHERE d.parent_department = sd.id
+)
+SELECT * FROM subdepartment ORDER BY name;
+
+-- extract all departments under 'A' with "level" number
+WITH RECURSIVE subdepartment(level, id, parent_department, name) AS
+(
+       -- non recursive term
+       SELECT 1, * FROM department WHERE name = 'A'
+
+       UNION ALL
+
+       -- recursive term
+       SELECT sd.level + 1, d.* FROM department AS d, subdepartment AS sd
+               WHERE d.parent_department = sd.id
+)
+SELECT * FROM subdepartment ORDER BY name;
+
+-- extract all departments under 'A' with "level" number.
+-- Only shows level 2 or more
+WITH RECURSIVE subdepartment(level, id, parent_department, name) AS
+(
+       -- non recursive term
+       SELECT 1, * FROM department WHERE name = 'A'
+
+       UNION ALL
+
+       -- recursive term
+       SELECT sd.level + 1, d.* FROM department AS d, subdepartment AS sd
+               WHERE d.parent_department = sd.id
+)
+SELECT * FROM subdepartment WHERE level >= 2 ORDER BY name;
+
+-- "RECURSIVE" is ignored if the query has no self-reference
+WITH RECURSIVE subdepartment AS
+(
+       -- note lack of recursive UNION structure
+       SELECT * FROM department WHERE name = 'A'
+)
+SELECT * FROM subdepartment ORDER BY name;
+
+-- inside subqueries
+SELECT count(*) FROM (
+    WITH RECURSIVE t(n) AS (
+        SELECT 1 UNION ALL SELECT n + 1 FROM t WHERE n < 500
+    )
+    SELECT * FROM t) AS t WHERE n < (
+        SELECT count(*) FROM (
+            WITH RECURSIVE t(n) AS (
+                   SELECT 1 UNION ALL SELECT n + 1 FROM t WHERE n < 100
+                )
+            SELECT * FROM t WHERE n < 50000
+         ) AS t WHERE n < 100);
+
+-- use same CTE twice at different subquery levels
+WITH q1(x,y) AS (
+    SELECT hundred, sum(ten) FROM tenk1 GROUP BY hundred
+  )
+SELECT count(*) FROM q1 WHERE y > (SELECT sum(y)/100 FROM q1 qsub);
+
+-- via a VIEW
+CREATE TEMPORARY VIEW vsubdepartment AS
+       WITH RECURSIVE subdepartment AS
+       (
+                -- non recursive term
+               SELECT * FROM department WHERE name = 'A'
+               UNION ALL
+               -- recursive term
+               SELECT d.* FROM department AS d, subdepartment AS sd
+                       WHERE d.parent_department = sd.id
+       )
+       SELECT * FROM subdepartment;
+
+SELECT * FROM vsubdepartment ORDER BY name;
+
+-- Check reverse listing
+SELECT pg_get_viewdef('vsubdepartment'::regclass);
+SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
+
+-- recursive term has sub-UNION
+WITH RECURSIVE t(i,j) AS (
+       VALUES (1,2)
+       UNION ALL
+       SELECT t2.i, t.j+1 FROM
+               (SELECT 2 AS i UNION ALL SELECT 3 AS i) AS t2
+               JOIN t ON (t2.i = t.i+1))
+
+       SELECT * FROM t;
+
+--
+-- different tree example
+--
+CREATE TEMPORARY TABLE tree(
+    id INTEGER PRIMARY KEY,
+    parent_id INTEGER REFERENCES tree(id)
+);
+
+INSERT INTO tree
+VALUES (1, NULL), (2, 1), (3,1), (4,2), (5,2), (6,2), (7,3), (8,3),
+       (9,4), (10,4), (11,7), (12,7), (13,7), (14, 9), (15,11), (16,11);
+
+--
+-- get all paths from "second level" nodes to leaf nodes
+--
+WITH RECURSIVE t(id, path) AS (
+    VALUES(1,ARRAY[]::integer[])
+UNION ALL
+    SELECT tree.id, t.path || tree.id
+    FROM tree JOIN t ON (tree.parent_id = t.id)
+)
+SELECT t1.*, t2.* FROM t AS t1 JOIN t AS t2 ON
+       (t1.path[1] = t2.path[1] AND
+       array_upper(t1.path,1) = 1 AND
+       array_upper(t2.path,1) > 1)
+       ORDER BY t1.id, t2.id;
+
+-- just count 'em
+WITH RECURSIVE t(id, path) AS (
+    VALUES(1,ARRAY[]::integer[])
+UNION ALL
+    SELECT tree.id, t.path || tree.id
+    FROM tree JOIN t ON (tree.parent_id = t.id)
+)
+SELECT t1.id, count(t2.*) FROM t AS t1 JOIN t AS t2 ON
+       (t1.path[1] = t2.path[1] AND
+       array_upper(t1.path,1) = 1 AND
+       array_upper(t2.path,1) > 1)
+       GROUP BY t1.id
+       ORDER BY t1.id;
+
+--
+-- test multiple WITH queries
+--
+WITH RECURSIVE
+  y (id) AS (VALUES (1)),
+  x (id) AS (SELECT * FROM y UNION ALL SELECT id+1 FROM x WHERE id < 5)
+SELECT * FROM x;
+
+-- forward reference OK
+WITH RECURSIVE
+    x(id) AS (SELECT * FROM y UNION ALL SELECT id+1 FROM x WHERE id < 5),
+    y(id) AS (values (1))
+ SELECT * FROM x;
+
+WITH RECURSIVE
+   x(id) AS
+     (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 5),
+   y(id) AS
+     (VALUES (1) UNION ALL SELECT id+1 FROM y WHERE id < 10)
+ SELECT y.*, x.* FROM y LEFT JOIN x USING (id);
+
+WITH RECURSIVE
+   x(id) AS
+     (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 5),
+   y(id) AS
+     (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 10)
+ SELECT y.*, x.* FROM y LEFT JOIN x USING (id);
+
+WITH RECURSIVE
+   x(id) AS
+     (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 3 ),
+   y(id) AS
+     (SELECT * FROM x UNION ALL SELECT * FROM x),
+   z(id) AS
+     (SELECT * FROM x UNION ALL SELECT id+1 FROM z WHERE id < 10)
+ SELECT * FROM z;
+
+WITH RECURSIVE
+   x(id) AS
+     (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 3 ),
+   y(id) AS
+     (SELECT * FROM x UNION ALL SELECT * FROM x),
+   z(id) AS
+     (SELECT * FROM y UNION ALL SELECT id+1 FROM z WHERE id < 10)
+ SELECT * FROM z;
+
+--
+-- error cases
+--
+
+-- UNION (should be supported someday)
+WITH RECURSIVE x(n) AS (SELECT 1 UNION SELECT n+1 FROM x)
+       SELECT * FROM x;
+
+-- INTERSECT
+WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x)
+       SELECT * FROM x;
+
+WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FROM x)
+       SELECT * FROM x;
+
+-- EXCEPT
+WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x)
+       SELECT * FROM x;
+
+WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM x)
+       SELECT * FROM x;
+
+-- no non-recursive term
+WITH RECURSIVE x(n) AS (SELECT n FROM x)
+       SELECT * FROM x;
+
+-- recursive term in the left hand side (strictly speaking, should allow this)
+WITH RECURSIVE x(n) AS (SELECT n FROM x UNION ALL SELECT 1)
+       SELECT * FROM x;
+
+CREATE TEMPORARY TABLE y (a INTEGER);
+INSERT INTO y SELECT generate_series(1, 10);
+
+-- LEFT JOIN
+
+WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1
+       UNION ALL
+       SELECT x.n+1 FROM y LEFT JOIN x ON x.n = y.a WHERE n < 10)
+SELECT * FROM x;
+
+-- RIGHT JOIN
+WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1
+       UNION ALL
+       SELECT x.n+1 FROM x RIGHT JOIN y ON x.n = y.a WHERE n < 10)
+SELECT * FROM x;
+
+-- FULL JOIN
+WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1
+       UNION ALL
+       SELECT x.n+1 FROM x FULL JOIN y ON x.n = y.a WHERE n < 10)
+SELECT * FROM x;
+
+-- subquery
+WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x
+                          WHERE n IN (SELECT * FROM x))
+  SELECT * FROM x;
+
+-- aggregate functions
+WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT count(*) FROM x)
+  SELECT * FROM x;
+
+WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT sum(n) FROM x)
+  SELECT * FROM x;
+
+-- ORDER BY
+WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x ORDER BY 1)
+  SELECT * FROM x;
+
+-- LIMIT/OFFSET
+WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x LIMIT 10 OFFSET 1)
+  SELECT * FROM x;
+
+-- FOR UPDATE
+WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x FOR UPDATE)
+  SELECT * FROM x;
+
+-- target list has a recursive query name
+WITH RECURSIVE x(id) AS (values (1)
+    UNION ALL
+    SELECT (SELECT * FROM x) FROM x WHERE id < 5
+) SELECT * FROM x;
+
+-- mutual recursive query (not implemented)
+WITH RECURSIVE
+  x (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM y WHERE id < 5),
+  y (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 5)
+SELECT * FROM x;
+
+-- non-linear recursion is not allowed
+WITH RECURSIVE foo(i) AS
+    (values (1)
+    UNION ALL
+       (SELECT i+1 FROM foo WHERE i < 10
+          UNION ALL
+       SELECT i+1 FROM foo WHERE i < 5)
+) SELECT * FROM foo;
+
+WITH RECURSIVE foo(i) AS
+    (values (1)
+    UNION ALL
+          SELECT * FROM
+       (SELECT i+1 FROM foo WHERE i < 10
+          UNION ALL
+       SELECT i+1 FROM foo WHERE i < 5) AS t
+) SELECT * FROM foo;
+
+WITH RECURSIVE foo(i) AS
+    (values (1)
+    UNION ALL
+       (SELECT i+1 FROM foo WHERE i < 10
+          EXCEPT
+       SELECT i+1 FROM foo WHERE i < 5)
+) SELECT * FROM foo;
+
+WITH RECURSIVE foo(i) AS
+    (values (1)
+    UNION ALL
+       (SELECT i+1 FROM foo WHERE i < 10
+          INTERSECT
+       SELECT i+1 FROM foo WHERE i < 5)
+) SELECT * FROM foo;
+
+-- Wrong type induced from non-recursive term
+WITH RECURSIVE foo(i) AS
+   (SELECT i FROM (VALUES(1),(2)) t(i)
+   UNION ALL
+   SELECT (i+1)::numeric(10,0) FROM foo WHERE i < 10)
+SELECT * FROM foo;
+
+-- rejects different typmod, too (should we allow this?)
+WITH RECURSIVE foo(i) AS
+   (SELECT i::numeric(3,0) FROM (VALUES(1),(2)) t(i)
+   UNION ALL
+   SELECT (i+1)::numeric(10,0) FROM foo WHERE i < 10)
+SELECT * FROM foo;