diff -U3 /home/postgres/postgres/src/test/regress/expected/temp.out /home/postgres/postgres/build/testrun/pg_upgrade/002_pg_upgrade/data/results/temp.out --- /home/postgres/postgres/src/test/regress/expected/temp.out 2026-01-01 05:40:40.113320740 +0000 +++ /home/postgres/postgres/build/testrun/pg_upgrade/002_pg_upgrade/data/results/temp.out 2026-01-01 05:42:36.677988844 +0000 @@ -1,568 +1 @@ --- --- TEMP --- Test temp relations and indexes --- --- test temp table/index masking -CREATE TABLE temptest(col int); -CREATE INDEX i_temptest ON temptest(col); -CREATE TEMP TABLE temptest(tcol int); -CREATE INDEX i_temptest ON temptest(tcol); -SELECT * FROM temptest; - tcol ------- -(0 rows) - -DROP INDEX i_temptest; -DROP TABLE temptest; -SELECT * FROM temptest; - col ------ -(0 rows) - -DROP INDEX i_temptest; -DROP TABLE temptest; --- test temp table selects -CREATE TABLE temptest(col int); -INSERT INTO temptest VALUES (1); -CREATE TEMP TABLE temptest(tcol float); -INSERT INTO temptest VALUES (2.1); -SELECT * FROM temptest; - tcol ------- - 2.1 -(1 row) - -DROP TABLE temptest; -SELECT * FROM temptest; - col ------ - 1 -(1 row) - -DROP TABLE temptest; --- test temp table deletion -CREATE TEMP TABLE temptest(col int); -\c -SELECT * FROM temptest; -ERROR: relation "temptest" does not exist -LINE 1: SELECT * FROM temptest; - ^ --- Test ON COMMIT DELETE ROWS -CREATE TEMP TABLE temptest(col int) ON COMMIT DELETE ROWS; --- while we're here, verify successful truncation of index with SQL function -CREATE INDEX ON temptest(bit_length('')); -BEGIN; -INSERT INTO temptest VALUES (1); -INSERT INTO temptest VALUES (2); -SELECT * FROM temptest; - col ------ - 1 - 2 -(2 rows) - -COMMIT; -SELECT * FROM temptest; - col ------ -(0 rows) - -DROP TABLE temptest; -BEGIN; -CREATE TEMP TABLE temptest(col) ON COMMIT DELETE ROWS AS SELECT 1; -SELECT * FROM temptest; - col ------ - 1 -(1 row) - -COMMIT; -SELECT * FROM temptest; - col ------ -(0 rows) - -DROP TABLE temptest; --- Test ON COMMIT DROP -BEGIN; -CREATE TEMP TABLE temptest(col int) ON COMMIT DROP; -INSERT INTO temptest VALUES (1); -INSERT INTO temptest VALUES (2); -SELECT * FROM temptest; - col ------ - 1 - 2 -(2 rows) - -COMMIT; -SELECT * FROM temptest; -ERROR: relation "temptest" does not exist -LINE 1: SELECT * FROM temptest; - ^ -BEGIN; -CREATE TEMP TABLE temptest(col) ON COMMIT DROP AS SELECT 1; -SELECT * FROM temptest; - col ------ - 1 -(1 row) - -COMMIT; -SELECT * FROM temptest; -ERROR: relation "temptest" does not exist -LINE 1: SELECT * FROM temptest; - ^ --- Test it with a CHECK condition that produces a toasted pg_constraint entry -BEGIN; -do $$ -begin - execute format($cmd$ - CREATE TEMP TABLE temptest (col text CHECK (col < %L)) ON COMMIT DROP - $cmd$, - (SELECT string_agg(g.i::text || ':' || random()::text, '|') - FROM generate_series(1, 100) g(i))); -end$$; -SELECT * FROM temptest; - col ------ -(0 rows) - -COMMIT; -SELECT * FROM temptest; -ERROR: relation "temptest" does not exist -LINE 1: SELECT * FROM temptest; - ^ --- ON COMMIT is only allowed for TEMP -CREATE TABLE temptest(col int) ON COMMIT DELETE ROWS; -ERROR: ON COMMIT can only be used on temporary tables -CREATE TABLE temptest(col) ON COMMIT DELETE ROWS AS SELECT 1; -ERROR: ON COMMIT can only be used on temporary tables --- Test foreign keys -BEGIN; -CREATE TEMP TABLE temptest1(col int PRIMARY KEY); -CREATE TEMP TABLE temptest2(col int REFERENCES temptest1) - ON COMMIT DELETE ROWS; -INSERT INTO temptest1 VALUES (1); -INSERT INTO temptest2 VALUES (1); -COMMIT; -SELECT * FROM temptest1; - col ------ - 1 -(1 row) - -SELECT * FROM temptest2; - col ------ -(0 rows) - -BEGIN; -CREATE TEMP TABLE temptest3(col int PRIMARY KEY) ON COMMIT DELETE ROWS; -CREATE TEMP TABLE temptest4(col int REFERENCES temptest3); -COMMIT; -ERROR: unsupported ON COMMIT and foreign key combination -DETAIL: Table "temptest4" references "temptest3", but they do not have the same ON COMMIT setting. --- Test manipulation of temp schema's placement in search path -create table public.whereami (f1 text); -insert into public.whereami values ('public'); -create temp table whereami (f1 text); -insert into whereami values ('temp'); -create function public.whoami() returns text - as $$select 'public'::text$$ language sql; -create function pg_temp.whoami() returns text - as $$select 'temp'::text$$ language sql; --- default should have pg_temp implicitly first, but only for tables -select * from whereami; - f1 ------- - temp -(1 row) - -select whoami(); - whoami --------- - public -(1 row) - --- can list temp first explicitly, but it still doesn't affect functions -set search_path = pg_temp, public; -select * from whereami; - f1 ------- - temp -(1 row) - -select whoami(); - whoami --------- - public -(1 row) - --- or put it last for security -set search_path = public, pg_temp; -select * from whereami; - f1 --------- - public -(1 row) - -select whoami(); - whoami --------- - public -(1 row) - --- you can invoke a temp function explicitly, though -select pg_temp.whoami(); - whoami --------- - temp -(1 row) - -drop table public.whereami; --- types in temp schema -set search_path = pg_temp, public; -create domain pg_temp.nonempty as text check (value <> ''); --- function-syntax invocation of types matches rules for functions -select nonempty(''); -ERROR: function nonempty(unknown) does not exist -LINE 1: select nonempty(''); - ^ -DETAIL: There is no function of that name. -select pg_temp.nonempty(''); -ERROR: value for domain nonempty violates check constraint "nonempty_check" --- other syntax matches rules for tables -select ''::nonempty; -ERROR: value for domain nonempty violates check constraint "nonempty_check" -reset search_path; --- For partitioned temp tables, ON COMMIT actions ignore storage-less --- partitioned tables. -begin; -create temp table temp_parted_oncommit (a int) - partition by list (a) on commit delete rows; -create temp table temp_parted_oncommit_1 - partition of temp_parted_oncommit - for values in (1) on commit delete rows; -insert into temp_parted_oncommit values (1); -commit; --- partitions are emptied by the previous commit -select * from temp_parted_oncommit; - a ---- -(0 rows) - -drop table temp_parted_oncommit; --- Check dependencies between ON COMMIT actions with a partitioned --- table and its partitions. Using ON COMMIT DROP on a parent removes --- the whole set. -begin; -create temp table temp_parted_oncommit_test (a int) - partition by list (a) on commit drop; -create temp table temp_parted_oncommit_test1 - partition of temp_parted_oncommit_test - for values in (1) on commit delete rows; -create temp table temp_parted_oncommit_test2 - partition of temp_parted_oncommit_test - for values in (2) on commit drop; -insert into temp_parted_oncommit_test values (1), (2); -commit; --- no relations remain in this case. -select relname from pg_class where relname ~ '^temp_parted_oncommit_test'; - relname ---------- -(0 rows) - --- Using ON COMMIT DELETE on a partitioned table does not remove --- all rows if partitions preserve their data. -begin; -create temp table temp_parted_oncommit_test (a int) - partition by list (a) on commit delete rows; -create temp table temp_parted_oncommit_test1 - partition of temp_parted_oncommit_test - for values in (1) on commit preserve rows; -create temp table temp_parted_oncommit_test2 - partition of temp_parted_oncommit_test - for values in (2) on commit drop; -insert into temp_parted_oncommit_test values (1), (2); -commit; --- Data from the remaining partition is still here as its rows are --- preserved. -select * from temp_parted_oncommit_test; - a ---- - 1 -(1 row) - --- two relations remain in this case. -select relname from pg_class where relname ~ '^temp_parted_oncommit_test' - order by relname; - relname ----------------------------- - temp_parted_oncommit_test - temp_parted_oncommit_test1 -(2 rows) - -drop table temp_parted_oncommit_test; --- Check dependencies between ON COMMIT actions with inheritance trees. --- Using ON COMMIT DROP on a parent removes the whole set. -begin; -create temp table temp_inh_oncommit_test (a int) on commit drop; -create temp table temp_inh_oncommit_test1 () - inherits(temp_inh_oncommit_test) on commit delete rows; -insert into temp_inh_oncommit_test1 values (1); -commit; --- no relations remain in this case -select relname from pg_class where relname ~ '^temp_inh_oncommit_test'; - relname ---------- -(0 rows) - --- Data on the parent is removed, and the child goes away. -begin; -create temp table temp_inh_oncommit_test (a int) on commit delete rows; -create temp table temp_inh_oncommit_test1 () - inherits(temp_inh_oncommit_test) on commit drop; -insert into temp_inh_oncommit_test1 values (1); -insert into temp_inh_oncommit_test values (1); -commit; -select * from temp_inh_oncommit_test; - a ---- -(0 rows) - --- one relation remains -select relname from pg_class where relname ~ '^temp_inh_oncommit_test'; - relname ------------------------- - temp_inh_oncommit_test -(1 row) - -drop table temp_inh_oncommit_test; --- Tests with two-phase commit --- Transactions creating objects in a temporary namespace cannot be used --- with two-phase commit. --- These cases generate errors about temporary namespace. --- Function creation -begin; -create function pg_temp.twophase_func() returns void as - $$ select '2pc_func'::text $$ language sql; -prepare transaction 'twophase_func'; -ERROR: cannot PREPARE a transaction that has operated on temporary objects --- Function drop -create function pg_temp.twophase_func() returns void as - $$ select '2pc_func'::text $$ language sql; -begin; -drop function pg_temp.twophase_func(); -prepare transaction 'twophase_func'; -ERROR: cannot PREPARE a transaction that has operated on temporary objects --- Operator creation -begin; -create operator pg_temp.@@ (leftarg = int4, rightarg = int4, procedure = int4mi); -prepare transaction 'twophase_operator'; -ERROR: cannot PREPARE a transaction that has operated on temporary objects --- These generate errors about temporary tables. -begin; -create type pg_temp.twophase_type as (a int); -prepare transaction 'twophase_type'; -ERROR: cannot PREPARE a transaction that has operated on temporary objects -begin; -create view pg_temp.twophase_view as select 1; -prepare transaction 'twophase_view'; -ERROR: cannot PREPARE a transaction that has operated on temporary objects -begin; -create sequence pg_temp.twophase_seq; -prepare transaction 'twophase_sequence'; -ERROR: cannot PREPARE a transaction that has operated on temporary objects --- Temporary tables cannot be used with two-phase commit. -create temp table twophase_tab (a int); -begin; -select a from twophase_tab; - a ---- -(0 rows) - -prepare transaction 'twophase_tab'; -ERROR: cannot PREPARE a transaction that has operated on temporary objects -begin; -insert into twophase_tab values (1); -prepare transaction 'twophase_tab'; -ERROR: cannot PREPARE a transaction that has operated on temporary objects -begin; -lock twophase_tab in access exclusive mode; -prepare transaction 'twophase_tab'; -ERROR: cannot PREPARE a transaction that has operated on temporary objects -begin; -drop table twophase_tab; -prepare transaction 'twophase_tab'; -ERROR: cannot PREPARE a transaction that has operated on temporary objects --- Corner case: current_schema may create a temporary schema if namespace --- creation is pending, so check after that. First reset the connection --- to remove the temporary namespace. -\c - -SET search_path TO 'pg_temp'; -BEGIN; -SELECT current_schema() ~ 'pg_temp' AS is_temp_schema; - is_temp_schema ----------------- - t -(1 row) - -PREPARE TRANSACTION 'twophase_search'; -ERROR: cannot PREPARE a transaction that has operated on temporary objects --- Tests to verify we recover correctly from exhausting buffer pins and --- related matters. --- use lower possible buffer limit to make the test cheaper -\c -SET temp_buffers = 100; -CREATE TEMPORARY TABLE test_temp(a int not null unique, b TEXT not null, cnt int not null); -INSERT INTO test_temp SELECT generate_series(1, 10000) as id, repeat('a', 200), 0; --- should be at least 2x as large than temp_buffers -SELECT pg_relation_size('test_temp') / current_setting('block_size')::int8 > 200; - ?column? ----------- - t -(1 row) - --- Don't want cursor names and plpgsql function lines in the error messages -\set VERBOSITY terse -/* helper function to create cursors for each page in [p_start, p_end] */ -CREATE FUNCTION test_temp_pin(p_start int, p_end int) -RETURNS void -LANGUAGE plpgsql -AS $f$ - DECLARE - cursorname text; - query text; - BEGIN - FOR i IN p_start..p_end LOOP - cursorname = 'c_'||i; - query = format($q$DECLARE %I CURSOR FOR SELECT ctid FROM test_temp WHERE ctid >= '( %s, 1)'::tid $q$, cursorname, i); - EXECUTE query; - EXECUTE 'FETCH NEXT FROM '||cursorname; - -- for test development - -- RAISE NOTICE '%: %', cursorname, query; - END LOOP; - END; -$f$; --- Test overflow of temp table buffers is handled correctly -BEGIN; --- should work, below max -SELECT test_temp_pin(0, 9); - test_temp_pin ---------------- - -(1 row) - --- should fail, too many buffers pinned -SELECT test_temp_pin(10, 105); -ERROR: no empty local buffer available -ROLLBACK; -BEGIN; --- have some working cursors to test after errors -SELECT test_temp_pin(0, 9); - test_temp_pin ---------------- - -(1 row) - -FETCH NEXT FROM c_3; - ctid -------- - (3,2) -(1 row) - --- exhaust buffer pins in subtrans, check things work after -SAVEPOINT rescue_me; -SELECT test_temp_pin(10, 105); -ERROR: no empty local buffer available -ROLLBACK TO SAVEPOINT rescue_me; --- pre-subtrans cursors continue to work -FETCH NEXT FROM c_3; - ctid -------- - (3,3) -(1 row) - --- new cursors with pins can be created after subtrans rollback -SELECT test_temp_pin(10, 94); - test_temp_pin ---------------- - -(1 row) - --- Check that read streams deal with lower number of pins available -SELECT count(*), max(a) max_a, min(a) min_a, max(cnt) max_cnt FROM test_temp; - count | max_a | min_a | max_cnt --------+-------+-------+--------- - 10000 | 10000 | 1 | 0 -(1 row) - -ROLLBACK; --- Check that temp tables with existing cursors can't be dropped. -BEGIN; -SELECT test_temp_pin(0, 1); - test_temp_pin ---------------- - -(1 row) - -DROP TABLE test_temp; -ERROR: cannot DROP TABLE "test_temp" because it is being used by active queries in this session -COMMIT; --- Check that temp tables with existing cursors can't be dropped. -BEGIN; -SELECT test_temp_pin(0, 1); - test_temp_pin ---------------- - -(1 row) - -TRUNCATE test_temp; -ERROR: cannot TRUNCATE "test_temp" because it is being used by active queries in this session -COMMIT; --- Check that temp tables that are dropped in transaction that's rolled back --- preserve buffer contents -SELECT count(*), max(a) max_a, min(a) min_a, max(cnt) max_cnt FROM test_temp; - count | max_a | min_a | max_cnt --------+-------+-------+--------- - 10000 | 10000 | 1 | 0 -(1 row) - -INSERT INTO test_temp(a, b, cnt) VALUES (-1, '', 0); -BEGIN; -INSERT INTO test_temp(a, b, cnt) VALUES (-2, '', 0); -DROP TABLE test_temp; -ROLLBACK; -SELECT count(*), max(a) max_a, min(a) min_a, max(cnt) max_cnt FROM test_temp; - count | max_a | min_a | max_cnt --------+-------+-------+--------- - 10001 | 10000 | -1 | 0 -(1 row) - --- Check that temp table drop is transactional and preserves dirty --- buffer contents -UPDATE test_temp SET cnt = cnt + 1 WHERE a = -1; -BEGIN; -DROP TABLE test_temp; -ROLLBACK; -SELECT count(*), max(a) max_a, min(a) min_a, max(cnt) max_cnt FROM test_temp; - count | max_a | min_a | max_cnt --------+-------+-------+--------- - 10001 | 10000 | -1 | 1 -(1 row) - --- Check that temp table truncation is transactional and preserves dirty --- buffer contents -UPDATE test_temp SET cnt = cnt + 1 WHERE a = -1; -BEGIN; -TRUNCATE test_temp; -ROLLBACK; -SELECT count(*), max(a) max_a, min(a) min_a, max(cnt) max_cnt FROM test_temp; - count | max_a | min_a | max_cnt --------+-------+-------+--------- - 10001 | 10000 | -1 | 2 -(1 row) - --- cleanup -DROP FUNCTION test_temp_pin(int, int); +psql: error: connection to server on socket "/tmp/FPi04qZ6km/.s.PGSQL.13876" failed: could not fork new process for connection: Resource temporarily unavailable diff -U3 /home/postgres/postgres/src/test/regress/expected/rangefuncs.out /home/postgres/postgres/build/testrun/pg_upgrade/002_pg_upgrade/data/results/rangefuncs.out --- /home/postgres/postgres/src/test/regress/expected/rangefuncs.out 2026-01-01 05:40:40.059379321 +0000 +++ /home/postgres/postgres/build/testrun/pg_upgrade/002_pg_upgrade/data/results/rangefuncs.out 2026-01-01 05:42:36.682122029 +0000 @@ -1,2507 +1 @@ -CREATE TABLE rngfunc2(rngfuncid int, f2 int); -INSERT INTO rngfunc2 VALUES(1, 11); -INSERT INTO rngfunc2 VALUES(2, 22); -INSERT INTO rngfunc2 VALUES(1, 111); -CREATE FUNCTION rngfunct(int) returns setof rngfunc2 as 'SELECT * FROM rngfunc2 WHERE rngfuncid = $1 ORDER BY f2;' LANGUAGE SQL; --- function with ORDINALITY -select * from rngfunct(1) with ordinality as z(a,b,ord); - a | b | ord ----+-----+----- - 1 | 11 | 1 - 1 | 111 | 2 -(2 rows) - -select * from rngfunct(1) with ordinality as z(a,b,ord) where b > 100; -- ordinal 2, not 1 - a | b | ord ----+-----+----- - 1 | 111 | 2 -(1 row) - --- ordinality vs. column names and types -select a,b,ord from rngfunct(1) with ordinality as z(a,b,ord); - a | b | ord ----+-----+----- - 1 | 11 | 1 - 1 | 111 | 2 -(2 rows) - -select a,ord from unnest(array['a','b']) with ordinality as z(a,ord); - a | ord ----+----- - a | 1 - b | 2 -(2 rows) - -select * from unnest(array['a','b']) with ordinality as z(a,ord); - a | ord ----+----- - a | 1 - b | 2 -(2 rows) - -select a,ord from unnest(array[1.0::float8]) with ordinality as z(a,ord); - a | ord ----+----- - 1 | 1 -(1 row) - -select * from unnest(array[1.0::float8]) with ordinality as z(a,ord); - a | ord ----+----- - 1 | 1 -(1 row) - -select row_to_json(s.*) from generate_series(11,14) with ordinality s; - row_to_json -------------------------- - {"s":11,"ordinality":1} - {"s":12,"ordinality":2} - {"s":13,"ordinality":3} - {"s":14,"ordinality":4} -(4 rows) - --- ordinality vs. views -create temporary view vw_ord as select * from (values (1)) v(n) join rngfunct(1) with ordinality as z(a,b,ord) on (n=ord); -select * from vw_ord; - n | a | b | ord ----+---+----+----- - 1 | 1 | 11 | 1 -(1 row) - -select definition from pg_views where viewname='vw_ord'; - definition -------------------------------------------------------------------------- - SELECT v.n, + - z.a, + - z.b, + - z.ord + - FROM (( VALUES (1)) v(n) + - JOIN rngfunct(1) WITH ORDINALITY z(a, b, ord) ON ((v.n = z.ord))); -(1 row) - -drop view vw_ord; --- multiple functions -select * from rows from(rngfunct(1),rngfunct(2)) with ordinality as z(a,b,c,d,ord); - a | b | c | d | ord ----+-----+---+----+----- - 1 | 11 | 2 | 22 | 1 - 1 | 111 | | | 2 -(2 rows) - -create temporary view vw_ord as select * from (values (1)) v(n) join rows from(rngfunct(1),rngfunct(2)) with ordinality as z(a,b,c,d,ord) on (n=ord); -select * from vw_ord; - n | a | b | c | d | ord ----+---+----+---+----+----- - 1 | 1 | 11 | 2 | 22 | 1 -(1 row) - -select definition from pg_views where viewname='vw_ord'; - definition -------------------------------------------------------------------------------------------------------- - SELECT v.n, + - z.a, + - z.b, + - z.c, + - z.d, + - z.ord + - FROM (( VALUES (1)) v(n) + - JOIN ROWS FROM(rngfunct(1), rngfunct(2)) WITH ORDINALITY z(a, b, c, d, ord) ON ((v.n = z.ord))); -(1 row) - -drop view vw_ord; --- expansions of unnest() -select * from unnest(array[10,20],array['foo','bar'],array[1.0]); - unnest | unnest | unnest ---------+--------+-------- - 10 | foo | 1.0 - 20 | bar | -(2 rows) - -select * from unnest(array[10,20],array['foo','bar'],array[1.0]) with ordinality as z(a,b,c,ord); - a | b | c | ord -----+-----+-----+----- - 10 | foo | 1.0 | 1 - 20 | bar | | 2 -(2 rows) - -select * from rows from(unnest(array[10,20],array['foo','bar'],array[1.0])) with ordinality as z(a,b,c,ord); - a | b | c | ord -----+-----+-----+----- - 10 | foo | 1.0 | 1 - 20 | bar | | 2 -(2 rows) - -select * from rows from(unnest(array[10,20],array['foo','bar']), generate_series(101,102)) with ordinality as z(a,b,c,ord); - a | b | c | ord -----+-----+-----+----- - 10 | foo | 101 | 1 - 20 | bar | 102 | 2 -(2 rows) - -create temporary view vw_ord as select * from unnest(array[10,20],array['foo','bar'],array[1.0]) as z(a,b,c); -select * from vw_ord; - a | b | c -----+-----+----- - 10 | foo | 1.0 - 20 | bar | -(2 rows) - -select definition from pg_views where viewname='vw_ord'; - definition ----------------------------------------------------------------------------------------- - SELECT a, + - b, + - c + - FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c); -(1 row) - -drop view vw_ord; -create temporary view vw_ord as select * from rows from(unnest(array[10,20],array['foo','bar'],array[1.0])) as z(a,b,c); -select * from vw_ord; - a | b | c -----+-----+----- - 10 | foo | 1.0 - 20 | bar | -(2 rows) - -select definition from pg_views where viewname='vw_ord'; - definition ----------------------------------------------------------------------------------------- - SELECT a, + - b, + - c + - FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c); -(1 row) - -drop view vw_ord; -create temporary view vw_ord as select * from rows from(unnest(array[10,20],array['foo','bar']), generate_series(1,2)) as z(a,b,c); -select * from vw_ord; - a | b | c -----+-----+--- - 10 | foo | 1 - 20 | bar | 2 -(2 rows) - -select definition from pg_views where viewname='vw_ord'; - definition ----------------------------------------------------------------------------------------------------------------------- - SELECT a, + - b, + - c + - FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c); -(1 row) - -drop view vw_ord; --- ordinality and multiple functions vs. rewind and reverse scan -begin; -declare rf_cur scroll cursor for select * from rows from(generate_series(1,5),generate_series(1,2)) with ordinality as g(i,j,o); -fetch all from rf_cur; - i | j | o ----+---+--- - 1 | 1 | 1 - 2 | 2 | 2 - 3 | | 3 - 4 | | 4 - 5 | | 5 -(5 rows) - -fetch backward all from rf_cur; - i | j | o ----+---+--- - 5 | | 5 - 4 | | 4 - 3 | | 3 - 2 | 2 | 2 - 1 | 1 | 1 -(5 rows) - -fetch all from rf_cur; - i | j | o ----+---+--- - 1 | 1 | 1 - 2 | 2 | 2 - 3 | | 3 - 4 | | 4 - 5 | | 5 -(5 rows) - -fetch next from rf_cur; - i | j | o ----+---+--- -(0 rows) - -fetch next from rf_cur; - i | j | o ----+---+--- -(0 rows) - -fetch prior from rf_cur; - i | j | o ----+---+--- - 5 | | 5 -(1 row) - -fetch absolute 1 from rf_cur; - i | j | o ----+---+--- - 1 | 1 | 1 -(1 row) - -fetch next from rf_cur; - i | j | o ----+---+--- - 2 | 2 | 2 -(1 row) - -fetch next from rf_cur; - i | j | o ----+---+--- - 3 | | 3 -(1 row) - -fetch next from rf_cur; - i | j | o ----+---+--- - 4 | | 4 -(1 row) - -fetch prior from rf_cur; - i | j | o ----+---+--- - 3 | | 3 -(1 row) - -fetch prior from rf_cur; - i | j | o ----+---+--- - 2 | 2 | 2 -(1 row) - -fetch prior from rf_cur; - i | j | o ----+---+--- - 1 | 1 | 1 -(1 row) - -commit; --- function with implicit LATERAL -select * from rngfunc2, rngfunct(rngfunc2.rngfuncid) z where rngfunc2.f2 = z.f2; - rngfuncid | f2 | rngfuncid | f2 ------------+-----+-----------+----- - 1 | 11 | 1 | 11 - 2 | 22 | 2 | 22 - 1 | 111 | 1 | 111 -(3 rows) - --- function with implicit LATERAL and explicit ORDINALITY -select * from rngfunc2, rngfunct(rngfunc2.rngfuncid) with ordinality as z(rngfuncid,f2,ord) where rngfunc2.f2 = z.f2; - rngfuncid | f2 | rngfuncid | f2 | ord ------------+-----+-----------+-----+----- - 1 | 11 | 1 | 11 | 1 - 2 | 22 | 2 | 22 | 1 - 1 | 111 | 1 | 111 | 2 -(3 rows) - --- function in subselect -select * from rngfunc2 where f2 in (select f2 from rngfunct(rngfunc2.rngfuncid) z where z.rngfuncid = rngfunc2.rngfuncid) ORDER BY 1,2; - rngfuncid | f2 ------------+----- - 1 | 11 - 1 | 111 - 2 | 22 -(3 rows) - --- function in subselect -select * from rngfunc2 where f2 in (select f2 from rngfunct(1) z where z.rngfuncid = rngfunc2.rngfuncid) ORDER BY 1,2; - rngfuncid | f2 ------------+----- - 1 | 11 - 1 | 111 -(2 rows) - --- function in subselect -select * from rngfunc2 where f2 in (select f2 from rngfunct(rngfunc2.rngfuncid) z where z.rngfuncid = 1) ORDER BY 1,2; - rngfuncid | f2 ------------+----- - 1 | 11 - 1 | 111 -(2 rows) - --- nested functions -select rngfunct.rngfuncid, rngfunct.f2 from rngfunct(sin(pi()/2)::int) ORDER BY 1,2; - rngfuncid | f2 ------------+----- - 1 | 11 - 1 | 111 -(2 rows) - -CREATE TABLE rngfunc (rngfuncid int, rngfuncsubid int, rngfuncname text, primary key(rngfuncid,rngfuncsubid)); -INSERT INTO rngfunc VALUES(1,1,'Joe'); -INSERT INTO rngfunc VALUES(1,2,'Ed'); -INSERT INTO rngfunc VALUES(2,1,'Mary'); --- sql, proretset = f, prorettype = b -CREATE FUNCTION getrngfunc1(int) RETURNS int AS 'SELECT $1;' LANGUAGE SQL; -SELECT * FROM getrngfunc1(1) AS t1; - t1 ----- - 1 -(1 row) - -SELECT * FROM getrngfunc1(1) WITH ORDINALITY AS t1(v,o); - v | o ----+--- - 1 | 1 -(1 row) - -CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc1(1); -SELECT * FROM vw_getrngfunc; - getrngfunc1 -------------- - 1 -(1 row) - -DROP VIEW vw_getrngfunc; -CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc1(1) WITH ORDINALITY as t1(v,o); -SELECT * FROM vw_getrngfunc; - v | o ----+--- - 1 | 1 -(1 row) - -DROP VIEW vw_getrngfunc; --- sql, proretset = t, prorettype = b -CREATE FUNCTION getrngfunc2(int) RETURNS setof int AS 'SELECT rngfuncid FROM rngfunc WHERE rngfuncid = $1;' LANGUAGE SQL; -SELECT * FROM getrngfunc2(1) AS t1; - t1 ----- - 1 - 1 -(2 rows) - -SELECT * FROM getrngfunc2(1) WITH ORDINALITY AS t1(v,o); - v | o ----+--- - 1 | 1 - 1 | 2 -(2 rows) - -CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc2(1); -SELECT * FROM vw_getrngfunc; - getrngfunc2 -------------- - 1 - 1 -(2 rows) - -DROP VIEW vw_getrngfunc; -CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc2(1) WITH ORDINALITY AS t1(v,o); -SELECT * FROM vw_getrngfunc; - v | o ----+--- - 1 | 1 - 1 | 2 -(2 rows) - -DROP VIEW vw_getrngfunc; --- sql, proretset = t, prorettype = b -CREATE FUNCTION getrngfunc3(int) RETURNS setof text AS 'SELECT rngfuncname FROM rngfunc WHERE rngfuncid = $1;' LANGUAGE SQL; -SELECT * FROM getrngfunc3(1) AS t1; - t1 ------ - Joe - Ed -(2 rows) - -SELECT * FROM getrngfunc3(1) WITH ORDINALITY AS t1(v,o); - v | o ------+--- - Joe | 1 - Ed | 2 -(2 rows) - -CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc3(1); -SELECT * FROM vw_getrngfunc; - getrngfunc3 -------------- - Joe - Ed -(2 rows) - -DROP VIEW vw_getrngfunc; -CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc3(1) WITH ORDINALITY AS t1(v,o); -SELECT * FROM vw_getrngfunc; - v | o ------+--- - Joe | 1 - Ed | 2 -(2 rows) - -DROP VIEW vw_getrngfunc; --- sql, proretset = f, prorettype = c -CREATE FUNCTION getrngfunc4(int) RETURNS rngfunc AS 'SELECT * FROM rngfunc WHERE rngfuncid = $1;' LANGUAGE SQL; -SELECT * FROM getrngfunc4(1) AS t1; - rngfuncid | rngfuncsubid | rngfuncname ------------+--------------+------------- - 1 | 1 | Joe -(1 row) - -SELECT * FROM getrngfunc4(1) WITH ORDINALITY AS t1(a,b,c,o); - a | b | c | o ----+---+-----+--- - 1 | 1 | Joe | 1 -(1 row) - -CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc4(1); -SELECT * FROM vw_getrngfunc; - rngfuncid | rngfuncsubid | rngfuncname ------------+--------------+------------- - 1 | 1 | Joe -(1 row) - -DROP VIEW vw_getrngfunc; -CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc4(1) WITH ORDINALITY AS t1(a,b,c,o); -SELECT * FROM vw_getrngfunc; - a | b | c | o ----+---+-----+--- - 1 | 1 | Joe | 1 -(1 row) - -DROP VIEW vw_getrngfunc; --- sql, proretset = t, prorettype = c -CREATE FUNCTION getrngfunc5(int) RETURNS setof rngfunc AS 'SELECT * FROM rngfunc WHERE rngfuncid = $1;' LANGUAGE SQL; -SELECT * FROM getrngfunc5(1) AS t1; - rngfuncid | rngfuncsubid | rngfuncname ------------+--------------+------------- - 1 | 1 | Joe - 1 | 2 | Ed -(2 rows) - -SELECT * FROM getrngfunc5(1) WITH ORDINALITY AS t1(a,b,c,o); - a | b | c | o ----+---+-----+--- - 1 | 1 | Joe | 1 - 1 | 2 | Ed | 2 -(2 rows) - -CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc5(1); -SELECT * FROM vw_getrngfunc; - rngfuncid | rngfuncsubid | rngfuncname ------------+--------------+------------- - 1 | 1 | Joe - 1 | 2 | Ed -(2 rows) - -DROP VIEW vw_getrngfunc; -CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc5(1) WITH ORDINALITY AS t1(a,b,c,o); -SELECT * FROM vw_getrngfunc; - a | b | c | o ----+---+-----+--- - 1 | 1 | Joe | 1 - 1 | 2 | Ed | 2 -(2 rows) - -DROP VIEW vw_getrngfunc; --- sql, proretset = f, prorettype = record -CREATE FUNCTION getrngfunc6(int) RETURNS RECORD AS 'SELECT * FROM rngfunc WHERE rngfuncid = $1;' LANGUAGE SQL; -SELECT * FROM getrngfunc6(1) AS t1(rngfuncid int, rngfuncsubid int, rngfuncname text); - rngfuncid | rngfuncsubid | rngfuncname ------------+--------------+------------- - 1 | 1 | Joe -(1 row) - -SELECT * FROM ROWS FROM( getrngfunc6(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text) ) WITH ORDINALITY; - rngfuncid | rngfuncsubid | rngfuncname | ordinality ------------+--------------+-------------+------------ - 1 | 1 | Joe | 1 -(1 row) - -CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc6(1) AS -(rngfuncid int, rngfuncsubid int, rngfuncname text); -SELECT * FROM vw_getrngfunc; - rngfuncid | rngfuncsubid | rngfuncname ------------+--------------+------------- - 1 | 1 | Joe -(1 row) - -DROP VIEW vw_getrngfunc; -CREATE VIEW vw_getrngfunc AS - SELECT * FROM ROWS FROM( getrngfunc6(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text) ) - WITH ORDINALITY; -SELECT * FROM vw_getrngfunc; - rngfuncid | rngfuncsubid | rngfuncname | ordinality ------------+--------------+-------------+------------ - 1 | 1 | Joe | 1 -(1 row) - -DROP VIEW vw_getrngfunc; --- sql, proretset = t, prorettype = record -CREATE FUNCTION getrngfunc7(int) RETURNS setof record AS 'SELECT * FROM rngfunc WHERE rngfuncid = $1;' LANGUAGE SQL; -SELECT * FROM getrngfunc7(1) AS t1(rngfuncid int, rngfuncsubid int, rngfuncname text); - rngfuncid | rngfuncsubid | rngfuncname ------------+--------------+------------- - 1 | 1 | Joe - 1 | 2 | Ed -(2 rows) - -SELECT * FROM ROWS FROM( getrngfunc7(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text) ) WITH ORDINALITY; - rngfuncid | rngfuncsubid | rngfuncname | ordinality ------------+--------------+-------------+------------ - 1 | 1 | Joe | 1 - 1 | 2 | Ed | 2 -(2 rows) - -CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc7(1) AS -(rngfuncid int, rngfuncsubid int, rngfuncname text); -SELECT * FROM vw_getrngfunc; - rngfuncid | rngfuncsubid | rngfuncname ------------+--------------+------------- - 1 | 1 | Joe - 1 | 2 | Ed -(2 rows) - -DROP VIEW vw_getrngfunc; -CREATE VIEW vw_getrngfunc AS - SELECT * FROM ROWS FROM( getrngfunc7(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text) ) - WITH ORDINALITY; -SELECT * FROM vw_getrngfunc; - rngfuncid | rngfuncsubid | rngfuncname | ordinality ------------+--------------+-------------+------------ - 1 | 1 | Joe | 1 - 1 | 2 | Ed | 2 -(2 rows) - -DROP VIEW vw_getrngfunc; --- plpgsql, proretset = f, prorettype = b -CREATE FUNCTION getrngfunc8(int) RETURNS int AS 'DECLARE rngfuncint int; BEGIN SELECT rngfuncid into rngfuncint FROM rngfunc WHERE rngfuncid = $1; RETURN rngfuncint; END;' LANGUAGE plpgsql; -SELECT * FROM getrngfunc8(1) AS t1; - t1 ----- - 1 -(1 row) - -SELECT * FROM getrngfunc8(1) WITH ORDINALITY AS t1(v,o); - v | o ----+--- - 1 | 1 -(1 row) - -CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc8(1); -SELECT * FROM vw_getrngfunc; - getrngfunc8 -------------- - 1 -(1 row) - -DROP VIEW vw_getrngfunc; -CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc8(1) WITH ORDINALITY AS t1(v,o); -SELECT * FROM vw_getrngfunc; - v | o ----+--- - 1 | 1 -(1 row) - -DROP VIEW vw_getrngfunc; --- plpgsql, proretset = f, prorettype = c -CREATE FUNCTION getrngfunc9(int) RETURNS rngfunc AS 'DECLARE rngfunctup rngfunc%ROWTYPE; BEGIN SELECT * into rngfunctup FROM rngfunc WHERE rngfuncid = $1; RETURN rngfunctup; END;' LANGUAGE plpgsql; -SELECT * FROM getrngfunc9(1) AS t1; - rngfuncid | rngfuncsubid | rngfuncname ------------+--------------+------------- - 1 | 1 | Joe -(1 row) - -SELECT * FROM getrngfunc9(1) WITH ORDINALITY AS t1(a,b,c,o); - a | b | c | o ----+---+-----+--- - 1 | 1 | Joe | 1 -(1 row) - -CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc9(1); -SELECT * FROM vw_getrngfunc; - rngfuncid | rngfuncsubid | rngfuncname ------------+--------------+------------- - 1 | 1 | Joe -(1 row) - -DROP VIEW vw_getrngfunc; -CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc9(1) WITH ORDINALITY AS t1(a,b,c,o); -SELECT * FROM vw_getrngfunc; - a | b | c | o ----+---+-----+--- - 1 | 1 | Joe | 1 -(1 row) - -DROP VIEW vw_getrngfunc; --- mix 'n match kinds, to exercise expandRTE and related logic -select * from rows from(getrngfunc1(1),getrngfunc2(1),getrngfunc3(1),getrngfunc4(1),getrngfunc5(1), - getrngfunc6(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text), - getrngfunc7(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text), - getrngfunc8(1),getrngfunc9(1)) - with ordinality as t1(a,b,c,d,e,f,g,h,i,j,k,l,m,o,p,q,r,s,t,u); - a | b | c | d | e | f | g | h | i | j | k | l | m | o | p | q | r | s | t | u ----+---+-----+---+---+-----+---+---+-----+---+---+-----+---+---+-----+---+---+---+-----+--- - 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | 1 | Joe | 1 - | 1 | Ed | | | | 1 | 2 | Ed | | | | 1 | 2 | Ed | | | | | 2 -(2 rows) - -select * from rows from(getrngfunc9(1),getrngfunc8(1), - getrngfunc7(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text), - getrngfunc6(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text), - getrngfunc5(1),getrngfunc4(1),getrngfunc3(1),getrngfunc2(1),getrngfunc1(1)) - with ordinality as t1(a,b,c,d,e,f,g,h,i,j,k,l,m,o,p,q,r,s,t,u); - a | b | c | d | e | f | g | h | i | j | k | l | m | o | p | q | r | s | t | u ----+---+-----+---+---+---+-----+---+---+-----+---+---+-----+---+---+-----+-----+---+---+--- - 1 | 1 | Joe | 1 | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | Joe | 1 | 1 | 1 - | | | | 1 | 2 | Ed | | | | 1 | 2 | Ed | | | | Ed | 1 | | 2 -(2 rows) - -create temporary view vw_rngfunc as - select * from rows from(getrngfunc9(1), - getrngfunc7(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text), - getrngfunc1(1)) - with ordinality as t1(a,b,c,d,e,f,g,n); -select * from vw_rngfunc; - a | b | c | d | e | f | g | n ----+---+-----+---+---+-----+---+--- - 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 - | | | 1 | 2 | Ed | | 2 -(2 rows) - -select pg_get_viewdef('vw_rngfunc'); - pg_get_viewdef ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - SELECT a, + - b, + - c, + - d, + - e, + - f, + - g, + - n + - FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n); -(1 row) - -drop view vw_rngfunc; -DROP FUNCTION getrngfunc1(int); -DROP FUNCTION getrngfunc2(int); -DROP FUNCTION getrngfunc3(int); -DROP FUNCTION getrngfunc4(int); -DROP FUNCTION getrngfunc5(int); -DROP FUNCTION getrngfunc6(int); -DROP FUNCTION getrngfunc7(int); -DROP FUNCTION getrngfunc8(int); -DROP FUNCTION getrngfunc9(int); -DROP FUNCTION rngfunct(int); -DROP TABLE rngfunc2; -DROP TABLE rngfunc; --- Rescan tests -- -CREATE TEMPORARY SEQUENCE rngfunc_rescan_seq1; -CREATE TEMPORARY SEQUENCE rngfunc_rescan_seq2; -CREATE TYPE rngfunc_rescan_t AS (i integer, s bigint); -CREATE FUNCTION rngfunc_sql(int,int) RETURNS setof rngfunc_rescan_t AS 'SELECT i, nextval(''rngfunc_rescan_seq1'') FROM generate_series($1,$2) i;' LANGUAGE SQL; --- plpgsql functions use materialize mode -CREATE FUNCTION rngfunc_mat(int,int) RETURNS setof rngfunc_rescan_t AS 'begin for i in $1..$2 loop return next (i, nextval(''rngfunc_rescan_seq2'')); end loop; end;' LANGUAGE plpgsql; ---invokes ExecReScanFunctionScan - all these cases should materialize the function only once --- LEFT JOIN on a condition that the planner can't prove to be true is used to ensure the function --- is on the inner path of a nestloop join -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN rngfunc_sql(11,13) ON (r+i)<100; - r | i | s ----+----+--- - 1 | 11 | 1 - 1 | 12 | 2 - 1 | 13 | 3 - 2 | 11 | 1 - 2 | 12 | 2 - 2 | 13 | 3 - 3 | 11 | 1 - 3 | 12 | 2 - 3 | 13 | 3 -(9 rows) - -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN rngfunc_sql(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100; - r | i | s | o ----+----+---+--- - 1 | 11 | 1 | 1 - 1 | 12 | 2 | 2 - 1 | 13 | 3 | 3 - 2 | 11 | 1 | 1 - 2 | 12 | 2 | 2 - 2 | 13 | 3 | 3 - 3 | 11 | 1 | 1 - 3 | 12 | 2 | 2 - 3 | 13 | 3 | 3 -(9 rows) - -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN rngfunc_mat(11,13) ON (r+i)<100; - r | i | s ----+----+--- - 1 | 11 | 1 - 1 | 12 | 2 - 1 | 13 | 3 - 2 | 11 | 1 - 2 | 12 | 2 - 2 | 13 | 3 - 3 | 11 | 1 - 3 | 12 | 2 - 3 | 13 | 3 -(9 rows) - -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN rngfunc_mat(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100; - r | i | s | o ----+----+---+--- - 1 | 11 | 1 | 1 - 1 | 12 | 2 | 2 - 1 | 13 | 3 | 3 - 2 | 11 | 1 | 1 - 2 | 12 | 2 | 2 - 2 | 13 | 3 | 3 - 3 | 11 | 1 | 1 - 3 | 12 | 2 | 2 - 3 | 13 | 3 | 3 -(9 rows) - -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN ROWS FROM( rngfunc_sql(11,13), rngfunc_mat(11,13) ) WITH ORDINALITY AS f(i1,s1,i2,s2,o) ON (r+i1+i2)<100; - r | i1 | s1 | i2 | s2 | o ----+----+----+----+----+--- - 1 | 11 | 1 | 11 | 1 | 1 - 1 | 12 | 2 | 12 | 2 | 2 - 1 | 13 | 3 | 13 | 3 | 3 - 2 | 11 | 1 | 11 | 1 | 1 - 2 | 12 | 2 | 12 | 2 | 2 - 2 | 13 | 3 | 13 | 3 | 3 - 3 | 11 | 1 | 11 | 1 | 1 - 3 | 12 | 2 | 12 | 2 | 2 - 3 | 13 | 3 | 13 | 3 | 3 -(9 rows) - -SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) f(i) ON (r+i)<100; - r | i ----+---- - 1 | 11 - 1 | 12 - 1 | 13 - 2 | 11 - 2 | 12 - 2 | 13 - 3 | 11 - 3 | 12 - 3 | 13 -(9 rows) - -SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) WITH ORDINALITY AS f(i,o) ON (r+i)<100; - r | i | o ----+----+--- - 1 | 11 | 1 - 1 | 12 | 2 - 1 | 13 | 3 - 2 | 11 | 1 - 2 | 12 | 2 - 2 | 13 | 3 - 3 | 11 | 1 - 3 | 12 | 2 - 3 | 13 | 3 -(9 rows) - -SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) f(i) ON (r+i)<100; - r | i ----+---- - 1 | 10 - 1 | 20 - 1 | 30 - 2 | 10 - 2 | 20 - 2 | 30 - 3 | 10 - 3 | 20 - 3 | 30 -(9 rows) - -SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) WITH ORDINALITY AS f(i,o) ON (r+i)<100; - r | i | o ----+----+--- - 1 | 10 | 1 - 1 | 20 | 2 - 1 | 30 | 3 - 2 | 10 | 1 - 2 | 20 | 2 - 2 | 30 | 3 - 3 | 10 | 1 - 3 | 20 | 2 - 3 | 30 | 3 -(9 rows) - ---invokes ExecReScanFunctionScan with chgParam != NULL (using implied LATERAL) -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_sql(10+r,13); - r | i | s ----+----+--- - 1 | 11 | 1 - 1 | 12 | 2 - 1 | 13 | 3 - 2 | 12 | 4 - 2 | 13 | 5 - 3 | 13 | 6 -(6 rows) - -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_sql(10+r,13) WITH ORDINALITY AS f(i,s,o); - r | i | s | o ----+----+---+--- - 1 | 11 | 1 | 1 - 1 | 12 | 2 | 2 - 1 | 13 | 3 | 3 - 2 | 12 | 4 | 1 - 2 | 13 | 5 | 2 - 3 | 13 | 6 | 1 -(6 rows) - -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_sql(11,10+r); - r | i | s ----+----+--- - 1 | 11 | 1 - 2 | 11 | 2 - 2 | 12 | 3 - 3 | 11 | 4 - 3 | 12 | 5 - 3 | 13 | 6 -(6 rows) - -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_sql(11,10+r) WITH ORDINALITY AS f(i,s,o); - r | i | s | o ----+----+---+--- - 1 | 11 | 1 | 1 - 2 | 11 | 2 | 1 - 2 | 12 | 3 | 2 - 3 | 11 | 4 | 1 - 3 | 12 | 5 | 2 - 3 | 13 | 6 | 3 -(6 rows) - -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), rngfunc_sql(r1,r2); - r1 | r2 | i | s -----+----+----+---- - 11 | 12 | 11 | 1 - 11 | 12 | 12 | 2 - 13 | 15 | 13 | 3 - 13 | 15 | 14 | 4 - 13 | 15 | 15 | 5 - 16 | 20 | 16 | 6 - 16 | 20 | 17 | 7 - 16 | 20 | 18 | 8 - 16 | 20 | 19 | 9 - 16 | 20 | 20 | 10 -(10 rows) - -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), rngfunc_sql(r1,r2) WITH ORDINALITY AS f(i,s,o); - r1 | r2 | i | s | o -----+----+----+----+--- - 11 | 12 | 11 | 1 | 1 - 11 | 12 | 12 | 2 | 2 - 13 | 15 | 13 | 3 | 1 - 13 | 15 | 14 | 4 | 2 - 13 | 15 | 15 | 5 | 3 - 16 | 20 | 16 | 6 | 1 - 16 | 20 | 17 | 7 | 2 - 16 | 20 | 18 | 8 | 3 - 16 | 20 | 19 | 9 | 4 - 16 | 20 | 20 | 10 | 5 -(10 rows) - -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_mat(10+r,13); - r | i | s ----+----+--- - 1 | 11 | 1 - 1 | 12 | 2 - 1 | 13 | 3 - 2 | 12 | 4 - 2 | 13 | 5 - 3 | 13 | 6 -(6 rows) - -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_mat(10+r,13) WITH ORDINALITY AS f(i,s,o); - r | i | s | o ----+----+---+--- - 1 | 11 | 1 | 1 - 1 | 12 | 2 | 2 - 1 | 13 | 3 | 3 - 2 | 12 | 4 | 1 - 2 | 13 | 5 | 2 - 3 | 13 | 6 | 1 -(6 rows) - -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_mat(11,10+r); - r | i | s ----+----+--- - 1 | 11 | 1 - 2 | 11 | 2 - 2 | 12 | 3 - 3 | 11 | 4 - 3 | 12 | 5 - 3 | 13 | 6 -(6 rows) - -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_mat(11,10+r) WITH ORDINALITY AS f(i,s,o); - r | i | s | o ----+----+---+--- - 1 | 11 | 1 | 1 - 2 | 11 | 2 | 1 - 2 | 12 | 3 | 2 - 3 | 11 | 4 | 1 - 3 | 12 | 5 | 2 - 3 | 13 | 6 | 3 -(6 rows) - -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), rngfunc_mat(r1,r2); - r1 | r2 | i | s -----+----+----+---- - 11 | 12 | 11 | 1 - 11 | 12 | 12 | 2 - 13 | 15 | 13 | 3 - 13 | 15 | 14 | 4 - 13 | 15 | 15 | 5 - 16 | 20 | 16 | 6 - 16 | 20 | 17 | 7 - 16 | 20 | 18 | 8 - 16 | 20 | 19 | 9 - 16 | 20 | 20 | 10 -(10 rows) - -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), rngfunc_mat(r1,r2) WITH ORDINALITY AS f(i,s,o); - r1 | r2 | i | s | o -----+----+----+----+--- - 11 | 12 | 11 | 1 | 1 - 11 | 12 | 12 | 2 | 2 - 13 | 15 | 13 | 3 | 1 - 13 | 15 | 14 | 4 | 2 - 13 | 15 | 15 | 5 | 3 - 16 | 20 | 16 | 6 | 1 - 16 | 20 | 17 | 7 | 2 - 16 | 20 | 18 | 8 | 3 - 16 | 20 | 19 | 9 | 4 - 16 | 20 | 20 | 10 | 5 -(10 rows) - --- selective rescan of multiple functions: -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( rngfunc_sql(11,11), rngfunc_mat(10+r,13) ); - r | i | s | i | s ----+----+---+----+--- - 1 | 11 | 1 | 11 | 1 - 1 | | | 12 | 2 - 1 | | | 13 | 3 - 2 | 11 | 1 | 12 | 4 - 2 | | | 13 | 5 - 3 | 11 | 1 | 13 | 6 -(6 rows) - -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( rngfunc_sql(10+r,13), rngfunc_mat(11,11) ); - r | i | s | i | s ----+----+---+----+--- - 1 | 11 | 1 | 11 | 1 - 1 | 12 | 2 | | - 1 | 13 | 3 | | - 2 | 12 | 4 | 11 | 1 - 2 | 13 | 5 | | - 3 | 13 | 6 | 11 | 1 -(6 rows) - -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( rngfunc_sql(10+r,13), rngfunc_mat(10+r,13) ); - r | i | s | i | s ----+----+---+----+--- - 1 | 11 | 1 | 11 | 1 - 1 | 12 | 2 | 12 | 2 - 1 | 13 | 3 | 13 | 3 - 2 | 12 | 4 | 12 | 4 - 2 | 13 | 5 | 13 | 5 - 3 | 13 | 6 | 13 | 6 -(6 rows) - -SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 -(1 row) - -SELECT * FROM generate_series(1,2) r1, generate_series(r1,3) r2, ROWS FROM( rngfunc_sql(10+r1,13), rngfunc_mat(10+r2,13) ); - r1 | r2 | i | s | i | s -----+----+----+----+----+--- - 1 | 1 | 11 | 1 | 11 | 1 - 1 | 1 | 12 | 2 | 12 | 2 - 1 | 1 | 13 | 3 | 13 | 3 - 1 | 2 | 11 | 4 | 12 | 4 - 1 | 2 | 12 | 5 | 13 | 5 - 1 | 2 | 13 | 6 | | - 1 | 3 | 11 | 7 | 13 | 6 - 1 | 3 | 12 | 8 | | - 1 | 3 | 13 | 9 | | - 2 | 2 | 12 | 10 | 12 | 7 - 2 | 2 | 13 | 11 | 13 | 8 - 2 | 3 | 12 | 12 | 13 | 9 - 2 | 3 | 13 | 13 | | -(13 rows) - -SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) f(i); - r | i ----+---- - 1 | 11 - 1 | 12 - 1 | 13 - 1 | 14 - 1 | 15 - 1 | 16 - 1 | 17 - 1 | 18 - 1 | 19 - 2 | 12 - 2 | 13 - 2 | 14 - 2 | 15 - 2 | 16 - 2 | 17 - 2 | 18 - 3 | 13 - 3 | 14 - 3 | 15 - 3 | 16 - 3 | 17 -(21 rows) - -SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) WITH ORDINALITY AS f(i,o); - r | i | o ----+----+--- - 1 | 11 | 1 - 1 | 12 | 2 - 1 | 13 | 3 - 1 | 14 | 4 - 1 | 15 | 5 - 1 | 16 | 6 - 1 | 17 | 7 - 1 | 18 | 8 - 1 | 19 | 9 - 2 | 12 | 1 - 2 | 13 | 2 - 2 | 14 | 3 - 2 | 15 | 4 - 2 | 16 | 5 - 2 | 17 | 6 - 2 | 18 | 7 - 3 | 13 | 1 - 3 | 14 | 2 - 3 | 15 | 3 - 3 | 16 | 4 - 3 | 17 | 5 -(21 rows) - -SELECT * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) f(i); - r | i ----+---- - 1 | 10 - 1 | 20 - 1 | 30 - 2 | 20 - 2 | 40 - 2 | 60 - 3 | 30 - 3 | 60 - 3 | 90 -(9 rows) - -SELECT * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) WITH ORDINALITY AS f(i,o); - r | i | o ----+----+--- - 1 | 10 | 1 - 1 | 20 | 2 - 1 | 30 | 3 - 2 | 20 | 1 - 2 | 40 | 2 - 2 | 60 | 3 - 3 | 30 | 1 - 3 | 60 | 2 - 3 | 90 | 3 -(9 rows) - --- deep nesting -SELECT * FROM (VALUES (1),(2),(3)) v1(r1), - LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2) - LEFT JOIN generate_series(21,23) f(i) ON ((r2+i)<100) OFFSET 0) s1; - r1 | r1 | r2 | i -----+----+----+---- - 1 | 1 | 10 | 21 - 1 | 1 | 10 | 22 - 1 | 1 | 10 | 23 - 1 | 1 | 20 | 21 - 1 | 1 | 20 | 22 - 1 | 1 | 20 | 23 - 1 | 1 | 30 | 21 - 1 | 1 | 30 | 22 - 1 | 1 | 30 | 23 - 2 | 2 | 10 | 21 - 2 | 2 | 10 | 22 - 2 | 2 | 10 | 23 - 2 | 2 | 20 | 21 - 2 | 2 | 20 | 22 - 2 | 2 | 20 | 23 - 2 | 2 | 30 | 21 - 2 | 2 | 30 | 22 - 2 | 2 | 30 | 23 - 3 | 3 | 10 | 21 - 3 | 3 | 10 | 22 - 3 | 3 | 10 | 23 - 3 | 3 | 20 | 21 - 3 | 3 | 20 | 22 - 3 | 3 | 20 | 23 - 3 | 3 | 30 | 21 - 3 | 3 | 30 | 22 - 3 | 3 | 30 | 23 -(27 rows) - -SELECT * FROM (VALUES (1),(2),(3)) v1(r1), - LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2) - LEFT JOIN generate_series(20+r1,23) f(i) ON ((r2+i)<100) OFFSET 0) s1; - r1 | r1 | r2 | i -----+----+----+---- - 1 | 1 | 10 | 21 - 1 | 1 | 10 | 22 - 1 | 1 | 10 | 23 - 1 | 1 | 20 | 21 - 1 | 1 | 20 | 22 - 1 | 1 | 20 | 23 - 1 | 1 | 30 | 21 - 1 | 1 | 30 | 22 - 1 | 1 | 30 | 23 - 2 | 2 | 10 | 22 - 2 | 2 | 10 | 23 - 2 | 2 | 20 | 22 - 2 | 2 | 20 | 23 - 2 | 2 | 30 | 22 - 2 | 2 | 30 | 23 - 3 | 3 | 10 | 23 - 3 | 3 | 20 | 23 - 3 | 3 | 30 | 23 -(18 rows) - -SELECT * FROM (VALUES (1),(2),(3)) v1(r1), - LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2) - LEFT JOIN generate_series(r2,r2+3) f(i) ON ((r2+i)<100) OFFSET 0) s1; - r1 | r1 | r2 | i -----+----+----+---- - 1 | 1 | 10 | 10 - 1 | 1 | 10 | 11 - 1 | 1 | 10 | 12 - 1 | 1 | 10 | 13 - 1 | 1 | 20 | 20 - 1 | 1 | 20 | 21 - 1 | 1 | 20 | 22 - 1 | 1 | 20 | 23 - 1 | 1 | 30 | 30 - 1 | 1 | 30 | 31 - 1 | 1 | 30 | 32 - 1 | 1 | 30 | 33 - 2 | 2 | 10 | 10 - 2 | 2 | 10 | 11 - 2 | 2 | 10 | 12 - 2 | 2 | 10 | 13 - 2 | 2 | 20 | 20 - 2 | 2 | 20 | 21 - 2 | 2 | 20 | 22 - 2 | 2 | 20 | 23 - 2 | 2 | 30 | 30 - 2 | 2 | 30 | 31 - 2 | 2 | 30 | 32 - 2 | 2 | 30 | 33 - 3 | 3 | 10 | 10 - 3 | 3 | 10 | 11 - 3 | 3 | 10 | 12 - 3 | 3 | 10 | 13 - 3 | 3 | 20 | 20 - 3 | 3 | 20 | 21 - 3 | 3 | 20 | 22 - 3 | 3 | 20 | 23 - 3 | 3 | 30 | 30 - 3 | 3 | 30 | 31 - 3 | 3 | 30 | 32 - 3 | 3 | 30 | 33 -(36 rows) - -SELECT * FROM (VALUES (1),(2),(3)) v1(r1), - LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2) - LEFT JOIN generate_series(r1,2+r2/5) f(i) ON ((r2+i)<100) OFFSET 0) s1; - r1 | r1 | r2 | i -----+----+----+--- - 1 | 1 | 10 | 1 - 1 | 1 | 10 | 2 - 1 | 1 | 10 | 3 - 1 | 1 | 10 | 4 - 1 | 1 | 20 | 1 - 1 | 1 | 20 | 2 - 1 | 1 | 20 | 3 - 1 | 1 | 20 | 4 - 1 | 1 | 20 | 5 - 1 | 1 | 20 | 6 - 1 | 1 | 30 | 1 - 1 | 1 | 30 | 2 - 1 | 1 | 30 | 3 - 1 | 1 | 30 | 4 - 1 | 1 | 30 | 5 - 1 | 1 | 30 | 6 - 1 | 1 | 30 | 7 - 1 | 1 | 30 | 8 - 2 | 2 | 10 | 2 - 2 | 2 | 10 | 3 - 2 | 2 | 10 | 4 - 2 | 2 | 20 | 2 - 2 | 2 | 20 | 3 - 2 | 2 | 20 | 4 - 2 | 2 | 20 | 5 - 2 | 2 | 20 | 6 - 2 | 2 | 30 | 2 - 2 | 2 | 30 | 3 - 2 | 2 | 30 | 4 - 2 | 2 | 30 | 5 - 2 | 2 | 30 | 6 - 2 | 2 | 30 | 7 - 2 | 2 | 30 | 8 - 3 | 3 | 10 | 3 - 3 | 3 | 10 | 4 - 3 | 3 | 20 | 3 - 3 | 3 | 20 | 4 - 3 | 3 | 20 | 5 - 3 | 3 | 20 | 6 - 3 | 3 | 30 | 3 - 3 | 3 | 30 | 4 - 3 | 3 | 30 | 5 - 3 | 3 | 30 | 6 - 3 | 3 | 30 | 7 - 3 | 3 | 30 | 8 -(45 rows) - --- check handling of FULL JOIN with multiple lateral references (bug #15741) -SELECT * -FROM (VALUES (1),(2)) v1(r1) - LEFT JOIN LATERAL ( - SELECT * - FROM generate_series(1, v1.r1) AS gs1 - LEFT JOIN LATERAL ( - SELECT * - FROM generate_series(1, gs1) AS gs2 - LEFT JOIN generate_series(1, gs2) AS gs3 ON TRUE - ) AS ss1 ON TRUE - FULL JOIN generate_series(1, v1.r1) AS gs4 ON FALSE - ) AS ss0 ON TRUE; - r1 | gs1 | gs2 | gs3 | gs4 -----+-----+-----+-----+----- - 1 | | | | 1 - 1 | 1 | 1 | 1 | - 2 | | | | 1 - 2 | | | | 2 - 2 | 1 | 1 | 1 | - 2 | 2 | 1 | 1 | - 2 | 2 | 2 | 1 | - 2 | 2 | 2 | 2 | -(8 rows) - -DROP FUNCTION rngfunc_sql(int,int); -DROP FUNCTION rngfunc_mat(int,int); -DROP SEQUENCE rngfunc_rescan_seq1; -DROP SEQUENCE rngfunc_rescan_seq2; --- --- Test cases involving OUT parameters --- -CREATE FUNCTION rngfunc(in f1 int, out f2 int) -AS 'select $1+1' LANGUAGE sql; -SELECT rngfunc(42); - rngfunc ---------- - 43 -(1 row) - -SELECT * FROM rngfunc(42); - f2 ----- - 43 -(1 row) - -SELECT * FROM rngfunc(42) AS p(x); - x ----- - 43 -(1 row) - --- explicit spec of return type is OK -CREATE OR REPLACE FUNCTION rngfunc(in f1 int, out f2 int) RETURNS int -AS 'select $1+1' LANGUAGE sql; --- error, wrong result type -CREATE OR REPLACE FUNCTION rngfunc(in f1 int, out f2 int) RETURNS float -AS 'select $1+1' LANGUAGE sql; -ERROR: function result type must be integer because of OUT parameters --- with multiple OUT params you must get a RECORD result -CREATE OR REPLACE FUNCTION rngfunc(in f1 int, out f2 int, out f3 text) RETURNS int -AS 'select $1+1' LANGUAGE sql; -ERROR: function result type must be record because of OUT parameters -CREATE OR REPLACE FUNCTION rngfunc(in f1 int, out f2 int, out f3 text) -RETURNS record -AS 'select $1+1' LANGUAGE sql; -ERROR: cannot change return type of existing function -HINT: Use DROP FUNCTION rngfunc(integer) first. -CREATE OR REPLACE FUNCTION rngfuncr(in f1 int, out f2 int, out text) -AS $$select $1-1, $1::text || 'z'$$ LANGUAGE sql; -SELECT f1, rngfuncr(f1) FROM int4_tbl; - f1 | rngfuncr --------------+---------------------------- - 0 | (-1,0z) - 123456 | (123455,123456z) - -123456 | (-123457,-123456z) - 2147483647 | (2147483646,2147483647z) - -2147483647 | (-2147483648,-2147483647z) -(5 rows) - -SELECT * FROM rngfuncr(42); - f2 | column2 -----+--------- - 41 | 42z -(1 row) - -SELECT * FROM rngfuncr(42) AS p(a,b); - a | b -----+----- - 41 | 42z -(1 row) - -CREATE OR REPLACE FUNCTION rngfuncb(in f1 int, inout f2 int, out text) -AS $$select $2-1, $1::text || 'z'$$ LANGUAGE sql; -SELECT f1, rngfuncb(f1, f1/2) FROM int4_tbl; - f1 | rngfuncb --------------+---------------------------- - 0 | (-1,0z) - 123456 | (61727,123456z) - -123456 | (-61729,-123456z) - 2147483647 | (1073741822,2147483647z) - -2147483647 | (-1073741824,-2147483647z) -(5 rows) - -SELECT * FROM rngfuncb(42, 99); - f2 | column2 -----+--------- - 98 | 42z -(1 row) - -SELECT * FROM rngfuncb(42, 99) AS p(a,b); - a | b -----+----- - 98 | 42z -(1 row) - --- Can reference function with or without OUT params for DROP, etc -DROP FUNCTION rngfunc(int); -DROP FUNCTION rngfuncr(in f2 int, out f1 int, out text); -DROP FUNCTION rngfuncb(in f1 int, inout f2 int); --- --- For my next trick, polymorphic OUT parameters --- -CREATE FUNCTION dup (f1 anyelement, f2 out anyelement, f3 out anyarray) -AS 'select $1, array[$1,$1]' LANGUAGE sql; -SELECT dup(22); - dup ----------------- - (22,"{22,22}") -(1 row) - -SELECT dup('xyz'); -- fails -ERROR: could not determine polymorphic type because input has type unknown -SELECT dup('xyz'::text); - dup -------------------- - (xyz,"{xyz,xyz}") -(1 row) - -SELECT * FROM dup('xyz'::text); - f2 | f3 ------+----------- - xyz | {xyz,xyz} -(1 row) - --- fails, as we are attempting to rename first argument -CREATE OR REPLACE FUNCTION dup (inout f2 anyelement, out f3 anyarray) -AS 'select $1, array[$1,$1]' LANGUAGE sql; -ERROR: cannot change name of input parameter "f1" -HINT: Use DROP FUNCTION dup(anyelement) first. -DROP FUNCTION dup(anyelement); --- equivalent behavior, though different name exposed for input arg -CREATE OR REPLACE FUNCTION dup (inout f2 anyelement, out f3 anyarray) -AS 'select $1, array[$1,$1]' LANGUAGE sql; -SELECT dup(22); - dup ----------------- - (22,"{22,22}") -(1 row) - -DROP FUNCTION dup(anyelement); --- fails, no way to deduce outputs -CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray) -AS 'select $1, array[$1,$1]' LANGUAGE sql; -ERROR: cannot determine result data type -DETAIL: A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange. -CREATE FUNCTION dup (f1 anycompatible, f2 anycompatiblearray, f3 out anycompatible, f4 out anycompatiblearray) -AS 'select $1, $2' LANGUAGE sql; -SELECT dup(22, array[44]); - dup ------------ - (22,{44}) -(1 row) - -SELECT dup(4.5, array[44]); - dup ------------- - (4.5,{44}) -(1 row) - -SELECT dup(22, array[44::bigint]); - dup ------------ - (22,{44}) -(1 row) - -SELECT *, pg_typeof(f3), pg_typeof(f4) FROM dup(22, array[44::bigint]); - f3 | f4 | pg_typeof | pg_typeof -----+------+-----------+----------- - 22 | {44} | bigint | bigint[] -(1 row) - -DROP FUNCTION dup(f1 anycompatible, f2 anycompatiblearray); -CREATE FUNCTION dup (f1 anycompatiblerange, f2 out anycompatible, f3 out anycompatiblearray, f4 out anycompatiblerange) -AS 'select lower($1), array[lower($1), upper($1)], $1' LANGUAGE sql; -SELECT dup(int4range(4,7)); - dup ---------------------- - (4,"{4,7}","[4,7)") -(1 row) - -SELECT dup(numrange(4,7)); - dup ---------------------- - (4,"{4,7}","[4,7)") -(1 row) - -SELECT dup(textrange('aaa', 'bbb')); - dup -------------------------------- - (aaa,"{aaa,bbb}","[aaa,bbb)") -(1 row) - -DROP FUNCTION dup(f1 anycompatiblerange); --- fails, no way to deduce outputs -CREATE FUNCTION bad (f1 anyarray, out f2 anycompatible, out f3 anycompatiblearray) -AS 'select $1, array[$1,$1]' LANGUAGE sql; -ERROR: cannot determine result data type -DETAIL: A result of type anycompatible requires at least one input of type anycompatible, anycompatiblearray, anycompatiblenonarray, anycompatiblerange, or anycompatiblemultirange. --- --- table functions --- -CREATE OR REPLACE FUNCTION rngfunc() -RETURNS TABLE(a int) -AS $$ SELECT a FROM generate_series(1,5) a(a) $$ LANGUAGE sql; -SELECT * FROM rngfunc(); - a ---- - 1 - 2 - 3 - 4 - 5 -(5 rows) - -DROP FUNCTION rngfunc(); -CREATE OR REPLACE FUNCTION rngfunc(int) -RETURNS TABLE(a int, b int) -AS $$ SELECT a, b - FROM generate_series(1,$1) a(a), - generate_series(1,$1) b(b) $$ LANGUAGE sql; -SELECT * FROM rngfunc(3); - a | b ----+--- - 1 | 1 - 1 | 2 - 1 | 3 - 2 | 1 - 2 | 2 - 2 | 3 - 3 | 1 - 3 | 2 - 3 | 3 -(9 rows) - -DROP FUNCTION rngfunc(int); --- case that causes change of typmod knowledge during inlining -CREATE OR REPLACE FUNCTION rngfunc() -RETURNS TABLE(a varchar(5)) -AS $$ SELECT 'hello'::varchar(5) $$ LANGUAGE sql STABLE; -SELECT * FROM rngfunc() GROUP BY 1; - a -------- - hello -(1 row) - -DROP FUNCTION rngfunc(); --- --- some tests on SQL functions with RETURNING --- -create temp table tt(f1 serial, data text); -create function insert_tt(text) returns int as -$$ insert into tt(data) values($1) returning f1 $$ -language sql; -select insert_tt('foo'); - insert_tt ------------ - 1 -(1 row) - -select insert_tt('bar'); - insert_tt ------------ - 2 -(1 row) - -select * from tt; - f1 | data -----+------ - 1 | foo - 2 | bar -(2 rows) - --- insert will execute to completion even if function needs just 1 row -create or replace function insert_tt(text) returns int as -$$ insert into tt(data) values($1),($1||$1) returning f1 $$ -language sql; -select insert_tt('fool'); - insert_tt ------------ - 3 -(1 row) - -select * from tt; - f1 | data -----+---------- - 1 | foo - 2 | bar - 3 | fool - 4 | foolfool -(4 rows) - --- setof does what's expected -create or replace function insert_tt2(text,text) returns setof int as -$$ insert into tt(data) values($1),($2) returning f1 $$ -language sql; -select insert_tt2('foolish','barrish'); - insert_tt2 ------------- - 5 - 6 -(2 rows) - -select * from insert_tt2('baz','quux'); - insert_tt2 ------------- - 7 - 8 -(2 rows) - -select * from tt; - f1 | data -----+---------- - 1 | foo - 2 | bar - 3 | fool - 4 | foolfool - 5 | foolish - 6 | barrish - 7 | baz - 8 | quux -(8 rows) - --- limit doesn't prevent execution to completion -select insert_tt2('foolish','barrish') limit 1; - insert_tt2 ------------- - 9 -(1 row) - -select * from tt; - f1 | data -----+---------- - 1 | foo - 2 | bar - 3 | fool - 4 | foolfool - 5 | foolish - 6 | barrish - 7 | baz - 8 | quux - 9 | foolish - 10 | barrish -(10 rows) - --- triggers will fire, too -create function noticetrigger() returns trigger as $$ -begin - raise notice 'noticetrigger % %', new.f1, new.data; - return null; -end $$ language plpgsql; -create trigger tnoticetrigger after insert on tt for each row -execute procedure noticetrigger(); -select insert_tt2('foolme','barme') limit 1; -NOTICE: noticetrigger 11 foolme -NOTICE: noticetrigger 12 barme - insert_tt2 ------------- - 11 -(1 row) - -select * from tt; - f1 | data -----+---------- - 1 | foo - 2 | bar - 3 | fool - 4 | foolfool - 5 | foolish - 6 | barrish - 7 | baz - 8 | quux - 9 | foolish - 10 | barrish - 11 | foolme - 12 | barme -(12 rows) - --- and rules work -create temp table tt_log(f1 int, data text); -create rule insert_tt_rule as on insert to tt do also - insert into tt_log values(new.*); -select insert_tt2('foollog','barlog') limit 1; -NOTICE: noticetrigger 13 foollog -NOTICE: noticetrigger 14 barlog - insert_tt2 ------------- - 13 -(1 row) - -select * from tt; - f1 | data -----+---------- - 1 | foo - 2 | bar - 3 | fool - 4 | foolfool - 5 | foolish - 6 | barrish - 7 | baz - 8 | quux - 9 | foolish - 10 | barrish - 11 | foolme - 12 | barme - 13 | foollog - 14 | barlog -(14 rows) - --- note that nextval() gets executed a second time in the rule expansion, --- which is expected. -select * from tt_log; - f1 | data -----+--------- - 15 | foollog - 16 | barlog -(2 rows) - --- test case for a whole-row-variable bug -create function rngfunc1(n integer, out a text, out b text) - returns setof record - language sql - as $$ select 'foo ' || i, 'bar ' || i from generate_series(1,$1) i $$; -set work_mem='64kB'; -select t.a, t, t.a from rngfunc1(10000) t limit 1; - a | t | a --------+-------------------+------- - foo 1 | ("foo 1","bar 1") | foo 1 -(1 row) - -reset work_mem; -select t.a, t, t.a from rngfunc1(10000) t limit 1; - a | t | a --------+-------------------+------- - foo 1 | ("foo 1","bar 1") | foo 1 -(1 row) - -drop function rngfunc1(n integer); --- test use of SQL functions returning record --- this is supported in some cases where the query doesn't specify --- the actual record type ... -create function array_to_set(anyarray) returns setof record as $$ - select i AS "index", $1[i] AS "value" from generate_subscripts($1, 1) i -$$ language sql strict immutable; -select array_to_set(array['one', 'two']); - array_to_set --------------- - (1,one) - (2,two) -(2 rows) - -select * from array_to_set(array['one', 'two']) as t(f1 int,f2 text); - f1 | f2 -----+----- - 1 | one - 2 | two -(2 rows) - -select * from array_to_set(array['one', 'two']); -- fail -ERROR: a column definition list is required for functions returning "record" -LINE 1: select * from array_to_set(array['one', 'two']); - ^ --- after-the-fact coercion of the columns is now possible, too -select * from array_to_set(array['one', 'two']) as t(f1 numeric(4,2),f2 text); - f1 | f2 -------+----- - 1.00 | one - 2.00 | two -(2 rows) - --- and if it doesn't work, you get a compile-time not run-time error -select * from array_to_set(array['one', 'two']) as t(f1 point,f2 text); -ERROR: return type mismatch in function declared to return record -DETAIL: Final statement returns integer instead of point at column 1. -CONTEXT: SQL function "array_to_set" statement 1 --- with "strict", this function can't be inlined in FROM -explain (verbose, costs off) - select * from array_to_set(array['one', 'two']) as t(f1 numeric(4,2),f2 text); - QUERY PLAN ----------------------------------------------------- - Function Scan on public.array_to_set t - Output: f1, f2 - Function Call: array_to_set('{one,two}'::text[]) -(3 rows) - --- but without, it can be: -create or replace function array_to_set(anyarray) returns setof record as $$ - select i AS "index", $1[i] AS "value" from generate_subscripts($1, 1) i -$$ language sql immutable; -select array_to_set(array['one', 'two']); - array_to_set --------------- - (1,one) - (2,two) -(2 rows) - -select * from array_to_set(array['one', 'two']) as t(f1 int,f2 text); - f1 | f2 -----+----- - 1 | one - 2 | two -(2 rows) - -select * from array_to_set(array['one', 'two']) as t(f1 numeric(4,2),f2 text); - f1 | f2 -------+----- - 1.00 | one - 2.00 | two -(2 rows) - -select * from array_to_set(array['one', 'two']) as t(f1 point,f2 text); -ERROR: return type mismatch in function declared to return record -DETAIL: Final statement returns integer instead of point at column 1. -CONTEXT: SQL function "array_to_set" during inlining -explain (verbose, costs off) - select * from array_to_set(array['one', 'two']) as t(f1 numeric(4,2),f2 text); - QUERY PLAN --------------------------------------------------------------- - Function Scan on pg_catalog.generate_subscripts i - Output: i.i, ('{one,two}'::text[])[i.i] - Function Call: generate_subscripts('{one,two}'::text[], 1) -(3 rows) - -create temp table rngfunc(f1 int8, f2 int8); -create function testrngfunc() returns record as $$ - insert into rngfunc values (1,2) returning *; -$$ language sql; -select testrngfunc(); - testrngfunc -------------- - (1,2) -(1 row) - -select * from testrngfunc() as t(f1 int8,f2 int8); - f1 | f2 -----+---- - 1 | 2 -(1 row) - -select * from testrngfunc(); -- fail -ERROR: a column definition list is required for functions returning "record" -LINE 1: select * from testrngfunc(); - ^ -drop function testrngfunc(); -create function testrngfunc() returns setof record as $$ - insert into rngfunc values (1,2), (3,4) returning *; -$$ language sql; -select testrngfunc(); - testrngfunc -------------- - (1,2) - (3,4) -(2 rows) - -select * from testrngfunc() as t(f1 int8,f2 int8); - f1 | f2 -----+---- - 1 | 2 - 3 | 4 -(2 rows) - -select * from testrngfunc(); -- fail -ERROR: a column definition list is required for functions returning "record" -LINE 1: select * from testrngfunc(); - ^ -drop function testrngfunc(); --- Check that typmod imposed by a composite type is honored -create type rngfunc_type as (f1 numeric(35,6), f2 numeric(35,2)); -create function testrngfunc() returns rngfunc_type as $$ - select 7.136178319899999964, 7.136178319899999964; -$$ language sql immutable; -explain (verbose, costs off) -select testrngfunc(); - QUERY PLAN -------------------------------------------- - Result - Output: '(7.136178,7.14)'::rngfunc_type -(2 rows) - -select testrngfunc(); - testrngfunc ------------------ - (7.136178,7.14) -(1 row) - -explain (verbose, costs off) -select * from testrngfunc(); - QUERY PLAN --------------------------------------------------- - Function Scan on testrngfunc - Output: f1, f2 - Function Call: '(7.136178,7.14)'::rngfunc_type -(3 rows) - -select * from testrngfunc(); - f1 | f2 -----------+------ - 7.136178 | 7.14 -(1 row) - -create or replace function testrngfunc() returns rngfunc_type as $$ - select 7.136178319899999964, 7.136178319899999964; -$$ language sql volatile; -explain (verbose, costs off) -select testrngfunc(); - QUERY PLAN -------------------------- - Result - Output: testrngfunc() -(2 rows) - -select testrngfunc(); - testrngfunc ------------------ - (7.136178,7.14) -(1 row) - -explain (verbose, costs off) -select * from testrngfunc(); - QUERY PLAN -------------------------------------- - Function Scan on public.testrngfunc - Output: f1, f2 - Function Call: testrngfunc() -(3 rows) - -select * from testrngfunc(); - f1 | f2 -----------+------ - 7.136178 | 7.14 -(1 row) - -drop function testrngfunc(); -create function testrngfunc() returns setof rngfunc_type as $$ - select 7.136178319899999964, 7.136178319899999964; -$$ language sql immutable; -explain (verbose, costs off) -select testrngfunc(); - QUERY PLAN -------------------------- - ProjectSet - Output: testrngfunc() - -> Result -(3 rows) - -select testrngfunc(); - testrngfunc ------------------ - (7.136178,7.14) -(1 row) - -explain (verbose, costs off) -select * from testrngfunc(); - QUERY PLAN --------------------------------------------------------- - Result - Output: 7.136178::numeric(35,6), 7.14::numeric(35,2) -(2 rows) - -select * from testrngfunc(); - f1 | f2 -----------+------ - 7.136178 | 7.14 -(1 row) - -create or replace function testrngfunc() returns setof rngfunc_type as $$ - select 7.136178319899999964, 7.136178319899999964; -$$ language sql volatile; -explain (verbose, costs off) -select testrngfunc(); - QUERY PLAN -------------------------- - ProjectSet - Output: testrngfunc() - -> Result -(3 rows) - -select testrngfunc(); - testrngfunc ------------------ - (7.136178,7.14) -(1 row) - -explain (verbose, costs off) -select * from testrngfunc(); - QUERY PLAN -------------------------------------- - Function Scan on public.testrngfunc - Output: f1, f2 - Function Call: testrngfunc() -(3 rows) - -select * from testrngfunc(); - f1 | f2 -----------+------ - 7.136178 | 7.14 -(1 row) - -create or replace function testrngfunc() returns setof rngfunc_type as $$ - select 1, 2 union select 3, 4 order by 1; -$$ language sql immutable; -explain (verbose, costs off) -select testrngfunc(); - QUERY PLAN -------------------------- - ProjectSet - Output: testrngfunc() - -> Result -(3 rows) - -select testrngfunc(); - testrngfunc ------------------ - (1.000000,2.00) - (3.000000,4.00) -(2 rows) - -explain (verbose, costs off) -select * from testrngfunc(); - QUERY PLAN ----------------------------------------------------------------------- - Subquery Scan on unnamed_subquery - Output: unnamed_subquery."?column?", unnamed_subquery."?column?_1" - -> Unique - Output: (1), (2) - -> Sort - Output: (1), (2) - Sort Key: (1), (2) - -> Append - -> Result - Output: 1, 2 - -> Result - Output: 3, 4 -(12 rows) - -select * from testrngfunc(); - f1 | f2 -----------+------ - 1.000000 | 2.00 - 3.000000 | 4.00 -(2 rows) - --- Check a couple of error cases while we're here -select * from testrngfunc() as t(f1 int8,f2 int8); -- fail, composite result -ERROR: a column definition list is redundant for a function returning a named composite type -LINE 1: select * from testrngfunc() as t(f1 int8,f2 int8); - ^ -select * from pg_get_keywords() as t(f1 int8,f2 int8); -- fail, OUT params -ERROR: a column definition list is redundant for a function with OUT parameters -LINE 1: select * from pg_get_keywords() as t(f1 int8,f2 int8); - ^ -select * from sin(3) as t(f1 int8,f2 int8); -- fail, scalar result type -ERROR: a column definition list is only allowed for functions returning "record" -LINE 1: select * from sin(3) as t(f1 int8,f2 int8); - ^ -drop type rngfunc_type cascade; -NOTICE: drop cascades to function testrngfunc() --- --- Check some cases involving added/dropped columns in a rowtype result --- -create temp table users (userid text, seq int, email text, todrop bool, moredrop int, enabled bool); -insert into users values ('id',1,'email',true,11,true); -insert into users values ('id2',2,'email2',true,12,true); -alter table users drop column todrop; -create or replace function get_first_user() returns users as -$$ SELECT * FROM users ORDER BY userid LIMIT 1; $$ -language sql stable; -NOTICE: function "get_first_user" will be effectively temporary -DETAIL: It depends on temporary type users. -SELECT get_first_user(); - get_first_user -------------------- - (id,1,email,11,t) -(1 row) - -SELECT * FROM get_first_user(); - userid | seq | email | moredrop | enabled ---------+-----+-------+----------+--------- - id | 1 | email | 11 | t -(1 row) - -create or replace function get_users() returns setof users as -$$ SELECT * FROM users ORDER BY userid; $$ -language sql stable; -NOTICE: function "get_users" will be effectively temporary -DETAIL: It depends on temporary type users. -SELECT get_users(); - get_users ---------------------- - (id,1,email,11,t) - (id2,2,email2,12,t) -(2 rows) - -SELECT * FROM get_users(); - userid | seq | email | moredrop | enabled ---------+-----+--------+----------+--------- - id | 1 | email | 11 | t - id2 | 2 | email2 | 12 | t -(2 rows) - -SELECT * FROM get_users() WITH ORDINALITY; -- make sure ordinality copes - userid | seq | email | moredrop | enabled | ordinality ---------+-----+--------+----------+---------+------------ - id | 1 | email | 11 | t | 1 - id2 | 2 | email2 | 12 | t | 2 -(2 rows) - --- multiple functions vs. dropped columns -SELECT * FROM ROWS FROM(generate_series(10,11), get_users()) WITH ORDINALITY; - generate_series | userid | seq | email | moredrop | enabled | ordinality ------------------+--------+-----+--------+----------+---------+------------ - 10 | id | 1 | email | 11 | t | 1 - 11 | id2 | 2 | email2 | 12 | t | 2 -(2 rows) - -SELECT * FROM ROWS FROM(get_users(), generate_series(10,11)) WITH ORDINALITY; - userid | seq | email | moredrop | enabled | generate_series | ordinality ---------+-----+--------+----------+---------+-----------------+------------ - id | 1 | email | 11 | t | 10 | 1 - id2 | 2 | email2 | 12 | t | 11 | 2 -(2 rows) - --- check that we can cope with post-parsing changes in rowtypes -create temp view usersview as -SELECT * FROM ROWS FROM(get_users(), generate_series(10,11)) WITH ORDINALITY; -select * from usersview; - userid | seq | email | moredrop | enabled | generate_series | ordinality ---------+-----+--------+----------+---------+-----------------+------------ - id | 1 | email | 11 | t | 10 | 1 - id2 | 2 | email2 | 12 | t | 11 | 2 -(2 rows) - -alter table users add column junk text; -select * from usersview; - userid | seq | email | moredrop | enabled | generate_series | ordinality ---------+-----+--------+----------+---------+-----------------+------------ - id | 1 | email | 11 | t | 10 | 1 - id2 | 2 | email2 | 12 | t | 11 | 2 -(2 rows) - -alter table users drop column moredrop; -- fail, view has reference -ERROR: cannot drop column moredrop of table users because other objects depend on it -DETAIL: view usersview depends on column moredrop of table users -HINT: Use DROP ... CASCADE to drop the dependent objects too. --- We used to have a bug that would allow the above to succeed, posing --- hazards for later execution of the view. Check that the internal --- defenses for those hazards haven't bit-rotted, in case some other --- bug with similar symptoms emerges. -begin; --- destroy the dependency entry that prevents the DROP: -delete from pg_depend where - objid = (select oid from pg_rewrite - where ev_class = 'usersview'::regclass and rulename = '_RETURN') - and refobjsubid = 5 -returning pg_describe_object(classid, objid, objsubid) as obj, - pg_describe_object(refclassid, refobjid, refobjsubid) as ref, - deptype; - obj | ref | deptype ---------------------------------+--------------------------------+--------- - rule _RETURN on view usersview | column moredrop of table users | n -(1 row) - -alter table users drop column moredrop; -select * from usersview; -- expect clean failure -ERROR: attribute 5 of type record has been dropped -rollback; -alter table users alter column seq type numeric; -- fail, view has reference -ERROR: cannot alter type of a column used by a view or rule -DETAIL: rule _RETURN on view usersview depends on column "seq" --- likewise, check we don't crash if the dependency goes wrong -begin; --- destroy the dependency entry that prevents the ALTER: -delete from pg_depend where - objid = (select oid from pg_rewrite - where ev_class = 'usersview'::regclass and rulename = '_RETURN') - and refobjsubid = 2 -returning pg_describe_object(classid, objid, objsubid) as obj, - pg_describe_object(refclassid, refobjid, refobjsubid) as ref, - deptype; - obj | ref | deptype ---------------------------------+---------------------------+--------- - rule _RETURN on view usersview | column seq of table users | n -(1 row) - -alter table users alter column seq type numeric; -select * from usersview; -- expect clean failure -ERROR: attribute 2 of type record has wrong type -DETAIL: Table has type numeric, but query expects integer. -rollback; -drop view usersview; -drop function get_first_user(); -drop function get_users(); -drop table users; --- check behavior with type coercion required for a set-op -create or replace function rngfuncbar() returns setof text as -$$ select 'foo'::varchar union all select 'bar'::varchar ; $$ -language sql stable; -select rngfuncbar(); - rngfuncbar ------------- - foo - bar -(2 rows) - -select * from rngfuncbar(); - rngfuncbar ------------- - foo - bar -(2 rows) - --- this function is now inlinable, too: -explain (verbose, costs off) select * from rngfuncbar(); - QUERY PLAN ------------------------------------------------- - Result - Output: ('foo'::character varying) - -> Append - -> Result - Output: 'foo'::character varying - -> Result - Output: 'bar'::character varying -(7 rows) - -drop function rngfuncbar(); --- check handling of a SQL function with multiple OUT params (bug #5777) -create or replace function rngfuncbar(out integer, out numeric) as -$$ select (1, 2.1) $$ language sql; -select * from rngfuncbar(); - column1 | column2 ----------+--------- - 1 | 2.1 -(1 row) - -create or replace function rngfuncbar(out integer, out numeric) as -$$ select (1, 2) $$ language sql; -select * from rngfuncbar(); -- fail -ERROR: function return row and query-specified return row do not match -DETAIL: Returned type integer at ordinal position 2, but query expects numeric. -create or replace function rngfuncbar(out integer, out numeric) as -$$ select (1, 2.1, 3) $$ language sql; -select * from rngfuncbar(); -- fail -ERROR: function return row and query-specified return row do not match -DETAIL: Returned row contains 3 attributes, but query expects 2. -drop function rngfuncbar(); --- check whole-row-Var handling in nested lateral functions (bug #11703) -create function extractq2(t int8_tbl) returns int8 as $$ - select t.q2 -$$ language sql immutable; -explain (verbose, costs off) -select x from int8_tbl, extractq2(int8_tbl) f(x); - QUERY PLAN ------------------------------------------- - Nested Loop - Output: f.x - -> Seq Scan on public.int8_tbl - Output: int8_tbl.q1, int8_tbl.q2 - -> Function Scan on f - Output: f.x - Function Call: int8_tbl.q2 -(7 rows) - -select x from int8_tbl, extractq2(int8_tbl) f(x); - x -------------------- - 456 - 4567890123456789 - 123 - 4567890123456789 - -4567890123456789 -(5 rows) - -create function extractq2_2(t int8_tbl) returns table(ret1 int8) as $$ - select extractq2(t) offset 0 -$$ language sql immutable; -explain (verbose, costs off) -select x from int8_tbl, extractq2_2(int8_tbl) f(x); - QUERY PLAN ------------------------------------ - Nested Loop - Output: ((int8_tbl.*).q2) - -> Seq Scan on public.int8_tbl - Output: int8_tbl.* - -> Result - Output: (int8_tbl.*).q2 -(6 rows) - -select x from int8_tbl, extractq2_2(int8_tbl) f(x); - x -------------------- - 456 - 4567890123456789 - 123 - 4567890123456789 - -4567890123456789 -(5 rows) - --- without the "offset 0", this function gets optimized quite differently -create function extractq2_2_opt(t int8_tbl) returns table(ret1 int8) as $$ - select extractq2(t) -$$ language sql immutable; -explain (verbose, costs off) -select x from int8_tbl, extractq2_2_opt(int8_tbl) f(x); - QUERY PLAN ------------------------------ - Seq Scan on public.int8_tbl - Output: int8_tbl.q2 -(2 rows) - -select x from int8_tbl, extractq2_2_opt(int8_tbl) f(x); - x -------------------- - 456 - 4567890123456789 - 123 - 4567890123456789 - -4567890123456789 -(5 rows) - --- check handling of nulls in SRF results (bug #7808) -create type rngfunc2 as (a integer, b text); -select *, row_to_json(u) from unnest(array[(1,'foo')::rngfunc2, null::rngfunc2]) u; - a | b | row_to_json ----+-----+--------------------- - 1 | foo | {"a":1,"b":"foo"} - | | {"a":null,"b":null} -(2 rows) - -select *, row_to_json(u) from unnest(array[null::rngfunc2, null::rngfunc2]) u; - a | b | row_to_json ----+---+--------------------- - | | {"a":null,"b":null} - | | {"a":null,"b":null} -(2 rows) - -select *, row_to_json(u) from unnest(array[null::rngfunc2, (1,'foo')::rngfunc2, null::rngfunc2]) u; - a | b | row_to_json ----+-----+--------------------- - | | {"a":null,"b":null} - 1 | foo | {"a":1,"b":"foo"} - | | {"a":null,"b":null} -(3 rows) - -select *, row_to_json(u) from unnest(array[]::rngfunc2[]) u; - a | b | row_to_json ----+---+------------- -(0 rows) - -drop type rngfunc2; --- check handling of functions pulled up into function RTEs (bug #17227) -explain (verbose, costs off) -select * from - (select jsonb_path_query_array(module->'lectures', '$[*]') as lecture - from unnest(array['{"lectures": [{"id": "1"}]}'::jsonb]) - as unnested_modules(module)) as ss, - jsonb_to_recordset(ss.lecture) as j (id text); - QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop - Output: jsonb_path_query_array((unnested_modules.module -> 'lectures'::text), '$[*]'::jsonpath, '{}'::jsonb, false), j.id - -> Function Scan on pg_catalog.unnest unnested_modules - Output: unnested_modules.module - Function Call: unnest('{"{\"lectures\": [{\"id\": \"1\"}]}"}'::jsonb[]) - -> Function Scan on pg_catalog.jsonb_to_recordset j - Output: j.id - Function Call: jsonb_to_recordset(jsonb_path_query_array((unnested_modules.module -> 'lectures'::text), '$[*]'::jsonpath, '{}'::jsonb, false)) -(8 rows) - -select * from - (select jsonb_path_query_array(module->'lectures', '$[*]') as lecture - from unnest(array['{"lectures": [{"id": "1"}]}'::jsonb]) - as unnested_modules(module)) as ss, - jsonb_to_recordset(ss.lecture) as j (id text); - lecture | id ----------------+---- - [{"id": "1"}] | 1 -(1 row) - --- check detection of mismatching record types with a const-folded expression -with a(b) as (values (row(1,2,3))) -select * from a, coalesce(b) as c(d int, e int); -- fail -ERROR: function return row and query-specified return row do not match -DETAIL: Returned row contains 3 attributes, but query expects 2. -with a(b) as (values (row(1,2,3))) -select * from a, coalesce(b) as c(d int, e int, f int, g int); -- fail -ERROR: function return row and query-specified return row do not match -DETAIL: Returned row contains 3 attributes, but query expects 4. -with a(b) as (values (row(1,2,3))) -select * from a, coalesce(b) as c(d int, e int, f float); -- fail -ERROR: function return row and query-specified return row do not match -DETAIL: Returned type integer at ordinal position 3, but query expects double precision. -select * from int8_tbl, coalesce(row(1)) as (a int, b int); -- fail -ERROR: function return row and query-specified return row do not match -DETAIL: Returned row contains 1 attribute, but query expects 2. +psql: error: connection to server on socket "/tmp/FPi04qZ6km/.s.PGSQL.13876" failed: could not fork new process for connection: Resource temporarily unavailable diff -U3 /home/postgres/postgres/src/test/regress/expected/with.out /home/postgres/postgres/build/testrun/pg_upgrade/002_pg_upgrade/data/results/with.out --- /home/postgres/postgres/src/test/regress/expected/with.out 2026-01-01 05:40:40.152494778 +0000 +++ /home/postgres/postgres/build/testrun/pg_upgrade/002_pg_upgrade/data/results/with.out 2026-01-01 05:42:36.678584493 +0000 @@ -1,3762 +1 @@ --- --- 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 ('01'::varbit) -UNION - SELECT n || '10'::varbit FROM t WHERE n < '100'::varbit -) -SELECT 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 - ^ -DETAIL: No operator of that name accepts the given argument types. -HINT: 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) - --- exercise the deduplication code of a UNION with mixed input slot types -WITH RECURSIVE subdepartment AS -( - -- select all columns to prevent projection - SELECT id, parent_department, name FROM department WHERE name = 'A' - UNION - -- joins do projection - SELECT d.id, d.parent_department, d.name FROM department AS d - INNER JOIN subdepartment AS sd ON d.parent_department = sd.id -) -SELECT * FROM subdepartment ORDER BY name; - id | parent_department | name -----+-------------------+------ - 1 | 0 | A - 2 | 1 | B - 3 | 2 | C - 4 | 2 | D - 6 | 4 | F -(5 rows) - --- 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 id, + - parent_department, + - 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 id, + - parent_department, + - 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(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) - -CREATE TEMP TABLE duplicates (a INT NOT NULL); -INSERT INTO duplicates VALUES(1), (1); --- Try out a recursive UNION case where the non-recursive part's table slot --- uses TTSOpsBufferHeapTuple and contains duplicate rows. -WITH RECURSIVE cte (a) as ( - SELECT a FROM duplicates - UNION - SELECT a FROM cte -) -SELECT a FROM cte; - a ---- - 1 -(1 row) - --- test that column statistics from a materialized CTE are available --- to upper planner (otherwise, we'd get a stupider plan) -explain (costs off) -with x as materialized (select unique1 from tenk1 b) -select count(*) from tenk1 a - where unique1 in (select * from x); - QUERY PLAN ------------------------------------------------------------- - Aggregate - CTE x - -> Index Only Scan using tenk1_unique1 on tenk1 b - -> Hash Semi Join - Hash Cond: (a.unique1 = x.unique1) - -> Index Only Scan using tenk1_unique1 on tenk1 a - -> Hash - -> CTE Scan on x -(8 rows) - -explain (costs off) -with x as materialized (insert into tenk1 default values returning unique1) -select count(*) from tenk1 a - where unique1 in (select * from x); - QUERY PLAN ------------------------------------------------------------- - Aggregate - CTE x - -> Insert on tenk1 - -> Result - -> Nested Loop - -> HashAggregate - Group Key: x.unique1 - -> CTE Scan on x - -> Index Only Scan using tenk1_unique1 on tenk1 a - Index Cond: (unique1 = x.unique1) -(10 rows) - --- test that pathkeys from a materialized CTE are propagated up to the --- outer query -explain (costs off) -with x as materialized (select unique1 from tenk1 b order by unique1) -select count(*) from tenk1 a - where unique1 in (select * from x); - QUERY PLAN ------------------------------------------------------------- - Aggregate - CTE x - -> Index Only Scan using tenk1_unique1 on tenk1 b - -> Merge Semi Join - Merge Cond: (a.unique1 = x.unique1) - -> Index Only Scan using tenk1_unique1 on tenk1 a - -> CTE Scan on x -(7 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) - --- a constant initial value causes issues for EXPLAIN -explain (verbose, costs off) -with recursive test as ( - select 1 as x - union all - select x + 1 - from test -) search depth first by x set y -select * from test limit 5; - QUERY PLAN ------------------------------------------------------------------------------------------ - Limit - Output: test.x, test.y - CTE test - -> Recursive Union - -> Result - Output: 1, '{(1)}'::record[] - -> WorkTable Scan on test test_1 - Output: (test_1.x + 1), array_cat(test_1.y, ARRAY[ROW((test_1.x + 1))]) - -> CTE Scan on test - Output: test.x, test.y -(10 rows) - -with recursive test as ( - select 1 as x - union all - select x + 1 - from test -) search depth first by x set y -select * from test limit 5; - x | y ----+----------------------- - 1 | {(1)} - 2 | {(1),(2)} - 3 | {(1),(2),(3)} - 4 | {(1),(2),(3),(4)} - 5 | {(1),(2),(3),(4),(5)} -(5 rows) - -explain (verbose, costs off) -with recursive test as ( - select 1 as x - union all - select x + 1 - from test -) search breadth first by x set y -select * from test limit 5; - QUERY PLAN --------------------------------------------------------------------------------------------- - Limit - Output: test.x, test.y - CTE test - -> Recursive Union - -> Result - Output: 1, '(0,1)'::record - -> WorkTable Scan on test test_1 - Output: (test_1.x + 1), ROW(int8inc((test_1.y)."*DEPTH*"), (test_1.x + 1)) - -> CTE Scan on test - Output: test.x, test.y -(10 rows) - -with recursive test as ( - select 1 as x - union all - select x + 1 - from test -) search breadth first by x set y -select * from test limit 5; - x | y ----+------- - 1 | (0,1) - 2 | (1,2) - 3 | (2,3) - 4 | (3,4) - 5 | (4,5) -(5 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 --- check that we distinguish same CTE name used at different levels --- (this case could be supported, perhaps, but it isn't today) -with recursive x(col) as ( - select 1 - union - (with x as (select * from x) - select * from x) -) search depth first by col set seq -select * from x; -ERROR: with a SEARCH or CYCLE clause, the recursive reference to WITH query "x" must be at the top level of its right-hand 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 f, + - t, + - 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) - -explain (verbose, costs off) -with recursive test as ( - select 0 as x - union all - select (x + 1) % 10 - from test -) cycle x set is_cycle using path -select * from test; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - CTE Scan on test - Output: test.x, test.is_cycle, test.path - CTE test - -> Recursive Union - -> Result - Output: 0, false, '{(0)}'::record[] - -> WorkTable Scan on test test_1 - Output: ((test_1.x + 1) % 10), CASE WHEN (ROW(((test_1.x + 1) % 10)) = ANY (test_1.path)) THEN true ELSE false END, array_cat(test_1.path, ARRAY[ROW(((test_1.x + 1) % 10))]) - Filter: (NOT test_1.is_cycle) -(9 rows) - -with recursive test as ( - select 0 as x - union all - select (x + 1) % 10 - from test -) cycle x set is_cycle using path -select * from test; - x | is_cycle | path ----+----------+----------------------------------------------- - 0 | f | {(0)} - 1 | f | {(0),(1)} - 2 | f | {(0),(1),(2)} - 3 | f | {(0),(1),(2),(3)} - 4 | f | {(0),(1),(2),(3),(4)} - 5 | f | {(0),(1),(2),(3),(4),(5)} - 6 | f | {(0),(1),(2),(3),(4),(5),(6)} - 7 | f | {(0),(1),(2),(3),(4),(5),(6),(7)} - 8 | f | {(0),(1),(2),(3),(4),(5),(6),(7),(8)} - 9 | f | {(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)} - 0 | t | {(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(0)} -(11 rows) - -with recursive test as ( - select 0 as x - union all - select (x + 1) % 10 - from test - where not is_cycle -- redundant, but legal -) cycle x set is_cycle using path -select * from test; - x | is_cycle | path ----+----------+----------------------------------------------- - 0 | f | {(0)} - 1 | f | {(0),(1)} - 2 | f | {(0),(1),(2)} - 3 | f | {(0),(1),(2),(3)} - 4 | f | {(0),(1),(2),(3),(4)} - 5 | f | {(0),(1),(2),(3),(4),(5)} - 6 | f | {(0),(1),(2),(3),(4),(5),(6)} - 7 | f | {(0),(1),(2),(3),(4),(5),(6),(7)} - 8 | f | {(0),(1),(2),(3),(4),(5),(6),(7),(8)} - 9 | f | {(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)} - 0 | t | {(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(0)} -(11 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 f, + - t, + - 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 f, + - t, + - 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 --- -WITH x(n, b) AS (SELECT 1) -SELECT * FROM x; -ERROR: WITH query "x" has 1 columns available but 2 columns specified -LINE 1: WITH x(n, b) AS (SELECT 1) - ^ --- 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) - ^ --- allow this, because we historically have -WITH RECURSIVE x(n) AS ( - WITH x1 AS (SELECT 1 AS n) - SELECT 0 - UNION - SELECT * FROM x1) - SELECT * FROM x; - n ---- - 0 - 1 -(2 rows) - --- but this should be rejected -WITH RECURSIVE x(n) AS ( - WITH x1 AS (SELECT 1 FROM x) - SELECT 0 - UNION - SELECT * FROM x1) - SELECT * FROM x; -ERROR: recursive reference to query "x" must not appear within a subquery -LINE 2: WITH x1 AS (SELECT 1 FROM x) - ^ --- and this too -WITH RECURSIVE x(n) AS ( - (WITH x1 AS (SELECT 1 FROM x) SELECT * FROM x1) - UNION - SELECT 0) - SELECT * FROM x; -ERROR: recursive reference to query "x" must not appear within its non-recursive term -LINE 2: (WITH x1 AS (SELECT 1 FROM x) SELECT * FROM x1) - ^ --- and this -WITH RECURSIVE x(n) AS ( - SELECT 0 UNION SELECT 1 - ORDER BY (SELECT n FROM x)) - SELECT * FROM x; -ERROR: ORDER BY in a recursive query is not implemented -LINE 3: ORDER BY (SELECT n FROM x)) - ^ --- and this -WITH RECURSIVE x(n) AS ( - WITH sub_cte AS (SELECT * FROM x) - DELETE FROM graph RETURNING f) - SELECT * FROM x; -ERROR: recursive query "x" must not contain data-modifying statements -LINE 1: WITH RECURSIVE x(n) AS ( - ^ -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 bug #19055: interaction of WITH with aggregates --- --- For now, we just throw an error if there's a use of a CTE below the --- semantic level that the SQL standard assigns to the aggregate. --- It's not entirely clear what we could do instead that doesn't risk --- breaking more things than it fixes. -select f1, (with cte1(x,y) as (select 1,2) - select count((select i4.f1 from cte1))) as ss -from int4_tbl i4; -ERROR: outer-level aggregate cannot use a nested CTE -LINE 2: select count((select i4.f1 from cte1))) as ss - ^ -DETAIL: CTE "cte1" is below the aggregate's semantic level. --- --- test for bug #19106: interaction of WITH with aggregates --- --- the initial fix for #19055 was too aggressive and broke this case -explain (verbose, costs off) -with a as ( select id from (values (1), (2)) as v(id) ), - b as ( select max((select sum(id) from a)) as agg ) -select agg from b; - QUERY PLAN --------------------------------------------- - Aggregate - Output: max((InitPlan expr_1).col1) - InitPlan expr_1 - -> Aggregate - Output: sum("*VALUES*".column1) - -> Values Scan on "*VALUES*" - Output: "*VALUES*".column1 - -> Result -(8 rows) - -with a as ( select id from (values (1), (2)) as v(id) ), - b as ( select max((select sum(id) from a)) as agg ) -select agg from b; - agg ------ - 3 -(1 row) - --- --- 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 debug_parallel_query = 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) - --- check that recursive CTE processing doesn't rewrite a CTE more than once --- (must not try to expand GENERATED ALWAYS IDENTITY columns more than once) -CREATE TEMP TABLE id_alw1 (i int GENERATED ALWAYS AS IDENTITY); -CREATE TEMP TABLE id_alw2 (i int GENERATED ALWAYS AS IDENTITY); -CREATE TEMP VIEW id_alw2_view AS SELECT * FROM id_alw2; -CREATE TEMP TABLE id_alw3 (i int GENERATED ALWAYS AS IDENTITY); -CREATE RULE id_alw3_ins AS ON INSERT TO id_alw3 DO INSTEAD - WITH t1 AS (INSERT INTO id_alw1 DEFAULT VALUES RETURNING i) - INSERT INTO id_alw2_view DEFAULT VALUES RETURNING i; -CREATE TEMP VIEW id_alw3_view AS SELECT * FROM id_alw3; -CREATE TEMP TABLE id_alw4 (i int GENERATED ALWAYS AS IDENTITY); -WITH t4 AS (INSERT INTO id_alw4 DEFAULT VALUES RETURNING i) - INSERT INTO id_alw3_view DEFAULT VALUES RETURNING i; - i ---- - 1 -(1 row) - -SELECT * from id_alw1; - i ---- - 1 -(1 row) - -SELECT * from id_alw2; - i ---- - 1 -(1 row) - -SELECT * from id_alw3; - i ---- -(0 rows) - -SELECT * from id_alw4; - i ---- - 1 -(1 row) - --- check case where CTE reference is removed due to optimization -EXPLAIN (VERBOSE, COSTS OFF) -SELECT q1 FROM -( - WITH t_cte AS (SELECT * FROM int8_tbl t) - SELECT q1, (SELECT q2 FROM t_cte WHERE t_cte.q1 = i8.q1) AS t_sub - FROM int8_tbl i8 -) ss; - QUERY PLAN --------------------------------------- - Subquery Scan on ss - Output: ss.q1 - -> Seq Scan on public.int8_tbl i8 - Output: i8.q1, NULL::bigint -(4 rows) - -SELECT q1 FROM -( - WITH t_cte AS (SELECT * FROM int8_tbl t) - SELECT q1, (SELECT q2 FROM t_cte WHERE t_cte.q1 = i8.q1) AS t_sub - FROM int8_tbl i8 -) ss; - q1 ------------------- - 123 - 123 - 4567890123456789 - 4567890123456789 - 4567890123456789 -(5 rows) - -EXPLAIN (VERBOSE, COSTS OFF) -SELECT q1 FROM -( - WITH t_cte AS MATERIALIZED (SELECT * FROM int8_tbl t) - SELECT q1, (SELECT q2 FROM t_cte WHERE t_cte.q1 = i8.q1) AS t_sub - FROM int8_tbl i8 -) ss; - QUERY PLAN ---------------------------------------------- - Subquery Scan on ss - Output: ss.q1 - -> Seq Scan on public.int8_tbl i8 - Output: i8.q1, NULL::bigint - CTE t_cte - -> Seq Scan on public.int8_tbl t - Output: t.q1, t.q2 -(7 rows) - -SELECT q1 FROM -( - WITH t_cte AS MATERIALIZED (SELECT * FROM int8_tbl t) - SELECT q1, (SELECT q2 FROM t_cte WHERE t_cte.q1 = i8.q1) AS t_sub - FROM int8_tbl i8 -) ss; - q1 ------------------- - 123 - 123 - 4567890123456789 - 4567890123456789 - 4567890123456789 -(5 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; --- WITH referenced by MERGE statement -CREATE TABLE m AS SELECT i AS k, (i || ' v')::text v FROM generate_series(1, 16, 3) i; -ALTER TABLE m ADD UNIQUE (k); -WITH RECURSIVE cte_basic AS (SELECT 1 a, 'cte_basic val' b) -MERGE INTO m USING (select 0 k, 'merge source SubPlan' v) o ON m.k=o.k -WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_basic WHERE cte_basic.a = m.k LIMIT 1) -WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v); -ERROR: WITH RECURSIVE is not supported for MERGE statement --- Basic: -WITH cte_basic AS MATERIALIZED (SELECT 1 a, 'cte_basic val' b) -MERGE INTO m USING (select 0 k, 'merge source SubPlan' v offset 0) o ON m.k=o.k -WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_basic WHERE cte_basic.a = m.k LIMIT 1) -WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v); --- Examine -SELECT * FROM m where k = 0; - k | v ----+---------------------- - 0 | merge source SubPlan -(1 row) - --- See EXPLAIN output for same query: -EXPLAIN (VERBOSE, COSTS OFF) -WITH cte_basic AS MATERIALIZED (SELECT 1 a, 'cte_basic val' b) -MERGE INTO m USING (select 0 k, 'merge source SubPlan' v offset 0) o ON m.k=o.k -WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_basic WHERE cte_basic.a = m.k LIMIT 1) -WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v); - QUERY PLAN -------------------------------------------------------------------- - Merge on public.m - CTE cte_basic - -> Result - Output: 1, 'cte_basic val'::text - -> Hash Right Join - Output: m.ctid, o.k, o.v, o.* - Hash Cond: (m.k = o.k) - -> Seq Scan on public.m - Output: m.ctid, m.k - -> Hash - Output: o.k, o.v, o.* - -> Subquery Scan on o - Output: o.k, o.v, o.* - -> Result - Output: 0, 'merge source SubPlan'::text - SubPlan expr_1 - -> Limit - Output: ((cte_basic.b || ' merge update'::text)) - -> CTE Scan on cte_basic - Output: (cte_basic.b || ' merge update'::text) - Filter: (cte_basic.a = m.k) -(21 rows) - --- InitPlan -WITH cte_init AS MATERIALIZED (SELECT 1 a, 'cte_init val' b) -MERGE INTO m USING (select 1 k, 'merge source InitPlan' v offset 0) o ON m.k=o.k -WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_init WHERE a = 1 LIMIT 1) -WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v); --- Examine -SELECT * FROM m where k = 1; - k | v ----+--------------------------- - 1 | cte_init val merge update -(1 row) - --- See EXPLAIN output for same query: -EXPLAIN (VERBOSE, COSTS OFF) -WITH cte_init AS MATERIALIZED (SELECT 1 a, 'cte_init val' b) -MERGE INTO m USING (select 1 k, 'merge source InitPlan' v offset 0) o ON m.k=o.k -WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_init WHERE a = 1 LIMIT 1) -WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v); - QUERY PLAN --------------------------------------------------------------------- - Merge on public.m - CTE cte_init - -> Result - Output: 1, 'cte_init val'::text - InitPlan expr_1 - -> Limit - Output: ((cte_init.b || ' merge update'::text)) - -> CTE Scan on cte_init - Output: (cte_init.b || ' merge update'::text) - Filter: (cte_init.a = 1) - -> Hash Right Join - Output: m.ctid, o.k, o.v, o.* - Hash Cond: (m.k = o.k) - -> Seq Scan on public.m - Output: m.ctid, m.k - -> Hash - Output: o.k, o.v, o.* - -> Subquery Scan on o - Output: o.k, o.v, o.* - -> Result - Output: 1, 'merge source InitPlan'::text -(21 rows) - --- MERGE source comes from CTE: -WITH merge_source_cte AS MATERIALIZED (SELECT 15 a, 'merge_source_cte val' b) -MERGE INTO m USING (select * from merge_source_cte) o ON m.k=o.a -WHEN MATCHED THEN UPDATE SET v = (SELECT b || merge_source_cte.*::text || ' merge update' FROM merge_source_cte WHERE a = 15) -WHEN NOT MATCHED THEN INSERT VALUES(o.a, o.b || (SELECT merge_source_cte.*::text || ' merge insert' FROM merge_source_cte)); --- Examine -SELECT * FROM m where k = 15; - k | v -----+-------------------------------------------------------------- - 15 | merge_source_cte val(15,"merge_source_cte val") merge insert -(1 row) - --- See EXPLAIN output for same query: -EXPLAIN (VERBOSE, COSTS OFF) -WITH merge_source_cte AS MATERIALIZED (SELECT 15 a, 'merge_source_cte val' b) -MERGE INTO m USING (select * from merge_source_cte) o ON m.k=o.a -WHEN MATCHED THEN UPDATE SET v = (SELECT b || merge_source_cte.*::text || ' merge update' FROM merge_source_cte WHERE a = 15) -WHEN NOT MATCHED THEN INSERT VALUES(o.a, o.b || (SELECT merge_source_cte.*::text || ' merge insert' FROM merge_source_cte)); - QUERY PLAN ------------------------------------------------------------------------------------------------------ - Merge on public.m - CTE merge_source_cte - -> Result - Output: 15, 'merge_source_cte val'::text - InitPlan expr_1 - -> CTE Scan on merge_source_cte merge_source_cte_1 - Output: ((merge_source_cte_1.b || (merge_source_cte_1.*)::text) || ' merge update'::text) - Filter: (merge_source_cte_1.a = 15) - InitPlan expr_2 - -> CTE Scan on merge_source_cte merge_source_cte_2 - Output: ((merge_source_cte_2.*)::text || ' merge insert'::text) - -> Hash Right Join - Output: m.ctid, merge_source_cte.a, merge_source_cte.b, merge_source_cte.* - Hash Cond: (m.k = merge_source_cte.a) - -> Seq Scan on public.m - Output: m.ctid, m.k - -> Hash - Output: merge_source_cte.a, merge_source_cte.b, merge_source_cte.* - -> CTE Scan on merge_source_cte - Output: merge_source_cte.a, merge_source_cte.b, merge_source_cte.* -(20 rows) - -DROP TABLE m; --- 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_star USING wcte WHERE aa = q2; - QUERY PLAN ---------------------------------------------------------------------------- - Delete on public.a_star - Delete on public.a_star a_star_1 - Delete on public.b_star a_star_2 - Delete on public.c_star a_star_3 - Delete on public.d_star a_star_4 - Delete on public.e_star a_star_5 - Delete on public.f_star a_star_6 - CTE wcte - -> Insert on public.int8_tbl - Output: int8_tbl.q2 - -> Result - Output: '42'::bigint, '47'::bigint - -> Hash Join - Output: wcte.*, a_star.tableoid, a_star.ctid - Hash Cond: (a_star.aa = wcte.q2) - -> Append - -> Seq Scan on public.a_star a_star_1 - Output: a_star_1.aa, a_star_1.tableoid, a_star_1.ctid - -> Seq Scan on public.b_star a_star_2 - Output: a_star_2.aa, a_star_2.tableoid, a_star_2.ctid - -> Seq Scan on public.c_star a_star_3 - Output: a_star_3.aa, a_star_3.tableoid, a_star_3.ctid - -> Seq Scan on public.d_star a_star_4 - Output: a_star_4.aa, a_star_4.tableoid, a_star_4.ctid - -> Seq Scan on public.e_star a_star_5 - Output: a_star_5.aa, a_star_5.tableoid, a_star_5.ctid - -> Seq Scan on public.f_star a_star_6 - Output: a_star_6.aa, a_star_6.tableoid, a_star_6.ctid - -> Hash - Output: wcte.*, wcte.q2 - -> CTE Scan on wcte - Output: wcte.*, wcte.q2 -(32 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; - ^ --- RETURNING tries to return its own output -WITH RECURSIVE t(action, a) AS ( - MERGE INTO y USING (VALUES (11)) v(a) ON y.a = v.a - WHEN NOT MATCHED THEN INSERT VALUES (v.a) - RETURNING merge_action(), (SELECT a FROM t) -) -SELECT * FROM t; -ERROR: recursive query "t" must not contain data-modifying statements -LINE 1: WITH RECURSIVE t(action, a) AS ( - ^ --- 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 with_test AS (SELECT 42) INSERT INTO with_test VALUES (1); -ERROR: relation "with_test" does not exist -LINE 1: WITH with_test AS (SELECT 42) INSERT INTO with_test VALUES (... - ^ --- 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 with_test (i int); -with with_test as (select 42) insert into with_test select * from with_test; -select * from with_test; - i ----- - 42 -(1 row) - -drop table with_test; +psql: error: connection to server on socket "/tmp/FPi04qZ6km/.s.PGSQL.13876" failed: could not fork new process for connection: Resource temporarily unavailable