diff --git a/pg15_tests/passing_tests.tsv b/pg15_tests/passing_tests.tsv index 6f6a0804aac4..df3040652d2a 100644 --- a/pg15_tests/passing_tests.tsv +++ b/pg15_tests/passing_tests.tsv @@ -60,6 +60,7 @@ JAVA org.yb.pgsql.TestPgRegressJson JAVA org.yb.pgsql.TestPgRegressMatview JAVA org.yb.pgsql.TestPgRegressMisc#testPgRegressMiscSerial2 JAVA org.yb.pgsql.TestPgRegressNonTxnCopy +JAVA org.yb.pgsql.TestPgRegressPgMisc JAVA org.yb.pgsql.TestPgRegressProc JAVA org.yb.pgsql.TestPgRegressPublication JAVA org.yb.pgsql.TestPgRegressPushdown#testPgRegressPushdown diff --git a/src/postgres/src/test/regress/expected/yb_pg_create_operator.out b/src/postgres/src/test/regress/expected/yb_pg_create_operator.out index 89f62b4c555a..81d4979b5dd6 100644 --- a/src/postgres/src/test/regress/expected/yb_pg_create_operator.out +++ b/src/postgres/src/test/regress/expected/yb_pg_create_operator.out @@ -8,17 +8,15 @@ CREATE OPERATOR ## ( commutator = ## ); CREATE OPERATOR @#@ ( - rightarg = int8, -- left unary - procedure = numeric_fac -); -CREATE OPERATOR #@# ( - leftarg = int8, -- right unary - procedure = numeric_fac + rightarg = int8, -- prefix + procedure = factorial ); CREATE OPERATOR #%# ( - leftarg = int8, -- right unary - procedure = numeric_fac + leftarg = int8, -- fail, postfix is no longer supported + procedure = factorial ); +ERROR: operator right argument type must be specified +DETAIL: Postfix operators are not supported. -- Test operator created above SELECT @#@ 24; ?column? @@ -27,12 +25,23 @@ SELECT @#@ 24; (1 row) -- Test comments -COMMENT ON OPERATOR ###### (int4, NONE) IS 'bad right unary'; -ERROR: operator does not exist: integer ###### --- => is disallowed now +COMMENT ON OPERATOR ###### (NONE, int4) IS 'bad prefix'; +ERROR: operator does not exist: ###### integer +COMMENT ON OPERATOR ###### (int4, NONE) IS 'bad postfix'; +ERROR: postfix operators are not supported +COMMENT ON OPERATOR ###### (int4, int8) IS 'bad infix'; +ERROR: operator does not exist: integer ###### bigint +-- Check that DROP on a nonexistent op behaves sanely, too +DROP OPERATOR ###### (NONE, int4); +ERROR: operator does not exist: ###### integer +DROP OPERATOR ###### (int4, NONE); +ERROR: postfix operators are not supported +DROP OPERATOR ###### (int4, int8); +ERROR: operator does not exist: integer ###### bigint +-- => is disallowed as an operator name now CREATE OPERATOR => ( - leftarg = int8, -- right unary - procedure = numeric_fac + rightarg = int8, + procedure = factorial ); ERROR: syntax error at or near "=>" LINE 1: CREATE OPERATOR => ( @@ -41,15 +50,20 @@ LINE 1: CREATE OPERATOR => ( -- (=> is tested elsewhere) -- this is legal because ! is not allowed in sql ops CREATE OPERATOR !=- ( - leftarg = int8, -- right unary - procedure = numeric_fac + rightarg = int8, + procedure = factorial ); -SELECT 2 !=-; +SELECT !=- 10; ?column? ---------- - 2 + 3628800 (1 row) +-- postfix operators don't work anymore +SELECT 10 !=-; +ERROR: syntax error at or near ";" +LINE 1: SELECT 10 !=-; + ^ -- make sure lexer returns != as <> even in edge cases SELECT 2 !=/**/ 1, 2 !=/**/ 2; ?column? | ?column? @@ -119,8 +133,8 @@ GRANT USAGE ON SCHEMA schema_op1 TO PUBLIC; REVOKE USAGE ON SCHEMA schema_op1 FROM regress_rol_op1; SET ROLE regress_rol_op1; CREATE OPERATOR schema_op1.#*# ( - leftarg = int8, -- right unary - procedure = numeric_fac + rightarg = int8, + procedure = factorial ); ERROR: permission denied for schema schema_op1 ROLLBACK; @@ -128,7 +142,7 @@ ROLLBACK; BEGIN TRANSACTION; CREATE OPERATOR #*# ( leftarg = SETOF int8, - procedure = numeric_fac + procedure = factorial ); ERROR: SETOF type not allowed for operator argument ROLLBACK; @@ -136,7 +150,7 @@ ROLLBACK; BEGIN TRANSACTION; CREATE OPERATOR #*# ( rightarg = SETOF int8, - procedure = numeric_fac + procedure = factorial ); ERROR: SETOF type not allowed for operator argument ROLLBACK; @@ -159,19 +173,19 @@ CREATE OPERATOR === ( ROLLBACK; -- Should fail. Invalid attribute CREATE OPERATOR #@%# ( - leftarg = int8, -- right unary - procedure = numeric_fac, + rightarg = int8, + procedure = factorial, invalid_att = int8 ); WARNING: operator attribute "invalid_att" not recognized --- Should fail. At least leftarg or rightarg should be mandatorily specified +-- Should fail. At least rightarg should be mandatorily specified CREATE OPERATOR #@%# ( - procedure = numeric_fac + procedure = factorial ); -ERROR: at least one of leftarg or rightarg must be specified +ERROR: operator argument types must be specified -- Should fail. Procedure should be mandatorily specified CREATE OPERATOR #@%# ( - leftarg = int8 + rightarg = int8 ); ERROR: operator function must be specified -- Should fail. CREATE OPERATOR requires USAGE on TYPE diff --git a/src/postgres/src/test/regress/expected/yb_pg_create_type.out b/src/postgres/src/test/regress/expected/yb_pg_create_type.out index 510045083af5..e000b63fc6e2 100644 --- a/src/postgres/src/test/regress/expected/yb_pg_create_type.out +++ b/src/postgres/src/test/regress/expected/yb_pg_create_type.out @@ -108,8 +108,10 @@ SELECT * FROM default_test; zippo | 42 (1 row) +-- We need a shell type to test some CREATE TYPE failure cases with +CREATE TYPE bogus_type; -- invalid: non-lowercase quoted identifiers -CREATE TYPE case_int42 ( +CREATE TYPE bogus_type ( "Internallength" = 4, "Input" = int42_in, "Output" = int42_out, @@ -136,6 +138,20 @@ WARNING: type attribute "Passedbyvalue" not recognized LINE 7: "Passedbyvalue" ^ ERROR: type input function must be specified +-- invalid: input/output function incompatibility +CREATE TYPE bogus_type (INPUT = array_in, + OUTPUT = array_out, + ELEMENT = int, + INTERNALLENGTH = 32); +ERROR: type input function array_in must return type bogus_type +DROP TYPE bogus_type; +-- It no longer is possible to issue CREATE TYPE without making a shell first +CREATE TYPE bogus_type (INPUT = array_in, + OUTPUT = array_out, + ELEMENT = int, + INTERNALLENGTH = 32); +ERROR: type "bogus_type" does not exist +HINT: Create the type as a shell type, then create its I/O functions, then do a full CREATE TYPE. -- Test stand-alone composite type CREATE TYPE default_test_row AS (f1 text_w_default, f2 int42); CREATE FUNCTION get_default_test() RETURNS SETOF default_test_row AS ' @@ -162,31 +178,25 @@ ERROR: type "text_w_default" already exists DROP TYPE default_test_row CASCADE; NOTICE: drop cascades to function get_default_test() DROP TABLE default_test; --- Check type create with input/output incompatibility -CREATE TYPE not_existing_type (INPUT = array_in, - OUTPUT = array_out, - ELEMENT = int, - INTERNALLENGTH = 32); -ERROR: function array_out(not_existing_type) does not exist --- Check dependency transfer of opaque functions when creating a new type -CREATE FUNCTION base_fn_in(cstring) RETURNS opaque AS 'boolin' +-- Check dependencies are established when creating a new type +CREATE TYPE base_type; +CREATE FUNCTION base_fn_in(cstring) RETURNS base_type AS 'boolin' LANGUAGE internal IMMUTABLE STRICT; -CREATE FUNCTION base_fn_out(opaque) RETURNS opaque AS 'boolout' +NOTICE: return type base_type is only a shell +CREATE FUNCTION base_fn_out(base_type) RETURNS cstring AS 'boolout' LANGUAGE internal IMMUTABLE STRICT; +NOTICE: argument type base_type is only a shell CREATE TYPE base_type(INPUT = base_fn_in, OUTPUT = base_fn_out); -WARNING: changing argument type of function base_fn_out from "opaque" to base_type -WARNING: changing return type of function base_fn_in from opaque to base_type -WARNING: changing return type of function base_fn_out from opaque to cstring -\set VERBOSITY terse \\ -- suppress cascade details +\set VERBOSITY terse \\ -- YB: suppress cascade details DROP FUNCTION base_fn_in(cstring); -- error ERROR: cannot drop function base_fn_in(cstring) because other objects depend on it -DROP FUNCTION base_fn_out(opaque); -- error -ERROR: function base_fn_out(opaque) does not exist +DROP FUNCTION base_fn_out(base_type); -- error +ERROR: cannot drop function base_fn_out(base_type) because other objects depend on it DROP TYPE base_type; -- error ERROR: cannot drop type base_type because other objects depend on it DROP TYPE base_type CASCADE; NOTICE: drop cascades to 2 other objects -\set VERBOSITY default +\set VERBOSITY default \\ -- YB: unsuppress cascade details -- Check usage of typmod with a user-defined type -- (we have borrowed numeric's typmod functions) CREATE TEMP TABLE mytab (foo widget(42,13,7)); -- should fail @@ -255,6 +265,16 @@ CREATE TABLE city ( location box, budget city_budget ); +INSERT INTO city VALUES +('Podunk', '(1,2),(3,4)', '100,127,1000'), +('Gotham', '(1000,34),(1100,334)', '123456,127,-1000,6789'); +TABLE city ORDER BY name DESC; -- YB-added ordering + name | location | budget +--------+----------------------+----------------------- + Podunk | (3,4),(1,2) | 100,127,1000,0 + Gotham | (1100,334),(1000,34) | 123456,127,-1000,6789 +(2 rows) + -- -- Test CREATE/ALTER TYPE using a type that's compatible with varchar, -- so we can re-use those support functions @@ -328,29 +348,11 @@ FROM pg_type WHERE typname = '_myvarchardom'; (1 row) -- ensure dependencies are straight --- YB note: trivial ordering difference in DETAIL as compared to PG +\set VERBOSITY terse \\ -- YB: suppress cascade details DROP FUNCTION myvarcharsend(myvarchar); -- fail ERROR: cannot drop function myvarcharsend(myvarchar) because other objects depend on it -DETAIL: function myvarcharin(cstring,oid,integer) depends on type myvarchar -function myvarcharout(myvarchar) depends on type myvarchar -function myvarcharrecv(internal,oid,integer) depends on type myvarchar -type myvarchar depends on function myvarcharsend(myvarchar) -type myvarchardom depends on function myvarcharsend(myvarchar) -HINT: Use DROP ... CASCADE to drop the dependent objects too. --- YB note: trivial ordering difference in DETAIL as compared to PG DROP TYPE myvarchar; -- fail ERROR: cannot drop type myvarchar because other objects depend on it -DETAIL: function myvarcharin(cstring,oid,integer) depends on type myvarchar -function myvarcharout(myvarchar) depends on type myvarchar -function myvarcharrecv(internal,oid,integer) depends on type myvarchar -function myvarcharsend(myvarchar) depends on type myvarchar -type myvarchardom depends on type myvarchar -HINT: Use DROP ... CASCADE to drop the dependent objects too. --- YB note: trivial ordering difference in DETAIL as compared to PG DROP TYPE myvarchar CASCADE; NOTICE: drop cascades to 5 other objects -DETAIL: drop cascades to function myvarcharin(cstring,oid,integer) -drop cascades to function myvarcharout(myvarchar) -drop cascades to function myvarcharrecv(internal,oid,integer) -drop cascades to function myvarcharsend(myvarchar) -drop cascades to type myvarchardom +\set VERBOSITY default \\ -- YB: unsuppress cascade details diff --git a/src/postgres/src/test/regress/expected/yb_pg_with.out b/src/postgres/src/test/regress/expected/yb_pg_with.out index 4af4a946d56a..79a92d160822 100644 --- a/src/postgres/src/test/regress/expected/yb_pg_with.out +++ b/src/postgres/src/test/regress/expected/yb_pg_with.out @@ -49,6 +49,15 @@ SELECT * FROM t; 5 (5 rows) +-- UNION DISTINCT requires hashable type +WITH RECURSIVE t(n) AS ( + VALUES (1::money) +UNION + SELECT n+1::money FROM t WHERE n < 100::money +) +SELECT sum(n) FROM t; +ERROR: could not implement recursive UNION +DETAIL: All column datatypes must be hashable. -- recursive view CREATE RECURSIVE VIEW nums (n) AS VALUES (1) @@ -133,10 +142,10 @@ SELECT * FROM t LIMIT 10; -- Test behavior with an unknown-type literal in the WITH WITH q AS (SELECT 'foo' AS x) -SELECT x, x IS OF (text) AS is_text FROM q; - x | is_text ------+--------- - foo | t +SELECT x, pg_typeof(x) FROM q; + x | pg_typeof +-----+----------- + foo | text (1 row) WITH RECURSIVE t(n) AS ( @@ -144,15 +153,15 @@ WITH RECURSIVE t(n) AS ( UNION ALL SELECT n || ' bar' FROM t WHERE length(n) < 20 ) -SELECT n, n IS OF (text) AS is_text FROM t; - n | is_text --------------------------+--------- - foo | t - foo bar | t - foo bar bar | t - foo bar bar bar | t - foo bar bar bar bar | t - foo bar bar bar bar bar | t +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 ... @@ -162,11 +171,70 @@ WITH RECURSIVE t(n) AS ( UNION ALL SELECT n+1 FROM t WHERE n < 10 ) -SELECT n, n IS OF (int) AS is_int FROM t; +SELECT n, pg_typeof(n) FROM t; ERROR: operator does not exist: text + integer LINE 4: SELECT n+1 FROM t WHERE n < 10 ^ HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +-- Deeply nested WITH caused a list-munging problem in v13 +-- Detection of cross-references and self-references +WITH RECURSIVE w1(c1) AS + (WITH w2(c2) AS + (WITH w3(c3) AS + (WITH w4(c4) AS + (WITH w5(c5) AS + (WITH RECURSIVE w6(c6) AS + (WITH w6(c6) AS + (WITH w8(c8) AS + (SELECT 1) + SELECT * FROM w8) + SELECT * FROM w6) + SELECT * FROM w6) + SELECT * FROM w5) + SELECT * FROM w4) + SELECT * FROM w3) + SELECT * FROM w2) +SELECT * FROM w1; + c1 +---- + 1 +(1 row) + +-- Detection of invalid self-references +WITH RECURSIVE outermost(x) AS ( + SELECT 1 + UNION (WITH innermost1 AS ( + SELECT 2 + UNION (WITH innermost2 AS ( + SELECT 3 + UNION (WITH innermost3 AS ( + SELECT 4 + UNION (WITH innermost4 AS ( + SELECT 5 + UNION (WITH innermost5 AS ( + SELECT 6 + UNION (WITH innermost6 AS + (SELECT 7) + SELECT * FROM innermost6)) + SELECT * FROM innermost5)) + SELECT * FROM innermost4)) + SELECT * FROM innermost3)) + SELECT * FROM innermost2)) + SELECT * FROM outermost + UNION SELECT * FROM innermost1) + ) + SELECT * FROM outermost ORDER BY 1; + x +--- + 1 + 2 + 3 + 4 + 5 + 6 + 7 +(7 rows) + -- -- Some examples with a tree -- @@ -547,7 +615,7 @@ UNION ALL 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) ORDER BY id; +(t1.id=t2.id) ORDER BY id; -- YB ordering id | path | t2 ----+-------------+-------------------- 1 | {} | (1,{}) @@ -568,6 +636,347 @@ SELECT t1.id, t2.path, t2 FROM t AS t1 JOIN t AS t2 ON 16 | {3,7,11,16} | (16,"{3,7,11,16}") (16 rows) +-- SEARCH clause +create temp table graph0( f int, t int, label text ); +insert into graph0 values + (1, 2, 'arc 1 -> 2'), + (1, 3, 'arc 1 -> 3'), + (2, 3, 'arc 2 -> 3'), + (1, 4, 'arc 1 -> 4'), + (4, 5, 'arc 4 -> 5'); +explain (verbose, costs off) +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search depth first by f, t set seq +select * from search_graph order by seq; + QUERY PLAN +---------------------------------------------------------------------------------------------- + Sort + Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq + Sort Key: search_graph.seq + CTE search_graph + -> Recursive Union + -> Seq Scan on pg_temp.graph0 g + Output: g.f, g.t, g.label, ARRAY[ROW(g.f, g.t)] + -> Merge Join + Output: g_1.f, g_1.t, g_1.label, array_cat(sg.seq, ARRAY[ROW(g_1.f, g_1.t)]) + Merge Cond: (g_1.f = sg.t) + -> Sort + Output: g_1.f, g_1.t, g_1.label + Sort Key: g_1.f + -> Seq Scan on pg_temp.graph0 g_1 + Output: g_1.f, g_1.t, g_1.label + -> Sort + Output: sg.seq, sg.t + Sort Key: sg.t + -> WorkTable Scan on search_graph sg + Output: sg.seq, sg.t + -> CTE Scan on search_graph + Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq +(22 rows) + +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search depth first by f, t set seq +select * from search_graph order by seq; + f | t | label | seq +---+---+------------+------------------- + 1 | 2 | arc 1 -> 2 | {"(1,2)"} + 2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"} + 1 | 3 | arc 1 -> 3 | {"(1,3)"} + 1 | 4 | arc 1 -> 4 | {"(1,4)"} + 4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"} + 2 | 3 | arc 2 -> 3 | {"(2,3)"} + 4 | 5 | arc 4 -> 5 | {"(4,5)"} +(7 rows) + +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union distinct + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search depth first by f, t set seq +select * from search_graph order by seq; + f | t | label | seq +---+---+------------+------------------- + 1 | 2 | arc 1 -> 2 | {"(1,2)"} + 2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"} + 1 | 3 | arc 1 -> 3 | {"(1,3)"} + 1 | 4 | arc 1 -> 4 | {"(1,4)"} + 4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"} + 2 | 3 | arc 2 -> 3 | {"(2,3)"} + 4 | 5 | arc 4 -> 5 | {"(4,5)"} +(7 rows) + +explain (verbose, costs off) +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search breadth first by f, t set seq +select * from search_graph order by seq; + QUERY PLAN +------------------------------------------------------------------------------------------------- + Sort + Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq + Sort Key: search_graph.seq + CTE search_graph + -> Recursive Union + -> Seq Scan on pg_temp.graph0 g + Output: g.f, g.t, g.label, ROW('0'::bigint, g.f, g.t) + -> Merge Join + Output: g_1.f, g_1.t, g_1.label, ROW(int8inc((sg.seq)."*DEPTH*"), g_1.f, g_1.t) + Merge Cond: (g_1.f = sg.t) + -> Sort + Output: g_1.f, g_1.t, g_1.label + Sort Key: g_1.f + -> Seq Scan on pg_temp.graph0 g_1 + Output: g_1.f, g_1.t, g_1.label + -> Sort + Output: sg.seq, sg.t + Sort Key: sg.t + -> WorkTable Scan on search_graph sg + Output: sg.seq, sg.t + -> CTE Scan on search_graph + Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq +(22 rows) + +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search breadth first by f, t set seq +select * from search_graph order by seq; + f | t | label | seq +---+---+------------+--------- + 1 | 2 | arc 1 -> 2 | (0,1,2) + 1 | 3 | arc 1 -> 3 | (0,1,3) + 1 | 4 | arc 1 -> 4 | (0,1,4) + 2 | 3 | arc 2 -> 3 | (0,2,3) + 4 | 5 | arc 4 -> 5 | (0,4,5) + 2 | 3 | arc 2 -> 3 | (1,2,3) + 4 | 5 | arc 4 -> 5 | (1,4,5) +(7 rows) + +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union distinct + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search breadth first by f, t set seq +select * from search_graph order by seq; + f | t | label | seq +---+---+------------+--------- + 1 | 2 | arc 1 -> 2 | (0,1,2) + 1 | 3 | arc 1 -> 3 | (0,1,3) + 1 | 4 | arc 1 -> 4 | (0,1,4) + 2 | 3 | arc 2 -> 3 | (0,2,3) + 4 | 5 | arc 4 -> 5 | (0,4,5) + 2 | 3 | arc 2 -> 3 | (1,2,3) + 4 | 5 | arc 4 -> 5 | (1,4,5) +(7 rows) + +-- 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 search_graph.f, + + search_graph.t, + + search_graph.label + + FROM search_graph; +(1 row) + +select * from v_search; + f | t | label +---+---+------------ + 1 | 2 | arc 1 -> 2 + 1 | 3 | arc 1 -> 3 + 2 | 3 | arc 2 -> 3 + 1 | 4 | arc 1 -> 4 + 4 | 5 | arc 4 -> 5 + 2 | 3 | arc 2 -> 3 + 4 | 5 | arc 4 -> 5 +(7 rows) + -- -- test cycle detection -- @@ -579,95 +988,653 @@ insert into graph values (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, path, cycle) as ( - select *, array[row(g.f, g.t)], false from graph g +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.*, path || row(g.f, g.t), row(g.f, g.t) = any(path) + 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 cycle + where g.f = sg.t and not is_cycle ) select * from search_graph order by path; - f | t | label | path | cycle ----+---+------------+-------------------------------------------+------- - 1 | 2 | arc 1 -> 2 | {"(1,2)"} | f - 2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"} | f - 1 | 3 | arc 1 -> 3 | {"(1,3)"} | f - 1 | 4 | arc 1 -> 4 | {"(1,4)"} | f - 4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"} | f - 5 | 1 | arc 5 -> 1 | {"(1,4)","(4,5)","(5,1)"} | f - 1 | 2 | arc 1 -> 2 | {"(1,4)","(4,5)","(5,1)","(1,2)"} | f - 2 | 3 | arc 2 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} | f - 1 | 3 | arc 1 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,3)"} | f - 1 | 4 | arc 1 -> 4 | {"(1,4)","(4,5)","(5,1)","(1,4)"} | t - 2 | 3 | arc 2 -> 3 | {"(2,3)"} | f - 4 | 5 | arc 4 -> 5 | {"(4,5)"} | f - 5 | 1 | arc 5 -> 1 | {"(4,5)","(5,1)"} | f - 1 | 2 | arc 1 -> 2 | {"(4,5)","(5,1)","(1,2)"} | f - 2 | 3 | arc 2 -> 3 | {"(4,5)","(5,1)","(1,2)","(2,3)"} | f - 1 | 3 | arc 1 -> 3 | {"(4,5)","(5,1)","(1,3)"} | f - 1 | 4 | arc 1 -> 4 | {"(4,5)","(5,1)","(1,4)"} | f - 4 | 5 | arc 4 -> 5 | {"(4,5)","(5,1)","(1,4)","(4,5)"} | t - 5 | 1 | arc 5 -> 1 | {"(5,1)"} | f - 1 | 2 | arc 1 -> 2 | {"(5,1)","(1,2)"} | f - 2 | 3 | arc 2 -> 3 | {"(5,1)","(1,2)","(2,3)"} | f - 1 | 3 | arc 1 -> 3 | {"(5,1)","(1,3)"} | f - 1 | 4 | arc 1 -> 4 | {"(5,1)","(1,4)"} | f - 4 | 5 | arc 4 -> 5 | {"(5,1)","(1,4)","(4,5)"} | f - 5 | 1 | arc 5 -> 1 | {"(5,1)","(1,4)","(4,5)","(5,1)"} | t + 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) -DROP TABLE graph; --- --- test cycle detection with regular table --- -create 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'); --- ordering by the path column has same effect as SEARCH DEPTH FIRST -with recursive search_graph(f, t, label, path, cycle) as ( - select *, array[row(g.f, g.t)], false from graph g +-- 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.*, path || row(g.f, g.t), row(g.f, g.t) = any(path) + select g.* from graph g, search_graph sg - where g.f = sg.t and not cycle -) -select * from search_graph order by path; - f | t | label | path | cycle ----+---+------------+-------------------------------------------+------- - 1 | 2 | arc 1 -> 2 | {"(1,2)"} | f - 2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"} | f - 1 | 3 | arc 1 -> 3 | {"(1,3)"} | f - 1 | 4 | arc 1 -> 4 | {"(1,4)"} | f - 4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"} | f - 5 | 1 | arc 5 -> 1 | {"(1,4)","(4,5)","(5,1)"} | f - 1 | 2 | arc 1 -> 2 | {"(1,4)","(4,5)","(5,1)","(1,2)"} | f - 2 | 3 | arc 2 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} | f - 1 | 3 | arc 1 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,3)"} | f - 1 | 4 | arc 1 -> 4 | {"(1,4)","(4,5)","(5,1)","(1,4)"} | t - 2 | 3 | arc 2 -> 3 | {"(2,3)"} | f - 4 | 5 | arc 4 -> 5 | {"(4,5)"} | f - 5 | 1 | arc 5 -> 1 | {"(4,5)","(5,1)"} | f - 1 | 2 | arc 1 -> 2 | {"(4,5)","(5,1)","(1,2)"} | f - 2 | 3 | arc 2 -> 3 | {"(4,5)","(5,1)","(1,2)","(2,3)"} | f - 1 | 3 | arc 1 -> 3 | {"(4,5)","(5,1)","(1,3)"} | f - 1 | 4 | arc 1 -> 4 | {"(4,5)","(5,1)","(1,4)"} | f - 4 | 5 | arc 4 -> 5 | {"(4,5)","(5,1)","(1,4)","(4,5)"} | t - 5 | 1 | arc 5 -> 1 | {"(5,1)"} | f - 1 | 2 | arc 1 -> 2 | {"(5,1)","(1,2)"} | f - 2 | 3 | arc 2 -> 3 | {"(5,1)","(1,2)","(2,3)"} | f - 1 | 3 | arc 1 -> 3 | {"(5,1)","(1,3)"} | f - 1 | 4 | arc 1 -> 4 | {"(5,1)","(1,4)"} | f - 4 | 5 | arc 4 -> 5 | {"(5,1)","(1,4)","(4,5)"} | f - 5 | 1 | arc 5 -> 1 | {"(5,1)","(1,4)","(4,5)","(5,1)"} | t + where g.f = sg.t +) cycle f, t set is_cycle to true default false using path +select f, t, label from search_graph; + f | t | label +---+---+------------ + 1 | 2 | arc 1 -> 2 + 1 | 3 | arc 1 -> 3 + 2 | 3 | arc 2 -> 3 + 1 | 4 | arc 1 -> 4 + 4 | 5 | arc 4 -> 5 + 5 | 1 | arc 5 -> 1 + 2 | 3 | arc 2 -> 3 + 4 | 5 | arc 4 -> 5 + 5 | 1 | arc 5 -> 1 + 1 | 4 | arc 1 -> 4 + 1 | 3 | arc 1 -> 3 + 1 | 2 | arc 1 -> 2 + 5 | 1 | arc 5 -> 1 + 1 | 4 | arc 1 -> 4 + 1 | 3 | arc 1 -> 3 + 1 | 2 | arc 1 -> 2 + 4 | 5 | arc 4 -> 5 + 2 | 3 | arc 2 -> 3 + 1 | 4 | arc 1 -> 4 + 1 | 3 | arc 1 -> 3 + 1 | 2 | arc 1 -> 2 + 4 | 5 | arc 4 -> 5 + 2 | 3 | arc 2 -> 3 + 5 | 1 | arc 5 -> 1 + 2 | 3 | arc 2 -> 3 +(25 rows) + +-- star expansion +with recursive a as ( + select 1 as b + union all + select * from a +) cycle b set c using p +select * from a; + b | c | p +---+---+----------- + 1 | f | {(1)} + 1 | t | {(1),(1)} +(2 rows) + +-- search+cycle +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) search depth first by f, t set seq + cycle f, t set is_cycle using path +select * from search_graph; + f | t | label | seq | is_cycle | path +---+---+------------+-------------------------------------------+----------+------------------------------------------- + 1 | 2 | arc 1 -> 2 | {"(1,2)"} | f | {"(1,2)"} + 1 | 3 | arc 1 -> 3 | {"(1,3)"} | f | {"(1,3)"} + 2 | 3 | arc 2 -> 3 | {"(2,3)"} | f | {"(2,3)"} + 1 | 4 | arc 1 -> 4 | {"(1,4)"} | f | {"(1,4)"} + 4 | 5 | arc 4 -> 5 | {"(4,5)"} | f | {"(4,5)"} + 5 | 1 | arc 5 -> 1 | {"(5,1)"} | f | {"(5,1)"} + 1 | 2 | arc 1 -> 2 | {"(5,1)","(1,2)"} | f | {"(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | {"(5,1)","(1,3)"} | f | {"(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | {"(5,1)","(1,4)"} | f | {"(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"} | f | {"(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"} | f | {"(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | {"(4,5)","(5,1)"} | f | {"(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | {"(4,5)","(5,1)","(1,2)"} | f | {"(4,5)","(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | {"(4,5)","(5,1)","(1,3)"} | f | {"(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | {"(4,5)","(5,1)","(1,4)"} | f | {"(4,5)","(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | {"(5,1)","(1,2)","(2,3)"} | f | {"(5,1)","(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | {"(5,1)","(1,4)","(4,5)"} | f | {"(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | {"(1,4)","(4,5)","(5,1)"} | f | {"(1,4)","(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | {"(1,4)","(4,5)","(5,1)","(1,2)"} | f | {"(1,4)","(4,5)","(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,3)"} | f | {"(1,4)","(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | {"(1,4)","(4,5)","(5,1)","(1,4)"} | t | {"(1,4)","(4,5)","(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | {"(4,5)","(5,1)","(1,2)","(2,3)"} | f | {"(4,5)","(5,1)","(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | {"(4,5)","(5,1)","(1,4)","(4,5)"} | t | {"(4,5)","(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | {"(5,1)","(1,4)","(4,5)","(5,1)"} | t | {"(5,1)","(1,4)","(4,5)","(5,1)"} + 2 | 3 | arc 2 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} +(25 rows) + +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) search breadth first by f, t set seq + cycle f, t set is_cycle using path +select * from search_graph; + f | t | label | seq | is_cycle | path +---+---+------------+---------+----------+------------------------------------------- + 1 | 2 | arc 1 -> 2 | (0,1,2) | f | {"(1,2)"} + 1 | 3 | arc 1 -> 3 | (0,1,3) | f | {"(1,3)"} + 2 | 3 | arc 2 -> 3 | (0,2,3) | f | {"(2,3)"} + 1 | 4 | arc 1 -> 4 | (0,1,4) | f | {"(1,4)"} + 4 | 5 | arc 4 -> 5 | (0,4,5) | f | {"(4,5)"} + 5 | 1 | arc 5 -> 1 | (0,5,1) | f | {"(5,1)"} + 1 | 2 | arc 1 -> 2 | (1,1,2) | f | {"(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | (1,1,3) | f | {"(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | (1,1,4) | f | {"(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | (1,2,3) | f | {"(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | (1,4,5) | f | {"(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | (1,5,1) | f | {"(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | (2,1,2) | f | {"(4,5)","(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | (2,1,3) | f | {"(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | (2,1,4) | f | {"(4,5)","(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | (2,2,3) | f | {"(5,1)","(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | (2,4,5) | f | {"(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | (2,5,1) | f | {"(1,4)","(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | (3,1,2) | f | {"(1,4)","(4,5)","(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | (3,1,3) | f | {"(1,4)","(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | (3,1,4) | t | {"(1,4)","(4,5)","(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | (3,2,3) | f | {"(4,5)","(5,1)","(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | (3,4,5) | t | {"(4,5)","(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | (3,5,1) | t | {"(5,1)","(1,4)","(4,5)","(5,1)"} + 2 | 3 | arc 2 -> 3 | (4,2,3) | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} +(25 rows) + +-- various syntax errors +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle foo, tar set is_cycle using path +select * from search_graph; +ERROR: cycle column "foo" not in WITH query column list +LINE 7: ) cycle foo, tar set is_cycle using path + ^ +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle to true default 55 using path +select * from search_graph; +ERROR: CYCLE types boolean and integer cannot be matched +LINE 7: ) cycle f, t set is_cycle to true default 55 using path + ^ +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle to point '(1,1)' default point '(0,0)' using path +select * from search_graph; +ERROR: could not identify an equality operator for type point +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set label to true default false using path +select * from search_graph; +ERROR: cycle mark column name "label" already used in WITH query column list +LINE 7: ) cycle f, t set label to true default false using path + ^ +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle to true default false using label +select * from search_graph; +ERROR: cycle path column name "label" already used in WITH query column list +LINE 7: ) cycle f, t set is_cycle to true default false using label + ^ +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set foo to true default false using foo +select * from search_graph; +ERROR: cycle mark column name and cycle path column name are the same +LINE 7: ) cycle f, t set foo to true default false using foo + ^ +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t, f set is_cycle to true default false using path +select * from search_graph; +ERROR: cycle column "f" specified more than once +LINE 7: ) cycle f, t, f set is_cycle to true default false using pat... + ^ +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) search depth first by f, t set foo + cycle f, t set foo to true default false using path +select * from search_graph; +ERROR: search sequence column name and cycle mark column name are the same +LINE 7: ) search depth first by f, t set foo + ^ +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) search depth first by f, t set foo + cycle f, t set is_cycle to true default false using foo +select * from search_graph; +ERROR: search sequence column name and cycle path column name are the same +LINE 7: ) search depth first by f, t set foo + ^ +-- test ruleutils and view expansion +create temp view v_cycle1 as +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle using path +select f, t, label from search_graph; +create temp view v_cycle2 as +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle to 'Y' default 'N' using path +select f, t, label from search_graph; +select pg_get_viewdef('v_cycle1'); + pg_get_viewdef +------------------------------------------------ + WITH RECURSIVE search_graph(f, t, label) AS (+ + SELECT g.f, + + g.t, + + g.label + + FROM graph g + + UNION ALL + + SELECT g.f, + + g.t, + + g.label + + FROM graph g, + + search_graph sg + + WHERE (g.f = sg.t) + + ) CYCLE f, t SET is_cycle USING path + + SELECT search_graph.f, + + search_graph.t, + + search_graph.label + + FROM search_graph; +(1 row) + +select pg_get_viewdef('v_cycle2'); + pg_get_viewdef +----------------------------------------------------------------------------- + WITH RECURSIVE search_graph(f, t, label) AS ( + + SELECT g.f, + + g.t, + + g.label + + FROM graph g + + UNION ALL + + SELECT g.f, + + g.t, + + g.label + + FROM graph g, + + search_graph sg + + WHERE (g.f = sg.t) + + ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+ + SELECT search_graph.f, + + search_graph.t, + + search_graph.label + + FROM search_graph; +(1 row) + +select * from v_cycle1; + f | t | label +---+---+------------ + 1 | 2 | arc 1 -> 2 + 1 | 3 | arc 1 -> 3 + 2 | 3 | arc 2 -> 3 + 1 | 4 | arc 1 -> 4 + 4 | 5 | arc 4 -> 5 + 5 | 1 | arc 5 -> 1 + 1 | 2 | arc 1 -> 2 + 1 | 3 | arc 1 -> 3 + 1 | 4 | arc 1 -> 4 + 2 | 3 | arc 2 -> 3 + 4 | 5 | arc 4 -> 5 + 5 | 1 | arc 5 -> 1 + 1 | 2 | arc 1 -> 2 + 1 | 3 | arc 1 -> 3 + 1 | 4 | arc 1 -> 4 + 2 | 3 | arc 2 -> 3 + 4 | 5 | arc 4 -> 5 + 5 | 1 | arc 5 -> 1 + 1 | 2 | arc 1 -> 2 + 1 | 3 | arc 1 -> 3 + 1 | 4 | arc 1 -> 4 + 2 | 3 | arc 2 -> 3 + 4 | 5 | arc 4 -> 5 + 5 | 1 | arc 5 -> 1 + 2 | 3 | arc 2 -> 3 +(25 rows) + +select * from v_cycle2; + f | t | label +---+---+------------ + 1 | 2 | arc 1 -> 2 + 1 | 3 | arc 1 -> 3 + 2 | 3 | arc 2 -> 3 + 1 | 4 | arc 1 -> 4 + 4 | 5 | arc 4 -> 5 + 5 | 1 | arc 5 -> 1 + 1 | 2 | arc 1 -> 2 + 1 | 3 | arc 1 -> 3 + 1 | 4 | arc 1 -> 4 + 2 | 3 | arc 2 -> 3 + 4 | 5 | arc 4 -> 5 + 5 | 1 | arc 5 -> 1 + 1 | 2 | arc 1 -> 2 + 1 | 3 | arc 1 -> 3 + 1 | 4 | arc 1 -> 4 + 2 | 3 | arc 2 -> 3 + 4 | 5 | arc 4 -> 5 + 5 | 1 | arc 5 -> 1 + 1 | 2 | arc 1 -> 2 + 1 | 3 | arc 1 -> 3 + 1 | 4 | arc 1 -> 4 + 2 | 3 | arc 2 -> 3 + 4 | 5 | arc 4 -> 5 + 5 | 1 | arc 5 -> 1 + 2 | 3 | arc 2 -> 3 (25 rows) -DROP TABLE graph; -- -- test multiple WITH queries -- @@ -970,6 +1937,11 @@ 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; @@ -1004,7 +1976,7 @@ WITH RECURSIVE x(n) AS (SELECT n FROM x UNION ALL SELECT 1) ERROR: recursive reference to query "x" must not appear within its non-recursive term LINE 1: WITH RECURSIVE x(n) AS (SELECT n FROM x UNION ALL SELECT 1) ^ -CREATE TABLE y (a INTEGER); +CREATE TABLE y (a INTEGER, ybsort SERIAL, PRIMARY KEY (ybsort ASC)); -- YB: exercise YB table instead of temp table INSERT INTO y SELECT generate_series(1, 10); -- LEFT JOIN WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1 @@ -1164,28 +2136,32 @@ with cte(foo) as ( select 42 ) select * from ((select foo from cte)) q; -- test CTE referencing an outer-level variable (to see that changed-parameter -- signaling still works properly after fixing this bug) +SELECT * FROM ( -- YB select ( with cte(foo) as ( values(f1) ) select (select foo from cte) ) -from int4_tbl ORDER BY foo; +from int4_tbl +LIMIT ALL) ybview ORDER BY (abs(foo) - sign(foo)); -- YB ordering foo ------------- - -2147483647 - -123456 0 123456 + -123456 2147483647 + -2147483647 (5 rows) +SELECT * FROM ( -- YB select ( with cte(foo) as ( values(f1) ) values((select foo from cte)) ) -from int4_tbl ORDER BY column1; +from int4_tbl +LIMIT ALL) ybview ORDER BY (abs(column1) - sign(column1)); -- YB ordering column1 ------------- - -2147483647 - -123456 0 123456 + -123456 2147483647 + -2147483647 (5 rows) -- @@ -1286,7 +2262,7 @@ WITH outermost(x) AS ( ) SELECT * FROM outermost ORDER BY 1; ERROR: relation "outermost" does not exist -LINE 4: SELECT * FROM outermost +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. @@ -1310,7 +2286,7 @@ WITH RECURSIVE outermost(x) AS ( ) 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) +LINE 2: WITH innermost as (SELECT 2 FROM outermost) -- fail ^ -- -- This test will fail with the old implementation of PARAM_EXEC parameter @@ -1323,7 +2299,7 @@ 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 ORDER BY id; +select * from C ORDER BY id; -- YB ordering (not sorted like upstream PG) id | array -------------------+------------------------------------- -4567890123456789 | {-4567890123456789} @@ -1427,7 +2403,7 @@ WITH t AS ( (20) RETURNING * ) -SELECT * FROM t ORDER BY a; +SELECT a FROM t; -- YB: avoid ybsort column a ---- 11 @@ -1442,7 +2418,7 @@ SELECT * FROM t ORDER BY a; 20 (10 rows) -SELECT * FROM y ORDER BY a; +SELECT a FROM y; -- YB: avoid ybsort column a ---- 1 @@ -1473,7 +2449,7 @@ WITH t AS ( SET a=a+1 RETURNING * ) -SELECT * FROM t ORDER BY a; +SELECT a FROM t; -- YB: avoid ybsort column a ---- 2 @@ -1498,7 +2474,7 @@ SELECT * FROM t ORDER BY a; 21 (20 rows) -SELECT * FROM y ORDER BY a; +SELECT a FROM y; -- YB: avoid ybsort column a ---- 2 @@ -1529,7 +2505,7 @@ WITH t AS ( WHERE a <= 10 RETURNING * ) -SELECT * FROM t ORDER BY a; +SELECT a FROM t; -- YB: avoid ybsort column a ---- 2 @@ -1543,7 +2519,7 @@ SELECT * FROM t ORDER BY a; 10 (9 rows) -SELECT * FROM y ORDER BY a; +SELECT a FROM y; -- YB: avoid ybsort column a ---- 11 @@ -1565,14 +2541,18 @@ WITH RECURSIVE t AS ( SELECT a+5 FROM t2 WHERE a > 5 RETURNING * ), t2 AS ( - UPDATE y SET a=a-11 RETURNING * + UPDATE y SET a=a-11, ybsort=nextval('y_ybsort_seq') RETURNING * -- YB: mimic upstream PG's new ctid allocation ) -SELECT * FROM t +SELECT a FROM t -- YB: avoid ybsort column UNION ALL -SELECT * FROM t2 -ORDER BY a; +SELECT a FROM t2; -- YB: avoid ybsort column a ---- + 11 + 12 + 13 + 14 + 15 0 1 2 @@ -1584,14 +2564,9 @@ ORDER BY a; 8 9 10 - 11 - 12 - 13 - 14 - 15 (16 rows) -SELECT * FROM y ORDER BY a; +SELECT a FROM y; -- YB: avoid ybsort column a ---- 0 @@ -1601,14 +2576,14 @@ SELECT * FROM y ORDER BY a; 4 5 6 - 7 - 8 - 9 - 10 11 + 7 12 + 8 13 + 9 14 + 10 15 (16 rows) @@ -1618,13 +2593,13 @@ CREATE RULE y_rule AS ON DELETE TO y DO INSTEAD WITH t AS ( DELETE FROM y RETURNING * ) -SELECT * FROM t; +SELECT a FROM t; -- YB: avoid ybsort column a ---- 42 (1 row) -SELECT * FROM y ORDER BY a; +SELECT a FROM y; -- YB: avoid ybsort column a ---- 0 @@ -1634,14 +2609,14 @@ SELECT * FROM y ORDER BY a; 4 5 6 - 7 - 8 - 9 - 10 11 + 7 12 + 8 13 + 9 14 + 10 15 42 (17 rows) @@ -1671,7 +2646,7 @@ SELECT * FROM bug6051; CREATE TEMP TABLE bug6051_2 (i int); CREATE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD INSERT INTO bug6051_2 - SELECT NEW.i; + VALUES(NEW.i); WITH t1 AS ( DELETE FROM bug6051 RETURNING * ) INSERT INTO bug6051 SELECT * FROM t1; SELECT * FROM bug6051; @@ -1687,6 +2662,105 @@ SELECT * FROM bug6051_2; 3 (3 rows) +-- check INSERT...SELECT rule actions are disallowed on commands +-- that have modifyingCTEs +CREATE OR REPLACE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD + INSERT INTO bug6051_2 + SELECT NEW.i; +WITH t1 AS ( DELETE FROM bug6051 RETURNING * ) +INSERT INTO bug6051 SELECT * FROM t1; +ERROR: INSERT...SELECT rule actions are not supported for queries having data-modifying statements in WITH +-- silly example to verify that hasModifyingCTE flag is propagated +CREATE TEMP TABLE bug6051_3 AS + SELECT a FROM generate_series(11,13) AS a; +CREATE RULE bug6051_3_ins AS ON INSERT TO bug6051_3 DO INSTEAD + SELECT i FROM bug6051_2; +BEGIN; SET LOCAL force_parallel_mode = on; +WITH t1 AS ( DELETE FROM bug6051_3 RETURNING * ) + INSERT INTO bug6051_3 SELECT * FROM t1; + i +--- + 1 + 2 + 3 + 1 + 2 + 3 + 1 + 2 + 3 +(9 rows) + +COMMIT; +SELECT * FROM bug6051_3; + a +--- +(0 rows) + +-- 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 @@ -1696,7 +2770,7 @@ WITH RECURSIVE t(a) AS ( INSERT INTO y SELECT * FROM t RETURNING * ) -SELECT * FROM t2 JOIN y USING (a) ORDER BY a; +SELECT a FROM t2 JOIN y USING (a) ORDER BY a; -- YB: avoid ybsort column a --- 0 @@ -1706,31 +2780,31 @@ SELECT * FROM t2 JOIN y USING (a) ORDER BY a; 4 (5 rows) -SELECT * FROM y ORDER BY a; +SELECT a FROM y; -- YB: avoid ybsort column a ---- 0 - 0 - 1 1 2 - 2 3 - 3 - 4 4 5 6 - 7 - 8 - 9 - 10 11 + 7 12 + 8 13 + 9 14 + 10 15 42 + 0 + 1 + 2 + 3 + 4 (22 rows) -- data-modifying WITH in a modifying statement @@ -1739,95 +2813,98 @@ WITH t AS ( WHERE a <= 10 RETURNING * ) --- Removing Returning clause to make result deterministic. -INSERT INTO y SELECT -a FROM t; -SELECT * FROM y ORDER BY a; - a +INSERT INTO y SELECT -a FROM t RETURNING a; -- YB: avoid ybsort column + a ----- - -10 - -9 - -8 - -7 - -6 - -5 - -4 - -4 - -3 - -3 - -2 - -2 - -1 - -1 0 + -1 + -2 + -3 + -4 + -5 + -6 + -7 + -8 + -9 + -10 0 + -1 + -2 + -3 + -4 +(16 rows) + +SELECT a FROM y; -- YB: avoid ybsort column + 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 * ) --- Disable test as results are not deterministic. -/* -SELECT * FROM t ORDER BY a LIMIT 10; -*/ -SELECT * FROM t ORDER BY a; +SELECT a FROM t LIMIT 10; -- YB: avoid ybsort column + a +------ + 1100 + 1200 + 1300 + 1400 + 1500 + 4200 + 0 + -100 + -200 + -300 +(10 rows) + +SELECT a FROM y; -- YB: avoid ybsort column a ------- - -1000 - -900 - -800 - -700 - -600 - -500 - -400 - -400 - -300 - -300 - -200 - -200 - -100 - -100 - 0 - 0 1100 1200 1300 1400 1500 4200 -(22 rows) - -SELECT * FROM y ORDER BY a; - a -------- - -1000 - -900 - -800 - -700 - -600 - -500 - -400 - -400 - -300 - -300 - -200 - -200 - -100 - -100 0 + -100 + -200 + -300 + -400 + -500 + -600 + -700 + -800 + -900 + -1000 0 - 1100 - 1200 - 1300 - 1400 - 1500 - 4200 + -100 + -200 + -300 + -400 (22 rows) -- data-modifying WITH containing INSERT...ON CONFLICT DO UPDATE @@ -1839,7 +2916,7 @@ WITH t AS ( 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; +SELECT k, v, a FROM t JOIN y ON t.k = y.a ORDER BY a, k; -- YB: avoid ybsort column k | v | a ---+--------+--- 0 | insert | 0 @@ -1899,7 +2976,7 @@ ON CONFLICT (k) DO UPDATE SET v = (SELECT b || ' update' FROM aa WHERE a = 'a' L 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); -/* Disable the below test until #6782 is fixed +/* YB: Disable the below test until #6782 is fixed -- 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. @@ -1914,12 +2991,37 @@ UPDATE SET (k, v) = (SELECT k, v FROM upsert_cte WHERE upsert_cte.k = withz.k) RETURNING k, v; */ 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: This statement not supported yet +LINE 1: WITH RECURSIVE cte_basic AS (SELECT 1 a, 'cte_basic val' b) + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +-- 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); -- YB: port more queries from the original test once this query is fixed +ERROR: This statement not supported yet +LINE 1: WITH cte_basic AS MATERIALIZED (SELECT 1 a, 'cte_basic val' ... + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +DROP TABLE m; -- check that run to completion happens in proper ordering TRUNCATE TABLE y; INSERT INTO y SELECT generate_series(1, 3); -CREATE TABLE yy (a INTEGER); +CREATE TABLE yy (a INTEGER, ybsort INT); -- YB: exercise YB table +ALTER TABLE y DROP CONSTRAINT y_pkey; -- YB: ybsort column needs to be non-unique for the next queries +NOTICE: table rewrite may lead to inconsistencies +DETAIL: Concurrent DMLs may not be reflected in the new table. +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19860. Set 'ysql_suppress_unsafe_alter_notice' yb-tserver gflag to true to suppress this notice. WITH RECURSIVE t1 AS ( - INSERT INTO y SELECT * FROM y RETURNING * + INSERT INTO y SELECT a FROM y ORDER BY y.ybsort RETURNING * -- YB: add ordering; avoid selecting ybsort column to auto-generate it during insert ), t2 AS ( INSERT INTO yy SELECT * FROM t1 RETURNING * ) @@ -1929,18 +3031,18 @@ SELECT 1; 1 (1 row) -SELECT * FROM y ORDER BY a; +SELECT a FROM y ORDER BY ybsort; -- YB: avoid ybsort column; add ordering a --- - 1 1 2 - 2 3 + 1 + 2 3 (6 rows) -SELECT * FROM yy ORDER BY a; +SELECT a FROM yy ORDER BY ybsort; -- YB: avoid ybsort column; add ordering a --- 1 @@ -1951,7 +3053,7 @@ SELECT * FROM yy ORDER BY a; WITH RECURSIVE t1 AS ( INSERT INTO yy SELECT * FROM t2 RETURNING * ), t2 AS ( - INSERT INTO y SELECT * FROM y RETURNING * + INSERT INTO y SELECT a FROM y ORDER BY y.ybsort RETURNING * -- YB: add ordering; avoid selecting ybsort column to auto-generate it during insert ) SELECT 1; ?column? @@ -1959,39 +3061,43 @@ SELECT 1; 1 (1 row) -SELECT * FROM y ORDER BY a; +SELECT a FROM y ORDER BY ybsort; -- YB: avoid ybsort column; add ordering a --- - 1 - 1 - 1 1 2 - 2 - 2 - 2 3 + 1 + 2 3 + 1 + 2 3 + 1 + 2 3 (12 rows) -SELECT * FROM yy ORDER BY a; +SELECT a FROM yy ORDER BY ybsort; -- YB: avoid ybsort column; add ordering a --- 1 - 1 - 1 - 2 - 2 2 3 + 1 + 2 3 + 1 + 2 3 (9 rows) -- triggers TRUNCATE TABLE y; +ALTER TABLE y ADD PRIMARY KEY (ybsort ASC); -- YB: ybsort column can be unique again +NOTICE: table rewrite may lead to inconsistencies +DETAIL: Concurrent DMLs may not be reflected in the new table. +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19860. Set 'ysql_suppress_unsafe_alter_notice' yb-tserver gflag to true to suppress this notice. INSERT INTO y SELECT generate_series(1, 10); CREATE FUNCTION y_trigger() RETURNS trigger AS $$ begin @@ -2009,7 +3115,7 @@ WITH t AS ( (23) RETURNING * ) -SELECT * FROM t ORDER BY a; +SELECT a FROM t; -- YB: avoid ybsort column NOTICE: y_trigger: a = 21 NOTICE: y_trigger: a = 22 NOTICE: y_trigger: a = 23 @@ -2020,7 +3126,7 @@ NOTICE: y_trigger: a = 23 23 (3 rows) -SELECT * FROM y ORDER BY a; +SELECT a FROM y; -- YB: avoid ybsort column a ---- 1 @@ -2049,7 +3155,7 @@ WITH t AS ( (33) RETURNING * ) -SELECT * FROM t LIMIT 1; +SELECT a FROM t LIMIT 1; -- YB: avoid ybsort column NOTICE: y_trigger: a = 31 NOTICE: y_trigger: a = 32 NOTICE: y_trigger: a = 33 @@ -2058,7 +3164,7 @@ NOTICE: y_trigger: a = 33 31 (1 row) -SELECT * FROM y ORDER BY a; +SELECT a FROM y; -- YB: avoid ybsort column a ---- 1 @@ -2096,7 +3202,7 @@ WITH t AS ( (43) RETURNING * ) -SELECT * FROM t ORDER BY a; +SELECT a FROM t; -- YB: avoid ybsort column NOTICE: y_trigger a ---- @@ -2105,7 +3211,7 @@ NOTICE: y_trigger 43 (3 rows) -SELECT * FROM y ORDER BY a; +SELECT a FROM y; -- YB: avoid ybsort column a ---- 1 @@ -2138,7 +3244,7 @@ ERROR: INHERITS not supported yet LINE 1: CREATE TEMP TABLE child1 ( ) INHERITS ( parent ); ^ HINT: See https://github.com/yugabyte/yugabyte-db/issues/1129. React with thumbs up to raise its priority -/* +/* YB CREATE TEMP TABLE child2 ( ) INHERITS ( parent ); INSERT INTO parent VALUES ( 1, 'p1' ); INSERT INTO child1 VALUES ( 11, 'c11' ),( 12, 'c12' ); @@ -2149,6 +3255,7 @@ SELECT * FROM parent; WITH wcte AS ( INSERT INTO child1 VALUES ( 42, 'new' ) RETURNING id AS newid ) UPDATE parent SET id = id + newid FROM wcte; SELECT * FROM parent; +WITH rcte AS ( SELECT max(id) AS maxid FROM parent ) DELETE FROM parent USING rcte WHERE id = maxid; SELECT * FROM parent; WITH wcte AS ( INSERT INTO child2 VALUES ( 42, 'new2' ) RETURNING id AS newid ) @@ -2165,9 +3272,9 @@ WITH RECURSIVE t AS ( INSERT INTO y SELECT * FROM t ) -VALUES(FALSE); +VALUES(FALSE); -- YB: it's unclear why the line number in output is different from upstream. ERROR: recursive query "t" must not contain data-modifying statements -LINE 24: WITH RECURSIVE t AS ( +LINE 25: WITH RECURSIVE t AS ( ^ -- no RETURNING in a referenced data-modifying WITH WITH t AS ( @@ -2192,6 +3299,31 @@ WITH t AS ( ) 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 @@ -2209,19 +3341,19 @@ with ordinality as (select 1 as x) select * from ordinality; (1 row) -- check sane response to attempt to modify CTE relation -WITH test AS (SELECT 42) INSERT INTO test VALUES (1); -ERROR: relation "test" does not exist -LINE 1: WITH test AS (SELECT 42) INSERT INTO test VALUES (1); - ^ +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 test (i int); -with test as (select 42) insert into test select * from test; -select * from test; - i +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 test; +drop table with_test; diff --git a/src/postgres/src/test/regress/sql/yb_pg_create_operator.sql b/src/postgres/src/test/regress/sql/yb_pg_create_operator.sql index 7759726648d2..f53e24db3c40 100644 --- a/src/postgres/src/test/regress/sql/yb_pg_create_operator.sql +++ b/src/postgres/src/test/regress/sql/yb_pg_create_operator.sql @@ -10,30 +10,32 @@ CREATE OPERATOR ## ( ); CREATE OPERATOR @#@ ( - rightarg = int8, -- left unary - procedure = numeric_fac -); - -CREATE OPERATOR #@# ( - leftarg = int8, -- right unary - procedure = numeric_fac + rightarg = int8, -- prefix + procedure = factorial ); CREATE OPERATOR #%# ( - leftarg = int8, -- right unary - procedure = numeric_fac + leftarg = int8, -- fail, postfix is no longer supported + procedure = factorial ); -- Test operator created above SELECT @#@ 24; -- Test comments -COMMENT ON OPERATOR ###### (int4, NONE) IS 'bad right unary'; +COMMENT ON OPERATOR ###### (NONE, int4) IS 'bad prefix'; +COMMENT ON OPERATOR ###### (int4, NONE) IS 'bad postfix'; +COMMENT ON OPERATOR ###### (int4, int8) IS 'bad infix'; --- => is disallowed now +-- Check that DROP on a nonexistent op behaves sanely, too +DROP OPERATOR ###### (NONE, int4); +DROP OPERATOR ###### (int4, NONE); +DROP OPERATOR ###### (int4, int8); + +-- => is disallowed as an operator name now CREATE OPERATOR => ( - leftarg = int8, -- right unary - procedure = numeric_fac + rightarg = int8, + procedure = factorial ); -- lexing of <=, >=, <>, != has a number of edge cases @@ -41,10 +43,12 @@ CREATE OPERATOR => ( -- this is legal because ! is not allowed in sql ops CREATE OPERATOR !=- ( - leftarg = int8, -- right unary - procedure = numeric_fac + rightarg = int8, + procedure = factorial ); -SELECT 2 !=-; +SELECT !=- 10; +-- postfix operators don't work anymore +SELECT 10 !=-; -- make sure lexer returns != as <> even in edge cases SELECT 2 !=/**/ 1, 2 !=/**/ 2; SELECT 2 !=-- comment to be removed by psql @@ -75,8 +79,8 @@ GRANT USAGE ON SCHEMA schema_op1 TO PUBLIC; REVOKE USAGE ON SCHEMA schema_op1 FROM regress_rol_op1; SET ROLE regress_rol_op1; CREATE OPERATOR schema_op1.#*# ( - leftarg = int8, -- right unary - procedure = numeric_fac + rightarg = int8, + procedure = factorial ); ROLLBACK; @@ -85,7 +89,7 @@ ROLLBACK; BEGIN TRANSACTION; CREATE OPERATOR #*# ( leftarg = SETOF int8, - procedure = numeric_fac + procedure = factorial ); ROLLBACK; @@ -94,7 +98,7 @@ ROLLBACK; BEGIN TRANSACTION; CREATE OPERATOR #*# ( rightarg = SETOF int8, - procedure = numeric_fac + procedure = factorial ); ROLLBACK; @@ -119,19 +123,19 @@ ROLLBACK; -- Should fail. Invalid attribute CREATE OPERATOR #@%# ( - leftarg = int8, -- right unary - procedure = numeric_fac, + rightarg = int8, + procedure = factorial, invalid_att = int8 ); --- Should fail. At least leftarg or rightarg should be mandatorily specified +-- Should fail. At least rightarg should be mandatorily specified CREATE OPERATOR #@%# ( - procedure = numeric_fac + procedure = factorial ); -- Should fail. Procedure should be mandatorily specified CREATE OPERATOR #@%# ( - leftarg = int8 + rightarg = int8 ); -- Should fail. CREATE OPERATOR requires USAGE on TYPE diff --git a/src/postgres/src/test/regress/sql/yb_pg_create_type.sql b/src/postgres/src/test/regress/sql/yb_pg_create_type.sql index 7dc4dca4bd55..f9fd17ab351d 100644 --- a/src/postgres/src/test/regress/sql/yb_pg_create_type.sql +++ b/src/postgres/src/test/regress/sql/yb_pg_create_type.sql @@ -109,8 +109,11 @@ INSERT INTO default_test DEFAULT VALUES; SELECT * FROM default_test; +-- We need a shell type to test some CREATE TYPE failure cases with +CREATE TYPE bogus_type; + -- invalid: non-lowercase quoted identifiers -CREATE TYPE case_int42 ( +CREATE TYPE bogus_type ( "Internallength" = 4, "Input" = int42_in, "Output" = int42_out, @@ -119,6 +122,20 @@ CREATE TYPE case_int42 ( "Passedbyvalue" ); +-- invalid: input/output function incompatibility +CREATE TYPE bogus_type (INPUT = array_in, + OUTPUT = array_out, + ELEMENT = int, + INTERNALLENGTH = 32); + +DROP TYPE bogus_type; + +-- It no longer is possible to issue CREATE TYPE without making a shell first +CREATE TYPE bogus_type (INPUT = array_in, + OUTPUT = array_out, + ELEMENT = int, + INTERNALLENGTH = 32); + -- Test stand-alone composite type CREATE TYPE default_test_row AS (f1 text_w_default, f2 int42); @@ -144,24 +161,19 @@ DROP TYPE default_test_row CASCADE; DROP TABLE default_test; --- Check type create with input/output incompatibility -CREATE TYPE not_existing_type (INPUT = array_in, - OUTPUT = array_out, - ELEMENT = int, - INTERNALLENGTH = 32); - --- Check dependency transfer of opaque functions when creating a new type -CREATE FUNCTION base_fn_in(cstring) RETURNS opaque AS 'boolin' +-- Check dependencies are established when creating a new type +CREATE TYPE base_type; +CREATE FUNCTION base_fn_in(cstring) RETURNS base_type AS 'boolin' LANGUAGE internal IMMUTABLE STRICT; -CREATE FUNCTION base_fn_out(opaque) RETURNS opaque AS 'boolout' +CREATE FUNCTION base_fn_out(base_type) RETURNS cstring AS 'boolout' LANGUAGE internal IMMUTABLE STRICT; CREATE TYPE base_type(INPUT = base_fn_in, OUTPUT = base_fn_out); -\set VERBOSITY terse \\ -- suppress cascade details +\set VERBOSITY terse \\ -- YB: suppress cascade details DROP FUNCTION base_fn_in(cstring); -- error -DROP FUNCTION base_fn_out(opaque); -- error +DROP FUNCTION base_fn_out(base_type); -- error DROP TYPE base_type; -- error DROP TYPE base_type CASCADE; -\set VERBOSITY default +\set VERBOSITY default \\ -- YB: unsuppress cascade details -- Check usage of typmod with a user-defined type -- (we have borrowed numeric's typmod functions) @@ -207,6 +219,12 @@ CREATE TABLE city ( budget city_budget ); +INSERT INTO city VALUES +('Podunk', '(1,2),(3,4)', '100,127,1000'), +('Gotham', '(1000,34),(1100,334)', '123456,127,-1000,6789'); + +TABLE city ORDER BY name DESC; -- YB-added ordering + -- -- Test CREATE/ALTER TYPE using a type that's compatible with varchar, -- so we can re-use those support functions @@ -269,10 +287,9 @@ SELECT typinput, typoutput, typreceive, typsend, typmodin, typmodout, FROM pg_type WHERE typname = '_myvarchardom'; -- ensure dependencies are straight --- YB note: trivial ordering difference in DETAIL as compared to PG +\set VERBOSITY terse \\ -- YB: suppress cascade details DROP FUNCTION myvarcharsend(myvarchar); -- fail --- YB note: trivial ordering difference in DETAIL as compared to PG DROP TYPE myvarchar; -- fail --- YB note: trivial ordering difference in DETAIL as compared to PG DROP TYPE myvarchar CASCADE; +\set VERBOSITY default \\ -- YB: unsuppress cascade details diff --git a/src/postgres/src/test/regress/sql/yb_pg_with.sql b/src/postgres/src/test/regress/sql/yb_pg_with.sql index 07a27ef2ede5..6a0f4ef30276 100644 --- a/src/postgres/src/test/regress/sql/yb_pg_with.sql +++ b/src/postgres/src/test/regress/sql/yb_pg_with.sql @@ -31,6 +31,14 @@ UNION ALL ) SELECT * FROM t; +-- UNION DISTINCT requires hashable type +WITH RECURSIVE t(n) AS ( + VALUES (1::money) +UNION + SELECT n+1::money FROM t WHERE n < 100::money +) +SELECT sum(n) FROM t; + -- recursive view CREATE RECURSIVE VIEW nums (n) AS VALUES (1) @@ -69,14 +77,14 @@ SELECT * FROM t LIMIT 10; -- Test behavior with an unknown-type literal in the WITH WITH q AS (SELECT 'foo' AS x) -SELECT x, x IS OF (text) AS is_text FROM q; +SELECT x, pg_typeof(x) FROM q; WITH RECURSIVE t(n) AS ( SELECT 'foo' UNION ALL SELECT n || ' bar' FROM t WHERE length(n) < 20 ) -SELECT n, n IS OF (text) AS is_text FROM t; +SELECT n, pg_typeof(n) FROM t; -- 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. @@ -85,7 +93,51 @@ WITH RECURSIVE t(n) AS ( UNION ALL SELECT n+1 FROM t WHERE n < 10 ) -SELECT n, n IS OF (int) AS is_int FROM t; +SELECT n, pg_typeof(n) FROM t; + +-- 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; +-- 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; -- -- Some examples with a tree @@ -293,7 +345,184 @@ UNION ALL 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) ORDER BY id; +(t1.id=t2.id) ORDER BY id; -- YB ordering + +-- 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; + +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; + +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; + +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; + +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; + +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; + +-- 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; + +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; + +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; + +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; + +-- 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; + +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; + +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; + +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; + +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; + +-- 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; + +-- 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'); + +select * from v_search; -- -- test cycle detection @@ -308,40 +537,249 @@ insert into graph values (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; + +-- 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; + -- ordering by the path column has same effect as SEARCH DEPTH FIRST -with recursive search_graph(f, t, label, path, cycle) as ( - select *, array[row(g.f, g.t)], false from graph g +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.*, path || row(g.f, g.t), row(g.f, g.t) = any(path) + 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 cycle + where g.f = sg.t and not is_cycle ) select * from search_graph order by path; -DROP TABLE graph; --- --- test cycle detection with regular table --- -create table graph( f int, t int, label text ); +-- CYCLE clause -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'); +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; --- ordering by the path column has same effect as SEARCH DEPTH FIRST -with recursive search_graph(f, t, label, path, cycle) as ( - select *, array[row(g.f, g.t)], false from graph g +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; + +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; + +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; + +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; + +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; + +-- 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.*, path || row(g.f, g.t), row(g.f, g.t) = any(path) + select g.* from graph g, search_graph sg - where g.f = sg.t and not cycle -) -select * from search_graph order by path; -DROP TABLE graph; + where g.f = sg.t +) cycle f, t set is_cycle to true default false using path +select f, t, label from search_graph; + +-- star expansion +with recursive a as ( + select 1 as b + union all + select * from a +) cycle b set c using p +select * from a; + +-- 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; + +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; + +-- 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; + +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; + +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; + +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; + +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; + +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; + +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; + +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; + +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; + +-- 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'); +select pg_get_viewdef('v_cycle2'); + +select * from v_cycle1; +select * from v_cycle2; -- -- test multiple WITH queries @@ -426,6 +864,9 @@ DROP TABLE y; -- error cases -- +WITH x(n, b) AS (SELECT 1) +SELECT * FROM x; + -- INTERSECT WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x) SELECT * FROM x; @@ -448,7 +889,7 @@ WITH RECURSIVE x(n) AS (SELECT n FROM x) WITH RECURSIVE x(n) AS (SELECT n FROM x UNION ALL SELECT 1) SELECT * FROM x; -CREATE TABLE y (a INTEGER); +CREATE TABLE y (a INTEGER, ybsort SERIAL, PRIMARY KEY (ybsort ASC)); -- YB: exercise YB table instead of temp table INSERT INTO y SELECT generate_series(1, 10); -- LEFT JOIN @@ -567,13 +1008,17 @@ with cte(foo) as ( select 42 ) select * from ((select foo from cte)) q; -- test CTE referencing an outer-level variable (to see that changed-parameter -- signaling still works properly after fixing this bug) +SELECT * FROM ( -- YB select ( with cte(foo) as ( values(f1) ) select (select foo from cte) ) -from int4_tbl ORDER BY foo; +from int4_tbl +LIMIT ALL) ybview ORDER BY (abs(foo) - sign(foo)); -- YB ordering +SELECT * FROM ( -- YB select ( with cte(foo) as ( values(f1) ) values((select foo from cte)) ) -from int4_tbl ORDER BY column1; +from int4_tbl +LIMIT ALL) ybview ORDER BY (abs(column1) - sign(column1)); -- YB ordering -- -- test for nested-recursive-WITH bug @@ -637,7 +1082,7 @@ 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 ORDER BY id; +select * from C ORDER BY id; -- YB ordering (not sorted like upstream PG) -- -- Test CTEs read in non-initialization orders @@ -700,7 +1145,6 @@ SELECT * FROM iter; -- -- INSERT ... RETURNING - WITH t AS ( INSERT INTO y VALUES @@ -716,9 +1160,9 @@ WITH t AS ( (20) RETURNING * ) -SELECT * FROM t ORDER BY a; +SELECT a FROM t; -- YB: avoid ybsort column -SELECT * FROM y ORDER BY a; +SELECT a FROM y; -- YB: avoid ybsort column -- UPDATE ... RETURNING WITH t AS ( @@ -726,9 +1170,9 @@ WITH t AS ( SET a=a+1 RETURNING * ) -SELECT * FROM t ORDER BY a; +SELECT a FROM t; -- YB: avoid ybsort column -SELECT * FROM y ORDER BY a; +SELECT a FROM y; -- YB: avoid ybsort column -- DELETE ... RETURNING WITH t AS ( @@ -736,9 +1180,9 @@ WITH t AS ( WHERE a <= 10 RETURNING * ) -SELECT * FROM t ORDER BY a; +SELECT a FROM t; -- YB: avoid ybsort column -SELECT * FROM y ORDER BY a; +SELECT a FROM y; -- YB: avoid ybsort column -- forward reference WITH RECURSIVE t AS ( @@ -746,14 +1190,13 @@ WITH RECURSIVE t AS ( SELECT a+5 FROM t2 WHERE a > 5 RETURNING * ), t2 AS ( - UPDATE y SET a=a-11 RETURNING * + UPDATE y SET a=a-11, ybsort=nextval('y_ybsort_seq') RETURNING * -- YB: mimic upstream PG's new ctid allocation ) -SELECT * FROM t +SELECT a FROM t -- YB: avoid ybsort column UNION ALL -SELECT * FROM t2 -ORDER BY a; +SELECT a FROM t2; -- YB: avoid ybsort column -SELECT * FROM y ORDER BY a; +SELECT a FROM y; -- YB: avoid ybsort column -- unconditional DO INSTEAD rule CREATE RULE y_rule AS ON DELETE TO y DO INSTEAD @@ -762,9 +1205,9 @@ CREATE RULE y_rule AS ON DELETE TO y DO INSTEAD WITH t AS ( DELETE FROM y RETURNING * ) -SELECT * FROM t; +SELECT a FROM t; -- YB: avoid ybsort column -SELECT * FROM y ORDER BY a; +SELECT a FROM y; -- YB: avoid ybsort column DROP RULE y_rule ON y; @@ -783,7 +1226,7 @@ CREATE TEMP TABLE bug6051_2 (i int); CREATE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD INSERT INTO bug6051_2 - SELECT NEW.i; + VALUES(NEW.i); WITH t1 AS ( DELETE FROM bug6051 RETURNING * ) INSERT INTO bug6051 SELECT * FROM t1; @@ -791,6 +1234,62 @@ INSERT INTO bug6051 SELECT * FROM t1; SELECT * FROM bug6051; SELECT * FROM bug6051_2; +-- 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; + +-- silly example to verify that hasModifyingCTE flag is propagated +CREATE TEMP TABLE bug6051_3 AS + SELECT a FROM generate_series(11,13) AS a; + +CREATE RULE bug6051_3_ins AS ON INSERT TO bug6051_3 DO INSTEAD + SELECT i FROM bug6051_2; + +BEGIN; SET LOCAL force_parallel_mode = on; + +WITH t1 AS ( DELETE FROM bug6051_3 RETURNING * ) + INSERT INTO bug6051_3 SELECT * FROM t1; + +COMMIT; + +SELECT * FROM bug6051_3; + +-- 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; + +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; + +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; + +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; + -- a truly recursive CTE in the same list WITH RECURSIVE t(a) AS ( SELECT 0 @@ -800,9 +1299,9 @@ WITH RECURSIVE t(a) AS ( INSERT INTO y SELECT * FROM t RETURNING * ) -SELECT * FROM t2 JOIN y USING (a) ORDER BY a; +SELECT a FROM t2 JOIN y USING (a) ORDER BY a; -- YB: avoid ybsort column -SELECT * FROM y ORDER BY a; +SELECT a FROM y; -- YB: avoid ybsort column -- data-modifying WITH in a modifying statement WITH t AS ( @@ -810,21 +1309,17 @@ WITH t AS ( WHERE a <= 10 RETURNING * ) +INSERT INTO y SELECT -a FROM t RETURNING a; -- YB: avoid ybsort column --- Removing Returning clause to make result deterministic. -INSERT INTO y SELECT -a FROM t; -SELECT * FROM y ORDER BY a; +SELECT a FROM y; -- YB: avoid ybsort column -- check that WITH query is run to completion even if outer query isn't WITH t AS ( UPDATE y SET a = a * 100 RETURNING * ) --- Disable test as results are not deterministic. -/* -SELECT * FROM t ORDER BY a LIMIT 10; -*/ -SELECT * FROM t ORDER BY a; -SELECT * FROM y ORDER BY a; +SELECT a FROM t LIMIT 10; -- YB: avoid ybsort column + +SELECT a FROM y; -- YB: avoid ybsort column -- 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; @@ -836,7 +1331,7 @@ WITH t AS ( 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; +SELECT k, v, a FROM t JOIN y ON t.k = y.a ORDER BY a, k; -- YB: avoid ybsort column -- Test EXCLUDED.* reference within CTE WITH aa AS ( @@ -869,7 +1364,7 @@ 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); -/* Disable the below test until #6782 is fixed +/* YB: Disable the below test until #6782 is fixed -- 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. @@ -885,35 +1380,54 @@ RETURNING k, v; */ 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); + +-- 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); -- YB: port more queries from the original test once this query is fixed + +DROP TABLE m; + -- check that run to completion happens in proper ordering TRUNCATE TABLE y; INSERT INTO y SELECT generate_series(1, 3); -CREATE TABLE yy (a INTEGER); +CREATE TABLE yy (a INTEGER, ybsort INT); -- YB: exercise YB table +ALTER TABLE y DROP CONSTRAINT y_pkey; -- YB: ybsort column needs to be non-unique for the next queries WITH RECURSIVE t1 AS ( - INSERT INTO y SELECT * FROM y RETURNING * + INSERT INTO y SELECT a FROM y ORDER BY y.ybsort RETURNING * -- YB: add ordering; avoid selecting ybsort column to auto-generate it during insert ), t2 AS ( INSERT INTO yy SELECT * FROM t1 RETURNING * ) SELECT 1; -SELECT * FROM y ORDER BY a; -SELECT * FROM yy ORDER BY a; +SELECT a FROM y ORDER BY ybsort; -- YB: avoid ybsort column; add ordering +SELECT a FROM yy ORDER BY ybsort; -- YB: avoid ybsort column; add ordering WITH RECURSIVE t1 AS ( INSERT INTO yy SELECT * FROM t2 RETURNING * ), t2 AS ( - INSERT INTO y SELECT * FROM y RETURNING * + INSERT INTO y SELECT a FROM y ORDER BY y.ybsort RETURNING * -- YB: add ordering; avoid selecting ybsort column to auto-generate it during insert ) SELECT 1; -SELECT * FROM y ORDER BY a; -SELECT * FROM yy ORDER BY a; +SELECT a FROM y ORDER BY ybsort; -- YB: avoid ybsort column; add ordering +SELECT a FROM yy ORDER BY ybsort; -- YB: avoid ybsort column; add ordering -- triggers TRUNCATE TABLE y; +ALTER TABLE y ADD PRIMARY KEY (ybsort ASC); -- YB: ybsort column can be unique again INSERT INTO y SELECT generate_series(1, 10); CREATE FUNCTION y_trigger() RETURNS trigger AS $$ @@ -934,9 +1448,9 @@ WITH t AS ( (23) RETURNING * ) -SELECT * FROM t ORDER BY a; +SELECT a FROM t; -- YB: avoid ybsort column -SELECT * FROM y ORDER BY a; +SELECT a FROM y; -- YB: avoid ybsort column DROP TRIGGER y_trig ON y; @@ -951,9 +1465,9 @@ WITH t AS ( (33) RETURNING * ) -SELECT * FROM t LIMIT 1; +SELECT a FROM t LIMIT 1; -- YB: avoid ybsort column -SELECT * FROM y ORDER BY a; +SELECT a FROM y; -- YB: avoid ybsort column DROP TRIGGER y_trig ON y; @@ -975,9 +1489,9 @@ WITH t AS ( (43) RETURNING * ) -SELECT * FROM t ORDER BY a; +SELECT a FROM t; -- YB: avoid ybsort column -SELECT * FROM y ORDER BY a; +SELECT a FROM y; -- YB: avoid ybsort column DROP TRIGGER y_trig ON y; DROP FUNCTION y_trigger(); @@ -986,7 +1500,7 @@ DROP FUNCTION y_trigger(); CREATE TEMP TABLE parent ( id int, val text ); CREATE TEMP TABLE child1 ( ) INHERITS ( parent ); -/* +/* YB CREATE TEMP TABLE child2 ( ) INHERITS ( parent ); INSERT INTO parent VALUES ( 1, 'p1' ); INSERT INTO child1 VALUES ( 11, 'c11' ),( 12, 'c12' ); @@ -997,6 +1511,7 @@ SELECT * FROM parent; WITH wcte AS ( INSERT INTO child1 VALUES ( 42, 'new' ) RETURNING id AS newid ) UPDATE parent SET id = id + newid FROM wcte; SELECT * FROM parent; +WITH rcte AS ( SELECT max(id) AS maxid FROM parent ) DELETE FROM parent USING rcte WHERE id = maxid; SELECT * FROM parent; WITH wcte AS ( INSERT INTO child2 VALUES ( 42, 'new2' ) RETURNING id AS newid ) @@ -1014,7 +1529,7 @@ WITH RECURSIVE t AS ( INSERT INTO y SELECT * FROM t ) -VALUES(FALSE); +VALUES(FALSE); -- YB: it's unclear why the line number in output is different from upstream. -- no RETURNING in a referenced data-modifying WITH WITH t AS ( @@ -1034,6 +1549,27 @@ WITH t AS ( INSERT INTO y VALUES(0) ) VALUES(FALSE); +CREATE OR REPLACE RULE y_rule AS ON INSERT TO y DO INSTEAD NOTHING; +WITH t AS ( + INSERT INTO y VALUES(0) +) +VALUES(FALSE); +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); +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); +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); DROP RULE y_rule ON y; -- check that parser lookahead for WITH doesn't cause any odd behavior @@ -1042,12 +1578,12 @@ create table foo (with ordinality); -- fail, WITH is a reserved word with ordinality as (select 1 as x) select * from ordinality; -- check sane response to attempt to modify CTE relation -WITH test AS (SELECT 42) INSERT INTO test VALUES (1); +WITH with_test AS (SELECT 42) INSERT INTO with_test VALUES (1); -- check response to attempt to modify table with same name as a CTE (perhaps -- surprisingly it works, because CTEs don't hide tables from data-modifying -- statements) -create temp table test (i int); -with test as (select 42) insert into test select * from test; -select * from test; -drop table test; +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; +drop table with_test;