Enhanced cycle mark values
authorPeter Eisentraut
Sat, 27 Feb 2021 07:11:14 +0000 (08:11 +0100)
committerPeter Eisentraut
Sat, 27 Feb 2021 07:13:24 +0000 (08:13 +0100)
Per SQL:202x draft, in the CYCLE clause of a recursive query, the
cycle mark values can be of type boolean and can be omitted, in which
case they default to TRUE and FALSE.

Reviewed-by: Vik Fearing
Discussion: https://www.postgresql.org/message-id/flat/db80ceee-6f97-9b4a-8ee8-3ba0c58e5be2@2ndquadrant.com

doc/src/sgml/queries.sgml
doc/src/sgml/ref/select.sgml
src/backend/catalog/sql_features.txt
src/backend/parser/gram.y
src/backend/utils/adt/ruleutils.c
src/test/regress/expected/with.out
src/test/regress/sql/with.sql

index 4741506eb564a11165ca16d90b523a8a0ea72932..bc0b3cc9fe13825e91cead6264562e31dcadf846 100644 (file)
@@ -2349,14 +2349,13 @@ WITH RECURSIVE search_graph(id, link, data, depth) AS (
     SELECT g.id, g.link, g.data, sg.depth + 1
     FROM graph g, search_graph sg
     WHERE g.id = sg.link
-) CYCLE id SET is_cycle TO true DEFAULT false USING path
+) CYCLE id SET is_cycle USING path
 SELECT * FROM search_graph;
 
    and it will be internally rewritten to the above form.  The
    CYCLE clause specifies first the list of columns to
    track for cycle detection, then a column name that will show whether a
-   cycle has been detected, then two values to use in that column for the yes
-   and no cases, and finally the name of another column that will track the
+   cycle has been detected, and finally the name of another column that will track the
    path.  The cycle and path columns will implicitly be added to the output
    rows of the CTE.
   
index eb8b52495188ab1938637b69f385a833af3a48f8..ab9110559948184c063895ee27953d63e94244f3 100644 (file)
@@ -74,7 +74,7 @@ SELECT [ ALL | DISTINCT [ ON ( expression
 
     with_query_name [ ( column_name [, ...] ) ] AS [ [ NOT ] MATERIALIZED ] ( select | values | insert | update | delete )
         [ SEARCH { BREADTH | DEPTH } FIRST BY column_name [, ...] SET search_seq_col_name ]
-        [ CYCLE column_name [, ...] SET cycle_mark_col_name TO cycle_mark_value DEFAULT cycle_mark_default USING cycle_path_col_name ]
+        [ CYCLE column_name [, ...] SET cycle_mark_col_name [ TO cycle_mark_value DEFAULT cycle_mark_default ] USING cycle_path_col_name ]
 
 TABLE [ ONLY ] table_name [ * ]
 
@@ -302,8 +302,10 @@ TABLE [ ONLY ] table_name [ * ]
     been detected.  cycle_mark_value and
     cycle_mark_default must be constants and they
     must be coercible to a common data type, and the data type must have an
-    inequality operator.  (The SQL standard requires that they be character
-    strings, but PostgreSQL does not require that.)  Furthermore, a column
+    inequality operator.  (The SQL standard requires that they be Boolean
+    constants or character strings, but PostgreSQL does not require that.)  By
+    default, TRUE and FALSE (of type
+    boolean) are used.  Furthermore, a column
     named cycle_path_col_name will be added to the
     result column list of the WITH query.  This column is
     used internally for tracking visited rows.  See 
index a24387c1e7645e9f88cc67d49ad9409511352077..ab0895ce3c8aa809592a0c3ce0c15d023b319171 100644 (file)
@@ -425,6 +425,7 @@ T121    WITH (excluding RECURSIVE) in query expression          YES
 T122   WITH (excluding RECURSIVE) in subquery          YES 
 T131   Recursive query         YES 
 T132   Recursive query in subquery         YES 
+T133   Enhanced cycle mark values          YES SQL:202x draft
 T141   SIMILAR predicate           YES 
 T151   DISTINCT predicate          YES 
 T152   DISTINCT predicate with negation            YES 
index dd72a9fc3c4bdc41e2d067c043f8d32a9e25577c..652be0b96da9519035a8e1b9da07257dc1272a77 100644 (file)
@@ -11442,6 +11442,17 @@ opt_cycle_clause:
                n->location = @1;
                $$ = (Node *) n;
            }
+       | CYCLE columnList SET ColId USING ColId
+           {
+               CTECycleClause *n = makeNode(CTECycleClause);
+               n->cycle_col_list = $2;
+               n->cycle_mark_column = $4;
+               n->cycle_mark_value = makeBoolAConst(true, -1);
+               n->cycle_mark_default = makeBoolAConst(false, -1);
+               n->cycle_path_column = $6;
+               n->location = @1;
+               $$ = (Node *) n;
+           }
        | /*EMPTY*/
            {
                $$ = NULL;
index 4a9244f4f665af3f19aee19bca769ee78bf2a71f..879288c1394489dc08f8f786ff21b0d989fd279b 100644 (file)
@@ -5208,10 +5208,21 @@ get_with_clause(Query *query, deparse_context *context)
            }
 
            appendStringInfo(buf, " SET %s", quote_identifier(cte->cycle_clause->cycle_mark_column));
-           appendStringInfoString(buf, " TO ");
-           get_rule_expr(cte->cycle_clause->cycle_mark_value, context, false);
-           appendStringInfoString(buf, " DEFAULT ");
-           get_rule_expr(cte->cycle_clause->cycle_mark_default, context, false);
+
+           {
+               Const      *cmv = castNode(Const, cte->cycle_clause->cycle_mark_value);
+               Const      *cmd = castNode(Const, cte->cycle_clause->cycle_mark_default);
+
+               if (!(cmv->consttype == BOOLOID && !cmv->constisnull && DatumGetBool(cmv->constvalue) == true &&
+                     cmd->consttype == BOOLOID && !cmd->constisnull && DatumGetBool(cmd->constvalue) == false))
+               {
+                   appendStringInfoString(buf, " TO ");
+                   get_rule_expr(cte->cycle_clause->cycle_mark_value, context, false);
+                   appendStringInfoString(buf, " DEFAULT ");
+                   get_rule_expr(cte->cycle_clause->cycle_mark_default, context, false);
+               }
+           }
+
            appendStringInfo(buf, " USING %s", quote_identifier(cte->cycle_clause->cycle_path_column));
        }
 
index a7a652822c9143f07038eaf9a7056faa802ca314..9a6b716ddccf04f04eea91ad3c53a73728ca300a 100644 (file)
@@ -951,7 +951,7 @@ with recursive search_graph(f, t, label) as (
    select g.*
    from graph g, search_graph sg
    where g.f = sg.t
-) cycle f, t set is_cycle to true default false using path
+) cycle f, t set is_cycle using path
 select * from search_graph;
  f | t |   label    | is_cycle |                   path                    
 ---+---+------------+----------+-------------------------------------------
@@ -1071,7 +1071,7 @@ with recursive a as (
    select 1 as b
    union all
    select * from a
-) cycle b set c to true default false using p
+) cycle b set c using p
 select * from a;
  b | c |     p     
 ---+---+-----------
@@ -1087,7 +1087,7 @@ with recursive search_graph(f, t, label) as (
    from graph g, search_graph sg
    where g.f = sg.t
 ) search depth first by f, t set seq
-  cycle f, t set is_cycle to true default false using path
+  cycle f, t set is_cycle using path
 select * from search_graph;
  f | t |   label    |                    seq                    | is_cycle |                   path                    
 ---+---+------------+-------------------------------------------+----------+-------------------------------------------
@@ -1125,7 +1125,7 @@ with recursive search_graph(f, t, label) as (
    from graph g, search_graph sg
    where g.f = sg.t
 ) search breadth first by f, t set seq
-  cycle f, t set is_cycle to true default false using path
+  cycle f, t set is_cycle using path
 select * from search_graph;
  f | t |   label    |   seq   | is_cycle |                   path                    
 ---+---+------------+---------+----------+-------------------------------------------
@@ -1163,10 +1163,10 @@ with recursive search_graph(f, t, label) as (
    select g.*
    from graph g, search_graph sg
    where g.f = sg.t
-) cycle foo, tar set is_cycle to true default false using path
+) cycle foo, tar set is_cycle using path
 select * from search_graph;
 ERROR:  cycle column "foo" not in WITH query column list
-LINE 7: ) cycle foo, tar set is_cycle to true default false using pa...
+LINE 7: ) cycle foo, tar set is_cycle using path
           ^
 with recursive search_graph(f, t, label) as (
    select * from graph g
@@ -1257,38 +1257,99 @@ ERROR:  search_sequence column name and cycle path column name are the same
 LINE 7: ) search depth first by f, t set foo
           ^
 -- test ruleutils and view expansion
-create temp view v_cycle as
+create temp view v_cycle1 as
 with recursive search_graph(f, t, label) as (
    select * from graph g
    union all
    select g.*
    from graph g, search_graph sg
    where g.f = sg.t
-) cycle f, t set is_cycle to true default false using path
+) cycle f, t set is_cycle using path
 select f, t, label from search_graph;
-select pg_get_viewdef('v_cycle');
-                           pg_get_viewdef                           
---------------------------------------------------------------------
-  WITH RECURSIVE search_graph(f, t, label) AS (                    +
-          SELECT g.f,                                              +
-             g.t,                                                  +
-             g.label                                               +
-            FROM graph g                                           +
-         UNION ALL                                                 +
-          SELECT g.f,                                              +
-             g.t,                                                  +
-             g.label                                               +
-            FROM graph g,                                          +
-             search_graph sg                                       +
-           WHERE (g.f = sg.t)                                      +
-         ) CYCLE f, t SET is_cycle TO true DEFAULT false USING path+
-  SELECT search_graph.f,                                           +
-     search_graph.t,                                               +
-     search_graph.label                                            +
+create temp view v_cycle2 as
+with recursive search_graph(f, t, label) as (
+   select * from graph g
+   union all
+   select g.*
+   from graph g, search_graph sg
+   where g.f = sg.t
+) cycle f, t set is_cycle to 'Y' default 'N' using path
+select f, t, label from search_graph;
+select pg_get_viewdef('v_cycle1');
+                 pg_get_viewdef                 
+------------------------------------------------
+  WITH RECURSIVE search_graph(f, t, label) AS (+
+          SELECT g.f,                          +
+             g.t,                              +
+             g.label                           +
+            FROM graph g                       +
+         UNION ALL                             +
+          SELECT g.f,                          +
+             g.t,                              +
+             g.label                           +
+            FROM graph g,                      +
+             search_graph sg                   +
+           WHERE (g.f = sg.t)                  +
+         ) CYCLE f, t SET is_cycle USING path  +
+  SELECT search_graph.f,                       +
+     search_graph.t,                           +
+     search_graph.label                        +
     FROM search_graph;
 (1 row)
 
-select * from v_cycle;
+select pg_get_viewdef('v_cycle2');
+                               pg_get_viewdef                                
+-----------------------------------------------------------------------------
+  WITH RECURSIVE search_graph(f, t, label) AS (                             +
+          SELECT g.f,                                                       +
+             g.t,                                                           +
+             g.label                                                        +
+            FROM graph g                                                    +
+         UNION ALL                                                          +
+          SELECT g.f,                                                       +
+             g.t,                                                           +
+             g.label                                                        +
+            FROM graph g,                                                   +
+             search_graph sg                                                +
+           WHERE (g.f = sg.t)                                               +
+         ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
+  SELECT search_graph.f,                                                    +
+     search_graph.t,                                                        +
+     search_graph.label                                                     +
+    FROM search_graph;
+(1 row)
+
+select * from v_cycle1;
+ f | t |   label    
+---+---+------------
+ 1 | 2 | arc 1 -> 2
+ 1 | 3 | arc 1 -> 3
+ 2 | 3 | arc 2 -> 3
+ 1 | 4 | arc 1 -> 4
+ 4 | 5 | arc 4 -> 5
+ 5 | 1 | arc 5 -> 1
+ 1 | 2 | arc 1 -> 2
+ 1 | 3 | arc 1 -> 3
+ 1 | 4 | arc 1 -> 4
+ 2 | 3 | arc 2 -> 3
+ 4 | 5 | arc 4 -> 5
+ 5 | 1 | arc 5 -> 1
+ 1 | 2 | arc 1 -> 2
+ 1 | 3 | arc 1 -> 3
+ 1 | 4 | arc 1 -> 4
+ 2 | 3 | arc 2 -> 3
+ 4 | 5 | arc 4 -> 5
+ 5 | 1 | arc 5 -> 1
+ 1 | 2 | arc 1 -> 2
+ 1 | 3 | arc 1 -> 3
+ 1 | 4 | arc 1 -> 4
+ 2 | 3 | arc 2 -> 3
+ 4 | 5 | arc 4 -> 5
+ 5 | 1 | arc 5 -> 1
+ 2 | 3 | arc 2 -> 3
+(25 rows)
+
+select * from v_cycle2;
  f | t |   label    
 ---+---+------------
  1 | 2 | arc 1 -> 2
index 85a671c6300a2d621e9214dc225b92ce21013469..1a9bdc9f3ea45d328df981ca91af960d64c25769 100644 (file)
@@ -509,7 +509,7 @@ with recursive search_graph(f, t, label) as (
    select g.*
    from graph g, search_graph sg
    where g.f = sg.t
-) cycle f, t set is_cycle to true default false using path
+) cycle f, t set is_cycle using path
 select * from search_graph;
 
 with recursive search_graph(f, t, label) as (
@@ -545,7 +545,7 @@ with recursive a as (
    select 1 as b
    union all
    select * from a
-) cycle b set c to true default false using p
+) cycle b set c using p
 select * from a;
 
 -- search+cycle
@@ -556,7 +556,7 @@ with recursive search_graph(f, t, label) as (
    from graph g, search_graph sg
    where g.f = sg.t
 ) search depth first by f, t set seq
-  cycle f, t set is_cycle to true default false using path
+  cycle f, t set is_cycle using path
 select * from search_graph;
 
 with recursive search_graph(f, t, label) as (
@@ -566,7 +566,7 @@ with recursive search_graph(f, t, label) as (
    from graph g, search_graph sg
    where g.f = sg.t
 ) search breadth first by f, t set seq
-  cycle f, t set is_cycle to true default false using path
+  cycle f, t set is_cycle using path
 select * from search_graph;
 
 -- various syntax errors
@@ -576,7 +576,7 @@ with recursive search_graph(f, t, label) as (
    select g.*
    from graph g, search_graph sg
    where g.f = sg.t
-) cycle foo, tar set is_cycle to true default false using path
+) cycle foo, tar set is_cycle using path
 select * from search_graph;
 
 with recursive search_graph(f, t, label) as (
@@ -654,19 +654,31 @@ with recursive search_graph(f, t, label) as (
 select * from search_graph;
 
 -- test ruleutils and view expansion
-create temp view v_cycle as
+create temp view v_cycle1 as
 with recursive search_graph(f, t, label) as (
    select * from graph g
    union all
    select g.*
    from graph g, search_graph sg
    where g.f = sg.t
-) cycle f, t set is_cycle to true default false using path
+) cycle f, t set is_cycle using path
+select f, t, label from search_graph;
+
+create temp view v_cycle2 as
+with recursive search_graph(f, t, label) as (
+   select * from graph g
+   union all
+   select g.*
+   from graph g, search_graph sg
+   where g.f = sg.t
+) cycle f, t set is_cycle to 'Y' default 'N' using path
 select f, t, label from search_graph;
 
-select pg_get_viewdef('v_cycle');
+select pg_get_viewdef('v_cycle1');
+select pg_get_viewdef('v_cycle2');
 
-select * from v_cycle;
+select * from v_cycle1;
+select * from v_cycle2;
 
 --
 -- test multiple WITH queries