Add CREATE RECURSIVE VIEW syntax
authorPeter Eisentraut <peter_e@gmx.net>
Fri, 1 Feb 2013 03:31:58 +0000 (22:31 -0500)
committerPeter Eisentraut <peter_e@gmx.net>
Fri, 1 Feb 2013 03:31:58 +0000 (22:31 -0500)
This is specified in the SQL standard.  The CREATE RECURSIVE VIEW
specification is transformed into a normal CREATE VIEW statement with a
WITH RECURSIVE clause.

reviewed by Abhijit Menon-Sen and Stephen Frost

doc/src/sgml/ref/create_view.sgml
src/backend/parser/gram.y
src/test/regress/expected/with.out
src/test/regress/sql/with.sql

index abbde94772c8dc733a3ec830eb2f2caea2ff2aec..0745e3cdb590b7f2e027fd93ae080ff3271536a8 100644 (file)
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
+CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] [ RECURSIVE ] VIEW <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
     [ WITH ( <replaceable class="PARAMETER">view_option_name</replaceable> [= <replaceable class="PARAMETER">view_option_value</replaceable>] [, ... ] ) ]
     AS <replaceable class="PARAMETER">query</replaceable>
 </synopsis>
@@ -80,6 +80,23 @@ CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW <replaceable class="PARAMETER">n
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>RECURSIVE</></term>
+    <listitem>
+     <para>
+      Creates a recursive view.  The syntax
+<synopsis>
+CREATE RECURSIVE VIEW <replaceable>name</> (<replaceable>columns</>) AS SELECT <replaceable>...</>;
+</synopsis>
+      is equivalent to
+<synopsis>
+CREATE VIEW <replaceable>name</> AS WITH RECURSIVE <replaceable>name</> (<replaceable>columns</>) AS (SELECT <replaceable>...</>) SELECT <replaceable>columns</> FROM <replaceable>name</>;
+</synopsis>
+      A view column list must be specified for a recursive view.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">name</replaceable></term>
     <listitem>
@@ -282,6 +299,16 @@ CREATE VIEW comedies AS
    <literal>*</> was used to create the view, columns added later to
    the table will not be part of the view.
   </para>
+
+  <para>
+   Create a recursive view consisting of the numbers from 1 to 100:
+<programlisting>
+CREATE RECURSIVE VIEW nums_1_100 (n) AS
+    VALUES (1)
+UNION ALL
+    SELECT n+1 FROM nums_1_100 WHERE n < 100;
+</programlisting>
+  </para>
  </refsect1>
 
  <refsect1>
index a2078ec95ef987d5e1d78cdd65092ddc2bdd00cb..342b7964242036116c94aeb72515041468e8ea42 100644 (file)
@@ -164,6 +164,7 @@ static void SplitColQualList(List *qualList,
 static void processCASbits(int cas_bits, int location, const char *constrType,
                           bool *deferrable, bool *initdeferred, bool *not_valid,
                           bool *no_inherit, core_yyscan_t yyscanner);
+static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %}
 
@@ -7839,6 +7840,30 @@ ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list opt_reloptions
                                        n->options = $8;
                                        $$ = (Node *) n;
                                }
+               | CREATE OptTemp RECURSIVE VIEW qualified_name '(' columnList ')' opt_reloptions
+                               AS SelectStmt
+                               {
+                                       ViewStmt *n = makeNode(ViewStmt);
+                                       n->view = $5;
+                                       n->view->relpersistence = $2;
+                                       n->aliases = $7;
+                                       n->query = makeRecursiveViewSelect(n->view->relname, n->aliases, $11);
+                                       n->replace = false;
+                                       n->options = $9;
+                                       $$ = (Node *) n;
+                               }
+               | CREATE OR REPLACE OptTemp RECURSIVE VIEW qualified_name '(' columnList ')' opt_reloptions
+                               AS SelectStmt
+                               {
+                                       ViewStmt *n = makeNode(ViewStmt);
+                                       n->view = $7;
+                                       n->view->relpersistence = $4;
+                                       n->aliases = $9;
+                                       n->query = makeRecursiveViewSelect(n->view->relname, n->aliases, $13);
+                                       n->replace = true;
+                                       n->options = $11;
+                                       $$ = (Node *) n;
+                               }
                ;
 
 opt_check_option:
@@ -13570,6 +13595,66 @@ processCASbits(int cas_bits, int location, const char *constrType,
        }
 }
 
+/*----------
+ * Recursive view transformation
+ *
+ * Convert
+ *
+ *     CREATE RECURSIVE VIEW relname (aliases) AS query
+ *
+ * to
+ *
+ *     CREATE VIEW relname (aliases) AS
+ *         WITH RECURSIVE relname (aliases) AS (query)
+ *         SELECT aliases FROM relname
+ *
+ * Actually, just the WITH ... part, which is then inserted into the original
+ * view definition as the query.
+ * ----------
+ */
+static Node *
+makeRecursiveViewSelect(char *relname, List *aliases, Node *query)
+{
+       SelectStmt *s = makeNode(SelectStmt);
+       WithClause *w = makeNode(WithClause);
+       CommonTableExpr *cte = makeNode(CommonTableExpr);
+       List       *tl = NIL;
+       ListCell   *lc;
+
+       /* create common table expression */
+       cte->ctename = relname;
+       cte->aliascolnames = aliases;
+       cte->ctequery = query;
+       cte->location = -1;
+
+       /* create WITH clause and attach CTE */
+       w->recursive = true;
+       w->ctes = list_make1(cte);
+       w->location = -1;
+
+       /* create target list for the new SELECT from the alias list of the
+        * recursive view specification */
+       foreach (lc, aliases)
+       {
+               ResTarget *rt = makeNode(ResTarget);
+
+               rt->name = NULL;
+               rt->indirection = NIL;
+               rt->val = makeColumnRef(strVal(lfirst(lc)), NIL, -1, 0);
+               rt->location = -1;
+
+               tl = lappend(tl, rt);
+       }
+
+       /* create new SELECT combining WITH clause, target list, and fake FROM
+        * clause */
+       s->withClause = w;
+       s->targetList = tl;
+       s->fromClause = list_make1(makeRangeVar(NULL, relname, -1));
+
+       return (Node *) s;
+}
+
 /* parser_init()
  * Initialize to parse one query string
  */
index b98ca630ee9ec0acf63dfa9e07f2568af9bab3d7..272118f7b70d01e18967e8de332f2afa66514892 100644 (file)
@@ -49,6 +49,36 @@ SELECT * FROM t;
  5
 (5 rows)
 
+-- recursive view
+CREATE RECURSIVE VIEW nums (n) AS
+    VALUES (1)
+UNION ALL
+    SELECT n+1 FROM nums WHERE n < 5;
+SELECT * FROM nums;
+ n 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+CREATE OR REPLACE RECURSIVE VIEW nums (n) AS
+    VALUES (1)
+UNION ALL
+    SELECT n+1 FROM nums WHERE n < 6;
+SELECT * FROM nums;
+ n 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+(6 rows)
+
 -- This is an infinite loop with UNION ALL, but not with UNION
 WITH RECURSIVE t(n) AS (
     SELECT 1
index 4ff852736bcf6b3af3828c70f64dfb53815b920c..c7163699576225662f4fd2cae3401fc40294b1ce 100644 (file)
@@ -31,6 +31,21 @@ UNION ALL
 )
 SELECT * FROM t;
 
+-- recursive view
+CREATE RECURSIVE VIEW nums (n) AS
+    VALUES (1)
+UNION ALL
+    SELECT n+1 FROM nums WHERE n < 5;
+
+SELECT * FROM nums;
+
+CREATE OR REPLACE RECURSIVE VIEW nums (n) AS
+    VALUES (1)
+UNION ALL
+    SELECT n+1 FROM nums WHERE n < 6;
+
+SELECT * FROM nums;
+
 -- This is an infinite loop with UNION ALL, but not with UNION
 WITH RECURSIVE t(n) AS (
     SELECT 1