mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-09-07 04:59:22 +02:00
psql's lexer has historically deleted dash-dash (single-line) comments from what's collected and sent to the server. This is inconsistent with what it does for slash-star comments, and people have complained before that they wish such comments would be captured in the server log. Undoing the decision completely seems like too big a behavioral change, however. In particular, comments on lines preceding the start of a query are generally not thought of as being part of that query. What we can do to improve the situation is to capture comments that are clearly *within* a query, that is after the first non-whitespace, non-comment token but before the query's ending semicolon or backslash command. This is a nearly trivial code change, and it affects only a few regression test results. (It is tempting to try to apply the same rule to slash-star comments. But it's hard to see how to do that without getting strange history behavior for comments that cross lines, especially if the user then starts a new query on the same line as the star-slash. In view of the lack of complaints, let's leave that case alone.) Discussion: https://postgr.es/m/CAJcOf-cAdMVr7azeYR7nWKsNp7qhORzc84rV6d7m7knG5Hrtsw@mail.gmail.com
3168 lines
79 KiB
Plaintext
3168 lines
79 KiB
Plaintext
--
|
|
-- 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)
|
|
|
|
-- UNION DISTINCT requires hashable type
|
|
WITH RECURSIVE t(n) AS (
|
|
VALUES (1::money)
|
|
UNION
|
|
SELECT n+1::money FROM t WHERE n < 100::money
|
|
)
|
|
SELECT sum(n) FROM t;
|
|
ERROR: could not implement recursive UNION
|
|
DETAIL: All column datatypes must be hashable.
|
|
-- 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
|
|
UNION
|
|
SELECT 10-n FROM t)
|
|
SELECT * FROM t;
|
|
n
|
|
---
|
|
1
|
|
9
|
|
(2 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)
|
|
|
|
-- UNION case should have same property
|
|
WITH RECURSIVE t(n) AS (
|
|
SELECT 1
|
|
UNION
|
|
SELECT n+1 FROM t)
|
|
SELECT * FROM t LIMIT 10;
|
|
n
|
|
----
|
|
1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
(10 rows)
|
|
|
|
-- Test behavior with an unknown-type literal in the WITH
|
|
WITH q AS (SELECT 'foo' AS x)
|
|
SELECT x, pg_typeof(x) FROM q;
|
|
x | pg_typeof
|
|
-----+-----------
|
|
foo | text
|
|
(1 row)
|
|
|
|
WITH RECURSIVE t(n) AS (
|
|
SELECT 'foo'
|
|
UNION ALL
|
|
SELECT n || ' bar' FROM t WHERE length(n) < 20
|
|
)
|
|
SELECT n, pg_typeof(n) FROM t;
|
|
n | pg_typeof
|
|
-------------------------+-----------
|
|
foo | text
|
|
foo bar | text
|
|
foo bar bar | text
|
|
foo bar bar bar | text
|
|
foo bar bar bar bar | text
|
|
foo bar bar bar bar bar | text
|
|
(6 rows)
|
|
|
|
-- In a perfect world, this would work and resolve the literal as int ...
|
|
-- but for now, we have to be content with resolving to text too soon.
|
|
WITH RECURSIVE t(n) AS (
|
|
SELECT '7'
|
|
UNION ALL
|
|
SELECT n+1 FROM t WHERE n < 10
|
|
)
|
|
SELECT n, pg_typeof(n) FROM t;
|
|
ERROR: operator does not exist: text + integer
|
|
LINE 4: SELECT n+1 FROM t WHERE n < 10
|
|
^
|
|
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
|
|
-- Deeply nested WITH caused a list-munging problem in v13
|
|
-- Detection of cross-references and self-references
|
|
WITH RECURSIVE w1(c1) AS
|
|
(WITH w2(c2) AS
|
|
(WITH w3(c3) AS
|
|
(WITH w4(c4) AS
|
|
(WITH w5(c5) AS
|
|
(WITH RECURSIVE w6(c6) AS
|
|
(WITH w6(c6) AS
|
|
(WITH w8(c8) AS
|
|
(SELECT 1)
|
|
SELECT * FROM w8)
|
|
SELECT * FROM w6)
|
|
SELECT * FROM w6)
|
|
SELECT * FROM w5)
|
|
SELECT * FROM w4)
|
|
SELECT * FROM w3)
|
|
SELECT * FROM w2)
|
|
SELECT * FROM w1;
|
|
c1
|
|
----
|
|
1
|
|
(1 row)
|
|
|
|
-- Detection of invalid self-references
|
|
WITH RECURSIVE outermost(x) AS (
|
|
SELECT 1
|
|
UNION (WITH innermost1 AS (
|
|
SELECT 2
|
|
UNION (WITH innermost2 AS (
|
|
SELECT 3
|
|
UNION (WITH innermost3 AS (
|
|
SELECT 4
|
|
UNION (WITH innermost4 AS (
|
|
SELECT 5
|
|
UNION (WITH innermost5 AS (
|
|
SELECT 6
|
|
UNION (WITH innermost6 AS
|
|
(SELECT 7)
|
|
SELECT * FROM innermost6))
|
|
SELECT * FROM innermost5))
|
|
SELECT * FROM innermost4))
|
|
SELECT * FROM innermost3))
|
|
SELECT * FROM innermost2))
|
|
SELECT * FROM outermost
|
|
UNION SELECT * FROM innermost1)
|
|
)
|
|
SELECT * FROM outermost ORDER BY 1;
|
|
x
|
|
---
|
|
1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
(7 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
|
|
);
|
|
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 name as root_name, * FROM department WHERE name = 'A'
|
|
UNION ALL
|
|
-- recursive term
|
|
SELECT sd.root_name, d.* FROM department AS d, subdepartment AS sd
|
|
WHERE d.parent_department = sd.id
|
|
)
|
|
SELECT * FROM subdepartment ORDER BY name;
|
|
root_name | id | parent_department | name
|
|
-----------+----+-------------------+------
|
|
A | 1 | 0 | A
|
|
A | 2 | 1 | B
|
|
A | 3 | 2 | C
|
|
A | 4 | 2 | D
|
|
A | 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)
|
|
|
|
-- Another reverse-listing example
|
|
CREATE VIEW sums_1_100 AS
|
|
WITH RECURSIVE t(n) AS (
|
|
VALUES (1)
|
|
UNION ALL
|
|
SELECT n+1 FROM t WHERE n < 100
|
|
)
|
|
SELECT sum(n) FROM t;
|
|
\d+ sums_1_100
|
|
View "public.sums_1_100"
|
|
Column | Type | Collation | Nullable | Default | Storage | Description
|
|
--------+--------+-----------+----------+---------+---------+-------------
|
|
sum | bigint | | | | plain |
|
|
View definition:
|
|
WITH RECURSIVE t(n) AS (
|
|
VALUES (1)
|
|
UNION ALL
|
|
SELECT t_1.n + 1
|
|
FROM t t_1
|
|
WHERE t_1.n < 100
|
|
)
|
|
SELECT sum(t.n) AS sum
|
|
FROM t;
|
|
|
|
-- corner case in which sub-WITH gets initialized first
|
|
with recursive q as (
|
|
select * from department
|
|
union all
|
|
(with x as (select * from q)
|
|
select * from x)
|
|
)
|
|
select * from q limit 24;
|
|
id | parent_department | name
|
|
----+-------------------+------
|
|
0 | | ROOT
|
|
1 | 0 | A
|
|
2 | 1 | B
|
|
3 | 2 | C
|
|
4 | 2 | D
|
|
5 | 0 | E
|
|
6 | 4 | F
|
|
7 | 5 | G
|
|
0 | | ROOT
|
|
1 | 0 | A
|
|
2 | 1 | B
|
|
3 | 2 | C
|
|
4 | 2 | D
|
|
5 | 0 | E
|
|
6 | 4 | F
|
|
7 | 5 | G
|
|
0 | | ROOT
|
|
1 | 0 | A
|
|
2 | 1 | B
|
|
3 | 2 | C
|
|
4 | 2 | D
|
|
5 | 0 | E
|
|
6 | 4 | F
|
|
7 | 5 | G
|
|
(24 rows)
|
|
|
|
with recursive q as (
|
|
select * from department
|
|
union all
|
|
(with recursive x as (
|
|
select * from department
|
|
union all
|
|
(select * from q union all select * from x)
|
|
)
|
|
select * from x)
|
|
)
|
|
select * from q limit 32;
|
|
id | parent_department | name
|
|
----+-------------------+------
|
|
0 | | ROOT
|
|
1 | 0 | A
|
|
2 | 1 | B
|
|
3 | 2 | C
|
|
4 | 2 | D
|
|
5 | 0 | E
|
|
6 | 4 | F
|
|
7 | 5 | G
|
|
0 | | ROOT
|
|
1 | 0 | A
|
|
2 | 1 | B
|
|
3 | 2 | C
|
|
4 | 2 | D
|
|
5 | 0 | E
|
|
6 | 4 | F
|
|
7 | 5 | G
|
|
0 | | ROOT
|
|
1 | 0 | A
|
|
2 | 1 | B
|
|
3 | 2 | C
|
|
4 | 2 | D
|
|
5 | 0 | E
|
|
6 | 4 | F
|
|
7 | 5 | G
|
|
0 | | ROOT
|
|
1 | 0 | A
|
|
2 | 1 | B
|
|
3 | 2 | C
|
|
4 | 2 | D
|
|
5 | 0 | E
|
|
6 | 4 | F
|
|
7 | 5 | G
|
|
(32 rows)
|
|
|
|
-- 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)
|
|
);
|
|
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)
|
|
|
|
-- this variant tickled a whole-row-variable bug in 8.4devel
|
|
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, t2.path, t2 FROM t AS t1 JOIN t AS t2 ON
|
|
(t1.id=t2.id);
|
|
id | path | t2
|
|
----+-------------+--------------------
|
|
1 | {} | (1,{})
|
|
2 | {2} | (2,{2})
|
|
3 | {3} | (3,{3})
|
|
4 | {2,4} | (4,"{2,4}")
|
|
5 | {2,5} | (5,"{2,5}")
|
|
6 | {2,6} | (6,"{2,6}")
|
|
7 | {3,7} | (7,"{3,7}")
|
|
8 | {3,8} | (8,"{3,8}")
|
|
9 | {2,4,9} | (9,"{2,4,9}")
|
|
10 | {2,4,10} | (10,"{2,4,10}")
|
|
11 | {3,7,11} | (11,"{3,7,11}")
|
|
12 | {3,7,12} | (12,"{3,7,12}")
|
|
13 | {3,7,13} | (13,"{3,7,13}")
|
|
14 | {2,4,9,14} | (14,"{2,4,9,14}")
|
|
15 | {3,7,11,15} | (15,"{3,7,11,15}")
|
|
16 | {3,7,11,16} | (16,"{3,7,11,16}")
|
|
(16 rows)
|
|
|
|
-- SEARCH clause
|
|
create temp table graph0( f int, t int, label text );
|
|
insert into graph0 values
|
|
(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');
|
|
explain (verbose, costs off)
|
|
with recursive search_graph(f, t, label) as (
|
|
select * from graph0 g
|
|
union all
|
|
select g.*
|
|
from graph0 g, search_graph sg
|
|
where g.f = sg.t
|
|
) search depth first by f, t set seq
|
|
select * from search_graph order by seq;
|
|
QUERY PLAN
|
|
----------------------------------------------------------------------------------------------
|
|
Sort
|
|
Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq
|
|
Sort Key: search_graph.seq
|
|
CTE search_graph
|
|
-> Recursive Union
|
|
-> Seq Scan on pg_temp.graph0 g
|
|
Output: g.f, g.t, g.label, ARRAY[ROW(g.f, g.t)]
|
|
-> Merge Join
|
|
Output: g_1.f, g_1.t, g_1.label, array_cat(sg.seq, ARRAY[ROW(g_1.f, g_1.t)])
|
|
Merge Cond: (g_1.f = sg.t)
|
|
-> Sort
|
|
Output: g_1.f, g_1.t, g_1.label
|
|
Sort Key: g_1.f
|
|
-> Seq Scan on pg_temp.graph0 g_1
|
|
Output: g_1.f, g_1.t, g_1.label
|
|
-> Sort
|
|
Output: sg.seq, sg.t
|
|
Sort Key: sg.t
|
|
-> WorkTable Scan on search_graph sg
|
|
Output: sg.seq, sg.t
|
|
-> CTE Scan on search_graph
|
|
Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq
|
|
(22 rows)
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
select * from graph0 g
|
|
union all
|
|
select g.*
|
|
from graph0 g, search_graph sg
|
|
where g.f = sg.t
|
|
) search depth first by f, t set seq
|
|
select * from search_graph order by seq;
|
|
f | t | label | seq
|
|
---+---+------------+-------------------
|
|
1 | 2 | arc 1 -> 2 | {"(1,2)"}
|
|
2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"}
|
|
1 | 3 | arc 1 -> 3 | {"(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | {"(1,4)"}
|
|
4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"}
|
|
2 | 3 | arc 2 -> 3 | {"(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | {"(4,5)"}
|
|
(7 rows)
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
select * from graph0 g
|
|
union distinct
|
|
select g.*
|
|
from graph0 g, search_graph sg
|
|
where g.f = sg.t
|
|
) search depth first by f, t set seq
|
|
select * from search_graph order by seq;
|
|
f | t | label | seq
|
|
---+---+------------+-------------------
|
|
1 | 2 | arc 1 -> 2 | {"(1,2)"}
|
|
2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"}
|
|
1 | 3 | arc 1 -> 3 | {"(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | {"(1,4)"}
|
|
4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"}
|
|
2 | 3 | arc 2 -> 3 | {"(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | {"(4,5)"}
|
|
(7 rows)
|
|
|
|
explain (verbose, costs off)
|
|
with recursive search_graph(f, t, label) as (
|
|
select * from graph0 g
|
|
union all
|
|
select g.*
|
|
from graph0 g, search_graph sg
|
|
where g.f = sg.t
|
|
) search breadth first by f, t set seq
|
|
select * from search_graph order by seq;
|
|
QUERY PLAN
|
|
-------------------------------------------------------------------------------------------------
|
|
Sort
|
|
Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq
|
|
Sort Key: search_graph.seq
|
|
CTE search_graph
|
|
-> Recursive Union
|
|
-> Seq Scan on pg_temp.graph0 g
|
|
Output: g.f, g.t, g.label, ROW('0'::bigint, g.f, g.t)
|
|
-> Merge Join
|
|
Output: g_1.f, g_1.t, g_1.label, ROW(int8inc((sg.seq)."*DEPTH*"), g_1.f, g_1.t)
|
|
Merge Cond: (g_1.f = sg.t)
|
|
-> Sort
|
|
Output: g_1.f, g_1.t, g_1.label
|
|
Sort Key: g_1.f
|
|
-> Seq Scan on pg_temp.graph0 g_1
|
|
Output: g_1.f, g_1.t, g_1.label
|
|
-> Sort
|
|
Output: sg.seq, sg.t
|
|
Sort Key: sg.t
|
|
-> WorkTable Scan on search_graph sg
|
|
Output: sg.seq, sg.t
|
|
-> CTE Scan on search_graph
|
|
Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq
|
|
(22 rows)
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
select * from graph0 g
|
|
union all
|
|
select g.*
|
|
from graph0 g, search_graph sg
|
|
where g.f = sg.t
|
|
) search breadth first by f, t set seq
|
|
select * from search_graph order by seq;
|
|
f | t | label | seq
|
|
---+---+------------+---------
|
|
1 | 2 | arc 1 -> 2 | (0,1,2)
|
|
1 | 3 | arc 1 -> 3 | (0,1,3)
|
|
1 | 4 | arc 1 -> 4 | (0,1,4)
|
|
2 | 3 | arc 2 -> 3 | (0,2,3)
|
|
4 | 5 | arc 4 -> 5 | (0,4,5)
|
|
2 | 3 | arc 2 -> 3 | (1,2,3)
|
|
4 | 5 | arc 4 -> 5 | (1,4,5)
|
|
(7 rows)
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
select * from graph0 g
|
|
union distinct
|
|
select g.*
|
|
from graph0 g, search_graph sg
|
|
where g.f = sg.t
|
|
) search breadth first by f, t set seq
|
|
select * from search_graph order by seq;
|
|
f | t | label | seq
|
|
---+---+------------+---------
|
|
1 | 2 | arc 1 -> 2 | (0,1,2)
|
|
1 | 3 | arc 1 -> 3 | (0,1,3)
|
|
1 | 4 | arc 1 -> 4 | (0,1,4)
|
|
2 | 3 | arc 2 -> 3 | (0,2,3)
|
|
4 | 5 | arc 4 -> 5 | (0,4,5)
|
|
2 | 3 | arc 2 -> 3 | (1,2,3)
|
|
4 | 5 | arc 4 -> 5 | (1,4,5)
|
|
(7 rows)
|
|
|
|
-- various syntax errors
|
|
with recursive search_graph(f, t, label) as (
|
|
select * from graph0 g
|
|
union all
|
|
select g.*
|
|
from graph0 g, search_graph sg
|
|
where g.f = sg.t
|
|
) search depth first by foo, tar set seq
|
|
select * from search_graph;
|
|
ERROR: search column "foo" not in WITH query column list
|
|
LINE 7: ) search depth first by foo, tar set seq
|
|
^
|
|
with recursive search_graph(f, t, label) as (
|
|
select * from graph0 g
|
|
union all
|
|
select g.*
|
|
from graph0 g, search_graph sg
|
|
where g.f = sg.t
|
|
) search depth first by f, t set label
|
|
select * from search_graph;
|
|
ERROR: search sequence column name "label" already used in WITH query column list
|
|
LINE 7: ) search depth first by f, t set label
|
|
^
|
|
with recursive search_graph(f, t, label) as (
|
|
select * from graph0 g
|
|
union all
|
|
select g.*
|
|
from graph0 g, search_graph sg
|
|
where g.f = sg.t
|
|
) search depth first by f, t, f set seq
|
|
select * from search_graph;
|
|
ERROR: search column "f" specified more than once
|
|
LINE 7: ) search depth first by f, t, f set seq
|
|
^
|
|
with recursive search_graph(f, t, label) as (
|
|
select * from graph0 g
|
|
union all
|
|
select * from graph0 g
|
|
union all
|
|
select g.*
|
|
from graph0 g, search_graph sg
|
|
where g.f = sg.t
|
|
) search depth first by f, t set seq
|
|
select * from search_graph order by seq;
|
|
ERROR: with a SEARCH or CYCLE clause, the left side of the UNION must be a SELECT
|
|
with recursive search_graph(f, t, label) as (
|
|
select * from graph0 g
|
|
union all
|
|
(select * from graph0 g
|
|
union all
|
|
select g.*
|
|
from graph0 g, search_graph sg
|
|
where g.f = sg.t)
|
|
) search depth first by f, t set seq
|
|
select * from search_graph order by seq;
|
|
ERROR: with a SEARCH or CYCLE clause, the right side of the UNION must be a SELECT
|
|
-- test ruleutils and view expansion
|
|
create temp view v_search as
|
|
with recursive search_graph(f, t, label) as (
|
|
select * from graph0 g
|
|
union all
|
|
select g.*
|
|
from graph0 g, search_graph sg
|
|
where g.f = sg.t
|
|
) search depth first by f, t set seq
|
|
select f, t, label from search_graph;
|
|
select pg_get_viewdef('v_search');
|
|
pg_get_viewdef
|
|
------------------------------------------------
|
|
WITH RECURSIVE search_graph(f, t, label) AS (+
|
|
SELECT g.f, +
|
|
g.t, +
|
|
g.label +
|
|
FROM graph0 g +
|
|
UNION ALL +
|
|
SELECT g.f, +
|
|
g.t, +
|
|
g.label +
|
|
FROM graph0 g, +
|
|
search_graph sg +
|
|
WHERE (g.f = sg.t) +
|
|
) SEARCH DEPTH FIRST BY f, t SET seq +
|
|
SELECT search_graph.f, +
|
|
search_graph.t, +
|
|
search_graph.label +
|
|
FROM search_graph;
|
|
(1 row)
|
|
|
|
select * from v_search;
|
|
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
|
|
2 | 3 | arc 2 -> 3
|
|
4 | 5 | arc 4 -> 5
|
|
(7 rows)
|
|
|
|
--
|
|
-- test cycle detection
|
|
--
|
|
create temp table graph( f int, t int, label text );
|
|
insert into graph values
|
|
(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');
|
|
with recursive search_graph(f, t, label, is_cycle, path) as (
|
|
select *, false, array[row(g.f, g.t)] from graph g
|
|
union all
|
|
select g.*, row(g.f, g.t) = any(path), path || row(g.f, g.t)
|
|
from graph g, search_graph sg
|
|
where g.f = sg.t and not is_cycle
|
|
)
|
|
select * from search_graph;
|
|
f | t | label | is_cycle | path
|
|
---+---+------------+----------+-------------------------------------------
|
|
1 | 2 | arc 1 -> 2 | f | {"(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | f | {"(1,3)"}
|
|
2 | 3 | arc 2 -> 3 | f | {"(2,3)"}
|
|
1 | 4 | arc 1 -> 4 | f | {"(1,4)"}
|
|
4 | 5 | arc 4 -> 5 | f | {"(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | f | {"(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"}
|
|
2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"}
|
|
2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
|
|
2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
|
|
2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
(25 rows)
|
|
|
|
-- UNION DISTINCT exercises row type hashing support
|
|
with recursive search_graph(f, t, label, is_cycle, path) as (
|
|
select *, false, array[row(g.f, g.t)] from graph g
|
|
union distinct
|
|
select g.*, row(g.f, g.t) = any(path), path || row(g.f, g.t)
|
|
from graph g, search_graph sg
|
|
where g.f = sg.t and not is_cycle
|
|
)
|
|
select * from search_graph;
|
|
f | t | label | is_cycle | path
|
|
---+---+------------+----------+-------------------------------------------
|
|
1 | 2 | arc 1 -> 2 | f | {"(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | f | {"(1,3)"}
|
|
2 | 3 | arc 2 -> 3 | f | {"(2,3)"}
|
|
1 | 4 | arc 1 -> 4 | f | {"(1,4)"}
|
|
4 | 5 | arc 4 -> 5 | f | {"(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | f | {"(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"}
|
|
2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"}
|
|
2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
|
|
2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
|
|
2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
(25 rows)
|
|
|
|
-- ordering by the path column has same effect as SEARCH DEPTH FIRST
|
|
with recursive search_graph(f, t, label, is_cycle, path) as (
|
|
select *, false, array[row(g.f, g.t)] from graph g
|
|
union all
|
|
select g.*, row(g.f, g.t) = any(path), path || row(g.f, g.t)
|
|
from graph g, search_graph sg
|
|
where g.f = sg.t and not is_cycle
|
|
)
|
|
select * from search_graph order by path;
|
|
f | t | label | is_cycle | path
|
|
---+---+------------+----------+-------------------------------------------
|
|
1 | 2 | arc 1 -> 2 | f | {"(1,2)"}
|
|
2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"}
|
|
1 | 3 | arc 1 -> 3 | f | {"(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | f | {"(1,4)"}
|
|
4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
|
|
2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
|
|
2 | 3 | arc 2 -> 3 | f | {"(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | f | {"(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"}
|
|
2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"}
|
|
4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | f | {"(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"}
|
|
2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"}
|
|
1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"}
|
|
4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
|
|
(25 rows)
|
|
|
|
-- CYCLE clause
|
|
explain (verbose, costs off)
|
|
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 using path
|
|
select * from search_graph;
|
|
QUERY PLAN
|
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
CTE Scan on search_graph
|
|
Output: search_graph.f, search_graph.t, search_graph.label, search_graph.is_cycle, search_graph.path
|
|
CTE search_graph
|
|
-> Recursive Union
|
|
-> Seq Scan on pg_temp.graph g
|
|
Output: g.f, g.t, g.label, false, ARRAY[ROW(g.f, g.t)]
|
|
-> Merge Join
|
|
Output: g_1.f, g_1.t, g_1.label, CASE WHEN (ROW(g_1.f, g_1.t) = ANY (sg.path)) THEN true ELSE false END, array_cat(sg.path, ARRAY[ROW(g_1.f, g_1.t)])
|
|
Merge Cond: (g_1.f = sg.t)
|
|
-> Sort
|
|
Output: g_1.f, g_1.t, g_1.label
|
|
Sort Key: g_1.f
|
|
-> Seq Scan on pg_temp.graph g_1
|
|
Output: g_1.f, g_1.t, g_1.label
|
|
-> Sort
|
|
Output: sg.path, sg.t
|
|
Sort Key: sg.t
|
|
-> WorkTable Scan on search_graph sg
|
|
Output: sg.path, sg.t
|
|
Filter: (NOT sg.is_cycle)
|
|
(20 rows)
|
|
|
|
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 using path
|
|
select * from search_graph;
|
|
f | t | label | is_cycle | path
|
|
---+---+------------+----------+-------------------------------------------
|
|
1 | 2 | arc 1 -> 2 | f | {"(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | f | {"(1,3)"}
|
|
2 | 3 | arc 2 -> 3 | f | {"(2,3)"}
|
|
1 | 4 | arc 1 -> 4 | f | {"(1,4)"}
|
|
4 | 5 | arc 4 -> 5 | f | {"(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | f | {"(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"}
|
|
2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"}
|
|
2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
|
|
2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
|
|
2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
(25 rows)
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
select * from graph g
|
|
union distinct
|
|
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 * from search_graph;
|
|
f | t | label | is_cycle | path
|
|
---+---+------------+----------+-------------------------------------------
|
|
1 | 2 | arc 1 -> 2 | N | {"(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | N | {"(1,3)"}
|
|
2 | 3 | arc 2 -> 3 | N | {"(2,3)"}
|
|
1 | 4 | arc 1 -> 4 | N | {"(1,4)"}
|
|
4 | 5 | arc 4 -> 5 | N | {"(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | N | {"(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | N | {"(5,1)","(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | N | {"(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | N | {"(5,1)","(1,4)"}
|
|
2 | 3 | arc 2 -> 3 | N | {"(1,2)","(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | N | {"(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | N | {"(4,5)","(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | N | {"(4,5)","(5,1)","(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | N | {"(4,5)","(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | N | {"(4,5)","(5,1)","(1,4)"}
|
|
2 | 3 | arc 2 -> 3 | N | {"(5,1)","(1,2)","(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | N | {"(5,1)","(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | N | {"(1,4)","(4,5)","(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | N | {"(1,4)","(4,5)","(5,1)","(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | N | {"(1,4)","(4,5)","(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | Y | {"(1,4)","(4,5)","(5,1)","(1,4)"}
|
|
2 | 3 | arc 2 -> 3 | N | {"(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | Y | {"(4,5)","(5,1)","(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | Y | {"(5,1)","(1,4)","(4,5)","(5,1)"}
|
|
2 | 3 | arc 2 -> 3 | N | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
(25 rows)
|
|
|
|
-- multiple CTEs
|
|
with recursive
|
|
graph(f, t, label) as (
|
|
values (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')
|
|
),
|
|
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
|
|
select f, t, label from search_graph;
|
|
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
|
|
2 | 3 | arc 2 -> 3
|
|
4 | 5 | arc 4 -> 5
|
|
5 | 1 | arc 5 -> 1
|
|
1 | 4 | arc 1 -> 4
|
|
1 | 3 | arc 1 -> 3
|
|
1 | 2 | arc 1 -> 2
|
|
5 | 1 | arc 5 -> 1
|
|
1 | 4 | arc 1 -> 4
|
|
1 | 3 | arc 1 -> 3
|
|
1 | 2 | arc 1 -> 2
|
|
4 | 5 | arc 4 -> 5
|
|
2 | 3 | arc 2 -> 3
|
|
1 | 4 | arc 1 -> 4
|
|
1 | 3 | arc 1 -> 3
|
|
1 | 2 | arc 1 -> 2
|
|
4 | 5 | arc 4 -> 5
|
|
2 | 3 | arc 2 -> 3
|
|
5 | 1 | arc 5 -> 1
|
|
2 | 3 | arc 2 -> 3
|
|
(25 rows)
|
|
|
|
-- star expansion
|
|
with recursive a as (
|
|
select 1 as b
|
|
union all
|
|
select * from a
|
|
) cycle b set c using p
|
|
select * from a;
|
|
b | c | p
|
|
---+---+-----------
|
|
1 | f | {(1)}
|
|
1 | t | {(1),(1)}
|
|
(2 rows)
|
|
|
|
-- search+cycle
|
|
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
|
|
) search depth first by f, t set seq
|
|
cycle f, t set is_cycle using path
|
|
select * from search_graph;
|
|
f | t | label | seq | is_cycle | path
|
|
---+---+------------+-------------------------------------------+----------+-------------------------------------------
|
|
1 | 2 | arc 1 -> 2 | {"(1,2)"} | f | {"(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | {"(1,3)"} | f | {"(1,3)"}
|
|
2 | 3 | arc 2 -> 3 | {"(2,3)"} | f | {"(2,3)"}
|
|
1 | 4 | arc 1 -> 4 | {"(1,4)"} | f | {"(1,4)"}
|
|
4 | 5 | arc 4 -> 5 | {"(4,5)"} | f | {"(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | {"(5,1)"} | f | {"(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | {"(5,1)","(1,2)"} | f | {"(5,1)","(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | {"(5,1)","(1,3)"} | f | {"(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | {"(5,1)","(1,4)"} | f | {"(5,1)","(1,4)"}
|
|
2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"} | f | {"(1,2)","(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"} | f | {"(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | {"(4,5)","(5,1)"} | f | {"(4,5)","(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | {"(4,5)","(5,1)","(1,2)"} | f | {"(4,5)","(5,1)","(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | {"(4,5)","(5,1)","(1,3)"} | f | {"(4,5)","(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | {"(4,5)","(5,1)","(1,4)"} | f | {"(4,5)","(5,1)","(1,4)"}
|
|
2 | 3 | arc 2 -> 3 | {"(5,1)","(1,2)","(2,3)"} | f | {"(5,1)","(1,2)","(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | {"(5,1)","(1,4)","(4,5)"} | f | {"(5,1)","(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | {"(1,4)","(4,5)","(5,1)"} | f | {"(1,4)","(4,5)","(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | {"(1,4)","(4,5)","(5,1)","(1,2)"} | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,3)"} | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | {"(1,4)","(4,5)","(5,1)","(1,4)"} | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
|
|
2 | 3 | arc 2 -> 3 | {"(4,5)","(5,1)","(1,2)","(2,3)"} | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | {"(4,5)","(5,1)","(1,4)","(4,5)"} | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | {"(5,1)","(1,4)","(4,5)","(5,1)"} | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
|
|
2 | 3 | arc 2 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
(25 rows)
|
|
|
|
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
|
|
) search breadth first by f, t set seq
|
|
cycle f, t set is_cycle using path
|
|
select * from search_graph;
|
|
f | t | label | seq | is_cycle | path
|
|
---+---+------------+---------+----------+-------------------------------------------
|
|
1 | 2 | arc 1 -> 2 | (0,1,2) | f | {"(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | (0,1,3) | f | {"(1,3)"}
|
|
2 | 3 | arc 2 -> 3 | (0,2,3) | f | {"(2,3)"}
|
|
1 | 4 | arc 1 -> 4 | (0,1,4) | f | {"(1,4)"}
|
|
4 | 5 | arc 4 -> 5 | (0,4,5) | f | {"(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | (0,5,1) | f | {"(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | (1,1,2) | f | {"(5,1)","(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | (1,1,3) | f | {"(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | (1,1,4) | f | {"(5,1)","(1,4)"}
|
|
2 | 3 | arc 2 -> 3 | (1,2,3) | f | {"(1,2)","(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | (1,4,5) | f | {"(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | (1,5,1) | f | {"(4,5)","(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | (2,1,2) | f | {"(4,5)","(5,1)","(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | (2,1,3) | f | {"(4,5)","(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | (2,1,4) | f | {"(4,5)","(5,1)","(1,4)"}
|
|
2 | 3 | arc 2 -> 3 | (2,2,3) | f | {"(5,1)","(1,2)","(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | (2,4,5) | f | {"(5,1)","(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | (2,5,1) | f | {"(1,4)","(4,5)","(5,1)"}
|
|
1 | 2 | arc 1 -> 2 | (3,1,2) | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
|
|
1 | 3 | arc 1 -> 3 | (3,1,3) | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
|
|
1 | 4 | arc 1 -> 4 | (3,1,4) | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
|
|
2 | 3 | arc 2 -> 3 | (3,2,3) | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
4 | 5 | arc 4 -> 5 | (3,4,5) | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
|
|
5 | 1 | arc 5 -> 1 | (3,5,1) | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
|
|
2 | 3 | arc 2 -> 3 | (4,2,3) | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
(25 rows)
|
|
|
|
-- various syntax errors
|
|
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 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 using path
|
|
^
|
|
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 55 using path
|
|
select * from search_graph;
|
|
ERROR: CYCLE types boolean and integer cannot be matched
|
|
LINE 7: ) cycle f, t set is_cycle to true default 55 using path
|
|
^
|
|
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 point '(1,1)' default point '(0,0)' using path
|
|
select * from search_graph;
|
|
ERROR: could not identify an equality operator for type point
|
|
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 label to true default false using path
|
|
select * from search_graph;
|
|
ERROR: cycle mark column name "label" already used in WITH query column list
|
|
LINE 7: ) cycle f, t set label to true default false using path
|
|
^
|
|
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 label
|
|
select * from search_graph;
|
|
ERROR: cycle path column name "label" already used in WITH query column list
|
|
LINE 7: ) cycle f, t set is_cycle to true default false using label
|
|
^
|
|
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 foo to true default false using foo
|
|
select * from search_graph;
|
|
ERROR: cycle mark column name and cycle path column name are the same
|
|
LINE 7: ) cycle f, t set foo to true default false using foo
|
|
^
|
|
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, f set is_cycle to true default false using path
|
|
select * from search_graph;
|
|
ERROR: cycle column "f" specified more than once
|
|
LINE 7: ) cycle f, t, f set is_cycle to true default false using pat...
|
|
^
|
|
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
|
|
) search depth first by f, t set foo
|
|
cycle f, t set foo to true default false using path
|
|
select * from search_graph;
|
|
ERROR: search sequence column name and cycle mark column name are the same
|
|
LINE 7: ) search depth first by f, t set foo
|
|
^
|
|
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
|
|
) search depth first by f, t set foo
|
|
cycle f, t set is_cycle to true default false using foo
|
|
select * from search_graph;
|
|
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_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 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_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 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
|
|
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)
|
|
|
|
--
|
|
-- 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)
|
|
|
|
--
|
|
-- Test WITH attached to a data-modifying statement
|
|
--
|
|
CREATE TEMPORARY TABLE y (a INTEGER);
|
|
INSERT INTO y SELECT generate_series(1, 10);
|
|
WITH t AS (
|
|
SELECT a FROM y
|
|
)
|
|
INSERT INTO y
|
|
SELECT a+20 FROM t RETURNING *;
|
|
a
|
|
----
|
|
21
|
|
22
|
|
23
|
|
24
|
|
25
|
|
26
|
|
27
|
|
28
|
|
29
|
|
30
|
|
(10 rows)
|
|
|
|
SELECT * FROM y;
|
|
a
|
|
----
|
|
1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
21
|
|
22
|
|
23
|
|
24
|
|
25
|
|
26
|
|
27
|
|
28
|
|
29
|
|
30
|
|
(20 rows)
|
|
|
|
WITH t AS (
|
|
SELECT a FROM y
|
|
)
|
|
UPDATE y SET a = y.a-10 FROM t WHERE y.a > 20 AND t.a = y.a RETURNING y.a;
|
|
a
|
|
----
|
|
11
|
|
12
|
|
13
|
|
14
|
|
15
|
|
16
|
|
17
|
|
18
|
|
19
|
|
20
|
|
(10 rows)
|
|
|
|
SELECT * FROM y;
|
|
a
|
|
----
|
|
1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
11
|
|
12
|
|
13
|
|
14
|
|
15
|
|
16
|
|
17
|
|
18
|
|
19
|
|
20
|
|
(20 rows)
|
|
|
|
WITH RECURSIVE t(a) AS (
|
|
SELECT 11
|
|
UNION ALL
|
|
SELECT a+1 FROM t WHERE a < 50
|
|
)
|
|
DELETE FROM y USING t WHERE t.a = y.a RETURNING y.a;
|
|
a
|
|
----
|
|
11
|
|
12
|
|
13
|
|
14
|
|
15
|
|
16
|
|
17
|
|
18
|
|
19
|
|
20
|
|
(10 rows)
|
|
|
|
SELECT * FROM y;
|
|
a
|
|
----
|
|
1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
(10 rows)
|
|
|
|
DROP TABLE y;
|
|
--
|
|
-- error cases
|
|
--
|
|
-- 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: aggregate functions are 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: aggregate functions are 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.
|
|
-- disallow OLD/NEW reference in CTE
|
|
CREATE TEMPORARY TABLE x (n integer);
|
|
CREATE RULE r2 AS ON UPDATE TO x DO INSTEAD
|
|
WITH t AS (SELECT OLD.*) UPDATE y SET a = t.n FROM t;
|
|
ERROR: cannot refer to OLD within WITH query
|
|
--
|
|
-- test for bug #4902
|
|
--
|
|
with cte(foo) as ( values(42) ) values((select foo from cte));
|
|
column1
|
|
---------
|
|
42
|
|
(1 row)
|
|
|
|
with cte(foo) as ( select 42 ) select * from ((select foo from cte)) q;
|
|
foo
|
|
-----
|
|
42
|
|
(1 row)
|
|
|
|
-- test CTE referencing an outer-level variable (to see that changed-parameter
|
|
-- signaling still works properly after fixing this bug)
|
|
select ( with cte(foo) as ( values(f1) )
|
|
select (select foo from cte) )
|
|
from int4_tbl;
|
|
foo
|
|
-------------
|
|
0
|
|
123456
|
|
-123456
|
|
2147483647
|
|
-2147483647
|
|
(5 rows)
|
|
|
|
select ( with cte(foo) as ( values(f1) )
|
|
values((select foo from cte)) )
|
|
from int4_tbl;
|
|
column1
|
|
-------------
|
|
0
|
|
123456
|
|
-123456
|
|
2147483647
|
|
-2147483647
|
|
(5 rows)
|
|
|
|
--
|
|
-- test for nested-recursive-WITH bug
|
|
--
|
|
WITH RECURSIVE t(j) AS (
|
|
WITH RECURSIVE s(i) AS (
|
|
VALUES (1)
|
|
UNION ALL
|
|
SELECT i+1 FROM s WHERE i < 10
|
|
)
|
|
SELECT i FROM s
|
|
UNION ALL
|
|
SELECT j+1 FROM t WHERE j < 10
|
|
)
|
|
SELECT * FROM t;
|
|
j
|
|
----
|
|
1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
7
|
|
8
|
|
9
|
|
10
|
|
8
|
|
9
|
|
10
|
|
9
|
|
10
|
|
10
|
|
(55 rows)
|
|
|
|
--
|
|
-- test WITH attached to intermediate-level set operation
|
|
--
|
|
WITH outermost(x) AS (
|
|
SELECT 1
|
|
UNION (WITH innermost as (SELECT 2)
|
|
SELECT * FROM innermost
|
|
UNION SELECT 3)
|
|
)
|
|
SELECT * FROM outermost ORDER BY 1;
|
|
x
|
|
---
|
|
1
|
|
2
|
|
3
|
|
(3 rows)
|
|
|
|
WITH outermost(x) AS (
|
|
SELECT 1
|
|
UNION (WITH innermost as (SELECT 2)
|
|
SELECT * FROM outermost -- fail
|
|
UNION SELECT * FROM innermost)
|
|
)
|
|
SELECT * FROM outermost ORDER BY 1;
|
|
ERROR: relation "outermost" does not exist
|
|
LINE 4: SELECT * FROM outermost -- fail
|
|
^
|
|
DETAIL: There is a WITH item named "outermost", but it cannot be referenced from this part of the query.
|
|
HINT: Use WITH RECURSIVE, or re-order the WITH items to remove forward references.
|
|
WITH RECURSIVE outermost(x) AS (
|
|
SELECT 1
|
|
UNION (WITH innermost as (SELECT 2)
|
|
SELECT * FROM outermost
|
|
UNION SELECT * FROM innermost)
|
|
)
|
|
SELECT * FROM outermost ORDER BY 1;
|
|
x
|
|
---
|
|
1
|
|
2
|
|
(2 rows)
|
|
|
|
WITH RECURSIVE outermost(x) AS (
|
|
WITH innermost as (SELECT 2 FROM outermost) -- fail
|
|
SELECT * FROM innermost
|
|
UNION SELECT * from outermost
|
|
)
|
|
SELECT * FROM outermost ORDER BY 1;
|
|
ERROR: recursive reference to query "outermost" must not appear within a subquery
|
|
LINE 2: WITH innermost as (SELECT 2 FROM outermost) -- fail
|
|
^
|
|
--
|
|
-- This test will fail with the old implementation of PARAM_EXEC parameter
|
|
-- assignment, because the "q1" Var passed down to A's targetlist subselect
|
|
-- looks exactly like the "A.id" Var passed down to C's subselect, causing
|
|
-- the old code to give them the same runtime PARAM_EXEC slot. But the
|
|
-- lifespans of the two parameters overlap, thanks to B also reading A.
|
|
--
|
|
with
|
|
A as ( select q2 as id, (select q1) as x from int8_tbl ),
|
|
B as ( select id, row_number() over (partition by id) as r from A ),
|
|
C as ( select A.id, array(select B.id from B where B.id = A.id) from A )
|
|
select * from C;
|
|
id | array
|
|
-------------------+-------------------------------------
|
|
456 | {456}
|
|
4567890123456789 | {4567890123456789,4567890123456789}
|
|
123 | {123}
|
|
4567890123456789 | {4567890123456789,4567890123456789}
|
|
-4567890123456789 | {-4567890123456789}
|
|
(5 rows)
|
|
|
|
--
|
|
-- Test CTEs read in non-initialization orders
|
|
--
|
|
WITH RECURSIVE
|
|
tab(id_key,link) AS (VALUES (1,17), (2,17), (3,17), (4,17), (6,17), (5,17)),
|
|
iter (id_key, row_type, link) AS (
|
|
SELECT 0, 'base', 17
|
|
UNION ALL (
|
|
WITH remaining(id_key, row_type, link, min) AS (
|
|
SELECT tab.id_key, 'true'::text, iter.link, MIN(tab.id_key) OVER ()
|
|
FROM tab INNER JOIN iter USING (link)
|
|
WHERE tab.id_key > iter.id_key
|
|
),
|
|
first_remaining AS (
|
|
SELECT id_key, row_type, link
|
|
FROM remaining
|
|
WHERE id_key=min
|
|
),
|
|
effect AS (
|
|
SELECT tab.id_key, 'new'::text, tab.link
|
|
FROM first_remaining e INNER JOIN tab ON e.id_key=tab.id_key
|
|
WHERE e.row_type = 'false'
|
|
)
|
|
SELECT * FROM first_remaining
|
|
UNION ALL SELECT * FROM effect
|
|
)
|
|
)
|
|
SELECT * FROM iter;
|
|
id_key | row_type | link
|
|
--------+----------+------
|
|
0 | base | 17
|
|
1 | true | 17
|
|
2 | true | 17
|
|
3 | true | 17
|
|
4 | true | 17
|
|
5 | true | 17
|
|
6 | true | 17
|
|
(7 rows)
|
|
|
|
WITH RECURSIVE
|
|
tab(id_key,link) AS (VALUES (1,17), (2,17), (3,17), (4,17), (6,17), (5,17)),
|
|
iter (id_key, row_type, link) AS (
|
|
SELECT 0, 'base', 17
|
|
UNION (
|
|
WITH remaining(id_key, row_type, link, min) AS (
|
|
SELECT tab.id_key, 'true'::text, iter.link, MIN(tab.id_key) OVER ()
|
|
FROM tab INNER JOIN iter USING (link)
|
|
WHERE tab.id_key > iter.id_key
|
|
),
|
|
first_remaining AS (
|
|
SELECT id_key, row_type, link
|
|
FROM remaining
|
|
WHERE id_key=min
|
|
),
|
|
effect AS (
|
|
SELECT tab.id_key, 'new'::text, tab.link
|
|
FROM first_remaining e INNER JOIN tab ON e.id_key=tab.id_key
|
|
WHERE e.row_type = 'false'
|
|
)
|
|
SELECT * FROM first_remaining
|
|
UNION ALL SELECT * FROM effect
|
|
)
|
|
)
|
|
SELECT * FROM iter;
|
|
id_key | row_type | link
|
|
--------+----------+------
|
|
0 | base | 17
|
|
1 | true | 17
|
|
2 | true | 17
|
|
3 | true | 17
|
|
4 | true | 17
|
|
5 | true | 17
|
|
6 | true | 17
|
|
(7 rows)
|
|
|
|
--
|
|
-- Data-modifying statements in WITH
|
|
--
|
|
-- INSERT ... RETURNING
|
|
WITH t AS (
|
|
INSERT INTO y
|
|
VALUES
|
|
(11),
|
|
(12),
|
|
(13),
|
|
(14),
|
|
(15),
|
|
(16),
|
|
(17),
|
|
(18),
|
|
(19),
|
|
(20)
|
|
RETURNING *
|
|
)
|
|
SELECT * FROM t;
|
|
a
|
|
----
|
|
11
|
|
12
|
|
13
|
|
14
|
|
15
|
|
16
|
|
17
|
|
18
|
|
19
|
|
20
|
|
(10 rows)
|
|
|
|
SELECT * FROM y;
|
|
a
|
|
----
|
|
1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
11
|
|
12
|
|
13
|
|
14
|
|
15
|
|
16
|
|
17
|
|
18
|
|
19
|
|
20
|
|
(20 rows)
|
|
|
|
-- UPDATE ... RETURNING
|
|
WITH t AS (
|
|
UPDATE y
|
|
SET a=a+1
|
|
RETURNING *
|
|
)
|
|
SELECT * FROM t;
|
|
a
|
|
----
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
11
|
|
12
|
|
13
|
|
14
|
|
15
|
|
16
|
|
17
|
|
18
|
|
19
|
|
20
|
|
21
|
|
(20 rows)
|
|
|
|
SELECT * FROM y;
|
|
a
|
|
----
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
11
|
|
12
|
|
13
|
|
14
|
|
15
|
|
16
|
|
17
|
|
18
|
|
19
|
|
20
|
|
21
|
|
(20 rows)
|
|
|
|
-- DELETE ... RETURNING
|
|
WITH t AS (
|
|
DELETE FROM y
|
|
WHERE a <= 10
|
|
RETURNING *
|
|
)
|
|
SELECT * FROM t;
|
|
a
|
|
----
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
(9 rows)
|
|
|
|
SELECT * FROM y;
|
|
a
|
|
----
|
|
11
|
|
12
|
|
13
|
|
14
|
|
15
|
|
16
|
|
17
|
|
18
|
|
19
|
|
20
|
|
21
|
|
(11 rows)
|
|
|
|
-- forward reference
|
|
WITH RECURSIVE t AS (
|
|
INSERT INTO y
|
|
SELECT a+5 FROM t2 WHERE a > 5
|
|
RETURNING *
|
|
), t2 AS (
|
|
UPDATE y SET a=a-11 RETURNING *
|
|
)
|
|
SELECT * FROM t
|
|
UNION ALL
|
|
SELECT * FROM t2;
|
|
a
|
|
----
|
|
11
|
|
12
|
|
13
|
|
14
|
|
15
|
|
0
|
|
1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
(16 rows)
|
|
|
|
SELECT * FROM y;
|
|
a
|
|
----
|
|
0
|
|
1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
11
|
|
7
|
|
12
|
|
8
|
|
13
|
|
9
|
|
14
|
|
10
|
|
15
|
|
(16 rows)
|
|
|
|
-- unconditional DO INSTEAD rule
|
|
CREATE RULE y_rule AS ON DELETE TO y DO INSTEAD
|
|
INSERT INTO y VALUES(42) RETURNING *;
|
|
WITH t AS (
|
|
DELETE FROM y RETURNING *
|
|
)
|
|
SELECT * FROM t;
|
|
a
|
|
----
|
|
42
|
|
(1 row)
|
|
|
|
SELECT * FROM y;
|
|
a
|
|
----
|
|
0
|
|
1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
11
|
|
7
|
|
12
|
|
8
|
|
13
|
|
9
|
|
14
|
|
10
|
|
15
|
|
42
|
|
(17 rows)
|
|
|
|
DROP RULE y_rule ON y;
|
|
-- check merging of outer CTE with CTE in a rule action
|
|
CREATE TEMP TABLE bug6051 AS
|
|
select i from generate_series(1,3) as t(i);
|
|
SELECT * FROM bug6051;
|
|
i
|
|
---
|
|
1
|
|
2
|
|
3
|
|
(3 rows)
|
|
|
|
WITH t1 AS ( DELETE FROM bug6051 RETURNING * )
|
|
INSERT INTO bug6051 SELECT * FROM t1;
|
|
SELECT * FROM bug6051;
|
|
i
|
|
---
|
|
1
|
|
2
|
|
3
|
|
(3 rows)
|
|
|
|
CREATE TEMP TABLE bug6051_2 (i int);
|
|
CREATE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD
|
|
INSERT INTO bug6051_2
|
|
VALUES(NEW.i);
|
|
WITH t1 AS ( DELETE FROM bug6051 RETURNING * )
|
|
INSERT INTO bug6051 SELECT * FROM t1;
|
|
SELECT * FROM bug6051;
|
|
i
|
|
---
|
|
(0 rows)
|
|
|
|
SELECT * FROM bug6051_2;
|
|
i
|
|
---
|
|
1
|
|
2
|
|
3
|
|
(3 rows)
|
|
|
|
-- check INSERT...SELECT rule actions are disallowed on commands
|
|
-- that have modifyingCTEs
|
|
CREATE OR REPLACE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD
|
|
INSERT INTO bug6051_2
|
|
SELECT NEW.i;
|
|
WITH t1 AS ( DELETE FROM bug6051 RETURNING * )
|
|
INSERT INTO bug6051 SELECT * FROM t1;
|
|
ERROR: INSERT...SELECT rule actions are not supported for queries having data-modifying statements in WITH
|
|
-- silly example to verify that hasModifyingCTE flag is propagated
|
|
CREATE TEMP TABLE bug6051_3 AS
|
|
SELECT a FROM generate_series(11,13) AS a;
|
|
CREATE RULE bug6051_3_ins AS ON INSERT TO bug6051_3 DO INSTEAD
|
|
SELECT i FROM bug6051_2;
|
|
BEGIN; SET LOCAL force_parallel_mode = on;
|
|
WITH t1 AS ( DELETE FROM bug6051_3 RETURNING * )
|
|
INSERT INTO bug6051_3 SELECT * FROM t1;
|
|
i
|
|
---
|
|
1
|
|
2
|
|
3
|
|
1
|
|
2
|
|
3
|
|
1
|
|
2
|
|
3
|
|
(9 rows)
|
|
|
|
COMMIT;
|
|
SELECT * FROM bug6051_3;
|
|
a
|
|
---
|
|
(0 rows)
|
|
|
|
-- a truly recursive CTE in the same list
|
|
WITH RECURSIVE t(a) AS (
|
|
SELECT 0
|
|
UNION ALL
|
|
SELECT a+1 FROM t WHERE a+1 < 5
|
|
), t2 as (
|
|
INSERT INTO y
|
|
SELECT * FROM t RETURNING *
|
|
)
|
|
SELECT * FROM t2 JOIN y USING (a) ORDER BY a;
|
|
a
|
|
---
|
|
0
|
|
1
|
|
2
|
|
3
|
|
4
|
|
(5 rows)
|
|
|
|
SELECT * FROM y;
|
|
a
|
|
----
|
|
0
|
|
1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
11
|
|
7
|
|
12
|
|
8
|
|
13
|
|
9
|
|
14
|
|
10
|
|
15
|
|
42
|
|
0
|
|
1
|
|
2
|
|
3
|
|
4
|
|
(22 rows)
|
|
|
|
-- data-modifying WITH in a modifying statement
|
|
WITH t AS (
|
|
DELETE FROM y
|
|
WHERE a <= 10
|
|
RETURNING *
|
|
)
|
|
INSERT INTO y SELECT -a FROM t RETURNING *;
|
|
a
|
|
-----
|
|
0
|
|
-1
|
|
-2
|
|
-3
|
|
-4
|
|
-5
|
|
-6
|
|
-7
|
|
-8
|
|
-9
|
|
-10
|
|
0
|
|
-1
|
|
-2
|
|
-3
|
|
-4
|
|
(16 rows)
|
|
|
|
SELECT * FROM y;
|
|
a
|
|
-----
|
|
11
|
|
12
|
|
13
|
|
14
|
|
15
|
|
42
|
|
0
|
|
-1
|
|
-2
|
|
-3
|
|
-4
|
|
-5
|
|
-6
|
|
-7
|
|
-8
|
|
-9
|
|
-10
|
|
0
|
|
-1
|
|
-2
|
|
-3
|
|
-4
|
|
(22 rows)
|
|
|
|
-- check that WITH query is run to completion even if outer query isn't
|
|
WITH t AS (
|
|
UPDATE y SET a = a * 100 RETURNING *
|
|
)
|
|
SELECT * FROM t LIMIT 10;
|
|
a
|
|
------
|
|
1100
|
|
1200
|
|
1300
|
|
1400
|
|
1500
|
|
4200
|
|
0
|
|
-100
|
|
-200
|
|
-300
|
|
(10 rows)
|
|
|
|
SELECT * FROM y;
|
|
a
|
|
-------
|
|
1100
|
|
1200
|
|
1300
|
|
1400
|
|
1500
|
|
4200
|
|
0
|
|
-100
|
|
-200
|
|
-300
|
|
-400
|
|
-500
|
|
-600
|
|
-700
|
|
-800
|
|
-900
|
|
-1000
|
|
0
|
|
-100
|
|
-200
|
|
-300
|
|
-400
|
|
(22 rows)
|
|
|
|
-- data-modifying WITH containing INSERT...ON CONFLICT DO UPDATE
|
|
CREATE TABLE withz AS SELECT i AS k, (i || ' v')::text v FROM generate_series(1, 16, 3) i;
|
|
ALTER TABLE withz ADD UNIQUE (k);
|
|
WITH t AS (
|
|
INSERT INTO withz SELECT i, 'insert'
|
|
FROM generate_series(0, 16) i
|
|
ON CONFLICT (k) DO UPDATE SET v = withz.v || ', now update'
|
|
RETURNING *
|
|
)
|
|
SELECT * FROM t JOIN y ON t.k = y.a ORDER BY a, k;
|
|
k | v | a
|
|
---+--------+---
|
|
0 | insert | 0
|
|
0 | insert | 0
|
|
(2 rows)
|
|
|
|
-- Test EXCLUDED.* reference within CTE
|
|
WITH aa AS (
|
|
INSERT INTO withz VALUES(1, 5) ON CONFLICT (k) DO UPDATE SET v = EXCLUDED.v
|
|
WHERE withz.k != EXCLUDED.k
|
|
RETURNING *
|
|
)
|
|
SELECT * FROM aa;
|
|
k | v
|
|
---+---
|
|
(0 rows)
|
|
|
|
-- New query/snapshot demonstrates side-effects of previous query.
|
|
SELECT * FROM withz ORDER BY k;
|
|
k | v
|
|
----+------------------
|
|
0 | insert
|
|
1 | 1 v, now update
|
|
2 | insert
|
|
3 | insert
|
|
4 | 4 v, now update
|
|
5 | insert
|
|
6 | insert
|
|
7 | 7 v, now update
|
|
8 | insert
|
|
9 | insert
|
|
10 | 10 v, now update
|
|
11 | insert
|
|
12 | insert
|
|
13 | 13 v, now update
|
|
14 | insert
|
|
15 | insert
|
|
16 | 16 v, now update
|
|
(17 rows)
|
|
|
|
--
|
|
-- Ensure subqueries within the update clause work, even if they
|
|
-- reference outside values
|
|
--
|
|
WITH aa AS (SELECT 1 a, 2 b)
|
|
INSERT INTO withz VALUES(1, 'insert')
|
|
ON CONFLICT (k) DO UPDATE SET v = (SELECT b || ' update' FROM aa WHERE a = 1 LIMIT 1);
|
|
WITH aa AS (SELECT 1 a, 2 b)
|
|
INSERT INTO withz VALUES(1, 'insert')
|
|
ON CONFLICT (k) DO UPDATE SET v = ' update' WHERE withz.k = (SELECT a FROM aa);
|
|
WITH aa AS (SELECT 1 a, 2 b)
|
|
INSERT INTO withz VALUES(1, 'insert')
|
|
ON CONFLICT (k) DO UPDATE SET v = (SELECT b || ' update' FROM aa WHERE a = 1 LIMIT 1);
|
|
WITH aa AS (SELECT 'a' a, 'b' b UNION ALL SELECT 'a' a, 'b' b)
|
|
INSERT INTO withz VALUES(1, 'insert')
|
|
ON CONFLICT (k) DO UPDATE SET v = (SELECT b || ' update' FROM aa WHERE a = 'a' LIMIT 1);
|
|
WITH aa AS (SELECT 1 a, 2 b)
|
|
INSERT INTO withz VALUES(1, (SELECT b || ' insert' FROM aa WHERE a = 1 ))
|
|
ON CONFLICT (k) DO UPDATE SET v = (SELECT b || ' update' FROM aa WHERE a = 1 LIMIT 1);
|
|
-- Update a row more than once, in different parts of a wCTE. That is
|
|
-- an allowed, presumably very rare, edge case, but since it was
|
|
-- broken in the past, having a test seems worthwhile.
|
|
WITH simpletup AS (
|
|
SELECT 2 k, 'Green' v),
|
|
upsert_cte AS (
|
|
INSERT INTO withz VALUES(2, 'Blue') ON CONFLICT (k) DO
|
|
UPDATE SET (k, v) = (SELECT k, v FROM simpletup WHERE simpletup.k = withz.k)
|
|
RETURNING k, v)
|
|
INSERT INTO withz VALUES(2, 'Red') ON CONFLICT (k) DO
|
|
UPDATE SET (k, v) = (SELECT k, v FROM upsert_cte WHERE upsert_cte.k = withz.k)
|
|
RETURNING k, v;
|
|
k | v
|
|
---+---
|
|
(0 rows)
|
|
|
|
DROP TABLE withz;
|
|
-- check that run to completion happens in proper ordering
|
|
TRUNCATE TABLE y;
|
|
INSERT INTO y SELECT generate_series(1, 3);
|
|
CREATE TEMPORARY TABLE yy (a INTEGER);
|
|
WITH RECURSIVE t1 AS (
|
|
INSERT INTO y SELECT * FROM y RETURNING *
|
|
), t2 AS (
|
|
INSERT INTO yy SELECT * FROM t1 RETURNING *
|
|
)
|
|
SELECT 1;
|
|
?column?
|
|
----------
|
|
1
|
|
(1 row)
|
|
|
|
SELECT * FROM y;
|
|
a
|
|
---
|
|
1
|
|
2
|
|
3
|
|
1
|
|
2
|
|
3
|
|
(6 rows)
|
|
|
|
SELECT * FROM yy;
|
|
a
|
|
---
|
|
1
|
|
2
|
|
3
|
|
(3 rows)
|
|
|
|
WITH RECURSIVE t1 AS (
|
|
INSERT INTO yy SELECT * FROM t2 RETURNING *
|
|
), t2 AS (
|
|
INSERT INTO y SELECT * FROM y RETURNING *
|
|
)
|
|
SELECT 1;
|
|
?column?
|
|
----------
|
|
1
|
|
(1 row)
|
|
|
|
SELECT * FROM y;
|
|
a
|
|
---
|
|
1
|
|
2
|
|
3
|
|
1
|
|
2
|
|
3
|
|
1
|
|
2
|
|
3
|
|
1
|
|
2
|
|
3
|
|
(12 rows)
|
|
|
|
SELECT * FROM yy;
|
|
a
|
|
---
|
|
1
|
|
2
|
|
3
|
|
1
|
|
2
|
|
3
|
|
1
|
|
2
|
|
3
|
|
(9 rows)
|
|
|
|
-- triggers
|
|
TRUNCATE TABLE y;
|
|
INSERT INTO y SELECT generate_series(1, 10);
|
|
CREATE FUNCTION y_trigger() RETURNS trigger AS $$
|
|
begin
|
|
raise notice 'y_trigger: a = %', new.a;
|
|
return new;
|
|
end;
|
|
$$ LANGUAGE plpgsql;
|
|
CREATE TRIGGER y_trig BEFORE INSERT ON y FOR EACH ROW
|
|
EXECUTE PROCEDURE y_trigger();
|
|
WITH t AS (
|
|
INSERT INTO y
|
|
VALUES
|
|
(21),
|
|
(22),
|
|
(23)
|
|
RETURNING *
|
|
)
|
|
SELECT * FROM t;
|
|
NOTICE: y_trigger: a = 21
|
|
NOTICE: y_trigger: a = 22
|
|
NOTICE: y_trigger: a = 23
|
|
a
|
|
----
|
|
21
|
|
22
|
|
23
|
|
(3 rows)
|
|
|
|
SELECT * FROM y;
|
|
a
|
|
----
|
|
1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
21
|
|
22
|
|
23
|
|
(13 rows)
|
|
|
|
DROP TRIGGER y_trig ON y;
|
|
CREATE TRIGGER y_trig AFTER INSERT ON y FOR EACH ROW
|
|
EXECUTE PROCEDURE y_trigger();
|
|
WITH t AS (
|
|
INSERT INTO y
|
|
VALUES
|
|
(31),
|
|
(32),
|
|
(33)
|
|
RETURNING *
|
|
)
|
|
SELECT * FROM t LIMIT 1;
|
|
NOTICE: y_trigger: a = 31
|
|
NOTICE: y_trigger: a = 32
|
|
NOTICE: y_trigger: a = 33
|
|
a
|
|
----
|
|
31
|
|
(1 row)
|
|
|
|
SELECT * FROM y;
|
|
a
|
|
----
|
|
1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
21
|
|
22
|
|
23
|
|
31
|
|
32
|
|
33
|
|
(16 rows)
|
|
|
|
DROP TRIGGER y_trig ON y;
|
|
CREATE OR REPLACE FUNCTION y_trigger() RETURNS trigger AS $$
|
|
begin
|
|
raise notice 'y_trigger';
|
|
return null;
|
|
end;
|
|
$$ LANGUAGE plpgsql;
|
|
CREATE TRIGGER y_trig AFTER INSERT ON y FOR EACH STATEMENT
|
|
EXECUTE PROCEDURE y_trigger();
|
|
WITH t AS (
|
|
INSERT INTO y
|
|
VALUES
|
|
(41),
|
|
(42),
|
|
(43)
|
|
RETURNING *
|
|
)
|
|
SELECT * FROM t;
|
|
NOTICE: y_trigger
|
|
a
|
|
----
|
|
41
|
|
42
|
|
43
|
|
(3 rows)
|
|
|
|
SELECT * FROM y;
|
|
a
|
|
----
|
|
1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
21
|
|
22
|
|
23
|
|
31
|
|
32
|
|
33
|
|
41
|
|
42
|
|
43
|
|
(19 rows)
|
|
|
|
DROP TRIGGER y_trig ON y;
|
|
DROP FUNCTION y_trigger();
|
|
-- WITH attached to inherited UPDATE or DELETE
|
|
CREATE TEMP TABLE parent ( id int, val text );
|
|
CREATE TEMP TABLE child1 ( ) INHERITS ( parent );
|
|
CREATE TEMP TABLE child2 ( ) INHERITS ( parent );
|
|
INSERT INTO parent VALUES ( 1, 'p1' );
|
|
INSERT INTO child1 VALUES ( 11, 'c11' ),( 12, 'c12' );
|
|
INSERT INTO child2 VALUES ( 23, 'c21' ),( 24, 'c22' );
|
|
WITH rcte AS ( SELECT sum(id) AS totalid FROM parent )
|
|
UPDATE parent SET id = id + totalid FROM rcte;
|
|
SELECT * FROM parent;
|
|
id | val
|
|
----+-----
|
|
72 | p1
|
|
82 | c11
|
|
83 | c12
|
|
94 | c21
|
|
95 | c22
|
|
(5 rows)
|
|
|
|
WITH wcte AS ( INSERT INTO child1 VALUES ( 42, 'new' ) RETURNING id AS newid )
|
|
UPDATE parent SET id = id + newid FROM wcte;
|
|
SELECT * FROM parent;
|
|
id | val
|
|
-----+-----
|
|
114 | p1
|
|
42 | new
|
|
124 | c11
|
|
125 | c12
|
|
136 | c21
|
|
137 | c22
|
|
(6 rows)
|
|
|
|
WITH rcte AS ( SELECT max(id) AS maxid FROM parent )
|
|
DELETE FROM parent USING rcte WHERE id = maxid;
|
|
SELECT * FROM parent;
|
|
id | val
|
|
-----+-----
|
|
114 | p1
|
|
42 | new
|
|
124 | c11
|
|
125 | c12
|
|
136 | c21
|
|
(5 rows)
|
|
|
|
WITH wcte AS ( INSERT INTO child2 VALUES ( 42, 'new2' ) RETURNING id AS newid )
|
|
DELETE FROM parent USING wcte WHERE id = newid;
|
|
SELECT * FROM parent;
|
|
id | val
|
|
-----+------
|
|
114 | p1
|
|
124 | c11
|
|
125 | c12
|
|
136 | c21
|
|
42 | new2
|
|
(5 rows)
|
|
|
|
-- check EXPLAIN VERBOSE for a wCTE with RETURNING
|
|
EXPLAIN (VERBOSE, COSTS OFF)
|
|
WITH wcte AS ( INSERT INTO int8_tbl VALUES ( 42, 47 ) RETURNING q2 )
|
|
DELETE FROM a USING wcte WHERE aa = q2;
|
|
QUERY PLAN
|
|
------------------------------------------------------------
|
|
Delete on public.a
|
|
Delete on public.a a_1
|
|
Delete on public.b a_2
|
|
Delete on public.c a_3
|
|
Delete on public.d a_4
|
|
CTE wcte
|
|
-> Insert on public.int8_tbl
|
|
Output: int8_tbl.q2
|
|
-> Result
|
|
Output: '42'::bigint, '47'::bigint
|
|
-> Hash Join
|
|
Output: wcte.*, a.tableoid, a.ctid
|
|
Hash Cond: (a.aa = wcte.q2)
|
|
-> Append
|
|
-> Seq Scan on public.a a_1
|
|
Output: a_1.aa, a_1.tableoid, a_1.ctid
|
|
-> Seq Scan on public.b a_2
|
|
Output: a_2.aa, a_2.tableoid, a_2.ctid
|
|
-> Seq Scan on public.c a_3
|
|
Output: a_3.aa, a_3.tableoid, a_3.ctid
|
|
-> Seq Scan on public.d a_4
|
|
Output: a_4.aa, a_4.tableoid, a_4.ctid
|
|
-> Hash
|
|
Output: wcte.*, wcte.q2
|
|
-> CTE Scan on wcte
|
|
Output: wcte.*, wcte.q2
|
|
(26 rows)
|
|
|
|
-- error cases
|
|
-- data-modifying WITH tries to use its own output
|
|
WITH RECURSIVE t AS (
|
|
INSERT INTO y
|
|
SELECT * FROM t
|
|
)
|
|
VALUES(FALSE);
|
|
ERROR: recursive query "t" must not contain data-modifying statements
|
|
LINE 1: WITH RECURSIVE t AS (
|
|
^
|
|
-- no RETURNING in a referenced data-modifying WITH
|
|
WITH t AS (
|
|
INSERT INTO y VALUES(0)
|
|
)
|
|
SELECT * FROM t;
|
|
ERROR: WITH query "t" does not have a RETURNING clause
|
|
LINE 4: SELECT * FROM t;
|
|
^
|
|
-- data-modifying WITH allowed only at the top level
|
|
SELECT * FROM (
|
|
WITH t AS (UPDATE y SET a=a+1 RETURNING *)
|
|
SELECT * FROM t
|
|
) ss;
|
|
ERROR: WITH clause containing a data-modifying statement must be at the top level
|
|
LINE 2: WITH t AS (UPDATE y SET a=a+1 RETURNING *)
|
|
^
|
|
-- most variants of rules aren't allowed
|
|
CREATE RULE y_rule AS ON INSERT TO y WHERE a=0 DO INSTEAD DELETE FROM y;
|
|
WITH t AS (
|
|
INSERT INTO y VALUES(0)
|
|
)
|
|
VALUES(FALSE);
|
|
ERROR: conditional DO INSTEAD rules are not supported for data-modifying statements in WITH
|
|
CREATE OR REPLACE RULE y_rule AS ON INSERT TO y DO INSTEAD NOTHING;
|
|
WITH t AS (
|
|
INSERT INTO y VALUES(0)
|
|
)
|
|
VALUES(FALSE);
|
|
ERROR: DO INSTEAD NOTHING rules are not supported for data-modifying statements in WITH
|
|
CREATE OR REPLACE RULE y_rule AS ON INSERT TO y DO INSTEAD NOTIFY foo;
|
|
WITH t AS (
|
|
INSERT INTO y VALUES(0)
|
|
)
|
|
VALUES(FALSE);
|
|
ERROR: DO INSTEAD NOTIFY rules are not supported for data-modifying statements in WITH
|
|
CREATE OR REPLACE RULE y_rule AS ON INSERT TO y DO ALSO NOTIFY foo;
|
|
WITH t AS (
|
|
INSERT INTO y VALUES(0)
|
|
)
|
|
VALUES(FALSE);
|
|
ERROR: DO ALSO rules are not supported for data-modifying statements in WITH
|
|
CREATE OR REPLACE RULE y_rule AS ON INSERT TO y
|
|
DO INSTEAD (NOTIFY foo; NOTIFY bar);
|
|
WITH t AS (
|
|
INSERT INTO y VALUES(0)
|
|
)
|
|
VALUES(FALSE);
|
|
ERROR: multi-statement DO INSTEAD rules are not supported for data-modifying statements in WITH
|
|
DROP RULE y_rule ON y;
|
|
-- check that parser lookahead for WITH doesn't cause any odd behavior
|
|
create table foo (with baz); -- fail, WITH is a reserved word
|
|
ERROR: syntax error at or near "with"
|
|
LINE 1: create table foo (with baz);
|
|
^
|
|
create table foo (with ordinality); -- fail, WITH is a reserved word
|
|
ERROR: syntax error at or near "with"
|
|
LINE 1: create table foo (with ordinality);
|
|
^
|
|
with ordinality as (select 1 as x) select * from ordinality;
|
|
x
|
|
---
|
|
1
|
|
(1 row)
|
|
|
|
-- check sane response to attempt to modify CTE relation
|
|
WITH test AS (SELECT 42) INSERT INTO test VALUES (1);
|
|
ERROR: relation "test" does not exist
|
|
LINE 1: WITH test AS (SELECT 42) INSERT INTO test VALUES (1);
|
|
^
|
|
-- check response to attempt to modify table with same name as a CTE (perhaps
|
|
-- surprisingly it works, because CTEs don't hide tables from data-modifying
|
|
-- statements)
|
|
create temp table test (i int);
|
|
with test as (select 42) insert into test select * from test;
|
|
select * from test;
|
|
i
|
|
----
|
|
42
|
|
(1 row)
|
|
|
|
drop table test;
|