diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/plpgsql.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/plpgsql.out --- /tmp/cirrus-ci-build/src/test/regress/expected/plpgsql.out 2024-09-22 06:28:18.737187137 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/plpgsql.out 2024-09-22 06:38:09.720217914 +0000 @@ -4021,1834 +4021,10 @@ raise; end; $$ language plpgsql; -select raise_test(); -ERROR: RAISE without parameters cannot be used outside an exception handler -CONTEXT: PL/pgSQL function raise_test() line 3 at RAISE --- test access to exception data -create function zero_divide() returns int as $$ -declare v int := 0; -begin - return 10 / v; -end; -$$ language plpgsql parallel safe; -create or replace function raise_test() returns void as $$ -begin - raise exception 'custom exception' - using detail = 'some detail of custom exception', - hint = 'some hint related to custom exception'; -end; -$$ language plpgsql; -create function stacked_diagnostics_test() returns void as $$ -declare _sqlstate text; - _message text; - _context text; -begin - perform zero_divide(); -exception when others then - get stacked diagnostics - _sqlstate = returned_sqlstate, - _message = message_text, - _context = pg_exception_context; - raise notice 'sqlstate: %, message: %, context: [%]', - _sqlstate, _message, replace(_context, E'\n', ' <- '); -end; -$$ language plpgsql; -select stacked_diagnostics_test(); -NOTICE: sqlstate: 22012, message: division by zero, context: [PL/pgSQL function zero_divide() line 4 at RETURN <- SQL statement "SELECT zero_divide()" <- PL/pgSQL function stacked_diagnostics_test() line 6 at PERFORM] - stacked_diagnostics_test --------------------------- - -(1 row) - -create or replace function stacked_diagnostics_test() returns void as $$ -declare _detail text; - _hint text; - _message text; -begin - perform raise_test(); -exception when others then - get stacked diagnostics - _message = message_text, - _detail = pg_exception_detail, - _hint = pg_exception_hint; - raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint; -end; -$$ language plpgsql; -select stacked_diagnostics_test(); -NOTICE: message: custom exception, detail: some detail of custom exception, hint: some hint related to custom exception - stacked_diagnostics_test --------------------------- - -(1 row) - --- fail, cannot use stacked diagnostics statement outside handler -create or replace function stacked_diagnostics_test() returns void as $$ -declare _detail text; - _hint text; - _message text; -begin - get stacked diagnostics - _message = message_text, - _detail = pg_exception_detail, - _hint = pg_exception_hint; - raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint; -end; -$$ language plpgsql; -select stacked_diagnostics_test(); -ERROR: GET STACKED DIAGNOSTICS cannot be used outside an exception handler -CONTEXT: PL/pgSQL function stacked_diagnostics_test() line 6 at GET STACKED DIAGNOSTICS -drop function stacked_diagnostics_test(); --- Test that an error recovery subtransaction is parallel safe -create function error_trap_test() returns text as $$ -begin - perform zero_divide(); - return 'no error detected!'; -exception when division_by_zero then - return 'division_by_zero detected'; -end; -$$ language plpgsql parallel safe; -set debug_parallel_query to on; -explain (verbose, costs off) select error_trap_test(); - QUERY PLAN ------------------------------------ - Gather - Output: (error_trap_test()) - Workers Planned: 1 - Single Copy: true - -> Result - Output: error_trap_test() -(6 rows) - -select error_trap_test(); - error_trap_test ---------------------------- - division_by_zero detected -(1 row) - -reset debug_parallel_query; -drop function error_trap_test(); -drop function zero_divide(); --- check cases where implicit SQLSTATE variable could be confused with --- SQLSTATE as a keyword, cf bug #5524 -create or replace function raise_test() returns void as $$ -begin - perform 1/0; -exception - when sqlstate '22012' then - raise notice using message = sqlstate; - raise sqlstate '22012' using message = 'substitute message'; -end; -$$ language plpgsql; -select raise_test(); -NOTICE: 22012 -ERROR: substitute message -CONTEXT: PL/pgSQL function raise_test() line 7 at RAISE -drop function raise_test(); --- test passing column_name, constraint_name, datatype_name, table_name --- and schema_name error fields -create or replace function stacked_diagnostics_test() returns void as $$ -declare _column_name text; - _constraint_name text; - _datatype_name text; - _table_name text; - _schema_name text; -begin - raise exception using - column = '>>some column name<<', - constraint = '>>some constraint name<<', - datatype = '>>some datatype name<<', - table = '>>some table name<<', - schema = '>>some schema name<<'; -exception when others then - get stacked diagnostics - _column_name = column_name, - _constraint_name = constraint_name, - _datatype_name = pg_datatype_name, - _table_name = table_name, - _schema_name = schema_name; - raise notice 'column %, constraint %, type %, table %, schema %', - _column_name, _constraint_name, _datatype_name, _table_name, _schema_name; -end; -$$ language plpgsql; -select stacked_diagnostics_test(); -NOTICE: column >>some column name<<, constraint >>some constraint name<<, type >>some datatype name<<, table >>some table name<<, schema >>some schema name<< - stacked_diagnostics_test --------------------------- - -(1 row) - -drop function stacked_diagnostics_test(); --- test variadic functions -create or replace function vari(variadic int[]) -returns void as $$ -begin - for i in array_lower($1,1)..array_upper($1,1) loop - raise notice '%', $1[i]; - end loop; end; -$$ language plpgsql; -select vari(1,2,3,4,5); -NOTICE: 1 -NOTICE: 2 -NOTICE: 3 -NOTICE: 4 -NOTICE: 5 - vari ------- - -(1 row) - -select vari(3,4,5); -NOTICE: 3 -NOTICE: 4 -NOTICE: 5 - vari ------- - -(1 row) - -select vari(variadic array[5,6,7]); -NOTICE: 5 -NOTICE: 6 -NOTICE: 7 - vari ------- - -(1 row) - -drop function vari(int[]); --- coercion test -create or replace function pleast(variadic numeric[]) -returns numeric as $$ -declare aux numeric = $1[array_lower($1,1)]; -begin - for i in array_lower($1,1)+1..array_upper($1,1) loop - if $1[i] < aux then aux := $1[i]; end if; - end loop; - return aux; -end; -$$ language plpgsql immutable strict; -select pleast(10,1,2,3,-16); - pleast --------- - -16 -(1 row) - -select pleast(10.2,2.2,-1.1); - pleast --------- - -1.1 -(1 row) - -select pleast(10.2,10, -20); - pleast --------- - -20 -(1 row) - -select pleast(10,20, -1.0); - pleast --------- - -1.0 -(1 row) - --- in case of conflict, non-variadic version is preferred -create or replace function pleast(numeric) -returns numeric as $$ -begin - raise notice 'non-variadic function called'; - return $1; -end; -$$ language plpgsql immutable strict; -select pleast(10); -NOTICE: non-variadic function called - pleast --------- - 10 -(1 row) - -drop function pleast(numeric[]); -drop function pleast(numeric); --- test table functions -create function tftest(int) returns table(a int, b int) as $$ -begin - return query select $1, $1+i from generate_series(1,5) g(i); -end; -$$ language plpgsql immutable strict; -select * from tftest(10); - a | b -----+---- - 10 | 11 - 10 | 12 - 10 | 13 - 10 | 14 - 10 | 15 -(5 rows) - -create or replace function tftest(a1 int) returns table(a int, b int) as $$ -begin - a := a1; b := a1 + 1; - return next; - a := a1 * 10; b := a1 * 10 + 1; - return next; -end; -$$ language plpgsql immutable strict; -select * from tftest(10); - a | b ------+----- - 10 | 11 - 100 | 101 -(2 rows) - -drop function tftest(int); -create function rttest() -returns setof int as $$ -declare rc int; -begin - return query values(10),(20); - get diagnostics rc = row_count; - raise notice '% %', found, rc; - return query select * from (values(10),(20)) f(a) where false; - get diagnostics rc = row_count; - raise notice '% %', found, rc; - return query execute 'values(10),(20)'; - get diagnostics rc = row_count; - raise notice '% %', found, rc; - return query execute 'select * from (values(10),(20)) f(a) where false'; - get diagnostics rc = row_count; - raise notice '% %', found, rc; -end; -$$ language plpgsql; -select * from rttest(); -NOTICE: t 2 -NOTICE: f 0 -NOTICE: t 2 -NOTICE: f 0 - rttest --------- - 10 - 20 - 10 - 20 -(4 rows) - --- check some error cases, too -create or replace function rttest() -returns setof int as $$ -begin - return query select 10 into no_such_table; -end; -$$ language plpgsql; -select * from rttest(); -ERROR: SELECT INTO query does not return tuples -CONTEXT: SQL statement "select 10 into no_such_table" -PL/pgSQL function rttest() line 3 at RETURN QUERY -create or replace function rttest() -returns setof int as $$ -begin - return query execute 'select 10 into no_such_table'; -end; -$$ language plpgsql; -select * from rttest(); -ERROR: SELECT INTO query does not return tuples -CONTEXT: SQL statement "select 10 into no_such_table" -PL/pgSQL function rttest() line 3 at RETURN QUERY -select * from no_such_table; -ERROR: relation "no_such_table" does not exist -LINE 1: select * from no_such_table; - ^ -drop function rttest(); --- Test for proper cleanup at subtransaction exit. This example --- exposed a bug in PG 8.2. -CREATE FUNCTION leaker_1(fail BOOL) RETURNS INTEGER AS $$ -DECLARE - v_var INTEGER; -BEGIN - BEGIN - v_var := (leaker_2(fail)).error_code; - EXCEPTION - WHEN others THEN RETURN 0; - END; - RETURN 1; -END; -$$ LANGUAGE plpgsql; -CREATE FUNCTION leaker_2(fail BOOL, OUT error_code INTEGER, OUT new_id INTEGER) - RETURNS RECORD AS $$ -BEGIN - IF fail THEN - RAISE EXCEPTION 'fail ...'; - END IF; - error_code := 1; - new_id := 1; - RETURN; -END; -$$ LANGUAGE plpgsql; -SELECT * FROM leaker_1(false); - leaker_1 ----------- - 1 -(1 row) - -SELECT * FROM leaker_1(true); - leaker_1 ----------- - 0 -(1 row) - -DROP FUNCTION leaker_1(bool); -DROP FUNCTION leaker_2(bool); --- Test for appropriate cleanup of non-simple expression evaluations --- (bug in all versions prior to August 2010) -CREATE FUNCTION nonsimple_expr_test() RETURNS text[] AS $$ -DECLARE - arr text[]; - lr text; - i integer; -BEGIN - arr := array[array['foo','bar'], array['baz', 'quux']]; - lr := 'fool'; - i := 1; - -- use sub-SELECTs to make expressions non-simple - arr[(SELECT i)][(SELECT i+1)] := (SELECT lr); - RETURN arr; -END; -$$ LANGUAGE plpgsql; -SELECT nonsimple_expr_test(); - nonsimple_expr_test -------------------------- - {{foo,fool},{baz,quux}} -(1 row) - -DROP FUNCTION nonsimple_expr_test(); -CREATE FUNCTION nonsimple_expr_test() RETURNS integer AS $$ -declare - i integer NOT NULL := 0; -begin - begin - i := (SELECT NULL::integer); -- should throw error - exception - WHEN OTHERS THEN - i := (SELECT 1::integer); - end; - return i; -end; -$$ LANGUAGE plpgsql; -SELECT nonsimple_expr_test(); - nonsimple_expr_test ---------------------- - 1 -(1 row) - -DROP FUNCTION nonsimple_expr_test(); --- --- Test cases involving recursion and error recovery in simple expressions --- (bugs in all versions before October 2010). The problems are most --- easily exposed by mutual recursion between plpgsql and sql functions. --- -create function recurse(float8) returns float8 as -$$ -begin - if ($1 > 0) then - return sql_recurse($1 - 1); - else - return $1; - end if; -end; -$$ language plpgsql; --- "limit" is to prevent this from being inlined -create function sql_recurse(float8) returns float8 as -$$ select recurse($1) limit 1; $$ language sql; -select recurse(10); - recurse ---------- - 0 -(1 row) - -create function error1(text) returns text language sql as -$$ SELECT relname::text FROM pg_class c WHERE c.oid = $1::regclass $$; -create function error2(p_name_table text) returns text language plpgsql as $$ -begin - return error1(p_name_table); -end$$; -BEGIN; -create table public.stuffs (stuff text); -SAVEPOINT a; -select error2('nonexistent.stuffs'); -ERROR: schema "nonexistent" does not exist -CONTEXT: SQL function "error1" statement 1 -PL/pgSQL function error2(text) line 3 at RETURN -ROLLBACK TO a; -select error2('public.stuffs'); - error2 --------- - stuffs -(1 row) - -rollback; -drop function error2(p_name_table text); -drop function error1(text); --- Test for proper handling of cast-expression caching -create function sql_to_date(integer) returns date as $$ -select $1::text::date -$$ language sql immutable strict; -create cast (integer as date) with function sql_to_date(integer) as assignment; -create function cast_invoker(integer) returns date as $$ -begin - return $1; -end$$ language plpgsql; -select cast_invoker(20150717); - cast_invoker --------------- - 07-17-2015 -(1 row) - -select cast_invoker(20150718); -- second call crashed in pre-release 9.5 - cast_invoker --------------- - 07-18-2015 -(1 row) - -begin; -select cast_invoker(20150717); - cast_invoker --------------- - 07-17-2015 -(1 row) - -select cast_invoker(20150718); - cast_invoker --------------- - 07-18-2015 -(1 row) - -savepoint s1; -select cast_invoker(20150718); - cast_invoker --------------- - 07-18-2015 -(1 row) - -select cast_invoker(-1); -- fails -ERROR: invalid input syntax for type date: "-1" -CONTEXT: SQL function "sql_to_date" statement 1 -PL/pgSQL function cast_invoker(integer) while casting return value to function's return type -rollback to savepoint s1; -select cast_invoker(20150719); - cast_invoker --------------- - 07-19-2015 -(1 row) - -select cast_invoker(20150720); - cast_invoker --------------- - 07-20-2015 -(1 row) - -commit; -drop function cast_invoker(integer); -drop function sql_to_date(integer) cascade; -NOTICE: drop cascades to cast from integer to date --- Test handling of cast cache inside DO blocks --- (to check the original crash case, this must be a cast not previously --- used in this session) -begin; -do $$ declare x text[]; begin x := '{1.23, 4.56}'::numeric[]; end $$; -do $$ declare x text[]; begin x := '{1.23, 4.56}'::numeric[]; end $$; -end; --- Test for consistent reporting of error context -create function fail() returns int language plpgsql as $$ -begin - return 1/0; -end -$$; -select fail(); -ERROR: division by zero -CONTEXT: PL/pgSQL expression "1/0" -PL/pgSQL function fail() line 3 at RETURN -select fail(); -ERROR: division by zero -CONTEXT: PL/pgSQL expression "1/0" -PL/pgSQL function fail() line 3 at RETURN -drop function fail(); --- Test handling of string literals. -set standard_conforming_strings = off; -create or replace function strtest() returns text as $$ -begin - raise notice 'foo\\bar\041baz'; - return 'foo\\bar\041baz'; -end -$$ language plpgsql; -WARNING: nonstandard use of \\ in a string literal -LINE 3: raise notice 'foo\\bar\041baz'; - ^ -HINT: Use the escape string syntax for backslashes, e.g., E'\\'. -WARNING: nonstandard use of \\ in a string literal -LINE 4: return 'foo\\bar\041baz'; - ^ -HINT: Use the escape string syntax for backslashes, e.g., E'\\'. -WARNING: nonstandard use of \\ in a string literal -LINE 4: return 'foo\\bar\041baz'; - ^ -HINT: Use the escape string syntax for backslashes, e.g., E'\\'. -select strtest(); -NOTICE: foo\bar!baz -WARNING: nonstandard use of \\ in a string literal -LINE 1: 'foo\\bar\041baz' - ^ -HINT: Use the escape string syntax for backslashes, e.g., E'\\'. -QUERY: 'foo\\bar\041baz' - strtest -------------- - foo\bar!baz -(1 row) - -create or replace function strtest() returns text as $$ -begin - raise notice E'foo\\bar\041baz'; - return E'foo\\bar\041baz'; -end -$$ language plpgsql; -select strtest(); -NOTICE: foo\bar!baz - strtest -------------- - foo\bar!baz -(1 row) - -set standard_conforming_strings = on; -create or replace function strtest() returns text as $$ -begin - raise notice 'foo\\bar\041baz\'; - return 'foo\\bar\041baz\'; -end -$$ language plpgsql; -select strtest(); -NOTICE: foo\\bar\041baz\ - strtest ------------------- - foo\\bar\041baz\ -(1 row) - -create or replace function strtest() returns text as $$ -begin - raise notice E'foo\\bar\041baz'; - return E'foo\\bar\041baz'; -end -$$ language plpgsql; -select strtest(); -NOTICE: foo\bar!baz - strtest -------------- - foo\bar!baz -(1 row) - -drop function strtest(); --- Test anonymous code blocks. -DO $$ -DECLARE r record; -BEGIN - FOR r IN SELECT rtrim(roomno) AS roomno, comment FROM Room ORDER BY roomno - LOOP - RAISE NOTICE '%, %', r.roomno, r.comment; - END LOOP; -END$$; -NOTICE: 001, Entrance -NOTICE: 002, Office -NOTICE: 003, Office -NOTICE: 004, Technical -NOTICE: 101, Office -NOTICE: 102, Conference -NOTICE: 103, Restroom -NOTICE: 104, Technical -NOTICE: 105, Office -NOTICE: 106, Office --- these are to check syntax error reporting -DO LANGUAGE plpgsql $$begin return 1; end$$; -ERROR: RETURN cannot have a parameter in function returning void -LINE 1: DO LANGUAGE plpgsql $$begin return 1; end$$; - ^ -DO $$ -DECLARE r record; -BEGIN - FOR r IN SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomno - LOOP - RAISE NOTICE '%, %', r.roomno, r.comment; - END LOOP; -END$$; -ERROR: column or variable "foo" does not exist -LINE 1: SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomn... - ^ -QUERY: SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomno -CONTEXT: PL/pgSQL function inline_code_block line 4 at FOR over SELECT rows --- Check handling of errors thrown from/into anonymous code blocks. -do $outer$ -begin - for i in 1..10 loop - begin - execute $ex$ - do $$ - declare x int = 0; - begin - x := 1 / x; - end; - $$; - $ex$; - exception when division_by_zero then - raise notice 'caught division by zero'; - end; - end loop; -end; -$outer$; -NOTICE: caught division by zero -NOTICE: caught division by zero -NOTICE: caught division by zero -NOTICE: caught division by zero -NOTICE: caught division by zero -NOTICE: caught division by zero -NOTICE: caught division by zero -NOTICE: caught division by zero -NOTICE: caught division by zero -NOTICE: caught division by zero --- Check variable scoping -- a var is not available in its own or prior --- default expressions, but it is available in later ones. -do $$ -declare x int := x + 1; -- error -begin - raise notice 'x = %', x; -end; -$$; -ERROR: column or variable "x" does not exist -LINE 1: x + 1 - ^ -QUERY: x + 1 -CONTEXT: PL/pgSQL function inline_code_block line 2 during statement block local variable initialization -do $$ -declare y int := x + 1; -- error - x int := 42; -begin - raise notice 'x = %, y = %', x, y; -end; -$$; -ERROR: column or variable "x" does not exist -LINE 1: x + 1 - ^ -QUERY: x + 1 -CONTEXT: PL/pgSQL function inline_code_block line 2 during statement block local variable initialization -do $$ -declare x int := 42; - y int := x + 1; -begin - raise notice 'x = %, y = %', x, y; -end; -$$; -NOTICE: x = 42, y = 43 -do $$ -declare x int := 42; -begin - declare y int := x + 1; - x int := x + 2; - z int := x * 10; - begin - raise notice 'x = %, y = %, z = %', x, y, z; - end; -end; -$$; -NOTICE: x = 44, y = 43, z = 440 --- Check handling of conflicts between plpgsql vars and table columns. -set plpgsql.variable_conflict = error; -create function conflict_test() returns setof int8_tbl as $$ -declare r record; - q1 bigint := 42; -begin - for r in select q1,q2 from int8_tbl loop - return next r; - end loop; -end; -$$ language plpgsql; -select * from conflict_test(); -ERROR: column reference "q1" is ambiguous -LINE 1: select q1,q2 from int8_tbl - ^ -DETAIL: It could refer to either a PL/pgSQL variable or a table column. -QUERY: select q1,q2 from int8_tbl -CONTEXT: PL/pgSQL function conflict_test() line 5 at FOR over SELECT rows -create or replace function conflict_test() returns setof int8_tbl as $$ -#variable_conflict use_variable -declare r record; - q1 bigint := 42; -begin - for r in select q1,q2 from int8_tbl loop - return next r; - end loop; -end; -$$ language plpgsql; -select * from conflict_test(); - q1 | q2 -----+------------------- - 42 | 456 - 42 | 4567890123456789 - 42 | 123 - 42 | 4567890123456789 - 42 | -4567890123456789 -(5 rows) - -create or replace function conflict_test() returns setof int8_tbl as $$ -#variable_conflict use_column -declare r record; - q1 bigint := 42; -begin - for r in select q1,q2 from int8_tbl loop - return next r; - end loop; -end; -$$ language plpgsql; -select * from conflict_test(); - q1 | q2 -------------------+------------------- - 123 | 456 - 123 | 4567890123456789 - 4567890123456789 | 123 - 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 -(5 rows) - -drop function conflict_test(); --- Check that an unreserved keyword can be used as a variable name -create function unreserved_test() returns int as $$ -declare - forward int := 21; -begin - forward := forward * 2; - return forward; -end -$$ language plpgsql; -select unreserved_test(); - unreserved_test ------------------ - 42 -(1 row) - -create or replace function unreserved_test() returns int as $$ -declare - return int := 42; -begin - return := return + 1; - return return; -end -$$ language plpgsql; -select unreserved_test(); - unreserved_test ------------------ - 43 -(1 row) - -create or replace function unreserved_test() returns int as $$ -declare - comment int := 21; -begin - comment := comment * 2; - comment on function unreserved_test() is 'this is a test'; - return comment; -end -$$ language plpgsql; -select unreserved_test(); - unreserved_test ------------------ - 42 -(1 row) - -select obj_description('unreserved_test()'::regprocedure, 'pg_proc'); - obj_description ------------------ - this is a test -(1 row) - -drop function unreserved_test(); --- --- Test FOREACH over arrays --- -create function foreach_test(anyarray) -returns void as $$ -declare x int; -begin - foreach x in array $1 - loop - raise notice '%', x; - end loop; - end; -$$ language plpgsql; -select foreach_test(ARRAY[1,2,3,4]); -NOTICE: 1 -NOTICE: 2 -NOTICE: 3 -NOTICE: 4 - foreach_test --------------- - -(1 row) - -select foreach_test(ARRAY[[1,2],[3,4]]); -NOTICE: 1 -NOTICE: 2 -NOTICE: 3 -NOTICE: 4 - foreach_test --------------- - -(1 row) - -create or replace function foreach_test(anyarray) -returns void as $$ -declare x int; -begin - foreach x slice 1 in array $1 - loop - raise notice '%', x; - end loop; - end; -$$ language plpgsql; --- should fail -select foreach_test(ARRAY[1,2,3,4]); -ERROR: FOREACH ... SLICE loop variable must be of an array type -CONTEXT: PL/pgSQL function foreach_test(anyarray) line 4 at FOREACH over array -select foreach_test(ARRAY[[1,2],[3,4]]); -ERROR: FOREACH ... SLICE loop variable must be of an array type -CONTEXT: PL/pgSQL function foreach_test(anyarray) line 4 at FOREACH over array -create or replace function foreach_test(anyarray) -returns void as $$ -declare x int[]; -begin - foreach x slice 1 in array $1 - loop - raise notice '%', x; - end loop; - end; -$$ language plpgsql; -select foreach_test(ARRAY[1,2,3,4]); -NOTICE: {1,2,3,4} - foreach_test --------------- - -(1 row) - -select foreach_test(ARRAY[[1,2],[3,4]]); -NOTICE: {1,2} -NOTICE: {3,4} - foreach_test --------------- - -(1 row) - --- higher level of slicing -create or replace function foreach_test(anyarray) -returns void as $$ -declare x int[]; -begin - foreach x slice 2 in array $1 - loop - raise notice '%', x; - end loop; - end; -$$ language plpgsql; --- should fail -select foreach_test(ARRAY[1,2,3,4]); -ERROR: slice dimension (2) is out of the valid range 0..1 -CONTEXT: PL/pgSQL function foreach_test(anyarray) line 4 at FOREACH over array --- ok -select foreach_test(ARRAY[[1,2],[3,4]]); -NOTICE: {{1,2},{3,4}} - foreach_test --------------- - -(1 row) - -select foreach_test(ARRAY[[[1,2]],[[3,4]]]); -NOTICE: {{1,2}} -NOTICE: {{3,4}} - foreach_test --------------- - -(1 row) - -create type xy_tuple AS (x int, y int); --- iteration over array of records -create or replace function foreach_test(anyarray) -returns void as $$ -declare r record; -begin - foreach r in array $1 - loop - raise notice '%', r; - end loop; - end; -$$ language plpgsql; -select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]); -NOTICE: (10,20) -NOTICE: (40,69) -NOTICE: (35,78) - foreach_test --------------- - -(1 row) - -select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]); -NOTICE: (10,20) -NOTICE: (40,69) -NOTICE: (35,78) -NOTICE: (88,76) - foreach_test --------------- - -(1 row) - -create or replace function foreach_test(anyarray) -returns void as $$ -declare x int; y int; -begin - foreach x, y in array $1 - loop - raise notice 'x = %, y = %', x, y; - end loop; - end; -$$ language plpgsql; -select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]); -NOTICE: x = 10, y = 20 -NOTICE: x = 40, y = 69 -NOTICE: x = 35, y = 78 - foreach_test --------------- - -(1 row) - -select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]); -NOTICE: x = 10, y = 20 -NOTICE: x = 40, y = 69 -NOTICE: x = 35, y = 78 -NOTICE: x = 88, y = 76 - foreach_test --------------- - -(1 row) - --- slicing over array of composite types -create or replace function foreach_test(anyarray) -returns void as $$ -declare x xy_tuple[]; -begin - foreach x slice 1 in array $1 - loop - raise notice '%', x; - end loop; - end; -$$ language plpgsql; -select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]); -NOTICE: {"(10,20)","(40,69)","(35,78)"} - foreach_test --------------- - -(1 row) - -select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]); -NOTICE: {"(10,20)","(40,69)"} -NOTICE: {"(35,78)","(88,76)"} - foreach_test --------------- - -(1 row) - -drop function foreach_test(anyarray); -drop type xy_tuple; --- --- Assorted tests for array subscript assignment --- -create temp table rtype (id int, ar text[]); -create function arrayassign1() returns text[] language plpgsql as $$ -declare - r record; -begin - r := row(12, '{foo,bar,baz}')::rtype; - r.ar[2] := 'replace'; - return r.ar; -end$$; -select arrayassign1(); - arrayassign1 -------------------- - {foo,replace,baz} -(1 row) - -select arrayassign1(); -- try again to exercise internal caching - arrayassign1 -------------------- - {foo,replace,baz} -(1 row) - -create domain orderedarray as int[2] - constraint sorted check (value[1] < value[2]); -select '{1,2}'::orderedarray; - orderedarray --------------- - {1,2} -(1 row) - -select '{2,1}'::orderedarray; -- fail -ERROR: value for domain orderedarray violates check constraint "sorted" -create function testoa(x1 int, x2 int, x3 int) returns orderedarray -language plpgsql as $$ -declare res orderedarray; -begin - res := array[x1, x2]; - res[2] := x3; - return res; -end$$; -select testoa(1,2,3); - testoa --------- - {1,3} -(1 row) - -select testoa(1,2,3); -- try again to exercise internal caching - testoa --------- - {1,3} -(1 row) - -select testoa(2,1,3); -- fail at initial assign -ERROR: value for domain orderedarray violates check constraint "sorted" -CONTEXT: PL/pgSQL function testoa(integer,integer,integer) line 4 at assignment -select testoa(1,2,1); -- fail at update -ERROR: value for domain orderedarray violates check constraint "sorted" -CONTEXT: PL/pgSQL function testoa(integer,integer,integer) line 5 at assignment -drop function arrayassign1(); -drop function testoa(x1 int, x2 int, x3 int); --- --- Test handling of expanded arrays --- -create function returns_rw_array(int) returns int[] -language plpgsql as $$ - declare r int[]; - begin r := array[$1, $1]; return r; end; -$$ stable; -create function consumes_rw_array(int[]) returns int -language plpgsql as $$ - begin return $1[1]; end; -$$ stable; -select consumes_rw_array(returns_rw_array(42)); - consumes_rw_array -------------------- - 42 -(1 row) - --- bug #14174 -explain (verbose, costs off) -select i, a from - (select returns_rw_array(1) as a offset 0) ss, - lateral consumes_rw_array(a) i; - QUERY PLAN ------------------------------------------------------------------ - Nested Loop - Output: i.i, (returns_rw_array(1)) - -> Result - Output: returns_rw_array(1) - -> Function Scan on public.consumes_rw_array i - Output: i.i - Function Call: consumes_rw_array((returns_rw_array(1))) -(7 rows) - -select i, a from - (select returns_rw_array(1) as a offset 0) ss, - lateral consumes_rw_array(a) i; - i | a ----+------- - 1 | {1,1} -(1 row) - -explain (verbose, costs off) -select consumes_rw_array(a), a from returns_rw_array(1) a; - QUERY PLAN --------------------------------------------- - Function Scan on public.returns_rw_array a - Output: consumes_rw_array(a), a - Function Call: returns_rw_array(1) -(3 rows) - -select consumes_rw_array(a), a from returns_rw_array(1) a; - consumes_rw_array | a --------------------+------- - 1 | {1,1} -(1 row) - -explain (verbose, costs off) -select consumes_rw_array(a), a from - (values (returns_rw_array(1)), (returns_rw_array(2))) v(a); - QUERY PLAN ---------------------------------------------------------------------- - Values Scan on "*VALUES*" - Output: consumes_rw_array("*VALUES*".column1), "*VALUES*".column1 -(2 rows) - -select consumes_rw_array(a), a from - (values (returns_rw_array(1)), (returns_rw_array(2))) v(a); - consumes_rw_array | a --------------------+------- - 1 | {1,1} - 2 | {2,2} -(2 rows) - -do $$ -declare a int[] := array[1,2]; -begin - a := a || 3; - raise notice 'a = %', a; -end$$; -NOTICE: a = {1,2,3} --- --- Test access to call stack --- -create function inner_func(int) -returns int as $$ -declare _context text; -begin - get diagnostics _context = pg_context; - raise notice '***%***', _context; - -- lets do it again, just for fun.. - get diagnostics _context = pg_context; - raise notice '***%***', _context; - raise notice 'lets make sure we didnt break anything'; - return 2 * $1; -end; -$$ language plpgsql; -create or replace function outer_func(int) -returns int as $$ -declare - myresult int; -begin - raise notice 'calling down into inner_func()'; - myresult := inner_func($1); - raise notice 'inner_func() done'; - return myresult; -end; -$$ language plpgsql; -create or replace function outer_outer_func(int) -returns int as $$ -declare - myresult int; -begin - raise notice 'calling down into outer_func()'; - myresult := outer_func($1); - raise notice 'outer_func() done'; - return myresult; -end; -$$ language plpgsql; -select outer_outer_func(10); -NOTICE: calling down into outer_func() -NOTICE: calling down into inner_func() -NOTICE: ***PL/pgSQL function inner_func(integer) line 4 at GET DIAGNOSTICS -PL/pgSQL function outer_func(integer) line 6 at assignment -PL/pgSQL function outer_outer_func(integer) line 6 at assignment*** -NOTICE: ***PL/pgSQL function inner_func(integer) line 7 at GET DIAGNOSTICS -PL/pgSQL function outer_func(integer) line 6 at assignment -PL/pgSQL function outer_outer_func(integer) line 6 at assignment*** -NOTICE: lets make sure we didnt break anything -NOTICE: inner_func() done -NOTICE: outer_func() done - outer_outer_func ------------------- - 20 -(1 row) - --- repeated call should work -select outer_outer_func(20); -NOTICE: calling down into outer_func() -NOTICE: calling down into inner_func() -NOTICE: ***PL/pgSQL function inner_func(integer) line 4 at GET DIAGNOSTICS -PL/pgSQL function outer_func(integer) line 6 at assignment -PL/pgSQL function outer_outer_func(integer) line 6 at assignment*** -NOTICE: ***PL/pgSQL function inner_func(integer) line 7 at GET DIAGNOSTICS -PL/pgSQL function outer_func(integer) line 6 at assignment -PL/pgSQL function outer_outer_func(integer) line 6 at assignment*** -NOTICE: lets make sure we didnt break anything -NOTICE: inner_func() done -NOTICE: outer_func() done - outer_outer_func ------------------- - 40 -(1 row) - -drop function outer_outer_func(int); -drop function outer_func(int); -drop function inner_func(int); --- access to call stack from exception -create function inner_func(int) -returns int as $$ -declare - _context text; - sx int := 5; -begin - begin - perform sx / 0; - exception - when division_by_zero then - get diagnostics _context = pg_context; - raise notice '***%***', _context; - end; - - -- lets do it again, just for fun.. - get diagnostics _context = pg_context; - raise notice '***%***', _context; - raise notice 'lets make sure we didnt break anything'; - return 2 * $1; -end; -$$ language plpgsql; -create or replace function outer_func(int) -returns int as $$ -declare - myresult int; -begin - raise notice 'calling down into inner_func()'; - myresult := inner_func($1); - raise notice 'inner_func() done'; - return myresult; -end; -$$ language plpgsql; -create or replace function outer_outer_func(int) -returns int as $$ -declare - myresult int; -begin - raise notice 'calling down into outer_func()'; - myresult := outer_func($1); - raise notice 'outer_func() done'; - return myresult; -end; -$$ language plpgsql; -select outer_outer_func(10); -NOTICE: calling down into outer_func() -NOTICE: calling down into inner_func() -NOTICE: ***PL/pgSQL function inner_func(integer) line 10 at GET DIAGNOSTICS -PL/pgSQL function outer_func(integer) line 6 at assignment -PL/pgSQL function outer_outer_func(integer) line 6 at assignment*** -NOTICE: ***PL/pgSQL function inner_func(integer) line 15 at GET DIAGNOSTICS -PL/pgSQL function outer_func(integer) line 6 at assignment -PL/pgSQL function outer_outer_func(integer) line 6 at assignment*** -NOTICE: lets make sure we didnt break anything -NOTICE: inner_func() done -NOTICE: outer_func() done - outer_outer_func ------------------- - 20 -(1 row) - --- repeated call should work -select outer_outer_func(20); -NOTICE: calling down into outer_func() -NOTICE: calling down into inner_func() -NOTICE: ***PL/pgSQL function inner_func(integer) line 10 at GET DIAGNOSTICS -PL/pgSQL function outer_func(integer) line 6 at assignment -PL/pgSQL function outer_outer_func(integer) line 6 at assignment*** -NOTICE: ***PL/pgSQL function inner_func(integer) line 15 at GET DIAGNOSTICS -PL/pgSQL function outer_func(integer) line 6 at assignment -PL/pgSQL function outer_outer_func(integer) line 6 at assignment*** -NOTICE: lets make sure we didnt break anything -NOTICE: inner_func() done -NOTICE: outer_func() done - outer_outer_func ------------------- - 40 -(1 row) - -drop function outer_outer_func(int); -drop function outer_func(int); -drop function inner_func(int); --- Test pg_routine_oid -create function current_function(text) -returns regprocedure as $$ -declare - fn_oid regprocedure; -begin - get diagnostics fn_oid = pg_routine_oid; - return fn_oid; -end; -$$ language plpgsql; -select current_function('foo'); - current_function ------------------------- - current_function(text) -(1 row) - -drop function current_function(text); --- shouldn't fail in DO, even though there's no useful data -do $$ -declare - fn_oid oid; -begin - get diagnostics fn_oid = pg_routine_oid; - raise notice 'pg_routine_oid = %', fn_oid; -end; -$$; -NOTICE: pg_routine_oid = 0 --- --- Test ASSERT --- -do $$ -begin - assert 1=1; -- should succeed -end; -$$; -do $$ -begin - assert 1=0; -- should fail -end; -$$; -ERROR: assertion failed -CONTEXT: PL/pgSQL function inline_code_block line 3 at ASSERT -do $$ -begin - assert NULL; -- should fail -end; -$$; -ERROR: assertion failed -CONTEXT: PL/pgSQL function inline_code_block line 3 at ASSERT --- check controlling GUC -set plpgsql.check_asserts = off; -do $$ -begin - assert 1=0; -- won't be tested -end; -$$; -reset plpgsql.check_asserts; --- test custom message -do $$ -declare var text := 'some value'; -begin - assert 1=0, format('assertion failed, var = "%s"', var); -end; -$$; -ERROR: assertion failed, var = "some value" -CONTEXT: PL/pgSQL function inline_code_block line 4 at ASSERT --- ensure assertions are not trapped by 'others' -do $$ -begin - assert 1=0, 'unhandled assertion'; -exception when others then - null; -- do nothing -end; -$$; -ERROR: unhandled assertion -CONTEXT: PL/pgSQL function inline_code_block line 3 at ASSERT --- Test use of plpgsql in a domain check constraint (cf. bug #14414) -create function plpgsql_domain_check(val int) returns boolean as $$ -begin return val > 0; end -$$ language plpgsql immutable; -create domain plpgsql_domain as integer check(plpgsql_domain_check(value)); -do $$ -declare v_test plpgsql_domain; -begin - v_test := 1; -end; -$$; -do $$ -declare v_test plpgsql_domain := 1; -begin - v_test := 0; -- fail -end; -$$; -ERROR: value for domain plpgsql_domain violates check constraint "plpgsql_domain_check" -CONTEXT: PL/pgSQL function inline_code_block line 4 at assignment --- Test handling of expanded array passed to a domain constraint (bug #14472) -create function plpgsql_arr_domain_check(val int[]) returns boolean as $$ -begin return val[1] > 0; end -$$ language plpgsql immutable; -create domain plpgsql_arr_domain as int[] check(plpgsql_arr_domain_check(value)); -do $$ -declare v_test plpgsql_arr_domain; -begin - v_test := array[1]; - v_test := v_test || 2; -end; -$$; -do $$ -declare v_test plpgsql_arr_domain := array[1]; -begin - v_test := 0 || v_test; -- fail -end; -$$; -ERROR: value for domain plpgsql_arr_domain violates check constraint "plpgsql_arr_domain_check" -CONTEXT: PL/pgSQL function inline_code_block line 4 at assignment --- --- test usage of transition tables in AFTER triggers --- -CREATE TABLE transition_table_base (id int PRIMARY KEY, val text); -CREATE FUNCTION transition_table_base_ins_func() - RETURNS trigger - LANGUAGE plpgsql -AS $$ -DECLARE - t text; - l text; -BEGIN - t = ''; - FOR l IN EXECUTE - $q$ - EXPLAIN (TIMING off, COSTS off, VERBOSE on) - SELECT * FROM newtable - $q$ LOOP - t = t || l || E'\n'; - END LOOP; - - RAISE INFO '%', t; - RETURN new; -END; -$$; -CREATE TRIGGER transition_table_base_ins_trig - AFTER INSERT ON transition_table_base - REFERENCING OLD TABLE AS oldtable NEW TABLE AS newtable - FOR EACH STATEMENT - EXECUTE PROCEDURE transition_table_base_ins_func(); -ERROR: OLD TABLE can only be specified for a DELETE or UPDATE trigger -CREATE TRIGGER transition_table_base_ins_trig - AFTER INSERT ON transition_table_base - REFERENCING NEW TABLE AS newtable - FOR EACH STATEMENT - EXECUTE PROCEDURE transition_table_base_ins_func(); -INSERT INTO transition_table_base VALUES (1, 'One'), (2, 'Two'); -INFO: Named Tuplestore Scan - Output: id, val - -INSERT INTO transition_table_base VALUES (3, 'Three'), (4, 'Four'); -INFO: Named Tuplestore Scan - Output: id, val - -CREATE OR REPLACE FUNCTION transition_table_base_upd_func() - RETURNS trigger - LANGUAGE plpgsql -AS $$ -DECLARE - t text; - l text; -BEGIN - t = ''; - FOR l IN EXECUTE - $q$ - EXPLAIN (TIMING off, COSTS off, VERBOSE on) - SELECT * FROM oldtable ot FULL JOIN newtable nt USING (id) - $q$ LOOP - t = t || l || E'\n'; - END LOOP; - - RAISE INFO '%', t; - RETURN new; -END; -$$; -CREATE TRIGGER transition_table_base_upd_trig - AFTER UPDATE ON transition_table_base - REFERENCING OLD TABLE AS oldtable NEW TABLE AS newtable - FOR EACH STATEMENT - EXECUTE PROCEDURE transition_table_base_upd_func(); -UPDATE transition_table_base - SET val = '*' || val || '*' - WHERE id BETWEEN 2 AND 3; -INFO: Hash Full Join - Output: COALESCE(ot.id, nt.id), ot.val, nt.val - Hash Cond: (ot.id = nt.id) - -> Named Tuplestore Scan - Output: ot.id, ot.val - -> Hash - Output: nt.id, nt.val - -> Named Tuplestore Scan - Output: nt.id, nt.val - -CREATE TABLE transition_table_level1 -( - level1_no serial NOT NULL , - level1_node_name varchar(255), - PRIMARY KEY (level1_no) -) WITHOUT OIDS; -CREATE TABLE transition_table_level2 -( - level2_no serial NOT NULL , - parent_no int NOT NULL, - level1_node_name varchar(255), - PRIMARY KEY (level2_no) -) WITHOUT OIDS; -CREATE TABLE transition_table_status -( - level int NOT NULL, - node_no int NOT NULL, - status int, - PRIMARY KEY (level, node_no) -) WITHOUT OIDS; -CREATE FUNCTION transition_table_level1_ri_parent_del_func() - RETURNS TRIGGER - LANGUAGE plpgsql -AS $$ - DECLARE n bigint; - BEGIN - PERFORM FROM p JOIN transition_table_level2 c ON c.parent_no = p.level1_no; - IF FOUND THEN - RAISE EXCEPTION 'RI error'; - END IF; - RETURN NULL; - END; -$$; -CREATE TRIGGER transition_table_level1_ri_parent_del_trigger - AFTER DELETE ON transition_table_level1 - REFERENCING OLD TABLE AS p - FOR EACH STATEMENT EXECUTE PROCEDURE - transition_table_level1_ri_parent_del_func(); -CREATE FUNCTION transition_table_level1_ri_parent_upd_func() - RETURNS TRIGGER - LANGUAGE plpgsql -AS $$ - DECLARE - x int; - BEGIN - WITH p AS (SELECT level1_no, sum(delta) cnt - FROM (SELECT level1_no, 1 AS delta FROM i - UNION ALL - SELECT level1_no, -1 AS delta FROM d) w - GROUP BY level1_no - HAVING sum(delta) < 0) - SELECT level1_no - FROM p JOIN transition_table_level2 c ON c.parent_no = p.level1_no - INTO x; - IF FOUND THEN - RAISE EXCEPTION 'RI error'; - END IF; - RETURN NULL; - END; -$$; -CREATE TRIGGER transition_table_level1_ri_parent_upd_trigger - AFTER UPDATE ON transition_table_level1 - REFERENCING OLD TABLE AS d NEW TABLE AS i - FOR EACH STATEMENT EXECUTE PROCEDURE - transition_table_level1_ri_parent_upd_func(); -CREATE FUNCTION transition_table_level2_ri_child_insupd_func() - RETURNS TRIGGER - LANGUAGE plpgsql -AS $$ - BEGIN - PERFORM FROM i - LEFT JOIN transition_table_level1 p - ON p.level1_no IS NOT NULL AND p.level1_no = i.parent_no - WHERE p.level1_no IS NULL; - IF FOUND THEN - RAISE EXCEPTION 'RI error'; - END IF; - RETURN NULL; - END; -$$; -CREATE TRIGGER transition_table_level2_ri_child_ins_trigger - AFTER INSERT ON transition_table_level2 - REFERENCING NEW TABLE AS i - FOR EACH STATEMENT EXECUTE PROCEDURE - transition_table_level2_ri_child_insupd_func(); -CREATE TRIGGER transition_table_level2_ri_child_upd_trigger - AFTER UPDATE ON transition_table_level2 - REFERENCING NEW TABLE AS i - FOR EACH STATEMENT EXECUTE PROCEDURE - transition_table_level2_ri_child_insupd_func(); --- create initial test data -INSERT INTO transition_table_level1 (level1_no) - SELECT generate_series(1,200); -ANALYZE transition_table_level1; -INSERT INTO transition_table_level2 (level2_no, parent_no) - SELECT level2_no, level2_no / 50 + 1 AS parent_no - FROM generate_series(1,9999) level2_no; -ANALYZE transition_table_level2; -INSERT INTO transition_table_status (level, node_no, status) - SELECT 1, level1_no, 0 FROM transition_table_level1; -INSERT INTO transition_table_status (level, node_no, status) - SELECT 2, level2_no, 0 FROM transition_table_level2; -ANALYZE transition_table_status; -INSERT INTO transition_table_level1(level1_no) - SELECT generate_series(201,1000); -ANALYZE transition_table_level1; --- behave reasonably if someone tries to modify a transition table -CREATE FUNCTION transition_table_level2_bad_usage_func() - RETURNS TRIGGER - LANGUAGE plpgsql -AS $$ - BEGIN - INSERT INTO dx VALUES (1000000, 1000000, 'x'); - RETURN NULL; - END; -$$; -CREATE TRIGGER transition_table_level2_bad_usage_trigger - AFTER DELETE ON transition_table_level2 - REFERENCING OLD TABLE AS dx - FOR EACH STATEMENT EXECUTE PROCEDURE - transition_table_level2_bad_usage_func(); -DELETE FROM transition_table_level2 - WHERE level2_no BETWEEN 301 AND 305; -ERROR: relation "dx" cannot be the target of a modifying statement -CONTEXT: SQL statement "INSERT INTO dx VALUES (1000000, 1000000, 'x')" -PL/pgSQL function transition_table_level2_bad_usage_func() line 3 at SQL statement -DROP TRIGGER transition_table_level2_bad_usage_trigger - ON transition_table_level2; --- attempt modifications which would break RI (should all fail) -DELETE FROM transition_table_level1 - WHERE level1_no = 25; -ERROR: RI error -CONTEXT: PL/pgSQL function transition_table_level1_ri_parent_del_func() line 6 at RAISE -UPDATE transition_table_level1 SET level1_no = -1 - WHERE level1_no = 30; -ERROR: RI error -CONTEXT: PL/pgSQL function transition_table_level1_ri_parent_upd_func() line 15 at RAISE -INSERT INTO transition_table_level2 (level2_no, parent_no) - VALUES (10000, 10000); -ERROR: RI error -CONTEXT: PL/pgSQL function transition_table_level2_ri_child_insupd_func() line 8 at RAISE -UPDATE transition_table_level2 SET parent_no = 2000 - WHERE level2_no = 40; -ERROR: RI error -CONTEXT: PL/pgSQL function transition_table_level2_ri_child_insupd_func() line 8 at RAISE --- attempt modifications which would not break RI (should all succeed) -DELETE FROM transition_table_level1 - WHERE level1_no BETWEEN 201 AND 1000; -DELETE FROM transition_table_level1 - WHERE level1_no BETWEEN 100000000 AND 100000010; -SELECT count(*) FROM transition_table_level1; - count -------- - 200 -(1 row) - -DELETE FROM transition_table_level2 - WHERE level2_no BETWEEN 211 AND 220; -SELECT count(*) FROM transition_table_level2; - count -------- - 9989 -(1 row) - -CREATE TABLE alter_table_under_transition_tables -( - id int PRIMARY KEY, - name text -); -CREATE FUNCTION alter_table_under_transition_tables_upd_func() - RETURNS TRIGGER - LANGUAGE plpgsql -AS $$ -BEGIN - RAISE WARNING 'old table = %, new table = %', - (SELECT string_agg(id || '=' || name, ',') FROM d), - (SELECT string_agg(id || '=' || name, ',') FROM i); - RAISE NOTICE 'one = %', (SELECT 1 FROM alter_table_under_transition_tables LIMIT 1); - RETURN NULL; -END; -$$; --- should fail, TRUNCATE is not compatible with transition tables -CREATE TRIGGER alter_table_under_transition_tables_upd_trigger - AFTER TRUNCATE OR UPDATE ON alter_table_under_transition_tables - REFERENCING OLD TABLE AS d NEW TABLE AS i - FOR EACH STATEMENT EXECUTE PROCEDURE - alter_table_under_transition_tables_upd_func(); -ERROR: TRUNCATE triggers with transition tables are not supported --- should work -CREATE TRIGGER alter_table_under_transition_tables_upd_trigger - AFTER UPDATE ON alter_table_under_transition_tables - REFERENCING OLD TABLE AS d NEW TABLE AS i - FOR EACH STATEMENT EXECUTE PROCEDURE - alter_table_under_transition_tables_upd_func(); -INSERT INTO alter_table_under_transition_tables - VALUES (1, '1'), (2, '2'), (3, '3'); -UPDATE alter_table_under_transition_tables - SET name = name || name; -WARNING: old table = 1=1,2=2,3=3, new table = 1=11,2=22,3=33 -NOTICE: one = 1 --- now change 'name' to an integer to see what happens... -ALTER TABLE alter_table_under_transition_tables - ALTER COLUMN name TYPE int USING name::integer; -UPDATE alter_table_under_transition_tables - SET name = (name::text || name::text)::integer; -WARNING: old table = 1=11,2=22,3=33, new table = 1=1111,2=2222,3=3333 -NOTICE: one = 1 --- now drop column 'name' -ALTER TABLE alter_table_under_transition_tables - DROP column name; -UPDATE alter_table_under_transition_tables - SET id = id; -ERROR: column or variable "name" does not exist -LINE 1: (SELECT string_agg(id || '=' || name, ',') FROM d) - ^ -QUERY: (SELECT string_agg(id || '=' || name, ',') FROM d) -CONTEXT: PL/pgSQL function alter_table_under_transition_tables_upd_func() line 3 at RAISE --- --- Test multiple reference to a transition table --- -CREATE TABLE multi_test (i int); -INSERT INTO multi_test VALUES (1); -CREATE OR REPLACE FUNCTION multi_test_trig() RETURNS trigger -LANGUAGE plpgsql AS $$ -BEGIN - RAISE NOTICE 'count = %', (SELECT COUNT(*) FROM new_test); - RAISE NOTICE 'count union = %', - (SELECT COUNT(*) - FROM (SELECT * FROM new_test UNION ALL SELECT * FROM new_test) ss); - RETURN NULL; -END$$; -CREATE TRIGGER my_trigger AFTER UPDATE ON multi_test - REFERENCING NEW TABLE AS new_test OLD TABLE as old_test - FOR EACH STATEMENT EXECUTE PROCEDURE multi_test_trig(); -UPDATE multi_test SET i = i; -NOTICE: count = 1 -NOTICE: count union = 2 -DROP TABLE multi_test; -DROP FUNCTION multi_test_trig(); --- --- Check type parsing and record fetching from partitioned tables --- -CREATE TABLE partitioned_table (a int, b text) PARTITION BY LIST (a); -CREATE TABLE pt_part1 PARTITION OF partitioned_table FOR VALUES IN (1); -CREATE TABLE pt_part2 PARTITION OF partitioned_table FOR VALUES IN (2); -INSERT INTO partitioned_table VALUES (1, 'Row 1'); -INSERT INTO partitioned_table VALUES (2, 'Row 2'); -CREATE OR REPLACE FUNCTION get_from_partitioned_table(partitioned_table.a%type) -RETURNS partitioned_table AS $$ -DECLARE - a_val partitioned_table.a%TYPE; - result partitioned_table%ROWTYPE; -BEGIN - a_val := $1; - SELECT * INTO result FROM partitioned_table WHERE a = a_val; - RETURN result; -END; $$ LANGUAGE plpgsql; -NOTICE: type reference partitioned_table.a%TYPE converted to integer -SELECT * FROM get_from_partitioned_table(1) AS t; - a | b ----+------- - 1 | Row 1 -(1 row) - -CREATE OR REPLACE FUNCTION list_partitioned_table() -RETURNS SETOF public.partitioned_table.a%TYPE AS $$ -DECLARE - row public.partitioned_table%ROWTYPE; - a_val public.partitioned_table.a%TYPE; -BEGIN - FOR row IN SELECT * FROM public.partitioned_table ORDER BY a LOOP - a_val := row.a; - RETURN NEXT a_val; - END LOOP; - RETURN; -END; $$ LANGUAGE plpgsql; -NOTICE: type reference public.partitioned_table.a%TYPE converted to integer -SELECT * FROM list_partitioned_table() AS t; - t ---- - 1 - 2 -(2 rows) - --- --- Check argument name is used instead of $n in error message --- -CREATE FUNCTION fx(x WSlot) RETURNS void AS $$ -BEGIN - GET DIAGNOSTICS x = ROW_COUNT; - RETURN; -END; $$ LANGUAGE plpgsql; -ERROR: "x" is not a scalar variable -LINE 3: GET DIAGNOSTICS x = ROW_COUNT; - ^ +WARNING: terminating connection because of crash of another server process +DETAIL: The postmaster has commanded this server process to roll back the current transaction and exit, because another server process exited abnormally and possibly corrupted shared memory. +HINT: In a moment you should be able to reconnect to the database and repeat your command. +server closed the connection unexpectedly + This probably means the server terminated abnormally + before or while processing the request. +connection to server was lost diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/alter_table.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/alter_table.out --- /tmp/cirrus-ci-build/src/test/regress/expected/alter_table.out 2024-09-22 06:28:18.676874537 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/alter_table.out 2024-09-22 06:38:09.740217870 +0000 @@ -2121,2586 +2121,10 @@ "at_part_1_b_idx" btree (b) \d at_part_2 - Table "public.at_part_2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - b | text | | | - a | integer | | | - -alter table at_partitioned attach partition at_part_2 for values from (1000) to (2000); -\d at_part_2 - Table "public.at_part_2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - b | text | | | - a | integer | | | -Partition of: at_partitioned FOR VALUES FROM (1000) TO (2000) -Indexes: - "at_part_2_a_idx" btree (a) - "at_part_2_b_idx" btree (b) - -alter table at_partitioned alter column b type numeric using b::numeric; -\d at_part_1 - Table "public.at_part_1" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | numeric | | | -Partition of: at_partitioned FOR VALUES FROM (0) TO (1000) -Indexes: - "at_part_1_a_idx" btree (a) - "at_part_1_b_idx" btree (b) - -\d at_part_2 - Table "public.at_part_2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - b | numeric | | | - a | integer | | | -Partition of: at_partitioned FOR VALUES FROM (1000) TO (2000) -Indexes: - "at_part_2_a_idx" btree (a) - "at_part_2_b_idx" btree (b) - -drop table at_partitioned; --- Alter column type when no table rewrite is required --- Also check that comments are preserved -create table at_partitioned(id int, name varchar(64), unique (id, name)) - partition by hash(id); -comment on constraint at_partitioned_id_name_key on at_partitioned is 'parent constraint'; -comment on index at_partitioned_id_name_key is 'parent index'; -create table at_partitioned_0 partition of at_partitioned - for values with (modulus 2, remainder 0); -comment on constraint at_partitioned_0_id_name_key on at_partitioned_0 is 'child 0 constraint'; -comment on index at_partitioned_0_id_name_key is 'child 0 index'; -create table at_partitioned_1 partition of at_partitioned - for values with (modulus 2, remainder 1); -comment on constraint at_partitioned_1_id_name_key on at_partitioned_1 is 'child 1 constraint'; -comment on index at_partitioned_1_id_name_key is 'child 1 index'; -insert into at_partitioned values(1, 'foo'); -insert into at_partitioned values(3, 'bar'); -create temp table old_oids as - select relname, oid as oldoid, relfilenode as oldfilenode - from pg_class where relname like 'at_partitioned%'; -select relname, - c.oid = oldoid as orig_oid, - case relfilenode - when 0 then 'none' - when c.oid then 'own' - when oldfilenode then 'orig' - else 'OTHER' - end as storage, - obj_description(c.oid, 'pg_class') as desc - from pg_class c left join old_oids using (relname) - where relname like 'at_partitioned%' - order by relname; - relname | orig_oid | storage | desc -------------------------------+----------+---------+--------------- - at_partitioned | t | none | - at_partitioned_0 | t | own | - at_partitioned_0_id_name_key | t | own | child 0 index - at_partitioned_1 | t | own | - at_partitioned_1_id_name_key | t | own | child 1 index - at_partitioned_id_name_key | t | none | parent index -(6 rows) - -select conname, obj_description(oid, 'pg_constraint') as desc - from pg_constraint where conname like 'at_partitioned%' - order by conname; - conname | desc -------------------------------+-------------------- - at_partitioned_0_id_name_key | child 0 constraint - at_partitioned_1_id_name_key | child 1 constraint - at_partitioned_id_name_key | parent constraint -(3 rows) - -alter table at_partitioned alter column name type varchar(127); --- Note: these tests currently show the wrong behavior for comments :-( -select relname, - c.oid = oldoid as orig_oid, - case relfilenode - when 0 then 'none' - when c.oid then 'own' - when oldfilenode then 'orig' - else 'OTHER' - end as storage, - obj_description(c.oid, 'pg_class') as desc - from pg_class c left join old_oids using (relname) - where relname like 'at_partitioned%' - order by relname; - relname | orig_oid | storage | desc -------------------------------+----------+---------+-------------- - at_partitioned | t | none | - at_partitioned_0 | t | own | - at_partitioned_0_id_name_key | f | own | parent index - at_partitioned_1 | t | own | - at_partitioned_1_id_name_key | f | own | parent index - at_partitioned_id_name_key | f | none | parent index -(6 rows) - -select conname, obj_description(oid, 'pg_constraint') as desc - from pg_constraint where conname like 'at_partitioned%' - order by conname; - conname | desc -------------------------------+------------------- - at_partitioned_0_id_name_key | - at_partitioned_1_id_name_key | - at_partitioned_id_name_key | parent constraint -(3 rows) - --- Don't remove this DROP, it exposes bug #15672 -drop table at_partitioned; --- disallow recursive containment of row types -create temp table recur1 (f1 int); -alter table recur1 add column f2 recur1; -- fails -ERROR: composite type recur1 cannot be made a member of itself -alter table recur1 add column f2 recur1[]; -- fails -ERROR: composite type recur1 cannot be made a member of itself -create domain array_of_recur1 as recur1[]; -alter table recur1 add column f2 array_of_recur1; -- fails -ERROR: composite type recur1 cannot be made a member of itself -create temp table recur2 (f1 int, f2 recur1); -alter table recur1 add column f2 recur2; -- fails -ERROR: composite type recur1 cannot be made a member of itself -alter table recur1 add column f2 int; -alter table recur1 alter column f2 type recur2; -- fails -ERROR: composite type recur1 cannot be made a member of itself --- SET STORAGE may need to add a TOAST table -create table test_storage (a text, c text storage plain); -select reltoastrelid <> 0 as has_toast_table - from pg_class where oid = 'test_storage'::regclass; - has_toast_table ------------------ - t -(1 row) - -alter table test_storage alter a set storage plain; --- rewrite table to remove its TOAST table; need a non-constant column default -alter table test_storage add b int default random()::int; -select reltoastrelid <> 0 as has_toast_table - from pg_class where oid = 'test_storage'::regclass; - has_toast_table ------------------ - f -(1 row) - -alter table test_storage alter a set storage default; -- re-add TOAST table -select reltoastrelid <> 0 as has_toast_table - from pg_class where oid = 'test_storage'::regclass; - has_toast_table ------------------ - t -(1 row) - --- check STORAGE correctness -create table test_storage_failed (a text, b int storage extended); -ERROR: column data type integer can only have storage PLAIN --- test that SET STORAGE propagates to index correctly -create index test_storage_idx on test_storage (b, a); -alter table test_storage alter column a set storage external; -\d+ test_storage - Table "public.test_storage" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+-------------------+----------+--------------+------------- - a | text | | | | external | | - c | text | | | | plain | | - b | integer | | | random()::integer | plain | | -Indexes: - "test_storage_idx" btree (b, a) - -\d+ test_storage_idx - Index "public.test_storage_idx" - Column | Type | Key? | Definition | Storage | Stats target ---------+---------+------+------------+----------+-------------- - b | integer | yes | b | plain | - a | text | yes | a | external | -btree, for table "public.test_storage" - --- ALTER COLUMN TYPE with a check constraint and a child table (bug #13779) -CREATE TABLE test_inh_check (a float check (a > 10.2), b float); -CREATE TABLE test_inh_check_child() INHERITS(test_inh_check); -\d test_inh_check - Table "public.test_inh_check" - Column | Type | Collation | Nullable | Default ---------+------------------+-----------+----------+--------- - a | double precision | | | - b | double precision | | | -Check constraints: - "test_inh_check_a_check" CHECK (a > 10.2::double precision) -Number of child tables: 1 (Use \d+ to list them.) - -\d test_inh_check_child - Table "public.test_inh_check_child" - Column | Type | Collation | Nullable | Default ---------+------------------+-----------+----------+--------- - a | double precision | | | - b | double precision | | | -Check constraints: - "test_inh_check_a_check" CHECK (a > 10.2::double precision) -Inherits: test_inh_check - -select relname, conname, coninhcount, conislocal, connoinherit - from pg_constraint c, pg_class r - where relname like 'test_inh_check%' and c.conrelid = r.oid - order by 1, 2; - relname | conname | coninhcount | conislocal | connoinherit -----------------------+------------------------+-------------+------------+-------------- - test_inh_check | test_inh_check_a_check | 0 | t | f - test_inh_check_child | test_inh_check_a_check | 1 | f | f -(2 rows) - -ALTER TABLE test_inh_check ALTER COLUMN a TYPE numeric; -\d test_inh_check - Table "public.test_inh_check" - Column | Type | Collation | Nullable | Default ---------+------------------+-----------+----------+--------- - a | numeric | | | - b | double precision | | | -Check constraints: - "test_inh_check_a_check" CHECK (a::double precision > 10.2::double precision) -Number of child tables: 1 (Use \d+ to list them.) - -\d test_inh_check_child - Table "public.test_inh_check_child" - Column | Type | Collation | Nullable | Default ---------+------------------+-----------+----------+--------- - a | numeric | | | - b | double precision | | | -Check constraints: - "test_inh_check_a_check" CHECK (a::double precision > 10.2::double precision) -Inherits: test_inh_check - -select relname, conname, coninhcount, conislocal, connoinherit - from pg_constraint c, pg_class r - where relname like 'test_inh_check%' and c.conrelid = r.oid - order by 1, 2; - relname | conname | coninhcount | conislocal | connoinherit -----------------------+------------------------+-------------+------------+-------------- - test_inh_check | test_inh_check_a_check | 0 | t | f - test_inh_check_child | test_inh_check_a_check | 1 | f | f -(2 rows) - --- also try noinherit, local, and local+inherited cases -ALTER TABLE test_inh_check ADD CONSTRAINT bnoinherit CHECK (b > 100) NO INHERIT; -ALTER TABLE test_inh_check_child ADD CONSTRAINT blocal CHECK (b < 1000); -ALTER TABLE test_inh_check_child ADD CONSTRAINT bmerged CHECK (b > 1); -ALTER TABLE test_inh_check ADD CONSTRAINT bmerged CHECK (b > 1); -NOTICE: merging constraint "bmerged" with inherited definition -\d test_inh_check - Table "public.test_inh_check" - Column | Type | Collation | Nullable | Default ---------+------------------+-----------+----------+--------- - a | numeric | | | - b | double precision | | | -Check constraints: - "bmerged" CHECK (b > 1::double precision) - "bnoinherit" CHECK (b > 100::double precision) NO INHERIT - "test_inh_check_a_check" CHECK (a::double precision > 10.2::double precision) -Number of child tables: 1 (Use \d+ to list them.) - -\d test_inh_check_child - Table "public.test_inh_check_child" - Column | Type | Collation | Nullable | Default ---------+------------------+-----------+----------+--------- - a | numeric | | | - b | double precision | | | -Check constraints: - "blocal" CHECK (b < 1000::double precision) - "bmerged" CHECK (b > 1::double precision) - "test_inh_check_a_check" CHECK (a::double precision > 10.2::double precision) -Inherits: test_inh_check - -select relname, conname, coninhcount, conislocal, connoinherit - from pg_constraint c, pg_class r - where relname like 'test_inh_check%' and c.conrelid = r.oid - order by 1, 2; - relname | conname | coninhcount | conislocal | connoinherit -----------------------+------------------------+-------------+------------+-------------- - test_inh_check | bmerged | 0 | t | f - test_inh_check | bnoinherit | 0 | t | t - test_inh_check | test_inh_check_a_check | 0 | t | f - test_inh_check_child | blocal | 0 | t | f - test_inh_check_child | bmerged | 1 | t | f - test_inh_check_child | test_inh_check_a_check | 1 | f | f -(6 rows) - -ALTER TABLE test_inh_check ALTER COLUMN b TYPE numeric; -NOTICE: merging constraint "bmerged" with inherited definition -\d test_inh_check - Table "public.test_inh_check" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | numeric | | | - b | numeric | | | -Check constraints: - "bmerged" CHECK (b::double precision > 1::double precision) - "bnoinherit" CHECK (b::double precision > 100::double precision) NO INHERIT - "test_inh_check_a_check" CHECK (a::double precision > 10.2::double precision) -Number of child tables: 1 (Use \d+ to list them.) - -\d test_inh_check_child - Table "public.test_inh_check_child" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | numeric | | | - b | numeric | | | -Check constraints: - "blocal" CHECK (b::double precision < 1000::double precision) - "bmerged" CHECK (b::double precision > 1::double precision) - "test_inh_check_a_check" CHECK (a::double precision > 10.2::double precision) -Inherits: test_inh_check - -select relname, conname, coninhcount, conislocal, connoinherit - from pg_constraint c, pg_class r - where relname like 'test_inh_check%' and c.conrelid = r.oid - order by 1, 2; - relname | conname | coninhcount | conislocal | connoinherit -----------------------+------------------------+-------------+------------+-------------- - test_inh_check | bmerged | 0 | t | f - test_inh_check | bnoinherit | 0 | t | t - test_inh_check | test_inh_check_a_check | 0 | t | f - test_inh_check_child | blocal | 0 | t | f - test_inh_check_child | bmerged | 1 | t | f - test_inh_check_child | test_inh_check_a_check | 1 | f | f -(6 rows) - --- ALTER COLUMN TYPE with different schema in children --- Bug at https://postgr.es/m/20170102225618.GA10071@telsasoft.com -CREATE TABLE test_type_diff (f1 int); -CREATE TABLE test_type_diff_c (extra smallint) INHERITS (test_type_diff); -ALTER TABLE test_type_diff ADD COLUMN f2 int; -INSERT INTO test_type_diff_c VALUES (1, 2, 3); -ALTER TABLE test_type_diff ALTER COLUMN f2 TYPE bigint USING f2::bigint; -CREATE TABLE test_type_diff2 (int_two int2, int_four int4, int_eight int8); -CREATE TABLE test_type_diff2_c1 (int_four int4, int_eight int8, int_two int2); -CREATE TABLE test_type_diff2_c2 (int_eight int8, int_two int2, int_four int4); -CREATE TABLE test_type_diff2_c3 (int_two int2, int_four int4, int_eight int8); -ALTER TABLE test_type_diff2_c1 INHERIT test_type_diff2; -ALTER TABLE test_type_diff2_c2 INHERIT test_type_diff2; -ALTER TABLE test_type_diff2_c3 INHERIT test_type_diff2; -INSERT INTO test_type_diff2_c1 VALUES (1, 2, 3); -INSERT INTO test_type_diff2_c2 VALUES (4, 5, 6); -INSERT INTO test_type_diff2_c3 VALUES (7, 8, 9); -ALTER TABLE test_type_diff2 ALTER COLUMN int_four TYPE int8 USING int_four::int8; --- whole-row references are disallowed -ALTER TABLE test_type_diff2 ALTER COLUMN int_four TYPE int4 USING (pg_column_size(test_type_diff2)); -ERROR: cannot convert whole-row table reference -DETAIL: USING expression contains a whole-row table reference. --- check for rollback of ANALYZE corrupting table property flags (bug #11638) -CREATE TABLE check_fk_presence_1 (id int PRIMARY KEY, t text); -CREATE TABLE check_fk_presence_2 (id int REFERENCES check_fk_presence_1, t text); -BEGIN; -ALTER TABLE check_fk_presence_2 DROP CONSTRAINT check_fk_presence_2_id_fkey; -ANALYZE check_fk_presence_2; -ROLLBACK; -\d check_fk_presence_2 - Table "public.check_fk_presence_2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - id | integer | | | - t | text | | | -Foreign-key constraints: - "check_fk_presence_2_id_fkey" FOREIGN KEY (id) REFERENCES check_fk_presence_1(id) - -DROP TABLE check_fk_presence_1, check_fk_presence_2; --- check column addition within a view (bug #14876) -create table at_base_table(id int, stuff text); -insert into at_base_table values (23, 'skidoo'); -create view at_view_1 as select * from at_base_table bt; -create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1; -\d+ at_view_1 - View "public.at_view_1" - Column | Type | Collation | Nullable | Default | Storage | Description ---------+---------+-----------+----------+---------+----------+------------- - id | integer | | | | plain | - stuff | text | | | | extended | -View definition: - SELECT id, - stuff - FROM at_base_table bt; - -\d+ at_view_2 - View "public.at_view_2" - Column | Type | Collation | Nullable | Default | Storage | Description ---------+---------+-----------+----------+---------+----------+------------- - id | integer | | | | plain | - stuff | text | | | | extended | - j | json | | | | extended | -View definition: - SELECT id, - stuff, - to_json(v1.*) AS j - FROM at_view_1 v1; - -explain (verbose, costs off) select * from at_view_2; - QUERY PLAN ----------------------------------------------------------- - Seq Scan on public.at_base_table bt - Output: bt.id, bt.stuff, to_json(ROW(bt.id, bt.stuff)) -(2 rows) - -select * from at_view_2; - id | stuff | j -----+--------+---------------------------- - 23 | skidoo | {"id":23,"stuff":"skidoo"} -(1 row) - -create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt; -\d+ at_view_1 - View "public.at_view_1" - Column | Type | Collation | Nullable | Default | Storage | Description ---------+---------+-----------+----------+---------+----------+------------- - id | integer | | | | plain | - stuff | text | | | | extended | - more | integer | | | | plain | -View definition: - SELECT id, - stuff, - 2 + 2 AS more - FROM at_base_table bt; - -\d+ at_view_2 - View "public.at_view_2" - Column | Type | Collation | Nullable | Default | Storage | Description ---------+---------+-----------+----------+---------+----------+------------- - id | integer | | | | plain | - stuff | text | | | | extended | - j | json | | | | extended | -View definition: - SELECT id, - stuff, - to_json(v1.*) AS j - FROM at_view_1 v1; - -explain (verbose, costs off) select * from at_view_2; - QUERY PLAN -------------------------------------------------------------- - Seq Scan on public.at_base_table bt - Output: bt.id, bt.stuff, to_json(ROW(bt.id, bt.stuff, 4)) -(2 rows) - -select * from at_view_2; - id | stuff | j -----+--------+------------------------------------- - 23 | skidoo | {"id":23,"stuff":"skidoo","more":4} -(1 row) - -drop view at_view_2; -drop view at_view_1; -drop table at_base_table; --- related case (bug #17811) -begin; -create temp table t1 as select * from int8_tbl; -create temp view v1 as select 1::int8 as q1; -create temp view v2 as select * from v1; -create or replace temp view v1 with (security_barrier = true) - as select * from t1; -create temp table log (q1 int8, q2 int8); -create rule v1_upd_rule as on update to v1 - do also insert into log values (new.*); -update v2 set q1 = q1 + 1 where q1 = 123; -select * from t1; - q1 | q2 -------------------+------------------- - 4567890123456789 | 123 - 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 - 124 | 456 - 124 | 4567890123456789 -(5 rows) - -select * from log; - q1 | q2 ------+------------------ - 124 | 456 - 124 | 4567890123456789 -(2 rows) - -rollback; --- check adding a column not itself requiring a rewrite, together with --- a column requiring a default (bug #16038) --- ensure that rewrites aren't silently optimized away, removing the --- value of the test -CREATE FUNCTION check_ddl_rewrite(p_tablename regclass, p_ddl text) -RETURNS boolean -LANGUAGE plpgsql AS $$ -DECLARE - v_relfilenode oid; -BEGIN - v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename; - - EXECUTE p_ddl; - - RETURN v_relfilenode <> (SELECT relfilenode FROM pg_class WHERE oid = p_tablename); -END; -$$; -CREATE TABLE rewrite_test(col text); -INSERT INTO rewrite_test VALUES ('something'); -INSERT INTO rewrite_test VALUES (NULL); --- empty[12] don't need rewrite, but notempty[12]_rewrite will force one -SELECT check_ddl_rewrite('rewrite_test', $$ - ALTER TABLE rewrite_test - ADD COLUMN empty1 text, - ADD COLUMN notempty1_rewrite serial; -$$); - check_ddl_rewrite -------------------- - t -(1 row) - -SELECT check_ddl_rewrite('rewrite_test', $$ - ALTER TABLE rewrite_test - ADD COLUMN notempty2_rewrite serial, - ADD COLUMN empty2 text; -$$); - check_ddl_rewrite -------------------- - t -(1 row) - --- also check that fast defaults cause no problem, first without rewrite -SELECT check_ddl_rewrite('rewrite_test', $$ - ALTER TABLE rewrite_test - ADD COLUMN empty3 text, - ADD COLUMN notempty3_norewrite int default 42; -$$); - check_ddl_rewrite -------------------- - f -(1 row) - -SELECT check_ddl_rewrite('rewrite_test', $$ - ALTER TABLE rewrite_test - ADD COLUMN notempty4_norewrite int default 42, - ADD COLUMN empty4 text; -$$); - check_ddl_rewrite -------------------- - f -(1 row) - --- then with rewrite -SELECT check_ddl_rewrite('rewrite_test', $$ - ALTER TABLE rewrite_test - ADD COLUMN empty5 text, - ADD COLUMN notempty5_norewrite int default 42, - ADD COLUMN notempty5_rewrite serial; -$$); - check_ddl_rewrite -------------------- - t -(1 row) - -SELECT check_ddl_rewrite('rewrite_test', $$ - ALTER TABLE rewrite_test - ADD COLUMN notempty6_rewrite serial, - ADD COLUMN empty6 text, - ADD COLUMN notempty6_norewrite int default 42; -$$); - check_ddl_rewrite -------------------- - t -(1 row) - --- cleanup -DROP FUNCTION check_ddl_rewrite(regclass, text); -DROP TABLE rewrite_test; --- --- lock levels --- -drop type lockmodes; -ERROR: type "lockmodes" does not exist -create type lockmodes as enum ( - 'SIReadLock' -,'AccessShareLock' -,'RowShareLock' -,'RowExclusiveLock' -,'ShareUpdateExclusiveLock' -,'ShareLock' -,'ShareRowExclusiveLock' -,'ExclusiveLock' -,'AccessExclusiveLock' -); -drop view my_locks; -ERROR: view "my_locks" does not exist -create or replace view my_locks as -select case when c.relname like 'pg_toast%' then 'pg_toast' else c.relname end, max(mode::lockmodes) as max_lockmode -from pg_locks l join pg_class c on l.relation = c.oid -where virtualtransaction = ( - select virtualtransaction - from pg_locks - where transactionid = pg_current_xact_id()::xid) -and locktype = 'relation' -and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog') -and c.relname != 'my_locks' -group by c.relname; -create table alterlock (f1 int primary key, f2 text); -insert into alterlock values (1, 'foo'); -create table alterlock2 (f3 int primary key, f1 int); -insert into alterlock2 values (1, 1); -begin; alter table alterlock alter column f2 set statistics 150; -select * from my_locks order by 1; - relname | max_lockmode ------------+-------------------------- - alterlock | ShareUpdateExclusiveLock -(1 row) - -rollback; -begin; alter table alterlock cluster on alterlock_pkey; -select * from my_locks order by 1; - relname | max_lockmode -----------------+-------------------------- - alterlock | ShareUpdateExclusiveLock - alterlock_pkey | ShareUpdateExclusiveLock -(2 rows) - -commit; -begin; alter table alterlock set without cluster; -select * from my_locks order by 1; - relname | max_lockmode ------------+-------------------------- - alterlock | ShareUpdateExclusiveLock -(1 row) - -commit; -begin; alter table alterlock set (fillfactor = 100); -select * from my_locks order by 1; - relname | max_lockmode ------------+-------------------------- - alterlock | ShareUpdateExclusiveLock - pg_toast | ShareUpdateExclusiveLock -(2 rows) - -commit; -begin; alter table alterlock reset (fillfactor); -select * from my_locks order by 1; - relname | max_lockmode ------------+-------------------------- - alterlock | ShareUpdateExclusiveLock - pg_toast | ShareUpdateExclusiveLock -(2 rows) - -commit; -begin; alter table alterlock set (toast.autovacuum_enabled = off); -select * from my_locks order by 1; - relname | max_lockmode ------------+-------------------------- - alterlock | ShareUpdateExclusiveLock - pg_toast | ShareUpdateExclusiveLock -(2 rows) - -commit; -begin; alter table alterlock set (autovacuum_enabled = off); -select * from my_locks order by 1; - relname | max_lockmode ------------+-------------------------- - alterlock | ShareUpdateExclusiveLock - pg_toast | ShareUpdateExclusiveLock -(2 rows) - -commit; -begin; alter table alterlock alter column f2 set (n_distinct = 1); -select * from my_locks order by 1; - relname | max_lockmode ------------+-------------------------- - alterlock | ShareUpdateExclusiveLock -(1 row) - -rollback; --- test that mixing options with different lock levels works as expected -begin; alter table alterlock set (autovacuum_enabled = off, fillfactor = 80); -select * from my_locks order by 1; - relname | max_lockmode ------------+-------------------------- - alterlock | ShareUpdateExclusiveLock - pg_toast | ShareUpdateExclusiveLock -(2 rows) - -commit; -begin; alter table alterlock alter column f2 set storage extended; -select * from my_locks order by 1; - relname | max_lockmode ------------+--------------------- - alterlock | AccessExclusiveLock -(1 row) - -rollback; -begin; alter table alterlock alter column f2 set default 'x'; -select * from my_locks order by 1; - relname | max_lockmode ------------+--------------------- - alterlock | AccessExclusiveLock -(1 row) - -rollback; -begin; -create trigger ttdummy - before delete or update on alterlock - for each row - execute procedure - ttdummy (1, 1); -select * from my_locks order by 1; - relname | max_lockmode ------------+----------------------- - alterlock | ShareRowExclusiveLock -(1 row) - -rollback; -begin; -select * from my_locks order by 1; - relname | max_lockmode ----------+-------------- -(0 rows) - -alter table alterlock2 add foreign key (f1) references alterlock (f1); -select * from my_locks order by 1; - relname | max_lockmode ------------------+----------------------- - alterlock | ShareRowExclusiveLock - alterlock2 | ShareRowExclusiveLock - alterlock2_pkey | AccessShareLock - alterlock_pkey | AccessShareLock -(4 rows) - -rollback; -begin; -alter table alterlock2 -add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID; -select * from my_locks order by 1; - relname | max_lockmode -------------+----------------------- - alterlock | ShareRowExclusiveLock - alterlock2 | ShareRowExclusiveLock -(2 rows) - -commit; -begin; -alter table alterlock2 validate constraint alterlock2nv; -select * from my_locks order by 1; - relname | max_lockmode ------------------+-------------------------- - alterlock | RowShareLock - alterlock2 | ShareUpdateExclusiveLock - alterlock2_pkey | AccessShareLock - alterlock_pkey | AccessShareLock -(4 rows) - -rollback; -create or replace view my_locks as -select case when c.relname like 'pg_toast%' then 'pg_toast' else c.relname end, max(mode::lockmodes) as max_lockmode -from pg_locks l join pg_class c on l.relation = c.oid -where virtualtransaction = ( - select virtualtransaction - from pg_locks - where transactionid = pg_current_xact_id()::xid) -and locktype = 'relation' -and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog') -and c.relname = 'my_locks' -group by c.relname; --- raise exception -alter table my_locks set (autovacuum_enabled = false); -ERROR: unrecognized parameter "autovacuum_enabled" -alter view my_locks set (autovacuum_enabled = false); -ERROR: unrecognized parameter "autovacuum_enabled" -alter table my_locks reset (autovacuum_enabled); -alter view my_locks reset (autovacuum_enabled); -begin; -alter view my_locks set (security_barrier=off); -select * from my_locks order by 1; - relname | max_lockmode -----------+--------------------- - my_locks | AccessExclusiveLock -(1 row) - -alter view my_locks reset (security_barrier); -rollback; --- this test intentionally applies the ALTER TABLE command against a view, but --- uses a view option so we expect this to succeed. This form of SQL is --- accepted for historical reasons, as shown in the docs for ALTER VIEW -begin; -alter table my_locks set (security_barrier=off); -select * from my_locks order by 1; - relname | max_lockmode -----------+--------------------- - my_locks | AccessExclusiveLock -(1 row) - -alter table my_locks reset (security_barrier); -rollback; --- cleanup -drop table alterlock2; -drop table alterlock; -drop view my_locks; -drop type lockmodes; --- --- alter function --- -create function test_strict(text) returns text as - 'select coalesce($1, ''got passed a null'');' - language sql returns null on null input; -select test_strict(NULL); - test_strict -------------- - -(1 row) - -alter function test_strict(text) called on null input; -select test_strict(NULL); - test_strict -------------------- - got passed a null -(1 row) - -create function non_strict(text) returns text as - 'select coalesce($1, ''got passed a null'');' - language sql called on null input; -select non_strict(NULL); - non_strict -------------------- - got passed a null -(1 row) - -alter function non_strict(text) returns null on null input; -select non_strict(NULL); - non_strict ------------- - -(1 row) - --- --- alter object set schema --- -create schema alter1; -create schema alter2; -create table alter1.t1(f1 serial primary key, f2 int check (f2 > 0)); -create view alter1.v1 as select * from alter1.t1; -create function alter1.plus1(int) returns int as 'select $1+1' language sql; -create domain alter1.posint integer check (value > 0); -create type alter1.ctype as (f1 int, f2 text); -create function alter1.same(alter1.ctype, alter1.ctype) returns boolean language sql -as 'select $1.f1 is not distinct from $2.f1 and $1.f2 is not distinct from $2.f2'; -create operator alter1.=(procedure = alter1.same, leftarg = alter1.ctype, rightarg = alter1.ctype); -create operator class alter1.ctype_hash_ops default for type alter1.ctype using hash as - operator 1 alter1.=(alter1.ctype, alter1.ctype); -create conversion alter1.latin1_to_utf8 for 'latin1' to 'utf8' from iso8859_1_to_utf8; -create text search parser alter1.prs(start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end, lextypes = prsd_lextype); -create text search configuration alter1.cfg(parser = alter1.prs); -create text search template alter1.tmpl(init = dsimple_init, lexize = dsimple_lexize); -create text search dictionary alter1.dict(template = alter1.tmpl); -insert into alter1.t1(f2) values(11); -insert into alter1.t1(f2) values(12); -alter table alter1.t1 set schema alter1; -- no-op, same schema -alter table alter1.t1 set schema alter2; -alter table alter1.v1 set schema alter2; -alter function alter1.plus1(int) set schema alter2; -alter domain alter1.posint set schema alter2; -alter operator class alter1.ctype_hash_ops using hash set schema alter2; -alter operator family alter1.ctype_hash_ops using hash set schema alter2; -alter operator alter1.=(alter1.ctype, alter1.ctype) set schema alter2; -alter function alter1.same(alter1.ctype, alter1.ctype) set schema alter2; -alter type alter1.ctype set schema alter1; -- no-op, same schema -alter type alter1.ctype set schema alter2; -alter conversion alter1.latin1_to_utf8 set schema alter2; -alter text search parser alter1.prs set schema alter2; -alter text search configuration alter1.cfg set schema alter2; -alter text search template alter1.tmpl set schema alter2; -alter text search dictionary alter1.dict set schema alter2; --- this should succeed because nothing is left in alter1 -drop schema alter1; -insert into alter2.t1(f2) values(13); -insert into alter2.t1(f2) values(14); -select * from alter2.t1; - f1 | f2 -----+---- - 1 | 11 - 2 | 12 - 3 | 13 - 4 | 14 -(4 rows) - -select * from alter2.v1; - f1 | f2 -----+---- - 1 | 11 - 2 | 12 - 3 | 13 - 4 | 14 -(4 rows) - -select alter2.plus1(41); - plus1 -------- - 42 -(1 row) - --- clean up -drop schema alter2 cascade; -NOTICE: drop cascades to 13 other objects -DETAIL: drop cascades to table alter2.t1 -drop cascades to view alter2.v1 -drop cascades to function alter2.plus1(integer) -drop cascades to type alter2.posint -drop cascades to type alter2.ctype -drop cascades to function alter2.same(alter2.ctype,alter2.ctype) -drop cascades to operator alter2.=(alter2.ctype,alter2.ctype) -drop cascades to operator family alter2.ctype_hash_ops for access method hash -drop cascades to conversion alter2.latin1_to_utf8 -drop cascades to text search parser alter2.prs -drop cascades to text search configuration alter2.cfg -drop cascades to text search template alter2.tmpl -drop cascades to text search dictionary alter2.dict --- --- composite types --- -CREATE TYPE test_type AS (a int); -\d test_type - Composite type "public.test_type" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - -ALTER TYPE nosuchtype ADD ATTRIBUTE b text; -- fails -ERROR: relation "nosuchtype" does not exist -ALTER TYPE test_type ADD ATTRIBUTE b text; -\d test_type - Composite type "public.test_type" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | text | | | - -ALTER TYPE test_type ADD ATTRIBUTE b text; -- fails -ERROR: column "b" of relation "test_type" already exists -ALTER TYPE test_type ALTER ATTRIBUTE b SET DATA TYPE varchar; -\d test_type - Composite type "public.test_type" - Column | Type | Collation | Nullable | Default ---------+-------------------+-----------+----------+--------- - a | integer | | | - b | character varying | | | - -ALTER TYPE test_type ALTER ATTRIBUTE b SET DATA TYPE integer; -\d test_type - Composite type "public.test_type" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | integer | | | - -ALTER TYPE test_type DROP ATTRIBUTE b; -\d test_type - Composite type "public.test_type" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - -ALTER TYPE test_type DROP ATTRIBUTE c; -- fails -ERROR: column "c" of relation "test_type" does not exist -ALTER TYPE test_type DROP ATTRIBUTE IF EXISTS c; -NOTICE: column "c" of relation "test_type" does not exist, skipping -ALTER TYPE test_type DROP ATTRIBUTE a, ADD ATTRIBUTE d boolean; -\d test_type - Composite type "public.test_type" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - d | boolean | | | - -ALTER TYPE test_type RENAME ATTRIBUTE a TO aa; -ERROR: column "a" does not exist -ALTER TYPE test_type RENAME ATTRIBUTE d TO dd; -\d test_type - Composite type "public.test_type" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - dd | boolean | | | - -DROP TYPE test_type; -CREATE TYPE test_type1 AS (a int, b text); -CREATE TABLE test_tbl1 (x int, y test_type1); -ALTER TYPE test_type1 ALTER ATTRIBUTE b TYPE varchar; -- fails -ERROR: cannot alter type "test_type1" because column "test_tbl1.y" uses it -DROP TABLE test_tbl1; -CREATE TABLE test_tbl1 (x int, y text); -CREATE INDEX test_tbl1_idx ON test_tbl1((row(x,y)::test_type1)); -ALTER TYPE test_type1 ALTER ATTRIBUTE b TYPE varchar; -- fails -ERROR: cannot alter type "test_type1" because column "test_tbl1_idx.row" uses it -DROP TABLE test_tbl1; -DROP TYPE test_type1; -CREATE TYPE test_type2 AS (a int, b text); -CREATE TABLE test_tbl2 OF test_type2; -CREATE TABLE test_tbl2_subclass () INHERITS (test_tbl2); -\d test_type2 - Composite type "public.test_type2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | text | | | - -\d test_tbl2 - Table "public.test_tbl2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | text | | | -Number of child tables: 1 (Use \d+ to list them.) -Typed table of type: test_type2 - -ALTER TYPE test_type2 ADD ATTRIBUTE c text; -- fails -ERROR: cannot alter type "test_type2" because it is the type of a typed table -HINT: Use ALTER ... CASCADE to alter the typed tables too. -ALTER TYPE test_type2 ADD ATTRIBUTE c text CASCADE; -\d test_type2 - Composite type "public.test_type2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | text | | | - c | text | | | - -\d test_tbl2 - Table "public.test_tbl2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | text | | | - c | text | | | -Number of child tables: 1 (Use \d+ to list them.) -Typed table of type: test_type2 - -ALTER TYPE test_type2 ALTER ATTRIBUTE b TYPE varchar; -- fails -ERROR: cannot alter type "test_type2" because it is the type of a typed table -HINT: Use ALTER ... CASCADE to alter the typed tables too. -ALTER TYPE test_type2 ALTER ATTRIBUTE b TYPE varchar CASCADE; -\d test_type2 - Composite type "public.test_type2" - Column | Type | Collation | Nullable | Default ---------+-------------------+-----------+----------+--------- - a | integer | | | - b | character varying | | | - c | text | | | - -\d test_tbl2 - Table "public.test_tbl2" - Column | Type | Collation | Nullable | Default ---------+-------------------+-----------+----------+--------- - a | integer | | | - b | character varying | | | - c | text | | | -Number of child tables: 1 (Use \d+ to list them.) -Typed table of type: test_type2 - -ALTER TYPE test_type2 DROP ATTRIBUTE b; -- fails -ERROR: cannot alter type "test_type2" because it is the type of a typed table -HINT: Use ALTER ... CASCADE to alter the typed tables too. -ALTER TYPE test_type2 DROP ATTRIBUTE b CASCADE; -\d test_type2 - Composite type "public.test_type2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - c | text | | | - -\d test_tbl2 - Table "public.test_tbl2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - c | text | | | -Number of child tables: 1 (Use \d+ to list them.) -Typed table of type: test_type2 - -ALTER TYPE test_type2 RENAME ATTRIBUTE a TO aa; -- fails -ERROR: cannot alter type "test_type2" because it is the type of a typed table -HINT: Use ALTER ... CASCADE to alter the typed tables too. -ALTER TYPE test_type2 RENAME ATTRIBUTE a TO aa CASCADE; -\d test_type2 - Composite type "public.test_type2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - aa | integer | | | - c | text | | | - -\d test_tbl2 - Table "public.test_tbl2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - aa | integer | | | - c | text | | | -Number of child tables: 1 (Use \d+ to list them.) -Typed table of type: test_type2 - -\d test_tbl2_subclass - Table "public.test_tbl2_subclass" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - aa | integer | | | - c | text | | | -Inherits: test_tbl2 - -DROP TABLE test_tbl2_subclass, test_tbl2; -DROP TYPE test_type2; -CREATE TYPE test_typex AS (a int, b text); -CREATE TABLE test_tblx (x int, y test_typex check ((y).a > 0)); -ALTER TYPE test_typex DROP ATTRIBUTE a; -- fails -ERROR: cannot drop column a of composite type test_typex because other objects depend on it -DETAIL: constraint test_tblx_y_check on table test_tblx depends on column a of composite type test_typex -HINT: Use DROP ... CASCADE to drop the dependent objects too. -ALTER TYPE test_typex DROP ATTRIBUTE a CASCADE; -NOTICE: drop cascades to constraint test_tblx_y_check on table test_tblx -\d test_tblx - Table "public.test_tblx" - Column | Type | Collation | Nullable | Default ---------+------------+-----------+----------+--------- - x | integer | | | - y | test_typex | | | - -DROP TABLE test_tblx; -DROP TYPE test_typex; --- This test isn't that interesting on its own, but the purpose is to leave --- behind a table to test pg_upgrade with. The table has a composite type --- column in it, and the composite type has a dropped attribute. -CREATE TYPE test_type3 AS (a int); -CREATE TABLE test_tbl3 (c) AS SELECT '(1)'::test_type3; -ALTER TYPE test_type3 DROP ATTRIBUTE a, ADD ATTRIBUTE b int; -CREATE TYPE test_type_empty AS (); -DROP TYPE test_type_empty; --- --- typed tables: OF / NOT OF --- -CREATE TYPE tt_t0 AS (z inet, x int, y numeric(8,2)); -ALTER TYPE tt_t0 DROP ATTRIBUTE z; -CREATE TABLE tt0 (x int NOT NULL, y numeric(8,2)); -- OK -CREATE TABLE tt1 (x int, y bigint); -- wrong base type -CREATE TABLE tt2 (x int, y numeric(9,2)); -- wrong typmod -CREATE TABLE tt3 (y numeric(8,2), x int); -- wrong column order -CREATE TABLE tt4 (x int); -- too few columns -CREATE TABLE tt5 (x int, y numeric(8,2), z int); -- too few columns -CREATE TABLE tt6 () INHERITS (tt0); -- can't have a parent -CREATE TABLE tt7 (x int, q text, y numeric(8,2)); -ALTER TABLE tt7 DROP q; -- OK -ALTER TABLE tt0 OF tt_t0; -ALTER TABLE tt1 OF tt_t0; -ERROR: table "tt1" has different type for column "y" -ALTER TABLE tt2 OF tt_t0; -ERROR: table "tt2" has different type for column "y" -ALTER TABLE tt3 OF tt_t0; -ERROR: table has column "y" where type requires "x" -ALTER TABLE tt4 OF tt_t0; -ERROR: table is missing column "y" -ALTER TABLE tt5 OF tt_t0; -ERROR: table has extra column "z" -ALTER TABLE tt6 OF tt_t0; -ERROR: typed tables cannot inherit -ALTER TABLE tt7 OF tt_t0; -CREATE TYPE tt_t1 AS (x int, y numeric(8,2)); -ALTER TABLE tt7 OF tt_t1; -- reassign an already-typed table -ALTER TABLE tt7 NOT OF; -\d tt7 - Table "public.tt7" - Column | Type | Collation | Nullable | Default ---------+--------------+-----------+----------+--------- - x | integer | | | - y | numeric(8,2) | | | - --- make sure we can drop a constraint on the parent but it remains on the child -CREATE TABLE test_drop_constr_parent (c text CHECK (c IS NOT NULL)); -CREATE TABLE test_drop_constr_child () INHERITS (test_drop_constr_parent); -ALTER TABLE ONLY test_drop_constr_parent DROP CONSTRAINT "test_drop_constr_parent_c_check"; --- should fail -INSERT INTO test_drop_constr_child (c) VALUES (NULL); -ERROR: new row for relation "test_drop_constr_child" violates check constraint "test_drop_constr_parent_c_check" -DETAIL: Failing row contains (null). -DROP TABLE test_drop_constr_parent CASCADE; -NOTICE: drop cascades to table test_drop_constr_child --- --- IF EXISTS test --- -ALTER TABLE IF EXISTS tt8 ADD COLUMN f int; -NOTICE: relation "tt8" does not exist, skipping -ALTER TABLE IF EXISTS tt8 ADD CONSTRAINT xxx PRIMARY KEY(f); -NOTICE: relation "tt8" does not exist, skipping -ALTER TABLE IF EXISTS tt8 ADD CHECK (f BETWEEN 0 AND 10); -NOTICE: relation "tt8" does not exist, skipping -ALTER TABLE IF EXISTS tt8 ALTER COLUMN f SET DEFAULT 0; -NOTICE: relation "tt8" does not exist, skipping -ALTER TABLE IF EXISTS tt8 RENAME COLUMN f TO f1; -NOTICE: relation "tt8" does not exist, skipping -ALTER TABLE IF EXISTS tt8 SET SCHEMA alter2; -NOTICE: relation "tt8" does not exist, skipping -CREATE TABLE tt8(a int); -CREATE SCHEMA alter2; -ALTER TABLE IF EXISTS tt8 ADD COLUMN f int; -ALTER TABLE IF EXISTS tt8 ADD CONSTRAINT xxx PRIMARY KEY(f); -ALTER TABLE IF EXISTS tt8 ADD CHECK (f BETWEEN 0 AND 10); -ALTER TABLE IF EXISTS tt8 ALTER COLUMN f SET DEFAULT 0; -ALTER TABLE IF EXISTS tt8 RENAME COLUMN f TO f1; -ALTER TABLE IF EXISTS tt8 SET SCHEMA alter2; -\d alter2.tt8 - Table "alter2.tt8" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - f1 | integer | | not null | 0 -Indexes: - "xxx" PRIMARY KEY, btree (f1) -Check constraints: - "tt8_f_check" CHECK (f1 >= 0 AND f1 <= 10) - -DROP TABLE alter2.tt8; -DROP SCHEMA alter2; --- --- Check conflicts between index and CHECK constraint names --- -CREATE TABLE tt9(c integer); -ALTER TABLE tt9 ADD CHECK(c > 1); -ALTER TABLE tt9 ADD CHECK(c > 2); -- picks nonconflicting name -ALTER TABLE tt9 ADD CONSTRAINT foo CHECK(c > 3); -ALTER TABLE tt9 ADD CONSTRAINT foo CHECK(c > 4); -- fail, dup name -ERROR: constraint "foo" for relation "tt9" already exists -ALTER TABLE tt9 ADD UNIQUE(c); -ALTER TABLE tt9 ADD UNIQUE(c); -- picks nonconflicting name -ALTER TABLE tt9 ADD CONSTRAINT tt9_c_key UNIQUE(c); -- fail, dup name -ERROR: relation "tt9_c_key" already exists -ALTER TABLE tt9 ADD CONSTRAINT foo UNIQUE(c); -- fail, dup name -ERROR: constraint "foo" for relation "tt9" already exists -ALTER TABLE tt9 ADD CONSTRAINT tt9_c_key CHECK(c > 5); -- fail, dup name -ERROR: constraint "tt9_c_key" for relation "tt9" already exists -ALTER TABLE tt9 ADD CONSTRAINT tt9_c_key2 CHECK(c > 6); -ALTER TABLE tt9 ADD UNIQUE(c); -- picks nonconflicting name -\d tt9 - Table "public.tt9" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - c | integer | | | -Indexes: - "tt9_c_key" UNIQUE CONSTRAINT, btree (c) - "tt9_c_key1" UNIQUE CONSTRAINT, btree (c) - "tt9_c_key3" UNIQUE CONSTRAINT, btree (c) -Check constraints: - "foo" CHECK (c > 3) - "tt9_c_check" CHECK (c > 1) - "tt9_c_check1" CHECK (c > 2) - "tt9_c_key2" CHECK (c > 6) - -DROP TABLE tt9; --- Check that comments on constraints and indexes are not lost at ALTER TABLE. -CREATE TABLE comment_test ( - id int, - positive_col int CHECK (positive_col > 0), - indexed_col int, - CONSTRAINT comment_test_pk PRIMARY KEY (id)); -CREATE INDEX comment_test_index ON comment_test(indexed_col); -COMMENT ON COLUMN comment_test.id IS 'Column ''id'' on comment_test'; -COMMENT ON INDEX comment_test_index IS 'Simple index on comment_test'; -COMMENT ON CONSTRAINT comment_test_positive_col_check ON comment_test IS 'CHECK constraint on comment_test.positive_col'; -COMMENT ON CONSTRAINT comment_test_pk ON comment_test IS 'PRIMARY KEY constraint of comment_test'; -COMMENT ON INDEX comment_test_pk IS 'Index backing the PRIMARY KEY of comment_test'; -SELECT col_description('comment_test'::regclass, 1) as comment; - comment ------------------------------ - Column 'id' on comment_test -(1 row) - -SELECT indexrelid::regclass::text as index, obj_description(indexrelid, 'pg_class') as comment FROM pg_index where indrelid = 'comment_test'::regclass ORDER BY 1, 2; - index | comment ---------------------+----------------------------------------------- - comment_test_index | Simple index on comment_test - comment_test_pk | Index backing the PRIMARY KEY of comment_test -(2 rows) - -SELECT conname as constraint, obj_description(oid, 'pg_constraint') as comment FROM pg_constraint where conrelid = 'comment_test'::regclass ORDER BY 1, 2; - constraint | comment ----------------------------------+----------------------------------------------- - comment_test_pk | PRIMARY KEY constraint of comment_test - comment_test_positive_col_check | CHECK constraint on comment_test.positive_col -(2 rows) - --- Change the datatype of all the columns. ALTER TABLE is optimized to not --- rebuild an index if the new data type is binary compatible with the old --- one. Check do a dummy ALTER TABLE that doesn't change the datatype --- first, to test that no-op codepath, and another one that does. -ALTER TABLE comment_test ALTER COLUMN indexed_col SET DATA TYPE int; -ALTER TABLE comment_test ALTER COLUMN indexed_col SET DATA TYPE text; -ALTER TABLE comment_test ALTER COLUMN id SET DATA TYPE int; -ALTER TABLE comment_test ALTER COLUMN id SET DATA TYPE text; -ALTER TABLE comment_test ALTER COLUMN positive_col SET DATA TYPE int; -ALTER TABLE comment_test ALTER COLUMN positive_col SET DATA TYPE bigint; --- Check that the comments are intact. -SELECT col_description('comment_test'::regclass, 1) as comment; - comment ------------------------------ - Column 'id' on comment_test -(1 row) - -SELECT indexrelid::regclass::text as index, obj_description(indexrelid, 'pg_class') as comment FROM pg_index where indrelid = 'comment_test'::regclass ORDER BY 1, 2; - index | comment ---------------------+----------------------------------------------- - comment_test_index | Simple index on comment_test - comment_test_pk | Index backing the PRIMARY KEY of comment_test -(2 rows) - -SELECT conname as constraint, obj_description(oid, 'pg_constraint') as comment FROM pg_constraint where conrelid = 'comment_test'::regclass ORDER BY 1, 2; - constraint | comment ----------------------------------+----------------------------------------------- - comment_test_pk | PRIMARY KEY constraint of comment_test - comment_test_positive_col_check | CHECK constraint on comment_test.positive_col -(2 rows) - --- Check compatibility for foreign keys and comments. This is done --- separately as rebuilding the column type of the parent leads --- to an error and would reduce the test scope. -CREATE TABLE comment_test_child ( - id text CONSTRAINT comment_test_child_fk REFERENCES comment_test); -CREATE INDEX comment_test_child_fk ON comment_test_child(id); -COMMENT ON COLUMN comment_test_child.id IS 'Column ''id'' on comment_test_child'; -COMMENT ON INDEX comment_test_child_fk IS 'Index backing the FOREIGN KEY of comment_test_child'; -COMMENT ON CONSTRAINT comment_test_child_fk ON comment_test_child IS 'FOREIGN KEY constraint of comment_test_child'; --- Change column type of parent -ALTER TABLE comment_test ALTER COLUMN id SET DATA TYPE text; -ALTER TABLE comment_test ALTER COLUMN id SET DATA TYPE int USING id::integer; -ERROR: foreign key constraint "comment_test_child_fk" cannot be implemented -DETAIL: Key columns "id" and "id" are of incompatible types: text and integer. --- Comments should be intact -SELECT col_description('comment_test_child'::regclass, 1) as comment; - comment ------------------------------------ - Column 'id' on comment_test_child -(1 row) - -SELECT indexrelid::regclass::text as index, obj_description(indexrelid, 'pg_class') as comment FROM pg_index where indrelid = 'comment_test_child'::regclass ORDER BY 1, 2; - index | comment ------------------------+----------------------------------------------------- - comment_test_child_fk | Index backing the FOREIGN KEY of comment_test_child -(1 row) - -SELECT conname as constraint, obj_description(oid, 'pg_constraint') as comment FROM pg_constraint where conrelid = 'comment_test_child'::regclass ORDER BY 1, 2; - constraint | comment ------------------------+---------------------------------------------- - comment_test_child_fk | FOREIGN KEY constraint of comment_test_child -(1 row) - --- Check that we map relation oids to filenodes and back correctly. Only --- display bad mappings so the test output doesn't change all the time. A --- filenode function call can return NULL for a relation dropped concurrently --- with the call's surrounding query, so ignore a NULL mapped_oid for --- relations that no longer exist after all calls finish. -CREATE TEMP TABLE filenode_mapping AS -SELECT - oid, mapped_oid, reltablespace, relfilenode, relname -FROM pg_class, - pg_filenode_relation(reltablespace, pg_relation_filenode(oid)) AS mapped_oid -WHERE relkind IN ('r', 'i', 'S', 't', 'm') AND mapped_oid IS DISTINCT FROM oid; -SELECT m.* FROM filenode_mapping m LEFT JOIN pg_class c ON c.oid = m.oid -WHERE c.oid IS NOT NULL OR m.mapped_oid IS NOT NULL; - oid | mapped_oid | reltablespace | relfilenode | relname ------+------------+---------------+-------------+--------- -(0 rows) - --- Checks on creating and manipulation of user defined relations in --- pg_catalog. -SHOW allow_system_table_mods; - allow_system_table_mods -------------------------- - off -(1 row) - --- disallowed because of search_path issues with pg_dump -CREATE TABLE pg_catalog.new_system_table(); -ERROR: permission denied to create "pg_catalog.new_system_table" -DETAIL: System catalog modifications are currently disallowed. --- instead create in public first, move to catalog -CREATE TABLE new_system_table(id serial primary key, othercol text); -ALTER TABLE new_system_table SET SCHEMA pg_catalog; -ALTER TABLE new_system_table SET SCHEMA public; -ALTER TABLE new_system_table SET SCHEMA pg_catalog; --- will be ignored -- already there: -ALTER TABLE new_system_table SET SCHEMA pg_catalog; -ALTER TABLE new_system_table RENAME TO old_system_table; -CREATE INDEX old_system_table__othercol ON old_system_table (othercol); -INSERT INTO old_system_table(othercol) VALUES ('somedata'), ('otherdata'); -UPDATE old_system_table SET id = -id; -DELETE FROM old_system_table WHERE othercol = 'somedata'; -TRUNCATE old_system_table; -ALTER TABLE old_system_table DROP CONSTRAINT new_system_table_pkey; -ALTER TABLE old_system_table DROP COLUMN othercol; -DROP TABLE old_system_table; --- set logged -CREATE UNLOGGED TABLE unlogged1(f1 SERIAL PRIMARY KEY, f2 TEXT); -- has sequence, toast --- check relpersistence of an unlogged table -SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged1' -UNION ALL -SELECT r.relname || ' toast table', t.relkind, t.relpersistence FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid WHERE r.relname ~ '^unlogged1' -UNION ALL -SELECT r.relname || ' toast index', ri.relkind, ri.relpersistence FROM pg_class r join pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid JOIN pg_class ri ON ri.oid = i.indexrelid WHERE r.relname ~ '^unlogged1' -ORDER BY relname; - relname | relkind | relpersistence ------------------------+---------+---------------- - unlogged1 | r | u - unlogged1 toast index | i | u - unlogged1 toast table | t | u - unlogged1_f1_seq | S | u - unlogged1_pkey | i | u -(5 rows) - -CREATE UNLOGGED TABLE unlogged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged1); -- foreign key -CREATE UNLOGGED TABLE unlogged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged3); -- self-referencing foreign key -ALTER TABLE unlogged3 SET LOGGED; -- skip self-referencing foreign key -ALTER TABLE unlogged2 SET LOGGED; -- fails because a foreign key to an unlogged table exists -ERROR: could not change table "unlogged2" to logged because it references unlogged table "unlogged1" -ALTER TABLE unlogged1 SET LOGGED; --- check relpersistence of an unlogged table after changing to permanent -SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged1' -UNION ALL -SELECT r.relname || ' toast table', t.relkind, t.relpersistence FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid WHERE r.relname ~ '^unlogged1' -UNION ALL -SELECT r.relname || ' toast index', ri.relkind, ri.relpersistence FROM pg_class r join pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid JOIN pg_class ri ON ri.oid = i.indexrelid WHERE r.relname ~ '^unlogged1' -ORDER BY relname; - relname | relkind | relpersistence ------------------------+---------+---------------- - unlogged1 | r | p - unlogged1 toast index | i | p - unlogged1 toast table | t | p - unlogged1_f1_seq | S | p - unlogged1_pkey | i | p -(5 rows) - -ALTER TABLE unlogged1 SET LOGGED; -- silently do nothing -DROP TABLE unlogged3; -DROP TABLE unlogged2; -DROP TABLE unlogged1; --- set unlogged -CREATE TABLE logged1(f1 SERIAL PRIMARY KEY, f2 TEXT); -- has sequence, toast --- check relpersistence of a permanent table -SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^logged1' -UNION ALL -SELECT r.relname || ' toast table', t.relkind, t.relpersistence FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid WHERE r.relname ~ '^logged1' -UNION ALL -SELECT r.relname ||' toast index', ri.relkind, ri.relpersistence FROM pg_class r join pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid JOIN pg_class ri ON ri.oid = i.indexrelid WHERE r.relname ~ '^logged1' -ORDER BY relname; - relname | relkind | relpersistence ----------------------+---------+---------------- - logged1 | r | p - logged1 toast index | i | p - logged1 toast table | t | p - logged1_f1_seq | S | p - logged1_pkey | i | p -(5 rows) - -CREATE TABLE logged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged1); -- foreign key -CREATE TABLE logged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged3); -- self-referencing foreign key -ALTER TABLE logged1 SET UNLOGGED; -- fails because a foreign key from a permanent table exists -ERROR: could not change table "logged1" to unlogged because it references logged table "logged2" -ALTER TABLE logged3 SET UNLOGGED; -- skip self-referencing foreign key -ALTER TABLE logged2 SET UNLOGGED; -ALTER TABLE logged1 SET UNLOGGED; --- check relpersistence of a permanent table after changing to unlogged -SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^logged1' -UNION ALL -SELECT r.relname || ' toast table', t.relkind, t.relpersistence FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid WHERE r.relname ~ '^logged1' -UNION ALL -SELECT r.relname || ' toast index', ri.relkind, ri.relpersistence FROM pg_class r join pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid JOIN pg_class ri ON ri.oid = i.indexrelid WHERE r.relname ~ '^logged1' -ORDER BY relname; - relname | relkind | relpersistence ----------------------+---------+---------------- - logged1 | r | u - logged1 toast index | i | u - logged1 toast table | t | u - logged1_f1_seq | S | u - logged1_pkey | i | u -(5 rows) - -ALTER TABLE logged1 SET UNLOGGED; -- silently do nothing -DROP TABLE logged3; -DROP TABLE logged2; -DROP TABLE logged1; --- test ADD COLUMN IF NOT EXISTS -CREATE TABLE test_add_column(c1 integer); -\d test_add_column - Table "public.test_add_column" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - c1 | integer | | | - -ALTER TABLE test_add_column - ADD COLUMN c2 integer; -\d test_add_column - Table "public.test_add_column" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - c1 | integer | | | - c2 | integer | | | - -ALTER TABLE test_add_column - ADD COLUMN c2 integer; -- fail because c2 already exists -ERROR: column "c2" of relation "test_add_column" already exists -ALTER TABLE ONLY test_add_column - ADD COLUMN c2 integer; -- fail because c2 already exists -ERROR: column "c2" of relation "test_add_column" already exists -\d test_add_column - Table "public.test_add_column" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - c1 | integer | | | - c2 | integer | | | - -ALTER TABLE test_add_column - ADD COLUMN IF NOT EXISTS c2 integer; -- skipping because c2 already exists -NOTICE: column "c2" of relation "test_add_column" already exists, skipping -ALTER TABLE ONLY test_add_column - ADD COLUMN IF NOT EXISTS c2 integer; -- skipping because c2 already exists -NOTICE: column "c2" of relation "test_add_column" already exists, skipping -\d test_add_column - Table "public.test_add_column" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - c1 | integer | | | - c2 | integer | | | - -ALTER TABLE test_add_column - ADD COLUMN c2 integer, -- fail because c2 already exists - ADD COLUMN c3 integer primary key; -ERROR: column "c2" of relation "test_add_column" already exists -\d test_add_column - Table "public.test_add_column" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - c1 | integer | | | - c2 | integer | | | - -ALTER TABLE test_add_column - ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists - ADD COLUMN c3 integer primary key; -NOTICE: column "c2" of relation "test_add_column" already exists, skipping -\d test_add_column - Table "public.test_add_column" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - c1 | integer | | | - c2 | integer | | | - c3 | integer | | not null | -Indexes: - "test_add_column_pkey" PRIMARY KEY, btree (c3) - -ALTER TABLE test_add_column - ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists - ADD COLUMN IF NOT EXISTS c3 integer primary key; -- skipping because c3 already exists -NOTICE: column "c2" of relation "test_add_column" already exists, skipping -NOTICE: column "c3" of relation "test_add_column" already exists, skipping -\d test_add_column - Table "public.test_add_column" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - c1 | integer | | | - c2 | integer | | | - c3 | integer | | not null | -Indexes: - "test_add_column_pkey" PRIMARY KEY, btree (c3) - -ALTER TABLE test_add_column - ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists - ADD COLUMN IF NOT EXISTS c3 integer, -- skipping because c3 already exists - ADD COLUMN c4 integer REFERENCES test_add_column; -NOTICE: column "c2" of relation "test_add_column" already exists, skipping -NOTICE: column "c3" of relation "test_add_column" already exists, skipping -\d test_add_column - Table "public.test_add_column" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - c1 | integer | | | - c2 | integer | | | - c3 | integer | | not null | - c4 | integer | | | -Indexes: - "test_add_column_pkey" PRIMARY KEY, btree (c3) -Foreign-key constraints: - "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3) -Referenced by: - TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3) - -ALTER TABLE test_add_column - ADD COLUMN IF NOT EXISTS c4 integer REFERENCES test_add_column; -NOTICE: column "c4" of relation "test_add_column" already exists, skipping -\d test_add_column - Table "public.test_add_column" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - c1 | integer | | | - c2 | integer | | | - c3 | integer | | not null | - c4 | integer | | | -Indexes: - "test_add_column_pkey" PRIMARY KEY, btree (c3) -Foreign-key constraints: - "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3) -Referenced by: - TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3) - -ALTER TABLE test_add_column - ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 8); -\d test_add_column - Table "public.test_add_column" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------------------------------------------- - c1 | integer | | | - c2 | integer | | | - c3 | integer | | not null | - c4 | integer | | | - c5 | integer | | not null | nextval('test_add_column_c5_seq'::regclass) -Indexes: - "test_add_column_pkey" PRIMARY KEY, btree (c3) -Check constraints: - "test_add_column_c5_check" CHECK (c5 > 8) -Foreign-key constraints: - "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3) -Referenced by: - TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3) - -ALTER TABLE test_add_column - ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 10); -NOTICE: column "c5" of relation "test_add_column" already exists, skipping -\d test_add_column* - Table "public.test_add_column" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------------------------------------------- - c1 | integer | | | - c2 | integer | | | - c3 | integer | | not null | - c4 | integer | | | - c5 | integer | | not null | nextval('test_add_column_c5_seq'::regclass) -Indexes: - "test_add_column_pkey" PRIMARY KEY, btree (c3) -Check constraints: - "test_add_column_c5_check" CHECK (c5 > 8) -Foreign-key constraints: - "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3) -Referenced by: - TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3) - - Sequence "public.test_add_column_c5_seq" - Type | Start | Minimum | Maximum | Increment | Cycles? | Cache ----------+-------+---------+------------+-----------+---------+------- - integer | 1 | 1 | 2147483647 | 1 | no | 1 -Owned by: public.test_add_column.c5 - - Index "public.test_add_column_pkey" - Column | Type | Key? | Definition ---------+---------+------+------------ - c3 | integer | yes | c3 -primary key, btree, for table "public.test_add_column" - -DROP TABLE test_add_column; -\d test_add_column* --- assorted cases with multiple ALTER TABLE steps -CREATE TABLE ataddindex(f1 INT); -INSERT INTO ataddindex VALUES (42), (43); -CREATE UNIQUE INDEX ataddindexi0 ON ataddindex(f1); -ALTER TABLE ataddindex - ADD PRIMARY KEY USING INDEX ataddindexi0, - ALTER f1 TYPE BIGINT; -\d ataddindex - Table "public.ataddindex" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+--------- - f1 | bigint | | not null | -Indexes: - "ataddindexi0" PRIMARY KEY, btree (f1) - -DROP TABLE ataddindex; -CREATE TABLE ataddindex(f1 VARCHAR(10)); -INSERT INTO ataddindex(f1) VALUES ('foo'), ('a'); -ALTER TABLE ataddindex - ALTER f1 SET DATA TYPE TEXT, - ADD EXCLUDE ((f1 LIKE 'a') WITH =); -\d ataddindex - Table "public.ataddindex" - Column | Type | Collation | Nullable | Default ---------+------+-----------+----------+--------- - f1 | text | | | -Indexes: - "ataddindex_expr_excl" EXCLUDE USING btree ((f1 ~~ 'a'::text) WITH =) - -DROP TABLE ataddindex; -CREATE TABLE ataddindex(id int, ref_id int); -ALTER TABLE ataddindex - ADD PRIMARY KEY (id), - ADD FOREIGN KEY (ref_id) REFERENCES ataddindex; -\d ataddindex - Table "public.ataddindex" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - id | integer | | not null | - ref_id | integer | | | -Indexes: - "ataddindex_pkey" PRIMARY KEY, btree (id) -Foreign-key constraints: - "ataddindex_ref_id_fkey" FOREIGN KEY (ref_id) REFERENCES ataddindex(id) -Referenced by: - TABLE "ataddindex" CONSTRAINT "ataddindex_ref_id_fkey" FOREIGN KEY (ref_id) REFERENCES ataddindex(id) - -DROP TABLE ataddindex; -CREATE TABLE ataddindex(id int, ref_id int); -ALTER TABLE ataddindex - ADD UNIQUE (id), - ADD FOREIGN KEY (ref_id) REFERENCES ataddindex (id); -\d ataddindex - Table "public.ataddindex" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - id | integer | | | - ref_id | integer | | | -Indexes: - "ataddindex_id_key" UNIQUE CONSTRAINT, btree (id) -Foreign-key constraints: - "ataddindex_ref_id_fkey" FOREIGN KEY (ref_id) REFERENCES ataddindex(id) -Referenced by: - TABLE "ataddindex" CONSTRAINT "ataddindex_ref_id_fkey" FOREIGN KEY (ref_id) REFERENCES ataddindex(id) - -DROP TABLE ataddindex; -CREATE TABLE atnotnull1 (); -ALTER TABLE atnotnull1 - ADD COLUMN a INT, - ALTER a SET NOT NULL; -ALTER TABLE atnotnull1 - ADD COLUMN c INT, - ADD PRIMARY KEY (c); -\d+ atnotnull1 - Table "public.atnotnull1" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+---------+---------+--------------+------------- - a | integer | | not null | | plain | | - c | integer | | not null | | plain | | -Indexes: - "atnotnull1_pkey" PRIMARY KEY, btree (c) - --- cannot drop column that is part of the partition key -CREATE TABLE partitioned ( - a int, - b int -) PARTITION BY RANGE (a, (a+b+1)); -ALTER TABLE partitioned DROP COLUMN a; -ERROR: cannot drop column "a" because it is part of the partition key of relation "partitioned" -ALTER TABLE partitioned ALTER COLUMN a TYPE char(5); -ERROR: cannot alter column "a" because it is part of the partition key of relation "partitioned" -ALTER TABLE partitioned DROP COLUMN b; -ERROR: cannot drop column "b" because it is part of the partition key of relation "partitioned" -ALTER TABLE partitioned ALTER COLUMN b TYPE char(5); -ERROR: cannot alter column "b" because it is part of the partition key of relation "partitioned" --- specifying storage parameters for partitioned tables is not supported -ALTER TABLE partitioned SET (fillfactor=100); -ERROR: cannot specify storage parameters for a partitioned table -HINT: Specify storage parameters for its leaf partitions instead. --- partitioned table cannot participate in regular inheritance -CREATE TABLE nonpartitioned ( - a int, - b int -); -ALTER TABLE partitioned INHERIT nonpartitioned; -ERROR: cannot change inheritance of partitioned table -ALTER TABLE nonpartitioned INHERIT partitioned; -ERROR: cannot inherit from partitioned table "partitioned" --- cannot add NO INHERIT constraint to partitioned tables -ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT; -ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned" -DROP TABLE partitioned, nonpartitioned; --- --- ATTACH PARTITION --- --- check that target table is partitioned -CREATE TABLE unparted ( - a int -); -CREATE TABLE fail_part (like unparted); -ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a'); -ERROR: table "unparted" is not partitioned -DROP TABLE unparted, fail_part; --- check that partition bound is compatible -CREATE TABLE list_parted ( - a int NOT NULL, - b char(2) COLLATE "C", - CONSTRAINT check_a CHECK (a > 0) -) PARTITION BY LIST (a); -CREATE TABLE fail_part (LIKE list_parted); -ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) TO (10); -ERROR: invalid bound specification for a list partition -LINE 1: ...list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) T... - ^ -DROP TABLE fail_part; --- check that the table being attached exists -ALTER TABLE list_parted ATTACH PARTITION nonexistent FOR VALUES IN (1); -ERROR: relation "nonexistent" does not exist --- check ownership of the source table -CREATE ROLE regress_test_me; -CREATE ROLE regress_test_not_me; -CREATE TABLE not_owned_by_me (LIKE list_parted); -ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me; -SET SESSION AUTHORIZATION regress_test_me; -CREATE TABLE owned_by_me ( - a int -) PARTITION BY LIST (a); -ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1); -ERROR: must be owner of table not_owned_by_me -RESET SESSION AUTHORIZATION; -DROP TABLE owned_by_me, not_owned_by_me; -DROP ROLE regress_test_not_me; -DROP ROLE regress_test_me; --- check that the table being attached is not part of regular inheritance -CREATE TABLE parent (LIKE list_parted); -CREATE TABLE child () INHERITS (parent); -ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1); -ERROR: cannot attach inheritance child as partition -ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1); -ERROR: cannot attach inheritance parent as partition -DROP TABLE child; --- now it should work, with a little tweak -ALTER TABLE parent ADD CONSTRAINT check_a CHECK (a > 0); -ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1); --- test insert/update, per bug #18550 -INSERT INTO parent VALUES (1); -UPDATE parent SET a = 2 WHERE a = 1; -ERROR: new row for relation "parent" violates partition constraint -DETAIL: Failing row contains (2, null). -DROP TABLE parent CASCADE; --- check any TEMP-ness -CREATE TEMP TABLE temp_parted (a int) PARTITION BY LIST (a); -CREATE TABLE perm_part (a int); -ALTER TABLE temp_parted ATTACH PARTITION perm_part FOR VALUES IN (1); -ERROR: cannot attach a permanent relation as partition of temporary relation "temp_parted" -DROP TABLE temp_parted, perm_part; --- check that the table being attached is not a typed table -CREATE TYPE mytype AS (a int); -CREATE TABLE fail_part OF mytype; -ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); -ERROR: cannot attach a typed table as partition -DROP TYPE mytype CASCADE; -NOTICE: drop cascades to table fail_part --- check that the table being attached has only columns present in the parent -CREATE TABLE fail_part (like list_parted, c int); -ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); -ERROR: table "fail_part" contains column "c" not found in parent "list_parted" -DETAIL: The new partition may contain only the columns present in parent. -DROP TABLE fail_part; --- check that the table being attached has every column of the parent -CREATE TABLE fail_part (a int NOT NULL); -ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); -ERROR: child table is missing column "b" -DROP TABLE fail_part; --- check that columns match in type, collation and NOT NULL status -CREATE TABLE fail_part ( - b char(3), - a int NOT NULL -); -ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); -ERROR: child table "fail_part" has different type for column "b" -ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "POSIX"; -ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); -ERROR: child table "fail_part" has different collation for column "b" -DROP TABLE fail_part; --- check that the table being attached has all constraints of the parent -CREATE TABLE fail_part ( - b char(2) COLLATE "C", - a int NOT NULL -); -ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); -ERROR: child table is missing constraint "check_a" --- check that the constraint matches in definition with parent's constraint -ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0); -ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); -ERROR: child table "fail_part" has different definition for check constraint "check_a" -DROP TABLE fail_part; --- check the attributes and constraints after partition is attached -CREATE TABLE part_1 ( - a int NOT NULL, - b char(2) COLLATE "C", - CONSTRAINT check_a CHECK (a > 0) -); -ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1); --- attislocal and conislocal are always false for merged attributes and constraints respectively. -SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0; - attislocal | attinhcount -------------+------------- - f | 1 - f | 1 -(2 rows) - -SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a'; - conislocal | coninhcount -------------+------------- - f | 1 -(1 row) - --- check that the new partition won't overlap with an existing partition -CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS); -ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); -ERROR: partition "fail_part" would overlap partition "part_1" -LINE 1: ...LE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); - ^ -DROP TABLE fail_part; --- check that an existing table can be attached as a default partition -CREATE TABLE def_part (LIKE list_parted INCLUDING CONSTRAINTS); -ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT; --- check attaching default partition fails if a default partition already --- exists -CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS); -ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT; -ERROR: partition "fail_def_part" conflicts with existing default partition "def_part" -LINE 1: ...ER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT; - ^ --- check validation when attaching list partitions -CREATE TABLE list_parted2 ( - a int, - b char -) PARTITION BY LIST (a); --- check that violating rows are correctly reported -CREATE TABLE part_2 (LIKE list_parted2); -INSERT INTO part_2 VALUES (3, 'a'); -ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2); -ERROR: partition constraint of relation "part_2" is violated by some row --- should be ok after deleting the bad row -DELETE FROM part_2; -ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2); --- check partition cannot be attached if default has some row for its values -CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT; -INSERT INTO list_parted2_def VALUES (11, 'z'); -CREATE TABLE part_3 (LIKE list_parted2); -ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11); -ERROR: updated partition constraint for default partition "list_parted2_def" would be violated by some row --- should be ok after deleting the bad row -DELETE FROM list_parted2_def WHERE a = 11; -ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11); --- adding constraints that describe the desired partition constraint --- (or more restrictive) will help skip the validation scan -CREATE TABLE part_3_4 ( - LIKE list_parted2, - CONSTRAINT check_a CHECK (a IN (3)) -); --- however, if a list partition does not accept nulls, there should be --- an explicit NOT NULL constraint on the partition key column for the --- validation scan to be skipped; -ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4); --- adding a NOT NULL constraint will cause the scan to be skipped -ALTER TABLE list_parted2 DETACH PARTITION part_3_4; -ALTER TABLE part_3_4 ALTER a SET NOT NULL; -ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4); --- check if default partition scan skipped -ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6)); -CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66); --- check validation when attaching range partitions -CREATE TABLE range_parted ( - a int, - b int -) PARTITION BY RANGE (a, b); --- check that violating rows are correctly reported -CREATE TABLE part1 ( - a int NOT NULL CHECK (a = 1), - b int NOT NULL CHECK (b >= 1 AND b <= 10) -); -INSERT INTO part1 VALUES (1, 10); --- Remember the TO bound is exclusive -ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10); -ERROR: partition constraint of relation "part1" is violated by some row --- should be ok after deleting the bad row -DELETE FROM part1; -ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10); --- adding constraints that describe the desired partition constraint --- (or more restrictive) will help skip the validation scan -CREATE TABLE part2 ( - a int NOT NULL CHECK (a = 1), - b int NOT NULL CHECK (b >= 10 AND b < 18) -); -ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20); --- Create default partition -CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT; --- Only one default partition is allowed, hence, following should give error -CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS); -ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT; -ERROR: partition "partr_def2" conflicts with existing default partition "partr_def1" -LINE 1: ...LTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT; - ^ --- Overlapping partitions cannot be attached, hence, following should give error -INSERT INTO partr_def1 VALUES (2, 10); -CREATE TABLE part3 (LIKE range_parted); -ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20); -ERROR: updated partition constraint for default partition "partr_def1" would be violated by some row --- Attaching partitions should be successful when there are no overlapping rows -ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20); --- check that leaf partitions are scanned when attaching a partitioned --- table -CREATE TABLE part_5 ( - LIKE list_parted2 -) PARTITION BY LIST (b); --- check that violating rows are correctly reported -CREATE TABLE part_5_a PARTITION OF part_5 FOR VALUES IN ('a'); -INSERT INTO part_5_a (a, b) VALUES (6, 'a'); -ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5); -ERROR: partition constraint of relation "part_5_a" is violated by some row --- delete the faulting row and also add a constraint to skip the scan -DELETE FROM part_5_a WHERE a NOT IN (3); -ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 5); -ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5); -ALTER TABLE list_parted2 DETACH PARTITION part_5; -ALTER TABLE part_5 DROP CONSTRAINT check_a; --- scan should again be skipped, even though NOT NULL is now a column property -ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL; -ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5); --- Check the case where attnos of the partitioning columns in the table being --- attached differs from the parent. It should not affect the constraint- --- checking logic that allows to skip the scan. -CREATE TABLE part_6 ( - c int, - LIKE list_parted2, - CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 6) -); -ALTER TABLE part_6 DROP c; -ALTER TABLE list_parted2 ATTACH PARTITION part_6 FOR VALUES IN (6); --- Similar to above, but the table being attached is a partitioned table --- whose partition has still different attnos for the root partitioning --- columns. -CREATE TABLE part_7 ( - LIKE list_parted2, - CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 7) -) PARTITION BY LIST (b); -CREATE TABLE part_7_a_null ( - c int, - d int, - e int, - LIKE list_parted2, -- 'a' will have attnum = 4 - CONSTRAINT check_b CHECK (b IS NULL OR b = 'a'), - CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 7) -); -ALTER TABLE part_7_a_null DROP c, DROP d, DROP e; -ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN ('a', null); -ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7); --- Same example, but check this time that the constraint correctly detects --- violating rows -ALTER TABLE list_parted2 DETACH PARTITION part_7; -ALTER TABLE part_7 DROP CONSTRAINT check_a; -- thusly, scan won't be skipped -INSERT INTO part_7 (a, b) VALUES (8, null), (9, 'a'); -SELECT tableoid::regclass, a, b FROM part_7 order by a; - tableoid | a | b ----------------+---+--- - part_7_a_null | 8 | - part_7_a_null | 9 | a -(2 rows) - -ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7); -ERROR: partition constraint of relation "part_7_a_null" is violated by some row --- check that leaf partitions of default partition are scanned when --- attaching a partitioned table. -ALTER TABLE part_5 DROP CONSTRAINT check_a; -CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a); -CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5); -INSERT INTO part5_def_p1 VALUES (5, 'y'); -CREATE TABLE part5_p1 (LIKE part_5); -ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y'); -ERROR: updated partition constraint for default partition "part5_def_p1" would be violated by some row --- should be ok after deleting the bad row -DELETE FROM part5_def_p1 WHERE b = 'y'; -ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y'); --- check that the table being attached is not already a partition -ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2); -ERROR: "part_2" is already a partition --- check that circular inheritance is not allowed -ALTER TABLE part_5 ATTACH PARTITION list_parted2 FOR VALUES IN ('b'); -ERROR: circular inheritance not allowed -DETAIL: "part_5" is already a child of "list_parted2". -ALTER TABLE list_parted2 ATTACH PARTITION list_parted2 FOR VALUES IN (0); -ERROR: circular inheritance not allowed -DETAIL: "list_parted2" is already a child of "list_parted2". --- If a partitioned table being created or an existing table being attached --- as a partition does not have a constraint that would allow validation scan --- to be skipped, but an individual partition does, then the partition's --- validation scan is skipped. -CREATE TABLE quuux (a int, b text) PARTITION BY LIST (a); -CREATE TABLE quuux_default PARTITION OF quuux DEFAULT PARTITION BY LIST (b); -CREATE TABLE quuux_default1 PARTITION OF quuux_default ( - CONSTRAINT check_1 CHECK (a IS NOT NULL AND a = 1) -) FOR VALUES IN ('b'); -CREATE TABLE quuux1 (a int, b text); -ALTER TABLE quuux ATTACH PARTITION quuux1 FOR VALUES IN (1); -- validate! -CREATE TABLE quuux2 (a int, b text); -ALTER TABLE quuux ATTACH PARTITION quuux2 FOR VALUES IN (2); -- skip validation -DROP TABLE quuux1, quuux2; --- should validate for quuux1, but not for quuux2 -CREATE TABLE quuux1 PARTITION OF quuux FOR VALUES IN (1); -CREATE TABLE quuux2 PARTITION OF quuux FOR VALUES IN (2); -DROP TABLE quuux; --- check validation when attaching hash partitions --- Use hand-rolled hash functions and operator class to get predictable result --- on different machines. part_test_int4_ops is defined in test_setup.sql. --- check that the new partition won't overlap with an existing partition -CREATE TABLE hash_parted ( - a int, - b int -) PARTITION BY HASH (a part_test_int4_ops); -CREATE TABLE hpart_1 PARTITION OF hash_parted FOR VALUES WITH (MODULUS 4, REMAINDER 0); -CREATE TABLE fail_part (LIKE hpart_1); -ALTER TABLE hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODULUS 8, REMAINDER 4); -ERROR: partition "fail_part" would overlap partition "hpart_1" -LINE 1: ...hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODU... - ^ -ALTER TABLE hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODULUS 8, REMAINDER 0); -ERROR: partition "fail_part" would overlap partition "hpart_1" -LINE 1: ...hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODU... - ^ -DROP TABLE fail_part; --- check validation when attaching hash partitions --- check that violating rows are correctly reported -CREATE TABLE hpart_2 (LIKE hash_parted); -INSERT INTO hpart_2 VALUES (3, 0); -ALTER TABLE hash_parted ATTACH PARTITION hpart_2 FOR VALUES WITH (MODULUS 4, REMAINDER 1); -ERROR: partition constraint of relation "hpart_2" is violated by some row --- should be ok after deleting the bad row -DELETE FROM hpart_2; -ALTER TABLE hash_parted ATTACH PARTITION hpart_2 FOR VALUES WITH (MODULUS 4, REMAINDER 1); --- check that leaf partitions are scanned when attaching a partitioned --- table -CREATE TABLE hpart_5 ( - LIKE hash_parted -) PARTITION BY LIST (b); --- check that violating rows are correctly reported -CREATE TABLE hpart_5_a PARTITION OF hpart_5 FOR VALUES IN ('1', '2', '3'); -INSERT INTO hpart_5_a (a, b) VALUES (7, 1); -ALTER TABLE hash_parted ATTACH PARTITION hpart_5 FOR VALUES WITH (MODULUS 4, REMAINDER 2); -ERROR: partition constraint of relation "hpart_5_a" is violated by some row --- should be ok after deleting the bad row -DELETE FROM hpart_5_a; -ALTER TABLE hash_parted ATTACH PARTITION hpart_5 FOR VALUES WITH (MODULUS 4, REMAINDER 2); --- check that the table being attach is with valid modulus and remainder value -CREATE TABLE fail_part(LIKE hash_parted); -ALTER TABLE hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODULUS 0, REMAINDER 1); -ERROR: modulus for hash partition must be an integer value greater than zero -ALTER TABLE hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODULUS 8, REMAINDER 8); -ERROR: remainder for hash partition must be less than modulus -ALTER TABLE hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODULUS 3, REMAINDER 2); -ERROR: every hash partition modulus must be a factor of the next larger modulus -DETAIL: The new modulus 3 is not a factor of 4, the modulus of existing partition "hpart_1". -DROP TABLE fail_part; --- --- DETACH PARTITION --- --- check that the table is partitioned at all -CREATE TABLE regular_table (a int); -ALTER TABLE regular_table DETACH PARTITION any_name; -ERROR: table "regular_table" is not partitioned -DROP TABLE regular_table; --- check that the partition being detached exists at all -ALTER TABLE list_parted2 DETACH PARTITION part_4; -ERROR: relation "part_4" does not exist -ALTER TABLE hash_parted DETACH PARTITION hpart_4; -ERROR: relation "hpart_4" does not exist --- check that the partition being detached is actually a partition of the parent -CREATE TABLE not_a_part (a int); -ALTER TABLE list_parted2 DETACH PARTITION not_a_part; -ERROR: relation "not_a_part" is not a partition of relation "list_parted2" -ALTER TABLE list_parted2 DETACH PARTITION part_1; -ERROR: relation "part_1" is not a partition of relation "list_parted2" -ALTER TABLE hash_parted DETACH PARTITION not_a_part; -ERROR: relation "not_a_part" is not a partition of relation "hash_parted" -DROP TABLE not_a_part; --- check that, after being detached, attinhcount/coninhcount is dropped to 0 and --- attislocal/conislocal is set to true -ALTER TABLE list_parted2 DETACH PARTITION part_3_4; -SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3_4'::regclass AND attnum > 0; - attinhcount | attislocal --------------+------------ - 0 | t - 0 | t -(2 rows) - -SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3_4'::regclass AND conname = 'check_a'; - coninhcount | conislocal --------------+------------ - 0 | t -(1 row) - -DROP TABLE part_3_4; --- check that a detached partition is not dropped on dropping a partitioned table -CREATE TABLE range_parted2 ( - a int -) PARTITION BY RANGE(a); -CREATE TABLE part_rp PARTITION OF range_parted2 FOR VALUES FROM (0) to (100); -ALTER TABLE range_parted2 DETACH PARTITION part_rp; -DROP TABLE range_parted2; -SELECT * from part_rp; - a ---- -(0 rows) - -DROP TABLE part_rp; --- concurrent detach -CREATE TABLE range_parted2 ( - a int -) PARTITION BY RANGE(a); -CREATE TABLE part_rp PARTITION OF range_parted2 FOR VALUES FROM (0) to (100); -BEGIN; --- doesn't work in a partition block -ALTER TABLE range_parted2 DETACH PARTITION part_rp CONCURRENTLY; -ERROR: ALTER TABLE ... DETACH CONCURRENTLY cannot run inside a transaction block -COMMIT; -CREATE TABLE part_rpd PARTITION OF range_parted2 DEFAULT; --- doesn't work if there's a default partition -ALTER TABLE range_parted2 DETACH PARTITION part_rp CONCURRENTLY; -ERROR: cannot detach partitions concurrently when a default partition exists --- doesn't work for the default partition -ALTER TABLE range_parted2 DETACH PARTITION part_rpd CONCURRENTLY; -ERROR: cannot detach partitions concurrently when a default partition exists -DROP TABLE part_rpd; --- works fine -ALTER TABLE range_parted2 DETACH PARTITION part_rp CONCURRENTLY; -\d+ range_parted2 - Partitioned table "public.range_parted2" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+---------+---------+--------------+------------- - a | integer | | | | plain | | -Partition key: RANGE (a) -Number of partitions: 0 - --- constraint should be created -\d part_rp - Table "public.part_rp" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | -Check constraints: - "part_rp_a_check" CHECK (a IS NOT NULL AND a >= 0 AND a < 100) - -CREATE TABLE part_rp100 PARTITION OF range_parted2 (CHECK (a>=123 AND a<133 AND a IS NOT NULL)) FOR VALUES FROM (100) to (200); -ALTER TABLE range_parted2 DETACH PARTITION part_rp100 CONCURRENTLY; --- redundant constraint should not be created -\d part_rp100 - Table "public.part_rp100" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | -Check constraints: - "part_rp100_a_check" CHECK (a >= 123 AND a < 133 AND a IS NOT NULL) - -DROP TABLE range_parted2; --- Check ALTER TABLE commands for partitioned tables and partitions --- cannot add/drop column to/from *only* the parent -ALTER TABLE ONLY list_parted2 ADD COLUMN c int; -ERROR: column must be added to child tables too -ALTER TABLE ONLY list_parted2 DROP COLUMN b; -ERROR: cannot drop column from only the partitioned table when partitions exist -HINT: Do not specify the ONLY keyword. --- cannot add a column to partition or drop an inherited one -ALTER TABLE part_2 ADD COLUMN c text; -ERROR: cannot add column to a partition -ALTER TABLE part_2 DROP COLUMN b; -ERROR: cannot drop inherited column "b" --- Nor rename, alter type -ALTER TABLE part_2 RENAME COLUMN b to c; -ERROR: cannot rename inherited column "b" -ALTER TABLE part_2 ALTER COLUMN b TYPE text; -ERROR: cannot alter inherited column "b" --- cannot add/drop NOT NULL or check constraints to *only* the parent, when --- partitions exist -ALTER TABLE ONLY list_parted2 ALTER b SET NOT NULL; -ERROR: constraint must be added to child tables too -DETAIL: Column "b" of relation "part_2" is not already NOT NULL. -HINT: Do not specify the ONLY keyword. -ALTER TABLE ONLY list_parted2 ADD CONSTRAINT check_b CHECK (b <> 'zz'); -ERROR: constraint must be added to child tables too -ALTER TABLE list_parted2 ALTER b SET NOT NULL; -ALTER TABLE ONLY list_parted2 ALTER b DROP NOT NULL; -ERROR: cannot remove constraint from only the partitioned table when partitions exist -HINT: Do not specify the ONLY keyword. -ALTER TABLE list_parted2 ADD CONSTRAINT check_b CHECK (b <> 'zz'); -ALTER TABLE ONLY list_parted2 DROP CONSTRAINT check_b; -ERROR: cannot remove constraint from only the partitioned table when partitions exist -HINT: Do not specify the ONLY keyword. --- It's alright though, if no partitions are yet created -CREATE TABLE parted_no_parts (a int) PARTITION BY LIST (a); -ALTER TABLE ONLY parted_no_parts ALTER a SET NOT NULL; -ALTER TABLE ONLY parted_no_parts ADD CONSTRAINT check_a CHECK (a > 0); -ALTER TABLE ONLY parted_no_parts ALTER a DROP NOT NULL; -ALTER TABLE ONLY parted_no_parts DROP CONSTRAINT check_a; -DROP TABLE parted_no_parts; --- cannot drop inherited NOT NULL or check constraints from partition -ALTER TABLE list_parted2 ALTER b SET NOT NULL, ADD CONSTRAINT check_a2 CHECK (a > 0); -ALTER TABLE part_2 ALTER b DROP NOT NULL; -ERROR: column "b" is marked NOT NULL in parent table -ALTER TABLE part_2 DROP CONSTRAINT check_a2; -ERROR: cannot drop inherited constraint "check_a2" of relation "part_2" --- Doesn't make sense to add NO INHERIT constraints on partitioned tables -ALTER TABLE list_parted2 add constraint check_b2 check (b <> 'zz') NO INHERIT; -ERROR: cannot add NO INHERIT constraint to partitioned table "list_parted2" --- check that a partition cannot participate in regular inheritance -CREATE TABLE inh_test () INHERITS (part_2); -ERROR: cannot inherit from partition "part_2" -CREATE TABLE inh_test (LIKE part_2); -ALTER TABLE inh_test INHERIT part_2; -ERROR: cannot inherit from a partition -ALTER TABLE part_2 INHERIT inh_test; -ERROR: cannot change inheritance of a partition --- cannot drop or alter type of partition key columns of lower level --- partitioned tables; for example, part_5, which is list_parted2's --- partition, is partitioned on b; -ALTER TABLE list_parted2 DROP COLUMN b; -ERROR: cannot drop column "b" because it is part of the partition key of relation "part_5" -ALTER TABLE list_parted2 ALTER COLUMN b TYPE text; -ERROR: cannot alter column "b" because it is part of the partition key of relation "part_5" --- dropping non-partition key columns should be allowed on the parent table. -ALTER TABLE list_parted DROP COLUMN b; -SELECT * FROM list_parted; - a ---- -(0 rows) - --- cleanup -DROP TABLE list_parted, list_parted2, range_parted; -DROP TABLE fail_def_part; -DROP TABLE hash_parted; --- more tests for certain multi-level partitioning scenarios -create table p (a int, b int) partition by range (a, b); -create table p1 (b int, a int not null) partition by range (b); -create table p11 (like p1); -alter table p11 drop a; -alter table p11 add a int; -alter table p11 drop a; -alter table p11 add a int not null; --- attnum for key attribute 'a' is different in p, p1, and p11 -select attrelid::regclass, attname, attnum -from pg_attribute -where attname = 'a' - and (attrelid = 'p'::regclass - or attrelid = 'p1'::regclass - or attrelid = 'p11'::regclass) -order by attrelid::regclass::text; - attrelid | attname | attnum -----------+---------+-------- - p | a | 1 - p1 | a | 2 - p11 | a | 4 -(3 rows) - -alter table p1 attach partition p11 for values from (2) to (5); -insert into p1 (a, b) values (2, 3); --- check that partition validation scan correctly detects violating rows -alter table p attach partition p1 for values from (1, 2) to (1, 10); -ERROR: partition constraint of relation "p11" is violated by some row --- cleanup -drop table p; -drop table p1; --- validate constraint on partitioned tables should only scan leaf partitions -create table parted_validate_test (a int) partition by list (a); -create table parted_validate_test_1 partition of parted_validate_test for values in (0, 1); -alter table parted_validate_test add constraint parted_validate_test_chka check (a > 0) not valid; -alter table parted_validate_test validate constraint parted_validate_test_chka; -drop table parted_validate_test; --- test alter column options -CREATE TABLE attmp(i integer); -INSERT INTO attmp VALUES (1); -ALTER TABLE attmp ALTER COLUMN i SET (n_distinct = 1, n_distinct_inherited = 2); -ALTER TABLE attmp ALTER COLUMN i RESET (n_distinct_inherited); -ANALYZE attmp; -DROP TABLE attmp; -DROP USER regress_alter_table_user1; --- check that violating rows are correctly reported when attaching as the --- default partition -create table defpart_attach_test (a int) partition by list (a); -create table defpart_attach_test1 partition of defpart_attach_test for values in (1); -create table defpart_attach_test_d (b int, a int); -alter table defpart_attach_test_d drop b; -insert into defpart_attach_test_d values (1), (2); --- error because its constraint as the default partition would be violated --- by the row containing 1 -alter table defpart_attach_test attach partition defpart_attach_test_d default; -ERROR: partition constraint of relation "defpart_attach_test_d" is violated by some row -delete from defpart_attach_test_d where a = 1; -alter table defpart_attach_test_d add check (a > 1); --- should be attached successfully and without needing to be scanned -alter table defpart_attach_test attach partition defpart_attach_test_d default; --- check that attaching a partition correctly reports any rows in the default --- partition that should not be there for the new partition to be attached --- successfully -create table defpart_attach_test_2 (like defpart_attach_test_d); -alter table defpart_attach_test attach partition defpart_attach_test_2 for values in (2); -ERROR: updated partition constraint for default partition "defpart_attach_test_d" would be violated by some row -drop table defpart_attach_test; --- check combinations of temporary and permanent relations when attaching --- partitions. -create table perm_part_parent (a int) partition by list (a); -create temp table temp_part_parent (a int) partition by list (a); -create table perm_part_child (a int); -create temp table temp_part_child (a int); -alter table temp_part_parent attach partition perm_part_child default; -- error -ERROR: cannot attach a permanent relation as partition of temporary relation "temp_part_parent" -alter table perm_part_parent attach partition temp_part_child default; -- error -ERROR: cannot attach a temporary relation as partition of permanent relation "perm_part_parent" -alter table temp_part_parent attach partition temp_part_child default; -- ok -drop table perm_part_parent cascade; -drop table temp_part_parent cascade; --- check that attaching partitions to a table while it is being used is --- prevented -create table tab_part_attach (a int) partition by list (a); -create or replace function func_part_attach() returns trigger - language plpgsql as $$ - begin - execute 'create table tab_part_attach_1 (a int)'; - execute 'alter table tab_part_attach attach partition tab_part_attach_1 for values in (1)'; - return null; - end $$; -create trigger trig_part_attach before insert on tab_part_attach - for each statement execute procedure func_part_attach(); -insert into tab_part_attach values (1); -ERROR: cannot ALTER TABLE "tab_part_attach" because it is being used by active queries in this session -CONTEXT: SQL statement "alter table tab_part_attach attach partition tab_part_attach_1 for values in (1)" -PL/pgSQL function func_part_attach() line 4 at EXECUTE -drop table tab_part_attach; -drop function func_part_attach(); --- test case where the partitioning operator is a SQL function whose --- evaluation results in the table's relcache being rebuilt partway through --- the execution of an ATTACH PARTITION command -create function at_test_sql_partop (int4, int4) returns int language sql -as $$ select case when $1 = $2 then 0 when $1 > $2 then 1 else -1 end; $$; -create operator class at_test_sql_partop for type int4 using btree as - operator 1 < (int4, int4), operator 2 <= (int4, int4), - operator 3 = (int4, int4), operator 4 >= (int4, int4), - operator 5 > (int4, int4), function 1 at_test_sql_partop(int4, int4); -create table at_test_sql_partop (a int) partition by range (a at_test_sql_partop); -create table at_test_sql_partop_1 (a int); -alter table at_test_sql_partop attach partition at_test_sql_partop_1 for values from (0) to (10); -drop table at_test_sql_partop; -drop operator class at_test_sql_partop using btree; -drop function at_test_sql_partop; -/* Test case for bug #16242 */ --- We create a parent and child where the child has missing --- non-null attribute values, and arrange to pass them through --- tuple conversion from the child to the parent tupdesc -create table bar1 (a integer, b integer not null default 1) - partition by range (a); -create table bar2 (a integer); -insert into bar2 values (1); -alter table bar2 add column b integer not null default 1; --- (at this point bar2 contains tuple with natts=1) -alter table bar1 attach partition bar2 default; --- this works: -select * from bar1; - a | b ----+--- - 1 | 1 -(1 row) - --- this exercises tuple conversion: -create function xtrig() - returns trigger language plpgsql -as $$ - declare - r record; - begin - for r in select * from old loop - raise info 'a=%, b=%', r.a, r.b; - end loop; - return NULL; - end; -$$; -create trigger xtrig - after update on bar1 - referencing old table as old - for each statement execute procedure xtrig(); -update bar1 set a = a + 1; -INFO: a=1, b=1 -/* End test case for bug #16242 */ -/* Test case for bug #17409 */ -create table attbl (p1 int constraint pk_attbl primary key); -create table atref (c1 int references attbl(p1)); -cluster attbl using pk_attbl; -alter table attbl alter column p1 set data type bigint; -alter table atref alter column c1 set data type bigint; -drop table attbl, atref; -create table attbl (p1 int constraint pk_attbl primary key); -alter table attbl replica identity using index pk_attbl; -create table atref (c1 int references attbl(p1)); -alter table attbl alter column p1 set data type bigint; -alter table atref alter column c1 set data type bigint; -drop table attbl, atref; -/* End test case for bug #17409 */ --- Test that ALTER TABLE rewrite preserves a clustered index --- for normal indexes and indexes on constraints. -create table alttype_cluster (a int); -alter table alttype_cluster add primary key (a); -create index alttype_cluster_ind on alttype_cluster (a); -alter table alttype_cluster cluster on alttype_cluster_ind; --- Normal index remains clustered. -select indexrelid::regclass, indisclustered from pg_index - where indrelid = 'alttype_cluster'::regclass - order by indexrelid::regclass::text; - indexrelid | indisclustered -----------------------+---------------- - alttype_cluster_ind | t - alttype_cluster_pkey | f -(2 rows) - -alter table alttype_cluster alter a type bigint; -select indexrelid::regclass, indisclustered from pg_index - where indrelid = 'alttype_cluster'::regclass - order by indexrelid::regclass::text; - indexrelid | indisclustered -----------------------+---------------- - alttype_cluster_ind | t - alttype_cluster_pkey | f -(2 rows) - --- Constraint index remains clustered. -alter table alttype_cluster cluster on alttype_cluster_pkey; -select indexrelid::regclass, indisclustered from pg_index - where indrelid = 'alttype_cluster'::regclass - order by indexrelid::regclass::text; - indexrelid | indisclustered -----------------------+---------------- - alttype_cluster_ind | f - alttype_cluster_pkey | t -(2 rows) - -alter table alttype_cluster alter a type int; -select indexrelid::regclass, indisclustered from pg_index - where indrelid = 'alttype_cluster'::regclass - order by indexrelid::regclass::text; - indexrelid | indisclustered -----------------------+---------------- - alttype_cluster_ind | f - alttype_cluster_pkey | t -(2 rows) - -drop table alttype_cluster; --- --- Check that attaching or detaching a partitioned partition correctly leads --- to its partitions' constraint being updated to reflect the parent's --- newly added/removed constraint -create table target_parted (a int, b int) partition by list (a); -create table attach_parted (a int, b int) partition by list (b); -create table attach_parted_part1 partition of attach_parted for values in (1); --- insert a row directly into the leaf partition so that its partition --- constraint is built and stored in the relcache -insert into attach_parted_part1 values (1, 1); --- the following better invalidate the partition constraint of the leaf --- partition too... -alter table target_parted attach partition attach_parted for values in (1); --- ...such that the following insert fails -insert into attach_parted_part1 values (2, 1); -ERROR: new row for relation "attach_parted_part1" violates partition constraint -DETAIL: Failing row contains (2, 1). --- ...and doesn't when the partition is detached along with its own partition -alter table target_parted detach partition attach_parted; -insert into attach_parted_part1 values (2, 1); --- Test altering table having publication -create schema alter1; -create schema alter2; -create table alter1.t1 (a int); -set client_min_messages = 'ERROR'; -create publication pub1 for table alter1.t1, tables in schema alter2; -reset client_min_messages; -alter table alter1.t1 set schema alter2; -\d+ alter2.t1 - Table "alter2.t1" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+---------+---------+--------------+------------- - a | integer | | | | plain | | -Publications: - "pub1" - -drop publication pub1; -drop schema alter1 cascade; -drop schema alter2 cascade; -NOTICE: drop cascades to table alter2.t1 +WARNING: terminating connection because of crash of another server process +DETAIL: The postmaster has commanded this server process to roll back the current transaction and exit, because another server process exited abnormally and possibly corrupted shared memory. +HINT: In a moment you should be able to reconnect to the database and repeat your command. +server closed the connection unexpectedly + This probably means the server terminated abnormally + before or while processing the request. +connection to server was lost diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/xml.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/xml.out --- /tmp/cirrus-ci-build/src/test/regress/expected/xml.out 2024-09-22 06:28:18.781416378 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/xml.out 2024-09-22 06:38:09.780217782 +0000 @@ -1773,96 +1773,10 @@ proargtypes text)) SELECT * FROM z EXCEPT SELECT * FROM x; - proname | proowner | procost | pronargs | proargnames | proargtypes ----------+----------+---------+----------+-------------+------------- -(0 rows) - -CREATE TABLE xmltest2(x xml, _path text); -INSERT INTO xmltest2 VALUES('1', 'A'); -INSERT INTO xmltest2 VALUES('2', 'B'); -INSERT INTO xmltest2 VALUES('3', 'C'); -INSERT INTO xmltest2 VALUES('2', 'D'); -SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c'); - a ---- - 1 - 2 - 3 - 2 -(4 rows) - -SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.'); - a ---- - 1 - 2 - 3 - 2 -(4 rows) - -SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54); - a ----- - 11 - 12 - 13 - 14 -(4 rows) - --- XPath result can be boolean or number too -SELECT * FROM XMLTABLE('*' PASSING 'a' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"', d boolean PATH '. = "a"', e integer PATH 'string-length(.)'); - a | b | c | d | e -----------+---+----+---+--- - a | a | hi | t | 1 -(1 row) - -\x -SELECT * FROM XMLTABLE('*' PASSING 'pre&deeppost' COLUMNS x xml PATH '/e/n2', y xml PATH '/'); --[ RECORD 1 ]----------------------------------------------------------- -x | &deep -y | pre&deeppost+ - | - -\x -SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '""', b xml PATH '""'); - a | b ---------+-------------- - | <foo/> -(1 row) - -SELECT xmltext(NULL); - xmltext ---------- - -(1 row) - -SELECT xmltext(''); - xmltext ---------- - -(1 row) - -SELECT xmltext(' '); - xmltext ---------- - -(1 row) - -SELECT xmltext('foo `$_-+?=*^%!|/\()[]{}'); - xmltext --------------------------- - foo `$_-+?=*^%!|/\()[]{} -(1 row) - -SELECT xmltext('foo & <"bar">'); - xmltext ------------------------------------ - foo & <"bar"> -(1 row) - -SELECT xmltext('x'|| '

73

'::xml || .42 || true || 'j'::char); - xmltext ---------------------------------- - x<P>73</P>0.42truej -(1 row) - +WARNING: terminating connection because of crash of another server process +DETAIL: The postmaster has commanded this server process to roll back the current transaction and exit, because another server process exited abnormally and possibly corrupted shared memory. +HINT: In a moment you should be able to reconnect to the database and repeat your command. +server closed the connection unexpectedly + This probably means the server terminated abnormally + before or while processing the request. +connection to server was lost diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/session_variables.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/session_variables.out --- /tmp/cirrus-ci-build/src/test/regress/expected/session_variables.out 2024-09-22 06:28:18.757291338 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/session_variables.out 2024-09-22 06:38:09.012219470 +0000 @@ -1365,498 +1365,7 @@ PREPARE p1(numeric) AS LET var1 = $1; PREPARE p2 AS SELECT var1; EXECUTE p1(pi() + 100); -EXECUTE p2; - var1 ------------------ - 103.14159265359 -(1 row) - --- prepared plan cache invalidation test -DROP VARIABLE var1; -CREATE VARIABLE var1 numeric; --- should be NULL -EXECUTE p2; - var1 ------- - -(1 row) - -DEALLOCATE p1; -DEALLOCATE p2; -DROP VARIABLE var1; -SET plan_cache_mode TO force_generic_plan; -CREATE VARIABLE var1 numeric[]; -PREPARE p1(int, numeric) AS LET var1[$1] = $2; -LET var1 = '{}'::numeric[]; -EXECUTE p1(1, 10.2); -EXECUTE p1(2, 10.3); -SELECT var1; - var1 -------------- - {10.2,10.3} -(1 row) - -DEALLOCATE p1; -DROP VARIABLE var1; --- temporary variables -CREATE TEMP VARIABLE var1 AS int; --- this view should be temporary -CREATE VIEW var_test_view AS SELECT var1; -NOTICE: view "var_test_view" will be a temporary view -DROP VARIABLE var1 CASCADE; -NOTICE: drop cascades to view var_test_view -BEGIN; - CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP; - LET var1 = 100; - SELECT var1; - var1 ------- - 100 -(1 row) - -COMMIT; --- should be zero -SELECT count(*) FROM pg_variable WHERE varname = 'var1'; - count -------- - 0 -(1 row) - --- should be zero -SELECT count(*) FROM pg_session_variables(); - count -------- - 0 -(1 row) - -BEGIN; - CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP; - LET var1 = 100; - SELECT var1; - var1 ------- - 100 -(1 row) - -ROLLBACK; --- should be zero -SELECT count(*) FROM pg_variable WHERE varname = 'var1'; - count -------- - 0 -(1 row) - --- should be zero -SELECT count(*) FROM pg_session_variables(); - count -------- - 0 -(1 row) - -BEGIN; - CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP; - LET var1 = 100; - DROP VARIABLE var1; -COMMIT; --- should be zero -SELECT count(*) FROM pg_variable WHERE varname = 'var1'; - count -------- - 0 -(1 row) - --- should be zero -SELECT count(*) FROM pg_session_variables(); - count -------- - 0 -(1 row) - -BEGIN; - CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP; - LET var1 = 100; - DROP VARIABLE var1; -ROLLBACK; --- should be zero -SELECT count(*) FROM pg_variable WHERE varname = 'var1'; - count -------- - 0 -(1 row) - --- should be zero -SELECT count(*) FROM pg_session_variables(); - count -------- - 0 -(1 row) - -BEGIN; - CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP; - LET var1 = 100; - SAVEPOINT s1; - DROP VARIABLE var1; - ROLLBACK TO s1; - SELECT var1; - var1 ------- - 100 -(1 row) - -COMMIT; --- should be zero -SELECT count(*) FROM pg_variable WHERE varname = 'var1'; - count -------- - 0 -(1 row) - --- should be zero -SELECT count(*) FROM pg_session_variables(); - count -------- - 0 -(1 row) - -CREATE VARIABLE var1 AS int ON TRANSACTION END RESET; -BEGIN; - LET var1 = 100; - SELECT var1; - var1 ------- - 100 -(1 row) - -COMMIT; --- should be NULL; -SELECT var1 IS NULL; - ?column? ----------- - t -(1 row) - -BEGIN; - LET var1 = 100; - SELECT var1; - var1 ------- - 100 -(1 row) - -ROLLBACK; --- should be NULL -SELECT var1 IS NULL; - ?column? ----------- - t -(1 row) - -DROP VARIABLE var1; -CREATE OR REPLACE FUNCTION vartest_fx() -RETURNS int AS $$ -BEGIN - RAISE NOTICE 'vartest_fx executed'; - RETURN 0; -END; -$$ LANGUAGE plpgsql; -CREATE VARIABLE var1 AS int DEFAULT vartest_fx(); --- vartest_fx should be protected by dep, should fail -DROP FUNCTION vartest_fx(); -ERROR: cannot drop function vartest_fx() because other objects depend on it -DETAIL: session variable var1 depends on function vartest_fx() -HINT: Use DROP ... CASCADE to drop the dependent objects too. --- should be ok -SELECT var1; -NOTICE: vartest_fx executed - var1 ------- - 0 -(1 row) - --- the defexpr should be evaluated only once -SELECT var1; - var1 ------- - 0 -(1 row) - -DISCARD VARIABLES; --- in this case, the defexpr should not be evaluated -LET var1 = 100; -SELECT var1; - var1 ------- - 100 -(1 row) - -DISCARD VARIABLES; -CREATE OR REPLACE FUNCTION vartest_fx() -RETURNS int AS $$ -BEGIN - RAISE EXCEPTION 'vartest_fx is executing'; - RETURN 0; -END; -$$ LANGUAGE plpgsql; --- should to fail, but not to crash -SELECT var1; -ERROR: vartest_fx is executing -CONTEXT: PL/pgSQL function vartest_fx() line 3 at RAISE --- again -SELECT var1; -ERROR: vartest_fx is executing -CONTEXT: PL/pgSQL function vartest_fx() line 3 at RAISE --- but we can write -LET var1 = 100; -SELECT var1; - var1 ------- - 100 -(1 row) - -DROP VARIABLE var1; -DROP FUNCTION vartest_fx(); --- test NOT NULL --- should be ok -CREATE VARIABLE var1 AS int NOT NULL; --- should to fail -SELECT var1; -ERROR: null value is not allowed for NOT NULL session variable "public.var1" -DETAIL: The session variable was not initialized yet. ---should be ok -LET var1 = 10; -SELECT var1; - var1 ------- - 10 -(1 row) - -DROP VARIABLE var1; --- should be ok -CREATE VARIABLE var1 AS int NOT NULL DEFAULT 0; ---should be ok -SELECT var1; - var1 ------- - 0 -(1 row) - --- should be ok -LET var1 = 10; -SELECT var1; - var1 ------- - 10 -(1 row) - -DISCARD VARIABLES; --- should to fail -LET var1 = NULL; -ERROR: null value is not allowed for NOT NULL session variable "public.var1" -DROP VARIABLE var1; --- test NOT NULL -CREATE OR REPLACE FUNCTION vartest_fx() -RETURNS int AS $$ -BEGIN - RETURN NULL; -END; -$$ LANGUAGE plpgsql; -CREATE VARIABLE var1 AS int NOT NULL DEFAULT vartest_fx(); --- should to fail -SELECT var1; -ERROR: null value is not allowed for NOT NULL session variable "public.var1" -DETAIL: The result of DEFAULT expression is NULL. -DISCARD VARIABLES; --- should be ok -LET var1 = 10; -SELECT var1; - var1 ------- - 10 -(1 row) - -CREATE OR REPLACE FUNCTION vartest_fx() -RETURNS int AS $$ -BEGIN - RETURN 0; -END; -$$ LANGUAGE plpgsql; -DISCARD VARIABLES; --- should be ok -SELECT var1; - var1 ------- - 0 -(1 row) - -DROP VARIABLE var1; -DROP FUNCTION vartest_fx(); --- test IMMUTBLE -CREATE IMMUTABLE VARIABLE var1 AS int; --- should be ok -SELECT var1; - var1 ------- - -(1 row) - --- first write should ok --- should be ok -LET var1 = 10; --- should fail -LET var1 = 20; -ERROR: session variable "public.var1" is declared IMMUTABLE -DISCARD VARIABLES; --- should be ok -LET var1 = 10; --- should fail -LET var1 = 20; -ERROR: session variable "public.var1" is declared IMMUTABLE -DISCARD VARIABLES; --- should be ok -SELECT var1; - var1 ------- - -(1 row) - --- should be ok -LET var1 = NULL; --- should fail -LET var1 = 20; -ERROR: session variable "public.var1" is declared IMMUTABLE -DROP VARIABLE var1; -CREATE IMMUTABLE VARIABLE var1 AS int DEFAULT 10; --- don't allow change when variable has DEFAULT value --- should to fail -LET var1 = 20; -ERROR: session variable "public.var1" is declared IMMUTABLE -DISCARD VARIABLES; --- should be ok -SELECT var1; - var1 ------- - 10 -(1 row) - --- should fail -LET var1 = 20; -ERROR: session variable "public.var1" is declared IMMUTABLE -DROP VARIABLE var1; --- should be ok -CREATE IMMUTABLE VARIABLE var1 AS INT NOT NULL DEFAULT 10; --- should to fail -LET var1 = 10; -ERROR: session variable "public.var1" is declared IMMUTABLE -LET var1 = 20; -ERROR: session variable "public.var1" is declared IMMUTABLE --- should be ok -SELECT var1; - var1 ------- - 10 -(1 row) - --- should to fail -LET var1 = 30; -ERROR: session variable "public.var1" is declared IMMUTABLE -DROP VARIABLE var1; --- test transactional variables -CREATE TRANSACTION VARIABLE tv AS int DEFAULT 0; -BEGIN; - LET tv = 100; - SELECT tv; - tv ------ - 100 -(1 row) - -ROLLBACK; -SELECT tv; - tv ----- - 0 -(1 row) - -LET tv = 100; -BEGIN; - LET tv = 1000; -COMMIT; -SELECT tv; - tv ------- - 1000 -(1 row) - -BEGIN; - LET tv = 0; - SELECT tv; - tv ----- - 0 -(1 row) - -ROLLBACK; -SELECT tv; - tv ------- - 1000 -(1 row) - --- test subtransactions -BEGIN; - LET tv = 1; -SAVEPOINT x1; - LET tv = 2; -SAVEPOINT x2; - LET tv = 3; -ROLLBACK TO x2; - SELECT tv; - tv ----- - 2 -(1 row) - - LET tv = 10; -ROLLBACK TO x1; - SELECT tv; - tv ----- - 1 -(1 row) - -ROLLBACK; -SELECT tv; - tv ------- - 1000 -(1 row) - -BEGIN; - LET tv = 1; -SAVEPOINT x1; - LET tv = 2; -SAVEPOINT x2; - LET tv = 3; -ROLLBACK TO x2; - SELECT tv; - tv ----- - 2 -(1 row) - - LET tv = 10; -ROLLBACK TO x1; - SELECT tv; - tv ----- - 1 -(1 row) - -COMMIT; -SELECT tv; - tv ----- - 1 -(1 row) - -DROP VARIABLE tv; +server closed the connection unexpectedly + This probably means the server terminated abnormally + before or while processing the request. +connection to server was lost diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/partition_join.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/partition_join.out --- /tmp/cirrus-ci-build/src/test/regress/expected/partition_join.out 2024-09-22 06:28:18.733166297 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/partition_join.out 2024-09-22 06:38:09.864217598 +0000 @@ -1,5231 +1,2 @@ --- --- PARTITION_JOIN --- Test partitionwise join between partitioned tables --- --- Enable partitionwise join, which by default is disabled. -SET enable_partitionwise_join to true; --- --- partitioned by a single column --- -CREATE TABLE prt1 (a int, b int, c varchar) PARTITION BY RANGE(a); -CREATE TABLE prt1_p1 PARTITION OF prt1 FOR VALUES FROM (0) TO (250); -CREATE TABLE prt1_p3 PARTITION OF prt1 FOR VALUES FROM (500) TO (600); -CREATE TABLE prt1_p2 PARTITION OF prt1 FOR VALUES FROM (250) TO (500); -INSERT INTO prt1 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 2 = 0; -CREATE INDEX iprt1_p1_a on prt1_p1(a); -CREATE INDEX iprt1_p2_a on prt1_p2(a); -CREATE INDEX iprt1_p3_a on prt1_p3(a); -ANALYZE prt1; -CREATE TABLE prt2 (a int, b int, c varchar) PARTITION BY RANGE(b); -CREATE TABLE prt2_p1 PARTITION OF prt2 FOR VALUES FROM (0) TO (250); -CREATE TABLE prt2_p2 PARTITION OF prt2 FOR VALUES FROM (250) TO (500); -CREATE TABLE prt2_p3 PARTITION OF prt2 FOR VALUES FROM (500) TO (600); -INSERT INTO prt2 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 3 = 0; -CREATE INDEX iprt2_p1_b on prt2_p1(b); -CREATE INDEX iprt2_p2_b on prt2_p2(b); -CREATE INDEX iprt2_p3_b on prt2_p3(b); -ANALYZE prt2; --- inner join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN --------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Join - Hash Cond: (t2_1.b = t1_1.a) - -> Seq Scan on prt2_p1 t2_1 - -> Hash - -> Seq Scan on prt1_p1 t1_1 - Filter: (b = 0) - -> Hash Join - Hash Cond: (t2_2.b = t1_2.a) - -> Seq Scan on prt2_p2 t2_2 - -> Hash - -> Seq Scan on prt1_p2 t1_2 - Filter: (b = 0) - -> Hash Join - Hash Cond: (t2_3.b = t1_3.a) - -> Seq Scan on prt2_p3 t2_3 - -> Hash - -> Seq Scan on prt1_p3 t1_3 - Filter: (b = 0) -(21 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 0 | 0000 | 0 | 0000 - 150 | 0150 | 150 | 0150 - 300 | 0300 | 300 | 0300 - 450 | 0450 | 450 | 0450 -(4 rows) - --- inner join with partially-redundant join clauses -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.a AND t1.a = t2.b ORDER BY t1.a, t2.b; - QUERY PLAN ---------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Merge Join - Merge Cond: (t1_1.a = t2_1.a) - -> Index Scan using iprt1_p1_a on prt1_p1 t1_1 - -> Sort - Sort Key: t2_1.b - -> Seq Scan on prt2_p1 t2_1 - Filter: (a = b) - -> Hash Join - Hash Cond: (t1_2.a = t2_2.a) - -> Seq Scan on prt1_p2 t1_2 - -> Hash - -> Seq Scan on prt2_p2 t2_2 - Filter: (a = b) - -> Hash Join - Hash Cond: (t1_3.a = t2_3.a) - -> Seq Scan on prt1_p3 t1_3 - -> Hash - -> Seq Scan on prt2_p3 t2_3 - Filter: (a = b) -(22 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.a AND t1.a = t2.b ORDER BY t1.a, t2.b; - a | c | b | c -----+------+----+------ - 0 | 0000 | 0 | 0000 - 6 | 0006 | 6 | 0006 - 12 | 0012 | 12 | 0012 - 18 | 0018 | 18 | 0018 - 24 | 0024 | 24 | 0024 -(5 rows) - --- left outer join, 3-way -EXPLAIN (COSTS OFF) -SELECT COUNT(*) FROM prt1 t1 - LEFT JOIN prt1 t2 ON t1.a = t2.a - LEFT JOIN prt1 t3 ON t2.a = t3.a; - QUERY PLAN --------------------------------------------------------- - Aggregate - -> Append - -> Hash Left Join - Hash Cond: (t2_1.a = t3_1.a) - -> Hash Left Join - Hash Cond: (t1_1.a = t2_1.a) - -> Seq Scan on prt1_p1 t1_1 - -> Hash - -> Seq Scan on prt1_p1 t2_1 - -> Hash - -> Seq Scan on prt1_p1 t3_1 - -> Hash Left Join - Hash Cond: (t2_2.a = t3_2.a) - -> Hash Left Join - Hash Cond: (t1_2.a = t2_2.a) - -> Seq Scan on prt1_p2 t1_2 - -> Hash - -> Seq Scan on prt1_p2 t2_2 - -> Hash - -> Seq Scan on prt1_p2 t3_2 - -> Hash Left Join - Hash Cond: (t2_3.a = t3_3.a) - -> Hash Left Join - Hash Cond: (t1_3.a = t2_3.a) - -> Seq Scan on prt1_p3 t1_3 - -> Hash - -> Seq Scan on prt1_p3 t2_3 - -> Hash - -> Seq Scan on prt1_p3 t3_3 -(29 rows) - -SELECT COUNT(*) FROM prt1 t1 - LEFT JOIN prt1 t2 ON t1.a = t2.a - LEFT JOIN prt1 t3 ON t2.a = t3.a; - count -------- - 300 -(1 row) - --- left outer join, with whole-row reference; partitionwise join does not apply -EXPLAIN (COSTS OFF) -SELECT t1, t2 FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN --------------------------------------------------- - Sort - Sort Key: t1.a, t2.b - -> Hash Right Join - Hash Cond: (t2.b = t1.a) - -> Append - -> Seq Scan on prt2_p1 t2_1 - -> Seq Scan on prt2_p2 t2_2 - -> Seq Scan on prt2_p3 t2_3 - -> Hash - -> Append - -> Seq Scan on prt1_p1 t1_1 - Filter: (b = 0) - -> Seq Scan on prt1_p2 t1_2 - Filter: (b = 0) - -> Seq Scan on prt1_p3 t1_3 - Filter: (b = 0) -(16 rows) - -SELECT t1, t2 FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; - t1 | t2 ---------------+-------------- - (0,0,0000) | (0,0,0000) - (50,0,0050) | - (100,0,0100) | - (150,0,0150) | (0,150,0150) - (200,0,0200) | - (250,0,0250) | - (300,0,0300) | (0,300,0300) - (350,0,0350) | - (400,0,0400) | - (450,0,0450) | (0,450,0450) - (500,0,0500) | - (550,0,0550) | -(12 rows) - --- right outer join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b; - QUERY PLAN ---------------------------------------------------------------- - Sort - Sort Key: t1.a, t2.b - -> Append - -> Hash Right Join - Hash Cond: (t1_1.a = t2_1.b) - -> Seq Scan on prt1_p1 t1_1 - -> Hash - -> Seq Scan on prt2_p1 t2_1 - Filter: (a = 0) - -> Hash Right Join - Hash Cond: (t1_2.a = t2_2.b) - -> Seq Scan on prt1_p2 t1_2 - -> Hash - -> Seq Scan on prt2_p2 t2_2 - Filter: (a = 0) - -> Nested Loop Left Join - -> Seq Scan on prt2_p3 t2_3 - Filter: (a = 0) - -> Index Scan using iprt1_p3_a on prt1_p3 t1_3 - Index Cond: (a = t2_3.b) -(20 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 0 | 0000 | 0 | 0000 - 150 | 0150 | 150 | 0150 - 300 | 0300 | 300 | 0300 - 450 | 0450 | 450 | 0450 - | | 75 | 0075 - | | 225 | 0225 - | | 375 | 0375 - | | 525 | 0525 -(8 rows) - --- full outer join, with placeholder vars -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.a = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.a OR t2.phv = t2.b ORDER BY t1.a, t2.b; - QUERY PLAN ----------------------------------------------------------------- - Sort - Sort Key: prt1.a, prt2.b - -> Append - -> Hash Full Join - Hash Cond: (prt1_1.a = prt2_1.b) - Filter: (((50) = prt1_1.a) OR ((75) = prt2_1.b)) - -> Seq Scan on prt1_p1 prt1_1 - Filter: (b = 0) - -> Hash - -> Seq Scan on prt2_p1 prt2_1 - Filter: (a = 0) - -> Hash Full Join - Hash Cond: (prt1_2.a = prt2_2.b) - Filter: (((50) = prt1_2.a) OR ((75) = prt2_2.b)) - -> Seq Scan on prt1_p2 prt1_2 - Filter: (b = 0) - -> Hash - -> Seq Scan on prt2_p2 prt2_2 - Filter: (a = 0) - -> Hash Full Join - Hash Cond: (prt1_3.a = prt2_3.b) - Filter: (((50) = prt1_3.a) OR ((75) = prt2_3.b)) - -> Seq Scan on prt1_p3 prt1_3 - Filter: (b = 0) - -> Hash - -> Seq Scan on prt2_p3 prt2_3 - Filter: (a = 0) -(27 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.a = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.a OR t2.phv = t2.b ORDER BY t1.a, t2.b; - a | c | b | c -----+------+----+------ - 50 | 0050 | | - | | 75 | 0075 -(2 rows) - --- Join with pruned partitions from joining relations -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------ - Sort - Sort Key: t1.a - -> Hash Join - Hash Cond: (t2.b = t1.a) - -> Seq Scan on prt2_p2 t2 - Filter: (b > 250) - -> Hash - -> Seq Scan on prt1_p2 t1 - Filter: ((a < 450) AND (b = 0)) -(9 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 300 | 0300 | 300 | 0300 -(1 row) - --- Currently we can't do partitioned join if nullable-side partitions are pruned -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------------ - Sort - Sort Key: prt1.a, prt2.b - -> Hash Right Join - Hash Cond: (prt2.b = prt1.a) - -> Append - -> Seq Scan on prt2_p2 prt2_1 - Filter: (b > 250) - -> Seq Scan on prt2_p3 prt2_2 - Filter: (b > 250) - -> Hash - -> Append - -> Seq Scan on prt1_p1 prt1_1 - Filter: ((a < 450) AND (b = 0)) - -> Seq Scan on prt1_p2 prt1_2 - Filter: ((a < 450) AND (b = 0)) -(15 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 0 | 0000 | | - 50 | 0050 | | - 100 | 0100 | | - 150 | 0150 | | - 200 | 0200 | | - 250 | 0250 | | - 300 | 0300 | 300 | 0300 - 350 | 0350 | | - 400 | 0400 | | -(9 rows) - --- Currently we can't do partitioned join if nullable-side partitions are pruned -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 FULL JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 OR t2.a = 0 ORDER BY t1.a, t2.b; - QUERY PLAN ----------------------------------------------------- - Sort - Sort Key: prt1.a, prt2.b - -> Hash Full Join - Hash Cond: (prt1.a = prt2.b) - Filter: ((prt1.b = 0) OR (prt2.a = 0)) - -> Append - -> Seq Scan on prt1_p1 prt1_1 - Filter: (a < 450) - -> Seq Scan on prt1_p2 prt1_2 - Filter: (a < 450) - -> Hash - -> Append - -> Seq Scan on prt2_p2 prt2_1 - Filter: (b > 250) - -> Seq Scan on prt2_p3 prt2_2 - Filter: (b > 250) -(16 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 FULL JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 OR t2.a = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 0 | 0000 | | - 50 | 0050 | | - 100 | 0100 | | - 150 | 0150 | | - 200 | 0200 | | - 250 | 0250 | | - 300 | 0300 | 300 | 0300 - 350 | 0350 | | - 400 | 0400 | | - | | 375 | 0375 - | | 450 | 0450 - | | 525 | 0525 -(12 rows) - --- Semi-join -EXPLAIN (COSTS OFF) -SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0) AND t1.b = 0 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Semi Join - Hash Cond: (t1_1.a = t2_1.b) - -> Seq Scan on prt1_p1 t1_1 - Filter: (b = 0) - -> Hash - -> Seq Scan on prt2_p1 t2_1 - Filter: (a = 0) - -> Hash Semi Join - Hash Cond: (t1_2.a = t2_2.b) - -> Seq Scan on prt1_p2 t1_2 - Filter: (b = 0) - -> Hash - -> Seq Scan on prt2_p2 t2_2 - Filter: (a = 0) - -> Nested Loop Semi Join - Join Filter: (t1_3.a = t2_3.b) - -> Seq Scan on prt1_p3 t1_3 - Filter: (b = 0) - -> Materialize - -> Seq Scan on prt2_p3 t2_3 - Filter: (a = 0) -(24 rows) - -SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0) AND t1.b = 0 ORDER BY t1.a; - a | b | c ------+---+------ - 0 | 0 | 0000 - 150 | 0 | 0150 - 300 | 0 | 0300 - 450 | 0 | 0450 -(4 rows) - --- Anti-join with aggregates -EXPLAIN (COSTS OFF) -SELECT sum(t1.a), avg(t1.a), sum(t1.b), avg(t1.b) FROM prt1 t1 WHERE NOT EXISTS (SELECT 1 FROM prt2 t2 WHERE t1.a = t2.b); - QUERY PLAN --------------------------------------------------- - Aggregate - -> Append - -> Hash Anti Join - Hash Cond: (t1_1.a = t2_1.b) - -> Seq Scan on prt1_p1 t1_1 - -> Hash - -> Seq Scan on prt2_p1 t2_1 - -> Hash Anti Join - Hash Cond: (t1_2.a = t2_2.b) - -> Seq Scan on prt1_p2 t1_2 - -> Hash - -> Seq Scan on prt2_p2 t2_2 - -> Hash Anti Join - Hash Cond: (t1_3.a = t2_3.b) - -> Seq Scan on prt1_p3 t1_3 - -> Hash - -> Seq Scan on prt2_p3 t2_3 -(17 rows) - -SELECT sum(t1.a), avg(t1.a), sum(t1.b), avg(t1.b) FROM prt1 t1 WHERE NOT EXISTS (SELECT 1 FROM prt2 t2 WHERE t1.a = t2.b); - sum | avg | sum | avg --------+----------------------+------+--------------------- - 60000 | 300.0000000000000000 | 2400 | 12.0000000000000000 -(1 row) - --- lateral reference -EXPLAIN (COSTS OFF) -SELECT * FROM prt1 t1 LEFT JOIN LATERAL - (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.b) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss - ON t1.a = ss.t2a WHERE t1.b = 0 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Nested Loop Left Join - -> Seq Scan on prt1_p1 t1_1 - Filter: (b = 0) - -> Nested Loop - -> Index Only Scan using iprt1_p1_a on prt1_p1 t2_1 - Index Cond: (a = t1_1.a) - -> Index Scan using iprt2_p1_b on prt2_p1 t3_1 - Index Cond: (b = t2_1.a) - -> Nested Loop Left Join - -> Seq Scan on prt1_p2 t1_2 - Filter: (b = 0) - -> Nested Loop - -> Index Only Scan using iprt1_p2_a on prt1_p2 t2_2 - Index Cond: (a = t1_2.a) - -> Index Scan using iprt2_p2_b on prt2_p2 t3_2 - Index Cond: (b = t2_2.a) - -> Nested Loop Left Join - -> Seq Scan on prt1_p3 t1_3 - Filter: (b = 0) - -> Nested Loop - -> Index Only Scan using iprt1_p3_a on prt1_p3 t2_3 - Index Cond: (a = t1_3.a) - -> Index Scan using iprt2_p3_b on prt2_p3 t3_3 - Index Cond: (b = t2_3.a) -(27 rows) - -SELECT * FROM prt1 t1 LEFT JOIN LATERAL - (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.b) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss - ON t1.a = ss.t2a WHERE t1.b = 0 ORDER BY t1.a; - a | b | c | t2a | t3a | least ------+---+------+-----+-----+------- - 0 | 0 | 0000 | 0 | 0 | 0 - 50 | 0 | 0050 | | | - 100 | 0 | 0100 | | | - 150 | 0 | 0150 | 150 | 0 | 150 - 200 | 0 | 0200 | | | - 250 | 0 | 0250 | | | - 300 | 0 | 0300 | 300 | 0 | 300 - 350 | 0 | 0350 | | | - 400 | 0 | 0400 | | | - 450 | 0 | 0450 | 450 | 0 | 450 - 500 | 0 | 0500 | | | - 550 | 0 | 0550 | | | -(12 rows) - -EXPLAIN (COSTS OFF) -SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL - (SELECT t2.a AS t2a, t3.a AS t3a, t2.b t2b, t2.c t2c, least(t1.a,t2.a,t3.b) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss - ON t1.c = ss.t2c WHERE (t1.b + coalesce(ss.t2b, 0)) = 0 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Hash Left Join - Hash Cond: ((t1.c)::text = (t2.c)::text) - Filter: ((t1.b + COALESCE(t2.b, 0)) = 0) - -> Append - -> Seq Scan on prt1_p1 t1_1 - -> Seq Scan on prt1_p2 t1_2 - -> Seq Scan on prt1_p3 t1_3 - -> Hash - -> Append - -> Hash Join - Hash Cond: (t2_1.a = t3_1.b) - -> Seq Scan on prt1_p1 t2_1 - -> Hash - -> Seq Scan on prt2_p1 t3_1 - -> Hash Join - Hash Cond: (t2_2.a = t3_2.b) - -> Seq Scan on prt1_p2 t2_2 - -> Hash - -> Seq Scan on prt2_p2 t3_2 - -> Hash Join - Hash Cond: (t2_3.a = t3_3.b) - -> Seq Scan on prt1_p3 t2_3 - -> Hash - -> Seq Scan on prt2_p3 t3_3 -(26 rows) - -SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL - (SELECT t2.a AS t2a, t3.a AS t3a, t2.b t2b, t2.c t2c, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss - ON t1.c = ss.t2c WHERE (t1.b + coalesce(ss.t2b, 0)) = 0 ORDER BY t1.a; - a | t2a | t2c ------+-----+------ - 0 | 0 | 0000 - 50 | | - 100 | | - 150 | 150 | 0150 - 200 | | - 250 | | - 300 | 300 | 0300 - 350 | | - 400 | | - 450 | 450 | 0450 - 500 | | - 550 | | -(12 rows) - --- lateral reference in sample scan -EXPLAIN (COSTS OFF) -SELECT * FROM prt1 t1 JOIN LATERAL - (SELECT * FROM prt1 t2 TABLESAMPLE SYSTEM (t1.a) REPEATABLE(t1.b)) s - ON t1.a = s.a; - QUERY PLAN -------------------------------------------------------------- - Append - -> Nested Loop - -> Seq Scan on prt1_p1 t1_1 - -> Sample Scan on prt1_p1 t2_1 - Sampling: system (t1_1.a) REPEATABLE (t1_1.b) - Filter: (t1_1.a = a) - -> Nested Loop - -> Seq Scan on prt1_p2 t1_2 - -> Sample Scan on prt1_p2 t2_2 - Sampling: system (t1_2.a) REPEATABLE (t1_2.b) - Filter: (t1_2.a = a) - -> Nested Loop - -> Seq Scan on prt1_p3 t1_3 - -> Sample Scan on prt1_p3 t2_3 - Sampling: system (t1_3.a) REPEATABLE (t1_3.b) - Filter: (t1_3.a = a) -(16 rows) - --- lateral reference in scan's restriction clauses -EXPLAIN (COSTS OFF) -SELECT count(*) FROM prt1 t1 LEFT JOIN LATERAL - (SELECT t1.b AS t1b, t2.* FROM prt2 t2) s - ON t1.a = s.b WHERE s.t1b = s.a; - QUERY PLAN ---------------------------------------------------------------- - Aggregate - -> Append - -> Nested Loop - -> Seq Scan on prt1_p1 t1_1 - -> Index Scan using iprt2_p1_b on prt2_p1 t2_1 - Index Cond: (b = t1_1.a) - Filter: (t1_1.b = a) - -> Nested Loop - -> Seq Scan on prt1_p2 t1_2 - -> Index Scan using iprt2_p2_b on prt2_p2 t2_2 - Index Cond: (b = t1_2.a) - Filter: (t1_2.b = a) - -> Nested Loop - -> Seq Scan on prt1_p3 t1_3 - -> Index Scan using iprt2_p3_b on prt2_p3 t2_3 - Index Cond: (b = t1_3.a) - Filter: (t1_3.b = a) -(17 rows) - -SELECT count(*) FROM prt1 t1 LEFT JOIN LATERAL - (SELECT t1.b AS t1b, t2.* FROM prt2 t2) s - ON t1.a = s.b WHERE s.t1b = s.a; - count -------- - 100 -(1 row) - -EXPLAIN (COSTS OFF) -SELECT count(*) FROM prt1 t1 LEFT JOIN LATERAL - (SELECT t1.b AS t1b, t2.* FROM prt2 t2) s - ON t1.a = s.b WHERE s.t1b = s.b; - QUERY PLAN --------------------------------------------------------------------- - Aggregate - -> Append - -> Nested Loop - -> Seq Scan on prt1_p1 t1_1 - -> Index Only Scan using iprt2_p1_b on prt2_p1 t2_1 - Index Cond: (b = t1_1.a) - Filter: (b = t1_1.b) - -> Nested Loop - -> Seq Scan on prt1_p2 t1_2 - -> Index Only Scan using iprt2_p2_b on prt2_p2 t2_2 - Index Cond: (b = t1_2.a) - Filter: (b = t1_2.b) - -> Nested Loop - -> Seq Scan on prt1_p3 t1_3 - -> Index Only Scan using iprt2_p3_b on prt2_p3 t2_3 - Index Cond: (b = t1_3.a) - Filter: (b = t1_3.b) -(17 rows) - -SELECT count(*) FROM prt1 t1 LEFT JOIN LATERAL - (SELECT t1.b AS t1b, t2.* FROM prt2 t2) s - ON t1.a = s.b WHERE s.t1b = s.b; - count -------- - 5 -(1 row) - --- bug with inadequate sort key representation -SET enable_partitionwise_aggregate TO true; -SET enable_hashjoin TO false; -EXPLAIN (COSTS OFF) -SELECT a, b FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) - WHERE a BETWEEN 490 AND 510 - GROUP BY 1, 2 ORDER BY 1, 2; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------ - Group - Group Key: (COALESCE(prt1.a, p2.a)), (COALESCE(prt1.b, p2.b)) - -> Merge Append - Sort Key: (COALESCE(prt1.a, p2.a)), (COALESCE(prt1.b, p2.b)) - -> Group - Group Key: (COALESCE(prt1.a, p2.a)), (COALESCE(prt1.b, p2.b)) - -> Sort - Sort Key: (COALESCE(prt1.a, p2.a)), (COALESCE(prt1.b, p2.b)) - -> Merge Full Join - Merge Cond: ((prt1.a = p2.a) AND (prt1.b = p2.b)) - Filter: ((COALESCE(prt1.a, p2.a) >= 490) AND (COALESCE(prt1.a, p2.a) <= 510)) - -> Sort - Sort Key: prt1.a, prt1.b - -> Seq Scan on prt1_p1 prt1 - -> Sort - Sort Key: p2.a, p2.b - -> Seq Scan on prt2_p1 p2 - -> Group - Group Key: (COALESCE(prt1_1.a, p2_1.a)), (COALESCE(prt1_1.b, p2_1.b)) - -> Sort - Sort Key: (COALESCE(prt1_1.a, p2_1.a)), (COALESCE(prt1_1.b, p2_1.b)) - -> Merge Full Join - Merge Cond: ((prt1_1.a = p2_1.a) AND (prt1_1.b = p2_1.b)) - Filter: ((COALESCE(prt1_1.a, p2_1.a) >= 490) AND (COALESCE(prt1_1.a, p2_1.a) <= 510)) - -> Sort - Sort Key: prt1_1.a, prt1_1.b - -> Seq Scan on prt1_p2 prt1_1 - -> Sort - Sort Key: p2_1.a, p2_1.b - -> Seq Scan on prt2_p2 p2_1 - -> Group - Group Key: (COALESCE(prt1_2.a, p2_2.a)), (COALESCE(prt1_2.b, p2_2.b)) - -> Sort - Sort Key: (COALESCE(prt1_2.a, p2_2.a)), (COALESCE(prt1_2.b, p2_2.b)) - -> Merge Full Join - Merge Cond: ((prt1_2.a = p2_2.a) AND (prt1_2.b = p2_2.b)) - Filter: ((COALESCE(prt1_2.a, p2_2.a) >= 490) AND (COALESCE(prt1_2.a, p2_2.a) <= 510)) - -> Sort - Sort Key: prt1_2.a, prt1_2.b - -> Seq Scan on prt1_p3 prt1_2 - -> Sort - Sort Key: p2_2.a, p2_2.b - -> Seq Scan on prt2_p3 p2_2 -(43 rows) - -SELECT a, b FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) - WHERE a BETWEEN 490 AND 510 - GROUP BY 1, 2 ORDER BY 1, 2; - a | b ------+---- - 490 | 15 - 492 | 17 - 494 | 19 - 495 | 20 - 496 | 21 - 498 | 23 - 500 | 0 - 501 | 1 - 502 | 2 - 504 | 4 - 506 | 6 - 507 | 7 - 508 | 8 - 510 | 10 -(14 rows) - -RESET enable_partitionwise_aggregate; -RESET enable_hashjoin; --- --- partitioned by expression --- -CREATE TABLE prt1_e (a int, b int, c int) PARTITION BY RANGE(((a + b)/2)); -CREATE TABLE prt1_e_p1 PARTITION OF prt1_e FOR VALUES FROM (0) TO (250); -CREATE TABLE prt1_e_p2 PARTITION OF prt1_e FOR VALUES FROM (250) TO (500); -CREATE TABLE prt1_e_p3 PARTITION OF prt1_e FOR VALUES FROM (500) TO (600); -INSERT INTO prt1_e SELECT i, i, i % 25 FROM generate_series(0, 599, 2) i; -CREATE INDEX iprt1_e_p1_ab2 on prt1_e_p1(((a+b)/2)); -CREATE INDEX iprt1_e_p2_ab2 on prt1_e_p2(((a+b)/2)); -CREATE INDEX iprt1_e_p3_ab2 on prt1_e_p3(((a+b)/2)); -ANALYZE prt1_e; -CREATE TABLE prt2_e (a int, b int, c int) PARTITION BY RANGE(((b + a)/2)); -CREATE TABLE prt2_e_p1 PARTITION OF prt2_e FOR VALUES FROM (0) TO (250); -CREATE TABLE prt2_e_p2 PARTITION OF prt2_e FOR VALUES FROM (250) TO (500); -CREATE TABLE prt2_e_p3 PARTITION OF prt2_e FOR VALUES FROM (500) TO (600); -INSERT INTO prt2_e SELECT i, i, i % 25 FROM generate_series(0, 599, 3) i; -ANALYZE prt2_e; -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_e t1, prt2_e t2 WHERE (t1.a + t1.b)/2 = (t2.b + t2.a)/2 AND t1.c = 0 ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------------------------------- - Sort - Sort Key: t1.a, t2.b - -> Append - -> Hash Join - Hash Cond: (((t2_1.b + t2_1.a) / 2) = ((t1_1.a + t1_1.b) / 2)) - -> Seq Scan on prt2_e_p1 t2_1 - -> Hash - -> Seq Scan on prt1_e_p1 t1_1 - Filter: (c = 0) - -> Hash Join - Hash Cond: (((t2_2.b + t2_2.a) / 2) = ((t1_2.a + t1_2.b) / 2)) - -> Seq Scan on prt2_e_p2 t2_2 - -> Hash - -> Seq Scan on prt1_e_p2 t1_2 - Filter: (c = 0) - -> Hash Join - Hash Cond: (((t2_3.b + t2_3.a) / 2) = ((t1_3.a + t1_3.b) / 2)) - -> Seq Scan on prt2_e_p3 t2_3 - -> Hash - -> Seq Scan on prt1_e_p3 t1_3 - Filter: (c = 0) -(21 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_e t1, prt2_e t2 WHERE (t1.a + t1.b)/2 = (t2.b + t2.a)/2 AND t1.c = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+---+-----+--- - 0 | 0 | 0 | 0 - 150 | 0 | 150 | 0 - 300 | 0 | 300 | 0 - 450 | 0 | 450 | 0 -(4 rows) - --- --- N-way join --- -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t3 WHERE t1.a = t2.b AND t1.a = (t3.a + t3.b)/2 AND t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN ---------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Nested Loop - Join Filter: (t1_1.a = ((t3_1.a + t3_1.b) / 2)) - -> Hash Join - Hash Cond: (t2_1.b = t1_1.a) - -> Seq Scan on prt2_p1 t2_1 - -> Hash - -> Seq Scan on prt1_p1 t1_1 - Filter: (b = 0) - -> Index Scan using iprt1_e_p1_ab2 on prt1_e_p1 t3_1 - Index Cond: (((a + b) / 2) = t2_1.b) - -> Nested Loop - Join Filter: (t1_2.a = ((t3_2.a + t3_2.b) / 2)) - -> Hash Join - Hash Cond: (t2_2.b = t1_2.a) - -> Seq Scan on prt2_p2 t2_2 - -> Hash - -> Seq Scan on prt1_p2 t1_2 - Filter: (b = 0) - -> Index Scan using iprt1_e_p2_ab2 on prt1_e_p2 t3_2 - Index Cond: (((a + b) / 2) = t2_2.b) - -> Nested Loop - Join Filter: (t1_3.a = ((t3_3.a + t3_3.b) / 2)) - -> Hash Join - Hash Cond: (t2_3.b = t1_3.a) - -> Seq Scan on prt2_p3 t2_3 - -> Hash - -> Seq Scan on prt1_p3 t1_3 - Filter: (b = 0) - -> Index Scan using iprt1_e_p3_ab2 on prt1_e_p3 t3_3 - Index Cond: (((a + b) / 2) = t2_3.b) -(33 rows) - -SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t3 WHERE t1.a = t2.b AND t1.a = (t3.a + t3.b)/2 AND t1.b = 0 ORDER BY t1.a, t2.b; - a | c | b | c | ?column? | c ------+------+-----+------+----------+--- - 0 | 0000 | 0 | 0000 | 0 | 0 - 150 | 0150 | 150 | 0150 | 300 | 0 - 300 | 0300 | 300 | 0300 | 600 | 0 - 450 | 0450 | 450 | 0450 | 900 | 0 -(4 rows) - -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; - QUERY PLAN --------------------------------------------------------------- - Sort - Sort Key: t1.a, t2.b, ((t3.a + t3.b)) - -> Append - -> Hash Right Join - Hash Cond: (((t3_1.a + t3_1.b) / 2) = t1_1.a) - -> Seq Scan on prt1_e_p1 t3_1 - -> Hash - -> Hash Right Join - Hash Cond: (t2_1.b = t1_1.a) - -> Seq Scan on prt2_p1 t2_1 - -> Hash - -> Seq Scan on prt1_p1 t1_1 - Filter: (b = 0) - -> Hash Right Join - Hash Cond: (((t3_2.a + t3_2.b) / 2) = t1_2.a) - -> Seq Scan on prt1_e_p2 t3_2 - -> Hash - -> Hash Right Join - Hash Cond: (t2_2.b = t1_2.a) - -> Seq Scan on prt2_p2 t2_2 - -> Hash - -> Seq Scan on prt1_p2 t1_2 - Filter: (b = 0) - -> Hash Right Join - Hash Cond: (((t3_3.a + t3_3.b) / 2) = t1_3.a) - -> Seq Scan on prt1_e_p3 t3_3 - -> Hash - -> Hash Right Join - Hash Cond: (t2_3.b = t1_3.a) - -> Seq Scan on prt2_p3 t2_3 - -> Hash - -> Seq Scan on prt1_p3 t1_3 - Filter: (b = 0) -(33 rows) - -SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; - a | c | b | c | ?column? | c ------+------+-----+------+----------+--- - 0 | 0000 | 0 | 0000 | 0 | 0 - 50 | 0050 | | | 100 | 0 - 100 | 0100 | | | 200 | 0 - 150 | 0150 | 150 | 0150 | 300 | 0 - 200 | 0200 | | | 400 | 0 - 250 | 0250 | | | 500 | 0 - 300 | 0300 | 300 | 0300 | 600 | 0 - 350 | 0350 | | | 700 | 0 - 400 | 0400 | | | 800 | 0 - 450 | 0450 | 450 | 0450 | 900 | 0 - 500 | 0500 | | | 1000 | 0 - 550 | 0550 | | | 1100 | 0 -(12 rows) - -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; - QUERY PLAN -------------------------------------------------------------------- - Sort - Sort Key: t1.a, t2.b, ((t3.a + t3.b)) - -> Append - -> Nested Loop Left Join - -> Hash Right Join - Hash Cond: (t1_1.a = ((t3_1.a + t3_1.b) / 2)) - -> Seq Scan on prt1_p1 t1_1 - -> Hash - -> Seq Scan on prt1_e_p1 t3_1 - Filter: (c = 0) - -> Index Scan using iprt2_p1_b on prt2_p1 t2_1 - Index Cond: (b = t1_1.a) - -> Nested Loop Left Join - -> Hash Right Join - Hash Cond: (t1_2.a = ((t3_2.a + t3_2.b) / 2)) - -> Seq Scan on prt1_p2 t1_2 - -> Hash - -> Seq Scan on prt1_e_p2 t3_2 - Filter: (c = 0) - -> Index Scan using iprt2_p2_b on prt2_p2 t2_2 - Index Cond: (b = t1_2.a) - -> Nested Loop Left Join - -> Hash Right Join - Hash Cond: (t1_3.a = ((t3_3.a + t3_3.b) / 2)) - -> Seq Scan on prt1_p3 t1_3 - -> Hash - -> Seq Scan on prt1_e_p3 t3_3 - Filter: (c = 0) - -> Index Scan using iprt2_p3_b on prt2_p3 t2_3 - Index Cond: (b = t1_3.a) -(30 rows) - -SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; - a | c | b | c | ?column? | c ------+------+-----+------+----------+--- - 0 | 0000 | 0 | 0000 | 0 | 0 - 50 | 0050 | | | 100 | 0 - 100 | 0100 | | | 200 | 0 - 150 | 0150 | 150 | 0150 | 300 | 0 - 200 | 0200 | | | 400 | 0 - 250 | 0250 | | | 500 | 0 - 300 | 0300 | 300 | 0300 | 600 | 0 - 350 | 0350 | | | 700 | 0 - 400 | 0400 | | | 800 | 0 - 450 | 0450 | 450 | 0450 | 900 | 0 - 500 | 0500 | | | 1000 | 0 - 550 | 0550 | | | 1100 | 0 -(12 rows) - --- --- 3-way full join --- -EXPLAIN (COSTS OFF) -SELECT COUNT(*) FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) FULL JOIN prt2 p3(b,a,c) USING (a, b) - WHERE a BETWEEN 490 AND 510; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------ - Aggregate - -> Append - -> Hash Full Join - Hash Cond: ((COALESCE(prt1_1.a, p2_1.a) = p3_1.a) AND (COALESCE(prt1_1.b, p2_1.b) = p3_1.b)) - Filter: ((COALESCE(COALESCE(prt1_1.a, p2_1.a), p3_1.a) >= 490) AND (COALESCE(COALESCE(prt1_1.a, p2_1.a), p3_1.a) <= 510)) - -> Hash Full Join - Hash Cond: ((prt1_1.a = p2_1.a) AND (prt1_1.b = p2_1.b)) - -> Seq Scan on prt1_p1 prt1_1 - -> Hash - -> Seq Scan on prt2_p1 p2_1 - -> Hash - -> Seq Scan on prt2_p1 p3_1 - -> Hash Full Join - Hash Cond: ((COALESCE(prt1_2.a, p2_2.a) = p3_2.a) AND (COALESCE(prt1_2.b, p2_2.b) = p3_2.b)) - Filter: ((COALESCE(COALESCE(prt1_2.a, p2_2.a), p3_2.a) >= 490) AND (COALESCE(COALESCE(prt1_2.a, p2_2.a), p3_2.a) <= 510)) - -> Hash Full Join - Hash Cond: ((prt1_2.a = p2_2.a) AND (prt1_2.b = p2_2.b)) - -> Seq Scan on prt1_p2 prt1_2 - -> Hash - -> Seq Scan on prt2_p2 p2_2 - -> Hash - -> Seq Scan on prt2_p2 p3_2 - -> Hash Full Join - Hash Cond: ((COALESCE(prt1_3.a, p2_3.a) = p3_3.a) AND (COALESCE(prt1_3.b, p2_3.b) = p3_3.b)) - Filter: ((COALESCE(COALESCE(prt1_3.a, p2_3.a), p3_3.a) >= 490) AND (COALESCE(COALESCE(prt1_3.a, p2_3.a), p3_3.a) <= 510)) - -> Hash Full Join - Hash Cond: ((prt1_3.a = p2_3.a) AND (prt1_3.b = p2_3.b)) - -> Seq Scan on prt1_p3 prt1_3 - -> Hash - -> Seq Scan on prt2_p3 p2_3 - -> Hash - -> Seq Scan on prt2_p3 p3_3 -(32 rows) - -SELECT COUNT(*) FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) FULL JOIN prt2 p3(b,a,c) USING (a, b) - WHERE a BETWEEN 490 AND 510; - count -------- - 14 -(1 row) - --- --- 4-way full join --- -EXPLAIN (COSTS OFF) -SELECT COUNT(*) FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) FULL JOIN prt2 p3(b,a,c) USING (a, b) FULL JOIN prt1 p4 (a,b,c) USING (a, b) - WHERE a BETWEEN 490 AND 510; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Aggregate - -> Append - -> Hash Full Join - Hash Cond: ((COALESCE(COALESCE(prt1_1.a, p2_1.a), p3_1.a) = p4_1.a) AND (COALESCE(COALESCE(prt1_1.b, p2_1.b), p3_1.b) = p4_1.b)) - Filter: ((COALESCE(COALESCE(COALESCE(prt1_1.a, p2_1.a), p3_1.a), p4_1.a) >= 490) AND (COALESCE(COALESCE(COALESCE(prt1_1.a, p2_1.a), p3_1.a), p4_1.a) <= 510)) - -> Hash Full Join - Hash Cond: ((COALESCE(prt1_1.a, p2_1.a) = p3_1.a) AND (COALESCE(prt1_1.b, p2_1.b) = p3_1.b)) - -> Hash Full Join - Hash Cond: ((prt1_1.a = p2_1.a) AND (prt1_1.b = p2_1.b)) - -> Seq Scan on prt1_p1 prt1_1 - -> Hash - -> Seq Scan on prt2_p1 p2_1 - -> Hash - -> Seq Scan on prt2_p1 p3_1 - -> Hash - -> Seq Scan on prt1_p1 p4_1 - -> Hash Full Join - Hash Cond: ((COALESCE(COALESCE(prt1_2.a, p2_2.a), p3_2.a) = p4_2.a) AND (COALESCE(COALESCE(prt1_2.b, p2_2.b), p3_2.b) = p4_2.b)) - Filter: ((COALESCE(COALESCE(COALESCE(prt1_2.a, p2_2.a), p3_2.a), p4_2.a) >= 490) AND (COALESCE(COALESCE(COALESCE(prt1_2.a, p2_2.a), p3_2.a), p4_2.a) <= 510)) - -> Hash Full Join - Hash Cond: ((COALESCE(prt1_2.a, p2_2.a) = p3_2.a) AND (COALESCE(prt1_2.b, p2_2.b) = p3_2.b)) - -> Hash Full Join - Hash Cond: ((prt1_2.a = p2_2.a) AND (prt1_2.b = p2_2.b)) - -> Seq Scan on prt1_p2 prt1_2 - -> Hash - -> Seq Scan on prt2_p2 p2_2 - -> Hash - -> Seq Scan on prt2_p2 p3_2 - -> Hash - -> Seq Scan on prt1_p2 p4_2 - -> Hash Full Join - Hash Cond: ((COALESCE(COALESCE(prt1_3.a, p2_3.a), p3_3.a) = p4_3.a) AND (COALESCE(COALESCE(prt1_3.b, p2_3.b), p3_3.b) = p4_3.b)) - Filter: ((COALESCE(COALESCE(COALESCE(prt1_3.a, p2_3.a), p3_3.a), p4_3.a) >= 490) AND (COALESCE(COALESCE(COALESCE(prt1_3.a, p2_3.a), p3_3.a), p4_3.a) <= 510)) - -> Hash Full Join - Hash Cond: ((COALESCE(prt1_3.a, p2_3.a) = p3_3.a) AND (COALESCE(prt1_3.b, p2_3.b) = p3_3.b)) - -> Hash Full Join - Hash Cond: ((prt1_3.a = p2_3.a) AND (prt1_3.b = p2_3.b)) - -> Seq Scan on prt1_p3 prt1_3 - -> Hash - -> Seq Scan on prt2_p3 p2_3 - -> Hash - -> Seq Scan on prt2_p3 p3_3 - -> Hash - -> Seq Scan on prt1_p3 p4_3 -(44 rows) - -SELECT COUNT(*) FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) FULL JOIN prt2 p3(b,a,c) USING (a, b) FULL JOIN prt1 p4 (a,b,c) USING (a, b) - WHERE a BETWEEN 490 AND 510; - count -------- - 14 -(1 row) - --- Cases with non-nullable expressions in subquery results; --- make sure these go to null as expected -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.phv, t2.b, t2.phv, t3.a + t3.b, t3.phv FROM ((SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.a = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT 50 phv, * FROM prt1_e WHERE prt1_e.c = 0) t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.a = t1.phv OR t2.b = t2.phv OR (t3.a + t3.b)/2 = t3.phv ORDER BY t1.a, t2.b, t3.a + t3.b; - QUERY PLAN ------------------------------------------------------------------------------------------------------------- - Sort - Sort Key: prt1.a, prt2.b, ((prt1_e.a + prt1_e.b)) - -> Append - -> Hash Full Join - Hash Cond: (prt1_1.a = ((prt1_e_1.a + prt1_e_1.b) / 2)) - Filter: ((prt1_1.a = (50)) OR (prt2_1.b = (75)) OR (((prt1_e_1.a + prt1_e_1.b) / 2) = (50))) - -> Hash Full Join - Hash Cond: (prt1_1.a = prt2_1.b) - -> Seq Scan on prt1_p1 prt1_1 - Filter: (b = 0) - -> Hash - -> Seq Scan on prt2_p1 prt2_1 - Filter: (a = 0) - -> Hash - -> Seq Scan on prt1_e_p1 prt1_e_1 - Filter: (c = 0) - -> Hash Full Join - Hash Cond: (prt1_2.a = ((prt1_e_2.a + prt1_e_2.b) / 2)) - Filter: ((prt1_2.a = (50)) OR (prt2_2.b = (75)) OR (((prt1_e_2.a + prt1_e_2.b) / 2) = (50))) - -> Hash Full Join - Hash Cond: (prt1_2.a = prt2_2.b) - -> Seq Scan on prt1_p2 prt1_2 - Filter: (b = 0) - -> Hash - -> Seq Scan on prt2_p2 prt2_2 - Filter: (a = 0) - -> Hash - -> Seq Scan on prt1_e_p2 prt1_e_2 - Filter: (c = 0) - -> Hash Full Join - Hash Cond: (prt1_3.a = ((prt1_e_3.a + prt1_e_3.b) / 2)) - Filter: ((prt1_3.a = (50)) OR (prt2_3.b = (75)) OR (((prt1_e_3.a + prt1_e_3.b) / 2) = (50))) - -> Hash Full Join - Hash Cond: (prt1_3.a = prt2_3.b) - -> Seq Scan on prt1_p3 prt1_3 - Filter: (b = 0) - -> Hash - -> Seq Scan on prt2_p3 prt2_3 - Filter: (a = 0) - -> Hash - -> Seq Scan on prt1_e_p3 prt1_e_3 - Filter: (c = 0) -(42 rows) - -SELECT t1.a, t1.phv, t2.b, t2.phv, t3.a + t3.b, t3.phv FROM ((SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.a = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT 50 phv, * FROM prt1_e WHERE prt1_e.c = 0) t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.a = t1.phv OR t2.b = t2.phv OR (t3.a + t3.b)/2 = t3.phv ORDER BY t1.a, t2.b, t3.a + t3.b; - a | phv | b | phv | ?column? | phv -----+-----+----+-----+----------+----- - 50 | 50 | | | 100 | 50 - | | 75 | 75 | | -(2 rows) - --- Semi-join -EXPLAIN (COSTS OFF) -SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1, prt1_e t2 WHERE t1.a = 0 AND t1.b = (t2.a + t2.b)/2) AND t1.b = 0 ORDER BY t1.a; - QUERY PLAN ---------------------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Nested Loop - Join Filter: (t1_2.a = t1_5.b) - -> HashAggregate - Group Key: t1_5.b - -> Hash Join - Hash Cond: (((t2_1.a + t2_1.b) / 2) = t1_5.b) - -> Seq Scan on prt1_e_p1 t2_1 - -> Hash - -> Seq Scan on prt2_p1 t1_5 - Filter: (a = 0) - -> Index Scan using iprt1_p1_a on prt1_p1 t1_2 - Index Cond: (a = ((t2_1.a + t2_1.b) / 2)) - Filter: (b = 0) - -> Nested Loop - Join Filter: (t1_3.a = t1_6.b) - -> HashAggregate - Group Key: t1_6.b - -> Hash Join - Hash Cond: (((t2_2.a + t2_2.b) / 2) = t1_6.b) - -> Seq Scan on prt1_e_p2 t2_2 - -> Hash - -> Seq Scan on prt2_p2 t1_6 - Filter: (a = 0) - -> Index Scan using iprt1_p2_a on prt1_p2 t1_3 - Index Cond: (a = ((t2_2.a + t2_2.b) / 2)) - Filter: (b = 0) - -> Nested Loop - Join Filter: (t1_4.a = t1_7.b) - -> HashAggregate - Group Key: t1_7.b - -> Nested Loop - -> Seq Scan on prt2_p3 t1_7 - Filter: (a = 0) - -> Index Scan using iprt1_e_p3_ab2 on prt1_e_p3 t2_3 - Index Cond: (((a + b) / 2) = t1_7.b) - -> Index Scan using iprt1_p3_a on prt1_p3 t1_4 - Index Cond: (a = ((t2_3.a + t2_3.b) / 2)) - Filter: (b = 0) -(41 rows) - -SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1, prt1_e t2 WHERE t1.a = 0 AND t1.b = (t2.a + t2.b)/2) AND t1.b = 0 ORDER BY t1.a; - a | b | c ------+---+------ - 0 | 0 | 0000 - 150 | 0 | 0150 - 300 | 0 | 0300 - 450 | 0 | 0450 -(4 rows) - -EXPLAIN (COSTS OFF) -SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a; - QUERY PLAN ---------------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Nested Loop - -> HashAggregate - Group Key: t1_6.b - -> Hash Semi Join - Hash Cond: (t1_6.b = ((t1_9.a + t1_9.b) / 2)) - -> Seq Scan on prt2_p1 t1_6 - -> Hash - -> Seq Scan on prt1_e_p1 t1_9 - Filter: (c = 0) - -> Index Scan using iprt1_p1_a on prt1_p1 t1_3 - Index Cond: (a = t1_6.b) - Filter: (b = 0) - -> Nested Loop - -> HashAggregate - Group Key: t1_7.b - -> Hash Semi Join - Hash Cond: (t1_7.b = ((t1_10.a + t1_10.b) / 2)) - -> Seq Scan on prt2_p2 t1_7 - -> Hash - -> Seq Scan on prt1_e_p2 t1_10 - Filter: (c = 0) - -> Index Scan using iprt1_p2_a on prt1_p2 t1_4 - Index Cond: (a = t1_7.b) - Filter: (b = 0) - -> Nested Loop - -> HashAggregate - Group Key: t1_8.b - -> Hash Semi Join - Hash Cond: (t1_8.b = ((t1_11.a + t1_11.b) / 2)) - -> Seq Scan on prt2_p3 t1_8 - -> Hash - -> Seq Scan on prt1_e_p3 t1_11 - Filter: (c = 0) - -> Index Scan using iprt1_p3_a on prt1_p3 t1_5 - Index Cond: (a = t1_8.b) - Filter: (b = 0) -(39 rows) - -SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a; - a | b | c ------+---+------ - 0 | 0 | 0000 - 150 | 0 | 0150 - 300 | 0 | 0300 - 450 | 0 | 0450 -(4 rows) - --- test merge joins -SET enable_hashjoin TO off; -SET enable_nestloop TO off; -EXPLAIN (COSTS OFF) -SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a; - QUERY PLAN ------------------------------------------------------------------- - Merge Append - Sort Key: t1.a - -> Merge Semi Join - Merge Cond: (t1_3.a = t1_6.b) - -> Sort - Sort Key: t1_3.a - -> Seq Scan on prt1_p1 t1_3 - Filter: (b = 0) - -> Merge Semi Join - Merge Cond: (t1_6.b = (((t1_9.a + t1_9.b) / 2))) - -> Sort - Sort Key: t1_6.b - -> Seq Scan on prt2_p1 t1_6 - -> Sort - Sort Key: (((t1_9.a + t1_9.b) / 2)) - -> Seq Scan on prt1_e_p1 t1_9 - Filter: (c = 0) - -> Merge Semi Join - Merge Cond: (t1_4.a = t1_7.b) - -> Sort - Sort Key: t1_4.a - -> Seq Scan on prt1_p2 t1_4 - Filter: (b = 0) - -> Merge Semi Join - Merge Cond: (t1_7.b = (((t1_10.a + t1_10.b) / 2))) - -> Sort - Sort Key: t1_7.b - -> Seq Scan on prt2_p2 t1_7 - -> Sort - Sort Key: (((t1_10.a + t1_10.b) / 2)) - -> Seq Scan on prt1_e_p2 t1_10 - Filter: (c = 0) - -> Merge Semi Join - Merge Cond: (t1_5.a = t1_8.b) - -> Sort - Sort Key: t1_5.a - -> Seq Scan on prt1_p3 t1_5 - Filter: (b = 0) - -> Merge Semi Join - Merge Cond: (t1_8.b = (((t1_11.a + t1_11.b) / 2))) - -> Sort - Sort Key: t1_8.b - -> Seq Scan on prt2_p3 t1_8 - -> Sort - Sort Key: (((t1_11.a + t1_11.b) / 2)) - -> Seq Scan on prt1_e_p3 t1_11 - Filter: (c = 0) -(47 rows) - -SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a; - a | b | c ------+---+------ - 0 | 0 | 0000 - 150 | 0 | 0150 - 300 | 0 | 0300 - 450 | 0 | 0450 -(4 rows) - -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; - QUERY PLAN ----------------------------------------------------------------------------- - Sort - Sort Key: t1.a, t2.b, ((t3.a + t3.b)) - -> Append - -> Merge Left Join - Merge Cond: (t1_1.a = t2_1.b) - -> Sort - Sort Key: t1_1.a - -> Merge Left Join - Merge Cond: ((((t3_1.a + t3_1.b) / 2)) = t1_1.a) - -> Sort - Sort Key: (((t3_1.a + t3_1.b) / 2)) - -> Seq Scan on prt1_e_p1 t3_1 - Filter: (c = 0) - -> Sort - Sort Key: t1_1.a - -> Seq Scan on prt1_p1 t1_1 - -> Sort - Sort Key: t2_1.b - -> Seq Scan on prt2_p1 t2_1 - -> Merge Left Join - Merge Cond: (t1_2.a = t2_2.b) - -> Sort - Sort Key: t1_2.a - -> Merge Left Join - Merge Cond: ((((t3_2.a + t3_2.b) / 2)) = t1_2.a) - -> Sort - Sort Key: (((t3_2.a + t3_2.b) / 2)) - -> Seq Scan on prt1_e_p2 t3_2 - Filter: (c = 0) - -> Sort - Sort Key: t1_2.a - -> Seq Scan on prt1_p2 t1_2 - -> Sort - Sort Key: t2_2.b - -> Seq Scan on prt2_p2 t2_2 - -> Merge Left Join - Merge Cond: (t1_3.a = t2_3.b) - -> Sort - Sort Key: t1_3.a - -> Merge Left Join - Merge Cond: ((((t3_3.a + t3_3.b) / 2)) = t1_3.a) - -> Sort - Sort Key: (((t3_3.a + t3_3.b) / 2)) - -> Seq Scan on prt1_e_p3 t3_3 - Filter: (c = 0) - -> Sort - Sort Key: t1_3.a - -> Seq Scan on prt1_p3 t1_3 - -> Sort - Sort Key: t2_3.b - -> Seq Scan on prt2_p3 t2_3 -(51 rows) - -SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; - a | c | b | c | ?column? | c ------+------+-----+------+----------+--- - 0 | 0000 | 0 | 0000 | 0 | 0 - 50 | 0050 | | | 100 | 0 - 100 | 0100 | | | 200 | 0 - 150 | 0150 | 150 | 0150 | 300 | 0 - 200 | 0200 | | | 400 | 0 - 250 | 0250 | | | 500 | 0 - 300 | 0300 | 300 | 0300 | 600 | 0 - 350 | 0350 | | | 700 | 0 - 400 | 0400 | | | 800 | 0 - 450 | 0450 | 450 | 0450 | 900 | 0 - 500 | 0500 | | | 1000 | 0 - 550 | 0550 | | | 1100 | 0 -(12 rows) - --- MergeAppend on nullable column --- This should generate a partitionwise join, but currently fails to -EXPLAIN (COSTS OFF) -SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------------ - Incremental Sort - Sort Key: prt1.a, prt2.b - Presorted Key: prt1.a - -> Merge Left Join - Merge Cond: (prt1.a = prt2.b) - -> Sort - Sort Key: prt1.a - -> Append - -> Seq Scan on prt1_p1 prt1_1 - Filter: ((a < 450) AND (b = 0)) - -> Seq Scan on prt1_p2 prt1_2 - Filter: ((a < 450) AND (b = 0)) - -> Sort - Sort Key: prt2.b - -> Append - -> Seq Scan on prt2_p2 prt2_1 - Filter: (b > 250) - -> Seq Scan on prt2_p3 prt2_2 - Filter: (b > 250) -(19 rows) - -SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; - a | b ------+----- - 0 | - 50 | - 100 | - 150 | - 200 | - 250 | - 300 | 300 - 350 | - 400 | -(9 rows) - --- merge join when expression with whole-row reference needs to be sorted; --- partitionwise join does not apply -EXPLAIN (COSTS OFF) -SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1::text = t2::text AND t1.a = t2.b ORDER BY t1.a; - QUERY PLAN ------------------------------------------------------------------------------------------ - Merge Join - Merge Cond: ((t1.a = t2.b) AND (((((t1.*)::prt1))::text) = ((((t2.*)::prt2))::text))) - -> Sort - Sort Key: t1.a, ((((t1.*)::prt1))::text) - -> Result - -> Append - -> Seq Scan on prt1_p1 t1_1 - -> Seq Scan on prt1_p2 t1_2 - -> Seq Scan on prt1_p3 t1_3 - -> Sort - Sort Key: t2.b, ((((t2.*)::prt2))::text) - -> Result - -> Append - -> Seq Scan on prt2_p1 t2_1 - -> Seq Scan on prt2_p2 t2_2 - -> Seq Scan on prt2_p3 t2_3 -(16 rows) - -SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1::text = t2::text AND t1.a = t2.b ORDER BY t1.a; - a | b -----+---- - 0 | 0 - 6 | 6 - 12 | 12 - 18 | 18 - 24 | 24 -(5 rows) - -RESET enable_hashjoin; -RESET enable_nestloop; --- --- partitioned by multiple columns --- -CREATE TABLE prt1_m (a int, b int, c int) PARTITION BY RANGE(a, ((a + b)/2)); -CREATE TABLE prt1_m_p1 PARTITION OF prt1_m FOR VALUES FROM (0, 0) TO (250, 250); -CREATE TABLE prt1_m_p2 PARTITION OF prt1_m FOR VALUES FROM (250, 250) TO (500, 500); -CREATE TABLE prt1_m_p3 PARTITION OF prt1_m FOR VALUES FROM (500, 500) TO (600, 600); -INSERT INTO prt1_m SELECT i, i, i % 25 FROM generate_series(0, 599, 2) i; -ANALYZE prt1_m; -CREATE TABLE prt2_m (a int, b int, c int) PARTITION BY RANGE(((b + a)/2), b); -CREATE TABLE prt2_m_p1 PARTITION OF prt2_m FOR VALUES FROM (0, 0) TO (250, 250); -CREATE TABLE prt2_m_p2 PARTITION OF prt2_m FOR VALUES FROM (250, 250) TO (500, 500); -CREATE TABLE prt2_m_p3 PARTITION OF prt2_m FOR VALUES FROM (500, 500) TO (600, 600); -INSERT INTO prt2_m SELECT i, i, i % 25 FROM generate_series(0, 599, 3) i; -ANALYZE prt2_m; -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_m WHERE prt1_m.c = 0) t1 FULL JOIN (SELECT * FROM prt2_m WHERE prt2_m.c = 0) t2 ON (t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2) ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------- - Sort - Sort Key: prt1_m.a, prt2_m.b - -> Append - -> Hash Full Join - Hash Cond: ((prt1_m_1.a = ((prt2_m_1.b + prt2_m_1.a) / 2)) AND (((prt1_m_1.a + prt1_m_1.b) / 2) = prt2_m_1.b)) - -> Seq Scan on prt1_m_p1 prt1_m_1 - Filter: (c = 0) - -> Hash - -> Seq Scan on prt2_m_p1 prt2_m_1 - Filter: (c = 0) - -> Hash Full Join - Hash Cond: ((prt1_m_2.a = ((prt2_m_2.b + prt2_m_2.a) / 2)) AND (((prt1_m_2.a + prt1_m_2.b) / 2) = prt2_m_2.b)) - -> Seq Scan on prt1_m_p2 prt1_m_2 - Filter: (c = 0) - -> Hash - -> Seq Scan on prt2_m_p2 prt2_m_2 - Filter: (c = 0) - -> Hash Full Join - Hash Cond: ((prt1_m_3.a = ((prt2_m_3.b + prt2_m_3.a) / 2)) AND (((prt1_m_3.a + prt1_m_3.b) / 2) = prt2_m_3.b)) - -> Seq Scan on prt1_m_p3 prt1_m_3 - Filter: (c = 0) - -> Hash - -> Seq Scan on prt2_m_p3 prt2_m_3 - Filter: (c = 0) -(24 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_m WHERE prt1_m.c = 0) t1 FULL JOIN (SELECT * FROM prt2_m WHERE prt2_m.c = 0) t2 ON (t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2) ORDER BY t1.a, t2.b; - a | c | b | c ------+---+-----+--- - 0 | 0 | 0 | 0 - 50 | 0 | | - 100 | 0 | | - 150 | 0 | 150 | 0 - 200 | 0 | | - 250 | 0 | | - 300 | 0 | 300 | 0 - 350 | 0 | | - 400 | 0 | | - 450 | 0 | 450 | 0 - 500 | 0 | | - 550 | 0 | | - | | 75 | 0 - | | 225 | 0 - | | 375 | 0 - | | 525 | 0 -(16 rows) - --- --- tests for list partitioned tables. --- -CREATE TABLE plt1 (a int, b int, c text) PARTITION BY LIST(c); -CREATE TABLE plt1_p1 PARTITION OF plt1 FOR VALUES IN ('0000', '0003', '0004', '0010'); -CREATE TABLE plt1_p2 PARTITION OF plt1 FOR VALUES IN ('0001', '0005', '0002', '0009'); -CREATE TABLE plt1_p3 PARTITION OF plt1 FOR VALUES IN ('0006', '0007', '0008', '0011'); -INSERT INTO plt1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; -ANALYZE plt1; -CREATE TABLE plt2 (a int, b int, c text) PARTITION BY LIST(c); -CREATE TABLE plt2_p1 PARTITION OF plt2 FOR VALUES IN ('0000', '0003', '0004', '0010'); -CREATE TABLE plt2_p2 PARTITION OF plt2 FOR VALUES IN ('0001', '0005', '0002', '0009'); -CREATE TABLE plt2_p3 PARTITION OF plt2 FOR VALUES IN ('0006', '0007', '0008', '0011'); -INSERT INTO plt2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 3) i; -ANALYZE plt2; --- --- list partitioned by expression --- -CREATE TABLE plt1_e (a int, b int, c text) PARTITION BY LIST(ltrim(c, 'A')); -CREATE TABLE plt1_e_p1 PARTITION OF plt1_e FOR VALUES IN ('0000', '0003', '0004', '0010'); -CREATE TABLE plt1_e_p2 PARTITION OF plt1_e FOR VALUES IN ('0001', '0005', '0002', '0009'); -CREATE TABLE plt1_e_p3 PARTITION OF plt1_e FOR VALUES IN ('0006', '0007', '0008', '0011'); -INSERT INTO plt1_e SELECT i, i, 'A' || to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; -ANALYZE plt1_e; --- test partition matching with N-way join -EXPLAIN (COSTS OFF) -SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; - QUERY PLAN --------------------------------------------------------------------------------- - GroupAggregate - Group Key: t1.c, t3.c - -> Sort - Sort Key: t1.c, t3.c - -> Append - -> Hash Join - Hash Cond: (t1_1.c = ltrim(t3_1.c, 'A'::text)) - -> Hash Join - Hash Cond: ((t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c)) - -> Seq Scan on plt1_p1 t1_1 - -> Hash - -> Seq Scan on plt2_p1 t2_1 - -> Hash - -> Seq Scan on plt1_e_p1 t3_1 - -> Hash Join - Hash Cond: (t1_2.c = ltrim(t3_2.c, 'A'::text)) - -> Hash Join - Hash Cond: ((t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c)) - -> Seq Scan on plt1_p2 t1_2 - -> Hash - -> Seq Scan on plt2_p2 t2_2 - -> Hash - -> Seq Scan on plt1_e_p2 t3_2 - -> Hash Join - Hash Cond: (t1_3.c = ltrim(t3_3.c, 'A'::text)) - -> Hash Join - Hash Cond: ((t1_3.b = t2_3.b) AND (t1_3.c = t2_3.c)) - -> Seq Scan on plt1_p3 t1_3 - -> Hash - -> Seq Scan on plt2_p3 t2_3 - -> Hash - -> Seq Scan on plt1_e_p3 t3_3 -(32 rows) - -SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; - avg | avg | avg | c | c | c -----------------------+----------------------+-----------------------+------+------+------- - 24.0000000000000000 | 24.0000000000000000 | 48.0000000000000000 | 0000 | 0000 | A0000 - 75.0000000000000000 | 75.0000000000000000 | 148.0000000000000000 | 0001 | 0001 | A0001 - 123.0000000000000000 | 123.0000000000000000 | 248.0000000000000000 | 0002 | 0002 | A0002 - 174.0000000000000000 | 174.0000000000000000 | 348.0000000000000000 | 0003 | 0003 | A0003 - 225.0000000000000000 | 225.0000000000000000 | 448.0000000000000000 | 0004 | 0004 | A0004 - 273.0000000000000000 | 273.0000000000000000 | 548.0000000000000000 | 0005 | 0005 | A0005 - 324.0000000000000000 | 324.0000000000000000 | 648.0000000000000000 | 0006 | 0006 | A0006 - 375.0000000000000000 | 375.0000000000000000 | 748.0000000000000000 | 0007 | 0007 | A0007 - 423.0000000000000000 | 423.0000000000000000 | 848.0000000000000000 | 0008 | 0008 | A0008 - 474.0000000000000000 | 474.0000000000000000 | 948.0000000000000000 | 0009 | 0009 | A0009 - 525.0000000000000000 | 525.0000000000000000 | 1048.0000000000000000 | 0010 | 0010 | A0010 - 573.0000000000000000 | 573.0000000000000000 | 1148.0000000000000000 | 0011 | 0011 | A0011 -(12 rows) - --- joins where one of the relations is proven empty -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a = 1 AND t1.a = 2; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 LEFT JOIN prt2 t2 ON t1.a = t2.b; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b, prt1 t3 WHERE t2.b = t3.a; - QUERY PLAN --------------------------------------------------- - Hash Left Join - Hash Cond: (t2.b = a) - -> Append - -> Hash Join - Hash Cond: (t3_1.a = t2_1.b) - -> Seq Scan on prt1_p1 t3_1 - -> Hash - -> Seq Scan on prt2_p1 t2_1 - -> Hash Join - Hash Cond: (t3_2.a = t2_2.b) - -> Seq Scan on prt1_p2 t3_2 - -> Hash - -> Seq Scan on prt2_p2 t2_2 - -> Hash Join - Hash Cond: (t3_3.a = t2_3.b) - -> Seq Scan on prt1_p3 t3_3 - -> Hash - -> Seq Scan on prt2_p3 t2_3 - -> Hash - -> Result - One-Time Filter: false -(21 rows) - -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 FULL JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b; - QUERY PLAN --------------------------------------------- - Sort - Sort Key: a, t2.b - -> Hash Left Join - Hash Cond: (t2.b = a) - -> Append - -> Seq Scan on prt2_p1 t2_1 - Filter: (a = 0) - -> Seq Scan on prt2_p2 t2_2 - Filter: (a = 0) - -> Seq Scan on prt2_p3 t2_3 - Filter: (a = 0) - -> Hash - -> Result - One-Time Filter: false -(14 rows) - --- --- tests for hash partitioned tables. --- -CREATE TABLE pht1 (a int, b int, c text) PARTITION BY HASH(c); -CREATE TABLE pht1_p1 PARTITION OF pht1 FOR VALUES WITH (MODULUS 3, REMAINDER 0); -CREATE TABLE pht1_p2 PARTITION OF pht1 FOR VALUES WITH (MODULUS 3, REMAINDER 1); -CREATE TABLE pht1_p3 PARTITION OF pht1 FOR VALUES WITH (MODULUS 3, REMAINDER 2); -INSERT INTO pht1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; -ANALYZE pht1; -CREATE TABLE pht2 (a int, b int, c text) PARTITION BY HASH(c); -CREATE TABLE pht2_p1 PARTITION OF pht2 FOR VALUES WITH (MODULUS 3, REMAINDER 0); -CREATE TABLE pht2_p2 PARTITION OF pht2 FOR VALUES WITH (MODULUS 3, REMAINDER 1); -CREATE TABLE pht2_p3 PARTITION OF pht2 FOR VALUES WITH (MODULUS 3, REMAINDER 2); -INSERT INTO pht2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 3) i; -ANALYZE pht2; --- --- hash partitioned by expression --- -CREATE TABLE pht1_e (a int, b int, c text) PARTITION BY HASH(ltrim(c, 'A')); -CREATE TABLE pht1_e_p1 PARTITION OF pht1_e FOR VALUES WITH (MODULUS 3, REMAINDER 0); -CREATE TABLE pht1_e_p2 PARTITION OF pht1_e FOR VALUES WITH (MODULUS 3, REMAINDER 1); -CREATE TABLE pht1_e_p3 PARTITION OF pht1_e FOR VALUES WITH (MODULUS 3, REMAINDER 2); -INSERT INTO pht1_e SELECT i, i, 'A' || to_char(i/50, 'FM0000') FROM generate_series(0, 299, 2) i; -ANALYZE pht1_e; --- test partition matching with N-way join -EXPLAIN (COSTS OFF) -SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM pht1 t1, pht2 t2, pht1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; - QUERY PLAN --------------------------------------------------------------------------------- - GroupAggregate - Group Key: t1.c, t3.c - -> Sort - Sort Key: t1.c, t3.c - -> Append - -> Hash Join - Hash Cond: (t1_1.c = ltrim(t3_1.c, 'A'::text)) - -> Hash Join - Hash Cond: ((t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c)) - -> Seq Scan on pht1_p1 t1_1 - -> Hash - -> Seq Scan on pht2_p1 t2_1 - -> Hash - -> Seq Scan on pht1_e_p1 t3_1 - -> Hash Join - Hash Cond: (t1_2.c = ltrim(t3_2.c, 'A'::text)) - -> Hash Join - Hash Cond: ((t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c)) - -> Seq Scan on pht1_p2 t1_2 - -> Hash - -> Seq Scan on pht2_p2 t2_2 - -> Hash - -> Seq Scan on pht1_e_p2 t3_2 - -> Hash Join - Hash Cond: (t1_3.c = ltrim(t3_3.c, 'A'::text)) - -> Hash Join - Hash Cond: ((t1_3.b = t2_3.b) AND (t1_3.c = t2_3.c)) - -> Seq Scan on pht1_p3 t1_3 - -> Hash - -> Seq Scan on pht2_p3 t2_3 - -> Hash - -> Seq Scan on pht1_e_p3 t3_3 -(32 rows) - -SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM pht1 t1, pht2 t2, pht1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; - avg | avg | avg | c | c | c -----------------------+----------------------+----------------------+------+------+------- - 24.0000000000000000 | 24.0000000000000000 | 48.0000000000000000 | 0000 | 0000 | A0000 - 75.0000000000000000 | 75.0000000000000000 | 148.0000000000000000 | 0001 | 0001 | A0001 - 123.0000000000000000 | 123.0000000000000000 | 248.0000000000000000 | 0002 | 0002 | A0002 - 174.0000000000000000 | 174.0000000000000000 | 348.0000000000000000 | 0003 | 0003 | A0003 - 225.0000000000000000 | 225.0000000000000000 | 448.0000000000000000 | 0004 | 0004 | A0004 - 273.0000000000000000 | 273.0000000000000000 | 548.0000000000000000 | 0005 | 0005 | A0005 -(6 rows) - --- test default partition behavior for range -ALTER TABLE prt1 DETACH PARTITION prt1_p3; -ALTER TABLE prt1 ATTACH PARTITION prt1_p3 DEFAULT; -ANALYZE prt1; -ALTER TABLE prt2 DETACH PARTITION prt2_p3; -ALTER TABLE prt2 ATTACH PARTITION prt2_p3 DEFAULT; -ANALYZE prt2; -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN --------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Join - Hash Cond: (t2_1.b = t1_1.a) - -> Seq Scan on prt2_p1 t2_1 - -> Hash - -> Seq Scan on prt1_p1 t1_1 - Filter: (b = 0) - -> Hash Join - Hash Cond: (t2_2.b = t1_2.a) - -> Seq Scan on prt2_p2 t2_2 - -> Hash - -> Seq Scan on prt1_p2 t1_2 - Filter: (b = 0) - -> Hash Join - Hash Cond: (t2_3.b = t1_3.a) - -> Seq Scan on prt2_p3 t2_3 - -> Hash - -> Seq Scan on prt1_p3 t1_3 - Filter: (b = 0) -(21 rows) - --- test default partition behavior for list -ALTER TABLE plt1 DETACH PARTITION plt1_p3; -ALTER TABLE plt1 ATTACH PARTITION plt1_p3 DEFAULT; -ANALYZE plt1; -ALTER TABLE plt2 DETACH PARTITION plt2_p3; -ALTER TABLE plt2 ATTACH PARTITION plt2_p3 DEFAULT; -ANALYZE plt2; -EXPLAIN (COSTS OFF) -SELECT avg(t1.a), avg(t2.b), t1.c, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c = t2.c WHERE t1.a % 25 = 0 GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c; - QUERY PLAN --------------------------------------------------------- - Sort - Sort Key: t1.c - -> HashAggregate - Group Key: t1.c - -> Append - -> Hash Join - Hash Cond: (t2_1.c = t1_1.c) - -> Seq Scan on plt2_p1 t2_1 - -> Hash - -> Seq Scan on plt1_p1 t1_1 - Filter: ((a % 25) = 0) - -> Hash Join - Hash Cond: (t2_2.c = t1_2.c) - -> Seq Scan on plt2_p2 t2_2 - -> Hash - -> Seq Scan on plt1_p2 t1_2 - Filter: ((a % 25) = 0) - -> Hash Join - Hash Cond: (t2_3.c = t1_3.c) - -> Seq Scan on plt2_p3 t2_3 - -> Hash - -> Seq Scan on plt1_p3 t1_3 - Filter: ((a % 25) = 0) -(23 rows) - --- --- multiple levels of partitioning --- -CREATE TABLE prt1_l (a int, b int, c varchar) PARTITION BY RANGE(a); -CREATE TABLE prt1_l_p1 PARTITION OF prt1_l FOR VALUES FROM (0) TO (250); -CREATE TABLE prt1_l_p2 PARTITION OF prt1_l FOR VALUES FROM (250) TO (500) PARTITION BY LIST (c); -CREATE TABLE prt1_l_p2_p1 PARTITION OF prt1_l_p2 FOR VALUES IN ('0000', '0001'); -CREATE TABLE prt1_l_p2_p2 PARTITION OF prt1_l_p2 FOR VALUES IN ('0002', '0003'); -CREATE TABLE prt1_l_p3 PARTITION OF prt1_l FOR VALUES FROM (500) TO (600) PARTITION BY RANGE (b); -CREATE TABLE prt1_l_p3_p1 PARTITION OF prt1_l_p3 FOR VALUES FROM (0) TO (13); -CREATE TABLE prt1_l_p3_p2 PARTITION OF prt1_l_p3 FOR VALUES FROM (13) TO (25); -INSERT INTO prt1_l SELECT i, i % 25, to_char(i % 4, 'FM0000') FROM generate_series(0, 599, 2) i; -ANALYZE prt1_l; -CREATE TABLE prt2_l (a int, b int, c varchar) PARTITION BY RANGE(b); -CREATE TABLE prt2_l_p1 PARTITION OF prt2_l FOR VALUES FROM (0) TO (250); -CREATE TABLE prt2_l_p2 PARTITION OF prt2_l FOR VALUES FROM (250) TO (500) PARTITION BY LIST (c); -CREATE TABLE prt2_l_p2_p1 PARTITION OF prt2_l_p2 FOR VALUES IN ('0000', '0001'); -CREATE TABLE prt2_l_p2_p2 PARTITION OF prt2_l_p2 FOR VALUES IN ('0002', '0003'); -CREATE TABLE prt2_l_p3 PARTITION OF prt2_l FOR VALUES FROM (500) TO (600) PARTITION BY RANGE (a); -CREATE TABLE prt2_l_p3_p1 PARTITION OF prt2_l_p3 FOR VALUES FROM (0) TO (13); -CREATE TABLE prt2_l_p3_p2 PARTITION OF prt2_l_p3 FOR VALUES FROM (13) TO (25); -INSERT INTO prt2_l SELECT i % 25, i, to_char(i % 4, 'FM0000') FROM generate_series(0, 599, 3) i; -ANALYZE prt2_l; --- inner join, qual covering only top-level partitions -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN -------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Join - Hash Cond: (t2_1.b = t1_1.a) - -> Seq Scan on prt2_l_p1 t2_1 - -> Hash - -> Seq Scan on prt1_l_p1 t1_1 - Filter: (b = 0) - -> Hash Join - Hash Cond: (t2_3.b = t1_3.a) - -> Append - -> Seq Scan on prt2_l_p2_p1 t2_3 - -> Seq Scan on prt2_l_p2_p2 t2_4 - -> Hash - -> Append - -> Seq Scan on prt1_l_p2_p1 t1_3 - Filter: (b = 0) - -> Seq Scan on prt1_l_p2_p2 t1_4 - Filter: (b = 0) - -> Hash Join - Hash Cond: (t2_6.b = t1_5.a) - -> Append - -> Seq Scan on prt2_l_p3_p1 t2_6 - -> Seq Scan on prt2_l_p3_p2 t2_7 - -> Hash - -> Seq Scan on prt1_l_p3_p1 t1_5 - Filter: (b = 0) -(28 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 0 | 0000 | 0 | 0000 - 150 | 0002 | 150 | 0002 - 300 | 0000 | 300 | 0000 - 450 | 0002 | 450 | 0002 -(4 rows) - --- inner join with partially-redundant join clauses -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.a AND t1.a = t2.b AND t1.c = t2.c ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Join - Hash Cond: ((t1_1.a = t2_1.a) AND ((t1_1.c)::text = (t2_1.c)::text)) - -> Seq Scan on prt1_l_p1 t1_1 - -> Hash - -> Seq Scan on prt2_l_p1 t2_1 - Filter: (a = b) - -> Hash Join - Hash Cond: ((t1_2.a = t2_2.a) AND ((t1_2.c)::text = (t2_2.c)::text)) - -> Seq Scan on prt1_l_p2_p1 t1_2 - -> Hash - -> Seq Scan on prt2_l_p2_p1 t2_2 - Filter: (a = b) - -> Hash Join - Hash Cond: ((t1_3.a = t2_3.a) AND ((t1_3.c)::text = (t2_3.c)::text)) - -> Seq Scan on prt1_l_p2_p2 t1_3 - -> Hash - -> Seq Scan on prt2_l_p2_p2 t2_3 - Filter: (a = b) - -> Hash Join - Hash Cond: ((t1_5.a = t2_5.a) AND ((t1_5.c)::text = (t2_5.c)::text)) - -> Append - -> Seq Scan on prt1_l_p3_p1 t1_5 - -> Seq Scan on prt1_l_p3_p2 t1_6 - -> Hash - -> Append - -> Seq Scan on prt2_l_p3_p1 t2_5 - Filter: (a = b) - -> Seq Scan on prt2_l_p3_p2 t2_6 - Filter: (a = b) -(32 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.a AND t1.a = t2.b AND t1.c = t2.c ORDER BY t1.a, t2.b; - a | c | b | c -----+------+----+------ - 0 | 0000 | 0 | 0000 - 6 | 0002 | 6 | 0002 - 12 | 0000 | 12 | 0000 - 18 | 0002 | 18 | 0002 - 24 | 0000 | 24 | 0000 -(5 rows) - --- left join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 LEFT JOIN prt2_l t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------------------------------------- - Sort - Sort Key: t1.a, t2.b - -> Append - -> Hash Right Join - Hash Cond: ((t2_1.b = t1_1.a) AND ((t2_1.c)::text = (t1_1.c)::text)) - -> Seq Scan on prt2_l_p1 t2_1 - -> Hash - -> Seq Scan on prt1_l_p1 t1_1 - Filter: (b = 0) - -> Hash Right Join - Hash Cond: ((t2_2.b = t1_2.a) AND ((t2_2.c)::text = (t1_2.c)::text)) - -> Seq Scan on prt2_l_p2_p1 t2_2 - -> Hash - -> Seq Scan on prt1_l_p2_p1 t1_2 - Filter: (b = 0) - -> Hash Right Join - Hash Cond: ((t2_3.b = t1_3.a) AND ((t2_3.c)::text = (t1_3.c)::text)) - -> Seq Scan on prt2_l_p2_p2 t2_3 - -> Hash - -> Seq Scan on prt1_l_p2_p2 t1_3 - Filter: (b = 0) - -> Hash Right Join - Hash Cond: ((t2_5.b = t1_4.a) AND ((t2_5.c)::text = (t1_4.c)::text)) - -> Append - -> Seq Scan on prt2_l_p3_p1 t2_5 - -> Seq Scan on prt2_l_p3_p2 t2_6 - -> Hash - -> Seq Scan on prt1_l_p3_p1 t1_4 - Filter: (b = 0) -(29 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 LEFT JOIN prt2_l t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t1.b = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 0 | 0000 | 0 | 0000 - 50 | 0002 | | - 100 | 0000 | | - 150 | 0002 | 150 | 0002 - 200 | 0000 | | - 250 | 0002 | | - 300 | 0000 | 300 | 0000 - 350 | 0002 | | - 400 | 0000 | | - 450 | 0002 | 450 | 0002 - 500 | 0000 | | - 550 | 0002 | | -(12 rows) - --- right join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t2.a = 0 ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------------------------------------- - Sort - Sort Key: t1.a, t2.b - -> Append - -> Hash Right Join - Hash Cond: ((t1_1.a = t2_1.b) AND ((t1_1.c)::text = (t2_1.c)::text)) - -> Seq Scan on prt1_l_p1 t1_1 - -> Hash - -> Seq Scan on prt2_l_p1 t2_1 - Filter: (a = 0) - -> Hash Right Join - Hash Cond: ((t1_2.a = t2_2.b) AND ((t1_2.c)::text = (t2_2.c)::text)) - -> Seq Scan on prt1_l_p2_p1 t1_2 - -> Hash - -> Seq Scan on prt2_l_p2_p1 t2_2 - Filter: (a = 0) - -> Hash Right Join - Hash Cond: ((t1_3.a = t2_3.b) AND ((t1_3.c)::text = (t2_3.c)::text)) - -> Seq Scan on prt1_l_p2_p2 t1_3 - -> Hash - -> Seq Scan on prt2_l_p2_p2 t2_3 - Filter: (a = 0) - -> Hash Right Join - Hash Cond: ((t1_5.a = t2_4.b) AND ((t1_5.c)::text = (t2_4.c)::text)) - -> Append - -> Seq Scan on prt1_l_p3_p1 t1_5 - -> Seq Scan on prt1_l_p3_p2 t1_6 - -> Hash - -> Seq Scan on prt2_l_p3_p1 t2_4 - Filter: (a = 0) -(29 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t2.a = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 0 | 0000 | 0 | 0000 - 150 | 0002 | 150 | 0002 - 300 | 0000 | 300 | 0000 - 450 | 0002 | 450 | 0002 - | | 75 | 0003 - | | 225 | 0001 - | | 375 | 0003 - | | 525 | 0001 -(8 rows) - --- full join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE prt1_l.b = 0) t1 FULL JOIN (SELECT * FROM prt2_l WHERE prt2_l.a = 0) t2 ON (t1.a = t2.b AND t1.c = t2.c) ORDER BY t1.a, t2.b; - QUERY PLAN ----------------------------------------------------------------------------------------------------- - Sort - Sort Key: prt1_l.a, prt2_l.b - -> Append - -> Hash Full Join - Hash Cond: ((prt1_l_1.a = prt2_l_1.b) AND ((prt1_l_1.c)::text = (prt2_l_1.c)::text)) - -> Seq Scan on prt1_l_p1 prt1_l_1 - Filter: (b = 0) - -> Hash - -> Seq Scan on prt2_l_p1 prt2_l_1 - Filter: (a = 0) - -> Hash Full Join - Hash Cond: ((prt1_l_2.a = prt2_l_2.b) AND ((prt1_l_2.c)::text = (prt2_l_2.c)::text)) - -> Seq Scan on prt1_l_p2_p1 prt1_l_2 - Filter: (b = 0) - -> Hash - -> Seq Scan on prt2_l_p2_p1 prt2_l_2 - Filter: (a = 0) - -> Hash Full Join - Hash Cond: ((prt1_l_3.a = prt2_l_3.b) AND ((prt1_l_3.c)::text = (prt2_l_3.c)::text)) - -> Seq Scan on prt1_l_p2_p2 prt1_l_3 - Filter: (b = 0) - -> Hash - -> Seq Scan on prt2_l_p2_p2 prt2_l_3 - Filter: (a = 0) - -> Hash Full Join - Hash Cond: ((prt1_l_4.a = prt2_l_4.b) AND ((prt1_l_4.c)::text = (prt2_l_4.c)::text)) - -> Seq Scan on prt1_l_p3_p1 prt1_l_4 - Filter: (b = 0) - -> Hash - -> Seq Scan on prt2_l_p3_p1 prt2_l_4 - Filter: (a = 0) -(31 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE prt1_l.b = 0) t1 FULL JOIN (SELECT * FROM prt2_l WHERE prt2_l.a = 0) t2 ON (t1.a = t2.b AND t1.c = t2.c) ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 0 | 0000 | 0 | 0000 - 50 | 0002 | | - 100 | 0000 | | - 150 | 0002 | 150 | 0002 - 200 | 0000 | | - 250 | 0002 | | - 300 | 0000 | 300 | 0000 - 350 | 0002 | | - 400 | 0000 | | - 450 | 0002 | 450 | 0002 - 500 | 0000 | | - 550 | 0002 | | - | | 75 | 0003 - | | 225 | 0001 - | | 375 | 0003 - | | 525 | 0001 -(16 rows) - --- lateral partitionwise join -EXPLAIN (COSTS OFF) -SELECT * FROM prt1_l t1 LEFT JOIN LATERAL - (SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.b AS t3b, least(t1.a,t2.a,t3.b) FROM prt1_l t2 JOIN prt2_l t3 ON (t2.a = t3.b AND t2.c = t3.c)) ss - ON t1.a = ss.t2a AND t1.c = ss.t2c WHERE t1.b = 0 ORDER BY t1.a; - QUERY PLAN ------------------------------------------------------------------------------------------------ - Sort - Sort Key: t1.a - -> Append - -> Nested Loop Left Join - -> Seq Scan on prt1_l_p1 t1_1 - Filter: (b = 0) - -> Hash Join - Hash Cond: ((t3_1.b = t2_1.a) AND ((t3_1.c)::text = (t2_1.c)::text)) - -> Seq Scan on prt2_l_p1 t3_1 - -> Hash - -> Seq Scan on prt1_l_p1 t2_1 - Filter: ((t1_1.a = a) AND ((t1_1.c)::text = (c)::text)) - -> Nested Loop Left Join - -> Seq Scan on prt1_l_p2_p1 t1_2 - Filter: (b = 0) - -> Hash Join - Hash Cond: ((t3_2.b = t2_2.a) AND ((t3_2.c)::text = (t2_2.c)::text)) - -> Seq Scan on prt2_l_p2_p1 t3_2 - -> Hash - -> Seq Scan on prt1_l_p2_p1 t2_2 - Filter: ((t1_2.a = a) AND ((t1_2.c)::text = (c)::text)) - -> Nested Loop Left Join - -> Seq Scan on prt1_l_p2_p2 t1_3 - Filter: (b = 0) - -> Hash Join - Hash Cond: ((t3_3.b = t2_3.a) AND ((t3_3.c)::text = (t2_3.c)::text)) - -> Seq Scan on prt2_l_p2_p2 t3_3 - -> Hash - -> Seq Scan on prt1_l_p2_p2 t2_3 - Filter: ((t1_3.a = a) AND ((t1_3.c)::text = (c)::text)) - -> Nested Loop Left Join - -> Seq Scan on prt1_l_p3_p1 t1_4 - Filter: (b = 0) - -> Hash Join - Hash Cond: ((t3_5.b = t2_5.a) AND ((t3_5.c)::text = (t2_5.c)::text)) - -> Append - -> Seq Scan on prt2_l_p3_p1 t3_5 - -> Seq Scan on prt2_l_p3_p2 t3_6 - -> Hash - -> Append - -> Seq Scan on prt1_l_p3_p1 t2_5 - Filter: ((t1_4.a = a) AND ((t1_4.c)::text = (c)::text)) - -> Seq Scan on prt1_l_p3_p2 t2_6 - Filter: ((t1_4.a = a) AND ((t1_4.c)::text = (c)::text)) -(44 rows) - -SELECT * FROM prt1_l t1 LEFT JOIN LATERAL - (SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.b AS t3b, least(t1.a,t2.a,t3.b) FROM prt1_l t2 JOIN prt2_l t3 ON (t2.a = t3.b AND t2.c = t3.c)) ss - ON t1.a = ss.t2a AND t1.c = ss.t2c WHERE t1.b = 0 ORDER BY t1.a; - a | b | c | t2a | t2c | t2b | t3b | least ------+---+------+-----+------+-----+-----+------- - 0 | 0 | 0000 | 0 | 0000 | 0 | 0 | 0 - 50 | 0 | 0002 | | | | | - 100 | 0 | 0000 | | | | | - 150 | 0 | 0002 | 150 | 0002 | 0 | 150 | 150 - 200 | 0 | 0000 | | | | | - 250 | 0 | 0002 | | | | | - 300 | 0 | 0000 | 300 | 0000 | 0 | 300 | 300 - 350 | 0 | 0002 | | | | | - 400 | 0 | 0000 | | | | | - 450 | 0 | 0002 | 450 | 0002 | 0 | 450 | 450 - 500 | 0 | 0000 | | | | | - 550 | 0 | 0002 | | | | | -(12 rows) - --- partitionwise join with lateral reference in sample scan -EXPLAIN (COSTS OFF) -SELECT * FROM prt1_l t1 JOIN LATERAL - (SELECT * FROM prt1_l t2 TABLESAMPLE SYSTEM (t1.a) REPEATABLE(t1.b)) s - ON t1.a = s.a AND t1.b = s.b AND t1.c = s.c; - QUERY PLAN ----------------------------------------------------------------------------------------- - Append - -> Nested Loop - -> Seq Scan on prt1_l_p1 t1_1 - -> Sample Scan on prt1_l_p1 t2_1 - Sampling: system (t1_1.a) REPEATABLE (t1_1.b) - Filter: ((t1_1.a = a) AND (t1_1.b = b) AND ((t1_1.c)::text = (c)::text)) - -> Nested Loop - -> Seq Scan on prt1_l_p2_p1 t1_2 - -> Sample Scan on prt1_l_p2_p1 t2_2 - Sampling: system (t1_2.a) REPEATABLE (t1_2.b) - Filter: ((t1_2.a = a) AND (t1_2.b = b) AND ((t1_2.c)::text = (c)::text)) - -> Nested Loop - -> Seq Scan on prt1_l_p2_p2 t1_3 - -> Sample Scan on prt1_l_p2_p2 t2_3 - Sampling: system (t1_3.a) REPEATABLE (t1_3.b) - Filter: ((t1_3.a = a) AND (t1_3.b = b) AND ((t1_3.c)::text = (c)::text)) - -> Nested Loop - -> Seq Scan on prt1_l_p3_p1 t1_4 - -> Sample Scan on prt1_l_p3_p1 t2_4 - Sampling: system (t1_4.a) REPEATABLE (t1_4.b) - Filter: ((t1_4.a = a) AND (t1_4.b = b) AND ((t1_4.c)::text = (c)::text)) - -> Nested Loop - -> Seq Scan on prt1_l_p3_p2 t1_5 - -> Sample Scan on prt1_l_p3_p2 t2_5 - Sampling: system (t1_5.a) REPEATABLE (t1_5.b) - Filter: ((t1_5.a = a) AND (t1_5.b = b) AND ((t1_5.c)::text = (c)::text)) -(26 rows) - --- partitionwise join with lateral reference in scan's restriction clauses -EXPLAIN (COSTS OFF) -SELECT COUNT(*) FROM prt1_l t1 LEFT JOIN LATERAL - (SELECT t1.b AS t1b, t2.* FROM prt2_l t2) s - ON t1.a = s.b AND t1.b = s.a AND t1.c = s.c - WHERE s.t1b = s.a; - QUERY PLAN ---------------------------------------------------------------------------------------------------------------- - Aggregate - -> Append - -> Nested Loop - -> Seq Scan on prt1_l_p1 t1_1 - -> Seq Scan on prt2_l_p1 t2_1 - Filter: ((a = t1_1.b) AND (t1_1.a = b) AND (t1_1.b = a) AND ((t1_1.c)::text = (c)::text)) - -> Nested Loop - -> Seq Scan on prt1_l_p2_p1 t1_2 - -> Seq Scan on prt2_l_p2_p1 t2_2 - Filter: ((a = t1_2.b) AND (t1_2.a = b) AND (t1_2.b = a) AND ((t1_2.c)::text = (c)::text)) - -> Nested Loop - -> Seq Scan on prt1_l_p2_p2 t1_3 - -> Seq Scan on prt2_l_p2_p2 t2_3 - Filter: ((a = t1_3.b) AND (t1_3.a = b) AND (t1_3.b = a) AND ((t1_3.c)::text = (c)::text)) - -> Nested Loop - -> Seq Scan on prt1_l_p3_p1 t1_4 - -> Seq Scan on prt2_l_p3_p1 t2_4 - Filter: ((a = t1_4.b) AND (t1_4.a = b) AND (t1_4.b = a) AND ((t1_4.c)::text = (c)::text)) - -> Nested Loop - -> Seq Scan on prt1_l_p3_p2 t1_5 - -> Seq Scan on prt2_l_p3_p2 t2_5 - Filter: ((a = t1_5.b) AND (t1_5.a = b) AND (t1_5.b = a) AND ((t1_5.c)::text = (c)::text)) -(22 rows) - -SELECT COUNT(*) FROM prt1_l t1 LEFT JOIN LATERAL - (SELECT t1.b AS t1b, t2.* FROM prt2_l t2) s - ON t1.a = s.b AND t1.b = s.a AND t1.c = s.c - WHERE s.t1b = s.a; - count -------- - 100 -(1 row) - --- join with one side empty -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE a = 1 AND a = 2) t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c; - QUERY PLAN -------------------------------------------------------------------------- - Hash Left Join - Hash Cond: ((t2.b = a) AND (t2.a = b) AND ((t2.c)::text = (c)::text)) - -> Append - -> Seq Scan on prt2_l_p1 t2_1 - -> Seq Scan on prt2_l_p2_p1 t2_2 - -> Seq Scan on prt2_l_p2_p2 t2_3 - -> Seq Scan on prt2_l_p3_p1 t2_4 - -> Seq Scan on prt2_l_p3_p2 t2_5 - -> Hash - -> Result - One-Time Filter: false -(11 rows) - --- Test case to verify proper handling of subqueries in a partitioned delete. --- The weird-looking lateral join is just there to force creation of a --- nestloop parameter within the subquery, which exposes the problem if the --- planner fails to make multiple copies of the subquery as appropriate. -EXPLAIN (COSTS OFF) -DELETE FROM prt1_l -WHERE EXISTS ( - SELECT 1 - FROM int4_tbl, - LATERAL (SELECT int4_tbl.f1 FROM int8_tbl LIMIT 2) ss - WHERE prt1_l.c IS NULL); - QUERY PLAN ----------------------------------------------------------- - Delete on prt1_l - Delete on prt1_l_p1 prt1_l_1 - Delete on prt1_l_p3_p1 prt1_l_2 - Delete on prt1_l_p3_p2 prt1_l_3 - -> Nested Loop Semi Join - -> Append - -> Seq Scan on prt1_l_p1 prt1_l_1 - Filter: (c IS NULL) - -> Seq Scan on prt1_l_p3_p1 prt1_l_2 - Filter: (c IS NULL) - -> Seq Scan on prt1_l_p3_p2 prt1_l_3 - Filter: (c IS NULL) - -> Materialize - -> Nested Loop - -> Seq Scan on int4_tbl - -> Subquery Scan on ss - -> Limit - -> Seq Scan on int8_tbl -(18 rows) - --- --- negative testcases --- -CREATE TABLE prt1_n (a int, b int, c varchar) PARTITION BY RANGE(c); -CREATE TABLE prt1_n_p1 PARTITION OF prt1_n FOR VALUES FROM ('0000') TO ('0250'); -CREATE TABLE prt1_n_p2 PARTITION OF prt1_n FOR VALUES FROM ('0250') TO ('0500'); -INSERT INTO prt1_n SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 499, 2) i; -ANALYZE prt1_n; -CREATE TABLE prt2_n (a int, b int, c text) PARTITION BY LIST(c); -CREATE TABLE prt2_n_p1 PARTITION OF prt2_n FOR VALUES IN ('0000', '0003', '0004', '0010', '0006', '0007'); -CREATE TABLE prt2_n_p2 PARTITION OF prt2_n FOR VALUES IN ('0001', '0005', '0002', '0009', '0008', '0011'); -INSERT INTO prt2_n SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; -ANALYZE prt2_n; -CREATE TABLE prt3_n (a int, b int, c text) PARTITION BY LIST(c); -CREATE TABLE prt3_n_p1 PARTITION OF prt3_n FOR VALUES IN ('0000', '0004', '0006', '0007'); -CREATE TABLE prt3_n_p2 PARTITION OF prt3_n FOR VALUES IN ('0001', '0002', '0008', '0010'); -CREATE TABLE prt3_n_p3 PARTITION OF prt3_n FOR VALUES IN ('0003', '0005', '0009', '0011'); -INSERT INTO prt2_n SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; -ANALYZE prt3_n; -CREATE TABLE prt4_n (a int, b int, c text) PARTITION BY RANGE(a); -CREATE TABLE prt4_n_p1 PARTITION OF prt4_n FOR VALUES FROM (0) TO (300); -CREATE TABLE prt4_n_p2 PARTITION OF prt4_n FOR VALUES FROM (300) TO (500); -CREATE TABLE prt4_n_p3 PARTITION OF prt4_n FOR VALUES FROM (500) TO (600); -INSERT INTO prt4_n SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i; -ANALYZE prt4_n; --- partitionwise join can not be applied if the partition ranges differ -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt4_n t2 WHERE t1.a = t2.a; - QUERY PLAN ----------------------------------------------- - Hash Join - Hash Cond: (t1.a = t2.a) - -> Append - -> Seq Scan on prt1_p1 t1_1 - -> Seq Scan on prt1_p2 t1_2 - -> Seq Scan on prt1_p3 t1_3 - -> Hash - -> Append - -> Seq Scan on prt4_n_p1 t2_1 - -> Seq Scan on prt4_n_p2 t2_2 - -> Seq Scan on prt4_n_p3 t2_3 -(11 rows) - -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt4_n t2, prt2 t3 WHERE t1.a = t2.a and t1.a = t3.b; - QUERY PLAN --------------------------------------------------------- - Hash Join - Hash Cond: (t2.a = t1.a) - -> Append - -> Seq Scan on prt4_n_p1 t2_1 - -> Seq Scan on prt4_n_p2 t2_2 - -> Seq Scan on prt4_n_p3 t2_3 - -> Hash - -> Append - -> Hash Join - Hash Cond: (t1_1.a = t3_1.b) - -> Seq Scan on prt1_p1 t1_1 - -> Hash - -> Seq Scan on prt2_p1 t3_1 - -> Hash Join - Hash Cond: (t1_2.a = t3_2.b) - -> Seq Scan on prt1_p2 t1_2 - -> Hash - -> Seq Scan on prt2_p2 t3_2 - -> Hash Join - Hash Cond: (t1_3.a = t3_3.b) - -> Seq Scan on prt1_p3 t1_3 - -> Hash - -> Seq Scan on prt2_p3 t3_3 -(23 rows) - --- partitionwise join can not be applied if there are no equi-join conditions --- between partition keys -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 LEFT JOIN prt2 t2 ON (t1.a < t2.b); - QUERY PLAN ---------------------------------------------------------- - Nested Loop Left Join - -> Append - -> Seq Scan on prt1_p1 t1_1 - -> Seq Scan on prt1_p2 t1_2 - -> Seq Scan on prt1_p3 t1_3 - -> Append - -> Index Scan using iprt2_p1_b on prt2_p1 t2_1 - Index Cond: (b > t1.a) - -> Index Scan using iprt2_p2_b on prt2_p2 t2_2 - Index Cond: (b > t1.a) - -> Index Scan using iprt2_p3_b on prt2_p3 t2_3 - Index Cond: (b > t1.a) -(12 rows) - --- equi-join with join condition on partial keys does not qualify for --- partitionwise join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_m t1, prt2_m t2 WHERE t1.a = (t2.b + t2.a)/2; - QUERY PLAN ----------------------------------------------- - Hash Join - Hash Cond: (((t2.b + t2.a) / 2) = t1.a) - -> Append - -> Seq Scan on prt2_m_p1 t2_1 - -> Seq Scan on prt2_m_p2 t2_2 - -> Seq Scan on prt2_m_p3 t2_3 - -> Hash - -> Append - -> Seq Scan on prt1_m_p1 t1_1 - -> Seq Scan on prt1_m_p2 t1_2 - -> Seq Scan on prt1_m_p3 t1_3 -(11 rows) - --- equi-join between out-of-order partition key columns does not qualify for --- partitionwise join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_m t1 LEFT JOIN prt2_m t2 ON t1.a = t2.b; - QUERY PLAN ----------------------------------------------- - Hash Left Join - Hash Cond: (t1.a = t2.b) - -> Append - -> Seq Scan on prt1_m_p1 t1_1 - -> Seq Scan on prt1_m_p2 t1_2 - -> Seq Scan on prt1_m_p3 t1_3 - -> Hash - -> Append - -> Seq Scan on prt2_m_p1 t2_1 - -> Seq Scan on prt2_m_p2 t2_2 - -> Seq Scan on prt2_m_p3 t2_3 -(11 rows) - --- equi-join between non-key columns does not qualify for partitionwise join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_m t1 LEFT JOIN prt2_m t2 ON t1.c = t2.c; - QUERY PLAN ----------------------------------------------- - Hash Left Join - Hash Cond: (t1.c = t2.c) - -> Append - -> Seq Scan on prt1_m_p1 t1_1 - -> Seq Scan on prt1_m_p2 t1_2 - -> Seq Scan on prt1_m_p3 t1_3 - -> Hash - -> Append - -> Seq Scan on prt2_m_p1 t2_1 - -> Seq Scan on prt2_m_p2 t2_2 - -> Seq Scan on prt2_m_p3 t2_3 -(11 rows) - --- partitionwise join can not be applied for a join between list and range --- partitioned tables -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_n t1 LEFT JOIN prt2_n t2 ON (t1.c = t2.c); - QUERY PLAN ----------------------------------------------- - Hash Right Join - Hash Cond: (t2.c = (t1.c)::text) - -> Append - -> Seq Scan on prt2_n_p1 t2_1 - -> Seq Scan on prt2_n_p2 t2_2 - -> Hash - -> Append - -> Seq Scan on prt1_n_p1 t1_1 - -> Seq Scan on prt1_n_p2 t1_2 -(9 rows) - --- partitionwise join can not be applied between tables with different --- partition lists -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_n t1 JOIN prt2_n t2 ON (t1.c = t2.c) JOIN plt1 t3 ON (t1.c = t3.c); - QUERY PLAN ----------------------------------------------------------- - Hash Join - Hash Cond: (t2.c = (t1.c)::text) - -> Append - -> Seq Scan on prt2_n_p1 t2_1 - -> Seq Scan on prt2_n_p2 t2_2 - -> Hash - -> Hash Join - Hash Cond: (t3.c = (t1.c)::text) - -> Append - -> Seq Scan on plt1_p1 t3_1 - -> Seq Scan on plt1_p2 t3_2 - -> Seq Scan on plt1_p3 t3_3 - -> Hash - -> Append - -> Seq Scan on prt1_n_p1 t1_1 - -> Seq Scan on prt1_n_p2 t1_2 -(16 rows) - --- partitionwise join can not be applied for a join between key column and --- non-key column -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_n t1 FULL JOIN prt1 t2 ON (t1.c = t2.c); - QUERY PLAN ----------------------------------------------- - Hash Full Join - Hash Cond: ((t2.c)::text = (t1.c)::text) - -> Append - -> Seq Scan on prt1_p1 t2_1 - -> Seq Scan on prt1_p2 t2_2 - -> Seq Scan on prt1_p3 t2_3 - -> Hash - -> Append - -> Seq Scan on prt1_n_p1 t1_1 - -> Seq Scan on prt1_n_p2 t1_2 -(10 rows) - --- --- Test some other plan types in a partitionwise join (unfortunately, --- we need larger tables to get the planner to choose these plan types) --- -create temp table prtx1 (a integer, b integer, c integer) - partition by range (a); -create temp table prtx1_1 partition of prtx1 for values from (1) to (11); -create temp table prtx1_2 partition of prtx1 for values from (11) to (21); -create temp table prtx1_3 partition of prtx1 for values from (21) to (31); -create temp table prtx2 (a integer, b integer, c integer) - partition by range (a); -create temp table prtx2_1 partition of prtx2 for values from (1) to (11); -create temp table prtx2_2 partition of prtx2 for values from (11) to (21); -create temp table prtx2_3 partition of prtx2 for values from (21) to (31); -insert into prtx1 select 1 + i%30, i, i - from generate_series(1,1000) i; -insert into prtx2 select 1 + i%30, i, i - from generate_series(1,500) i, generate_series(1,10) j; -create index on prtx2 (b); -create index on prtx2 (c); -analyze prtx1; -analyze prtx2; -explain (costs off) -select * from prtx1 -where not exists (select 1 from prtx2 - where prtx2.a=prtx1.a and prtx2.b=prtx1.b and prtx2.c=123) - and a<20 and c=120; - QUERY PLAN -------------------------------------------------------------- - Append - -> Nested Loop Anti Join - -> Seq Scan on prtx1_1 - Filter: ((a < 20) AND (c = 120)) - -> Bitmap Heap Scan on prtx2_1 - Recheck Cond: ((b = prtx1_1.b) AND (c = 123)) - Filter: (a = prtx1_1.a) - -> BitmapAnd - -> Bitmap Index Scan on prtx2_1_b_idx - Index Cond: (b = prtx1_1.b) - -> Bitmap Index Scan on prtx2_1_c_idx - Index Cond: (c = 123) - -> Nested Loop Anti Join - -> Seq Scan on prtx1_2 - Filter: ((a < 20) AND (c = 120)) - -> Bitmap Heap Scan on prtx2_2 - Recheck Cond: ((b = prtx1_2.b) AND (c = 123)) - Filter: (a = prtx1_2.a) - -> BitmapAnd - -> Bitmap Index Scan on prtx2_2_b_idx - Index Cond: (b = prtx1_2.b) - -> Bitmap Index Scan on prtx2_2_c_idx - Index Cond: (c = 123) -(23 rows) - -select * from prtx1 -where not exists (select 1 from prtx2 - where prtx2.a=prtx1.a and prtx2.b=prtx1.b and prtx2.c=123) - and a<20 and c=120; - a | b | c ----+-----+----- - 1 | 120 | 120 -(1 row) - -explain (costs off) -select * from prtx1 -where not exists (select 1 from prtx2 - where prtx2.a=prtx1.a and (prtx2.b=prtx1.b+1 or prtx2.c=99)) - and a<20 and c=91; - QUERY PLAN ------------------------------------------------------------------ - Append - -> Nested Loop Anti Join - -> Seq Scan on prtx1_1 - Filter: ((a < 20) AND (c = 91)) - -> Bitmap Heap Scan on prtx2_1 - Recheck Cond: ((b = (prtx1_1.b + 1)) OR (c = 99)) - Filter: (a = prtx1_1.a) - -> BitmapOr - -> Bitmap Index Scan on prtx2_1_b_idx - Index Cond: (b = (prtx1_1.b + 1)) - -> Bitmap Index Scan on prtx2_1_c_idx - Index Cond: (c = 99) - -> Nested Loop Anti Join - -> Seq Scan on prtx1_2 - Filter: ((a < 20) AND (c = 91)) - -> Bitmap Heap Scan on prtx2_2 - Recheck Cond: ((b = (prtx1_2.b + 1)) OR (c = 99)) - Filter: (a = prtx1_2.a) - -> BitmapOr - -> Bitmap Index Scan on prtx2_2_b_idx - Index Cond: (b = (prtx1_2.b + 1)) - -> Bitmap Index Scan on prtx2_2_c_idx - Index Cond: (c = 99) -(23 rows) - -select * from prtx1 -where not exists (select 1 from prtx2 - where prtx2.a=prtx1.a and (prtx2.b=prtx1.b+1 or prtx2.c=99)) - and a<20 and c=91; - a | b | c ----+----+---- - 2 | 91 | 91 -(1 row) - --- --- Test advanced partition-matching algorithm for partitioned join --- --- Tests for range-partitioned tables -CREATE TABLE prt1_adv (a int, b int, c varchar) PARTITION BY RANGE (a); -CREATE TABLE prt1_adv_p1 PARTITION OF prt1_adv FOR VALUES FROM (100) TO (200); -CREATE TABLE prt1_adv_p2 PARTITION OF prt1_adv FOR VALUES FROM (200) TO (300); -CREATE TABLE prt1_adv_p3 PARTITION OF prt1_adv FOR VALUES FROM (300) TO (400); -CREATE INDEX prt1_adv_a_idx ON prt1_adv (a); -INSERT INTO prt1_adv SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(100, 399) i; -ANALYZE prt1_adv; -CREATE TABLE prt2_adv (a int, b int, c varchar) PARTITION BY RANGE (b); -CREATE TABLE prt2_adv_p1 PARTITION OF prt2_adv FOR VALUES FROM (100) TO (150); -CREATE TABLE prt2_adv_p2 PARTITION OF prt2_adv FOR VALUES FROM (200) TO (300); -CREATE TABLE prt2_adv_p3 PARTITION OF prt2_adv FOR VALUES FROM (350) TO (500); -CREATE INDEX prt2_adv_b_idx ON prt2_adv (b); -INSERT INTO prt2_adv_p1 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(100, 149) i; -INSERT INTO prt2_adv_p2 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(200, 299) i; -INSERT INTO prt2_adv_p3 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(350, 499) i; -ANALYZE prt2_adv; --- inner join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Join - Hash Cond: (t2_1.b = t1_1.a) - -> Seq Scan on prt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on prt1_adv_p1 t1_1 - Filter: (b = 0) - -> Hash Join - Hash Cond: (t2_2.b = t1_2.a) - -> Seq Scan on prt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on prt1_adv_p2 t1_2 - Filter: (b = 0) - -> Hash Join - Hash Cond: (t2_3.b = t1_3.a) - -> Seq Scan on prt2_adv_p3 t2_3 - -> Hash - -> Seq Scan on prt1_adv_p3 t1_3 - Filter: (b = 0) -(21 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 100 | 0100 | 100 | 0100 - 125 | 0125 | 125 | 0125 - 200 | 0200 | 200 | 0200 - 225 | 0225 | 225 | 0225 - 250 | 0250 | 250 | 0250 - 275 | 0275 | 275 | 0275 - 350 | 0350 | 350 | 0350 - 375 | 0375 | 375 | 0375 -(8 rows) - --- semi join -EXPLAIN (COSTS OFF) -SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Right Semi Join - Hash Cond: (t2_1.b = t1_1.a) - -> Seq Scan on prt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on prt1_adv_p1 t1_1 - Filter: (b = 0) - -> Hash Right Semi Join - Hash Cond: (t2_2.b = t1_2.a) - -> Seq Scan on prt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on prt1_adv_p2 t1_2 - Filter: (b = 0) - -> Hash Right Semi Join - Hash Cond: (t2_3.b = t1_3.a) - -> Seq Scan on prt2_adv_p3 t2_3 - -> Hash - -> Seq Scan on prt1_adv_p3 t1_3 - Filter: (b = 0) -(21 rows) - -SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; - a | b | c ------+---+------ - 100 | 0 | 0100 - 125 | 0 | 0125 - 200 | 0 | 0200 - 225 | 0 | 0225 - 250 | 0 | 0250 - 275 | 0 | 0275 - 350 | 0 | 0350 - 375 | 0 | 0375 -(8 rows) - --- left join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a, t2.b - -> Append - -> Hash Right Join - Hash Cond: (t2_1.b = t1_1.a) - -> Seq Scan on prt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on prt1_adv_p1 t1_1 - Filter: (b = 0) - -> Hash Right Join - Hash Cond: (t2_2.b = t1_2.a) - -> Seq Scan on prt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on prt1_adv_p2 t1_2 - Filter: (b = 0) - -> Hash Right Join - Hash Cond: (t2_3.b = t1_3.a) - -> Seq Scan on prt2_adv_p3 t2_3 - -> Hash - -> Seq Scan on prt1_adv_p3 t1_3 - Filter: (b = 0) -(21 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 100 | 0100 | 100 | 0100 - 125 | 0125 | 125 | 0125 - 150 | 0150 | | - 175 | 0175 | | - 200 | 0200 | 200 | 0200 - 225 | 0225 | 225 | 0225 - 250 | 0250 | 250 | 0250 - 275 | 0275 | 275 | 0275 - 300 | 0300 | | - 325 | 0325 | | - 350 | 0350 | 350 | 0350 - 375 | 0375 | 375 | 0375 -(12 rows) - --- anti join -EXPLAIN (COSTS OFF) -SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Right Anti Join - Hash Cond: (t2_1.b = t1_1.a) - -> Seq Scan on prt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on prt1_adv_p1 t1_1 - Filter: (b = 0) - -> Hash Right Anti Join - Hash Cond: (t2_2.b = t1_2.a) - -> Seq Scan on prt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on prt1_adv_p2 t1_2 - Filter: (b = 0) - -> Hash Right Anti Join - Hash Cond: (t2_3.b = t1_3.a) - -> Seq Scan on prt2_adv_p3 t2_3 - -> Hash - -> Seq Scan on prt1_adv_p3 t1_3 - Filter: (b = 0) -(21 rows) - -SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; - a | b | c ------+---+------ - 150 | 0 | 0150 - 175 | 0 | 0175 - 300 | 0 | 0300 - 325 | 0 | 0325 -(4 rows) - --- full join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 175 phv, * FROM prt1_adv WHERE prt1_adv.b = 0) t1 FULL JOIN (SELECT 425 phv, * FROM prt2_adv WHERE prt2_adv.a = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.a OR t2.phv = t2.b ORDER BY t1.a, t2.b; - QUERY PLAN --------------------------------------------------------------------------- - Sort - Sort Key: prt1_adv.a, prt2_adv.b - -> Append - -> Hash Full Join - Hash Cond: (prt1_adv_1.a = prt2_adv_1.b) - Filter: (((175) = prt1_adv_1.a) OR ((425) = prt2_adv_1.b)) - -> Seq Scan on prt1_adv_p1 prt1_adv_1 - Filter: (b = 0) - -> Hash - -> Seq Scan on prt2_adv_p1 prt2_adv_1 - Filter: (a = 0) - -> Hash Full Join - Hash Cond: (prt1_adv_2.a = prt2_adv_2.b) - Filter: (((175) = prt1_adv_2.a) OR ((425) = prt2_adv_2.b)) - -> Seq Scan on prt1_adv_p2 prt1_adv_2 - Filter: (b = 0) - -> Hash - -> Seq Scan on prt2_adv_p2 prt2_adv_2 - Filter: (a = 0) - -> Hash Full Join - Hash Cond: (prt2_adv_3.b = prt1_adv_3.a) - Filter: (((175) = prt1_adv_3.a) OR ((425) = prt2_adv_3.b)) - -> Seq Scan on prt2_adv_p3 prt2_adv_3 - Filter: (a = 0) - -> Hash - -> Seq Scan on prt1_adv_p3 prt1_adv_3 - Filter: (b = 0) -(27 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 175 phv, * FROM prt1_adv WHERE prt1_adv.b = 0) t1 FULL JOIN (SELECT 425 phv, * FROM prt2_adv WHERE prt2_adv.a = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.a OR t2.phv = t2.b ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 175 | 0175 | | - | | 425 | 0425 -(2 rows) - --- Test cases where one side has an extra partition -CREATE TABLE prt2_adv_extra PARTITION OF prt2_adv FOR VALUES FROM (500) TO (MAXVALUE); -INSERT INTO prt2_adv SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(500, 599) i; -ANALYZE prt2_adv; --- inner join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Join - Hash Cond: (t2_1.b = t1_1.a) - -> Seq Scan on prt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on prt1_adv_p1 t1_1 - Filter: (b = 0) - -> Hash Join - Hash Cond: (t2_2.b = t1_2.a) - -> Seq Scan on prt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on prt1_adv_p2 t1_2 - Filter: (b = 0) - -> Hash Join - Hash Cond: (t2_3.b = t1_3.a) - -> Seq Scan on prt2_adv_p3 t2_3 - -> Hash - -> Seq Scan on prt1_adv_p3 t1_3 - Filter: (b = 0) -(21 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 100 | 0100 | 100 | 0100 - 125 | 0125 | 125 | 0125 - 200 | 0200 | 200 | 0200 - 225 | 0225 | 225 | 0225 - 250 | 0250 | 250 | 0250 - 275 | 0275 | 275 | 0275 - 350 | 0350 | 350 | 0350 - 375 | 0375 | 375 | 0375 -(8 rows) - --- semi join -EXPLAIN (COSTS OFF) -SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Right Semi Join - Hash Cond: (t2_1.b = t1_1.a) - -> Seq Scan on prt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on prt1_adv_p1 t1_1 - Filter: (b = 0) - -> Hash Right Semi Join - Hash Cond: (t2_2.b = t1_2.a) - -> Seq Scan on prt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on prt1_adv_p2 t1_2 - Filter: (b = 0) - -> Hash Right Semi Join - Hash Cond: (t2_3.b = t1_3.a) - -> Seq Scan on prt2_adv_p3 t2_3 - -> Hash - -> Seq Scan on prt1_adv_p3 t1_3 - Filter: (b = 0) -(21 rows) - -SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; - a | b | c ------+---+------ - 100 | 0 | 0100 - 125 | 0 | 0125 - 200 | 0 | 0200 - 225 | 0 | 0225 - 250 | 0 | 0250 - 275 | 0 | 0275 - 350 | 0 | 0350 - 375 | 0 | 0375 -(8 rows) - --- left join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a, t2.b - -> Append - -> Hash Right Join - Hash Cond: (t2_1.b = t1_1.a) - -> Seq Scan on prt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on prt1_adv_p1 t1_1 - Filter: (b = 0) - -> Hash Right Join - Hash Cond: (t2_2.b = t1_2.a) - -> Seq Scan on prt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on prt1_adv_p2 t1_2 - Filter: (b = 0) - -> Hash Right Join - Hash Cond: (t2_3.b = t1_3.a) - -> Seq Scan on prt2_adv_p3 t2_3 - -> Hash - -> Seq Scan on prt1_adv_p3 t1_3 - Filter: (b = 0) -(21 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 100 | 0100 | 100 | 0100 - 125 | 0125 | 125 | 0125 - 150 | 0150 | | - 175 | 0175 | | - 200 | 0200 | 200 | 0200 - 225 | 0225 | 225 | 0225 - 250 | 0250 | 250 | 0250 - 275 | 0275 | 275 | 0275 - 300 | 0300 | | - 325 | 0325 | | - 350 | 0350 | 350 | 0350 - 375 | 0375 | 375 | 0375 -(12 rows) - --- left join; currently we can't do partitioned join if there are no matched --- partitions on the nullable side -EXPLAIN (COSTS OFF) -SELECT t1.b, t1.c, t2.a, t2.c FROM prt2_adv t1 LEFT JOIN prt1_adv t2 ON (t1.b = t2.a) WHERE t1.a = 0 ORDER BY t1.b, t2.a; - QUERY PLAN ---------------------------------------------------------- - Sort - Sort Key: t1.b, t2.a - -> Hash Right Join - Hash Cond: (t2.a = t1.b) - -> Append - -> Seq Scan on prt1_adv_p1 t2_1 - -> Seq Scan on prt1_adv_p2 t2_2 - -> Seq Scan on prt1_adv_p3 t2_3 - -> Hash - -> Append - -> Seq Scan on prt2_adv_p1 t1_1 - Filter: (a = 0) - -> Seq Scan on prt2_adv_p2 t1_2 - Filter: (a = 0) - -> Seq Scan on prt2_adv_p3 t1_3 - Filter: (a = 0) - -> Seq Scan on prt2_adv_extra t1_4 - Filter: (a = 0) -(18 rows) - --- anti join -EXPLAIN (COSTS OFF) -SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Right Anti Join - Hash Cond: (t2_1.b = t1_1.a) - -> Seq Scan on prt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on prt1_adv_p1 t1_1 - Filter: (b = 0) - -> Hash Right Anti Join - Hash Cond: (t2_2.b = t1_2.a) - -> Seq Scan on prt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on prt1_adv_p2 t1_2 - Filter: (b = 0) - -> Hash Right Anti Join - Hash Cond: (t2_3.b = t1_3.a) - -> Seq Scan on prt2_adv_p3 t2_3 - -> Hash - -> Seq Scan on prt1_adv_p3 t1_3 - Filter: (b = 0) -(21 rows) - -SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; - a | b | c ------+---+------ - 150 | 0 | 0150 - 175 | 0 | 0175 - 300 | 0 | 0300 - 325 | 0 | 0325 -(4 rows) - --- anti join; currently we can't do partitioned join if there are no matched --- partitions on the nullable side -EXPLAIN (COSTS OFF) -SELECT t1.* FROM prt2_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt1_adv t2 WHERE t1.b = t2.a) AND t1.a = 0 ORDER BY t1.b; - QUERY PLAN ---------------------------------------------------------- - Sort - Sort Key: t1.b - -> Hash Right Anti Join - Hash Cond: (t2.a = t1.b) - -> Append - -> Seq Scan on prt1_adv_p1 t2_1 - -> Seq Scan on prt1_adv_p2 t2_2 - -> Seq Scan on prt1_adv_p3 t2_3 - -> Hash - -> Append - -> Seq Scan on prt2_adv_p1 t1_1 - Filter: (a = 0) - -> Seq Scan on prt2_adv_p2 t1_2 - Filter: (a = 0) - -> Seq Scan on prt2_adv_p3 t1_3 - Filter: (a = 0) - -> Seq Scan on prt2_adv_extra t1_4 - Filter: (a = 0) -(18 rows) - --- full join; currently we can't do partitioned join if there are no matched --- partitions on the nullable side -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 175 phv, * FROM prt1_adv WHERE prt1_adv.b = 0) t1 FULL JOIN (SELECT 425 phv, * FROM prt2_adv WHERE prt2_adv.a = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.a OR t2.phv = t2.b ORDER BY t1.a, t2.b; - QUERY PLAN ----------------------------------------------------------------- - Sort - Sort Key: prt1_adv.a, prt2_adv.b - -> Hash Full Join - Hash Cond: (prt2_adv.b = prt1_adv.a) - Filter: (((175) = prt1_adv.a) OR ((425) = prt2_adv.b)) - -> Append - -> Seq Scan on prt2_adv_p1 prt2_adv_1 - Filter: (a = 0) - -> Seq Scan on prt2_adv_p2 prt2_adv_2 - Filter: (a = 0) - -> Seq Scan on prt2_adv_p3 prt2_adv_3 - Filter: (a = 0) - -> Seq Scan on prt2_adv_extra prt2_adv_4 - Filter: (a = 0) - -> Hash - -> Append - -> Seq Scan on prt1_adv_p1 prt1_adv_1 - Filter: (b = 0) - -> Seq Scan on prt1_adv_p2 prt1_adv_2 - Filter: (b = 0) - -> Seq Scan on prt1_adv_p3 prt1_adv_3 - Filter: (b = 0) -(22 rows) - --- 3-way join where not every pair of relations can do partitioned join -EXPLAIN (COSTS OFF) -SELECT t1.b, t1.c, t2.a, t2.c, t3.a, t3.c FROM prt2_adv t1 LEFT JOIN prt1_adv t2 ON (t1.b = t2.a) INNER JOIN prt1_adv t3 ON (t1.b = t3.a) WHERE t1.a = 0 ORDER BY t1.b, t2.a, t3.a; - QUERY PLAN --------------------------------------------------------------------------------- - Sort - Sort Key: t1.b, t2.a - -> Append - -> Nested Loop Left Join - -> Nested Loop - -> Seq Scan on prt2_adv_p1 t1_1 - Filter: (a = 0) - -> Index Scan using prt1_adv_p1_a_idx on prt1_adv_p1 t3_1 - Index Cond: (a = t1_1.b) - -> Index Scan using prt1_adv_p1_a_idx on prt1_adv_p1 t2_1 - Index Cond: (a = t1_1.b) - -> Hash Right Join - Hash Cond: (t2_2.a = t1_2.b) - -> Seq Scan on prt1_adv_p2 t2_2 - -> Hash - -> Hash Join - Hash Cond: (t3_2.a = t1_2.b) - -> Seq Scan on prt1_adv_p2 t3_2 - -> Hash - -> Seq Scan on prt2_adv_p2 t1_2 - Filter: (a = 0) - -> Hash Right Join - Hash Cond: (t2_3.a = t1_3.b) - -> Seq Scan on prt1_adv_p3 t2_3 - -> Hash - -> Hash Join - Hash Cond: (t3_3.a = t1_3.b) - -> Seq Scan on prt1_adv_p3 t3_3 - -> Hash - -> Seq Scan on prt2_adv_p3 t1_3 - Filter: (a = 0) -(31 rows) - -SELECT t1.b, t1.c, t2.a, t2.c, t3.a, t3.c FROM prt2_adv t1 LEFT JOIN prt1_adv t2 ON (t1.b = t2.a) INNER JOIN prt1_adv t3 ON (t1.b = t3.a) WHERE t1.a = 0 ORDER BY t1.b, t2.a, t3.a; - b | c | a | c | a | c ------+------+-----+------+-----+------ - 100 | 0100 | 100 | 0100 | 100 | 0100 - 125 | 0125 | 125 | 0125 | 125 | 0125 - 200 | 0200 | 200 | 0200 | 200 | 0200 - 225 | 0225 | 225 | 0225 | 225 | 0225 - 250 | 0250 | 250 | 0250 | 250 | 0250 - 275 | 0275 | 275 | 0275 | 275 | 0275 - 350 | 0350 | 350 | 0350 | 350 | 0350 - 375 | 0375 | 375 | 0375 | 375 | 0375 -(8 rows) - -DROP TABLE prt2_adv_extra; --- Test cases where a partition on one side matches multiple partitions on --- the other side; we currently can't do partitioned join in such cases -ALTER TABLE prt2_adv DETACH PARTITION prt2_adv_p3; --- Split prt2_adv_p3 into two partitions so that prt1_adv_p3 matches both -CREATE TABLE prt2_adv_p3_1 PARTITION OF prt2_adv FOR VALUES FROM (350) TO (375); -CREATE TABLE prt2_adv_p3_2 PARTITION OF prt2_adv FOR VALUES FROM (375) TO (500); -INSERT INTO prt2_adv SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(350, 499) i; -ANALYZE prt2_adv; --- inner join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a - -> Hash Join - Hash Cond: (t2.b = t1.a) - -> Append - -> Seq Scan on prt2_adv_p1 t2_1 - -> Seq Scan on prt2_adv_p2 t2_2 - -> Seq Scan on prt2_adv_p3_1 t2_3 - -> Seq Scan on prt2_adv_p3_2 t2_4 - -> Hash - -> Append - -> Seq Scan on prt1_adv_p1 t1_1 - Filter: (b = 0) - -> Seq Scan on prt1_adv_p2 t1_2 - Filter: (b = 0) - -> Seq Scan on prt1_adv_p3 t1_3 - Filter: (b = 0) -(17 rows) - --- semi join -EXPLAIN (COSTS OFF) -SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a - -> Hash Right Semi Join - Hash Cond: (t2.b = t1.a) - -> Append - -> Seq Scan on prt2_adv_p1 t2_1 - -> Seq Scan on prt2_adv_p2 t2_2 - -> Seq Scan on prt2_adv_p3_1 t2_3 - -> Seq Scan on prt2_adv_p3_2 t2_4 - -> Hash - -> Append - -> Seq Scan on prt1_adv_p1 t1_1 - Filter: (b = 0) - -> Seq Scan on prt1_adv_p2 t1_2 - Filter: (b = 0) - -> Seq Scan on prt1_adv_p3 t1_3 - Filter: (b = 0) -(17 rows) - --- left join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a, t2.b - -> Hash Right Join - Hash Cond: (t2.b = t1.a) - -> Append - -> Seq Scan on prt2_adv_p1 t2_1 - -> Seq Scan on prt2_adv_p2 t2_2 - -> Seq Scan on prt2_adv_p3_1 t2_3 - -> Seq Scan on prt2_adv_p3_2 t2_4 - -> Hash - -> Append - -> Seq Scan on prt1_adv_p1 t1_1 - Filter: (b = 0) - -> Seq Scan on prt1_adv_p2 t1_2 - Filter: (b = 0) - -> Seq Scan on prt1_adv_p3 t1_3 - Filter: (b = 0) -(17 rows) - --- anti join -EXPLAIN (COSTS OFF) -SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a - -> Hash Right Anti Join - Hash Cond: (t2.b = t1.a) - -> Append - -> Seq Scan on prt2_adv_p1 t2_1 - -> Seq Scan on prt2_adv_p2 t2_2 - -> Seq Scan on prt2_adv_p3_1 t2_3 - -> Seq Scan on prt2_adv_p3_2 t2_4 - -> Hash - -> Append - -> Seq Scan on prt1_adv_p1 t1_1 - Filter: (b = 0) - -> Seq Scan on prt1_adv_p2 t1_2 - Filter: (b = 0) - -> Seq Scan on prt1_adv_p3 t1_3 - Filter: (b = 0) -(17 rows) - --- full join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 175 phv, * FROM prt1_adv WHERE prt1_adv.b = 0) t1 FULL JOIN (SELECT 425 phv, * FROM prt2_adv WHERE prt2_adv.a = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.a OR t2.phv = t2.b ORDER BY t1.a, t2.b; - QUERY PLAN ----------------------------------------------------------------- - Sort - Sort Key: prt1_adv.a, prt2_adv.b - -> Hash Full Join - Hash Cond: (prt2_adv.b = prt1_adv.a) - Filter: (((175) = prt1_adv.a) OR ((425) = prt2_adv.b)) - -> Append - -> Seq Scan on prt2_adv_p1 prt2_adv_1 - Filter: (a = 0) - -> Seq Scan on prt2_adv_p2 prt2_adv_2 - Filter: (a = 0) - -> Seq Scan on prt2_adv_p3_1 prt2_adv_3 - Filter: (a = 0) - -> Seq Scan on prt2_adv_p3_2 prt2_adv_4 - Filter: (a = 0) - -> Hash - -> Append - -> Seq Scan on prt1_adv_p1 prt1_adv_1 - Filter: (b = 0) - -> Seq Scan on prt1_adv_p2 prt1_adv_2 - Filter: (b = 0) - -> Seq Scan on prt1_adv_p3 prt1_adv_3 - Filter: (b = 0) -(22 rows) - -DROP TABLE prt2_adv_p3_1; -DROP TABLE prt2_adv_p3_2; -ANALYZE prt2_adv; --- Test default partitions -ALTER TABLE prt1_adv DETACH PARTITION prt1_adv_p1; --- Change prt1_adv_p1 to the default partition -ALTER TABLE prt1_adv ATTACH PARTITION prt1_adv_p1 DEFAULT; -ALTER TABLE prt1_adv DETACH PARTITION prt1_adv_p3; -ANALYZE prt1_adv; --- We can do partitioned join even if only one of relations has the default --- partition -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Join - Hash Cond: (t2_1.b = t1_2.a) - -> Seq Scan on prt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on prt1_adv_p1 t1_2 - Filter: (b = 0) - -> Hash Join - Hash Cond: (t2_2.b = t1_1.a) - -> Seq Scan on prt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on prt1_adv_p2 t1_1 - Filter: (b = 0) -(15 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 100 | 0100 | 100 | 0100 - 125 | 0125 | 125 | 0125 - 200 | 0200 | 200 | 0200 - 225 | 0225 | 225 | 0225 - 250 | 0250 | 250 | 0250 - 275 | 0275 | 275 | 0275 -(6 rows) - --- Restore prt1_adv_p3 -ALTER TABLE prt1_adv ATTACH PARTITION prt1_adv_p3 FOR VALUES FROM (300) TO (400); -ANALYZE prt1_adv; --- Restore prt2_adv_p3 -ALTER TABLE prt2_adv ATTACH PARTITION prt2_adv_p3 FOR VALUES FROM (350) TO (500); -ANALYZE prt2_adv; --- Partitioned join can't be applied because the default partition of prt1_adv --- matches prt2_adv_p1 and prt2_adv_p3 -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a - -> Hash Join - Hash Cond: (t2.b = t1.a) - -> Append - -> Seq Scan on prt2_adv_p1 t2_1 - -> Seq Scan on prt2_adv_p2 t2_2 - -> Seq Scan on prt2_adv_p3 t2_3 - -> Hash - -> Append - -> Seq Scan on prt1_adv_p2 t1_1 - Filter: (b = 0) - -> Seq Scan on prt1_adv_p3 t1_2 - Filter: (b = 0) - -> Seq Scan on prt1_adv_p1 t1_3 - Filter: (b = 0) -(16 rows) - -ALTER TABLE prt2_adv DETACH PARTITION prt2_adv_p3; --- Change prt2_adv_p3 to the default partition -ALTER TABLE prt2_adv ATTACH PARTITION prt2_adv_p3 DEFAULT; -ANALYZE prt2_adv; --- Partitioned join can't be applied because the default partition of prt1_adv --- matches prt2_adv_p1 and prt2_adv_p3 -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a - -> Hash Join - Hash Cond: (t2.b = t1.a) - -> Append - -> Seq Scan on prt2_adv_p1 t2_1 - -> Seq Scan on prt2_adv_p2 t2_2 - -> Seq Scan on prt2_adv_p3 t2_3 - -> Hash - -> Append - -> Seq Scan on prt1_adv_p2 t1_1 - Filter: (b = 0) - -> Seq Scan on prt1_adv_p3 t1_2 - Filter: (b = 0) - -> Seq Scan on prt1_adv_p1 t1_3 - Filter: (b = 0) -(16 rows) - -DROP TABLE prt1_adv_p3; -ANALYZE prt1_adv; -DROP TABLE prt2_adv_p3; -ANALYZE prt2_adv; -CREATE TABLE prt3_adv (a int, b int, c varchar) PARTITION BY RANGE (a); -CREATE TABLE prt3_adv_p1 PARTITION OF prt3_adv FOR VALUES FROM (200) TO (300); -CREATE TABLE prt3_adv_p2 PARTITION OF prt3_adv FOR VALUES FROM (300) TO (400); -CREATE INDEX prt3_adv_a_idx ON prt3_adv (a); -INSERT INTO prt3_adv SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(200, 399) i; -ANALYZE prt3_adv; --- 3-way join to test the default partition of a join relation -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c, t3.a, t3.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) LEFT JOIN prt3_adv t3 ON (t1.a = t3.a) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a; - QUERY PLAN ------------------------------------------------------------------- - Sort - Sort Key: t1.a, t2.b, t3.a - -> Append - -> Hash Right Join - Hash Cond: (t3_1.a = t1_1.a) - -> Seq Scan on prt3_adv_p1 t3_1 - -> Hash - -> Hash Right Join - Hash Cond: (t2_2.b = t1_1.a) - -> Seq Scan on prt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on prt1_adv_p2 t1_1 - Filter: (b = 0) - -> Hash Right Join - Hash Cond: (t3_2.a = t1_2.a) - -> Seq Scan on prt3_adv_p2 t3_2 - -> Hash - -> Hash Right Join - Hash Cond: (t2_1.b = t1_2.a) - -> Seq Scan on prt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on prt1_adv_p1 t1_2 - Filter: (b = 0) -(23 rows) - -SELECT t1.a, t1.c, t2.b, t2.c, t3.a, t3.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) LEFT JOIN prt3_adv t3 ON (t1.a = t3.a) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a; - a | c | b | c | a | c ------+------+-----+------+-----+------ - 100 | 0100 | 100 | 0100 | | - 125 | 0125 | 125 | 0125 | | - 150 | 0150 | | | | - 175 | 0175 | | | | - 200 | 0200 | 200 | 0200 | 200 | 0200 - 225 | 0225 | 225 | 0225 | 225 | 0225 - 250 | 0250 | 250 | 0250 | 250 | 0250 - 275 | 0275 | 275 | 0275 | 275 | 0275 -(8 rows) - -DROP TABLE prt1_adv; -DROP TABLE prt2_adv; -DROP TABLE prt3_adv; --- Test interaction of partitioned join with partition pruning -CREATE TABLE prt1_adv (a int, b int, c varchar) PARTITION BY RANGE (a); -CREATE TABLE prt1_adv_p1 PARTITION OF prt1_adv FOR VALUES FROM (100) TO (200); -CREATE TABLE prt1_adv_p2 PARTITION OF prt1_adv FOR VALUES FROM (200) TO (300); -CREATE TABLE prt1_adv_p3 PARTITION OF prt1_adv FOR VALUES FROM (300) TO (400); -CREATE INDEX prt1_adv_a_idx ON prt1_adv (a); -INSERT INTO prt1_adv SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(100, 399) i; -ANALYZE prt1_adv; -CREATE TABLE prt2_adv (a int, b int, c varchar) PARTITION BY RANGE (b); -CREATE TABLE prt2_adv_p1 PARTITION OF prt2_adv FOR VALUES FROM (100) TO (200); -CREATE TABLE prt2_adv_p2 PARTITION OF prt2_adv FOR VALUES FROM (200) TO (400); -CREATE INDEX prt2_adv_b_idx ON prt2_adv (b); -INSERT INTO prt2_adv SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(100, 399) i; -ANALYZE prt2_adv; -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------------ - Sort - Sort Key: t1.a - -> Append - -> Hash Join - Hash Cond: (t2_1.b = t1_1.a) - -> Seq Scan on prt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on prt1_adv_p1 t1_1 - Filter: ((a < 300) AND (b = 0)) - -> Hash Join - Hash Cond: (t2_2.b = t1_2.a) - -> Seq Scan on prt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on prt1_adv_p2 t1_2 - Filter: ((a < 300) AND (b = 0)) -(15 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 100 | 0100 | 100 | 0100 - 125 | 0125 | 125 | 0125 - 150 | 0150 | 150 | 0150 - 175 | 0175 | 175 | 0175 - 200 | 0200 | 200 | 0200 - 225 | 0225 | 225 | 0225 - 250 | 0250 | 250 | 0250 - 275 | 0275 | 275 | 0275 -(8 rows) - -DROP TABLE prt1_adv_p3; -CREATE TABLE prt1_adv_default PARTITION OF prt1_adv DEFAULT; -ANALYZE prt1_adv; -CREATE TABLE prt2_adv_default PARTITION OF prt2_adv DEFAULT; -ANALYZE prt2_adv; -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a >= 100 AND t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN --------------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Join - Hash Cond: (t2_1.b = t1_1.a) - -> Seq Scan on prt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on prt1_adv_p1 t1_1 - Filter: ((a >= 100) AND (a < 300) AND (b = 0)) - -> Hash Join - Hash Cond: (t2_2.b = t1_2.a) - -> Seq Scan on prt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on prt1_adv_p2 t1_2 - Filter: ((a >= 100) AND (a < 300) AND (b = 0)) -(15 rows) - -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a >= 100 AND t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 100 | 0100 | 100 | 0100 - 125 | 0125 | 125 | 0125 - 150 | 0150 | 150 | 0150 - 175 | 0175 | 175 | 0175 - 200 | 0200 | 200 | 0200 - 225 | 0225 | 225 | 0225 - 250 | 0250 | 250 | 0250 - 275 | 0275 | 275 | 0275 -(8 rows) - -DROP TABLE prt1_adv; -DROP TABLE prt2_adv; --- Tests for list-partitioned tables -CREATE TABLE plt1_adv (a int, b int, c text) PARTITION BY LIST (c); -CREATE TABLE plt1_adv_p1 PARTITION OF plt1_adv FOR VALUES IN ('0001', '0003'); -CREATE TABLE plt1_adv_p2 PARTITION OF plt1_adv FOR VALUES IN ('0004', '0006'); -CREATE TABLE plt1_adv_p3 PARTITION OF plt1_adv FOR VALUES IN ('0008', '0009'); -INSERT INTO plt1_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (1, 3, 4, 6, 8, 9); -ANALYZE plt1_adv; -CREATE TABLE plt2_adv (a int, b int, c text) PARTITION BY LIST (c); -CREATE TABLE plt2_adv_p1 PARTITION OF plt2_adv FOR VALUES IN ('0002', '0003'); -CREATE TABLE plt2_adv_p2 PARTITION OF plt2_adv FOR VALUES IN ('0004', '0006'); -CREATE TABLE plt2_adv_p3 PARTITION OF plt2_adv FOR VALUES IN ('0007', '0009'); -INSERT INTO plt2_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); -ANALYZE plt2_adv; --- inner join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Join - Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on plt1_adv_p1 t1_1 - Filter: (b < 10) - -> Hash Join - Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Hash Join - Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) - -> Seq Scan on plt2_adv_p3 t2_3 - -> Hash - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) -(21 rows) - -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - a | c | a | c ----+------+---+------ - 3 | 0003 | 3 | 0003 - 4 | 0004 | 4 | 0004 - 6 | 0006 | 6 | 0006 - 9 | 0009 | 9 | 0009 -(4 rows) - --- semi join -EXPLAIN (COSTS OFF) -SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Right Semi Join - Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on plt1_adv_p1 t1_1 - Filter: (b < 10) - -> Hash Right Semi Join - Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Hash Right Semi Join - Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) - -> Seq Scan on plt2_adv_p3 t2_3 - -> Hash - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) -(21 rows) - -SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; - a | b | c ----+---+------ - 3 | 3 | 0003 - 4 | 4 | 0004 - 6 | 6 | 0006 - 9 | 9 | 0009 -(4 rows) - --- left join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Right Join - Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on plt1_adv_p1 t1_1 - Filter: (b < 10) - -> Hash Right Join - Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Hash Right Join - Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) - -> Seq Scan on plt2_adv_p3 t2_3 - -> Hash - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) -(21 rows) - -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - a | c | a | c ----+------+---+------ - 1 | 0001 | | - 3 | 0003 | 3 | 0003 - 4 | 0004 | 4 | 0004 - 6 | 0006 | 6 | 0006 - 8 | 0008 | | - 9 | 0009 | 9 | 0009 -(6 rows) - --- anti join -EXPLAIN (COSTS OFF) -SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Right Anti Join - Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on plt1_adv_p1 t1_1 - Filter: (b < 10) - -> Hash Right Anti Join - Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Hash Right Anti Join - Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) - -> Seq Scan on plt2_adv_p3 t2_3 - -> Hash - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) -(21 rows) - -SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; - a | b | c ----+---+------ - 1 | 1 | 0001 - 8 | 8 | 0008 -(2 rows) - --- full join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; - QUERY PLAN ------------------------------------------------------------------------------------ - Sort - Sort Key: t1.a, t2.a - -> Append - -> Hash Full Join - Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c)) - Filter: ((COALESCE(t1_1.b, 0) < 10) AND (COALESCE(t2_1.b, 0) < 10)) - -> Seq Scan on plt1_adv_p1 t1_1 - -> Hash - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash Full Join - Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c)) - Filter: ((COALESCE(t1_2.b, 0) < 10) AND (COALESCE(t2_2.b, 0) < 10)) - -> Seq Scan on plt1_adv_p2 t1_2 - -> Hash - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash Full Join - Hash Cond: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c)) - Filter: ((COALESCE(t1_3.b, 0) < 10) AND (COALESCE(t2_3.b, 0) < 10)) - -> Seq Scan on plt1_adv_p3 t1_3 - -> Hash - -> Seq Scan on plt2_adv_p3 t2_3 -(21 rows) - -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; - a | c | a | c ----+------+---+------ - 1 | 0001 | | - 3 | 0003 | 3 | 0003 - 4 | 0004 | 4 | 0004 - 6 | 0006 | 6 | 0006 - 8 | 0008 | | - 9 | 0009 | 9 | 0009 - | | 2 | 0002 - | | 7 | 0007 -(8 rows) - --- Test cases where one side has an extra partition -CREATE TABLE plt2_adv_extra PARTITION OF plt2_adv FOR VALUES IN ('0000'); -INSERT INTO plt2_adv_extra VALUES (0, 0, '0000'); -ANALYZE plt2_adv; --- inner join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Join - Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on plt1_adv_p1 t1_1 - Filter: (b < 10) - -> Hash Join - Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Hash Join - Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) - -> Seq Scan on plt2_adv_p3 t2_3 - -> Hash - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) -(21 rows) - -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - a | c | a | c ----+------+---+------ - 3 | 0003 | 3 | 0003 - 4 | 0004 | 4 | 0004 - 6 | 0006 | 6 | 0006 - 9 | 0009 | 9 | 0009 -(4 rows) - --- semi join -EXPLAIN (COSTS OFF) -SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Right Semi Join - Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on plt1_adv_p1 t1_1 - Filter: (b < 10) - -> Hash Right Semi Join - Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Hash Right Semi Join - Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) - -> Seq Scan on plt2_adv_p3 t2_3 - -> Hash - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) -(21 rows) - -SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; - a | b | c ----+---+------ - 3 | 3 | 0003 - 4 | 4 | 0004 - 6 | 6 | 0006 - 9 | 9 | 0009 -(4 rows) - --- left join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Right Join - Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on plt1_adv_p1 t1_1 - Filter: (b < 10) - -> Hash Right Join - Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Hash Right Join - Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) - -> Seq Scan on plt2_adv_p3 t2_3 - -> Hash - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) -(21 rows) - -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - a | c | a | c ----+------+---+------ - 1 | 0001 | | - 3 | 0003 | 3 | 0003 - 4 | 0004 | 4 | 0004 - 6 | 0006 | 6 | 0006 - 8 | 0008 | | - 9 | 0009 | 9 | 0009 -(6 rows) - --- left join; currently we can't do partitioned join if there are no matched --- partitions on the nullable side -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt2_adv t1 LEFT JOIN plt1_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - QUERY PLAN ---------------------------------------------------------- - Sort - Sort Key: t1.a - -> Hash Right Join - Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c)) - -> Append - -> Seq Scan on plt1_adv_p1 t2_1 - -> Seq Scan on plt1_adv_p2 t2_2 - -> Seq Scan on plt1_adv_p3 t2_3 - -> Hash - -> Append - -> Seq Scan on plt2_adv_extra t1_1 - Filter: (b < 10) - -> Seq Scan on plt2_adv_p1 t1_2 - Filter: (b < 10) - -> Seq Scan on plt2_adv_p2 t1_3 - Filter: (b < 10) - -> Seq Scan on plt2_adv_p3 t1_4 - Filter: (b < 10) -(18 rows) - --- anti join -EXPLAIN (COSTS OFF) -SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Right Anti Join - Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on plt1_adv_p1 t1_1 - Filter: (b < 10) - -> Hash Right Anti Join - Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Hash Right Anti Join - Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) - -> Seq Scan on plt2_adv_p3 t2_3 - -> Hash - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) -(21 rows) - -SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; - a | b | c ----+---+------ - 1 | 1 | 0001 - 8 | 8 | 0008 -(2 rows) - --- anti join; currently we can't do partitioned join if there are no matched --- partitions on the nullable side -EXPLAIN (COSTS OFF) -SELECT t1.* FROM plt2_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt1_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; - QUERY PLAN ---------------------------------------------------------- - Sort - Sort Key: t1.a - -> Hash Right Anti Join - Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c)) - -> Append - -> Seq Scan on plt1_adv_p1 t2_1 - -> Seq Scan on plt1_adv_p2 t2_2 - -> Seq Scan on plt1_adv_p3 t2_3 - -> Hash - -> Append - -> Seq Scan on plt2_adv_extra t1_1 - Filter: (b < 10) - -> Seq Scan on plt2_adv_p1 t1_2 - Filter: (b < 10) - -> Seq Scan on plt2_adv_p2 t1_3 - Filter: (b < 10) - -> Seq Scan on plt2_adv_p3 t1_4 - Filter: (b < 10) -(18 rows) - --- full join; currently we can't do partitioned join if there are no matched --- partitions on the nullable side -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; - QUERY PLAN -------------------------------------------------------------------------- - Sort - Sort Key: t1.a, t2.a - -> Hash Full Join - Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c)) - Filter: ((COALESCE(t1.b, 0) < 10) AND (COALESCE(t2.b, 0) < 10)) - -> Append - -> Seq Scan on plt2_adv_extra t2_1 - -> Seq Scan on plt2_adv_p1 t2_2 - -> Seq Scan on plt2_adv_p2 t2_3 - -> Seq Scan on plt2_adv_p3 t2_4 - -> Hash - -> Append - -> Seq Scan on plt1_adv_p1 t1_1 - -> Seq Scan on plt1_adv_p2 t1_2 - -> Seq Scan on plt1_adv_p3 t1_3 -(15 rows) - -DROP TABLE plt2_adv_extra; --- Test cases where a partition on one side matches multiple partitions on --- the other side; we currently can't do partitioned join in such cases -ALTER TABLE plt2_adv DETACH PARTITION plt2_adv_p2; --- Split plt2_adv_p2 into two partitions so that plt1_adv_p2 matches both -CREATE TABLE plt2_adv_p2_1 PARTITION OF plt2_adv FOR VALUES IN ('0004'); -CREATE TABLE plt2_adv_p2_2 PARTITION OF plt2_adv FOR VALUES IN ('0006'); -INSERT INTO plt2_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (4, 6); -ANALYZE plt2_adv; --- inner join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a - -> Hash Join - Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c)) - -> Append - -> Seq Scan on plt2_adv_p1 t2_1 - -> Seq Scan on plt2_adv_p2_1 t2_2 - -> Seq Scan on plt2_adv_p2_2 t2_3 - -> Seq Scan on plt2_adv_p3 t2_4 - -> Hash - -> Append - -> Seq Scan on plt1_adv_p1 t1_1 - Filter: (b < 10) - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) -(17 rows) - --- semi join -EXPLAIN (COSTS OFF) -SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a - -> Hash Right Semi Join - Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c)) - -> Append - -> Seq Scan on plt2_adv_p1 t2_1 - -> Seq Scan on plt2_adv_p2_1 t2_2 - -> Seq Scan on plt2_adv_p2_2 t2_3 - -> Seq Scan on plt2_adv_p3 t2_4 - -> Hash - -> Append - -> Seq Scan on plt1_adv_p1 t1_1 - Filter: (b < 10) - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) -(17 rows) - --- left join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a - -> Hash Right Join - Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c)) - -> Append - -> Seq Scan on plt2_adv_p1 t2_1 - -> Seq Scan on plt2_adv_p2_1 t2_2 - -> Seq Scan on plt2_adv_p2_2 t2_3 - -> Seq Scan on plt2_adv_p3 t2_4 - -> Hash - -> Append - -> Seq Scan on plt1_adv_p1 t1_1 - Filter: (b < 10) - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) -(17 rows) - --- anti join -EXPLAIN (COSTS OFF) -SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a - -> Hash Right Anti Join - Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c)) - -> Append - -> Seq Scan on plt2_adv_p1 t2_1 - -> Seq Scan on plt2_adv_p2_1 t2_2 - -> Seq Scan on plt2_adv_p2_2 t2_3 - -> Seq Scan on plt2_adv_p3 t2_4 - -> Hash - -> Append - -> Seq Scan on plt1_adv_p1 t1_1 - Filter: (b < 10) - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) -(17 rows) - --- full join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; - QUERY PLAN -------------------------------------------------------------------------- - Sort - Sort Key: t1.a, t2.a - -> Hash Full Join - Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c)) - Filter: ((COALESCE(t1.b, 0) < 10) AND (COALESCE(t2.b, 0) < 10)) - -> Append - -> Seq Scan on plt2_adv_p1 t2_1 - -> Seq Scan on plt2_adv_p2_1 t2_2 - -> Seq Scan on plt2_adv_p2_2 t2_3 - -> Seq Scan on plt2_adv_p3 t2_4 - -> Hash - -> Append - -> Seq Scan on plt1_adv_p1 t1_1 - -> Seq Scan on plt1_adv_p2 t1_2 - -> Seq Scan on plt1_adv_p3 t1_3 -(15 rows) - -DROP TABLE plt2_adv_p2_1; -DROP TABLE plt2_adv_p2_2; --- Restore plt2_adv_p2 -ALTER TABLE plt2_adv ATTACH PARTITION plt2_adv_p2 FOR VALUES IN ('0004', '0006'); --- Test NULL partitions -ALTER TABLE plt1_adv DETACH PARTITION plt1_adv_p1; --- Change plt1_adv_p1 to the NULL partition -CREATE TABLE plt1_adv_p1_null PARTITION OF plt1_adv FOR VALUES IN (NULL, '0001', '0003'); -INSERT INTO plt1_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (1, 3); -INSERT INTO plt1_adv VALUES (-1, -1, NULL); -ANALYZE plt1_adv; -ALTER TABLE plt2_adv DETACH PARTITION plt2_adv_p3; --- Change plt2_adv_p3 to the NULL partition -CREATE TABLE plt2_adv_p3_null PARTITION OF plt2_adv FOR VALUES IN (NULL, '0007', '0009'); -INSERT INTO plt2_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (7, 9); -INSERT INTO plt2_adv VALUES (-1, -1, NULL); -ANALYZE plt2_adv; --- inner join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Join - Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on plt1_adv_p1_null t1_1 - Filter: (b < 10) - -> Hash Join - Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Hash Join - Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) - -> Seq Scan on plt2_adv_p3_null t2_3 - -> Hash - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) -(21 rows) - -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - a | c | a | c ----+------+---+------ - 3 | 0003 | 3 | 0003 - 4 | 0004 | 4 | 0004 - 6 | 0006 | 6 | 0006 - 9 | 0009 | 9 | 0009 -(4 rows) - --- semi join -EXPLAIN (COSTS OFF) -SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Right Semi Join - Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on plt1_adv_p1_null t1_1 - Filter: (b < 10) - -> Hash Right Semi Join - Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Hash Right Semi Join - Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) - -> Seq Scan on plt2_adv_p3_null t2_3 - -> Hash - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) -(21 rows) - -SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; - a | b | c ----+---+------ - 3 | 3 | 0003 - 4 | 4 | 0004 - 6 | 6 | 0006 - 9 | 9 | 0009 -(4 rows) - --- left join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Right Join - Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on plt1_adv_p1_null t1_1 - Filter: (b < 10) - -> Hash Right Join - Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Hash Right Join - Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) - -> Seq Scan on plt2_adv_p3_null t2_3 - -> Hash - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) -(21 rows) - -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - a | c | a | c -----+------+---+------ - -1 | | | - 1 | 0001 | | - 3 | 0003 | 3 | 0003 - 4 | 0004 | 4 | 0004 - 6 | 0006 | 6 | 0006 - 8 | 0008 | | - 9 | 0009 | 9 | 0009 -(7 rows) - --- anti join -EXPLAIN (COSTS OFF) -SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Right Anti Join - Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on plt1_adv_p1_null t1_1 - Filter: (b < 10) - -> Hash Right Anti Join - Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Hash Right Anti Join - Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) - -> Seq Scan on plt2_adv_p3_null t2_3 - -> Hash - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) -(21 rows) - -SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; - a | b | c -----+----+------ - -1 | -1 | - 1 | 1 | 0001 - 8 | 8 | 0008 -(3 rows) - --- full join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; - QUERY PLAN ------------------------------------------------------------------------------------ - Sort - Sort Key: t1.a, t2.a - -> Append - -> Hash Full Join - Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c)) - Filter: ((COALESCE(t1_1.b, 0) < 10) AND (COALESCE(t2_1.b, 0) < 10)) - -> Seq Scan on plt1_adv_p1_null t1_1 - -> Hash - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash Full Join - Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c)) - Filter: ((COALESCE(t1_2.b, 0) < 10) AND (COALESCE(t2_2.b, 0) < 10)) - -> Seq Scan on plt1_adv_p2 t1_2 - -> Hash - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash Full Join - Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) - Filter: ((COALESCE(t1_3.b, 0) < 10) AND (COALESCE(t2_3.b, 0) < 10)) - -> Seq Scan on plt2_adv_p3_null t2_3 - -> Hash - -> Seq Scan on plt1_adv_p3 t1_3 -(21 rows) - -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; - a | c | a | c -----+------+----+------ - -1 | | | - 1 | 0001 | | - 3 | 0003 | 3 | 0003 - 4 | 0004 | 4 | 0004 - 6 | 0006 | 6 | 0006 - 8 | 0008 | | - 9 | 0009 | 9 | 0009 - | | -1 | - | | 2 | 0002 - | | 7 | 0007 -(10 rows) - -DROP TABLE plt1_adv_p1_null; --- Restore plt1_adv_p1 -ALTER TABLE plt1_adv ATTACH PARTITION plt1_adv_p1 FOR VALUES IN ('0001', '0003'); --- Add to plt1_adv the extra NULL partition containing only NULL values as the --- key values -CREATE TABLE plt1_adv_extra PARTITION OF plt1_adv FOR VALUES IN (NULL); -INSERT INTO plt1_adv VALUES (-1, -1, NULL); -ANALYZE plt1_adv; -DROP TABLE plt2_adv_p3_null; --- Restore plt2_adv_p3 -ALTER TABLE plt2_adv ATTACH PARTITION plt2_adv_p3 FOR VALUES IN ('0007', '0009'); -ANALYZE plt2_adv; --- inner join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Join - Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on plt1_adv_p1 t1_1 - Filter: (b < 10) - -> Hash Join - Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Hash Join - Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) - -> Seq Scan on plt2_adv_p3 t2_3 - -> Hash - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) -(21 rows) - -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - a | c | a | c ----+------+---+------ - 3 | 0003 | 3 | 0003 - 4 | 0004 | 4 | 0004 - 6 | 0006 | 6 | 0006 - 9 | 0009 | 9 | 0009 -(4 rows) - --- left join; currently we can't do partitioned join if there are no matched --- partitions on the nullable side -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - QUERY PLAN ---------------------------------------------------------- - Sort - Sort Key: t1.a - -> Hash Right Join - Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c)) - -> Append - -> Seq Scan on plt2_adv_p1 t2_1 - -> Seq Scan on plt2_adv_p2 t2_2 - -> Seq Scan on plt2_adv_p3 t2_3 - -> Hash - -> Append - -> Seq Scan on plt1_adv_p1 t1_1 - Filter: (b < 10) - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) - -> Seq Scan on plt1_adv_extra t1_4 - Filter: (b < 10) -(18 rows) - --- full join; currently we can't do partitioned join if there are no matched --- partitions on the nullable side -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; - QUERY PLAN -------------------------------------------------------------------------- - Sort - Sort Key: t1.a, t2.a - -> Hash Full Join - Hash Cond: ((t1.a = t2.a) AND (t1.c = t2.c)) - Filter: ((COALESCE(t1.b, 0) < 10) AND (COALESCE(t2.b, 0) < 10)) - -> Append - -> Seq Scan on plt1_adv_p1 t1_1 - -> Seq Scan on plt1_adv_p2 t1_2 - -> Seq Scan on plt1_adv_p3 t1_3 - -> Seq Scan on plt1_adv_extra t1_4 - -> Hash - -> Append - -> Seq Scan on plt2_adv_p1 t2_1 - -> Seq Scan on plt2_adv_p2 t2_2 - -> Seq Scan on plt2_adv_p3 t2_3 -(15 rows) - --- Add to plt2_adv the extra NULL partition containing only NULL values as the --- key values -CREATE TABLE plt2_adv_extra PARTITION OF plt2_adv FOR VALUES IN (NULL); -INSERT INTO plt2_adv VALUES (-1, -1, NULL); -ANALYZE plt2_adv; --- inner join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Join - Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on plt1_adv_p1 t1_1 - Filter: (b < 10) - -> Hash Join - Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Hash Join - Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) - -> Seq Scan on plt2_adv_p3 t2_3 - -> Hash - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) -(21 rows) - -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - a | c | a | c ----+------+---+------ - 3 | 0003 | 3 | 0003 - 4 | 0004 | 4 | 0004 - 6 | 0006 | 6 | 0006 - 9 | 0009 | 9 | 0009 -(4 rows) - --- left join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - QUERY PLAN ----------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Right Join - Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on plt1_adv_p1 t1_1 - Filter: (b < 10) - -> Hash Right Join - Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Hash Right Join - Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) - -> Seq Scan on plt2_adv_p3 t2_3 - -> Hash - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) - -> Nested Loop Left Join - Join Filter: ((t1_4.a = t2_4.a) AND (t1_4.c = t2_4.c)) - -> Seq Scan on plt1_adv_extra t1_4 - Filter: (b < 10) - -> Seq Scan on plt2_adv_extra t2_4 -(26 rows) - -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - a | c | a | c -----+------+---+------ - -1 | | | - 1 | 0001 | | - 3 | 0003 | 3 | 0003 - 4 | 0004 | 4 | 0004 - 6 | 0006 | 6 | 0006 - 8 | 0008 | | - 9 | 0009 | 9 | 0009 -(7 rows) - --- full join -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; - QUERY PLAN ------------------------------------------------------------------------------------ - Sort - Sort Key: t1.a, t2.a - -> Append - -> Hash Full Join - Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c)) - Filter: ((COALESCE(t1_1.b, 0) < 10) AND (COALESCE(t2_1.b, 0) < 10)) - -> Seq Scan on plt1_adv_p1 t1_1 - -> Hash - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash Full Join - Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c)) - Filter: ((COALESCE(t1_2.b, 0) < 10) AND (COALESCE(t2_2.b, 0) < 10)) - -> Seq Scan on plt1_adv_p2 t1_2 - -> Hash - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash Full Join - Hash Cond: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c)) - Filter: ((COALESCE(t1_3.b, 0) < 10) AND (COALESCE(t2_3.b, 0) < 10)) - -> Seq Scan on plt1_adv_p3 t1_3 - -> Hash - -> Seq Scan on plt2_adv_p3 t2_3 - -> Hash Full Join - Hash Cond: ((t1_4.a = t2_4.a) AND (t1_4.c = t2_4.c)) - Filter: ((COALESCE(t1_4.b, 0) < 10) AND (COALESCE(t2_4.b, 0) < 10)) - -> Seq Scan on plt1_adv_extra t1_4 - -> Hash - -> Seq Scan on plt2_adv_extra t2_4 -(27 rows) - -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; - a | c | a | c -----+------+----+------ - -1 | | | - 1 | 0001 | | - 3 | 0003 | 3 | 0003 - 4 | 0004 | 4 | 0004 - 6 | 0006 | 6 | 0006 - 8 | 0008 | | - 9 | 0009 | 9 | 0009 - | | -1 | - | | 2 | 0002 - | | 7 | 0007 -(10 rows) - --- 3-way join to test the NULL partition of a join relation -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt1_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Right Join - Hash Cond: ((t3_1.a = t1_1.a) AND (t3_1.c = t1_1.c)) - -> Seq Scan on plt1_adv_p1 t3_1 - -> Hash - -> Hash Right Join - Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on plt1_adv_p1 t1_1 - Filter: (b < 10) - -> Hash Right Join - Hash Cond: ((t3_2.a = t1_2.a) AND (t3_2.c = t1_2.c)) - -> Seq Scan on plt1_adv_p2 t3_2 - -> Hash - -> Hash Right Join - Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p2 t1_2 - Filter: (b < 10) - -> Hash Right Join - Hash Cond: ((t3_3.a = t1_3.a) AND (t3_3.c = t1_3.c)) - -> Seq Scan on plt1_adv_p3 t3_3 - -> Hash - -> Hash Right Join - Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) - -> Seq Scan on plt2_adv_p3 t2_3 - -> Hash - -> Seq Scan on plt1_adv_p3 t1_3 - Filter: (b < 10) - -> Nested Loop Left Join - Join Filter: ((t1_4.a = t3_4.a) AND (t1_4.c = t3_4.c)) - -> Nested Loop Left Join - Join Filter: ((t1_4.a = t2_4.a) AND (t1_4.c = t2_4.c)) - -> Seq Scan on plt1_adv_extra t1_4 - Filter: (b < 10) - -> Seq Scan on plt2_adv_extra t2_4 - -> Seq Scan on plt1_adv_extra t3_4 -(41 rows) - -SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt1_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a; - a | c | a | c | a | c -----+------+---+------+---+------ - -1 | | | | | - 1 | 0001 | | | 1 | 0001 - 3 | 0003 | 3 | 0003 | 3 | 0003 - 4 | 0004 | 4 | 0004 | 4 | 0004 - 6 | 0006 | 6 | 0006 | 6 | 0006 - 8 | 0008 | | | 8 | 0008 - 9 | 0009 | 9 | 0009 | 9 | 0009 -(7 rows) - -DROP TABLE plt1_adv_extra; -DROP TABLE plt2_adv_extra; --- Test default partitions -ALTER TABLE plt1_adv DETACH PARTITION plt1_adv_p1; --- Change plt1_adv_p1 to the default partition -ALTER TABLE plt1_adv ATTACH PARTITION plt1_adv_p1 DEFAULT; -DROP TABLE plt1_adv_p3; -ANALYZE plt1_adv; -DROP TABLE plt2_adv_p3; -ANALYZE plt2_adv; --- We can do partitioned join even if only one of relations has the default --- partition -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Join - Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c)) - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on plt1_adv_p1 t1_2 - Filter: (b < 10) - -> Hash Join - Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c)) - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p2 t1_1 - Filter: (b < 10) -(15 rows) - -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - a | c | a | c ----+------+---+------ - 3 | 0003 | 3 | 0003 - 4 | 0004 | 4 | 0004 - 6 | 0006 | 6 | 0006 -(3 rows) - -ALTER TABLE plt2_adv DETACH PARTITION plt2_adv_p2; --- Change plt2_adv_p2 to contain '0005' in addition to '0004' and '0006' as --- the key values -CREATE TABLE plt2_adv_p2_ext PARTITION OF plt2_adv FOR VALUES IN ('0004', '0005', '0006'); -INSERT INTO plt2_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (4, 5, 6); -ANALYZE plt2_adv; --- Partitioned join can't be applied because the default partition of plt1_adv --- matches plt2_adv_p1 and plt2_adv_p2_ext -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a - -> Hash Join - Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c)) - -> Append - -> Seq Scan on plt2_adv_p1 t2_1 - -> Seq Scan on plt2_adv_p2_ext t2_2 - -> Hash - -> Append - -> Seq Scan on plt1_adv_p2 t1_1 - Filter: (b < 10) - -> Seq Scan on plt1_adv_p1 t1_2 - Filter: (b < 10) -(13 rows) - -ALTER TABLE plt2_adv DETACH PARTITION plt2_adv_p2_ext; --- Change plt2_adv_p2_ext to the default partition -ALTER TABLE plt2_adv ATTACH PARTITION plt2_adv_p2_ext DEFAULT; -ANALYZE plt2_adv; --- Partitioned join can't be applied because the default partition of plt1_adv --- matches plt2_adv_p1 and plt2_adv_p2_ext -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a - -> Hash Join - Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c)) - -> Append - -> Seq Scan on plt2_adv_p1 t2_1 - -> Seq Scan on plt2_adv_p2_ext t2_2 - -> Hash - -> Append - -> Seq Scan on plt1_adv_p2 t1_1 - Filter: (b < 10) - -> Seq Scan on plt1_adv_p1 t1_2 - Filter: (b < 10) -(13 rows) - -DROP TABLE plt2_adv_p2_ext; --- Restore plt2_adv_p2 -ALTER TABLE plt2_adv ATTACH PARTITION plt2_adv_p2 FOR VALUES IN ('0004', '0006'); -ANALYZE plt2_adv; -CREATE TABLE plt3_adv (a int, b int, c text) PARTITION BY LIST (c); -CREATE TABLE plt3_adv_p1 PARTITION OF plt3_adv FOR VALUES IN ('0004', '0006'); -CREATE TABLE plt3_adv_p2 PARTITION OF plt3_adv FOR VALUES IN ('0007', '0009'); -INSERT INTO plt3_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (4, 6, 7, 9); -ANALYZE plt3_adv; --- 3-way join to test the default partition of a join relation -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt3_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Right Join - Hash Cond: ((t3_1.a = t1_1.a) AND (t3_1.c = t1_1.c)) - -> Seq Scan on plt3_adv_p1 t3_1 - -> Hash - -> Hash Right Join - Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c)) - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p2 t1_1 - Filter: (b < 10) - -> Hash Right Join - Hash Cond: ((t3_2.a = t1_2.a) AND (t3_2.c = t1_2.c)) - -> Seq Scan on plt3_adv_p2 t3_2 - -> Hash - -> Hash Right Join - Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c)) - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on plt1_adv_p1 t1_2 - Filter: (b < 10) -(23 rows) - -SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt3_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a; - a | c | a | c | a | c ----+------+---+------+---+------ - 1 | 0001 | | | | - 3 | 0003 | 3 | 0003 | | - 4 | 0004 | 4 | 0004 | 4 | 0004 - 6 | 0006 | 6 | 0006 | 6 | 0006 -(4 rows) - --- Test cases where one side has the default partition while the other side --- has the NULL partition -DROP TABLE plt2_adv_p1; --- Add the NULL partition to plt2_adv -CREATE TABLE plt2_adv_p1_null PARTITION OF plt2_adv FOR VALUES IN (NULL, '0001', '0003'); -INSERT INTO plt2_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (1, 3); -INSERT INTO plt2_adv VALUES (-1, -1, NULL); -ANALYZE plt2_adv; -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Join - Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c)) - -> Seq Scan on plt2_adv_p1_null t2_1 - -> Hash - -> Seq Scan on plt1_adv_p1 t1_2 - Filter: (b < 10) - -> Hash Join - Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c)) - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p2 t1_1 - Filter: (b < 10) -(15 rows) - -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - a | c | a | c ----+------+---+------ - 1 | 0001 | 1 | 0001 - 3 | 0003 | 3 | 0003 - 4 | 0004 | 4 | 0004 - 6 | 0006 | 6 | 0006 -(4 rows) - -DROP TABLE plt2_adv_p1_null; --- Add the NULL partition that contains only NULL values as the key values -CREATE TABLE plt2_adv_p1_null PARTITION OF plt2_adv FOR VALUES IN (NULL); -INSERT INTO plt2_adv VALUES (-1, -1, NULL); -ANALYZE plt2_adv; -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: t1.a - -> Hash Join - Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c)) - -> Seq Scan on plt2_adv_p2 t2 - -> Hash - -> Seq Scan on plt1_adv_p2 t1 - Filter: (b < 10) -(8 rows) - -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; - a | c | a | c ----+------+---+------ - 4 | 0004 | 4 | 0004 - 6 | 0006 | 6 | 0006 -(2 rows) - -DROP TABLE plt1_adv; -DROP TABLE plt2_adv; -DROP TABLE plt3_adv; --- Test interaction of partitioned join with partition pruning -CREATE TABLE plt1_adv (a int, b int, c text) PARTITION BY LIST (c); -CREATE TABLE plt1_adv_p1 PARTITION OF plt1_adv FOR VALUES IN ('0001'); -CREATE TABLE plt1_adv_p2 PARTITION OF plt1_adv FOR VALUES IN ('0002'); -CREATE TABLE plt1_adv_p3 PARTITION OF plt1_adv FOR VALUES IN ('0003'); -CREATE TABLE plt1_adv_p4 PARTITION OF plt1_adv FOR VALUES IN (NULL, '0004', '0005'); -INSERT INTO plt1_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (1, 2, 3, 4, 5); -INSERT INTO plt1_adv VALUES (-1, -1, NULL); -ANALYZE plt1_adv; -CREATE TABLE plt2_adv (a int, b int, c text) PARTITION BY LIST (c); -CREATE TABLE plt2_adv_p1 PARTITION OF plt2_adv FOR VALUES IN ('0001', '0002'); -CREATE TABLE plt2_adv_p2 PARTITION OF plt2_adv FOR VALUES IN (NULL); -CREATE TABLE plt2_adv_p3 PARTITION OF plt2_adv FOR VALUES IN ('0003'); -CREATE TABLE plt2_adv_p4 PARTITION OF plt2_adv FOR VALUES IN ('0004', '0005'); -INSERT INTO plt2_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (1, 2, 3, 4, 5); -INSERT INTO plt2_adv VALUES (-1, -1, NULL); -ANALYZE plt2_adv; -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a; - QUERY PLAN ------------------------------------------------------------------------------------------ - Sort - Sort Key: t1.a - -> Append - -> Hash Join - Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) - -> Seq Scan on plt2_adv_p3 t2_1 - -> Hash - -> Seq Scan on plt1_adv_p3 t1_1 - Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[]))) - -> Hash Join - Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) - -> Seq Scan on plt2_adv_p4 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p4 t1_2 - Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[]))) -(15 rows) - -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a; - a | c | a | c ----+------+---+------ - 3 | 0003 | 3 | 0003 - 4 | 0004 | 4 | 0004 - 5 | 0005 | 5 | 0005 -(3 rows) - -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------- - Sort - Sort Key: t1.a - -> Hash Right Join - Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c)) - -> Seq Scan on plt2_adv_p4 t2 - -> Hash - -> Seq Scan on plt1_adv_p4 t1 - Filter: ((c IS NULL) AND (b < 10)) -(8 rows) - -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a; - a | c | a | c -----+---+---+--- - -1 | | | -(1 row) - -CREATE TABLE plt1_adv_default PARTITION OF plt1_adv DEFAULT; -ANALYZE plt1_adv; -CREATE TABLE plt2_adv_default PARTITION OF plt2_adv DEFAULT; -ANALYZE plt2_adv; -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a; - QUERY PLAN ------------------------------------------------------------------------------------------ - Sort - Sort Key: t1.a - -> Append - -> Hash Join - Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) - -> Seq Scan on plt2_adv_p3 t2_1 - -> Hash - -> Seq Scan on plt1_adv_p3 t1_1 - Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[]))) - -> Hash Join - Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) - -> Seq Scan on plt2_adv_p4 t2_2 - -> Hash - -> Seq Scan on plt1_adv_p4 t1_2 - Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[]))) -(15 rows) - -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a; - a | c | a | c ----+------+---+------ - 3 | 0003 | 3 | 0003 - 4 | 0004 | 4 | 0004 - 5 | 0005 | 5 | 0005 -(3 rows) - -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------- - Sort - Sort Key: t1.a - -> Hash Right Join - Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c)) - -> Seq Scan on plt2_adv_p4 t2 - -> Hash - -> Seq Scan on plt1_adv_p4 t1 - Filter: ((c IS NULL) AND (b < 10)) -(8 rows) - -SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a; - a | c | a | c -----+---+---+--- - -1 | | | -(1 row) - -DROP TABLE plt1_adv; -DROP TABLE plt2_adv; --- Test the process_outer_partition() code path -CREATE TABLE plt1_adv (a int, b int, c text) PARTITION BY LIST (c); -CREATE TABLE plt1_adv_p1 PARTITION OF plt1_adv FOR VALUES IN ('0000', '0001', '0002'); -CREATE TABLE plt1_adv_p2 PARTITION OF plt1_adv FOR VALUES IN ('0003', '0004'); -INSERT INTO plt1_adv SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i; -ANALYZE plt1_adv; -CREATE TABLE plt2_adv (a int, b int, c text) PARTITION BY LIST (c); -CREATE TABLE plt2_adv_p1 PARTITION OF plt2_adv FOR VALUES IN ('0002'); -CREATE TABLE plt2_adv_p2 PARTITION OF plt2_adv FOR VALUES IN ('0003', '0004'); -INSERT INTO plt2_adv SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (2, 3, 4); -ANALYZE plt2_adv; -CREATE TABLE plt3_adv (a int, b int, c text) PARTITION BY LIST (c); -CREATE TABLE plt3_adv_p1 PARTITION OF plt3_adv FOR VALUES IN ('0001'); -CREATE TABLE plt3_adv_p2 PARTITION OF plt3_adv FOR VALUES IN ('0003', '0004'); -INSERT INTO plt3_adv SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (1, 3, 4); -ANALYZE plt3_adv; --- This tests that when merging partitions from plt1_adv and plt2_adv in --- merge_list_bounds(), process_outer_partition() returns an already-assigned --- merged partition when re-called with plt1_adv_p1 for the second list value --- '0001' of that partition -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM (plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.c = t2.c)) FULL JOIN plt3_adv t3 ON (t1.c = t3.c) WHERE coalesce(t1.a, 0) % 5 != 3 AND coalesce(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a, t3.a; - QUERY PLAN ------------------------------------------------------------------------------------------------ - Sort - Sort Key: t1.c, t1.a, t2.a, t3.a - -> Append - -> Hash Full Join - Hash Cond: (t1_1.c = t3_1.c) - Filter: (((COALESCE(t1_1.a, 0) % 5) <> 3) AND ((COALESCE(t1_1.a, 0) % 5) <> 4)) - -> Hash Left Join - Hash Cond: (t1_1.c = t2_1.c) - -> Seq Scan on plt1_adv_p1 t1_1 - -> Hash - -> Seq Scan on plt2_adv_p1 t2_1 - -> Hash - -> Seq Scan on plt3_adv_p1 t3_1 - -> Hash Full Join - Hash Cond: (t1_2.c = t3_2.c) - Filter: (((COALESCE(t1_2.a, 0) % 5) <> 3) AND ((COALESCE(t1_2.a, 0) % 5) <> 4)) - -> Hash Left Join - Hash Cond: (t1_2.c = t2_2.c) - -> Seq Scan on plt1_adv_p2 t1_2 - -> Hash - -> Seq Scan on plt2_adv_p2 t2_2 - -> Hash - -> Seq Scan on plt3_adv_p2 t3_2 -(23 rows) - -SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM (plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.c = t2.c)) FULL JOIN plt3_adv t3 ON (t1.c = t3.c) WHERE coalesce(t1.a, 0) % 5 != 3 AND coalesce(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a, t3.a; - a | c | a | c | a | c -----+------+----+------+----+------ - 0 | 0000 | | | | - 5 | 0000 | | | | - 10 | 0000 | | | | - 15 | 0000 | | | | - 20 | 0000 | | | | - 1 | 0001 | | | 1 | 0001 - 1 | 0001 | | | 6 | 0001 - 1 | 0001 | | | 11 | 0001 - 1 | 0001 | | | 16 | 0001 - 1 | 0001 | | | 21 | 0001 - 6 | 0001 | | | 1 | 0001 - 6 | 0001 | | | 6 | 0001 - 6 | 0001 | | | 11 | 0001 - 6 | 0001 | | | 16 | 0001 - 6 | 0001 | | | 21 | 0001 - 11 | 0001 | | | 1 | 0001 - 11 | 0001 | | | 6 | 0001 - 11 | 0001 | | | 11 | 0001 - 11 | 0001 | | | 16 | 0001 - 11 | 0001 | | | 21 | 0001 - 16 | 0001 | | | 1 | 0001 - 16 | 0001 | | | 6 | 0001 - 16 | 0001 | | | 11 | 0001 - 16 | 0001 | | | 16 | 0001 - 16 | 0001 | | | 21 | 0001 - 21 | 0001 | | | 1 | 0001 - 21 | 0001 | | | 6 | 0001 - 21 | 0001 | | | 11 | 0001 - 21 | 0001 | | | 16 | 0001 - 21 | 0001 | | | 21 | 0001 - 2 | 0002 | 2 | 0002 | | - 2 | 0002 | 7 | 0002 | | - 2 | 0002 | 12 | 0002 | | - 2 | 0002 | 17 | 0002 | | - 2 | 0002 | 22 | 0002 | | - 7 | 0002 | 2 | 0002 | | - 7 | 0002 | 7 | 0002 | | - 7 | 0002 | 12 | 0002 | | - 7 | 0002 | 17 | 0002 | | - 7 | 0002 | 22 | 0002 | | - 12 | 0002 | 2 | 0002 | | - 12 | 0002 | 7 | 0002 | | - 12 | 0002 | 12 | 0002 | | - 12 | 0002 | 17 | 0002 | | - 12 | 0002 | 22 | 0002 | | - 17 | 0002 | 2 | 0002 | | - 17 | 0002 | 7 | 0002 | | - 17 | 0002 | 12 | 0002 | | - 17 | 0002 | 17 | 0002 | | - 17 | 0002 | 22 | 0002 | | - 22 | 0002 | 2 | 0002 | | - 22 | 0002 | 7 | 0002 | | - 22 | 0002 | 12 | 0002 | | - 22 | 0002 | 17 | 0002 | | - 22 | 0002 | 22 | 0002 | | -(55 rows) - -DROP TABLE plt1_adv; -DROP TABLE plt2_adv; -DROP TABLE plt3_adv; --- Tests for multi-level partitioned tables -CREATE TABLE alpha (a double precision, b int, c text) PARTITION BY RANGE (a); -CREATE TABLE alpha_neg PARTITION OF alpha FOR VALUES FROM ('-Infinity') TO (0) PARTITION BY RANGE (b); -CREATE TABLE alpha_pos PARTITION OF alpha FOR VALUES FROM (0) TO (10.0) PARTITION BY LIST (c); -CREATE TABLE alpha_neg_p1 PARTITION OF alpha_neg FOR VALUES FROM (100) TO (200); -CREATE TABLE alpha_neg_p2 PARTITION OF alpha_neg FOR VALUES FROM (200) TO (300); -CREATE TABLE alpha_neg_p3 PARTITION OF alpha_neg FOR VALUES FROM (300) TO (400); -CREATE TABLE alpha_pos_p1 PARTITION OF alpha_pos FOR VALUES IN ('0001', '0003'); -CREATE TABLE alpha_pos_p2 PARTITION OF alpha_pos FOR VALUES IN ('0004', '0006'); -CREATE TABLE alpha_pos_p3 PARTITION OF alpha_pos FOR VALUES IN ('0008', '0009'); -INSERT INTO alpha_neg SELECT -1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(100, 399) i WHERE i % 10 IN (1, 3, 4, 6, 8, 9); -INSERT INTO alpha_pos SELECT 1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(100, 399) i WHERE i % 10 IN (1, 3, 4, 6, 8, 9); -ANALYZE alpha; -CREATE TABLE beta (a double precision, b int, c text) PARTITION BY RANGE (a); -CREATE TABLE beta_neg PARTITION OF beta FOR VALUES FROM (-10.0) TO (0) PARTITION BY RANGE (b); -CREATE TABLE beta_pos PARTITION OF beta FOR VALUES FROM (0) TO ('Infinity') PARTITION BY LIST (c); -CREATE TABLE beta_neg_p1 PARTITION OF beta_neg FOR VALUES FROM (100) TO (150); -CREATE TABLE beta_neg_p2 PARTITION OF beta_neg FOR VALUES FROM (200) TO (300); -CREATE TABLE beta_neg_p3 PARTITION OF beta_neg FOR VALUES FROM (350) TO (500); -CREATE TABLE beta_pos_p1 PARTITION OF beta_pos FOR VALUES IN ('0002', '0003'); -CREATE TABLE beta_pos_p2 PARTITION OF beta_pos FOR VALUES IN ('0004', '0006'); -CREATE TABLE beta_pos_p3 PARTITION OF beta_pos FOR VALUES IN ('0007', '0009'); -INSERT INTO beta_neg SELECT -1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(100, 149) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); -INSERT INTO beta_neg SELECT -1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(200, 299) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); -INSERT INTO beta_neg SELECT -1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(350, 499) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); -INSERT INTO beta_pos SELECT 1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(100, 149) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); -INSERT INTO beta_pos SELECT 1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(200, 299) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); -INSERT INTO beta_pos SELECT 1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(350, 499) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); -ANALYZE beta; -EXPLAIN (COSTS OFF) -SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b) WHERE t1.b >= 125 AND t1.b < 225 ORDER BY t1.a, t1.b; - QUERY PLAN --------------------------------------------------------------------- - Sort - Sort Key: t1.a, t1.b - -> Append - -> Hash Join - Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.b = t2_1.b)) - -> Seq Scan on alpha_neg_p1 t1_1 - Filter: ((b >= 125) AND (b < 225)) - -> Hash - -> Seq Scan on beta_neg_p1 t2_1 - -> Hash Join - Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.b = t1_2.b)) - -> Seq Scan on beta_neg_p2 t2_2 - -> Hash - -> Seq Scan on alpha_neg_p2 t1_2 - Filter: ((b >= 125) AND (b < 225)) - -> Hash Join - Hash Cond: ((t2_4.a = t1_4.a) AND (t2_4.b = t1_4.b)) - -> Append - -> Seq Scan on beta_pos_p1 t2_4 - -> Seq Scan on beta_pos_p2 t2_5 - -> Seq Scan on beta_pos_p3 t2_6 - -> Hash - -> Append - -> Seq Scan on alpha_pos_p1 t1_4 - Filter: ((b >= 125) AND (b < 225)) - -> Seq Scan on alpha_pos_p2 t1_5 - Filter: ((b >= 125) AND (b < 225)) - -> Seq Scan on alpha_pos_p3 t1_6 - Filter: ((b >= 125) AND (b < 225)) -(29 rows) - -SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b) WHERE t1.b >= 125 AND t1.b < 225 ORDER BY t1.a, t1.b; - a | b | c | a | b | c -----+-----+------+----+-----+------ - -1 | 126 | 0006 | -1 | 126 | 0006 - -1 | 129 | 0009 | -1 | 129 | 0009 - -1 | 133 | 0003 | -1 | 133 | 0003 - -1 | 134 | 0004 | -1 | 134 | 0004 - -1 | 136 | 0006 | -1 | 136 | 0006 - -1 | 139 | 0009 | -1 | 139 | 0009 - -1 | 143 | 0003 | -1 | 143 | 0003 - -1 | 144 | 0004 | -1 | 144 | 0004 - -1 | 146 | 0006 | -1 | 146 | 0006 - -1 | 149 | 0009 | -1 | 149 | 0009 - -1 | 203 | 0003 | -1 | 203 | 0003 - -1 | 204 | 0004 | -1 | 204 | 0004 - -1 | 206 | 0006 | -1 | 206 | 0006 - -1 | 209 | 0009 | -1 | 209 | 0009 - -1 | 213 | 0003 | -1 | 213 | 0003 - -1 | 214 | 0004 | -1 | 214 | 0004 - -1 | 216 | 0006 | -1 | 216 | 0006 - -1 | 219 | 0009 | -1 | 219 | 0009 - -1 | 223 | 0003 | -1 | 223 | 0003 - -1 | 224 | 0004 | -1 | 224 | 0004 - 1 | 126 | 0006 | 1 | 126 | 0006 - 1 | 129 | 0009 | 1 | 129 | 0009 - 1 | 133 | 0003 | 1 | 133 | 0003 - 1 | 134 | 0004 | 1 | 134 | 0004 - 1 | 136 | 0006 | 1 | 136 | 0006 - 1 | 139 | 0009 | 1 | 139 | 0009 - 1 | 143 | 0003 | 1 | 143 | 0003 - 1 | 144 | 0004 | 1 | 144 | 0004 - 1 | 146 | 0006 | 1 | 146 | 0006 - 1 | 149 | 0009 | 1 | 149 | 0009 - 1 | 203 | 0003 | 1 | 203 | 0003 - 1 | 204 | 0004 | 1 | 204 | 0004 - 1 | 206 | 0006 | 1 | 206 | 0006 - 1 | 209 | 0009 | 1 | 209 | 0009 - 1 | 213 | 0003 | 1 | 213 | 0003 - 1 | 214 | 0004 | 1 | 214 | 0004 - 1 | 216 | 0006 | 1 | 216 | 0006 - 1 | 219 | 0009 | 1 | 219 | 0009 - 1 | 223 | 0003 | 1 | 223 | 0003 - 1 | 224 | 0004 | 1 | 224 | 0004 -(40 rows) - -EXPLAIN (COSTS OFF) -SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b, t2.b; - QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------- - Sort - Sort Key: t1.a, t1.b, t2.b - -> Append - -> Hash Join - Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c)) - -> Append - -> Seq Scan on alpha_neg_p1 t1_2 - Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))) - -> Seq Scan on alpha_neg_p2 t1_3 - Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))) - -> Hash - -> Append - -> Seq Scan on beta_neg_p1 t2_2 - Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))) - -> Seq Scan on beta_neg_p2 t2_3 - Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))) - -> Nested Loop - Join Filter: ((t1_4.a = t2_4.a) AND (t1_4.c = t2_4.c)) - -> Seq Scan on alpha_pos_p2 t1_4 - Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))) - -> Seq Scan on beta_pos_p2 t2_4 - Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))) - -> Nested Loop - Join Filter: ((t1_5.a = t2_5.a) AND (t1_5.c = t2_5.c)) - -> Seq Scan on alpha_pos_p3 t1_5 - Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))) - -> Seq Scan on beta_pos_p3 t2_5 - Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))) -(28 rows) - -SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b, t2.b; - a | b | c | a | b | c -----+-----+------+----+-----+------ - -1 | 104 | 0004 | -1 | 104 | 0004 - -1 | 104 | 0004 | -1 | 204 | 0004 - -1 | 109 | 0009 | -1 | 109 | 0009 - -1 | 109 | 0009 | -1 | 209 | 0009 - -1 | 204 | 0004 | -1 | 104 | 0004 - -1 | 204 | 0004 | -1 | 204 | 0004 - -1 | 209 | 0009 | -1 | 109 | 0009 - -1 | 209 | 0009 | -1 | 209 | 0009 - 1 | 104 | 0004 | 1 | 104 | 0004 - 1 | 104 | 0004 | 1 | 204 | 0004 - 1 | 109 | 0009 | 1 | 109 | 0009 - 1 | 109 | 0009 | 1 | 209 | 0009 - 1 | 204 | 0004 | 1 | 104 | 0004 - 1 | 204 | 0004 | 1 | 204 | 0004 - 1 | 209 | 0009 | 1 | 109 | 0009 - 1 | 209 | 0009 | 1 | 209 | 0009 -(16 rows) - -EXPLAIN (COSTS OFF) -SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b; - QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------- - Sort - Sort Key: t1.a, t1.b - -> Append - -> Hash Join - Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c)) - -> Seq Scan on alpha_neg_p1 t1_1 - Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))) - -> Hash - -> Seq Scan on beta_neg_p1 t2_1 - Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))) - -> Hash Join - Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c)) - -> Seq Scan on alpha_neg_p2 t1_2 - Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))) - -> Hash - -> Seq Scan on beta_neg_p2 t2_2 - Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))) - -> Nested Loop - Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.b = t2_3.b) AND (t1_3.c = t2_3.c)) - -> Seq Scan on alpha_pos_p2 t1_3 - Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))) - -> Seq Scan on beta_pos_p2 t2_3 - Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))) - -> Nested Loop - Join Filter: ((t1_4.a = t2_4.a) AND (t1_4.b = t2_4.b) AND (t1_4.c = t2_4.c)) - -> Seq Scan on alpha_pos_p3 t1_4 - Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))) - -> Seq Scan on beta_pos_p3 t2_4 - Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))) -(29 rows) - -SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b; - a | b | c | a | b | c -----+-----+------+----+-----+------ - -1 | 104 | 0004 | -1 | 104 | 0004 - -1 | 109 | 0009 | -1 | 109 | 0009 - -1 | 204 | 0004 | -1 | 204 | 0004 - -1 | 209 | 0009 | -1 | 209 | 0009 - 1 | 104 | 0004 | 1 | 104 | 0004 - 1 | 109 | 0009 | 1 | 109 | 0009 - 1 | 204 | 0004 | 1 | 204 | 0004 - 1 | 209 | 0009 | 1 | 209 | 0009 -(8 rows) - --- partitionwise join with fractional paths -CREATE TABLE fract_t (id BIGINT, PRIMARY KEY (id)) PARTITION BY RANGE (id); -CREATE TABLE fract_t0 PARTITION OF fract_t FOR VALUES FROM ('0') TO ('1000'); -CREATE TABLE fract_t1 PARTITION OF fract_t FOR VALUES FROM ('1000') TO ('2000'); --- insert data -INSERT INTO fract_t (id) (SELECT generate_series(0, 1999)); -ANALYZE fract_t; --- verify plan; nested index only scans -SET max_parallel_workers_per_gather = 0; -SET enable_partitionwise_join = on; -EXPLAIN (COSTS OFF) -SELECT x.id, y.id FROM fract_t x LEFT JOIN fract_t y USING (id) ORDER BY x.id ASC LIMIT 10; - QUERY PLAN ------------------------------------------------------------------------ - Limit - -> Merge Append - Sort Key: x.id - -> Merge Left Join - Merge Cond: (x_1.id = y_1.id) - -> Index Only Scan using fract_t0_pkey on fract_t0 x_1 - -> Index Only Scan using fract_t0_pkey on fract_t0 y_1 - -> Merge Left Join - Merge Cond: (x_2.id = y_2.id) - -> Index Only Scan using fract_t1_pkey on fract_t1 x_2 - -> Index Only Scan using fract_t1_pkey on fract_t1 y_2 -(11 rows) - -EXPLAIN (COSTS OFF) -SELECT x.id, y.id FROM fract_t x LEFT JOIN fract_t y USING (id) ORDER BY x.id DESC LIMIT 10; - QUERY PLAN --------------------------------------------------------------------------------- - Limit - -> Merge Append - Sort Key: x.id DESC - -> Nested Loop Left Join - -> Index Only Scan Backward using fract_t0_pkey on fract_t0 x_1 - -> Index Only Scan using fract_t0_pkey on fract_t0 y_1 - Index Cond: (id = x_1.id) - -> Nested Loop Left Join - -> Index Only Scan Backward using fract_t1_pkey on fract_t1 x_2 - -> Index Only Scan using fract_t1_pkey on fract_t1 y_2 - Index Cond: (id = x_2.id) -(11 rows) - --- cleanup -DROP TABLE fract_t; -RESET max_parallel_workers_per_gather; -RESET enable_partitionwise_join; +psql: error: connection to server on socket "/tmp/bBabKAIw6z/.s.PGSQL.19533" failed: No such file or directory + Is the server running locally and accepting connections on that socket? diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/partition_prune.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/partition_prune.out --- /tmp/cirrus-ci-build/src/test/regress/expected/partition_prune.out 2024-09-22 06:28:18.737187137 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/partition_prune.out 2024-09-22 06:38:09.864217598 +0000 @@ -1,4442 +1,2 @@ --- --- Test partitioning planner code --- --- Helper function which can be used for masking out portions of EXPLAIN --- ANALYZE which could contain information that's not consistent on all --- platforms. -create function explain_analyze(query text) returns setof text -language plpgsql as -$$ -declare - ln text; -begin - for ln in - execute format('explain (analyze, costs off, summary off, timing off) %s', - query) - loop - ln := regexp_replace(ln, 'Maximum Storage: \d+', 'Maximum Storage: N'); - return next ln; - end loop; -end; -$$; --- Force generic plans to be used for all prepared statements in this file. -set plan_cache_mode = force_generic_plan; -create table lp (a char) partition by list (a); -create table lp_default partition of lp default; -create table lp_ef partition of lp for values in ('e', 'f'); -create table lp_ad partition of lp for values in ('a', 'd'); -create table lp_bc partition of lp for values in ('b', 'c'); -create table lp_g partition of lp for values in ('g'); -create table lp_null partition of lp for values in (null); -explain (costs off) select * from lp; - QUERY PLAN ------------------------------------ - Append - -> Seq Scan on lp_ad lp_1 - -> Seq Scan on lp_bc lp_2 - -> Seq Scan on lp_ef lp_3 - -> Seq Scan on lp_g lp_4 - -> Seq Scan on lp_null lp_5 - -> Seq Scan on lp_default lp_6 -(7 rows) - -explain (costs off) select * from lp where a > 'a' and a < 'd'; - QUERY PLAN ------------------------------------------------------------ - Append - -> Seq Scan on lp_bc lp_1 - Filter: ((a > 'a'::bpchar) AND (a < 'd'::bpchar)) - -> Seq Scan on lp_default lp_2 - Filter: ((a > 'a'::bpchar) AND (a < 'd'::bpchar)) -(5 rows) - -explain (costs off) select * from lp where a > 'a' and a <= 'd'; - QUERY PLAN ------------------------------------------------------------- - Append - -> Seq Scan on lp_ad lp_1 - Filter: ((a > 'a'::bpchar) AND (a <= 'd'::bpchar)) - -> Seq Scan on lp_bc lp_2 - Filter: ((a > 'a'::bpchar) AND (a <= 'd'::bpchar)) - -> Seq Scan on lp_default lp_3 - Filter: ((a > 'a'::bpchar) AND (a <= 'd'::bpchar)) -(7 rows) - -explain (costs off) select * from lp where a = 'a'; - QUERY PLAN ------------------------------ - Seq Scan on lp_ad lp - Filter: (a = 'a'::bpchar) -(2 rows) - -explain (costs off) select * from lp where 'a' = a; /* commuted */ - QUERY PLAN ------------------------------ - Seq Scan on lp_ad lp - Filter: ('a'::bpchar = a) -(2 rows) - -explain (costs off) select * from lp where a is not null; - QUERY PLAN ------------------------------------ - Append - -> Seq Scan on lp_ad lp_1 - Filter: (a IS NOT NULL) - -> Seq Scan on lp_bc lp_2 - Filter: (a IS NOT NULL) - -> Seq Scan on lp_ef lp_3 - Filter: (a IS NOT NULL) - -> Seq Scan on lp_g lp_4 - Filter: (a IS NOT NULL) - -> Seq Scan on lp_default lp_5 - Filter: (a IS NOT NULL) -(11 rows) - -explain (costs off) select * from lp where a is null; - QUERY PLAN ------------------------- - Seq Scan on lp_null lp - Filter: (a IS NULL) -(2 rows) - -explain (costs off) select * from lp where a = 'a' or a = 'c'; - QUERY PLAN ----------------------------------------------------------- - Append - -> Seq Scan on lp_ad lp_1 - Filter: ((a = 'a'::bpchar) OR (a = 'c'::bpchar)) - -> Seq Scan on lp_bc lp_2 - Filter: ((a = 'a'::bpchar) OR (a = 'c'::bpchar)) -(5 rows) - -explain (costs off) select * from lp where a is not null and (a = 'a' or a = 'c'); - QUERY PLAN --------------------------------------------------------------------------------- - Append - -> Seq Scan on lp_ad lp_1 - Filter: ((a IS NOT NULL) AND ((a = 'a'::bpchar) OR (a = 'c'::bpchar))) - -> Seq Scan on lp_bc lp_2 - Filter: ((a IS NOT NULL) AND ((a = 'a'::bpchar) OR (a = 'c'::bpchar))) -(5 rows) - -explain (costs off) select * from lp where a <> 'g'; - QUERY PLAN ------------------------------------- - Append - -> Seq Scan on lp_ad lp_1 - Filter: (a <> 'g'::bpchar) - -> Seq Scan on lp_bc lp_2 - Filter: (a <> 'g'::bpchar) - -> Seq Scan on lp_ef lp_3 - Filter: (a <> 'g'::bpchar) - -> Seq Scan on lp_default lp_4 - Filter: (a <> 'g'::bpchar) -(9 rows) - -explain (costs off) select * from lp where a <> 'a' and a <> 'd'; - QUERY PLAN -------------------------------------------------------------- - Append - -> Seq Scan on lp_bc lp_1 - Filter: ((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) - -> Seq Scan on lp_ef lp_2 - Filter: ((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) - -> Seq Scan on lp_g lp_3 - Filter: ((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) - -> Seq Scan on lp_default lp_4 - Filter: ((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) -(9 rows) - -explain (costs off) select * from lp where a not in ('a', 'd'); - QUERY PLAN ------------------------------------------------- - Append - -> Seq Scan on lp_bc lp_1 - Filter: (a <> ALL ('{a,d}'::bpchar[])) - -> Seq Scan on lp_ef lp_2 - Filter: (a <> ALL ('{a,d}'::bpchar[])) - -> Seq Scan on lp_g lp_3 - Filter: (a <> ALL ('{a,d}'::bpchar[])) - -> Seq Scan on lp_default lp_4 - Filter: (a <> ALL ('{a,d}'::bpchar[])) -(9 rows) - --- collation matches the partitioning collation, pruning works -create table coll_pruning (a text collate "C") partition by list (a); -create table coll_pruning_a partition of coll_pruning for values in ('a'); -create table coll_pruning_b partition of coll_pruning for values in ('b'); -create table coll_pruning_def partition of coll_pruning default; -explain (costs off) select * from coll_pruning where a collate "C" = 'a' collate "C"; - QUERY PLAN ------------------------------------------ - Seq Scan on coll_pruning_a coll_pruning - Filter: (a = 'a'::text COLLATE "C") -(2 rows) - --- collation doesn't match the partitioning collation, no pruning occurs -explain (costs off) select * from coll_pruning where a collate "POSIX" = 'a' collate "POSIX"; - QUERY PLAN ---------------------------------------------------------- - Append - -> Seq Scan on coll_pruning_a coll_pruning_1 - Filter: ((a)::text = 'a'::text COLLATE "POSIX") - -> Seq Scan on coll_pruning_b coll_pruning_2 - Filter: ((a)::text = 'a'::text COLLATE "POSIX") - -> Seq Scan on coll_pruning_def coll_pruning_3 - Filter: ((a)::text = 'a'::text COLLATE "POSIX") -(7 rows) - -create table rlp (a int, b varchar) partition by range (a); -create table rlp_default partition of rlp default partition by list (a); -create table rlp_default_default partition of rlp_default default; -create table rlp_default_10 partition of rlp_default for values in (10); -create table rlp_default_30 partition of rlp_default for values in (30); -create table rlp_default_null partition of rlp_default for values in (null); -create table rlp1 partition of rlp for values from (minvalue) to (1); -create table rlp2 partition of rlp for values from (1) to (10); -create table rlp3 (b varchar, a int) partition by list (b varchar_ops); -create table rlp3_default partition of rlp3 default; -create table rlp3abcd partition of rlp3 for values in ('ab', 'cd'); -create table rlp3efgh partition of rlp3 for values in ('ef', 'gh'); -create table rlp3nullxy partition of rlp3 for values in (null, 'xy'); -alter table rlp attach partition rlp3 for values from (15) to (20); -create table rlp4 partition of rlp for values from (20) to (30) partition by range (a); -create table rlp4_default partition of rlp4 default; -create table rlp4_1 partition of rlp4 for values from (20) to (25); -create table rlp4_2 partition of rlp4 for values from (25) to (29); -create table rlp5 partition of rlp for values from (31) to (maxvalue) partition by range (a); -create table rlp5_default partition of rlp5 default; -create table rlp5_1 partition of rlp5 for values from (31) to (40); -explain (costs off) select * from rlp where a < 1; - QUERY PLAN ----------------------- - Seq Scan on rlp1 rlp - Filter: (a < 1) -(2 rows) - -explain (costs off) select * from rlp where 1 > a; /* commuted */ - QUERY PLAN ----------------------- - Seq Scan on rlp1 rlp - Filter: (1 > a) -(2 rows) - -explain (costs off) select * from rlp where a <= 1; - QUERY PLAN ------------------------------- - Append - -> Seq Scan on rlp1 rlp_1 - Filter: (a <= 1) - -> Seq Scan on rlp2 rlp_2 - Filter: (a <= 1) -(5 rows) - -explain (costs off) select * from rlp where a = 1; - QUERY PLAN ----------------------- - Seq Scan on rlp2 rlp - Filter: (a = 1) -(2 rows) - -explain (costs off) select * from rlp where a = 1::bigint; /* same as above */ - QUERY PLAN ------------------------------ - Seq Scan on rlp2 rlp - Filter: (a = '1'::bigint) -(2 rows) - -explain (costs off) select * from rlp where a = 1::numeric; /* no pruning */ - QUERY PLAN ------------------------------------------------ - Append - -> Seq Scan on rlp1 rlp_1 - Filter: ((a)::numeric = '1'::numeric) - -> Seq Scan on rlp2 rlp_2 - Filter: ((a)::numeric = '1'::numeric) - -> Seq Scan on rlp3abcd rlp_3 - Filter: ((a)::numeric = '1'::numeric) - -> Seq Scan on rlp3efgh rlp_4 - Filter: ((a)::numeric = '1'::numeric) - -> Seq Scan on rlp3nullxy rlp_5 - Filter: ((a)::numeric = '1'::numeric) - -> Seq Scan on rlp3_default rlp_6 - Filter: ((a)::numeric = '1'::numeric) - -> Seq Scan on rlp4_1 rlp_7 - Filter: ((a)::numeric = '1'::numeric) - -> Seq Scan on rlp4_2 rlp_8 - Filter: ((a)::numeric = '1'::numeric) - -> Seq Scan on rlp4_default rlp_9 - Filter: ((a)::numeric = '1'::numeric) - -> Seq Scan on rlp5_1 rlp_10 - Filter: ((a)::numeric = '1'::numeric) - -> Seq Scan on rlp5_default rlp_11 - Filter: ((a)::numeric = '1'::numeric) - -> Seq Scan on rlp_default_10 rlp_12 - Filter: ((a)::numeric = '1'::numeric) - -> Seq Scan on rlp_default_30 rlp_13 - Filter: ((a)::numeric = '1'::numeric) - -> Seq Scan on rlp_default_null rlp_14 - Filter: ((a)::numeric = '1'::numeric) - -> Seq Scan on rlp_default_default rlp_15 - Filter: ((a)::numeric = '1'::numeric) -(31 rows) - -explain (costs off) select * from rlp where a <= 10; - QUERY PLAN ---------------------------------------------- - Append - -> Seq Scan on rlp1 rlp_1 - Filter: (a <= 10) - -> Seq Scan on rlp2 rlp_2 - Filter: (a <= 10) - -> Seq Scan on rlp_default_10 rlp_3 - Filter: (a <= 10) - -> Seq Scan on rlp_default_default rlp_4 - Filter: (a <= 10) -(9 rows) - -explain (costs off) select * from rlp where a > 10; - QUERY PLAN ----------------------------------------------- - Append - -> Seq Scan on rlp3abcd rlp_1 - Filter: (a > 10) - -> Seq Scan on rlp3efgh rlp_2 - Filter: (a > 10) - -> Seq Scan on rlp3nullxy rlp_3 - Filter: (a > 10) - -> Seq Scan on rlp3_default rlp_4 - Filter: (a > 10) - -> Seq Scan on rlp4_1 rlp_5 - Filter: (a > 10) - -> Seq Scan on rlp4_2 rlp_6 - Filter: (a > 10) - -> Seq Scan on rlp4_default rlp_7 - Filter: (a > 10) - -> Seq Scan on rlp5_1 rlp_8 - Filter: (a > 10) - -> Seq Scan on rlp5_default rlp_9 - Filter: (a > 10) - -> Seq Scan on rlp_default_30 rlp_10 - Filter: (a > 10) - -> Seq Scan on rlp_default_default rlp_11 - Filter: (a > 10) -(23 rows) - -explain (costs off) select * from rlp where a < 15; - QUERY PLAN ---------------------------------------------- - Append - -> Seq Scan on rlp1 rlp_1 - Filter: (a < 15) - -> Seq Scan on rlp2 rlp_2 - Filter: (a < 15) - -> Seq Scan on rlp_default_10 rlp_3 - Filter: (a < 15) - -> Seq Scan on rlp_default_default rlp_4 - Filter: (a < 15) -(9 rows) - -explain (costs off) select * from rlp where a <= 15; - QUERY PLAN ---------------------------------------------- - Append - -> Seq Scan on rlp1 rlp_1 - Filter: (a <= 15) - -> Seq Scan on rlp2 rlp_2 - Filter: (a <= 15) - -> Seq Scan on rlp3abcd rlp_3 - Filter: (a <= 15) - -> Seq Scan on rlp3efgh rlp_4 - Filter: (a <= 15) - -> Seq Scan on rlp3nullxy rlp_5 - Filter: (a <= 15) - -> Seq Scan on rlp3_default rlp_6 - Filter: (a <= 15) - -> Seq Scan on rlp_default_10 rlp_7 - Filter: (a <= 15) - -> Seq Scan on rlp_default_default rlp_8 - Filter: (a <= 15) -(17 rows) - -explain (costs off) select * from rlp where a > 15 and b = 'ab'; - QUERY PLAN ---------------------------------------------------------- - Append - -> Seq Scan on rlp3abcd rlp_1 - Filter: ((a > 15) AND ((b)::text = 'ab'::text)) - -> Seq Scan on rlp4_1 rlp_2 - Filter: ((a > 15) AND ((b)::text = 'ab'::text)) - -> Seq Scan on rlp4_2 rlp_3 - Filter: ((a > 15) AND ((b)::text = 'ab'::text)) - -> Seq Scan on rlp4_default rlp_4 - Filter: ((a > 15) AND ((b)::text = 'ab'::text)) - -> Seq Scan on rlp5_1 rlp_5 - Filter: ((a > 15) AND ((b)::text = 'ab'::text)) - -> Seq Scan on rlp5_default rlp_6 - Filter: ((a > 15) AND ((b)::text = 'ab'::text)) - -> Seq Scan on rlp_default_30 rlp_7 - Filter: ((a > 15) AND ((b)::text = 'ab'::text)) - -> Seq Scan on rlp_default_default rlp_8 - Filter: ((a > 15) AND ((b)::text = 'ab'::text)) -(17 rows) - -explain (costs off) select * from rlp where a = 16; - QUERY PLAN --------------------------------------- - Append - -> Seq Scan on rlp3abcd rlp_1 - Filter: (a = 16) - -> Seq Scan on rlp3efgh rlp_2 - Filter: (a = 16) - -> Seq Scan on rlp3nullxy rlp_3 - Filter: (a = 16) - -> Seq Scan on rlp3_default rlp_4 - Filter: (a = 16) -(9 rows) - -explain (costs off) select * from rlp where a = 16 and b in ('not', 'in', 'here'); - QUERY PLAN ----------------------------------------------------------------------- - Seq Scan on rlp3_default rlp - Filter: ((a = 16) AND ((b)::text = ANY ('{not,in,here}'::text[]))) -(2 rows) - -explain (costs off) select * from rlp where a = 16 and b < 'ab'; - QUERY PLAN ---------------------------------------------------- - Seq Scan on rlp3_default rlp - Filter: (((b)::text < 'ab'::text) AND (a = 16)) -(2 rows) - -explain (costs off) select * from rlp where a = 16 and b <= 'ab'; - QUERY PLAN ----------------------------------------------------------- - Append - -> Seq Scan on rlp3abcd rlp_1 - Filter: (((b)::text <= 'ab'::text) AND (a = 16)) - -> Seq Scan on rlp3_default rlp_2 - Filter: (((b)::text <= 'ab'::text) AND (a = 16)) -(5 rows) - -explain (costs off) select * from rlp where a = 16 and b is null; - QUERY PLAN --------------------------------------- - Seq Scan on rlp3nullxy rlp - Filter: ((b IS NULL) AND (a = 16)) -(2 rows) - -explain (costs off) select * from rlp where a = 16 and b is not null; - QUERY PLAN ------------------------------------------------- - Append - -> Seq Scan on rlp3abcd rlp_1 - Filter: ((b IS NOT NULL) AND (a = 16)) - -> Seq Scan on rlp3efgh rlp_2 - Filter: ((b IS NOT NULL) AND (a = 16)) - -> Seq Scan on rlp3nullxy rlp_3 - Filter: ((b IS NOT NULL) AND (a = 16)) - -> Seq Scan on rlp3_default rlp_4 - Filter: ((b IS NOT NULL) AND (a = 16)) -(9 rows) - -explain (costs off) select * from rlp where a is null; - QUERY PLAN ----------------------------------- - Seq Scan on rlp_default_null rlp - Filter: (a IS NULL) -(2 rows) - -explain (costs off) select * from rlp where a is not null; - QUERY PLAN ----------------------------------------------- - Append - -> Seq Scan on rlp1 rlp_1 - Filter: (a IS NOT NULL) - -> Seq Scan on rlp2 rlp_2 - Filter: (a IS NOT NULL) - -> Seq Scan on rlp3abcd rlp_3 - Filter: (a IS NOT NULL) - -> Seq Scan on rlp3efgh rlp_4 - Filter: (a IS NOT NULL) - -> Seq Scan on rlp3nullxy rlp_5 - Filter: (a IS NOT NULL) - -> Seq Scan on rlp3_default rlp_6 - Filter: (a IS NOT NULL) - -> Seq Scan on rlp4_1 rlp_7 - Filter: (a IS NOT NULL) - -> Seq Scan on rlp4_2 rlp_8 - Filter: (a IS NOT NULL) - -> Seq Scan on rlp4_default rlp_9 - Filter: (a IS NOT NULL) - -> Seq Scan on rlp5_1 rlp_10 - Filter: (a IS NOT NULL) - -> Seq Scan on rlp5_default rlp_11 - Filter: (a IS NOT NULL) - -> Seq Scan on rlp_default_10 rlp_12 - Filter: (a IS NOT NULL) - -> Seq Scan on rlp_default_30 rlp_13 - Filter: (a IS NOT NULL) - -> Seq Scan on rlp_default_default rlp_14 - Filter: (a IS NOT NULL) -(29 rows) - -explain (costs off) select * from rlp where a > 30; - QUERY PLAN ---------------------------------------------- - Append - -> Seq Scan on rlp5_1 rlp_1 - Filter: (a > 30) - -> Seq Scan on rlp5_default rlp_2 - Filter: (a > 30) - -> Seq Scan on rlp_default_default rlp_3 - Filter: (a > 30) -(7 rows) - -explain (costs off) select * from rlp where a = 30; /* only default is scanned */ - QUERY PLAN --------------------------------- - Seq Scan on rlp_default_30 rlp - Filter: (a = 30) -(2 rows) - -explain (costs off) select * from rlp where a <= 31; - QUERY PLAN ----------------------------------------------- - Append - -> Seq Scan on rlp1 rlp_1 - Filter: (a <= 31) - -> Seq Scan on rlp2 rlp_2 - Filter: (a <= 31) - -> Seq Scan on rlp3abcd rlp_3 - Filter: (a <= 31) - -> Seq Scan on rlp3efgh rlp_4 - Filter: (a <= 31) - -> Seq Scan on rlp3nullxy rlp_5 - Filter: (a <= 31) - -> Seq Scan on rlp3_default rlp_6 - Filter: (a <= 31) - -> Seq Scan on rlp4_1 rlp_7 - Filter: (a <= 31) - -> Seq Scan on rlp4_2 rlp_8 - Filter: (a <= 31) - -> Seq Scan on rlp4_default rlp_9 - Filter: (a <= 31) - -> Seq Scan on rlp5_1 rlp_10 - Filter: (a <= 31) - -> Seq Scan on rlp_default_10 rlp_11 - Filter: (a <= 31) - -> Seq Scan on rlp_default_30 rlp_12 - Filter: (a <= 31) - -> Seq Scan on rlp_default_default rlp_13 - Filter: (a <= 31) -(27 rows) - -explain (costs off) select * from rlp where a = 1 or a = 7; - QUERY PLAN --------------------------------- - Seq Scan on rlp2 rlp - Filter: ((a = 1) OR (a = 7)) -(2 rows) - -explain (costs off) select * from rlp where a = 1 or b = 'ab'; - QUERY PLAN -------------------------------------------------------- - Append - -> Seq Scan on rlp1 rlp_1 - Filter: ((a = 1) OR ((b)::text = 'ab'::text)) - -> Seq Scan on rlp2 rlp_2 - Filter: ((a = 1) OR ((b)::text = 'ab'::text)) - -> Seq Scan on rlp3abcd rlp_3 - Filter: ((a = 1) OR ((b)::text = 'ab'::text)) - -> Seq Scan on rlp4_1 rlp_4 - Filter: ((a = 1) OR ((b)::text = 'ab'::text)) - -> Seq Scan on rlp4_2 rlp_5 - Filter: ((a = 1) OR ((b)::text = 'ab'::text)) - -> Seq Scan on rlp4_default rlp_6 - Filter: ((a = 1) OR ((b)::text = 'ab'::text)) - -> Seq Scan on rlp5_1 rlp_7 - Filter: ((a = 1) OR ((b)::text = 'ab'::text)) - -> Seq Scan on rlp5_default rlp_8 - Filter: ((a = 1) OR ((b)::text = 'ab'::text)) - -> Seq Scan on rlp_default_10 rlp_9 - Filter: ((a = 1) OR ((b)::text = 'ab'::text)) - -> Seq Scan on rlp_default_30 rlp_10 - Filter: ((a = 1) OR ((b)::text = 'ab'::text)) - -> Seq Scan on rlp_default_null rlp_11 - Filter: ((a = 1) OR ((b)::text = 'ab'::text)) - -> Seq Scan on rlp_default_default rlp_12 - Filter: ((a = 1) OR ((b)::text = 'ab'::text)) -(25 rows) - -explain (costs off) select * from rlp where a > 20 and a < 27; - QUERY PLAN ------------------------------------------ - Append - -> Seq Scan on rlp4_1 rlp_1 - Filter: ((a > 20) AND (a < 27)) - -> Seq Scan on rlp4_2 rlp_2 - Filter: ((a > 20) AND (a < 27)) -(5 rows) - -explain (costs off) select * from rlp where a = 29; - QUERY PLAN ------------------------------- - Seq Scan on rlp4_default rlp - Filter: (a = 29) -(2 rows) - -explain (costs off) select * from rlp where a >= 29; - QUERY PLAN ---------------------------------------------- - Append - -> Seq Scan on rlp4_default rlp_1 - Filter: (a >= 29) - -> Seq Scan on rlp5_1 rlp_2 - Filter: (a >= 29) - -> Seq Scan on rlp5_default rlp_3 - Filter: (a >= 29) - -> Seq Scan on rlp_default_30 rlp_4 - Filter: (a >= 29) - -> Seq Scan on rlp_default_default rlp_5 - Filter: (a >= 29) -(11 rows) - -explain (costs off) select * from rlp where a < 1 or (a > 20 and a < 25); - QUERY PLAN ------------------------------------------------------- - Append - -> Seq Scan on rlp1 rlp_1 - Filter: ((a < 1) OR ((a > 20) AND (a < 25))) - -> Seq Scan on rlp4_1 rlp_2 - Filter: ((a < 1) OR ((a > 20) AND (a < 25))) -(5 rows) - --- where clause contradicts sub-partition's constraint -explain (costs off) select * from rlp where a = 20 or a = 40; - QUERY PLAN ----------------------------------------- - Append - -> Seq Scan on rlp4_1 rlp_1 - Filter: ((a = 20) OR (a = 40)) - -> Seq Scan on rlp5_default rlp_2 - Filter: ((a = 20) OR (a = 40)) -(5 rows) - -explain (costs off) select * from rlp3 where a = 20; /* empty */ - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - --- redundant clauses are eliminated -explain (costs off) select * from rlp where a > 1 and a = 10; /* only default */ - QUERY PLAN ----------------------------------- - Seq Scan on rlp_default_10 rlp - Filter: ((a > 1) AND (a = 10)) -(2 rows) - -explain (costs off) select * from rlp where a > 1 and a >=15; /* rlp3 onwards, including default */ - QUERY PLAN ----------------------------------------------- - Append - -> Seq Scan on rlp3abcd rlp_1 - Filter: ((a > 1) AND (a >= 15)) - -> Seq Scan on rlp3efgh rlp_2 - Filter: ((a > 1) AND (a >= 15)) - -> Seq Scan on rlp3nullxy rlp_3 - Filter: ((a > 1) AND (a >= 15)) - -> Seq Scan on rlp3_default rlp_4 - Filter: ((a > 1) AND (a >= 15)) - -> Seq Scan on rlp4_1 rlp_5 - Filter: ((a > 1) AND (a >= 15)) - -> Seq Scan on rlp4_2 rlp_6 - Filter: ((a > 1) AND (a >= 15)) - -> Seq Scan on rlp4_default rlp_7 - Filter: ((a > 1) AND (a >= 15)) - -> Seq Scan on rlp5_1 rlp_8 - Filter: ((a > 1) AND (a >= 15)) - -> Seq Scan on rlp5_default rlp_9 - Filter: ((a > 1) AND (a >= 15)) - -> Seq Scan on rlp_default_30 rlp_10 - Filter: ((a > 1) AND (a >= 15)) - -> Seq Scan on rlp_default_default rlp_11 - Filter: ((a > 1) AND (a >= 15)) -(23 rows) - -explain (costs off) select * from rlp where a = 1 and a = 3; /* empty */ - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -explain (costs off) select * from rlp where (a = 1 and a = 3) or (a > 1 and a = 15); - QUERY PLAN -------------------------------------------------------------------- - Append - -> Seq Scan on rlp2 rlp_1 - Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) - -> Seq Scan on rlp3abcd rlp_2 - Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) - -> Seq Scan on rlp3efgh rlp_3 - Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) - -> Seq Scan on rlp3nullxy rlp_4 - Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) - -> Seq Scan on rlp3_default rlp_5 - Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) -(11 rows) - --- multi-column keys -create table mc3p (a int, b int, c int) partition by range (a, abs(b), c); -create table mc3p_default partition of mc3p default; -create table mc3p0 partition of mc3p for values from (minvalue, minvalue, minvalue) to (1, 1, 1); -create table mc3p1 partition of mc3p for values from (1, 1, 1) to (10, 5, 10); -create table mc3p2 partition of mc3p for values from (10, 5, 10) to (10, 10, 10); -create table mc3p3 partition of mc3p for values from (10, 10, 10) to (10, 10, 20); -create table mc3p4 partition of mc3p for values from (10, 10, 20) to (10, maxvalue, maxvalue); -create table mc3p5 partition of mc3p for values from (11, 1, 1) to (20, 10, 10); -create table mc3p6 partition of mc3p for values from (20, 10, 10) to (20, 20, 20); -create table mc3p7 partition of mc3p for values from (20, 20, 20) to (maxvalue, maxvalue, maxvalue); -explain (costs off) select * from mc3p where a = 1; - QUERY PLAN ---------------------------------------- - Append - -> Seq Scan on mc3p0 mc3p_1 - Filter: (a = 1) - -> Seq Scan on mc3p1 mc3p_2 - Filter: (a = 1) - -> Seq Scan on mc3p_default mc3p_3 - Filter: (a = 1) -(7 rows) - -explain (costs off) select * from mc3p where a = 1 and abs(b) < 1; - QUERY PLAN --------------------------------------------- - Append - -> Seq Scan on mc3p0 mc3p_1 - Filter: ((a = 1) AND (abs(b) < 1)) - -> Seq Scan on mc3p_default mc3p_2 - Filter: ((a = 1) AND (abs(b) < 1)) -(5 rows) - -explain (costs off) select * from mc3p where a = 1 and abs(b) = 1; - QUERY PLAN --------------------------------------------- - Append - -> Seq Scan on mc3p0 mc3p_1 - Filter: ((a = 1) AND (abs(b) = 1)) - -> Seq Scan on mc3p1 mc3p_2 - Filter: ((a = 1) AND (abs(b) = 1)) - -> Seq Scan on mc3p_default mc3p_3 - Filter: ((a = 1) AND (abs(b) = 1)) -(7 rows) - -explain (costs off) select * from mc3p where a = 1 and abs(b) = 1 and c < 8; - QUERY PLAN --------------------------------------------------------- - Append - -> Seq Scan on mc3p0 mc3p_1 - Filter: ((c < 8) AND (a = 1) AND (abs(b) = 1)) - -> Seq Scan on mc3p1 mc3p_2 - Filter: ((c < 8) AND (a = 1) AND (abs(b) = 1)) -(5 rows) - -explain (costs off) select * from mc3p where a = 10 and abs(b) between 5 and 35; - QUERY PLAN ------------------------------------------------------------------ - Append - -> Seq Scan on mc3p1 mc3p_1 - Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35)) - -> Seq Scan on mc3p2 mc3p_2 - Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35)) - -> Seq Scan on mc3p3 mc3p_3 - Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35)) - -> Seq Scan on mc3p4 mc3p_4 - Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35)) - -> Seq Scan on mc3p_default mc3p_5 - Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35)) -(11 rows) - -explain (costs off) select * from mc3p where a > 10; - QUERY PLAN ---------------------------------------- - Append - -> Seq Scan on mc3p5 mc3p_1 - Filter: (a > 10) - -> Seq Scan on mc3p6 mc3p_2 - Filter: (a > 10) - -> Seq Scan on mc3p7 mc3p_3 - Filter: (a > 10) - -> Seq Scan on mc3p_default mc3p_4 - Filter: (a > 10) -(9 rows) - -explain (costs off) select * from mc3p where a >= 10; - QUERY PLAN ---------------------------------------- - Append - -> Seq Scan on mc3p1 mc3p_1 - Filter: (a >= 10) - -> Seq Scan on mc3p2 mc3p_2 - Filter: (a >= 10) - -> Seq Scan on mc3p3 mc3p_3 - Filter: (a >= 10) - -> Seq Scan on mc3p4 mc3p_4 - Filter: (a >= 10) - -> Seq Scan on mc3p5 mc3p_5 - Filter: (a >= 10) - -> Seq Scan on mc3p6 mc3p_6 - Filter: (a >= 10) - -> Seq Scan on mc3p7 mc3p_7 - Filter: (a >= 10) - -> Seq Scan on mc3p_default mc3p_8 - Filter: (a >= 10) -(17 rows) - -explain (costs off) select * from mc3p where a < 10; - QUERY PLAN ---------------------------------------- - Append - -> Seq Scan on mc3p0 mc3p_1 - Filter: (a < 10) - -> Seq Scan on mc3p1 mc3p_2 - Filter: (a < 10) - -> Seq Scan on mc3p_default mc3p_3 - Filter: (a < 10) -(7 rows) - -explain (costs off) select * from mc3p where a <= 10 and abs(b) < 10; - QUERY PLAN ------------------------------------------------ - Append - -> Seq Scan on mc3p0 mc3p_1 - Filter: ((a <= 10) AND (abs(b) < 10)) - -> Seq Scan on mc3p1 mc3p_2 - Filter: ((a <= 10) AND (abs(b) < 10)) - -> Seq Scan on mc3p2 mc3p_3 - Filter: ((a <= 10) AND (abs(b) < 10)) - -> Seq Scan on mc3p_default mc3p_4 - Filter: ((a <= 10) AND (abs(b) < 10)) -(9 rows) - -explain (costs off) select * from mc3p where a = 11 and abs(b) = 0; - QUERY PLAN ---------------------------------------- - Seq Scan on mc3p_default mc3p - Filter: ((a = 11) AND (abs(b) = 0)) -(2 rows) - -explain (costs off) select * from mc3p where a = 20 and abs(b) = 10 and c = 100; - QUERY PLAN ------------------------------------------------------- - Seq Scan on mc3p6 mc3p - Filter: ((a = 20) AND (c = 100) AND (abs(b) = 10)) -(2 rows) - -explain (costs off) select * from mc3p where a > 20; - QUERY PLAN ---------------------------------------- - Append - -> Seq Scan on mc3p7 mc3p_1 - Filter: (a > 20) - -> Seq Scan on mc3p_default mc3p_2 - Filter: (a > 20) -(5 rows) - -explain (costs off) select * from mc3p where a >= 20; - QUERY PLAN ---------------------------------------- - Append - -> Seq Scan on mc3p5 mc3p_1 - Filter: (a >= 20) - -> Seq Scan on mc3p6 mc3p_2 - Filter: (a >= 20) - -> Seq Scan on mc3p7 mc3p_3 - Filter: (a >= 20) - -> Seq Scan on mc3p_default mc3p_4 - Filter: (a >= 20) -(9 rows) - -explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1 and c = 1) or (a = 10 and abs(b) = 5 and c = 10) or (a > 11 and a < 20); - QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------- - Append - -> Seq Scan on mc3p1 mc3p_1 - Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20))) - -> Seq Scan on mc3p2 mc3p_2 - Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20))) - -> Seq Scan on mc3p5 mc3p_3 - Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20))) - -> Seq Scan on mc3p_default mc3p_4 - Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20))) -(9 rows) - -explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1 and c = 1) or (a = 10 and abs(b) = 5 and c = 10) or (a > 11 and a < 20) or a < 1; - QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------- - Append - -> Seq Scan on mc3p0 mc3p_1 - Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1)) - -> Seq Scan on mc3p1 mc3p_2 - Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1)) - -> Seq Scan on mc3p2 mc3p_3 - Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1)) - -> Seq Scan on mc3p5 mc3p_4 - Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1)) - -> Seq Scan on mc3p_default mc3p_5 - Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1)) -(11 rows) - -explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1 and c = 1) or (a = 10 and abs(b) = 5 and c = 10) or (a > 11 and a < 20) or a < 1 or a = 1; - QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------- - Append - -> Seq Scan on mc3p0 mc3p_1 - Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1) OR (a = 1)) - -> Seq Scan on mc3p1 mc3p_2 - Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1) OR (a = 1)) - -> Seq Scan on mc3p2 mc3p_3 - Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1) OR (a = 1)) - -> Seq Scan on mc3p5 mc3p_4 - Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1) OR (a = 1)) - -> Seq Scan on mc3p_default mc3p_5 - Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1) OR (a = 1)) -(11 rows) - -explain (costs off) select * from mc3p where a = 1 or abs(b) = 1 or c = 1; - QUERY PLAN ------------------------------------------------------- - Append - -> Seq Scan on mc3p0 mc3p_1 - Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) - -> Seq Scan on mc3p1 mc3p_2 - Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) - -> Seq Scan on mc3p2 mc3p_3 - Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) - -> Seq Scan on mc3p3 mc3p_4 - Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) - -> Seq Scan on mc3p4 mc3p_5 - Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) - -> Seq Scan on mc3p5 mc3p_6 - Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) - -> Seq Scan on mc3p6 mc3p_7 - Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) - -> Seq Scan on mc3p7 mc3p_8 - Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) - -> Seq Scan on mc3p_default mc3p_9 - Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) -(19 rows) - -explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1) or (a = 10 and abs(b) = 10); - QUERY PLAN ------------------------------------------------------------------------------- - Append - -> Seq Scan on mc3p0 mc3p_1 - Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10))) - -> Seq Scan on mc3p1 mc3p_2 - Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10))) - -> Seq Scan on mc3p2 mc3p_3 - Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10))) - -> Seq Scan on mc3p3 mc3p_4 - Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10))) - -> Seq Scan on mc3p4 mc3p_5 - Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10))) - -> Seq Scan on mc3p_default mc3p_6 - Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10))) -(13 rows) - -explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1) or (a = 10 and abs(b) = 9); - QUERY PLAN ------------------------------------------------------------------------------ - Append - -> Seq Scan on mc3p0 mc3p_1 - Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 9))) - -> Seq Scan on mc3p1 mc3p_2 - Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 9))) - -> Seq Scan on mc3p2 mc3p_3 - Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 9))) - -> Seq Scan on mc3p_default mc3p_4 - Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 9))) -(9 rows) - --- a simpler multi-column keys case -create table mc2p (a int, b int) partition by range (a, b); -create table mc2p_default partition of mc2p default; -create table mc2p0 partition of mc2p for values from (minvalue, minvalue) to (1, minvalue); -create table mc2p1 partition of mc2p for values from (1, minvalue) to (1, 1); -create table mc2p2 partition of mc2p for values from (1, 1) to (2, minvalue); -create table mc2p3 partition of mc2p for values from (2, minvalue) to (2, 1); -create table mc2p4 partition of mc2p for values from (2, 1) to (2, maxvalue); -create table mc2p5 partition of mc2p for values from (2, maxvalue) to (maxvalue, maxvalue); -explain (costs off) select * from mc2p where a < 2; - QUERY PLAN ---------------------------------------- - Append - -> Seq Scan on mc2p0 mc2p_1 - Filter: (a < 2) - -> Seq Scan on mc2p1 mc2p_2 - Filter: (a < 2) - -> Seq Scan on mc2p2 mc2p_3 - Filter: (a < 2) - -> Seq Scan on mc2p_default mc2p_4 - Filter: (a < 2) -(9 rows) - -explain (costs off) select * from mc2p where a = 2 and b < 1; - QUERY PLAN ---------------------------------- - Seq Scan on mc2p3 mc2p - Filter: ((b < 1) AND (a = 2)) -(2 rows) - -explain (costs off) select * from mc2p where a > 1; - QUERY PLAN ---------------------------------------- - Append - -> Seq Scan on mc2p2 mc2p_1 - Filter: (a > 1) - -> Seq Scan on mc2p3 mc2p_2 - Filter: (a > 1) - -> Seq Scan on mc2p4 mc2p_3 - Filter: (a > 1) - -> Seq Scan on mc2p5 mc2p_4 - Filter: (a > 1) - -> Seq Scan on mc2p_default mc2p_5 - Filter: (a > 1) -(11 rows) - -explain (costs off) select * from mc2p where a = 1 and b > 1; - QUERY PLAN ---------------------------------- - Seq Scan on mc2p2 mc2p - Filter: ((b > 1) AND (a = 1)) -(2 rows) - --- all partitions but the default one should be pruned -explain (costs off) select * from mc2p where a = 1 and b is null; - QUERY PLAN -------------------------------------- - Seq Scan on mc2p_default mc2p - Filter: ((b IS NULL) AND (a = 1)) -(2 rows) - -explain (costs off) select * from mc2p where a is null and b is null; - QUERY PLAN ------------------------------------------ - Seq Scan on mc2p_default mc2p - Filter: ((a IS NULL) AND (b IS NULL)) -(2 rows) - -explain (costs off) select * from mc2p where a is null and b = 1; - QUERY PLAN -------------------------------------- - Seq Scan on mc2p_default mc2p - Filter: ((a IS NULL) AND (b = 1)) -(2 rows) - -explain (costs off) select * from mc2p where a is null; - QUERY PLAN -------------------------------- - Seq Scan on mc2p_default mc2p - Filter: (a IS NULL) -(2 rows) - -explain (costs off) select * from mc2p where b is null; - QUERY PLAN -------------------------------- - Seq Scan on mc2p_default mc2p - Filter: (b IS NULL) -(2 rows) - --- boolean partitioning -create table boolpart (a bool) partition by list (a); -create table boolpart_default partition of boolpart default; -create table boolpart_t partition of boolpart for values in ('true'); -create table boolpart_f partition of boolpart for values in ('false'); -insert into boolpart values (true), (false), (null); -explain (costs off) select * from boolpart where a in (true, false); - QUERY PLAN ------------------------------------------------- - Append - -> Seq Scan on boolpart_f boolpart_1 - Filter: (a = ANY ('{t,f}'::boolean[])) - -> Seq Scan on boolpart_t boolpart_2 - Filter: (a = ANY ('{t,f}'::boolean[])) -(5 rows) - -explain (costs off) select * from boolpart where a = false; - QUERY PLAN ---------------------------------- - Seq Scan on boolpart_f boolpart - Filter: (NOT a) -(2 rows) - -explain (costs off) select * from boolpart where not a = false; - QUERY PLAN ---------------------------------- - Seq Scan on boolpart_t boolpart - Filter: a -(2 rows) - -explain (costs off) select * from boolpart where a is true or a is not true; - QUERY PLAN --------------------------------------------------- - Append - -> Seq Scan on boolpart_f boolpart_1 - Filter: ((a IS TRUE) OR (a IS NOT TRUE)) - -> Seq Scan on boolpart_t boolpart_2 - Filter: ((a IS TRUE) OR (a IS NOT TRUE)) - -> Seq Scan on boolpart_default boolpart_3 - Filter: ((a IS TRUE) OR (a IS NOT TRUE)) -(7 rows) - -explain (costs off) select * from boolpart where a is not true; - QUERY PLAN ------------------------------------------------ - Append - -> Seq Scan on boolpart_f boolpart_1 - Filter: (a IS NOT TRUE) - -> Seq Scan on boolpart_default boolpart_2 - Filter: (a IS NOT TRUE) -(5 rows) - -explain (costs off) select * from boolpart where a is not true and a is not false; - QUERY PLAN --------------------------------------------------- - Seq Scan on boolpart_default boolpart - Filter: ((a IS NOT TRUE) AND (a IS NOT FALSE)) -(2 rows) - -explain (costs off) select * from boolpart where a is unknown; - QUERY PLAN ---------------------------------------- - Seq Scan on boolpart_default boolpart - Filter: (a IS UNKNOWN) -(2 rows) - -explain (costs off) select * from boolpart where a is not unknown; - QUERY PLAN ------------------------------------------------ - Append - -> Seq Scan on boolpart_f boolpart_1 - Filter: (a IS NOT UNKNOWN) - -> Seq Scan on boolpart_t boolpart_2 - Filter: (a IS NOT UNKNOWN) - -> Seq Scan on boolpart_default boolpart_3 - Filter: (a IS NOT UNKNOWN) -(7 rows) - -select * from boolpart where a in (true, false); - a ---- - f - t -(2 rows) - -select * from boolpart where a = false; - a ---- - f -(1 row) - -select * from boolpart where not a = false; - a ---- - t -(1 row) - -select * from boolpart where a is true or a is not true; - a ---- - f - t - -(3 rows) - -select * from boolpart where a is not true; - a ---- - f - -(2 rows) - -select * from boolpart where a is not true and a is not false; - a ---- - -(1 row) - -select * from boolpart where a is unknown; - a ---- - -(1 row) - -select * from boolpart where a is not unknown; - a ---- - f - t -(2 rows) - --- try some other permutations with a NULL partition instead of a DEFAULT -delete from boolpart where a is null; -create table boolpart_null partition of boolpart for values in (null); -insert into boolpart values(null); -explain (costs off) select * from boolpart where a is not true; - QUERY PLAN --------------------------------------------- - Append - -> Seq Scan on boolpart_f boolpart_1 - Filter: (a IS NOT TRUE) - -> Seq Scan on boolpart_null boolpart_2 - Filter: (a IS NOT TRUE) -(5 rows) - -explain (costs off) select * from boolpart where a is not true and a is not false; - QUERY PLAN --------------------------------------------------- - Seq Scan on boolpart_null boolpart - Filter: ((a IS NOT TRUE) AND (a IS NOT FALSE)) -(2 rows) - -explain (costs off) select * from boolpart where a is not false; - QUERY PLAN --------------------------------------------- - Append - -> Seq Scan on boolpart_t boolpart_1 - Filter: (a IS NOT FALSE) - -> Seq Scan on boolpart_null boolpart_2 - Filter: (a IS NOT FALSE) -(5 rows) - -explain (costs off) select * from boolpart where a is not unknown; - QUERY PLAN ------------------------------------------------ - Append - -> Seq Scan on boolpart_f boolpart_1 - Filter: (a IS NOT UNKNOWN) - -> Seq Scan on boolpart_t boolpart_2 - Filter: (a IS NOT UNKNOWN) - -> Seq Scan on boolpart_default boolpart_3 - Filter: (a IS NOT UNKNOWN) -(7 rows) - -select * from boolpart where a is not true; - a ---- - f - -(2 rows) - -select * from boolpart where a is not true and a is not false; - a ---- - -(1 row) - -select * from boolpart where a is not false; - a ---- - t - -(2 rows) - -select * from boolpart where a is not unknown; - a ---- - f - t -(2 rows) - --- check that all partitions are pruned when faced with conflicting clauses -explain (costs off) select * from boolpart where a is not unknown and a is unknown; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -explain (costs off) select * from boolpart where a is false and a is unknown; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -explain (costs off) select * from boolpart where a is true and a is unknown; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - --- inverse boolean partitioning - a seemingly unlikely design, but we've got --- code for it, so we'd better test it. -create table iboolpart (a bool) partition by list ((not a)); -create table iboolpart_default partition of iboolpart default; -create table iboolpart_f partition of iboolpart for values in ('true'); -create table iboolpart_t partition of iboolpart for values in ('false'); -insert into iboolpart values (true), (false), (null); -explain (costs off) select * from iboolpart where a in (true, false); - QUERY PLAN -------------------------------------------------- - Append - -> Seq Scan on iboolpart_t iboolpart_1 - Filter: (a = ANY ('{t,f}'::boolean[])) - -> Seq Scan on iboolpart_f iboolpart_2 - Filter: (a = ANY ('{t,f}'::boolean[])) - -> Seq Scan on iboolpart_default iboolpart_3 - Filter: (a = ANY ('{t,f}'::boolean[])) -(7 rows) - -explain (costs off) select * from iboolpart where a = false; - QUERY PLAN ------------------------------------ - Seq Scan on iboolpart_f iboolpart - Filter: (NOT a) -(2 rows) - -explain (costs off) select * from iboolpart where not a = false; - QUERY PLAN ------------------------------------ - Seq Scan on iboolpart_t iboolpart - Filter: a -(2 rows) - -explain (costs off) select * from iboolpart where a is true or a is not true; - QUERY PLAN --------------------------------------------------- - Append - -> Seq Scan on iboolpart_t iboolpart_1 - Filter: ((a IS TRUE) OR (a IS NOT TRUE)) - -> Seq Scan on iboolpart_f iboolpart_2 - Filter: ((a IS TRUE) OR (a IS NOT TRUE)) - -> Seq Scan on iboolpart_default iboolpart_3 - Filter: ((a IS TRUE) OR (a IS NOT TRUE)) -(7 rows) - -explain (costs off) select * from iboolpart where a is not true; - QUERY PLAN -------------------------------------------------- - Append - -> Seq Scan on iboolpart_t iboolpart_1 - Filter: (a IS NOT TRUE) - -> Seq Scan on iboolpart_f iboolpart_2 - Filter: (a IS NOT TRUE) - -> Seq Scan on iboolpart_default iboolpart_3 - Filter: (a IS NOT TRUE) -(7 rows) - -explain (costs off) select * from iboolpart where a is not true and a is not false; - QUERY PLAN --------------------------------------------------------- - Append - -> Seq Scan on iboolpart_t iboolpart_1 - Filter: ((a IS NOT TRUE) AND (a IS NOT FALSE)) - -> Seq Scan on iboolpart_f iboolpart_2 - Filter: ((a IS NOT TRUE) AND (a IS NOT FALSE)) - -> Seq Scan on iboolpart_default iboolpart_3 - Filter: ((a IS NOT TRUE) AND (a IS NOT FALSE)) -(7 rows) - -explain (costs off) select * from iboolpart where a is unknown; - QUERY PLAN -------------------------------------------------- - Append - -> Seq Scan on iboolpart_t iboolpart_1 - Filter: (a IS UNKNOWN) - -> Seq Scan on iboolpart_f iboolpart_2 - Filter: (a IS UNKNOWN) - -> Seq Scan on iboolpart_default iboolpart_3 - Filter: (a IS UNKNOWN) -(7 rows) - -explain (costs off) select * from iboolpart where a is not unknown; - QUERY PLAN -------------------------------------------------- - Append - -> Seq Scan on iboolpart_t iboolpart_1 - Filter: (a IS NOT UNKNOWN) - -> Seq Scan on iboolpart_f iboolpart_2 - Filter: (a IS NOT UNKNOWN) - -> Seq Scan on iboolpart_default iboolpart_3 - Filter: (a IS NOT UNKNOWN) -(7 rows) - -select * from iboolpart where a in (true, false); - a ---- - t - f -(2 rows) - -select * from iboolpart where a = false; - a ---- - f -(1 row) - -select * from iboolpart where not a = false; - a ---- - t -(1 row) - -select * from iboolpart where a is true or a is not true; - a ---- - t - f - -(3 rows) - -select * from iboolpart where a is not true; - a ---- - f - -(2 rows) - -select * from iboolpart where a is not true and a is not false; - a ---- - -(1 row) - -select * from iboolpart where a is unknown; - a ---- - -(1 row) - -select * from iboolpart where a is not unknown; - a ---- - t - f -(2 rows) - --- Try some other permutations with a NULL partition instead of a DEFAULT -delete from iboolpart where a is null; -create table iboolpart_null partition of iboolpart for values in (null); -insert into iboolpart values(null); --- Pruning shouldn't take place for these. Just check the result is correct -select * from iboolpart where a is not true; - a ---- - f - -(2 rows) - -select * from iboolpart where a is not true and a is not false; - a ---- - -(1 row) - -select * from iboolpart where a is not false; - a ---- - t - -(2 rows) - -create table boolrangep (a bool, b bool, c int) partition by range (a,b,c); -create table boolrangep_tf partition of boolrangep for values from ('true', 'false', 0) to ('true', 'false', 100); -create table boolrangep_ft partition of boolrangep for values from ('false', 'true', 0) to ('false', 'true', 100); -create table boolrangep_ff1 partition of boolrangep for values from ('false', 'false', 0) to ('false', 'false', 50); -create table boolrangep_ff2 partition of boolrangep for values from ('false', 'false', 50) to ('false', 'false', 100); -create table boolrangep_null partition of boolrangep default; --- try a more complex case that's been known to trip up pruning in the past -explain (costs off) select * from boolrangep where not a and not b and c = 25; - QUERY PLAN ----------------------------------------------- - Seq Scan on boolrangep_ff1 boolrangep - Filter: ((NOT a) AND (NOT b) AND (c = 25)) -(2 rows) - --- ensure we prune boolrangep_tf -explain (costs off) select * from boolrangep where a is not true and not b and c = 25; - QUERY PLAN ------------------------------------------------------------- - Append - -> Seq Scan on boolrangep_ff1 boolrangep_1 - Filter: ((a IS NOT TRUE) AND (NOT b) AND (c = 25)) - -> Seq Scan on boolrangep_ff2 boolrangep_2 - Filter: ((a IS NOT TRUE) AND (NOT b) AND (c = 25)) - -> Seq Scan on boolrangep_ft boolrangep_3 - Filter: ((a IS NOT TRUE) AND (NOT b) AND (c = 25)) - -> Seq Scan on boolrangep_null boolrangep_4 - Filter: ((a IS NOT TRUE) AND (NOT b) AND (c = 25)) -(9 rows) - --- ensure we prune everything apart from boolrangep_tf and boolrangep_null -explain (costs off) select * from boolrangep where a is not false and not b and c = 25; - QUERY PLAN -------------------------------------------------------------- - Append - -> Seq Scan on boolrangep_tf boolrangep_1 - Filter: ((a IS NOT FALSE) AND (NOT b) AND (c = 25)) - -> Seq Scan on boolrangep_null boolrangep_2 - Filter: ((a IS NOT FALSE) AND (NOT b) AND (c = 25)) -(5 rows) - --- test scalar-to-array operators -create table coercepart (a varchar) partition by list (a); -create table coercepart_ab partition of coercepart for values in ('ab'); -create table coercepart_bc partition of coercepart for values in ('bc'); -create table coercepart_cd partition of coercepart for values in ('cd'); -explain (costs off) select * from coercepart where a in ('ab', to_char(125, '999')); - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------- - Append - -> Seq Scan on coercepart_ab coercepart_1 - Filter: ((a)::text = ANY ((ARRAY['ab'::character varying, (to_char(125, '999'::text))::character varying])::text[])) - -> Seq Scan on coercepart_bc coercepart_2 - Filter: ((a)::text = ANY ((ARRAY['ab'::character varying, (to_char(125, '999'::text))::character varying])::text[])) - -> Seq Scan on coercepart_cd coercepart_3 - Filter: ((a)::text = ANY ((ARRAY['ab'::character varying, (to_char(125, '999'::text))::character varying])::text[])) -(7 rows) - -explain (costs off) select * from coercepart where a ~ any ('{ab}'); - QUERY PLAN ----------------------------------------------------- - Append - -> Seq Scan on coercepart_ab coercepart_1 - Filter: ((a)::text ~ ANY ('{ab}'::text[])) - -> Seq Scan on coercepart_bc coercepart_2 - Filter: ((a)::text ~ ANY ('{ab}'::text[])) - -> Seq Scan on coercepart_cd coercepart_3 - Filter: ((a)::text ~ ANY ('{ab}'::text[])) -(7 rows) - -explain (costs off) select * from coercepart where a !~ all ('{ab}'); - QUERY PLAN ------------------------------------------------------ - Append - -> Seq Scan on coercepart_ab coercepart_1 - Filter: ((a)::text !~ ALL ('{ab}'::text[])) - -> Seq Scan on coercepart_bc coercepart_2 - Filter: ((a)::text !~ ALL ('{ab}'::text[])) - -> Seq Scan on coercepart_cd coercepart_3 - Filter: ((a)::text !~ ALL ('{ab}'::text[])) -(7 rows) - -explain (costs off) select * from coercepart where a ~ any ('{ab,bc}'); - QUERY PLAN -------------------------------------------------------- - Append - -> Seq Scan on coercepart_ab coercepart_1 - Filter: ((a)::text ~ ANY ('{ab,bc}'::text[])) - -> Seq Scan on coercepart_bc coercepart_2 - Filter: ((a)::text ~ ANY ('{ab,bc}'::text[])) - -> Seq Scan on coercepart_cd coercepart_3 - Filter: ((a)::text ~ ANY ('{ab,bc}'::text[])) -(7 rows) - -explain (costs off) select * from coercepart where a !~ all ('{ab,bc}'); - QUERY PLAN --------------------------------------------------------- - Append - -> Seq Scan on coercepart_ab coercepart_1 - Filter: ((a)::text !~ ALL ('{ab,bc}'::text[])) - -> Seq Scan on coercepart_bc coercepart_2 - Filter: ((a)::text !~ ALL ('{ab,bc}'::text[])) - -> Seq Scan on coercepart_cd coercepart_3 - Filter: ((a)::text !~ ALL ('{ab,bc}'::text[])) -(7 rows) - -explain (costs off) select * from coercepart where a = any ('{ab,bc}'); - QUERY PLAN -------------------------------------------------------- - Append - -> Seq Scan on coercepart_ab coercepart_1 - Filter: ((a)::text = ANY ('{ab,bc}'::text[])) - -> Seq Scan on coercepart_bc coercepart_2 - Filter: ((a)::text = ANY ('{ab,bc}'::text[])) -(5 rows) - -explain (costs off) select * from coercepart where a = any ('{ab,null}'); - QUERY PLAN ---------------------------------------------------- - Seq Scan on coercepart_ab coercepart - Filter: ((a)::text = ANY ('{ab,NULL}'::text[])) -(2 rows) - -explain (costs off) select * from coercepart where a = any (null::text[]); - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -explain (costs off) select * from coercepart where a = all ('{ab}'); - QUERY PLAN ----------------------------------------------- - Seq Scan on coercepart_ab coercepart - Filter: ((a)::text = ALL ('{ab}'::text[])) -(2 rows) - -explain (costs off) select * from coercepart where a = all ('{ab,bc}'); - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -explain (costs off) select * from coercepart where a = all ('{ab,null}'); - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -explain (costs off) select * from coercepart where a = all (null::text[]); - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -drop table coercepart; -CREATE TABLE part (a INT, b INT) PARTITION BY LIST (a); -CREATE TABLE part_p1 PARTITION OF part FOR VALUES IN (-2,-1,0,1,2); -CREATE TABLE part_p2 PARTITION OF part DEFAULT PARTITION BY RANGE(a); -CREATE TABLE part_p2_p1 PARTITION OF part_p2 DEFAULT; -CREATE TABLE part_rev (b INT, c INT, a INT); -ALTER TABLE part ATTACH PARTITION part_rev FOR VALUES IN (3); -- fail -ERROR: table "part_rev" contains column "c" not found in parent "part" -DETAIL: The new partition may contain only the columns present in parent. -ALTER TABLE part_rev DROP COLUMN c; -ALTER TABLE part ATTACH PARTITION part_rev FOR VALUES IN (3); -- now it's ok -INSERT INTO part VALUES (-1,-1), (1,1), (2,NULL), (NULL,-2),(NULL,NULL); -EXPLAIN (COSTS OFF) SELECT tableoid::regclass as part, a, b FROM part WHERE a IS NULL ORDER BY 1, 2, 3; - QUERY PLAN ---------------------------------------------------------- - Sort - Sort Key: ((part.tableoid)::regclass), part.a, part.b - -> Seq Scan on part_p2_p1 part - Filter: (a IS NULL) -(4 rows) - -EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM part p(x) ORDER BY x; - QUERY PLAN ------------------------------------------------ - Sort - Output: p.x, p.b - Sort Key: p.x - -> Append - -> Seq Scan on public.part_p1 p_1 - Output: p_1.x, p_1.b - -> Seq Scan on public.part_rev p_2 - Output: p_2.x, p_2.b - -> Seq Scan on public.part_p2_p1 p_3 - Output: p_3.x, p_3.b -(10 rows) - --- --- some more cases --- --- --- pruning for partitioned table appearing inside a sub-query --- --- pruning won't work for mc3p, because some keys are Params -explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.a = t1.b and abs(t2.b) = 1 and t2.c = 1) s where t1.a = 1; - QUERY PLAN ------------------------------------------------------------------------ - Nested Loop - -> Append - -> Seq Scan on mc2p1 t1_1 - Filter: (a = 1) - -> Seq Scan on mc2p2 t1_2 - Filter: (a = 1) - -> Seq Scan on mc2p_default t1_3 - Filter: (a = 1) - -> Aggregate - -> Append - -> Seq Scan on mc3p0 t2_1 - Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) - -> Seq Scan on mc3p1 t2_2 - Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) - -> Seq Scan on mc3p2 t2_3 - Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) - -> Seq Scan on mc3p3 t2_4 - Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) - -> Seq Scan on mc3p4 t2_5 - Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) - -> Seq Scan on mc3p5 t2_6 - Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) - -> Seq Scan on mc3p6 t2_7 - Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) - -> Seq Scan on mc3p7 t2_8 - Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) - -> Seq Scan on mc3p_default t2_9 - Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) -(28 rows) - --- pruning should work fine, because values for a prefix of keys (a, b) are --- available -explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.c = t1.b and abs(t2.b) = 1 and t2.a = 1) s where t1.a = 1; - QUERY PLAN ------------------------------------------------------------------------ - Nested Loop - -> Append - -> Seq Scan on mc2p1 t1_1 - Filter: (a = 1) - -> Seq Scan on mc2p2 t1_2 - Filter: (a = 1) - -> Seq Scan on mc2p_default t1_3 - Filter: (a = 1) - -> Aggregate - -> Append - -> Seq Scan on mc3p0 t2_1 - Filter: ((c = t1.b) AND (a = 1) AND (abs(b) = 1)) - -> Seq Scan on mc3p1 t2_2 - Filter: ((c = t1.b) AND (a = 1) AND (abs(b) = 1)) - -> Seq Scan on mc3p_default t2_3 - Filter: ((c = t1.b) AND (a = 1) AND (abs(b) = 1)) -(16 rows) - --- also here, because values for all keys are provided -explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.a = 1 and abs(t2.b) = 1 and t2.c = 1) s where t1.a = 1; - QUERY PLAN --------------------------------------------------------------- - Nested Loop - -> Aggregate - -> Seq Scan on mc3p1 t2 - Filter: ((a = 1) AND (c = 1) AND (abs(b) = 1)) - -> Append - -> Seq Scan on mc2p1 t1_1 - Filter: (a = 1) - -> Seq Scan on mc2p2 t1_2 - Filter: (a = 1) - -> Seq Scan on mc2p_default t1_3 - Filter: (a = 1) -(11 rows) - --- --- pruning with clauses containing <> operator --- --- doesn't prune range partitions -create table rp (a int) partition by range (a); -create table rp0 partition of rp for values from (minvalue) to (1); -create table rp1 partition of rp for values from (1) to (2); -create table rp2 partition of rp for values from (2) to (maxvalue); -explain (costs off) select * from rp where a <> 1; - QUERY PLAN ----------------------------- - Append - -> Seq Scan on rp0 rp_1 - Filter: (a <> 1) - -> Seq Scan on rp1 rp_2 - Filter: (a <> 1) - -> Seq Scan on rp2 rp_3 - Filter: (a <> 1) -(7 rows) - -explain (costs off) select * from rp where a <> 1 and a <> 2; - QUERY PLAN ------------------------------------------ - Append - -> Seq Scan on rp0 rp_1 - Filter: ((a <> 1) AND (a <> 2)) - -> Seq Scan on rp1 rp_2 - Filter: ((a <> 1) AND (a <> 2)) - -> Seq Scan on rp2 rp_3 - Filter: ((a <> 1) AND (a <> 2)) -(7 rows) - --- null partition should be eliminated due to strict <> clause. -explain (costs off) select * from lp where a <> 'a'; - QUERY PLAN ------------------------------------- - Append - -> Seq Scan on lp_ad lp_1 - Filter: (a <> 'a'::bpchar) - -> Seq Scan on lp_bc lp_2 - Filter: (a <> 'a'::bpchar) - -> Seq Scan on lp_ef lp_3 - Filter: (a <> 'a'::bpchar) - -> Seq Scan on lp_g lp_4 - Filter: (a <> 'a'::bpchar) - -> Seq Scan on lp_default lp_5 - Filter: (a <> 'a'::bpchar) -(11 rows) - --- ensure we detect contradictions in clauses; a can't be NULL and NOT NULL. -explain (costs off) select * from lp where a <> 'a' and a is null; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -explain (costs off) select * from lp where (a <> 'a' and a <> 'd') or a is null; - QUERY PLAN ------------------------------------------------------------------------------- - Append - -> Seq Scan on lp_bc lp_1 - Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL)) - -> Seq Scan on lp_ef lp_2 - Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL)) - -> Seq Scan on lp_g lp_3 - Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL)) - -> Seq Scan on lp_null lp_4 - Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL)) - -> Seq Scan on lp_default lp_5 - Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL)) -(11 rows) - --- check that it also works for a partitioned table that's not root, --- which in this case are partitions of rlp that are themselves --- list-partitioned on b -explain (costs off) select * from rlp where a = 15 and b <> 'ab' and b <> 'cd' and b <> 'xy' and b is not null; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------- - Append - -> Seq Scan on rlp3efgh rlp_1 - Filter: ((b IS NOT NULL) AND ((b)::text <> 'ab'::text) AND ((b)::text <> 'cd'::text) AND ((b)::text <> 'xy'::text) AND (a = 15)) - -> Seq Scan on rlp3_default rlp_2 - Filter: ((b IS NOT NULL) AND ((b)::text <> 'ab'::text) AND ((b)::text <> 'cd'::text) AND ((b)::text <> 'xy'::text) AND (a = 15)) -(5 rows) - --- --- different collations for different keys with same expression --- -create table coll_pruning_multi (a text) partition by range (substr(a, 1) collate "POSIX", substr(a, 1) collate "C"); -create table coll_pruning_multi1 partition of coll_pruning_multi for values from ('a', 'a') to ('a', 'e'); -create table coll_pruning_multi2 partition of coll_pruning_multi for values from ('a', 'e') to ('a', 'z'); -create table coll_pruning_multi3 partition of coll_pruning_multi for values from ('b', 'a') to ('b', 'e'); --- no pruning, because no value for the leading key -explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'e' collate "C"; - QUERY PLAN ------------------------------------------------------------- - Append - -> Seq Scan on coll_pruning_multi1 coll_pruning_multi_1 - Filter: (substr(a, 1) = 'e'::text COLLATE "C") - -> Seq Scan on coll_pruning_multi2 coll_pruning_multi_2 - Filter: (substr(a, 1) = 'e'::text COLLATE "C") - -> Seq Scan on coll_pruning_multi3 coll_pruning_multi_3 - Filter: (substr(a, 1) = 'e'::text COLLATE "C") -(7 rows) - --- pruning, with a value provided for the leading key -explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'a' collate "POSIX"; - QUERY PLAN ------------------------------------------------------------- - Append - -> Seq Scan on coll_pruning_multi1 coll_pruning_multi_1 - Filter: (substr(a, 1) = 'a'::text COLLATE "POSIX") - -> Seq Scan on coll_pruning_multi2 coll_pruning_multi_2 - Filter: (substr(a, 1) = 'a'::text COLLATE "POSIX") -(5 rows) - --- pruning, with values provided for both keys -explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'e' collate "C" and substr(a, 1) = 'a' collate "POSIX"; - QUERY PLAN ---------------------------------------------------------------------------------------------------- - Seq Scan on coll_pruning_multi2 coll_pruning_multi - Filter: ((substr(a, 1) = 'e'::text COLLATE "C") AND (substr(a, 1) = 'a'::text COLLATE "POSIX")) -(2 rows) - --- --- LIKE operators don't prune --- -create table like_op_noprune (a text) partition by list (a); -create table like_op_noprune1 partition of like_op_noprune for values in ('ABC'); -create table like_op_noprune2 partition of like_op_noprune for values in ('BCD'); -explain (costs off) select * from like_op_noprune where a like '%BC'; - QUERY PLAN ------------------------------------------------------- - Append - -> Seq Scan on like_op_noprune1 like_op_noprune_1 - Filter: (a ~~ '%BC'::text) - -> Seq Scan on like_op_noprune2 like_op_noprune_2 - Filter: (a ~~ '%BC'::text) -(5 rows) - --- --- tests wherein clause value requires a cross-type comparison function --- -create table lparted_by_int2 (a smallint) partition by list (a); -create table lparted_by_int2_1 partition of lparted_by_int2 for values in (1); -create table lparted_by_int2_16384 partition of lparted_by_int2 for values in (16384); -explain (costs off) select * from lparted_by_int2 where a = 100_000_000_000_000; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -create table rparted_by_int2 (a smallint) partition by range (a); -create table rparted_by_int2_1 partition of rparted_by_int2 for values from (1) to (10); -create table rparted_by_int2_16384 partition of rparted_by_int2 for values from (10) to (16384); --- all partitions pruned -explain (costs off) select * from rparted_by_int2 where a > 100_000_000_000_000; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -create table rparted_by_int2_maxvalue partition of rparted_by_int2 for values from (16384) to (maxvalue); --- all partitions but rparted_by_int2_maxvalue pruned -explain (costs off) select * from rparted_by_int2 where a > 100_000_000_000_000; - QUERY PLAN ------------------------------------------------------- - Seq Scan on rparted_by_int2_maxvalue rparted_by_int2 - Filter: (a > '100000000000000'::bigint) -(2 rows) - -drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, iboolpart, boolrangep, rp, coll_pruning_multi, like_op_noprune, lparted_by_int2, rparted_by_int2; --- --- Test Partition pruning for HASH partitioning --- --- Use hand-rolled hash functions and operator classes to get predictable --- result on different machines. See the definitions of --- part_test_int4_ops and part_test_text_ops in test_setup.sql. --- -create table hp (a int, b text, c int) - partition by hash (a part_test_int4_ops, b part_test_text_ops); -create table hp0 partition of hp for values with (modulus 4, remainder 0); -create table hp3 partition of hp for values with (modulus 4, remainder 3); -create table hp1 partition of hp for values with (modulus 4, remainder 1); -create table hp2 partition of hp for values with (modulus 4, remainder 2); -insert into hp values (null, null, 0); -insert into hp values (1, null, 1); -insert into hp values (1, 'xxx', 2); -insert into hp values (null, 'xxx', 3); -insert into hp values (2, 'xxx', 4); -insert into hp values (1, 'abcde', 5); -select tableoid::regclass, * from hp order by c; - tableoid | a | b | c -----------+---+-------+--- - hp0 | | | 0 - hp1 | 1 | | 1 - hp0 | 1 | xxx | 2 - hp2 | | xxx | 3 - hp3 | 2 | xxx | 4 - hp2 | 1 | abcde | 5 -(6 rows) - --- partial keys won't prune, nor would non-equality conditions -explain (costs off) select * from hp where a = 1; - QUERY PLAN ----------------------------- - Append - -> Seq Scan on hp0 hp_1 - Filter: (a = 1) - -> Seq Scan on hp1 hp_2 - Filter: (a = 1) - -> Seq Scan on hp2 hp_3 - Filter: (a = 1) - -> Seq Scan on hp3 hp_4 - Filter: (a = 1) -(9 rows) - -explain (costs off) select * from hp where b = 'xxx'; - QUERY PLAN ------------------------------------ - Append - -> Seq Scan on hp0 hp_1 - Filter: (b = 'xxx'::text) - -> Seq Scan on hp1 hp_2 - Filter: (b = 'xxx'::text) - -> Seq Scan on hp2 hp_3 - Filter: (b = 'xxx'::text) - -> Seq Scan on hp3 hp_4 - Filter: (b = 'xxx'::text) -(9 rows) - -explain (costs off) select * from hp where a is null; - QUERY PLAN ------------------------------ - Append - -> Seq Scan on hp0 hp_1 - Filter: (a IS NULL) - -> Seq Scan on hp1 hp_2 - Filter: (a IS NULL) - -> Seq Scan on hp2 hp_3 - Filter: (a IS NULL) - -> Seq Scan on hp3 hp_4 - Filter: (a IS NULL) -(9 rows) - -explain (costs off) select * from hp where b is null; - QUERY PLAN ------------------------------ - Append - -> Seq Scan on hp0 hp_1 - Filter: (b IS NULL) - -> Seq Scan on hp1 hp_2 - Filter: (b IS NULL) - -> Seq Scan on hp2 hp_3 - Filter: (b IS NULL) - -> Seq Scan on hp3 hp_4 - Filter: (b IS NULL) -(9 rows) - -explain (costs off) select * from hp where a < 1 and b = 'xxx'; - QUERY PLAN -------------------------------------------------- - Append - -> Seq Scan on hp0 hp_1 - Filter: ((a < 1) AND (b = 'xxx'::text)) - -> Seq Scan on hp1 hp_2 - Filter: ((a < 1) AND (b = 'xxx'::text)) - -> Seq Scan on hp2 hp_3 - Filter: ((a < 1) AND (b = 'xxx'::text)) - -> Seq Scan on hp3 hp_4 - Filter: ((a < 1) AND (b = 'xxx'::text)) -(9 rows) - -explain (costs off) select * from hp where a <> 1 and b = 'yyy'; - QUERY PLAN --------------------------------------------------- - Append - -> Seq Scan on hp0 hp_1 - Filter: ((a <> 1) AND (b = 'yyy'::text)) - -> Seq Scan on hp1 hp_2 - Filter: ((a <> 1) AND (b = 'yyy'::text)) - -> Seq Scan on hp2 hp_3 - Filter: ((a <> 1) AND (b = 'yyy'::text)) - -> Seq Scan on hp3 hp_4 - Filter: ((a <> 1) AND (b = 'yyy'::text)) -(9 rows) - -explain (costs off) select * from hp where a <> 1 and b <> 'xxx'; - QUERY PLAN ---------------------------------------------------- - Append - -> Seq Scan on hp0 hp_1 - Filter: ((a <> 1) AND (b <> 'xxx'::text)) - -> Seq Scan on hp1 hp_2 - Filter: ((a <> 1) AND (b <> 'xxx'::text)) - -> Seq Scan on hp2 hp_3 - Filter: ((a <> 1) AND (b <> 'xxx'::text)) - -> Seq Scan on hp3 hp_4 - Filter: ((a <> 1) AND (b <> 'xxx'::text)) -(9 rows) - --- pruning should work if either a value or a IS NULL clause is provided for --- each of the keys -explain (costs off) select * from hp where a is null and b is null; - QUERY PLAN ------------------------------------------ - Seq Scan on hp0 hp - Filter: ((a IS NULL) AND (b IS NULL)) -(2 rows) - -explain (costs off) select * from hp where a = 1 and b is null; - QUERY PLAN -------------------------------------- - Seq Scan on hp1 hp - Filter: ((b IS NULL) AND (a = 1)) -(2 rows) - -explain (costs off) select * from hp where a = 1 and b = 'xxx'; - QUERY PLAN -------------------------------------------- - Seq Scan on hp0 hp - Filter: ((a = 1) AND (b = 'xxx'::text)) -(2 rows) - -explain (costs off) select * from hp where a is null and b = 'xxx'; - QUERY PLAN ------------------------------------------------ - Seq Scan on hp2 hp - Filter: ((a IS NULL) AND (b = 'xxx'::text)) -(2 rows) - -explain (costs off) select * from hp where a = 2 and b = 'xxx'; - QUERY PLAN -------------------------------------------- - Seq Scan on hp3 hp - Filter: ((a = 2) AND (b = 'xxx'::text)) -(2 rows) - -explain (costs off) select * from hp where a = 1 and b = 'abcde'; - QUERY PLAN ---------------------------------------------- - Seq Scan on hp2 hp - Filter: ((a = 1) AND (b = 'abcde'::text)) -(2 rows) - -explain (costs off) select * from hp where (a = 1 and b = 'abcde') or (a = 2 and b = 'xxx') or (a is null and b is null); - QUERY PLAN -------------------------------------------------------------------------------------------------------------------------- - Append - -> Seq Scan on hp0 hp_1 - Filter: (((a = 1) AND (b = 'abcde'::text)) OR ((a = 2) AND (b = 'xxx'::text)) OR ((a IS NULL) AND (b IS NULL))) - -> Seq Scan on hp2 hp_2 - Filter: (((a = 1) AND (b = 'abcde'::text)) OR ((a = 2) AND (b = 'xxx'::text)) OR ((a IS NULL) AND (b IS NULL))) - -> Seq Scan on hp3 hp_3 - Filter: (((a = 1) AND (b = 'abcde'::text)) OR ((a = 2) AND (b = 'xxx'::text)) OR ((a IS NULL) AND (b IS NULL))) -(7 rows) - --- test pruning when not all the partitions exist -drop table hp1; -drop table hp3; -explain (costs off) select * from hp where a = 1 and b = 'abcde'; - QUERY PLAN ---------------------------------------------- - Seq Scan on hp2 hp - Filter: ((a = 1) AND (b = 'abcde'::text)) -(2 rows) - -explain (costs off) select * from hp where a = 1 and b = 'abcde' and - (c = 2 or c = 3); - QUERY PLAN ----------------------------------------------------------------------- - Seq Scan on hp2 hp - Filter: ((a = 1) AND (b = 'abcde'::text) AND ((c = 2) OR (c = 3))) -(2 rows) - -drop table hp2; -explain (costs off) select * from hp where a = 1 and b = 'abcde' and - (c = 2 or c = 3); - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - --- --- Test runtime partition pruning --- -create table ab (a int not null, b int not null) partition by list (a); -create table ab_a2 partition of ab for values in(2) partition by list (b); -create table ab_a2_b1 partition of ab_a2 for values in (1); -create table ab_a2_b2 partition of ab_a2 for values in (2); -create table ab_a2_b3 partition of ab_a2 for values in (3); -create table ab_a1 partition of ab for values in(1) partition by list (b); -create table ab_a1_b1 partition of ab_a1 for values in (1); -create table ab_a1_b2 partition of ab_a1 for values in (2); -create table ab_a1_b3 partition of ab_a1 for values in (3); -create table ab_a3 partition of ab for values in(3) partition by list (b); -create table ab_a3_b1 partition of ab_a3 for values in (1); -create table ab_a3_b2 partition of ab_a3 for values in (2); -create table ab_a3_b3 partition of ab_a3 for values in (3); --- Disallow index only scans as concurrent transactions may stop visibility --- bits being set causing "Heap Fetches" to be unstable in the EXPLAIN ANALYZE --- output. -set enable_indexonlyscan = off; -prepare ab_q1 (int, int, int) as -select * from ab where a between $1 and $2 and b <= $3; -explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 2, 3); - QUERY PLAN ---------------------------------------------------------- - Append (actual rows=0 loops=1) - Subplans Removed: 6 - -> Seq Scan on ab_a2_b1 ab_1 (actual rows=0 loops=1) - Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) - -> Seq Scan on ab_a2_b2 ab_2 (actual rows=0 loops=1) - Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) - -> Seq Scan on ab_a2_b3 ab_3 (actual rows=0 loops=1) - Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) -(8 rows) - -explain (analyze, costs off, summary off, timing off) execute ab_q1 (1, 2, 3); - QUERY PLAN ---------------------------------------------------------- - Append (actual rows=0 loops=1) - Subplans Removed: 3 - -> Seq Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1) - Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) - -> Seq Scan on ab_a1_b2 ab_2 (actual rows=0 loops=1) - Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) - -> Seq Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1) - Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) - -> Seq Scan on ab_a2_b1 ab_4 (actual rows=0 loops=1) - Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) - -> Seq Scan on ab_a2_b2 ab_5 (actual rows=0 loops=1) - Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) - -> Seq Scan on ab_a2_b3 ab_6 (actual rows=0 loops=1) - Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) -(14 rows) - -deallocate ab_q1; --- Runtime pruning after optimizer pruning -prepare ab_q1 (int, int) as -select a from ab where a between $1 and $2 and b < 3; -explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 2); - QUERY PLAN ---------------------------------------------------------- - Append (actual rows=0 loops=1) - Subplans Removed: 4 - -> Seq Scan on ab_a2_b1 ab_1 (actual rows=0 loops=1) - Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) - -> Seq Scan on ab_a2_b2 ab_2 (actual rows=0 loops=1) - Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) -(6 rows) - -explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 4); - QUERY PLAN ---------------------------------------------------------- - Append (actual rows=0 loops=1) - Subplans Removed: 2 - -> Seq Scan on ab_a2_b1 ab_1 (actual rows=0 loops=1) - Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) - -> Seq Scan on ab_a2_b2 ab_2 (actual rows=0 loops=1) - Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) - -> Seq Scan on ab_a3_b1 ab_3 (actual rows=0 loops=1) - Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) - -> Seq Scan on ab_a3_b2 ab_4 (actual rows=0 loops=1) - Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) -(10 rows) - --- Ensure a mix of PARAM_EXTERN and PARAM_EXEC Params work together at --- different levels of partitioning. -prepare ab_q2 (int, int) as -select a from ab where a between $1 and $2 and b < (select 3); -explain (analyze, costs off, summary off, timing off) execute ab_q2 (2, 2); - QUERY PLAN ------------------------------------------------------------------------ - Append (actual rows=0 loops=1) - Subplans Removed: 6 - InitPlan 1 - -> Result (actual rows=1 loops=1) - -> Seq Scan on ab_a2_b1 ab_1 (actual rows=0 loops=1) - Filter: ((a >= $1) AND (a <= $2) AND (b < (InitPlan 1).col1)) - -> Seq Scan on ab_a2_b2 ab_2 (actual rows=0 loops=1) - Filter: ((a >= $1) AND (a <= $2) AND (b < (InitPlan 1).col1)) - -> Seq Scan on ab_a2_b3 ab_3 (never executed) - Filter: ((a >= $1) AND (a <= $2) AND (b < (InitPlan 1).col1)) -(10 rows) - --- As above, but swap the PARAM_EXEC Param to the first partition level -prepare ab_q3 (int, int) as -select a from ab where b between $1 and $2 and a < (select 3); -explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2); - QUERY PLAN ------------------------------------------------------------------------ - Append (actual rows=0 loops=1) - Subplans Removed: 6 - InitPlan 1 - -> Result (actual rows=1 loops=1) - -> Seq Scan on ab_a1_b2 ab_1 (actual rows=0 loops=1) - Filter: ((b >= $1) AND (b <= $2) AND (a < (InitPlan 1).col1)) - -> Seq Scan on ab_a2_b2 ab_2 (actual rows=0 loops=1) - Filter: ((b >= $1) AND (b <= $2) AND (a < (InitPlan 1).col1)) - -> Seq Scan on ab_a3_b2 ab_3 (never executed) - Filter: ((b >= $1) AND (b <= $2) AND (a < (InitPlan 1).col1)) -(10 rows) - --- --- Test runtime pruning with hash partitioned tables --- --- recreate partitions dropped above -create table hp1 partition of hp for values with (modulus 4, remainder 1); -create table hp2 partition of hp for values with (modulus 4, remainder 2); -create table hp3 partition of hp for values with (modulus 4, remainder 3); --- Ensure we correctly prune unneeded partitions when there is an IS NULL qual -prepare hp_q1 (text) as -select * from hp where a is null and b = $1; -explain (costs off) execute hp_q1('xxx'); - QUERY PLAN --------------------------------------------- - Append - Subplans Removed: 3 - -> Seq Scan on hp2 hp_1 - Filter: ((a IS NULL) AND (b = $1)) -(4 rows) - -deallocate hp_q1; -drop table hp; --- Test a backwards Append scan -create table list_part (a int) partition by list (a); -create table list_part1 partition of list_part for values in (1); -create table list_part2 partition of list_part for values in (2); -create table list_part3 partition of list_part for values in (3); -create table list_part4 partition of list_part for values in (4); -insert into list_part select generate_series(1,4); -begin; --- Don't select an actual value out of the table as the order of the Append's --- subnodes may not be stable. -declare cur SCROLL CURSOR for select 1 from list_part where a > (select 1) and a < (select 4); --- move beyond the final row -move 3 from cur; --- Ensure we get two rows. -fetch backward all from cur; - ?column? ----------- - 1 - 1 -(2 rows) - -commit; -begin; --- Test run-time pruning using stable functions -create function list_part_fn(int) returns int as $$ begin return $1; end;$$ language plpgsql stable; --- Ensure pruning works using a stable function containing no Vars -explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(1); - QUERY PLAN ------------------------------------------------------------------- - Append (actual rows=1 loops=1) - Subplans Removed: 3 - -> Seq Scan on list_part1 list_part_1 (actual rows=1 loops=1) - Filter: (a = list_part_fn(1)) -(4 rows) - --- Ensure pruning does not take place when the function has a Var parameter -explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(a); - QUERY PLAN ------------------------------------------------------------------- - Append (actual rows=4 loops=1) - -> Seq Scan on list_part1 list_part_1 (actual rows=1 loops=1) - Filter: (a = list_part_fn(a)) - -> Seq Scan on list_part2 list_part_2 (actual rows=1 loops=1) - Filter: (a = list_part_fn(a)) - -> Seq Scan on list_part3 list_part_3 (actual rows=1 loops=1) - Filter: (a = list_part_fn(a)) - -> Seq Scan on list_part4 list_part_4 (actual rows=1 loops=1) - Filter: (a = list_part_fn(a)) -(9 rows) - --- Ensure pruning does not take place when the expression contains a Var. -explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(1) + a; - QUERY PLAN ------------------------------------------------------------------- - Append (actual rows=0 loops=1) - -> Seq Scan on list_part1 list_part_1 (actual rows=0 loops=1) - Filter: (a = (list_part_fn(1) + a)) - Rows Removed by Filter: 1 - -> Seq Scan on list_part2 list_part_2 (actual rows=0 loops=1) - Filter: (a = (list_part_fn(1) + a)) - Rows Removed by Filter: 1 - -> Seq Scan on list_part3 list_part_3 (actual rows=0 loops=1) - Filter: (a = (list_part_fn(1) + a)) - Rows Removed by Filter: 1 - -> Seq Scan on list_part4 list_part_4 (actual rows=0 loops=1) - Filter: (a = (list_part_fn(1) + a)) - Rows Removed by Filter: 1 -(13 rows) - -rollback; -drop table list_part; --- Parallel append --- Parallel queries won't necessarily get as many workers as the planner --- asked for. This affects not only the "Workers Launched:" field of EXPLAIN --- results, but also row counts and loop counts for parallel scans, Gathers, --- and everything in between. This function filters out the values we can't --- rely on to be stable. --- This removes enough info that you might wonder why bother with EXPLAIN --- ANALYZE at all. The answer is that we need to see '(never executed)' --- notations because that's the only way to verify runtime pruning. -create function explain_parallel_append(text) returns setof text -language plpgsql as -$$ -declare - ln text; -begin - for ln in - execute format('explain (analyze, costs off, summary off, timing off) %s', - $1) - loop - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, 'Rows Removed by Filter: \d+', 'Rows Removed by Filter: N'); - return next ln; - end loop; -end; -$$; -prepare ab_q4 (int, int) as -select avg(a) from ab where a between $1 and $2 and b < 4; --- Encourage use of parallel plans -set parallel_setup_cost = 0; -set parallel_tuple_cost = 0; -set min_parallel_table_scan_size = 0; -set max_parallel_workers_per_gather = 2; -select explain_parallel_append('execute ab_q4 (2, 2)'); - explain_parallel_append ------------------------------------------------------------------------------------- - Finalize Aggregate (actual rows=N loops=N) - -> Gather (actual rows=N loops=N) - Workers Planned: 2 - Workers Launched: N - -> Partial Aggregate (actual rows=N loops=N) - -> Parallel Append (actual rows=N loops=N) - Subplans Removed: 6 - -> Parallel Seq Scan on ab_a2_b1 ab_1 (actual rows=N loops=N) - Filter: ((a >= $1) AND (a <= $2) AND (b < 4)) - -> Parallel Seq Scan on ab_a2_b2 ab_2 (actual rows=N loops=N) - Filter: ((a >= $1) AND (a <= $2) AND (b < 4)) - -> Parallel Seq Scan on ab_a2_b3 ab_3 (actual rows=N loops=N) - Filter: ((a >= $1) AND (a <= $2) AND (b < 4)) -(13 rows) - --- Test run-time pruning with IN lists. -prepare ab_q5 (int, int, int) as -select avg(a) from ab where a in($1,$2,$3) and b < 4; -select explain_parallel_append('execute ab_q5 (1, 1, 1)'); - explain_parallel_append ------------------------------------------------------------------------------------- - Finalize Aggregate (actual rows=N loops=N) - -> Gather (actual rows=N loops=N) - Workers Planned: 2 - Workers Launched: N - -> Partial Aggregate (actual rows=N loops=N) - -> Parallel Append (actual rows=N loops=N) - Subplans Removed: 6 - -> Parallel Seq Scan on ab_a1_b1 ab_1 (actual rows=N loops=N) - Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) - -> Parallel Seq Scan on ab_a1_b2 ab_2 (actual rows=N loops=N) - Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) - -> Parallel Seq Scan on ab_a1_b3 ab_3 (actual rows=N loops=N) - Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) -(13 rows) - -select explain_parallel_append('execute ab_q5 (2, 3, 3)'); - explain_parallel_append ------------------------------------------------------------------------------------- - Finalize Aggregate (actual rows=N loops=N) - -> Gather (actual rows=N loops=N) - Workers Planned: 2 - Workers Launched: N - -> Partial Aggregate (actual rows=N loops=N) - -> Parallel Append (actual rows=N loops=N) - Subplans Removed: 3 - -> Parallel Seq Scan on ab_a2_b1 ab_1 (actual rows=N loops=N) - Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) - -> Parallel Seq Scan on ab_a2_b2 ab_2 (actual rows=N loops=N) - Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) - -> Parallel Seq Scan on ab_a2_b3 ab_3 (actual rows=N loops=N) - Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) - -> Parallel Seq Scan on ab_a3_b1 ab_4 (actual rows=N loops=N) - Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) - -> Parallel Seq Scan on ab_a3_b2 ab_5 (actual rows=N loops=N) - Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) - -> Parallel Seq Scan on ab_a3_b3 ab_6 (actual rows=N loops=N) - Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) -(19 rows) - --- Try some params whose values do not belong to any partition. -select explain_parallel_append('execute ab_q5 (33, 44, 55)'); - explain_parallel_append ------------------------------------------------------------ - Finalize Aggregate (actual rows=N loops=N) - -> Gather (actual rows=N loops=N) - Workers Planned: 2 - Workers Launched: N - -> Partial Aggregate (actual rows=N loops=N) - -> Parallel Append (actual rows=N loops=N) - Subplans Removed: 9 -(7 rows) - --- Test Parallel Append with PARAM_EXEC Params -select explain_parallel_append('select count(*) from ab where (a = (select 1) or a = (select 3)) and b = 2'); - explain_parallel_append ------------------------------------------------------------------------------------------------- - Aggregate (actual rows=N loops=N) - InitPlan 1 - -> Result (actual rows=N loops=N) - InitPlan 2 - -> Result (actual rows=N loops=N) - -> Gather (actual rows=N loops=N) - Workers Planned: 2 - Workers Launched: N - -> Parallel Append (actual rows=N loops=N) - -> Parallel Seq Scan on ab_a1_b2 ab_1 (actual rows=N loops=N) - Filter: ((b = 2) AND ((a = (InitPlan 1).col1) OR (a = (InitPlan 2).col1))) - -> Parallel Seq Scan on ab_a2_b2 ab_2 (never executed) - Filter: ((b = 2) AND ((a = (InitPlan 1).col1) OR (a = (InitPlan 2).col1))) - -> Parallel Seq Scan on ab_a3_b2 ab_3 (actual rows=N loops=N) - Filter: ((b = 2) AND ((a = (InitPlan 1).col1) OR (a = (InitPlan 2).col1))) -(15 rows) - --- Test pruning during parallel nested loop query -create table lprt_a (a int not null); --- Insert some values we won't find in ab -insert into lprt_a select 0 from generate_series(1,100); --- and insert some values that we should find. -insert into lprt_a values(1),(1); -analyze lprt_a; -create index ab_a2_b1_a_idx on ab_a2_b1 (a); -create index ab_a2_b2_a_idx on ab_a2_b2 (a); -create index ab_a2_b3_a_idx on ab_a2_b3 (a); -create index ab_a1_b1_a_idx on ab_a1_b1 (a); -create index ab_a1_b2_a_idx on ab_a1_b2 (a); -create index ab_a1_b3_a_idx on ab_a1_b3 (a); -create index ab_a3_b1_a_idx on ab_a3_b1 (a); -create index ab_a3_b2_a_idx on ab_a3_b2 (a); -create index ab_a3_b3_a_idx on ab_a3_b3 (a); -set enable_hashjoin = 0; -set enable_mergejoin = 0; -set enable_memoize = 0; -select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(0, 0, 1)'); - explain_parallel_append --------------------------------------------------------------------------------------------------------- - Finalize Aggregate (actual rows=N loops=N) - -> Gather (actual rows=N loops=N) - Workers Planned: 1 - Workers Launched: N - -> Partial Aggregate (actual rows=N loops=N) - -> Nested Loop (actual rows=N loops=N) - -> Parallel Seq Scan on lprt_a a (actual rows=N loops=N) - Filter: (a = ANY ('{0,0,1}'::integer[])) - -> Append (actual rows=N loops=N) - -> Index Scan using ab_a1_b1_a_idx on ab_a1_b1 ab_1 (actual rows=N loops=N) - Index Cond: (a = a.a) - -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (actual rows=N loops=N) - Index Cond: (a = a.a) - -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed) - Index Cond: (a = a.a) -(27 rows) - --- Ensure the same partitions are pruned when we make the nested loop --- parameter an Expr rather than a plain Param. -select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a + 0 where a.a in(0, 0, 1)'); - explain_parallel_append --------------------------------------------------------------------------------------------------------- - Finalize Aggregate (actual rows=N loops=N) - -> Gather (actual rows=N loops=N) - Workers Planned: 1 - Workers Launched: N - -> Partial Aggregate (actual rows=N loops=N) - -> Nested Loop (actual rows=N loops=N) - -> Parallel Seq Scan on lprt_a a (actual rows=N loops=N) - Filter: (a = ANY ('{0,0,1}'::integer[])) - -> Append (actual rows=N loops=N) - -> Index Scan using ab_a1_b1_a_idx on ab_a1_b1 ab_1 (actual rows=N loops=N) - Index Cond: (a = (a.a + 0)) - -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (actual rows=N loops=N) - Index Cond: (a = (a.a + 0)) - -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N) - Index Cond: (a = (a.a + 0)) - -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed) - Index Cond: (a = (a.a + 0)) - -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed) - Index Cond: (a = (a.a + 0)) - -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed) - Index Cond: (a = (a.a + 0)) - -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed) - Index Cond: (a = (a.a + 0)) - -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed) - Index Cond: (a = (a.a + 0)) - -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed) - Index Cond: (a = (a.a + 0)) -(27 rows) - -insert into lprt_a values(3),(3); -select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 3)'); - explain_parallel_append --------------------------------------------------------------------------------------------------------- - Finalize Aggregate (actual rows=N loops=N) - -> Gather (actual rows=N loops=N) - Workers Planned: 1 - Workers Launched: N - -> Partial Aggregate (actual rows=N loops=N) - -> Nested Loop (actual rows=N loops=N) - -> Parallel Seq Scan on lprt_a a (actual rows=N loops=N) - Filter: (a = ANY ('{1,0,3}'::integer[])) - -> Append (actual rows=N loops=N) - -> Index Scan using ab_a1_b1_a_idx on ab_a1_b1 ab_1 (actual rows=N loops=N) - Index Cond: (a = a.a) - -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (actual rows=N loops=N) - Index Cond: (a = a.a) - -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (actual rows=N loops=N) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (actual rows=N loops=N) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (actual rows=N loops=N) - Index Cond: (a = a.a) -(27 rows) - -select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 0)'); - explain_parallel_append --------------------------------------------------------------------------------------------------------- - Finalize Aggregate (actual rows=N loops=N) - -> Gather (actual rows=N loops=N) - Workers Planned: 1 - Workers Launched: N - -> Partial Aggregate (actual rows=N loops=N) - -> Nested Loop (actual rows=N loops=N) - -> Parallel Seq Scan on lprt_a a (actual rows=N loops=N) - Filter: (a = ANY ('{1,0,0}'::integer[])) - Rows Removed by Filter: N - -> Append (actual rows=N loops=N) - -> Index Scan using ab_a1_b1_a_idx on ab_a1_b1 ab_1 (actual rows=N loops=N) - Index Cond: (a = a.a) - -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (actual rows=N loops=N) - Index Cond: (a = a.a) - -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed) - Index Cond: (a = a.a) -(28 rows) - -delete from lprt_a where a = 1; -select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 0)'); - explain_parallel_append -------------------------------------------------------------------------------------------------- - Finalize Aggregate (actual rows=N loops=N) - -> Gather (actual rows=N loops=N) - Workers Planned: 1 - Workers Launched: N - -> Partial Aggregate (actual rows=N loops=N) - -> Nested Loop (actual rows=N loops=N) - -> Parallel Seq Scan on lprt_a a (actual rows=N loops=N) - Filter: (a = ANY ('{1,0,0}'::integer[])) - Rows Removed by Filter: N - -> Append (actual rows=N loops=N) - -> Index Scan using ab_a1_b1_a_idx on ab_a1_b1 ab_1 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed) - Index Cond: (a = a.a) -(28 rows) - -reset enable_hashjoin; -reset enable_mergejoin; -reset enable_memoize; -reset parallel_setup_cost; -reset parallel_tuple_cost; -reset min_parallel_table_scan_size; -reset max_parallel_workers_per_gather; --- Test run-time partition pruning with an initplan -explain (analyze, costs off, summary off, timing off) -select * from ab where a = (select max(a) from lprt_a) and b = (select max(a)-1 from lprt_a); - QUERY PLAN -------------------------------------------------------------------------- - Append (actual rows=0 loops=1) - InitPlan 1 - -> Aggregate (actual rows=1 loops=1) - -> Seq Scan on lprt_a (actual rows=102 loops=1) - InitPlan 2 - -> Aggregate (actual rows=1 loops=1) - -> Seq Scan on lprt_a lprt_a_1 (actual rows=102 loops=1) - -> Bitmap Heap Scan on ab_a1_b1 ab_1 (never executed) - Recheck Cond: (a = (InitPlan 1).col1) - Filter: (b = (InitPlan 2).col1) - -> Bitmap Index Scan on ab_a1_b1_a_idx (never executed) - Index Cond: (a = (InitPlan 1).col1) - -> Bitmap Heap Scan on ab_a1_b2 ab_2 (never executed) - Recheck Cond: (a = (InitPlan 1).col1) - Filter: (b = (InitPlan 2).col1) - -> Bitmap Index Scan on ab_a1_b2_a_idx (never executed) - Index Cond: (a = (InitPlan 1).col1) - -> Bitmap Heap Scan on ab_a1_b3 ab_3 (never executed) - Recheck Cond: (a = (InitPlan 1).col1) - Filter: (b = (InitPlan 2).col1) - -> Bitmap Index Scan on ab_a1_b3_a_idx (never executed) - Index Cond: (a = (InitPlan 1).col1) - -> Bitmap Heap Scan on ab_a2_b1 ab_4 (never executed) - Recheck Cond: (a = (InitPlan 1).col1) - Filter: (b = (InitPlan 2).col1) - -> Bitmap Index Scan on ab_a2_b1_a_idx (never executed) - Index Cond: (a = (InitPlan 1).col1) - -> Bitmap Heap Scan on ab_a2_b2 ab_5 (never executed) - Recheck Cond: (a = (InitPlan 1).col1) - Filter: (b = (InitPlan 2).col1) - -> Bitmap Index Scan on ab_a2_b2_a_idx (never executed) - Index Cond: (a = (InitPlan 1).col1) - -> Bitmap Heap Scan on ab_a2_b3 ab_6 (never executed) - Recheck Cond: (a = (InitPlan 1).col1) - Filter: (b = (InitPlan 2).col1) - -> Bitmap Index Scan on ab_a2_b3_a_idx (never executed) - Index Cond: (a = (InitPlan 1).col1) - -> Bitmap Heap Scan on ab_a3_b1 ab_7 (never executed) - Recheck Cond: (a = (InitPlan 1).col1) - Filter: (b = (InitPlan 2).col1) - -> Bitmap Index Scan on ab_a3_b1_a_idx (never executed) - Index Cond: (a = (InitPlan 1).col1) - -> Bitmap Heap Scan on ab_a3_b2 ab_8 (actual rows=0 loops=1) - Recheck Cond: (a = (InitPlan 1).col1) - Filter: (b = (InitPlan 2).col1) - -> Bitmap Index Scan on ab_a3_b2_a_idx (actual rows=0 loops=1) - Index Cond: (a = (InitPlan 1).col1) - -> Bitmap Heap Scan on ab_a3_b3 ab_9 (never executed) - Recheck Cond: (a = (InitPlan 1).col1) - Filter: (b = (InitPlan 2).col1) - -> Bitmap Index Scan on ab_a3_b3_a_idx (never executed) - Index Cond: (a = (InitPlan 1).col1) -(52 rows) - --- Test run-time partition pruning with UNION ALL parents -explain (analyze, costs off, summary off, timing off) -select * from (select * from ab where a = 1 union all select * from ab) ab where b = (select 1); - QUERY PLAN -------------------------------------------------------------------------------- - Append (actual rows=0 loops=1) - InitPlan 1 - -> Result (actual rows=1 loops=1) - -> Append (actual rows=0 loops=1) - -> Bitmap Heap Scan on ab_a1_b1 ab_11 (actual rows=0 loops=1) - Recheck Cond: (a = 1) - Filter: (b = (InitPlan 1).col1) - -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) - Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b2 ab_12 (never executed) - Recheck Cond: (a = 1) - Filter: (b = (InitPlan 1).col1) - -> Bitmap Index Scan on ab_a1_b2_a_idx (never executed) - Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b3 ab_13 (never executed) - Recheck Cond: (a = 1) - Filter: (b = (InitPlan 1).col1) - -> Bitmap Index Scan on ab_a1_b3_a_idx (never executed) - Index Cond: (a = 1) - -> Seq Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1) - Filter: (b = (InitPlan 1).col1) - -> Seq Scan on ab_a1_b2 ab_2 (never executed) - Filter: (b = (InitPlan 1).col1) - -> Seq Scan on ab_a1_b3 ab_3 (never executed) - Filter: (b = (InitPlan 1).col1) - -> Seq Scan on ab_a2_b1 ab_4 (actual rows=0 loops=1) - Filter: (b = (InitPlan 1).col1) - -> Seq Scan on ab_a2_b2 ab_5 (never executed) - Filter: (b = (InitPlan 1).col1) - -> Seq Scan on ab_a2_b3 ab_6 (never executed) - Filter: (b = (InitPlan 1).col1) - -> Seq Scan on ab_a3_b1 ab_7 (actual rows=0 loops=1) - Filter: (b = (InitPlan 1).col1) - -> Seq Scan on ab_a3_b2 ab_8 (never executed) - Filter: (b = (InitPlan 1).col1) - -> Seq Scan on ab_a3_b3 ab_9 (never executed) - Filter: (b = (InitPlan 1).col1) -(37 rows) - --- A case containing a UNION ALL with a non-partitioned child. -explain (analyze, costs off, summary off, timing off) -select * from (select * from ab where a = 1 union all (values(10,5)) union all select * from ab) ab where b = (select 1); - QUERY PLAN -------------------------------------------------------------------------------- - Append (actual rows=0 loops=1) - InitPlan 1 - -> Result (actual rows=1 loops=1) - -> Append (actual rows=0 loops=1) - -> Bitmap Heap Scan on ab_a1_b1 ab_11 (actual rows=0 loops=1) - Recheck Cond: (a = 1) - Filter: (b = (InitPlan 1).col1) - -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) - Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b2 ab_12 (never executed) - Recheck Cond: (a = 1) - Filter: (b = (InitPlan 1).col1) - -> Bitmap Index Scan on ab_a1_b2_a_idx (never executed) - Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b3 ab_13 (never executed) - Recheck Cond: (a = 1) - Filter: (b = (InitPlan 1).col1) - -> Bitmap Index Scan on ab_a1_b3_a_idx (never executed) - Index Cond: (a = 1) - -> Result (actual rows=0 loops=1) - One-Time Filter: (5 = (InitPlan 1).col1) - -> Seq Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1) - Filter: (b = (InitPlan 1).col1) - -> Seq Scan on ab_a1_b2 ab_2 (never executed) - Filter: (b = (InitPlan 1).col1) - -> Seq Scan on ab_a1_b3 ab_3 (never executed) - Filter: (b = (InitPlan 1).col1) - -> Seq Scan on ab_a2_b1 ab_4 (actual rows=0 loops=1) - Filter: (b = (InitPlan 1).col1) - -> Seq Scan on ab_a2_b2 ab_5 (never executed) - Filter: (b = (InitPlan 1).col1) - -> Seq Scan on ab_a2_b3 ab_6 (never executed) - Filter: (b = (InitPlan 1).col1) - -> Seq Scan on ab_a3_b1 ab_7 (actual rows=0 loops=1) - Filter: (b = (InitPlan 1).col1) - -> Seq Scan on ab_a3_b2 ab_8 (never executed) - Filter: (b = (InitPlan 1).col1) - -> Seq Scan on ab_a3_b3 ab_9 (never executed) - Filter: (b = (InitPlan 1).col1) -(39 rows) - --- Another UNION ALL test, but containing a mix of exec init and exec run-time pruning. -create table xy_1 (x int, y int); -insert into xy_1 values(100,-10); -set enable_bitmapscan = 0; -set enable_indexscan = 0; -prepare ab_q6 as -select * from ( - select tableoid::regclass,a,b from ab -union all - select tableoid::regclass,x,y from xy_1 -union all - select tableoid::regclass,a,b from ab -) ab where a = $1 and b = (select -10); --- Ensure the xy_1 subplan is not pruned. -explain (analyze, costs off, summary off, timing off) execute ab_q6(1); - QUERY PLAN --------------------------------------------------------- - Append (actual rows=0 loops=1) - Subplans Removed: 12 - InitPlan 1 - -> Result (actual rows=1 loops=1) - -> Seq Scan on ab_a1_b1 ab_1 (never executed) - Filter: ((a = $1) AND (b = (InitPlan 1).col1)) - -> Seq Scan on ab_a1_b2 ab_2 (never executed) - Filter: ((a = $1) AND (b = (InitPlan 1).col1)) - -> Seq Scan on ab_a1_b3 ab_3 (never executed) - Filter: ((a = $1) AND (b = (InitPlan 1).col1)) - -> Seq Scan on xy_1 (actual rows=0 loops=1) - Filter: ((x = $1) AND (y = (InitPlan 1).col1)) - Rows Removed by Filter: 1 - -> Seq Scan on ab_a1_b1 ab_4 (never executed) - Filter: ((a = $1) AND (b = (InitPlan 1).col1)) - -> Seq Scan on ab_a1_b2 ab_5 (never executed) - Filter: ((a = $1) AND (b = (InitPlan 1).col1)) - -> Seq Scan on ab_a1_b3 ab_6 (never executed) - Filter: ((a = $1) AND (b = (InitPlan 1).col1)) -(19 rows) - --- Ensure we see just the xy_1 row. -execute ab_q6(100); - tableoid | a | b -----------+-----+----- - xy_1 | 100 | -10 -(1 row) - -reset enable_bitmapscan; -reset enable_indexscan; -deallocate ab_q1; -deallocate ab_q2; -deallocate ab_q3; -deallocate ab_q4; -deallocate ab_q5; -deallocate ab_q6; --- UPDATE on a partition subtree has been seen to have problems. -insert into ab values (1,2); -select explain_analyze(' -update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a;'); - explain_analyze -------------------------------------------------------------------------------------------- - Update on ab_a1 (actual rows=0 loops=1) - Update on ab_a1_b1 ab_a1_1 - Update on ab_a1_b2 ab_a1_2 - Update on ab_a1_b3 ab_a1_3 - -> Nested Loop (actual rows=1 loops=1) - -> Append (actual rows=1 loops=1) - -> Bitmap Heap Scan on ab_a1_b1 ab_a1_1 (actual rows=0 loops=1) - Recheck Cond: (a = 1) - -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) - Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1) - Recheck Cond: (a = 1) - Heap Blocks: exact=1 - -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1) - Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b3 ab_a1_3 (actual rows=0 loops=1) - Recheck Cond: (a = 1) - Heap Blocks: exact=1 - -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1) - Index Cond: (a = 1) - -> Materialize (actual rows=1 loops=1) - Storage: Memory Maximum Storage: NkB - -> Append (actual rows=1 loops=1) - -> Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1) - Recheck Cond: (a = 1) - -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) - Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1) - Recheck Cond: (a = 1) - Heap Blocks: exact=1 - -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1) - Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1) - Recheck Cond: (a = 1) - Heap Blocks: exact=1 - -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1) - Index Cond: (a = 1) -(37 rows) - -table ab; - a | b ----+--- - 1 | 3 -(1 row) - --- Test UPDATE where source relation has run-time pruning enabled -truncate ab; -insert into ab values (1, 1), (1, 2), (1, 3), (2, 1); -select explain_analyze(' -update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1);'); - explain_analyze ------------------------------------------------------------------------------- - Update on ab_a1 (actual rows=0 loops=1) - Update on ab_a1_b1 ab_a1_1 - Update on ab_a1_b2 ab_a1_2 - Update on ab_a1_b3 ab_a1_3 - InitPlan 1 - -> Result (actual rows=1 loops=1) - -> Nested Loop (actual rows=3 loops=1) - -> Append (actual rows=3 loops=1) - -> Seq Scan on ab_a1_b1 ab_a1_1 (actual rows=1 loops=1) - -> Seq Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1) - -> Seq Scan on ab_a1_b3 ab_a1_3 (actual rows=1 loops=1) - -> Materialize (actual rows=1 loops=3) - Storage: Memory Maximum Storage: NkB - -> Append (actual rows=1 loops=1) - -> Seq Scan on ab_a2_b1 ab_a2_1 (actual rows=1 loops=1) - Filter: (b = (InitPlan 1).col1) - -> Seq Scan on ab_a2_b2 ab_a2_2 (never executed) - Filter: (b = (InitPlan 1).col1) - -> Seq Scan on ab_a2_b3 ab_a2_3 (never executed) - Filter: (b = (InitPlan 1).col1) -(20 rows) - -select tableoid::regclass, * from ab; - tableoid | a | b -----------+---+--- - ab_a1_b3 | 1 | 3 - ab_a1_b3 | 1 | 3 - ab_a1_b3 | 1 | 3 - ab_a2_b1 | 2 | 1 -(4 rows) - -drop table ab, lprt_a; --- Join -create table tbl1(col1 int); -insert into tbl1 values (501), (505); --- Basic table -create table tprt (col1 int) partition by range (col1); -create table tprt_1 partition of tprt for values from (1) to (501); -create table tprt_2 partition of tprt for values from (501) to (1001); -create table tprt_3 partition of tprt for values from (1001) to (2001); -create table tprt_4 partition of tprt for values from (2001) to (3001); -create table tprt_5 partition of tprt for values from (3001) to (4001); -create table tprt_6 partition of tprt for values from (4001) to (5001); -create index tprt1_idx on tprt_1 (col1); -create index tprt2_idx on tprt_2 (col1); -create index tprt3_idx on tprt_3 (col1); -create index tprt4_idx on tprt_4 (col1); -create index tprt5_idx on tprt_5 (col1); -create index tprt6_idx on tprt_6 (col1); -insert into tprt values (10), (20), (501), (502), (505), (1001), (4500); -set enable_hashjoin = off; -set enable_mergejoin = off; -explain (analyze, costs off, summary off, timing off) -select * from tbl1 join tprt on tbl1.col1 > tprt.col1; - QUERY PLAN --------------------------------------------------------------------------- - Nested Loop (actual rows=6 loops=1) - -> Seq Scan on tbl1 (actual rows=2 loops=1) - -> Append (actual rows=3 loops=2) - -> Index Scan using tprt1_idx on tprt_1 (actual rows=2 loops=2) - Index Cond: (col1 < tbl1.col1) - -> Index Scan using tprt2_idx on tprt_2 (actual rows=2 loops=1) - Index Cond: (col1 < tbl1.col1) - -> Index Scan using tprt3_idx on tprt_3 (never executed) - Index Cond: (col1 < tbl1.col1) - -> Index Scan using tprt4_idx on tprt_4 (never executed) - Index Cond: (col1 < tbl1.col1) - -> Index Scan using tprt5_idx on tprt_5 (never executed) - Index Cond: (col1 < tbl1.col1) - -> Index Scan using tprt6_idx on tprt_6 (never executed) - Index Cond: (col1 < tbl1.col1) -(15 rows) - -explain (analyze, costs off, summary off, timing off) -select * from tbl1 join tprt on tbl1.col1 = tprt.col1; - QUERY PLAN --------------------------------------------------------------------------- - Nested Loop (actual rows=2 loops=1) - -> Seq Scan on tbl1 (actual rows=2 loops=1) - -> Append (actual rows=1 loops=2) - -> Index Scan using tprt1_idx on tprt_1 (never executed) - Index Cond: (col1 = tbl1.col1) - -> Index Scan using tprt2_idx on tprt_2 (actual rows=1 loops=2) - Index Cond: (col1 = tbl1.col1) - -> Index Scan using tprt3_idx on tprt_3 (never executed) - Index Cond: (col1 = tbl1.col1) - -> Index Scan using tprt4_idx on tprt_4 (never executed) - Index Cond: (col1 = tbl1.col1) - -> Index Scan using tprt5_idx on tprt_5 (never executed) - Index Cond: (col1 = tbl1.col1) - -> Index Scan using tprt6_idx on tprt_6 (never executed) - Index Cond: (col1 = tbl1.col1) -(15 rows) - -select tbl1.col1, tprt.col1 from tbl1 -inner join tprt on tbl1.col1 > tprt.col1 -order by tbl1.col1, tprt.col1; - col1 | col1 -------+------ - 501 | 10 - 501 | 20 - 505 | 10 - 505 | 20 - 505 | 501 - 505 | 502 -(6 rows) - -select tbl1.col1, tprt.col1 from tbl1 -inner join tprt on tbl1.col1 = tprt.col1 -order by tbl1.col1, tprt.col1; - col1 | col1 -------+------ - 501 | 501 - 505 | 505 -(2 rows) - --- Multiple partitions -insert into tbl1 values (1001), (1010), (1011); -explain (analyze, costs off, summary off, timing off) -select * from tbl1 inner join tprt on tbl1.col1 > tprt.col1; - QUERY PLAN --------------------------------------------------------------------------- - Nested Loop (actual rows=23 loops=1) - -> Seq Scan on tbl1 (actual rows=5 loops=1) - -> Append (actual rows=5 loops=5) - -> Index Scan using tprt1_idx on tprt_1 (actual rows=2 loops=5) - Index Cond: (col1 < tbl1.col1) - -> Index Scan using tprt2_idx on tprt_2 (actual rows=3 loops=4) - Index Cond: (col1 < tbl1.col1) - -> Index Scan using tprt3_idx on tprt_3 (actual rows=1 loops=2) - Index Cond: (col1 < tbl1.col1) - -> Index Scan using tprt4_idx on tprt_4 (never executed) - Index Cond: (col1 < tbl1.col1) - -> Index Scan using tprt5_idx on tprt_5 (never executed) - Index Cond: (col1 < tbl1.col1) - -> Index Scan using tprt6_idx on tprt_6 (never executed) - Index Cond: (col1 < tbl1.col1) -(15 rows) - -explain (analyze, costs off, summary off, timing off) -select * from tbl1 inner join tprt on tbl1.col1 = tprt.col1; - QUERY PLAN --------------------------------------------------------------------------- - Nested Loop (actual rows=3 loops=1) - -> Seq Scan on tbl1 (actual rows=5 loops=1) - -> Append (actual rows=1 loops=5) - -> Index Scan using tprt1_idx on tprt_1 (never executed) - Index Cond: (col1 = tbl1.col1) - -> Index Scan using tprt2_idx on tprt_2 (actual rows=1 loops=2) - Index Cond: (col1 = tbl1.col1) - -> Index Scan using tprt3_idx on tprt_3 (actual rows=0 loops=3) - Index Cond: (col1 = tbl1.col1) - -> Index Scan using tprt4_idx on tprt_4 (never executed) - Index Cond: (col1 = tbl1.col1) - -> Index Scan using tprt5_idx on tprt_5 (never executed) - Index Cond: (col1 = tbl1.col1) - -> Index Scan using tprt6_idx on tprt_6 (never executed) - Index Cond: (col1 = tbl1.col1) -(15 rows) - -select tbl1.col1, tprt.col1 from tbl1 -inner join tprt on tbl1.col1 > tprt.col1 -order by tbl1.col1, tprt.col1; - col1 | col1 -------+------ - 501 | 10 - 501 | 20 - 505 | 10 - 505 | 20 - 505 | 501 - 505 | 502 - 1001 | 10 - 1001 | 20 - 1001 | 501 - 1001 | 502 - 1001 | 505 - 1010 | 10 - 1010 | 20 - 1010 | 501 - 1010 | 502 - 1010 | 505 - 1010 | 1001 - 1011 | 10 - 1011 | 20 - 1011 | 501 - 1011 | 502 - 1011 | 505 - 1011 | 1001 -(23 rows) - -select tbl1.col1, tprt.col1 from tbl1 -inner join tprt on tbl1.col1 = tprt.col1 -order by tbl1.col1, tprt.col1; - col1 | col1 -------+------ - 501 | 501 - 505 | 505 - 1001 | 1001 -(3 rows) - --- Last partition -delete from tbl1; -insert into tbl1 values (4400); -explain (analyze, costs off, summary off, timing off) -select * from tbl1 join tprt on tbl1.col1 < tprt.col1; - QUERY PLAN --------------------------------------------------------------------------- - Nested Loop (actual rows=1 loops=1) - -> Seq Scan on tbl1 (actual rows=1 loops=1) - -> Append (actual rows=1 loops=1) - -> Index Scan using tprt1_idx on tprt_1 (never executed) - Index Cond: (col1 > tbl1.col1) - -> Index Scan using tprt2_idx on tprt_2 (never executed) - Index Cond: (col1 > tbl1.col1) - -> Index Scan using tprt3_idx on tprt_3 (never executed) - Index Cond: (col1 > tbl1.col1) - -> Index Scan using tprt4_idx on tprt_4 (never executed) - Index Cond: (col1 > tbl1.col1) - -> Index Scan using tprt5_idx on tprt_5 (never executed) - Index Cond: (col1 > tbl1.col1) - -> Index Scan using tprt6_idx on tprt_6 (actual rows=1 loops=1) - Index Cond: (col1 > tbl1.col1) -(15 rows) - -select tbl1.col1, tprt.col1 from tbl1 -inner join tprt on tbl1.col1 < tprt.col1 -order by tbl1.col1, tprt.col1; - col1 | col1 -------+------ - 4400 | 4500 -(1 row) - --- No matching partition -delete from tbl1; -insert into tbl1 values (10000); -explain (analyze, costs off, summary off, timing off) -select * from tbl1 join tprt on tbl1.col1 = tprt.col1; - QUERY PLAN -------------------------------------------------------------------- - Nested Loop (actual rows=0 loops=1) - -> Seq Scan on tbl1 (actual rows=1 loops=1) - -> Append (actual rows=0 loops=1) - -> Index Scan using tprt1_idx on tprt_1 (never executed) - Index Cond: (col1 = tbl1.col1) - -> Index Scan using tprt2_idx on tprt_2 (never executed) - Index Cond: (col1 = tbl1.col1) - -> Index Scan using tprt3_idx on tprt_3 (never executed) - Index Cond: (col1 = tbl1.col1) - -> Index Scan using tprt4_idx on tprt_4 (never executed) - Index Cond: (col1 = tbl1.col1) - -> Index Scan using tprt5_idx on tprt_5 (never executed) - Index Cond: (col1 = tbl1.col1) - -> Index Scan using tprt6_idx on tprt_6 (never executed) - Index Cond: (col1 = tbl1.col1) -(15 rows) - -select tbl1.col1, tprt.col1 from tbl1 -inner join tprt on tbl1.col1 = tprt.col1 -order by tbl1.col1, tprt.col1; - col1 | col1 -------+------ -(0 rows) - -drop table tbl1, tprt; --- Test with columns defined in varying orders between each level -create table part_abc (a int not null, b int not null, c int not null) partition by list (a); -create table part_bac (b int not null, a int not null, c int not null) partition by list (b); -create table part_cab (c int not null, a int not null, b int not null) partition by list (c); -create table part_abc_p1 (a int not null, b int not null, c int not null); -alter table part_abc attach partition part_bac for values in(1); -alter table part_bac attach partition part_cab for values in(2); -alter table part_cab attach partition part_abc_p1 for values in(3); -prepare part_abc_q1 (int, int, int) as -select * from part_abc where a = $1 and b = $2 and c = $3; --- Single partition should be scanned. -explain (analyze, costs off, summary off, timing off) execute part_abc_q1 (1, 2, 3); - QUERY PLAN ----------------------------------------------------------- - Seq Scan on part_abc_p1 part_abc (actual rows=0 loops=1) - Filter: ((a = $1) AND (b = $2) AND (c = $3)) -(2 rows) - -deallocate part_abc_q1; -drop table part_abc; --- Ensure that an Append node properly handles a sub-partitioned table --- matching without any of its leaf partitions matching the clause. -create table listp (a int, b int) partition by list (a); -create table listp_1 partition of listp for values in(1) partition by list (b); -create table listp_1_1 partition of listp_1 for values in(1); -create table listp_2 partition of listp for values in(2) partition by list (b); -create table listp_2_1 partition of listp_2 for values in(2); -select * from listp where b = 1; - a | b ----+--- -(0 rows) - --- Ensure that an Append node properly can handle selection of all first level --- partitions before finally detecting the correct set of 2nd level partitions --- which match the given parameter. -prepare q1 (int,int) as select * from listp where b in ($1,$2); -explain (analyze, costs off, summary off, timing off) execute q1 (1,1); - QUERY PLAN -------------------------------------------------------------- - Append (actual rows=0 loops=1) - Subplans Removed: 1 - -> Seq Scan on listp_1_1 listp_1 (actual rows=0 loops=1) - Filter: (b = ANY (ARRAY[$1, $2])) -(4 rows) - -explain (analyze, costs off, summary off, timing off) execute q1 (2,2); - QUERY PLAN -------------------------------------------------------------- - Append (actual rows=0 loops=1) - Subplans Removed: 1 - -> Seq Scan on listp_2_1 listp_1 (actual rows=0 loops=1) - Filter: (b = ANY (ARRAY[$1, $2])) -(4 rows) - --- Try with no matching partitions. -explain (analyze, costs off, summary off, timing off) execute q1 (0,0); - QUERY PLAN --------------------------------- - Append (actual rows=0 loops=1) - Subplans Removed: 2 -(2 rows) - -deallocate q1; --- Test more complex cases where a not-equal condition further eliminates partitions. -prepare q1 (int,int,int,int) as select * from listp where b in($1,$2) and $3 <> b and $4 <> b; --- Both partitions allowed by IN clause, but one disallowed by <> clause -explain (analyze, costs off, summary off, timing off) execute q1 (1,2,2,0); - QUERY PLAN -------------------------------------------------------------------------- - Append (actual rows=0 loops=1) - Subplans Removed: 1 - -> Seq Scan on listp_1_1 listp_1 (actual rows=0 loops=1) - Filter: ((b = ANY (ARRAY[$1, $2])) AND ($3 <> b) AND ($4 <> b)) -(4 rows) - --- Both partitions allowed by IN clause, then both excluded again by <> clauses. -explain (analyze, costs off, summary off, timing off) execute q1 (1,2,2,1); - QUERY PLAN --------------------------------- - Append (actual rows=0 loops=1) - Subplans Removed: 2 -(2 rows) - --- Ensure Params that evaluate to NULL properly prune away all partitions -explain (analyze, costs off, summary off, timing off) -select * from listp where a = (select null::int); - QUERY PLAN ------------------------------------------------------- - Append (actual rows=0 loops=1) - InitPlan 1 - -> Result (actual rows=1 loops=1) - -> Seq Scan on listp_1_1 listp_1 (never executed) - Filter: (a = (InitPlan 1).col1) - -> Seq Scan on listp_2_1 listp_2 (never executed) - Filter: (a = (InitPlan 1).col1) -(7 rows) - -drop table listp; --- --- check that stable query clauses are only used in run-time pruning --- -create table stable_qual_pruning (a timestamp) partition by range (a); -create table stable_qual_pruning1 partition of stable_qual_pruning - for values from ('2000-01-01') to ('2000-02-01'); -create table stable_qual_pruning2 partition of stable_qual_pruning - for values from ('2000-02-01') to ('2000-03-01'); -create table stable_qual_pruning3 partition of stable_qual_pruning - for values from ('3000-02-01') to ('3000-03-01'); --- comparison against a stable value requires run-time pruning -explain (analyze, costs off, summary off, timing off) -select * from stable_qual_pruning where a < localtimestamp; - QUERY PLAN --------------------------------------------------------------------------------------- - Append (actual rows=0 loops=1) - Subplans Removed: 1 - -> Seq Scan on stable_qual_pruning1 stable_qual_pruning_1 (actual rows=0 loops=1) - Filter: (a < LOCALTIMESTAMP) - -> Seq Scan on stable_qual_pruning2 stable_qual_pruning_2 (actual rows=0 loops=1) - Filter: (a < LOCALTIMESTAMP) -(6 rows) - --- timestamp < timestamptz comparison is only stable, not immutable -explain (analyze, costs off, summary off, timing off) -select * from stable_qual_pruning where a < '2000-02-01'::timestamptz; - QUERY PLAN --------------------------------------------------------------------------------------- - Append (actual rows=0 loops=1) - Subplans Removed: 2 - -> Seq Scan on stable_qual_pruning1 stable_qual_pruning_1 (actual rows=0 loops=1) - Filter: (a < 'Tue Feb 01 00:00:00 2000 PST'::timestamp with time zone) -(4 rows) - --- check ScalarArrayOp cases -explain (analyze, costs off, summary off, timing off) -select * from stable_qual_pruning - where a = any(array['2010-02-01', '2020-01-01']::timestamp[]); - QUERY PLAN --------------------------------- - Result (actual rows=0 loops=1) - One-Time Filter: false -(2 rows) - -explain (analyze, costs off, summary off, timing off) -select * from stable_qual_pruning - where a = any(array['2000-02-01', '2010-01-01']::timestamp[]); - QUERY PLAN ----------------------------------------------------------------------------------------------------------------- - Seq Scan on stable_qual_pruning2 stable_qual_pruning (actual rows=0 loops=1) - Filter: (a = ANY ('{"Tue Feb 01 00:00:00 2000","Fri Jan 01 00:00:00 2010"}'::timestamp without time zone[])) -(2 rows) - -explain (analyze, costs off, summary off, timing off) -select * from stable_qual_pruning - where a = any(array['2000-02-01', localtimestamp]::timestamp[]); - QUERY PLAN ------------------------------------------------------------------------------------------------------------- - Append (actual rows=0 loops=1) - Subplans Removed: 2 - -> Seq Scan on stable_qual_pruning2 stable_qual_pruning_1 (actual rows=0 loops=1) - Filter: (a = ANY (ARRAY['Tue Feb 01 00:00:00 2000'::timestamp without time zone, LOCALTIMESTAMP])) -(4 rows) - -explain (analyze, costs off, summary off, timing off) -select * from stable_qual_pruning - where a = any(array['2010-02-01', '2020-01-01']::timestamptz[]); - QUERY PLAN --------------------------------- - Append (actual rows=0 loops=1) - Subplans Removed: 3 -(2 rows) - -explain (analyze, costs off, summary off, timing off) -select * from stable_qual_pruning - where a = any(array['2000-02-01', '2010-01-01']::timestamptz[]); - QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------- - Append (actual rows=0 loops=1) - Subplans Removed: 2 - -> Seq Scan on stable_qual_pruning2 stable_qual_pruning_1 (actual rows=0 loops=1) - Filter: (a = ANY ('{"Tue Feb 01 00:00:00 2000 PST","Fri Jan 01 00:00:00 2010 PST"}'::timestamp with time zone[])) -(4 rows) - -explain (analyze, costs off, summary off, timing off) -select * from stable_qual_pruning - where a = any(null::timestamptz[]); - QUERY PLAN --------------------------------------------------------------------------------------- - Append (actual rows=0 loops=1) - -> Seq Scan on stable_qual_pruning1 stable_qual_pruning_1 (actual rows=0 loops=1) - Filter: (a = ANY (NULL::timestamp with time zone[])) - -> Seq Scan on stable_qual_pruning2 stable_qual_pruning_2 (actual rows=0 loops=1) - Filter: (a = ANY (NULL::timestamp with time zone[])) - -> Seq Scan on stable_qual_pruning3 stable_qual_pruning_3 (actual rows=0 loops=1) - Filter: (a = ANY (NULL::timestamp with time zone[])) -(7 rows) - -drop table stable_qual_pruning; --- --- Check that pruning with composite range partitioning works correctly when --- it must ignore clauses for trailing keys once it has seen a clause with --- non-inclusive operator for an earlier key --- -create table mc3p (a int, b int, c int) partition by range (a, abs(b), c); -create table mc3p0 partition of mc3p - for values from (0, 0, 0) to (0, maxvalue, maxvalue); -create table mc3p1 partition of mc3p - for values from (1, 1, 1) to (2, minvalue, minvalue); -create table mc3p2 partition of mc3p - for values from (2, minvalue, minvalue) to (3, maxvalue, maxvalue); -insert into mc3p values (0, 1, 1), (1, 1, 1), (2, 1, 1); -explain (analyze, costs off, summary off, timing off) -select * from mc3p where a < 3 and abs(b) = 1; - QUERY PLAN --------------------------------------------------------- - Append (actual rows=3 loops=1) - -> Seq Scan on mc3p0 mc3p_1 (actual rows=1 loops=1) - Filter: ((a < 3) AND (abs(b) = 1)) - -> Seq Scan on mc3p1 mc3p_2 (actual rows=1 loops=1) - Filter: ((a < 3) AND (abs(b) = 1)) - -> Seq Scan on mc3p2 mc3p_3 (actual rows=1 loops=1) - Filter: ((a < 3) AND (abs(b) = 1)) -(7 rows) - --- --- Check that pruning with composite range partitioning works correctly when --- a combination of runtime parameters is specified, not all of whose values --- are available at the same time --- -prepare ps1 as - select * from mc3p where a = $1 and abs(b) < (select 3); -explain (analyze, costs off, summary off, timing off) -execute ps1(1); - QUERY PLAN -------------------------------------------------------------- - Append (actual rows=1 loops=1) - Subplans Removed: 2 - InitPlan 1 - -> Result (actual rows=1 loops=1) - -> Seq Scan on mc3p1 mc3p_1 (actual rows=1 loops=1) - Filter: ((a = $1) AND (abs(b) < (InitPlan 1).col1)) -(6 rows) - -deallocate ps1; -prepare ps2 as - select * from mc3p where a <= $1 and abs(b) < (select 3); -explain (analyze, costs off, summary off, timing off) -execute ps2(1); - QUERY PLAN --------------------------------------------------------------- - Append (actual rows=2 loops=1) - Subplans Removed: 1 - InitPlan 1 - -> Result (actual rows=1 loops=1) - -> Seq Scan on mc3p0 mc3p_1 (actual rows=1 loops=1) - Filter: ((a <= $1) AND (abs(b) < (InitPlan 1).col1)) - -> Seq Scan on mc3p1 mc3p_2 (actual rows=1 loops=1) - Filter: ((a <= $1) AND (abs(b) < (InitPlan 1).col1)) -(8 rows) - -deallocate ps2; -drop table mc3p; --- Ensure runtime pruning works with initplans params with boolean types -create table boolvalues (value bool not null); -insert into boolvalues values('t'),('f'); -create table boolp (a bool) partition by list (a); -create table boolp_t partition of boolp for values in('t'); -create table boolp_f partition of boolp for values in('f'); -explain (analyze, costs off, summary off, timing off) -select * from boolp where a = (select value from boolvalues where value); - QUERY PLAN ------------------------------------------------------------ - Append (actual rows=0 loops=1) - InitPlan 1 - -> Seq Scan on boolvalues (actual rows=1 loops=1) - Filter: value - Rows Removed by Filter: 1 - -> Seq Scan on boolp_f boolp_1 (never executed) - Filter: (a = (InitPlan 1).col1) - -> Seq Scan on boolp_t boolp_2 (actual rows=0 loops=1) - Filter: (a = (InitPlan 1).col1) -(9 rows) - -explain (analyze, costs off, summary off, timing off) -select * from boolp where a = (select value from boolvalues where not value); - QUERY PLAN ------------------------------------------------------------ - Append (actual rows=0 loops=1) - InitPlan 1 - -> Seq Scan on boolvalues (actual rows=1 loops=1) - Filter: (NOT value) - Rows Removed by Filter: 1 - -> Seq Scan on boolp_f boolp_1 (actual rows=0 loops=1) - Filter: (a = (InitPlan 1).col1) - -> Seq Scan on boolp_t boolp_2 (never executed) - Filter: (a = (InitPlan 1).col1) -(9 rows) - -drop table boolp; --- --- Test run-time pruning of MergeAppend subnodes --- -set enable_seqscan = off; -set enable_sort = off; -create table ma_test (a int, b int) partition by range (a); -create table ma_test_p1 partition of ma_test for values from (0) to (10); -create table ma_test_p2 partition of ma_test for values from (10) to (20); -create table ma_test_p3 partition of ma_test for values from (20) to (30); -insert into ma_test select x,x from generate_series(0,29) t(x); -create index on ma_test (b); -analyze ma_test; -prepare mt_q1 (int) as select a from ma_test where a >= $1 and a % 10 = 5 order by b; -explain (analyze, costs off, summary off, timing off) execute mt_q1(15); - QUERY PLAN ------------------------------------------------------------------------------------------ - Merge Append (actual rows=2 loops=1) - Sort Key: ma_test.b - Subplans Removed: 1 - -> Index Scan using ma_test_p2_b_idx on ma_test_p2 ma_test_1 (actual rows=1 loops=1) - Filter: ((a >= $1) AND ((a % 10) = 5)) - Rows Removed by Filter: 9 - -> Index Scan using ma_test_p3_b_idx on ma_test_p3 ma_test_2 (actual rows=1 loops=1) - Filter: ((a >= $1) AND ((a % 10) = 5)) - Rows Removed by Filter: 9 -(9 rows) - -execute mt_q1(15); - a ----- - 15 - 25 -(2 rows) - -explain (analyze, costs off, summary off, timing off) execute mt_q1(25); - QUERY PLAN ------------------------------------------------------------------------------------------ - Merge Append (actual rows=1 loops=1) - Sort Key: ma_test.b - Subplans Removed: 2 - -> Index Scan using ma_test_p3_b_idx on ma_test_p3 ma_test_1 (actual rows=1 loops=1) - Filter: ((a >= $1) AND ((a % 10) = 5)) - Rows Removed by Filter: 9 -(6 rows) - -execute mt_q1(25); - a ----- - 25 -(1 row) - --- Ensure MergeAppend behaves correctly when no subplans match -explain (analyze, costs off, summary off, timing off) execute mt_q1(35); - QUERY PLAN --------------------------------------- - Merge Append (actual rows=0 loops=1) - Sort Key: ma_test.b - Subplans Removed: 3 -(3 rows) - -execute mt_q1(35); - a ---- -(0 rows) - -deallocate mt_q1; -prepare mt_q2 (int) as select * from ma_test where a >= $1 order by b limit 1; --- Ensure output list looks sane when the MergeAppend has no subplans. -explain (analyze, verbose, costs off, summary off, timing off) execute mt_q2 (35); - QUERY PLAN --------------------------------------------- - Limit (actual rows=0 loops=1) - Output: ma_test.a, ma_test.b - -> Merge Append (actual rows=0 loops=1) - Sort Key: ma_test.b - Subplans Removed: 3 -(5 rows) - -deallocate mt_q2; --- ensure initplan params properly prune partitions -explain (analyze, costs off, summary off, timing off) select * from ma_test where a >= (select min(b) from ma_test_p2) order by b; - QUERY PLAN ------------------------------------------------------------------------------------------------ - Merge Append (actual rows=20 loops=1) - Sort Key: ma_test.b - InitPlan 2 - -> Result (actual rows=1 loops=1) - InitPlan 1 - -> Limit (actual rows=1 loops=1) - -> Index Scan using ma_test_p2_b_idx on ma_test_p2 (actual rows=1 loops=1) - Index Cond: (b IS NOT NULL) - -> Index Scan using ma_test_p1_b_idx on ma_test_p1 ma_test_1 (never executed) - Filter: (a >= (InitPlan 2).col1) - -> Index Scan using ma_test_p2_b_idx on ma_test_p2 ma_test_2 (actual rows=10 loops=1) - Filter: (a >= (InitPlan 2).col1) - -> Index Scan using ma_test_p3_b_idx on ma_test_p3 ma_test_3 (actual rows=10 loops=1) - Filter: (a >= (InitPlan 2).col1) -(14 rows) - -reset enable_seqscan; -reset enable_sort; -drop table ma_test; -reset enable_indexonlyscan; --- --- check that pruning works properly when the partition key is of a --- pseudotype --- --- array type list partition key -create table pp_arrpart (a int[]) partition by list (a); -create table pp_arrpart1 partition of pp_arrpart for values in ('{1}'); -create table pp_arrpart2 partition of pp_arrpart for values in ('{2, 3}', '{4, 5}'); -explain (costs off) select * from pp_arrpart where a = '{1}'; - QUERY PLAN ------------------------------------- - Seq Scan on pp_arrpart1 pp_arrpart - Filter: (a = '{1}'::integer[]) -(2 rows) - -explain (costs off) select * from pp_arrpart where a = '{1, 2}'; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -explain (costs off) select * from pp_arrpart where a in ('{4, 5}', '{1}'); - QUERY PLAN ----------------------------------------------------------------------- - Append - -> Seq Scan on pp_arrpart1 pp_arrpart_1 - Filter: ((a = '{4,5}'::integer[]) OR (a = '{1}'::integer[])) - -> Seq Scan on pp_arrpart2 pp_arrpart_2 - Filter: ((a = '{4,5}'::integer[]) OR (a = '{1}'::integer[])) -(5 rows) - -explain (costs off) update pp_arrpart set a = a where a = '{1}'; - QUERY PLAN --------------------------------------------- - Update on pp_arrpart - Update on pp_arrpart1 pp_arrpart_1 - -> Seq Scan on pp_arrpart1 pp_arrpart_1 - Filter: (a = '{1}'::integer[]) -(4 rows) - -explain (costs off) delete from pp_arrpart where a = '{1}'; - QUERY PLAN --------------------------------------------- - Delete on pp_arrpart - Delete on pp_arrpart1 pp_arrpart_1 - -> Seq Scan on pp_arrpart1 pp_arrpart_1 - Filter: (a = '{1}'::integer[]) -(4 rows) - -drop table pp_arrpart; --- array type hash partition key -create table pph_arrpart (a int[]) partition by hash (a); -create table pph_arrpart1 partition of pph_arrpart for values with (modulus 2, remainder 0); -create table pph_arrpart2 partition of pph_arrpart for values with (modulus 2, remainder 1); -insert into pph_arrpart values ('{1}'), ('{1, 2}'), ('{4, 5}'); -select tableoid::regclass, * from pph_arrpart order by 1; - tableoid | a ---------------+------- - pph_arrpart1 | {1,2} - pph_arrpart1 | {4,5} - pph_arrpart2 | {1} -(3 rows) - -explain (costs off) select * from pph_arrpart where a = '{1}'; - QUERY PLAN --------------------------------------- - Seq Scan on pph_arrpart2 pph_arrpart - Filter: (a = '{1}'::integer[]) -(2 rows) - -explain (costs off) select * from pph_arrpart where a = '{1, 2}'; - QUERY PLAN --------------------------------------- - Seq Scan on pph_arrpart1 pph_arrpart - Filter: (a = '{1,2}'::integer[]) -(2 rows) - -explain (costs off) select * from pph_arrpart where a in ('{4, 5}', '{1}'); - QUERY PLAN ----------------------------------------------------------------------- - Append - -> Seq Scan on pph_arrpart1 pph_arrpart_1 - Filter: ((a = '{4,5}'::integer[]) OR (a = '{1}'::integer[])) - -> Seq Scan on pph_arrpart2 pph_arrpart_2 - Filter: ((a = '{4,5}'::integer[]) OR (a = '{1}'::integer[])) -(5 rows) - -drop table pph_arrpart; --- enum type list partition key -create type pp_colors as enum ('green', 'blue', 'black'); -create table pp_enumpart (a pp_colors) partition by list (a); -create table pp_enumpart_green partition of pp_enumpart for values in ('green'); -create table pp_enumpart_blue partition of pp_enumpart for values in ('blue'); -explain (costs off) select * from pp_enumpart where a = 'blue'; - QUERY PLAN ------------------------------------------- - Seq Scan on pp_enumpart_blue pp_enumpart - Filter: (a = 'blue'::pp_colors) -(2 rows) - -explain (costs off) select * from pp_enumpart where a = 'black'; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -drop table pp_enumpart; -drop type pp_colors; --- record type as partition key -create type pp_rectype as (a int, b int); -create table pp_recpart (a pp_rectype) partition by list (a); -create table pp_recpart_11 partition of pp_recpart for values in ('(1,1)'); -create table pp_recpart_23 partition of pp_recpart for values in ('(2,3)'); -explain (costs off) select * from pp_recpart where a = '(1,1)'::pp_rectype; - QUERY PLAN --------------------------------------- - Seq Scan on pp_recpart_11 pp_recpart - Filter: (a = '(1,1)'::pp_rectype) -(2 rows) - -explain (costs off) select * from pp_recpart where a = '(1,2)'::pp_rectype; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -drop table pp_recpart; -drop type pp_rectype; --- range type partition key -create table pp_intrangepart (a int4range) partition by list (a); -create table pp_intrangepart12 partition of pp_intrangepart for values in ('[1,2]'); -create table pp_intrangepart2inf partition of pp_intrangepart for values in ('[2,)'); -explain (costs off) select * from pp_intrangepart where a = '[1,2]'::int4range; - QUERY PLAN ------------------------------------------------ - Seq Scan on pp_intrangepart12 pp_intrangepart - Filter: (a = '[1,3)'::int4range) -(2 rows) - -explain (costs off) select * from pp_intrangepart where a = '(1,2)'::int4range; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -drop table pp_intrangepart; --- --- Ensure the enable_partition_prune GUC properly disables partition pruning. --- -create table pp_lp (a int, value int) partition by list (a); -create table pp_lp1 partition of pp_lp for values in(1); -create table pp_lp2 partition of pp_lp for values in(2); -explain (costs off) select * from pp_lp where a = 1; - QUERY PLAN --------------------------- - Seq Scan on pp_lp1 pp_lp - Filter: (a = 1) -(2 rows) - -explain (costs off) update pp_lp set value = 10 where a = 1; - QUERY PLAN ----------------------------------- - Update on pp_lp - Update on pp_lp1 pp_lp_1 - -> Seq Scan on pp_lp1 pp_lp_1 - Filter: (a = 1) -(4 rows) - -explain (costs off) delete from pp_lp where a = 1; - QUERY PLAN ----------------------------------- - Delete on pp_lp - Delete on pp_lp1 pp_lp_1 - -> Seq Scan on pp_lp1 pp_lp_1 - Filter: (a = 1) -(4 rows) - -set enable_partition_pruning = off; -set constraint_exclusion = 'partition'; -- this should not affect the result. -explain (costs off) select * from pp_lp where a = 1; - QUERY PLAN ----------------------------------- - Append - -> Seq Scan on pp_lp1 pp_lp_1 - Filter: (a = 1) - -> Seq Scan on pp_lp2 pp_lp_2 - Filter: (a = 1) -(5 rows) - -explain (costs off) update pp_lp set value = 10 where a = 1; - QUERY PLAN ----------------------------------------- - Update on pp_lp - Update on pp_lp1 pp_lp_1 - Update on pp_lp2 pp_lp_2 - -> Append - -> Seq Scan on pp_lp1 pp_lp_1 - Filter: (a = 1) - -> Seq Scan on pp_lp2 pp_lp_2 - Filter: (a = 1) -(8 rows) - -explain (costs off) delete from pp_lp where a = 1; - QUERY PLAN ----------------------------------------- - Delete on pp_lp - Delete on pp_lp1 pp_lp_1 - Delete on pp_lp2 pp_lp_2 - -> Append - -> Seq Scan on pp_lp1 pp_lp_1 - Filter: (a = 1) - -> Seq Scan on pp_lp2 pp_lp_2 - Filter: (a = 1) -(8 rows) - -set constraint_exclusion = 'off'; -- this should not affect the result. -explain (costs off) select * from pp_lp where a = 1; - QUERY PLAN ----------------------------------- - Append - -> Seq Scan on pp_lp1 pp_lp_1 - Filter: (a = 1) - -> Seq Scan on pp_lp2 pp_lp_2 - Filter: (a = 1) -(5 rows) - -explain (costs off) update pp_lp set value = 10 where a = 1; - QUERY PLAN ----------------------------------------- - Update on pp_lp - Update on pp_lp1 pp_lp_1 - Update on pp_lp2 pp_lp_2 - -> Append - -> Seq Scan on pp_lp1 pp_lp_1 - Filter: (a = 1) - -> Seq Scan on pp_lp2 pp_lp_2 - Filter: (a = 1) -(8 rows) - -explain (costs off) delete from pp_lp where a = 1; - QUERY PLAN ----------------------------------------- - Delete on pp_lp - Delete on pp_lp1 pp_lp_1 - Delete on pp_lp2 pp_lp_2 - -> Append - -> Seq Scan on pp_lp1 pp_lp_1 - Filter: (a = 1) - -> Seq Scan on pp_lp2 pp_lp_2 - Filter: (a = 1) -(8 rows) - -drop table pp_lp; --- Ensure enable_partition_prune does not affect non-partitioned tables. -create table inh_lp (a int, value int); -create table inh_lp1 (a int, value int, check(a = 1)) inherits (inh_lp); -NOTICE: merging column "a" with inherited definition -NOTICE: merging column "value" with inherited definition -create table inh_lp2 (a int, value int, check(a = 2)) inherits (inh_lp); -NOTICE: merging column "a" with inherited definition -NOTICE: merging column "value" with inherited definition -set constraint_exclusion = 'partition'; --- inh_lp2 should be removed in the following 3 cases. -explain (costs off) select * from inh_lp where a = 1; - QUERY PLAN ------------------------------------- - Append - -> Seq Scan on inh_lp inh_lp_1 - Filter: (a = 1) - -> Seq Scan on inh_lp1 inh_lp_2 - Filter: (a = 1) -(5 rows) - -explain (costs off) update inh_lp set value = 10 where a = 1; - QUERY PLAN ------------------------------------------------- - Update on inh_lp - Update on inh_lp inh_lp_1 - Update on inh_lp1 inh_lp_2 - -> Result - -> Append - -> Seq Scan on inh_lp inh_lp_1 - Filter: (a = 1) - -> Seq Scan on inh_lp1 inh_lp_2 - Filter: (a = 1) -(9 rows) - -explain (costs off) delete from inh_lp where a = 1; - QUERY PLAN ------------------------------------------- - Delete on inh_lp - Delete on inh_lp inh_lp_1 - Delete on inh_lp1 inh_lp_2 - -> Append - -> Seq Scan on inh_lp inh_lp_1 - Filter: (a = 1) - -> Seq Scan on inh_lp1 inh_lp_2 - Filter: (a = 1) -(8 rows) - --- Ensure we don't exclude normal relations when we only expect to exclude --- inheritance children -explain (costs off) update inh_lp1 set value = 10 where a = 2; - QUERY PLAN ---------------------------- - Update on inh_lp1 - -> Seq Scan on inh_lp1 - Filter: (a = 2) -(3 rows) - -drop table inh_lp cascade; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to table inh_lp1 -drop cascades to table inh_lp2 -reset enable_partition_pruning; -reset constraint_exclusion; --- Check pruning for a partition tree containing only temporary relations -create temp table pp_temp_parent (a int) partition by list (a); -create temp table pp_temp_part_1 partition of pp_temp_parent for values in (1); -create temp table pp_temp_part_def partition of pp_temp_parent default; -explain (costs off) select * from pp_temp_parent where true; - QUERY PLAN ------------------------------------------------------ - Append - -> Seq Scan on pp_temp_part_1 pp_temp_parent_1 - -> Seq Scan on pp_temp_part_def pp_temp_parent_2 -(3 rows) - -explain (costs off) select * from pp_temp_parent where a = 2; - QUERY PLAN ---------------------------------------------- - Seq Scan on pp_temp_part_def pp_temp_parent - Filter: (a = 2) -(2 rows) - -drop table pp_temp_parent; --- Stress run-time partition pruning a bit more, per bug reports -create temp table p (a int, b int, c int) partition by list (a); -create temp table p1 partition of p for values in (1); -create temp table p2 partition of p for values in (2); -create temp table q (a int, b int, c int) partition by list (a); -create temp table q1 partition of q for values in (1) partition by list (b); -create temp table q11 partition of q1 for values in (1) partition by list (c); -create temp table q111 partition of q11 for values in (1); -create temp table q2 partition of q for values in (2) partition by list (b); -create temp table q21 partition of q2 for values in (1); -create temp table q22 partition of q2 for values in (2); -insert into q22 values (2, 2, 3); -explain (costs off) -select * -from ( - select * from p - union all - select * from q1 - union all - select 1, 1, 1 - ) s(a, b, c) -where s.a = 1 and s.b = 1 and s.c = (select 1); - QUERY PLAN -------------------------------------------------------------------- - Append - InitPlan 1 - -> Result - -> Seq Scan on p1 p - Filter: ((a = 1) AND (b = 1) AND (c = (InitPlan 1).col1)) - -> Seq Scan on q111 q1 - Filter: ((a = 1) AND (b = 1) AND (c = (InitPlan 1).col1)) - -> Result - One-Time Filter: (1 = (InitPlan 1).col1) -(9 rows) - -select * -from ( - select * from p - union all - select * from q1 - union all - select 1, 1, 1 - ) s(a, b, c) -where s.a = 1 and s.b = 1 and s.c = (select 1); - a | b | c ----+---+--- - 1 | 1 | 1 -(1 row) - -prepare q (int, int) as -select * -from ( - select * from p - union all - select * from q1 - union all - select 1, 1, 1 - ) s(a, b, c) -where s.a = $1 and s.b = $2 and s.c = (select 1); -explain (costs off) execute q (1, 1); - QUERY PLAN ------------------------------------------------------------------------------- - Append - Subplans Removed: 1 - InitPlan 1 - -> Result - -> Seq Scan on p1 p - Filter: ((a = $1) AND (b = $2) AND (c = (InitPlan 1).col1)) - -> Seq Scan on q111 q1 - Filter: ((a = $1) AND (b = $2) AND (c = (InitPlan 1).col1)) - -> Result - One-Time Filter: ((1 = $1) AND (1 = $2) AND (1 = (InitPlan 1).col1)) -(10 rows) - -execute q (1, 1); - a | b | c ----+---+--- - 1 | 1 | 1 -(1 row) - -drop table p, q; --- Ensure run-time pruning works correctly when we match a partitioned table --- on the first level but find no matching partitions on the second level. -create table listp (a int, b int) partition by list (a); -create table listp1 partition of listp for values in(1); -create table listp2 partition of listp for values in(2) partition by list(b); -create table listp2_10 partition of listp2 for values in (10); -explain (analyze, costs off, summary off, timing off) -select * from listp where a = (select 2) and b <> 10; - QUERY PLAN ---------------------------------------------------- - Seq Scan on listp1 listp (actual rows=0 loops=1) - Filter: ((b <> 10) AND (a = (InitPlan 1).col1)) - InitPlan 1 - -> Result (never executed) -(4 rows) - --- --- check that a partition directly accessed in a query is excluded with --- constraint_exclusion = on --- --- turn off partition pruning, so that it doesn't interfere -set enable_partition_pruning to off; --- setting constraint_exclusion to 'partition' disables exclusion -set constraint_exclusion to 'partition'; -explain (costs off) select * from listp1 where a = 2; - QUERY PLAN --------------------- - Seq Scan on listp1 - Filter: (a = 2) -(2 rows) - -explain (costs off) update listp1 set a = 1 where a = 2; - QUERY PLAN --------------------------- - Update on listp1 - -> Seq Scan on listp1 - Filter: (a = 2) -(3 rows) - --- constraint exclusion enabled -set constraint_exclusion to 'on'; -explain (costs off) select * from listp1 where a = 2; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -explain (costs off) update listp1 set a = 1 where a = 2; - QUERY PLAN --------------------------------- - Update on listp1 - -> Result - One-Time Filter: false -(3 rows) - -reset constraint_exclusion; -reset enable_partition_pruning; -drop table listp; --- Ensure run-time pruning works correctly for nested Append nodes -set parallel_setup_cost to 0; -set parallel_tuple_cost to 0; -create table listp (a int) partition by list(a); -create table listp_12 partition of listp for values in(1,2) partition by list(a); -create table listp_12_1 partition of listp_12 for values in(1); -create table listp_12_2 partition of listp_12 for values in(2); --- Force the 2nd subnode of the Append to be non-parallel. This results in --- a nested Append node because the mixed parallel / non-parallel paths cannot --- be pulled into the top-level Append. -alter table listp_12_1 set (parallel_workers = 0); --- Ensure that listp_12_2 is not scanned. (The nested Append is not seen in --- the plan as it's pulled in setref.c due to having just a single subnode). -select explain_parallel_append('select * from listp where a = (select 1);'); - explain_parallel_append ----------------------------------------------------------------------- - Gather (actual rows=N loops=N) - Workers Planned: 2 - Workers Launched: N - InitPlan 1 - -> Result (actual rows=N loops=N) - -> Parallel Append (actual rows=N loops=N) - -> Seq Scan on listp_12_1 listp_1 (actual rows=N loops=N) - Filter: (a = (InitPlan 1).col1) - -> Parallel Seq Scan on listp_12_2 listp_2 (never executed) - Filter: (a = (InitPlan 1).col1) -(10 rows) - --- Like the above but throw some more complexity at the planner by adding --- a UNION ALL. We expect both sides of the union not to scan the --- non-required partitions. -select explain_parallel_append( -'select * from listp where a = (select 1) - union all -select * from listp where a = (select 2);'); - explain_parallel_append ------------------------------------------------------------------------------------ - Gather (actual rows=N loops=N) - Workers Planned: 2 - Workers Launched: N - -> Parallel Append (actual rows=N loops=N) - -> Parallel Append (actual rows=N loops=N) - InitPlan 2 - -> Result (actual rows=N loops=N) - -> Seq Scan on listp_12_1 listp_1 (never executed) - Filter: (a = (InitPlan 2).col1) - -> Parallel Seq Scan on listp_12_2 listp_2 (actual rows=N loops=N) - Filter: (a = (InitPlan 2).col1) - -> Parallel Append (actual rows=N loops=N) - InitPlan 1 - -> Result (actual rows=N loops=N) - -> Seq Scan on listp_12_1 listp_4 (actual rows=N loops=N) - Filter: (a = (InitPlan 1).col1) - -> Parallel Seq Scan on listp_12_2 listp_5 (never executed) - Filter: (a = (InitPlan 1).col1) -(18 rows) - -drop table listp; -reset parallel_tuple_cost; -reset parallel_setup_cost; --- Test case for run-time pruning with a nested Merge Append -set enable_sort to 0; -create table rangep (a int, b int) partition by range (a); -create table rangep_0_to_100 partition of rangep for values from (0) to (100) partition by list (b); --- We need 3 sub-partitions. 1 to validate pruning worked and another two --- because a single remaining partition would be pulled up to the main Append. -create table rangep_0_to_100_1 partition of rangep_0_to_100 for values in(1); -create table rangep_0_to_100_2 partition of rangep_0_to_100 for values in(2); -create table rangep_0_to_100_3 partition of rangep_0_to_100 for values in(3); -create table rangep_100_to_200 partition of rangep for values from (100) to (200); -create index on rangep (a); --- Ensure run-time pruning works on the nested Merge Append -explain (analyze on, costs off, timing off, summary off) -select * from rangep where b IN((select 1),(select 2)) order by a; - QUERY PLAN ------------------------------------------------------------------------------------------------------------- - Append (actual rows=0 loops=1) - InitPlan 1 - -> Result (actual rows=1 loops=1) - InitPlan 2 - -> Result (actual rows=1 loops=1) - -> Merge Append (actual rows=0 loops=1) - Sort Key: rangep_2.a - -> Index Scan using rangep_0_to_100_1_a_idx on rangep_0_to_100_1 rangep_2 (actual rows=0 loops=1) - Filter: (b = ANY (ARRAY[(InitPlan 1).col1, (InitPlan 2).col1])) - -> Index Scan using rangep_0_to_100_2_a_idx on rangep_0_to_100_2 rangep_3 (actual rows=0 loops=1) - Filter: (b = ANY (ARRAY[(InitPlan 1).col1, (InitPlan 2).col1])) - -> Index Scan using rangep_0_to_100_3_a_idx on rangep_0_to_100_3 rangep_4 (never executed) - Filter: (b = ANY (ARRAY[(InitPlan 1).col1, (InitPlan 2).col1])) - -> Index Scan using rangep_100_to_200_a_idx on rangep_100_to_200 rangep_5 (actual rows=0 loops=1) - Filter: (b = ANY (ARRAY[(InitPlan 1).col1, (InitPlan 2).col1])) -(15 rows) - -reset enable_sort; -drop table rangep; --- --- Check that gen_prune_steps_from_opexps() works well for various cases of --- clauses for different partition keys --- -create table rp_prefix_test1 (a int, b varchar) partition by range(a, b); -create table rp_prefix_test1_p1 partition of rp_prefix_test1 for values from (1, 'a') to (1, 'b'); -create table rp_prefix_test1_p2 partition of rp_prefix_test1 for values from (2, 'a') to (2, 'b'); --- Don't call get_steps_using_prefix() with the last partition key b plus --- an empty prefix -explain (costs off) select * from rp_prefix_test1 where a <= 1 and b = 'a'; - QUERY PLAN --------------------------------------------------- - Seq Scan on rp_prefix_test1_p1 rp_prefix_test1 - Filter: ((a <= 1) AND ((b)::text = 'a'::text)) -(2 rows) - -create table rp_prefix_test2 (a int, b int, c int) partition by range(a, b, c); -create table rp_prefix_test2_p1 partition of rp_prefix_test2 for values from (1, 1, 0) to (1, 1, 10); -create table rp_prefix_test2_p2 partition of rp_prefix_test2 for values from (2, 2, 0) to (2, 2, 10); --- Don't call get_steps_using_prefix() with the last partition key c plus --- an invalid prefix (ie, b = 1) -explain (costs off) select * from rp_prefix_test2 where a <= 1 and b = 1 and c >= 0; - QUERY PLAN ------------------------------------------------- - Seq Scan on rp_prefix_test2_p1 rp_prefix_test2 - Filter: ((a <= 1) AND (c >= 0) AND (b = 1)) -(2 rows) - -create table rp_prefix_test3 (a int, b int, c int, d int) partition by range(a, b, c, d); -create table rp_prefix_test3_p1 partition of rp_prefix_test3 for values from (1, 1, 1, 0) to (1, 1, 1, 10); -create table rp_prefix_test3_p2 partition of rp_prefix_test3 for values from (2, 2, 2, 0) to (2, 2, 2, 10); --- Test that get_steps_using_prefix() handles a prefix that contains multiple --- clauses for the partition key b (ie, b >= 1 and b >= 2) -explain (costs off) select * from rp_prefix_test3 where a >= 1 and b >= 1 and b >= 2 and c >= 2 and d >= 0; - QUERY PLAN --------------------------------------------------------------------------- - Seq Scan on rp_prefix_test3_p2 rp_prefix_test3 - Filter: ((a >= 1) AND (b >= 1) AND (b >= 2) AND (c >= 2) AND (d >= 0)) -(2 rows) - --- Test that get_steps_using_prefix() handles a prefix that contains multiple --- clauses for the partition key b (ie, b >= 1 and b = 2) (This also tests --- that the caller arranges clauses in that prefix in the required order) -explain (costs off) select * from rp_prefix_test3 where a >= 1 and b >= 1 and b = 2 and c = 2 and d >= 0; - QUERY PLAN ------------------------------------------------------------------------- - Seq Scan on rp_prefix_test3_p2 rp_prefix_test3 - Filter: ((a >= 1) AND (b >= 1) AND (d >= 0) AND (b = 2) AND (c = 2)) -(2 rows) - -drop table rp_prefix_test1; -drop table rp_prefix_test2; -drop table rp_prefix_test3; --- --- Test that get_steps_using_prefix() handles IS NULL clauses correctly --- -create table hp_prefix_test (a int, b int, c int, d int) - partition by hash (a part_test_int4_ops, b part_test_int4_ops, c part_test_int4_ops, d part_test_int4_ops); --- create 8 partitions -select 'create table hp_prefix_test_p' || x::text || ' partition of hp_prefix_test for values with (modulus 8, remainder ' || x::text || ');' -from generate_Series(0,7) x; - ?column? ------------------------------------------------------------------------------------------------------- - create table hp_prefix_test_p0 partition of hp_prefix_test for values with (modulus 8, remainder 0); - create table hp_prefix_test_p1 partition of hp_prefix_test for values with (modulus 8, remainder 1); - create table hp_prefix_test_p2 partition of hp_prefix_test for values with (modulus 8, remainder 2); - create table hp_prefix_test_p3 partition of hp_prefix_test for values with (modulus 8, remainder 3); - create table hp_prefix_test_p4 partition of hp_prefix_test for values with (modulus 8, remainder 4); - create table hp_prefix_test_p5 partition of hp_prefix_test for values with (modulus 8, remainder 5); - create table hp_prefix_test_p6 partition of hp_prefix_test for values with (modulus 8, remainder 6); - create table hp_prefix_test_p7 partition of hp_prefix_test for values with (modulus 8, remainder 7); -(8 rows) - -\gexec -create table hp_prefix_test_p0 partition of hp_prefix_test for values with (modulus 8, remainder 0); -create table hp_prefix_test_p1 partition of hp_prefix_test for values with (modulus 8, remainder 1); -create table hp_prefix_test_p2 partition of hp_prefix_test for values with (modulus 8, remainder 2); -create table hp_prefix_test_p3 partition of hp_prefix_test for values with (modulus 8, remainder 3); -create table hp_prefix_test_p4 partition of hp_prefix_test for values with (modulus 8, remainder 4); -create table hp_prefix_test_p5 partition of hp_prefix_test for values with (modulus 8, remainder 5); -create table hp_prefix_test_p6 partition of hp_prefix_test for values with (modulus 8, remainder 6); -create table hp_prefix_test_p7 partition of hp_prefix_test for values with (modulus 8, remainder 7); --- insert 16 rows, one row for each test to perform. -insert into hp_prefix_test -select - case a when 0 then null else 1 end, - case b when 0 then null else 2 end, - case c when 0 then null else 3 end, - case d when 0 then null else 4 end -from - generate_series(0,1) a, - generate_series(0,1) b, - generate_Series(0,1) c, - generate_Series(0,1) d; --- Ensure partition pruning works correctly for each combination of IS NULL --- and equality quals. This may seem a little excessive, but there have been --- a number of bugs in this area over the years. We make use of row only --- output to reduce the size of the expected results. -\t on -select - 'explain (costs off) select tableoid::regclass,* from hp_prefix_test where ' || - string_agg(c.colname || case when g.s & (1 << c.colpos) = 0 then ' is null' else ' = ' || (colpos+1)::text end, ' and ' order by c.colpos) -from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), generate_Series(0,15) g(s) -group by g.s -order by g.s; - explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null - explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null - explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null - explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null - explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null - explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null - explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null - explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null - explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4 - explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4 - explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4 - explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4 - explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4 - explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4 - explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4 - explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4 - -\gexec -explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null - Seq Scan on hp_prefix_test_p0 hp_prefix_test - Filter: ((a IS NULL) AND (b IS NULL) AND (c IS NULL) AND (d IS NULL)) - -explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null - Seq Scan on hp_prefix_test_p1 hp_prefix_test - Filter: ((b IS NULL) AND (c IS NULL) AND (d IS NULL) AND (a = 1)) - -explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null - Seq Scan on hp_prefix_test_p2 hp_prefix_test - Filter: ((a IS NULL) AND (c IS NULL) AND (d IS NULL) AND (b = 2)) - -explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null - Seq Scan on hp_prefix_test_p4 hp_prefix_test - Filter: ((c IS NULL) AND (d IS NULL) AND (a = 1) AND (b = 2)) - -explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null - Seq Scan on hp_prefix_test_p3 hp_prefix_test - Filter: ((a IS NULL) AND (b IS NULL) AND (d IS NULL) AND (c = 3)) - -explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null - Seq Scan on hp_prefix_test_p7 hp_prefix_test - Filter: ((b IS NULL) AND (d IS NULL) AND (a = 1) AND (c = 3)) - -explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null - Seq Scan on hp_prefix_test_p4 hp_prefix_test - Filter: ((a IS NULL) AND (d IS NULL) AND (b = 2) AND (c = 3)) - -explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null - Seq Scan on hp_prefix_test_p5 hp_prefix_test - Filter: ((d IS NULL) AND (a = 1) AND (b = 2) AND (c = 3)) - -explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4 - Seq Scan on hp_prefix_test_p4 hp_prefix_test - Filter: ((a IS NULL) AND (b IS NULL) AND (c IS NULL) AND (d = 4)) - -explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4 - Seq Scan on hp_prefix_test_p6 hp_prefix_test - Filter: ((b IS NULL) AND (c IS NULL) AND (a = 1) AND (d = 4)) - -explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4 - Seq Scan on hp_prefix_test_p5 hp_prefix_test - Filter: ((a IS NULL) AND (c IS NULL) AND (b = 2) AND (d = 4)) - -explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4 - Seq Scan on hp_prefix_test_p6 hp_prefix_test - Filter: ((c IS NULL) AND (a = 1) AND (b = 2) AND (d = 4)) - -explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4 - Seq Scan on hp_prefix_test_p4 hp_prefix_test - Filter: ((a IS NULL) AND (b IS NULL) AND (c = 3) AND (d = 4)) - -explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4 - Seq Scan on hp_prefix_test_p5 hp_prefix_test - Filter: ((b IS NULL) AND (a = 1) AND (c = 3) AND (d = 4)) - -explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4 - Seq Scan on hp_prefix_test_p6 hp_prefix_test - Filter: ((a IS NULL) AND (b = 2) AND (c = 3) AND (d = 4)) - -explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4 - Seq Scan on hp_prefix_test_p4 hp_prefix_test - Filter: ((a = 1) AND (b = 2) AND (c = 3) AND (d = 4)) - --- And ensure we get exactly 1 row from each. Again, all 16 possible combinations. -select - 'select tableoid::regclass,* from hp_prefix_test where ' || - string_agg(c.colname || case when g.s & (1 << c.colpos) = 0 then ' is null' else ' = ' || (colpos+1)::text end, ' and ' order by c.colpos) -from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), generate_Series(0,15) g(s) -group by g.s -order by g.s; - select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null - select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null - select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null - select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null - select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null - select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null - select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null - select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null - select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4 - select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4 - select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4 - select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4 - select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4 - select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4 - select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4 - select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4 - -\gexec -select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null - hp_prefix_test_p0 | | | | - -select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null - hp_prefix_test_p1 | 1 | | | - -select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null - hp_prefix_test_p2 | | 2 | | - -select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null - hp_prefix_test_p4 | 1 | 2 | | - -select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null - hp_prefix_test_p3 | | | 3 | - -select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null - hp_prefix_test_p7 | 1 | | 3 | - -select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null - hp_prefix_test_p4 | | 2 | 3 | - -select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null - hp_prefix_test_p5 | 1 | 2 | 3 | - -select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4 - hp_prefix_test_p4 | | | | 4 - -select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4 - hp_prefix_test_p6 | 1 | | | 4 - -select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4 - hp_prefix_test_p5 | | 2 | | 4 - -select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4 - hp_prefix_test_p6 | 1 | 2 | | 4 - -select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4 - hp_prefix_test_p4 | | | 3 | 4 - -select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4 - hp_prefix_test_p5 | 1 | | 3 | 4 - -select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4 - hp_prefix_test_p6 | | 2 | 3 | 4 - -select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4 - hp_prefix_test_p4 | 1 | 2 | 3 | 4 - -\t off -drop table hp_prefix_test; --- --- Check that gen_partprune_steps() detects self-contradiction from clauses --- regardless of the order of the clauses (Here we use a custom operator to --- prevent the equivclass.c machinery from reordering the clauses) --- -create operator === ( - leftarg = int4, - rightarg = int4, - procedure = int4eq, - commutator = ===, - hashes -); -create operator class part_test_int4_ops2 -for type int4 -using hash as -operator 1 ===, -function 2 part_hashint4_noop(int4, int8); -create table hp_contradict_test (a int, b int) partition by hash (a part_test_int4_ops2, b part_test_int4_ops2); -create table hp_contradict_test_p1 partition of hp_contradict_test for values with (modulus 2, remainder 0); -create table hp_contradict_test_p2 partition of hp_contradict_test for values with (modulus 2, remainder 1); -explain (costs off) select * from hp_contradict_test where a is null and a === 1 and b === 1; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -explain (costs off) select * from hp_contradict_test where a === 1 and b === 1 and a is null; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -drop table hp_contradict_test; -drop operator class part_test_int4_ops2 using hash; -drop operator ===(int4, int4); -drop function explain_analyze(text); +psql: error: connection to server on socket "/tmp/bBabKAIw6z/.s.PGSQL.19533" failed: No such file or directory + Is the server running locally and accepting connections on that socket? diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/reloptions.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/reloptions.out --- /tmp/cirrus-ci-build/src/test/regress/expected/reloptions.out 2024-09-22 06:28:18.749249658 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/reloptions.out 2024-09-22 06:38:09.896217527 +0000 @@ -1,237 +1,2 @@ --- Simple create -CREATE TABLE reloptions_test(i INT) WITH (FiLLFaCToR=30, - autovacuum_enabled = false, autovacuum_analyze_scale_factor = 0.2); -SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; - reloptions ------------------------------------------------------------------------------- - {fillfactor=30,autovacuum_enabled=false,autovacuum_analyze_scale_factor=0.2} -(1 row) - --- Fail min/max values check -CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=2); -ERROR: value 2 out of bounds for option "fillfactor" -DETAIL: Valid values are between "10" and "100". -CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=110); -ERROR: value 110 out of bounds for option "fillfactor" -DETAIL: Valid values are between "10" and "100". -CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor = -10.0); -ERROR: value -10.0 out of bounds for option "autovacuum_analyze_scale_factor" -DETAIL: Valid values are between "0.000000" and "100.000000". -CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor = 110.0); -ERROR: value 110.0 out of bounds for option "autovacuum_analyze_scale_factor" -DETAIL: Valid values are between "0.000000" and "100.000000". --- Fail when option and namespace do not exist -CREATE TABLE reloptions_test2(i INT) WITH (not_existing_option=2); -ERROR: unrecognized parameter "not_existing_option" -CREATE TABLE reloptions_test2(i INT) WITH (not_existing_namespace.fillfactor=2); -ERROR: unrecognized parameter namespace "not_existing_namespace" --- Fail while setting improper values -CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=-30.1); -ERROR: value -30.1 out of bounds for option "fillfactor" -DETAIL: Valid values are between "10" and "100". -CREATE TABLE reloptions_test2(i INT) WITH (fillfactor='string'); -ERROR: invalid value for integer option "fillfactor": string -CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=true); -ERROR: invalid value for integer option "fillfactor": true -CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled=12); -ERROR: invalid value for boolean option "autovacuum_enabled": 12 -CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled=30.5); -ERROR: invalid value for boolean option "autovacuum_enabled": 30.5 -CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled='string'); -ERROR: invalid value for boolean option "autovacuum_enabled": string -CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor='string'); -ERROR: invalid value for floating point option "autovacuum_analyze_scale_factor": string -CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor=true); -ERROR: invalid value for floating point option "autovacuum_analyze_scale_factor": true --- Fail if option is specified twice -CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=30, fillfactor=40); -ERROR: parameter "fillfactor" specified more than once --- Specifying name only for a non-Boolean option should fail -CREATE TABLE reloptions_test2(i INT) WITH (fillfactor); -ERROR: invalid value for integer option "fillfactor": true --- Simple ALTER TABLE -ALTER TABLE reloptions_test SET (fillfactor=31, - autovacuum_analyze_scale_factor = 0.3); -SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; - reloptions ------------------------------------------------------------------------------- - {autovacuum_enabled=false,fillfactor=31,autovacuum_analyze_scale_factor=0.3} -(1 row) - --- Set boolean option to true without specifying value -ALTER TABLE reloptions_test SET (autovacuum_enabled, fillfactor=32); -SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; - reloptions ------------------------------------------------------------------------------ - {autovacuum_analyze_scale_factor=0.3,autovacuum_enabled=true,fillfactor=32} -(1 row) - --- Check that RESET works well -ALTER TABLE reloptions_test RESET (fillfactor); -SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; - reloptions ---------------------------------------------------------------- - {autovacuum_analyze_scale_factor=0.3,autovacuum_enabled=true} -(1 row) - --- Resetting all values causes the column to become null -ALTER TABLE reloptions_test RESET (autovacuum_enabled, - autovacuum_analyze_scale_factor); -SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass AND - reloptions IS NULL; - reloptions ------------- - -(1 row) - --- RESET fails if a value is specified -ALTER TABLE reloptions_test RESET (fillfactor=12); -ERROR: RESET must not include values for parameters --- We can RESET an invalid option which for some reason is already set -UPDATE pg_class - SET reloptions = '{fillfactor=13,autovacuum_enabled=false,illegal_option=4}' - WHERE oid = 'reloptions_test'::regclass; -ALTER TABLE reloptions_test RESET (illegal_option); -SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; - reloptions ------------------------------------------- - {fillfactor=13,autovacuum_enabled=false} -(1 row) - --- Test vacuum_truncate option -DROP TABLE reloptions_test; -CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) - WITH (vacuum_truncate=false, - toast.vacuum_truncate=false, - autovacuum_enabled=false); -SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; - reloptions --------------------------------------------------- - {vacuum_truncate=false,autovacuum_enabled=false} -(1 row) - -INSERT INTO reloptions_test VALUES (1, NULL), (NULL, NULL); -ERROR: null value in column "i" of relation "reloptions_test" violates not-null constraint -DETAIL: Failing row contains (null, null). --- Do an aggressive vacuum to prevent page-skipping. -VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) reloptions_test; -SELECT pg_relation_size('reloptions_test') > 0; - ?column? ----------- - t -(1 row) - -SELECT reloptions FROM pg_class WHERE oid = - (SELECT reltoastrelid FROM pg_class - WHERE oid = 'reloptions_test'::regclass); - reloptions -------------------------- - {vacuum_truncate=false} -(1 row) - -ALTER TABLE reloptions_test RESET (vacuum_truncate); -SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; - reloptions ----------------------------- - {autovacuum_enabled=false} -(1 row) - -INSERT INTO reloptions_test VALUES (1, NULL), (NULL, NULL); -ERROR: null value in column "i" of relation "reloptions_test" violates not-null constraint -DETAIL: Failing row contains (null, null). --- Do an aggressive vacuum to prevent page-skipping. -VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) reloptions_test; -SELECT pg_relation_size('reloptions_test') = 0; - ?column? ----------- - t -(1 row) - --- Test toast.* options -DROP TABLE reloptions_test; -CREATE TABLE reloptions_test (s VARCHAR) - WITH (toast.autovacuum_vacuum_cost_delay = 23); -SELECT reltoastrelid as toast_oid - FROM pg_class WHERE oid = 'reloptions_test'::regclass \gset -SELECT reloptions FROM pg_class WHERE oid = :toast_oid; - reloptions ------------------------------------ - {autovacuum_vacuum_cost_delay=23} -(1 row) - -ALTER TABLE reloptions_test SET (toast.autovacuum_vacuum_cost_delay = 24); -SELECT reloptions FROM pg_class WHERE oid = :toast_oid; - reloptions ------------------------------------ - {autovacuum_vacuum_cost_delay=24} -(1 row) - -ALTER TABLE reloptions_test RESET (toast.autovacuum_vacuum_cost_delay); -SELECT reloptions FROM pg_class WHERE oid = :toast_oid; - reloptions ------------- - -(1 row) - --- Fail on non-existent options in toast namespace -CREATE TABLE reloptions_test2 (i int) WITH (toast.not_existing_option = 42); -ERROR: unrecognized parameter "not_existing_option" --- Mix TOAST & heap -DROP TABLE reloptions_test; -CREATE TABLE reloptions_test (s VARCHAR) WITH - (toast.autovacuum_vacuum_cost_delay = 23, - autovacuum_vacuum_cost_delay = 24, fillfactor = 40); -SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; - reloptions -------------------------------------------------- - {autovacuum_vacuum_cost_delay=24,fillfactor=40} -(1 row) - -SELECT reloptions FROM pg_class WHERE oid = ( - SELECT reltoastrelid FROM pg_class WHERE oid = 'reloptions_test'::regclass); - reloptions ------------------------------------ - {autovacuum_vacuum_cost_delay=23} -(1 row) - --- --- CREATE INDEX, ALTER INDEX for btrees --- -CREATE INDEX reloptions_test_idx ON reloptions_test (s) WITH (fillfactor=30); -SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test_idx'::regclass; - reloptions ------------------ - {fillfactor=30} -(1 row) - --- Fail when option and namespace do not exist -CREATE INDEX reloptions_test_idx ON reloptions_test (s) - WITH (not_existing_option=2); -ERROR: unrecognized parameter "not_existing_option" -CREATE INDEX reloptions_test_idx ON reloptions_test (s) - WITH (not_existing_ns.fillfactor=2); -ERROR: unrecognized parameter namespace "not_existing_ns" --- Check allowed ranges -CREATE INDEX reloptions_test_idx2 ON reloptions_test (s) WITH (fillfactor=1); -ERROR: value 1 out of bounds for option "fillfactor" -DETAIL: Valid values are between "10" and "100". -CREATE INDEX reloptions_test_idx2 ON reloptions_test (s) WITH (fillfactor=130); -ERROR: value 130 out of bounds for option "fillfactor" -DETAIL: Valid values are between "10" and "100". --- Check ALTER -ALTER INDEX reloptions_test_idx SET (fillfactor=40); -SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test_idx'::regclass; - reloptions ------------------ - {fillfactor=40} -(1 row) - --- Check ALTER on empty reloption list -CREATE INDEX reloptions_test_idx3 ON reloptions_test (s); -ALTER INDEX reloptions_test_idx3 SET (fillfactor=40); -SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test_idx3'::regclass; - reloptions ------------------ - {fillfactor=40} -(1 row) - +psql: error: connection to server on socket "/tmp/bBabKAIw6z/.s.PGSQL.19533" failed: No such file or directory + Is the server running locally and accepting connections on that socket? diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/hash_part.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/hash_part.out --- /tmp/cirrus-ci-build/src/test/regress/expected/hash_part.out 2024-09-22 06:28:18.705020417 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/hash_part.out 2024-09-22 06:38:09.884217553 +0000 @@ -1,114 +1,2 @@ --- --- Hash partitioning. --- --- Use hand-rolled hash functions and operator classes to get predictable --- result on different machines. See the definitions of --- part_test_int4_ops and part_test_text_ops in test_setup.sql. -CREATE TABLE mchash (a int, b text, c jsonb) - PARTITION BY HASH (a part_test_int4_ops, b part_test_text_ops); -CREATE TABLE mchash1 - PARTITION OF mchash FOR VALUES WITH (MODULUS 4, REMAINDER 0); --- invalid OID, no such table -SELECT satisfies_hash_partition(0, 4, 0, NULL); -ERROR: could not open relation with OID 0 --- not partitioned -SELECT satisfies_hash_partition('tenk1'::regclass, 4, 0, NULL); -ERROR: "tenk1" is not a hash partitioned table --- partition rather than the parent -SELECT satisfies_hash_partition('mchash1'::regclass, 4, 0, NULL); -ERROR: "mchash1" is not a hash partitioned table --- invalid modulus -SELECT satisfies_hash_partition('mchash'::regclass, 0, 0, NULL); -ERROR: modulus for hash partition must be an integer value greater than zero --- remainder too small -SELECT satisfies_hash_partition('mchash'::regclass, 1, -1, NULL); -ERROR: remainder for hash partition must be an integer value greater than or equal to zero --- remainder too large -SELECT satisfies_hash_partition('mchash'::regclass, 1, 1, NULL); -ERROR: remainder for hash partition must be less than modulus --- modulus is null -SELECT satisfies_hash_partition('mchash'::regclass, NULL, 0, NULL); - satisfies_hash_partition --------------------------- - f -(1 row) - --- remainder is null -SELECT satisfies_hash_partition('mchash'::regclass, 4, NULL, NULL); - satisfies_hash_partition --------------------------- - f -(1 row) - --- too many arguments -SELECT satisfies_hash_partition('mchash'::regclass, 4, 0, NULL::int, NULL::text, NULL::json); -ERROR: number of partitioning columns (2) does not match number of partition keys provided (3) --- too few arguments -SELECT satisfies_hash_partition('mchash'::regclass, 3, 1, NULL::int); -ERROR: number of partitioning columns (2) does not match number of partition keys provided (1) --- wrong argument type -SELECT satisfies_hash_partition('mchash'::regclass, 2, 1, NULL::int, NULL::int); -ERROR: column 2 of the partition key has type text, but supplied value is of type integer --- ok, should be false -SELECT satisfies_hash_partition('mchash'::regclass, 4, 0, 0, ''::text); - satisfies_hash_partition --------------------------- - f -(1 row) - --- ok, should be true -SELECT satisfies_hash_partition('mchash'::regclass, 4, 0, 2, ''::text); - satisfies_hash_partition --------------------------- - t -(1 row) - --- argument via variadic syntax, should fail because not all partitioning --- columns are of the correct type -SELECT satisfies_hash_partition('mchash'::regclass, 2, 1, - variadic array[1,2]::int[]); -ERROR: column 2 of the partition key has type "text", but supplied value is of type "integer" --- multiple partitioning columns of the same type -CREATE TABLE mcinthash (a int, b int, c jsonb) - PARTITION BY HASH (a part_test_int4_ops, b part_test_int4_ops); --- now variadic should work, should be false -SELECT satisfies_hash_partition('mcinthash'::regclass, 4, 0, - variadic array[0, 0]); - satisfies_hash_partition --------------------------- - f -(1 row) - --- should be true -SELECT satisfies_hash_partition('mcinthash'::regclass, 4, 0, - variadic array[0, 1]); - satisfies_hash_partition --------------------------- - t -(1 row) - --- wrong length -SELECT satisfies_hash_partition('mcinthash'::regclass, 4, 0, - variadic array[]::int[]); -ERROR: number of partitioning columns (2) does not match number of partition keys provided (0) --- wrong type -SELECT satisfies_hash_partition('mcinthash'::regclass, 4, 0, - variadic array[now(), now()]); -ERROR: column 1 of the partition key has type "integer", but supplied value is of type "timestamp with time zone" --- check satisfies_hash_partition passes correct collation -create table text_hashp (a text) partition by hash (a); -create table text_hashp0 partition of text_hashp for values with (modulus 2, remainder 0); -create table text_hashp1 partition of text_hashp for values with (modulus 2, remainder 1); --- The result here should always be true, because 'xxx' must belong to --- one of the two defined partitions -select satisfies_hash_partition('text_hashp'::regclass, 2, 0, 'xxx'::text) OR - satisfies_hash_partition('text_hashp'::regclass, 2, 1, 'xxx'::text) AS satisfies; - satisfies ------------ - t -(1 row) - --- cleanup -DROP TABLE mchash; -DROP TABLE mcinthash; -DROP TABLE text_hashp; +psql: error: connection to server on socket "/tmp/bBabKAIw6z/.s.PGSQL.19533" failed: No such file or directory + Is the server running locally and accepting connections on that socket? diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/indexing.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/indexing.out --- /tmp/cirrus-ci-build/src/test/regress/expected/indexing.out 2024-09-22 06:28:18.705020417 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/indexing.out 2024-09-22 06:38:09.888217545 +0000 @@ -1,1642 +1,2 @@ --- Creating an index on a partitioned table makes the partitions --- automatically get the index -create table idxpart (a int, b int, c text) partition by range (a); --- relhassubclass of a partitioned index is false before creating any partition. --- It will be set after the first partition is created. -create index idxpart_idx on idxpart (a); -select relhassubclass from pg_class where relname = 'idxpart_idx'; - relhassubclass ----------------- - f -(1 row) - --- Check that partitioned indexes are present in pg_indexes. -select indexdef from pg_indexes where indexname like 'idxpart_idx%'; - indexdef ------------------------------------------------------------------ - CREATE INDEX idxpart_idx ON ONLY public.idxpart USING btree (a) -(1 row) - -drop index idxpart_idx; -create table idxpart1 partition of idxpart for values from (0) to (10); -create table idxpart2 partition of idxpart for values from (10) to (100) - partition by range (b); -create table idxpart21 partition of idxpart2 for values from (0) to (100); --- Even with partitions, relhassubclass should not be set if a partitioned --- index is created only on the parent. -create index idxpart_idx on only idxpart(a); -select relhassubclass from pg_class where relname = 'idxpart_idx'; - relhassubclass ----------------- - f -(1 row) - -drop index idxpart_idx; -create index on idxpart (a); -select relname, relkind, relhassubclass, inhparent::regclass - from pg_class left join pg_index ix on (indexrelid = oid) - left join pg_inherits on (ix.indexrelid = inhrelid) - where relname like 'idxpart%' order by relname; - relname | relkind | relhassubclass | inhparent ------------------+---------+----------------+---------------- - idxpart | p | t | - idxpart1 | r | f | - idxpart1_a_idx | i | f | idxpart_a_idx - idxpart2 | p | t | - idxpart21 | r | f | - idxpart21_a_idx | i | f | idxpart2_a_idx - idxpart2_a_idx | I | t | idxpart_a_idx - idxpart_a_idx | I | t | -(8 rows) - -drop table idxpart; --- Some unsupported features -create table idxpart (a int, b int, c text) partition by range (a); -create table idxpart1 partition of idxpart for values from (0) to (10); -create index concurrently on idxpart (a); -ERROR: cannot create index on partitioned table "idxpart" concurrently -drop table idxpart; --- Verify bugfix with query on indexed partitioned table with no partitions --- https://postgr.es/m/20180124162006.pmapfiznhgngwtjf@alvherre.pgsql -CREATE TABLE idxpart (col1 INT) PARTITION BY RANGE (col1); -CREATE INDEX ON idxpart (col1); -CREATE TABLE idxpart_two (col2 INT); -SELECT col2 FROM idxpart_two fk LEFT OUTER JOIN idxpart pk ON (col1 = col2); - col2 ------- -(0 rows) - -DROP table idxpart, idxpart_two; --- Verify bugfix with index rewrite on ALTER TABLE / SET DATA TYPE --- https://postgr.es/m/CAKcux6mxNCGsgATwf5CGMF8g4WSupCXicCVMeKUTuWbyxHOMsQ@mail.gmail.com -CREATE TABLE idxpart (a INT, b TEXT, c INT) PARTITION BY RANGE(a); -CREATE TABLE idxpart1 PARTITION OF idxpart FOR VALUES FROM (MINVALUE) TO (MAXVALUE); -CREATE INDEX partidx_abc_idx ON idxpart (a, b, c); -INSERT INTO idxpart (a, b, c) SELECT i, i, i FROM generate_series(1, 50) i; -ALTER TABLE idxpart ALTER COLUMN c TYPE numeric; -DROP TABLE idxpart; --- If a table without index is attached as partition to a table with --- an index, the index is automatically created -create table idxpart (a int, b int, c text) partition by range (a); -create index idxparti on idxpart (a); -create index idxparti2 on idxpart (b, c); -create table idxpart1 (like idxpart); -\d idxpart1 - Table "public.idxpart1" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | integer | | | - c | text | | | - -alter table idxpart attach partition idxpart1 for values from (0) to (10); -\d idxpart1 - Table "public.idxpart1" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | integer | | | - c | text | | | -Partition of: idxpart FOR VALUES FROM (0) TO (10) -Indexes: - "idxpart1_a_idx" btree (a) - "idxpart1_b_c_idx" btree (b, c) - -\d+ idxpart1_a_idx - Index "public.idxpart1_a_idx" - Column | Type | Key? | Definition | Storage | Stats target ---------+---------+------+------------+---------+-------------- - a | integer | yes | a | plain | -Partition of: idxparti -No partition constraint -btree, for table "public.idxpart1" - -\d+ idxpart1_b_c_idx - Index "public.idxpart1_b_c_idx" - Column | Type | Key? | Definition | Storage | Stats target ---------+---------+------+------------+----------+-------------- - b | integer | yes | b | plain | - c | text | yes | c | extended | -Partition of: idxparti2 -No partition constraint -btree, for table "public.idxpart1" - --- Forbid ALTER TABLE when attaching or detaching an index to a partition. -create index idxpart_c on only idxpart (c); -create index idxpart1_c on idxpart1 (c); -alter table idxpart_c attach partition idxpart1_c for values from (10) to (20); -ERROR: "idxpart_c" is not a partitioned table -alter index idxpart_c attach partition idxpart1_c; -select relname, relpartbound from pg_class - where relname in ('idxpart_c', 'idxpart1_c') - order by relname; - relname | relpartbound -------------+-------------- - idxpart1_c | - idxpart_c | -(2 rows) - -alter table idxpart_c detach partition idxpart1_c; -ERROR: ALTER action DETACH PARTITION cannot be performed on relation "idxpart_c" -DETAIL: This operation is not supported for partitioned indexes. -drop table idxpart; --- If a partition already has an index, don't create a duplicative one -create table idxpart (a int, b int) partition by range (a, b); -create table idxpart1 partition of idxpart for values from (0, 0) to (10, 10); -create index on idxpart1 (a, b); -create index on idxpart (a, b); -\d idxpart1 - Table "public.idxpart1" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | integer | | | -Partition of: idxpart FOR VALUES FROM (0, 0) TO (10, 10) -Indexes: - "idxpart1_a_b_idx" btree (a, b) - -select relname, relkind, relhassubclass, inhparent::regclass - from pg_class left join pg_index ix on (indexrelid = oid) - left join pg_inherits on (ix.indexrelid = inhrelid) - where relname like 'idxpart%' order by relname; - relname | relkind | relhassubclass | inhparent -------------------+---------+----------------+----------------- - idxpart | p | t | - idxpart1 | r | f | - idxpart1_a_b_idx | i | f | idxpart_a_b_idx - idxpart_a_b_idx | I | t | -(4 rows) - -drop table idxpart; --- DROP behavior for partitioned indexes -create table idxpart (a int) partition by range (a); -create index on idxpart (a); -create table idxpart1 partition of idxpart for values from (0) to (10); -drop index idxpart1_a_idx; -- no way -ERROR: cannot drop index idxpart1_a_idx because index idxpart_a_idx requires it -HINT: You can drop index idxpart_a_idx instead. -drop index concurrently idxpart_a_idx; -- unsupported -ERROR: cannot drop partitioned index "idxpart_a_idx" concurrently -drop index idxpart_a_idx; -- both indexes go away -select relname, relkind from pg_class - where relname like 'idxpart%' order by relname; - relname | relkind -----------+--------- - idxpart | p - idxpart1 | r -(2 rows) - -create index on idxpart (a); -drop table idxpart1; -- the index on partition goes away too -select relname, relkind from pg_class - where relname like 'idxpart%' order by relname; - relname | relkind ----------------+--------- - idxpart | p - idxpart_a_idx | I -(2 rows) - -drop table idxpart; --- DROP behavior with temporary partitioned indexes -create temp table idxpart_temp (a int) partition by range (a); -create index on idxpart_temp(a); -create temp table idxpart1_temp partition of idxpart_temp - for values from (0) to (10); -drop index idxpart1_temp_a_idx; -- error -ERROR: cannot drop index idxpart1_temp_a_idx because index idxpart_temp_a_idx requires it -HINT: You can drop index idxpart_temp_a_idx instead. --- non-concurrent drop is enforced here, so it is a valid case. -drop index concurrently idxpart_temp_a_idx; -select relname, relkind from pg_class - where relname like 'idxpart_temp%' order by relname; - relname | relkind ---------------+--------- - idxpart_temp | p -(1 row) - -drop table idxpart_temp; --- ALTER INDEX .. ATTACH, error cases -create table idxpart (a int, b int) partition by range (a, b); -create table idxpart1 partition of idxpart for values from (0, 0) to (10, 10); -create index idxpart_a_b_idx on only idxpart (a, b); -create index idxpart1_a_b_idx on idxpart1 (a, b); -create index idxpart1_tst1 on idxpart1 (b, a); -create index idxpart1_tst2 on idxpart1 using hash (a); -create index idxpart1_tst3 on idxpart1 (a, b) where a > 10; -alter index idxpart attach partition idxpart1; -ERROR: "idxpart" is not an index -alter index idxpart_a_b_idx attach partition idxpart1; -ERROR: "idxpart1" is not an index -alter index idxpart_a_b_idx attach partition idxpart_a_b_idx; -ERROR: cannot attach index "idxpart_a_b_idx" as a partition of index "idxpart_a_b_idx" -DETAIL: Index "idxpart_a_b_idx" is not an index on any partition of table "idxpart". -alter index idxpart_a_b_idx attach partition idxpart1_b_idx; -ERROR: relation "idxpart1_b_idx" does not exist -alter index idxpart_a_b_idx attach partition idxpart1_tst1; -ERROR: cannot attach index "idxpart1_tst1" as a partition of index "idxpart_a_b_idx" -DETAIL: The index definitions do not match. -alter index idxpart_a_b_idx attach partition idxpart1_tst2; -ERROR: cannot attach index "idxpart1_tst2" as a partition of index "idxpart_a_b_idx" -DETAIL: The index definitions do not match. -alter index idxpart_a_b_idx attach partition idxpart1_tst3; -ERROR: cannot attach index "idxpart1_tst3" as a partition of index "idxpart_a_b_idx" -DETAIL: The index definitions do not match. --- OK -alter index idxpart_a_b_idx attach partition idxpart1_a_b_idx; -alter index idxpart_a_b_idx attach partition idxpart1_a_b_idx; -- quiet --- reject dupe -create index idxpart1_2_a_b on idxpart1 (a, b); -alter index idxpart_a_b_idx attach partition idxpart1_2_a_b; -ERROR: cannot attach index "idxpart1_2_a_b" as a partition of index "idxpart_a_b_idx" -DETAIL: Another index is already attached for partition "idxpart1". -drop table idxpart; --- make sure everything's gone -select indexrelid::regclass, indrelid::regclass - from pg_index where indexrelid::regclass::text like 'idxpart%'; - indexrelid | indrelid -------------+---------- -(0 rows) - --- Don't auto-attach incompatible indexes -create table idxpart (a int, b int) partition by range (a); -create table idxpart1 (a int, b int); -create index on idxpart1 using hash (a); -create index on idxpart1 (a) where b > 1; -create index on idxpart1 ((a + 0)); -create index on idxpart1 (a, a); -create index on idxpart (a); -alter table idxpart attach partition idxpart1 for values from (0) to (1000); -\d idxpart1 - Table "public.idxpart1" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | integer | | | -Partition of: idxpart FOR VALUES FROM (0) TO (1000) -Indexes: - "idxpart1_a_a1_idx" btree (a, a) - "idxpart1_a_idx" hash (a) - "idxpart1_a_idx1" btree (a) WHERE b > 1 - "idxpart1_a_idx2" btree (a) - "idxpart1_expr_idx" btree ((a + 0)) - -drop table idxpart; --- If CREATE INDEX ONLY, don't create indexes on partitions; and existing --- indexes on partitions don't change parent. ALTER INDEX ATTACH can change --- the parent after the fact. -create table idxpart (a int) partition by range (a); -create table idxpart1 partition of idxpart for values from (0) to (100); -create table idxpart2 partition of idxpart for values from (100) to (1000) - partition by range (a); -create table idxpart21 partition of idxpart2 for values from (100) to (200); -create table idxpart22 partition of idxpart2 for values from (200) to (300); -create index on idxpart22 (a); -create index on only idxpart2 (a); -create index on idxpart (a); --- Here we expect that idxpart1 and idxpart2 have a new index, but idxpart21 --- does not; also, idxpart22 is not attached. -\d idxpart1 - Table "public.idxpart1" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | -Partition of: idxpart FOR VALUES FROM (0) TO (100) -Indexes: - "idxpart1_a_idx" btree (a) - -\d idxpart2 - Partitioned table "public.idxpart2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | -Partition of: idxpart FOR VALUES FROM (100) TO (1000) -Partition key: RANGE (a) -Indexes: - "idxpart2_a_idx" btree (a) INVALID -Number of partitions: 2 (Use \d+ to list them.) - -\d idxpart21 - Table "public.idxpart21" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | -Partition of: idxpart2 FOR VALUES FROM (100) TO (200) - -select indexrelid::regclass, indrelid::regclass, inhparent::regclass - from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid) -where indexrelid::regclass::text like 'idxpart%' - order by indexrelid::regclass::text collate "C"; - indexrelid | indrelid | inhparent ------------------+-----------+--------------- - idxpart1_a_idx | idxpart1 | idxpart_a_idx - idxpart22_a_idx | idxpart22 | - idxpart2_a_idx | idxpart2 | idxpart_a_idx - idxpart_a_idx | idxpart | -(4 rows) - -alter index idxpart2_a_idx attach partition idxpart22_a_idx; -select indexrelid::regclass, indrelid::regclass, inhparent::regclass - from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid) -where indexrelid::regclass::text like 'idxpart%' - order by indexrelid::regclass::text collate "C"; - indexrelid | indrelid | inhparent ------------------+-----------+---------------- - idxpart1_a_idx | idxpart1 | idxpart_a_idx - idxpart22_a_idx | idxpart22 | idxpart2_a_idx - idxpart2_a_idx | idxpart2 | idxpart_a_idx - idxpart_a_idx | idxpart | -(4 rows) - --- attaching idxpart22 is not enough to set idxpart22_a_idx valid ... -alter index idxpart2_a_idx attach partition idxpart22_a_idx; -\d idxpart2 - Partitioned table "public.idxpart2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | -Partition of: idxpart FOR VALUES FROM (100) TO (1000) -Partition key: RANGE (a) -Indexes: - "idxpart2_a_idx" btree (a) INVALID -Number of partitions: 2 (Use \d+ to list them.) - --- ... but this one is. -create index on idxpart21 (a); -alter index idxpart2_a_idx attach partition idxpart21_a_idx; -\d idxpart2 - Partitioned table "public.idxpart2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | -Partition of: idxpart FOR VALUES FROM (100) TO (1000) -Partition key: RANGE (a) -Indexes: - "idxpart2_a_idx" btree (a) -Number of partitions: 2 (Use \d+ to list them.) - -drop table idxpart; --- When a table is attached a partition and it already has an index, a --- duplicate index should not get created, but rather the index becomes --- attached to the parent's index. -create table idxpart (a int, b int, c text, d bool) partition by range (a); -create index idxparti on idxpart (a); -create index idxparti2 on idxpart (b, c); -create table idxpart1 (like idxpart including indexes); -\d idxpart1 - Table "public.idxpart1" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | integer | | | - c | text | | | - d | boolean | | | -Indexes: - "idxpart1_a_idx" btree (a) - "idxpart1_b_c_idx" btree (b, c) - -select relname, relkind, inhparent::regclass - from pg_class left join pg_index ix on (indexrelid = oid) - left join pg_inherits on (ix.indexrelid = inhrelid) - where relname like 'idxpart%' order by relname; - relname | relkind | inhparent -------------------+---------+----------- - idxpart | p | - idxpart1 | r | - idxpart1_a_idx | i | - idxpart1_b_c_idx | i | - idxparti | I | - idxparti2 | I | -(6 rows) - -alter table idxpart attach partition idxpart1 for values from (0) to (10); -\d idxpart1 - Table "public.idxpart1" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | integer | | | - c | text | | | - d | boolean | | | -Partition of: idxpart FOR VALUES FROM (0) TO (10) -Indexes: - "idxpart1_a_idx" btree (a) - "idxpart1_b_c_idx" btree (b, c) - -select relname, relkind, inhparent::regclass - from pg_class left join pg_index ix on (indexrelid = oid) - left join pg_inherits on (ix.indexrelid = inhrelid) - where relname like 'idxpart%' order by relname; - relname | relkind | inhparent -------------------+---------+----------- - idxpart | p | - idxpart1 | r | - idxpart1_a_idx | i | idxparti - idxpart1_b_c_idx | i | idxparti2 - idxparti | I | - idxparti2 | I | -(6 rows) - --- While here, also check matching when creating an index after the fact. -create index on idxpart1 ((a+b)) where d = true; -\d idxpart1 - Table "public.idxpart1" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | integer | | | - c | text | | | - d | boolean | | | -Partition of: idxpart FOR VALUES FROM (0) TO (10) -Indexes: - "idxpart1_a_idx" btree (a) - "idxpart1_b_c_idx" btree (b, c) - "idxpart1_expr_idx" btree ((a + b)) WHERE d = true - -select relname, relkind, inhparent::regclass - from pg_class left join pg_index ix on (indexrelid = oid) - left join pg_inherits on (ix.indexrelid = inhrelid) - where relname like 'idxpart%' order by relname; - relname | relkind | inhparent --------------------+---------+----------- - idxpart | p | - idxpart1 | r | - idxpart1_a_idx | i | idxparti - idxpart1_b_c_idx | i | idxparti2 - idxpart1_expr_idx | i | - idxparti | I | - idxparti2 | I | -(7 rows) - -create index idxparti3 on idxpart ((a+b)) where d = true; -\d idxpart1 - Table "public.idxpart1" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | integer | | | - c | text | | | - d | boolean | | | -Partition of: idxpart FOR VALUES FROM (0) TO (10) -Indexes: - "idxpart1_a_idx" btree (a) - "idxpart1_b_c_idx" btree (b, c) - "idxpart1_expr_idx" btree ((a + b)) WHERE d = true - -select relname, relkind, inhparent::regclass - from pg_class left join pg_index ix on (indexrelid = oid) - left join pg_inherits on (ix.indexrelid = inhrelid) - where relname like 'idxpart%' order by relname; - relname | relkind | inhparent --------------------+---------+----------- - idxpart | p | - idxpart1 | r | - idxpart1_a_idx | i | idxparti - idxpart1_b_c_idx | i | idxparti2 - idxpart1_expr_idx | i | idxparti3 - idxparti | I | - idxparti2 | I | - idxparti3 | I | -(8 rows) - -drop table idxpart; --- Verify that attaching an invalid index does not mark the parent index valid. --- On the other hand, attaching a valid index marks not only its direct --- ancestor valid, but also any indirect ancestor that was only missing the one --- that was just made valid -create table idxpart (a int, b int) partition by range (a); -create table idxpart1 partition of idxpart for values from (1) to (1000) partition by range (a); -create table idxpart11 partition of idxpart1 for values from (1) to (100); -create index on only idxpart1 (a); -create index on only idxpart (a); --- this results in two invalid indexes: -select relname, indisvalid from pg_class join pg_index on indexrelid = oid - where relname like 'idxpart%' order by relname; - relname | indisvalid -----------------+------------ - idxpart1_a_idx | f - idxpart_a_idx | f -(2 rows) - --- idxpart1_a_idx is not valid, so idxpart_a_idx should not become valid: -alter index idxpart_a_idx attach partition idxpart1_a_idx; -select relname, indisvalid from pg_class join pg_index on indexrelid = oid - where relname like 'idxpart%' order by relname; - relname | indisvalid -----------------+------------ - idxpart1_a_idx | f - idxpart_a_idx | f -(2 rows) - --- after creating and attaching this, both idxpart1_a_idx and idxpart_a_idx --- should become valid -create index on idxpart11 (a); -alter index idxpart1_a_idx attach partition idxpart11_a_idx; -select relname, indisvalid from pg_class join pg_index on indexrelid = oid - where relname like 'idxpart%' order by relname; - relname | indisvalid ------------------+------------ - idxpart11_a_idx | t - idxpart1_a_idx | t - idxpart_a_idx | t -(3 rows) - -drop table idxpart; --- verify dependency handling during ALTER TABLE DETACH PARTITION -create table idxpart (a int) partition by range (a); -create table idxpart1 (like idxpart); -create index on idxpart1 (a); -create index on idxpart (a); -create table idxpart2 (like idxpart); -alter table idxpart attach partition idxpart1 for values from (0000) to (1000); -alter table idxpart attach partition idxpart2 for values from (1000) to (2000); -create table idxpart3 partition of idxpart for values from (2000) to (3000); -select relname, relkind from pg_class where relname like 'idxpart%' order by relname; - relname | relkind -----------------+--------- - idxpart | p - idxpart1 | r - idxpart1_a_idx | i - idxpart2 | r - idxpart2_a_idx | i - idxpart3 | r - idxpart3_a_idx | i - idxpart_a_idx | I -(8 rows) - --- a) after detaching partitions, the indexes can be dropped independently -alter table idxpart detach partition idxpart1; -alter table idxpart detach partition idxpart2; -alter table idxpart detach partition idxpart3; -drop index idxpart1_a_idx; -drop index idxpart2_a_idx; -drop index idxpart3_a_idx; -select relname, relkind from pg_class where relname like 'idxpart%' order by relname; - relname | relkind ----------------+--------- - idxpart | p - idxpart1 | r - idxpart2 | r - idxpart3 | r - idxpart_a_idx | I -(5 rows) - -drop table idxpart, idxpart1, idxpart2, idxpart3; -select relname, relkind from pg_class where relname like 'idxpart%' order by relname; - relname | relkind ----------+--------- -(0 rows) - -create table idxpart (a int) partition by range (a); -create table idxpart1 (like idxpart); -create index on idxpart1 (a); -create index on idxpart (a); -create table idxpart2 (like idxpart); -alter table idxpart attach partition idxpart1 for values from (0000) to (1000); -alter table idxpart attach partition idxpart2 for values from (1000) to (2000); -create table idxpart3 partition of idxpart for values from (2000) to (3000); --- b) after detaching, dropping the index on parent does not remove the others -select relname, relkind from pg_class where relname like 'idxpart%' order by relname; - relname | relkind -----------------+--------- - idxpart | p - idxpart1 | r - idxpart1_a_idx | i - idxpart2 | r - idxpart2_a_idx | i - idxpart3 | r - idxpart3_a_idx | i - idxpart_a_idx | I -(8 rows) - -alter table idxpart detach partition idxpart1; -alter table idxpart detach partition idxpart2; -alter table idxpart detach partition idxpart3; -drop index idxpart_a_idx; -select relname, relkind from pg_class where relname like 'idxpart%' order by relname; - relname | relkind -----------------+--------- - idxpart | p - idxpart1 | r - idxpart1_a_idx | i - idxpart2 | r - idxpart2_a_idx | i - idxpart3 | r - idxpart3_a_idx | i -(7 rows) - -drop table idxpart, idxpart1, idxpart2, idxpart3; -select relname, relkind from pg_class where relname like 'idxpart%' order by relname; - relname | relkind ----------+--------- -(0 rows) - -create table idxpart (a int, b int, c int) partition by range(a); -create index on idxpart(c); -create table idxpart1 partition of idxpart for values from (0) to (250); -create table idxpart2 partition of idxpart for values from (250) to (500); -alter table idxpart detach partition idxpart2; -\d idxpart2 - Table "public.idxpart2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | integer | | | - c | integer | | | -Indexes: - "idxpart2_c_idx" btree (c) - -alter table idxpart2 drop column c; -\d idxpart2 - Table "public.idxpart2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | integer | | | - -drop table idxpart, idxpart2; --- Verify that expression indexes inherit correctly -create table idxpart (a int, b int) partition by range (a); -create table idxpart1 (like idxpart); -create index on idxpart1 ((a + b)); -create index on idxpart ((a + b)); -create table idxpart2 (like idxpart); -alter table idxpart attach partition idxpart1 for values from (0000) to (1000); -alter table idxpart attach partition idxpart2 for values from (1000) to (2000); -create table idxpart3 partition of idxpart for values from (2000) to (3000); -select relname as child, inhparent::regclass as parent, pg_get_indexdef as childdef - from pg_class join pg_inherits on inhrelid = oid, - lateral pg_get_indexdef(pg_class.oid) - where relkind in ('i', 'I') and relname like 'idxpart%' order by relname; - child | parent | childdef --------------------+------------------+--------------------------------------------------------------------------- - idxpart1_expr_idx | idxpart_expr_idx | CREATE INDEX idxpart1_expr_idx ON public.idxpart1 USING btree (((a + b))) - idxpart2_expr_idx | idxpart_expr_idx | CREATE INDEX idxpart2_expr_idx ON public.idxpart2 USING btree (((a + b))) - idxpart3_expr_idx | idxpart_expr_idx | CREATE INDEX idxpart3_expr_idx ON public.idxpart3 USING btree (((a + b))) -(3 rows) - -drop table idxpart; --- Verify behavior for collation (mis)matches -create table idxpart (a text) partition by range (a); -create table idxpart1 (like idxpart); -create table idxpart2 (like idxpart); -create index on idxpart2 (a collate "POSIX"); -create index on idxpart2 (a); -create index on idxpart2 (a collate "C"); -alter table idxpart attach partition idxpart1 for values from ('aaa') to ('bbb'); -alter table idxpart attach partition idxpart2 for values from ('bbb') to ('ccc'); -create table idxpart3 partition of idxpart for values from ('ccc') to ('ddd'); -create index on idxpart (a collate "C"); -create table idxpart4 partition of idxpart for values from ('ddd') to ('eee'); -select relname as child, inhparent::regclass as parent, pg_get_indexdef as childdef - from pg_class left join pg_inherits on inhrelid = oid, - lateral pg_get_indexdef(pg_class.oid) - where relkind in ('i', 'I') and relname like 'idxpart%' order by relname; - child | parent | childdef ------------------+---------------+-------------------------------------------------------------------------------- - idxpart1_a_idx | idxpart_a_idx | CREATE INDEX idxpart1_a_idx ON public.idxpart1 USING btree (a COLLATE "C") - idxpart2_a_idx | | CREATE INDEX idxpart2_a_idx ON public.idxpart2 USING btree (a COLLATE "POSIX") - idxpart2_a_idx1 | | CREATE INDEX idxpart2_a_idx1 ON public.idxpart2 USING btree (a) - idxpart2_a_idx2 | idxpart_a_idx | CREATE INDEX idxpart2_a_idx2 ON public.idxpart2 USING btree (a COLLATE "C") - idxpart3_a_idx | idxpart_a_idx | CREATE INDEX idxpart3_a_idx ON public.idxpart3 USING btree (a COLLATE "C") - idxpart4_a_idx | idxpart_a_idx | CREATE INDEX idxpart4_a_idx ON public.idxpart4 USING btree (a COLLATE "C") - idxpart_a_idx | | CREATE INDEX idxpart_a_idx ON ONLY public.idxpart USING btree (a COLLATE "C") -(7 rows) - -drop table idxpart; --- Verify behavior for opclass (mis)matches -create table idxpart (a text) partition by range (a); -create table idxpart1 (like idxpart); -create table idxpart2 (like idxpart); -create index on idxpart2 (a); -alter table idxpart attach partition idxpart1 for values from ('aaa') to ('bbb'); -alter table idxpart attach partition idxpart2 for values from ('bbb') to ('ccc'); -create table idxpart3 partition of idxpart for values from ('ccc') to ('ddd'); -create index on idxpart (a text_pattern_ops); -create table idxpart4 partition of idxpart for values from ('ddd') to ('eee'); --- must *not* have attached the index we created on idxpart2 -select relname as child, inhparent::regclass as parent, pg_get_indexdef as childdef - from pg_class left join pg_inherits on inhrelid = oid, - lateral pg_get_indexdef(pg_class.oid) - where relkind in ('i', 'I') and relname like 'idxpart%' order by relname; - child | parent | childdef ------------------+---------------+------------------------------------------------------------------------------------ - idxpart1_a_idx | idxpart_a_idx | CREATE INDEX idxpart1_a_idx ON public.idxpart1 USING btree (a text_pattern_ops) - idxpart2_a_idx | | CREATE INDEX idxpart2_a_idx ON public.idxpart2 USING btree (a) - idxpart2_a_idx1 | idxpart_a_idx | CREATE INDEX idxpart2_a_idx1 ON public.idxpart2 USING btree (a text_pattern_ops) - idxpart3_a_idx | idxpart_a_idx | CREATE INDEX idxpart3_a_idx ON public.idxpart3 USING btree (a text_pattern_ops) - idxpart4_a_idx | idxpart_a_idx | CREATE INDEX idxpart4_a_idx ON public.idxpart4 USING btree (a text_pattern_ops) - idxpart_a_idx | | CREATE INDEX idxpart_a_idx ON ONLY public.idxpart USING btree (a text_pattern_ops) -(6 rows) - -drop index idxpart_a_idx; -create index on only idxpart (a text_pattern_ops); --- must reject -alter index idxpart_a_idx attach partition idxpart2_a_idx; -ERROR: cannot attach index "idxpart2_a_idx" as a partition of index "idxpart_a_idx" -DETAIL: The index definitions do not match. -drop table idxpart; --- Verify that attaching indexes maps attribute numbers correctly -create table idxpart (col1 int, a int, col2 int, b int) partition by range (a); -create table idxpart1 (b int, col1 int, col2 int, col3 int, a int); -alter table idxpart drop column col1, drop column col2; -alter table idxpart1 drop column col1, drop column col2, drop column col3; -alter table idxpart attach partition idxpart1 for values from (0) to (1000); -create index idxpart_1_idx on only idxpart (b, a); -create index idxpart1_1_idx on idxpart1 (b, a); -create index idxpart1_1b_idx on idxpart1 (b); --- test expressions and partial-index predicate, too -create index idxpart_2_idx on only idxpart ((b + a)) where a > 1; -create index idxpart1_2_idx on idxpart1 ((b + a)) where a > 1; -create index idxpart1_2b_idx on idxpart1 ((a + b)) where a > 1; -create index idxpart1_2c_idx on idxpart1 ((b + a)) where b > 1; -alter index idxpart_1_idx attach partition idxpart1_1b_idx; -- fail -ERROR: cannot attach index "idxpart1_1b_idx" as a partition of index "idxpart_1_idx" -DETAIL: The index definitions do not match. -alter index idxpart_1_idx attach partition idxpart1_1_idx; -alter index idxpart_2_idx attach partition idxpart1_2b_idx; -- fail -ERROR: cannot attach index "idxpart1_2b_idx" as a partition of index "idxpart_2_idx" -DETAIL: The index definitions do not match. -alter index idxpart_2_idx attach partition idxpart1_2c_idx; -- fail -ERROR: cannot attach index "idxpart1_2c_idx" as a partition of index "idxpart_2_idx" -DETAIL: The index definitions do not match. -alter index idxpart_2_idx attach partition idxpart1_2_idx; -- ok -select relname as child, inhparent::regclass as parent, pg_get_indexdef as childdef - from pg_class left join pg_inherits on inhrelid = oid, - lateral pg_get_indexdef(pg_class.oid) - where relkind in ('i', 'I') and relname like 'idxpart%' order by relname; - child | parent | childdef ------------------+---------------+----------------------------------------------------------------------------------------- - idxpart1_1_idx | idxpart_1_idx | CREATE INDEX idxpart1_1_idx ON public.idxpart1 USING btree (b, a) - idxpart1_1b_idx | | CREATE INDEX idxpart1_1b_idx ON public.idxpart1 USING btree (b) - idxpart1_2_idx | idxpart_2_idx | CREATE INDEX idxpart1_2_idx ON public.idxpart1 USING btree (((b + a))) WHERE (a > 1) - idxpart1_2b_idx | | CREATE INDEX idxpart1_2b_idx ON public.idxpart1 USING btree (((a + b))) WHERE (a > 1) - idxpart1_2c_idx | | CREATE INDEX idxpart1_2c_idx ON public.idxpart1 USING btree (((b + a))) WHERE (b > 1) - idxpart_1_idx | | CREATE INDEX idxpart_1_idx ON ONLY public.idxpart USING btree (b, a) - idxpart_2_idx | | CREATE INDEX idxpart_2_idx ON ONLY public.idxpart USING btree (((b + a))) WHERE (a > 1) -(7 rows) - -drop table idxpart; --- Make sure the partition columns are mapped correctly -create table idxpart (a int, b int, c text) partition by range (a); -create index idxparti on idxpart (a); -create index idxparti2 on idxpart (c, b); -create table idxpart1 (c text, a int, b int); -alter table idxpart attach partition idxpart1 for values from (0) to (10); -create table idxpart2 (c text, a int, b int); -create index on idxpart2 (a); -create index on idxpart2 (c, b); -alter table idxpart attach partition idxpart2 for values from (10) to (20); -select c.relname, pg_get_indexdef(indexrelid) - from pg_class c join pg_index i on c.oid = i.indexrelid - where indrelid::regclass::text like 'idxpart%' - order by indexrelid::regclass::text collate "C"; - relname | pg_get_indexdef -------------------+--------------------------------------------------------------------- - idxpart1_a_idx | CREATE INDEX idxpart1_a_idx ON public.idxpart1 USING btree (a) - idxpart1_c_b_idx | CREATE INDEX idxpart1_c_b_idx ON public.idxpart1 USING btree (c, b) - idxpart2_a_idx | CREATE INDEX idxpart2_a_idx ON public.idxpart2 USING btree (a) - idxpart2_c_b_idx | CREATE INDEX idxpart2_c_b_idx ON public.idxpart2 USING btree (c, b) - idxparti | CREATE INDEX idxparti ON ONLY public.idxpart USING btree (a) - idxparti2 | CREATE INDEX idxparti2 ON ONLY public.idxpart USING btree (c, b) -(6 rows) - -drop table idxpart; --- Verify that columns are mapped correctly in expression indexes -create table idxpart (col1 int, col2 int, a int, b int) partition by range (a); -create table idxpart1 (col2 int, b int, col1 int, a int); -create table idxpart2 (col1 int, col2 int, b int, a int); -alter table idxpart drop column col1, drop column col2; -alter table idxpart1 drop column col1, drop column col2; -alter table idxpart2 drop column col1, drop column col2; -create index on idxpart2 (abs(b)); -alter table idxpart attach partition idxpart2 for values from (0) to (1); -create index on idxpart (abs(b)); -create index on idxpart ((b + 1)); -alter table idxpart attach partition idxpart1 for values from (1) to (2); -select c.relname, pg_get_indexdef(indexrelid) - from pg_class c join pg_index i on c.oid = i.indexrelid - where indrelid::regclass::text like 'idxpart%' - order by indexrelid::regclass::text collate "C"; - relname | pg_get_indexdef --------------------+------------------------------------------------------------------------------ - idxpart1_abs_idx | CREATE INDEX idxpart1_abs_idx ON public.idxpart1 USING btree (abs(b)) - idxpart1_expr_idx | CREATE INDEX idxpart1_expr_idx ON public.idxpart1 USING btree (((b + 1))) - idxpart2_abs_idx | CREATE INDEX idxpart2_abs_idx ON public.idxpart2 USING btree (abs(b)) - idxpart2_expr_idx | CREATE INDEX idxpart2_expr_idx ON public.idxpart2 USING btree (((b + 1))) - idxpart_abs_idx | CREATE INDEX idxpart_abs_idx ON ONLY public.idxpart USING btree (abs(b)) - idxpart_expr_idx | CREATE INDEX idxpart_expr_idx ON ONLY public.idxpart USING btree (((b + 1))) -(6 rows) - -drop table idxpart; --- Verify that columns are mapped correctly for WHERE in a partial index -create table idxpart (col1 int, a int, col3 int, b int) partition by range (a); -alter table idxpart drop column col1, drop column col3; -create table idxpart1 (col1 int, col2 int, col3 int, col4 int, b int, a int); -alter table idxpart1 drop column col1, drop column col2, drop column col3, drop column col4; -alter table idxpart attach partition idxpart1 for values from (0) to (1000); -create table idxpart2 (col1 int, col2 int, b int, a int); -create index on idxpart2 (a) where b > 1000; -alter table idxpart2 drop column col1, drop column col2; -alter table idxpart attach partition idxpart2 for values from (1000) to (2000); -create index on idxpart (a) where b > 1000; -select c.relname, pg_get_indexdef(indexrelid) - from pg_class c join pg_index i on c.oid = i.indexrelid - where indrelid::regclass::text like 'idxpart%' - order by indexrelid::regclass::text collate "C"; - relname | pg_get_indexdef -----------------+------------------------------------------------------------------------------------ - idxpart1_a_idx | CREATE INDEX idxpart1_a_idx ON public.idxpart1 USING btree (a) WHERE (b > 1000) - idxpart2_a_idx | CREATE INDEX idxpart2_a_idx ON public.idxpart2 USING btree (a) WHERE (b > 1000) - idxpart_a_idx | CREATE INDEX idxpart_a_idx ON ONLY public.idxpart USING btree (a) WHERE (b > 1000) -(3 rows) - -drop table idxpart; --- Column number mapping: dropped columns in the partition -create table idxpart1 (drop_1 int, drop_2 int, col_keep int, drop_3 int); -alter table idxpart1 drop column drop_1; -alter table idxpart1 drop column drop_2; -alter table idxpart1 drop column drop_3; -create index on idxpart1 (col_keep); -create table idxpart (col_keep int) partition by range (col_keep); -create index on idxpart (col_keep); -alter table idxpart attach partition idxpart1 for values from (0) to (1000); -\d idxpart - Partitioned table "public.idxpart" - Column | Type | Collation | Nullable | Default -----------+---------+-----------+----------+--------- - col_keep | integer | | | -Partition key: RANGE (col_keep) -Indexes: - "idxpart_col_keep_idx" btree (col_keep) -Number of partitions: 1 (Use \d+ to list them.) - -\d idxpart1 - Table "public.idxpart1" - Column | Type | Collation | Nullable | Default -----------+---------+-----------+----------+--------- - col_keep | integer | | | -Partition of: idxpart FOR VALUES FROM (0) TO (1000) -Indexes: - "idxpart1_col_keep_idx" btree (col_keep) - -select attrelid::regclass, attname, attnum from pg_attribute - where attrelid::regclass::text like 'idxpart%' and attnum > 0 - order by attrelid::regclass, attnum; - attrelid | attname | attnum ------------------------+------------------------------+-------- - idxpart1 | ........pg.dropped.1........ | 1 - idxpart1 | ........pg.dropped.2........ | 2 - idxpart1 | col_keep | 3 - idxpart1 | ........pg.dropped.4........ | 4 - idxpart1_col_keep_idx | col_keep | 1 - idxpart | col_keep | 1 - idxpart_col_keep_idx | col_keep | 1 -(7 rows) - -drop table idxpart; --- Column number mapping: dropped columns in the parent table -create table idxpart(drop_1 int, drop_2 int, col_keep int, drop_3 int) partition by range (col_keep); -alter table idxpart drop column drop_1; -alter table idxpart drop column drop_2; -alter table idxpart drop column drop_3; -create table idxpart1 (col_keep int); -create index on idxpart1 (col_keep); -create index on idxpart (col_keep); -alter table idxpart attach partition idxpart1 for values from (0) to (1000); -\d idxpart - Partitioned table "public.idxpart" - Column | Type | Collation | Nullable | Default -----------+---------+-----------+----------+--------- - col_keep | integer | | | -Partition key: RANGE (col_keep) -Indexes: - "idxpart_col_keep_idx" btree (col_keep) -Number of partitions: 1 (Use \d+ to list them.) - -\d idxpart1 - Table "public.idxpart1" - Column | Type | Collation | Nullable | Default -----------+---------+-----------+----------+--------- - col_keep | integer | | | -Partition of: idxpart FOR VALUES FROM (0) TO (1000) -Indexes: - "idxpart1_col_keep_idx" btree (col_keep) - -select attrelid::regclass, attname, attnum from pg_attribute - where attrelid::regclass::text like 'idxpart%' and attnum > 0 - order by attrelid::regclass, attnum; - attrelid | attname | attnum ------------------------+------------------------------+-------- - idxpart | ........pg.dropped.1........ | 1 - idxpart | ........pg.dropped.2........ | 2 - idxpart | col_keep | 3 - idxpart | ........pg.dropped.4........ | 4 - idxpart1 | col_keep | 1 - idxpart1_col_keep_idx | col_keep | 1 - idxpart_col_keep_idx | col_keep | 1 -(7 rows) - -drop table idxpart; --- --- Constraint-related indexes --- --- Verify that it works to add primary key / unique to partitioned tables -create table idxpart (a int primary key, b int) partition by range (a); -\d idxpart - Partitioned table "public.idxpart" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | not null | - b | integer | | | -Partition key: RANGE (a) -Indexes: - "idxpart_pkey" PRIMARY KEY, btree (a) -Number of partitions: 0 - --- multiple primary key on child should fail -create table failpart partition of idxpart (b primary key) for values from (0) to (100); -ERROR: multiple primary keys for table "failpart" are not allowed -drop table idxpart; --- primary key on child is okay if there's no PK in the parent, though -create table idxpart (a int) partition by range (a); -create table idxpart1pk partition of idxpart (a primary key) for values from (0) to (100); -\d idxpart1pk - Table "public.idxpart1pk" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | not null | -Partition of: idxpart FOR VALUES FROM (0) TO (100) -Indexes: - "idxpart1pk_pkey" PRIMARY KEY, btree (a) - -drop table idxpart; --- Failing to use the full partition key is not allowed -create table idxpart (a int unique, b int) partition by range (a, b); -ERROR: unique constraint on partitioned table must include all partitioning columns -DETAIL: UNIQUE constraint on table "idxpart" lacks column "b" which is part of the partition key. -create table idxpart (a int, b int unique) partition by range (a, b); -ERROR: unique constraint on partitioned table must include all partitioning columns -DETAIL: UNIQUE constraint on table "idxpart" lacks column "a" which is part of the partition key. -create table idxpart (a int primary key, b int) partition by range (b, a); -ERROR: unique constraint on partitioned table must include all partitioning columns -DETAIL: PRIMARY KEY constraint on table "idxpart" lacks column "b" which is part of the partition key. -create table idxpart (a int, b int primary key) partition by range (b, a); -ERROR: unique constraint on partitioned table must include all partitioning columns -DETAIL: PRIMARY KEY constraint on table "idxpart" lacks column "a" which is part of the partition key. --- OK if you use them in some other order -create table idxpart (a int, b int, c text, primary key (a, b, c)) partition by range (b, c, a); -drop table idxpart; --- OK to add an exclusion constraint if partitioning by its equal column -create table idxpart (a int4range, exclude USING GIST (a with = )) partition by range (a); -drop table idxpart; --- OK more than one equal column -create table idxpart (a int4range, b int4range, exclude USING GIST (a with =, b with =)) partition by range (a, b); -drop table idxpart; --- OK with more than one equal column: constraint is a proper superset of partition key -create table idxpart (a int4range, b int4range, exclude USING GIST (a with =, b with =)) partition by range (a); -drop table idxpart; --- Not OK more than one equal column: partition keys are a proper superset of constraint -create table idxpart (a int4range, b int4range, exclude USING GIST (a with = )) partition by range (a, b); -ERROR: unique constraint on partitioned table must include all partitioning columns -DETAIL: EXCLUDE constraint on table "idxpart" lacks column "b" which is part of the partition key. --- Not OK with just -|- -create table idxpart (a int4range, exclude USING GIST (a with -|- )) partition by range (a); -ERROR: cannot match partition key to index on column "a" using non-equal operator "-|-" --- OK with equals and &&, and equals is the partition key -create table idxpart (a int4range, b int4range, exclude USING GIST (a with =, b with &&)) partition by range (a); -drop table idxpart; --- Not OK with equals and &&, and equals is not the partition key -create table idxpart (a int4range, b int4range, c int4range, exclude USING GIST (b with =, c with &&)) partition by range (a); -ERROR: unique constraint on partitioned table must include all partitioning columns -DETAIL: EXCLUDE constraint on table "idxpart" lacks column "a" which is part of the partition key. --- OK more than one equal column and a && column -create table idxpart (a int4range, b int4range, c int4range, exclude USING GIST (a with =, b with =, c with &&)) partition by range (a, b); -drop table idxpart; --- no expressions in partition key for PK/UNIQUE -create table idxpart (a int primary key, b int) partition by range ((b + a)); -ERROR: unsupported PRIMARY KEY constraint with partition key definition -DETAIL: PRIMARY KEY constraints cannot be used when partition keys include expressions. -create table idxpart (a int unique, b int) partition by range ((b + a)); -ERROR: unsupported UNIQUE constraint with partition key definition -DETAIL: UNIQUE constraints cannot be used when partition keys include expressions. --- use ALTER TABLE to add a primary key -create table idxpart (a int, b int, c text) partition by range (a, b); -alter table idxpart add primary key (a); -- not an incomplete one though -ERROR: unique constraint on partitioned table must include all partitioning columns -DETAIL: PRIMARY KEY constraint on table "idxpart" lacks column "b" which is part of the partition key. -alter table idxpart add primary key (a, b); -- this works -\d idxpart - Partitioned table "public.idxpart" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | not null | - b | integer | | not null | - c | text | | | -Partition key: RANGE (a, b) -Indexes: - "idxpart_pkey" PRIMARY KEY, btree (a, b) -Number of partitions: 0 - -create table idxpart1 partition of idxpart for values from (0, 0) to (1000, 1000); -\d idxpart1 - Table "public.idxpart1" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | not null | - b | integer | | not null | - c | text | | | -Partition of: idxpart FOR VALUES FROM (0, 0) TO (1000, 1000) -Indexes: - "idxpart1_pkey" PRIMARY KEY, btree (a, b) - -drop table idxpart; --- use ALTER TABLE to add a unique constraint -create table idxpart (a int, b int) partition by range (a, b); -alter table idxpart add unique (a); -- not an incomplete one though -ERROR: unique constraint on partitioned table must include all partitioning columns -DETAIL: UNIQUE constraint on table "idxpart" lacks column "b" which is part of the partition key. -alter table idxpart add unique (b, a); -- this works -\d idxpart - Partitioned table "public.idxpart" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | integer | | | -Partition key: RANGE (a, b) -Indexes: - "idxpart_b_a_key" UNIQUE CONSTRAINT, btree (b, a) -Number of partitions: 0 - -drop table idxpart; --- Exclusion constraints can be added if partitioning by their equal column -create table idxpart (a int4range, b int4range) partition by range (a); -alter table idxpart add exclude USING GIST (a with =); -drop table idxpart; --- OK more than one equal column -create table idxpart (a int4range, b int4range) partition by range (a, b); -alter table idxpart add exclude USING GIST (a with =, b with =); -drop table idxpart; --- OK with more than one equal column: constraint is a proper superset of partition key -create table idxpart (a int4range, b int4range) partition by range (a); -alter table idxpart add exclude USING GIST (a with =, b with =); -drop table idxpart; --- Not OK more than one equal column: partition keys are a proper superset of constraint -create table idxpart (a int4range, b int4range) partition by range (a, b); -alter table idxpart add exclude USING GIST (a with =); -ERROR: unique constraint on partitioned table must include all partitioning columns -DETAIL: EXCLUDE constraint on table "idxpart" lacks column "b" which is part of the partition key. -drop table idxpart; --- Not OK with just -|- -create table idxpart (a int4range, b int4range) partition by range (a, b); -alter table idxpart add exclude USING GIST (a with -|-); -ERROR: cannot match partition key to index on column "a" using non-equal operator "-|-" -drop table idxpart; --- OK with equals and &&, and equals is the partition key -create table idxpart (a int4range, b int4range) partition by range (a); -alter table idxpart add exclude USING GIST (a with =, b with &&); -drop table idxpart; --- Not OK with equals and &&, and equals is not the partition key -create table idxpart (a int4range, b int4range, c int4range) partition by range (a); -alter table idxpart add exclude USING GIST (b with =, c with &&); -ERROR: unique constraint on partitioned table must include all partitioning columns -DETAIL: EXCLUDE constraint on table "idxpart" lacks column "a" which is part of the partition key. -drop table idxpart; --- OK more than one equal column and a && column -create table idxpart (a int4range, b int4range, c int4range) partition by range (a, b); -alter table idxpart add exclude USING GIST (a with =, b with =, c with &&); -drop table idxpart; --- When (sub)partitions are created, they also contain the constraint -create table idxpart (a int, b int, primary key (a, b)) partition by range (a, b); -create table idxpart1 partition of idxpart for values from (1, 1) to (10, 10); -create table idxpart2 partition of idxpart for values from (10, 10) to (20, 20) - partition by range (b); -create table idxpart21 partition of idxpart2 for values from (10) to (15); -create table idxpart22 partition of idxpart2 for values from (15) to (20); -create table idxpart3 (b int not null, a int not null); -alter table idxpart attach partition idxpart3 for values from (20, 20) to (30, 30); -select conname, contype, conrelid::regclass, conindid::regclass, conkey - from pg_constraint where conrelid::regclass::text like 'idxpart%' - order by conrelid::regclass::text, conname; - conname | contype | conrelid | conindid | conkey -----------------+---------+-----------+----------------+-------- - idxpart_pkey | p | idxpart | idxpart_pkey | {1,2} - idxpart1_pkey | p | idxpart1 | idxpart1_pkey | {1,2} - idxpart2_pkey | p | idxpart2 | idxpart2_pkey | {1,2} - idxpart21_pkey | p | idxpart21 | idxpart21_pkey | {1,2} - idxpart22_pkey | p | idxpart22 | idxpart22_pkey | {1,2} - idxpart3_pkey | p | idxpart3 | idxpart3_pkey | {2,1} -(6 rows) - -drop table idxpart; --- Verify that multi-layer partitioning honors the requirement that all --- columns in the partition key must appear in primary/unique key -create table idxpart (a int, b int, primary key (a)) partition by range (a); -create table idxpart2 partition of idxpart -for values from (0) to (1000) partition by range (b); -- fail -ERROR: unique constraint on partitioned table must include all partitioning columns -DETAIL: PRIMARY KEY constraint on table "idxpart2" lacks column "b" which is part of the partition key. -drop table idxpart; --- Ditto for the ATTACH PARTITION case -create table idxpart (a int unique, b int) partition by range (a); -create table idxpart1 (a int not null, b int, unique (a, b)) - partition by range (a, b); -alter table idxpart attach partition idxpart1 for values from (1) to (1000); -ERROR: unique constraint on partitioned table must include all partitioning columns -DETAIL: UNIQUE constraint on table "idxpart1" lacks column "b" which is part of the partition key. -DROP TABLE idxpart, idxpart1; --- Multi-layer partitioning works correctly in this case: -create table idxpart (a int, b int, primary key (a, b)) partition by range (a); -create table idxpart2 partition of idxpart for values from (0) to (1000) partition by range (b); -create table idxpart21 partition of idxpart2 for values from (0) to (1000); -select conname, contype, conrelid::regclass, conindid::regclass, conkey - from pg_constraint where conrelid::regclass::text like 'idxpart%' - order by conname; - conname | contype | conrelid | conindid | conkey -----------------+---------+-----------+----------------+-------- - idxpart21_pkey | p | idxpart21 | idxpart21_pkey | {1,2} - idxpart2_pkey | p | idxpart2 | idxpart2_pkey | {1,2} - idxpart_pkey | p | idxpart | idxpart_pkey | {1,2} -(3 rows) - -drop table idxpart; --- If a partitioned table has a unique/PK constraint, then it's not possible --- to drop the corresponding constraint in the children; nor it's possible --- to drop the indexes individually. Dropping the constraint in the parent --- gets rid of the lot. -create table idxpart (i int) partition by hash (i); -create table idxpart0 partition of idxpart (i) for values with (modulus 2, remainder 0); -create table idxpart1 partition of idxpart (i) for values with (modulus 2, remainder 1); -alter table idxpart0 add primary key(i); -alter table idxpart add primary key(i); -select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid, - conname, conislocal, coninhcount, connoinherit, convalidated - from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid) - left join pg_constraint con on (idx.indexrelid = con.conindid) - where indrelid::regclass::text like 'idxpart%' - order by indexrelid::regclass::text collate "C"; - indrelid | indexrelid | inhparent | indisvalid | conname | conislocal | coninhcount | connoinherit | convalidated -----------+---------------+--------------+------------+---------------+------------+-------------+--------------+-------------- - idxpart0 | idxpart0_pkey | idxpart_pkey | t | idxpart0_pkey | f | 1 | t | t - idxpart1 | idxpart1_pkey | idxpart_pkey | t | idxpart1_pkey | f | 1 | f | t - idxpart | idxpart_pkey | | t | idxpart_pkey | t | 0 | t | t -(3 rows) - -drop index idxpart0_pkey; -- fail -ERROR: cannot drop index idxpart0_pkey because index idxpart_pkey requires it -HINT: You can drop index idxpart_pkey instead. -drop index idxpart1_pkey; -- fail -ERROR: cannot drop index idxpart1_pkey because index idxpart_pkey requires it -HINT: You can drop index idxpart_pkey instead. -alter table idxpart0 drop constraint idxpart0_pkey; -- fail -ERROR: cannot drop inherited constraint "idxpart0_pkey" of relation "idxpart0" -alter table idxpart1 drop constraint idxpart1_pkey; -- fail -ERROR: cannot drop inherited constraint "idxpart1_pkey" of relation "idxpart1" -alter table idxpart drop constraint idxpart_pkey; -- ok -select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid, - conname, conislocal, coninhcount, connoinherit, convalidated - from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid) - left join pg_constraint con on (idx.indexrelid = con.conindid) - where indrelid::regclass::text like 'idxpart%' - order by indexrelid::regclass::text collate "C"; - indrelid | indexrelid | inhparent | indisvalid | conname | conislocal | coninhcount | connoinherit | convalidated -----------+------------+-----------+------------+---------+------------+-------------+--------------+-------------- -(0 rows) - -drop table idxpart; --- If the partition to be attached already has a primary key, fail if --- it doesn't match the parent's PK. -CREATE TABLE idxpart (c1 INT PRIMARY KEY, c2 INT, c3 VARCHAR(10)) PARTITION BY RANGE(c1); -CREATE TABLE idxpart1 (LIKE idxpart); -ALTER TABLE idxpart1 ADD PRIMARY KEY (c1, c2); -ALTER TABLE idxpart ATTACH PARTITION idxpart1 FOR VALUES FROM (100) TO (200); -ERROR: multiple primary keys for table "idxpart1" are not allowed -DROP TABLE idxpart, idxpart1; --- Ditto if there is some distance between the PKs (subpartitioning) -create table idxpart (a int, b int, primary key (a)) partition by range (a); -create table idxpart1 (a int not null, b int) partition by range (a); -create table idxpart11 (a int not null, b int primary key); -alter table idxpart1 attach partition idxpart11 for values from (0) to (1000); -alter table idxpart attach partition idxpart1 for values from (0) to (10000); -ERROR: multiple primary keys for table "idxpart11" are not allowed -drop table idxpart, idxpart1, idxpart11; --- If a partitioned table has a constraint whose index is not valid, --- attaching a missing partition makes it valid. -create table idxpart (a int) partition by range (a); -create table idxpart0 (like idxpart); -alter table idxpart0 add primary key (a); -alter table idxpart attach partition idxpart0 for values from (0) to (1000); -alter table only idxpart add primary key (a); -select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid, - conname, conislocal, coninhcount, connoinherit, convalidated - from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid) - left join pg_constraint con on (idx.indexrelid = con.conindid) - where indrelid::regclass::text like 'idxpart%' - order by indexrelid::regclass::text collate "C"; - indrelid | indexrelid | inhparent | indisvalid | conname | conislocal | coninhcount | connoinherit | convalidated -----------+---------------+-----------+------------+---------------+------------+-------------+--------------+-------------- - idxpart0 | idxpart0_pkey | | t | idxpart0_pkey | t | 0 | t | t - idxpart | idxpart_pkey | | f | idxpart_pkey | t | 0 | t | t -(2 rows) - -alter index idxpart_pkey attach partition idxpart0_pkey; -select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid, - conname, conislocal, coninhcount, connoinherit, convalidated - from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid) - left join pg_constraint con on (idx.indexrelid = con.conindid) - where indrelid::regclass::text like 'idxpart%' - order by indexrelid::regclass::text collate "C"; - indrelid | indexrelid | inhparent | indisvalid | conname | conislocal | coninhcount | connoinherit | convalidated -----------+---------------+--------------+------------+---------------+------------+-------------+--------------+-------------- - idxpart0 | idxpart0_pkey | idxpart_pkey | t | idxpart0_pkey | f | 1 | t | t - idxpart | idxpart_pkey | | t | idxpart_pkey | t | 0 | t | t -(2 rows) - -drop table idxpart; --- Related to the above scenario: ADD PRIMARY KEY on the parent mustn't --- automatically propagate NOT NULL to child columns. -create table idxpart (a int) partition by range (a); -create table idxpart0 (like idxpart); -alter table idxpart0 add unique (a); -alter table idxpart attach partition idxpart0 default; -alter table only idxpart add primary key (a); -- fail, no not-null constraint -ERROR: constraint must be added to child tables too -DETAIL: Column "a" of relation "idxpart0" is not already NOT NULL. -HINT: Do not specify the ONLY keyword. -alter table idxpart0 alter column a set not null; -alter table only idxpart add primary key (a); -- now it works -alter index idxpart_pkey attach partition idxpart0_a_key; -alter table idxpart0 alter column a drop not null; -- fail, pkey needs it -ERROR: column "a" is marked NOT NULL in parent table -drop table idxpart; --- if a partition has a unique index without a constraint, does not attach --- automatically; creates a new index instead. -create table idxpart (a int, b int) partition by range (a); -create table idxpart1 (a int not null, b int); -create unique index on idxpart1 (a); -alter table idxpart add primary key (a); -alter table idxpart attach partition idxpart1 for values from (1) to (1000); -select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid, - conname, conislocal, coninhcount, connoinherit, convalidated - from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid) - left join pg_constraint con on (idx.indexrelid = con.conindid) - where indrelid::regclass::text like 'idxpart%' - order by indexrelid::regclass::text collate "C"; - indrelid | indexrelid | inhparent | indisvalid | conname | conislocal | coninhcount | connoinherit | convalidated -----------+----------------+--------------+------------+---------------+------------+-------------+--------------+-------------- - idxpart1 | idxpart1_a_idx | | t | | | | | - idxpart1 | idxpart1_pkey | idxpart_pkey | t | idxpart1_pkey | f | 1 | f | t - idxpart | idxpart_pkey | | t | idxpart_pkey | t | 0 | t | t -(3 rows) - -drop table idxpart; --- Can't attach an index without a corresponding constraint -create table idxpart (a int, b int) partition by range (a); -create table idxpart1 (a int not null, b int); -create unique index on idxpart1 (a); -alter table idxpart attach partition idxpart1 for values from (1) to (1000); -alter table only idxpart add primary key (a); -alter index idxpart_pkey attach partition idxpart1_a_idx; -- fail -ERROR: cannot attach index "idxpart1_a_idx" as a partition of index "idxpart_pkey" -DETAIL: The index "idxpart_pkey" belongs to a constraint in table "idxpart" but no constraint exists for index "idxpart1_a_idx". -drop table idxpart; --- Test that unique constraints are working -create table idxpart (a int, b text, primary key (a, b)) partition by range (a); -create table idxpart1 partition of idxpart for values from (0) to (100000); -create table idxpart2 (c int, like idxpart); -insert into idxpart2 (c, a, b) values (42, 572814, 'inserted first'); -alter table idxpart2 drop column c; -create unique index on idxpart (a); -alter table idxpart attach partition idxpart2 for values from (100000) to (1000000); -insert into idxpart values (0, 'zero'), (42, 'life'), (2^16, 'sixteen'); -insert into idxpart select 2^g, format('two to power of %s', g) from generate_series(15, 17) g; -ERROR: duplicate key value violates unique constraint "idxpart1_a_idx" -DETAIL: Key (a)=(65536) already exists. -insert into idxpart values (16, 'sixteen'); -insert into idxpart (b, a) values ('one', 142857), ('two', 285714); -insert into idxpart select a * 2, b || b from idxpart where a between 2^16 and 2^19; -ERROR: duplicate key value violates unique constraint "idxpart2_a_idx" -DETAIL: Key (a)=(285714) already exists. -insert into idxpart values (572814, 'five'); -ERROR: duplicate key value violates unique constraint "idxpart2_a_idx" -DETAIL: Key (a)=(572814) already exists. -insert into idxpart values (857142, 'six'); -select tableoid::regclass, * from idxpart order by a; - tableoid | a | b -----------+--------+---------------- - idxpart1 | 0 | zero - idxpart1 | 16 | sixteen - idxpart1 | 42 | life - idxpart1 | 65536 | sixteen - idxpart2 | 142857 | one - idxpart2 | 285714 | two - idxpart2 | 572814 | inserted first - idxpart2 | 857142 | six -(8 rows) - -drop table idxpart; --- Test some other non-btree index types -create table idxpart (a int, b text, c int[]) partition by range (a); -create table idxpart1 partition of idxpart for values from (0) to (100000); -set enable_seqscan to off; -create index idxpart_brin on idxpart using brin(b); -explain (costs off) select * from idxpart where b = 'abcd'; - QUERY PLAN -------------------------------------------- - Bitmap Heap Scan on idxpart1 idxpart - Recheck Cond: (b = 'abcd'::text) - -> Bitmap Index Scan on idxpart1_b_idx - Index Cond: (b = 'abcd'::text) -(4 rows) - -drop index idxpart_brin; -create index idxpart_spgist on idxpart using spgist(b); -explain (costs off) select * from idxpart where b = 'abcd'; - QUERY PLAN -------------------------------------------- - Bitmap Heap Scan on idxpart1 idxpart - Recheck Cond: (b = 'abcd'::text) - -> Bitmap Index Scan on idxpart1_b_idx - Index Cond: (b = 'abcd'::text) -(4 rows) - -drop index idxpart_spgist; -create index idxpart_gin on idxpart using gin(c); -explain (costs off) select * from idxpart where c @> array[42]; - QUERY PLAN ----------------------------------------------- - Bitmap Heap Scan on idxpart1 idxpart - Recheck Cond: (c @> '{42}'::integer[]) - -> Bitmap Index Scan on idxpart1_c_idx - Index Cond: (c @> '{42}'::integer[]) -(4 rows) - -drop index idxpart_gin; -reset enable_seqscan; -drop table idxpart; --- intentionally leave some objects around -create table idxpart (a int) partition by range (a); -create table idxpart1 partition of idxpart for values from (0) to (100); -create table idxpart2 partition of idxpart for values from (100) to (1000) - partition by range (a); -create table idxpart21 partition of idxpart2 for values from (100) to (200); -create table idxpart22 partition of idxpart2 for values from (200) to (300); -create index on idxpart22 (a); -create index on only idxpart2 (a); -alter index idxpart2_a_idx attach partition idxpart22_a_idx; -create index on idxpart (a); -create table idxpart_another (a int, b int, primary key (a, b)) partition by range (a); -create table idxpart_another_1 partition of idxpart_another for values from (0) to (100); -create table idxpart3 (c int, b int, a int) partition by range (a); -alter table idxpart3 drop column b, drop column c; -create table idxpart31 partition of idxpart3 for values from (1000) to (1200); -create table idxpart32 partition of idxpart3 for values from (1200) to (1400); -alter table idxpart attach partition idxpart3 for values from (1000) to (2000); --- More objects intentionally left behind, to verify some pg_dump/pg_upgrade --- behavior; see https://postgr.es/m/20190321204928.GA17535@alvherre.pgsql -create schema regress_indexing; -set search_path to regress_indexing; -create table pk (a int primary key) partition by range (a); -create table pk1 partition of pk for values from (0) to (1000); -create table pk2 (b int, a int); -alter table pk2 drop column b; -alter table pk2 alter a set not null; -alter table pk attach partition pk2 for values from (1000) to (2000); -create table pk3 partition of pk for values from (2000) to (3000); -create table pk4 (like pk); -alter table pk attach partition pk4 for values from (3000) to (4000); -create table pk5 (like pk) partition by range (a); -create table pk51 partition of pk5 for values from (4000) to (4500); -create table pk52 partition of pk5 for values from (4500) to (5000); -alter table pk attach partition pk5 for values from (4000) to (5000); -reset search_path; --- Test that covering partitioned indexes work in various cases -create table covidxpart (a int, b int) partition by list (a); -create unique index on covidxpart (a) include (b); -create table covidxpart1 partition of covidxpart for values in (1); -create table covidxpart2 partition of covidxpart for values in (2); -insert into covidxpart values (1, 1); -insert into covidxpart values (1, 1); -ERROR: duplicate key value violates unique constraint "covidxpart1_a_b_idx" -DETAIL: Key (a)=(1) already exists. -create table covidxpart3 (b int, c int, a int); -alter table covidxpart3 drop c; -alter table covidxpart attach partition covidxpart3 for values in (3); -insert into covidxpart values (3, 1); -insert into covidxpart values (3, 1); -ERROR: duplicate key value violates unique constraint "covidxpart3_a_b_idx" -DETAIL: Key (a)=(3) already exists. -create table covidxpart4 (b int, a int); -create unique index on covidxpart4 (a) include (b); -create unique index on covidxpart4 (a); -alter table covidxpart attach partition covidxpart4 for values in (4); -insert into covidxpart values (4, 1); -insert into covidxpart values (4, 1); -ERROR: duplicate key value violates unique constraint "covidxpart4_a_b_idx" -DETAIL: Key (a)=(4) already exists. -create unique index on covidxpart (b) include (a); -- should fail -ERROR: unique constraint on partitioned table must include all partitioning columns -DETAIL: UNIQUE constraint on table "covidxpart" lacks column "a" which is part of the partition key. --- check that detaching a partition also detaches the primary key constraint -create table parted_pk_detach_test (a int primary key) partition by list (a); -create table parted_pk_detach_test1 partition of parted_pk_detach_test for values in (1); -alter table parted_pk_detach_test1 drop constraint parted_pk_detach_test1_pkey; -- should fail -ERROR: cannot drop inherited constraint "parted_pk_detach_test1_pkey" of relation "parted_pk_detach_test1" -alter table parted_pk_detach_test detach partition parted_pk_detach_test1; -alter table parted_pk_detach_test1 drop constraint parted_pk_detach_test1_pkey; -drop table parted_pk_detach_test, parted_pk_detach_test1; -create table parted_uniq_detach_test (a int unique) partition by list (a); -create table parted_uniq_detach_test1 partition of parted_uniq_detach_test for values in (1); -alter table parted_uniq_detach_test1 drop constraint parted_uniq_detach_test1_a_key; -- should fail -ERROR: cannot drop inherited constraint "parted_uniq_detach_test1_a_key" of relation "parted_uniq_detach_test1" -alter table parted_uniq_detach_test detach partition parted_uniq_detach_test1; -alter table parted_uniq_detach_test1 drop constraint parted_uniq_detach_test1_a_key; -drop table parted_uniq_detach_test, parted_uniq_detach_test1; --- check that dropping a column takes with it any partitioned indexes --- depending on it. -create table parted_index_col_drop(a int, b int, c int) - partition by list (a); -create table parted_index_col_drop1 partition of parted_index_col_drop - for values in (1) partition by list (a); --- leave this partition without children. -create table parted_index_col_drop2 partition of parted_index_col_drop - for values in (2) partition by list (a); -create table parted_index_col_drop11 partition of parted_index_col_drop1 - for values in (1); -create index on parted_index_col_drop (b); -create index on parted_index_col_drop (c); -create index on parted_index_col_drop (b, c); -alter table parted_index_col_drop drop column c; -\d parted_index_col_drop - Partitioned table "public.parted_index_col_drop" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | integer | | | -Partition key: LIST (a) -Indexes: - "parted_index_col_drop_b_idx" btree (b) -Number of partitions: 2 (Use \d+ to list them.) - -\d parted_index_col_drop1 - Partitioned table "public.parted_index_col_drop1" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | integer | | | -Partition of: parted_index_col_drop FOR VALUES IN (1) -Partition key: LIST (a) -Indexes: - "parted_index_col_drop1_b_idx" btree (b) -Number of partitions: 1 (Use \d+ to list them.) - -\d parted_index_col_drop2 - Partitioned table "public.parted_index_col_drop2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | integer | | | -Partition of: parted_index_col_drop FOR VALUES IN (2) -Partition key: LIST (a) -Indexes: - "parted_index_col_drop2_b_idx" btree (b) -Number of partitions: 0 - -\d parted_index_col_drop11 - Table "public.parted_index_col_drop11" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | integer | | | -Partition of: parted_index_col_drop1 FOR VALUES IN (1) -Indexes: - "parted_index_col_drop11_b_idx" btree (b) - -drop table parted_index_col_drop; --- Check that invalid indexes are not selected when attaching a partition. -create table parted_inval_tab (a int) partition by range (a); -create index parted_inval_idx on parted_inval_tab (a); -create table parted_inval_tab_1 (a int) partition by range (a); -create table parted_inval_tab_1_1 partition of parted_inval_tab_1 - for values from (0) to (10); -create table parted_inval_tab_1_2 partition of parted_inval_tab_1 - for values from (10) to (20); --- this creates an invalid index. -create index parted_inval_ixd_1 on only parted_inval_tab_1 (a); --- this creates new indexes for all the partitions of parted_inval_tab_1, --- discarding the invalid index created previously as what is chosen. -alter table parted_inval_tab attach partition parted_inval_tab_1 - for values from (1) to (100); -select indexrelid::regclass, indisvalid, - indrelid::regclass, inhparent::regclass - from pg_index idx left join - pg_inherits inh on (idx.indexrelid = inh.inhrelid) - where indexrelid::regclass::text like 'parted_inval%' - order by indexrelid::regclass::text collate "C"; - indexrelid | indisvalid | indrelid | inhparent -----------------------------+------------+----------------------+-------------------------- - parted_inval_idx | t | parted_inval_tab | - parted_inval_ixd_1 | f | parted_inval_tab_1 | - parted_inval_tab_1_1_a_idx | t | parted_inval_tab_1_1 | parted_inval_tab_1_a_idx - parted_inval_tab_1_2_a_idx | t | parted_inval_tab_1_2 | parted_inval_tab_1_a_idx - parted_inval_tab_1_a_idx | t | parted_inval_tab_1 | parted_inval_idx -(5 rows) - -drop table parted_inval_tab; --- Check setup of indisvalid across a complex partition tree on index --- creation. If one index in a partition index is invalid, so should its --- partitioned index. -create table parted_isvalid_tab (a int, b int) partition by range (a); -create table parted_isvalid_tab_1 partition of parted_isvalid_tab - for values from (1) to (10) partition by range (a); -create table parted_isvalid_tab_2 partition of parted_isvalid_tab - for values from (10) to (20) partition by range (a); -create table parted_isvalid_tab_11 partition of parted_isvalid_tab_1 - for values from (1) to (5); -create table parted_isvalid_tab_12 partition of parted_isvalid_tab_1 - for values from (5) to (10); --- create an invalid index on one of the partitions. -insert into parted_isvalid_tab_11 values (1, 0); -create index concurrently parted_isvalid_idx_11 on parted_isvalid_tab_11 ((a/b)); -ERROR: division by zero --- The previous invalid index is selected, invalidating all the indexes up to --- the top-most parent. -create index parted_isvalid_idx on parted_isvalid_tab ((a/b)); -select indexrelid::regclass, indisvalid, - indrelid::regclass, inhparent::regclass - from pg_index idx left join - pg_inherits inh on (idx.indexrelid = inh.inhrelid) - where indexrelid::regclass::text like 'parted_isvalid%' - order by indexrelid::regclass::text collate "C"; - indexrelid | indisvalid | indrelid | inhparent ---------------------------------+------------+-----------------------+------------------------------- - parted_isvalid_idx | f | parted_isvalid_tab | - parted_isvalid_idx_11 | f | parted_isvalid_tab_11 | parted_isvalid_tab_1_expr_idx - parted_isvalid_tab_12_expr_idx | t | parted_isvalid_tab_12 | parted_isvalid_tab_1_expr_idx - parted_isvalid_tab_1_expr_idx | f | parted_isvalid_tab_1 | parted_isvalid_idx - parted_isvalid_tab_2_expr_idx | t | parted_isvalid_tab_2 | parted_isvalid_idx -(5 rows) - -drop table parted_isvalid_tab; --- Check state of replica indexes when attaching a partition. -begin; -create table parted_replica_tab (id int not null) partition by range (id); -create table parted_replica_tab_1 partition of parted_replica_tab - for values from (1) to (10) partition by range (id); -create table parted_replica_tab_11 partition of parted_replica_tab_1 - for values from (1) to (5); -create unique index parted_replica_idx - on only parted_replica_tab using btree (id); -create unique index parted_replica_idx_1 - on only parted_replica_tab_1 using btree (id); --- This triggers an update of pg_index.indisreplident for parted_replica_idx. -alter table only parted_replica_tab_1 replica identity - using index parted_replica_idx_1; -create unique index parted_replica_idx_11 on parted_replica_tab_11 USING btree (id); -select indexrelid::regclass, indisvalid, indisreplident, - indrelid::regclass, inhparent::regclass - from pg_index idx left join - pg_inherits inh on (idx.indexrelid = inh.inhrelid) - where indexrelid::regclass::text like 'parted_replica%' - order by indexrelid::regclass::text collate "C"; - indexrelid | indisvalid | indisreplident | indrelid | inhparent ------------------------+------------+----------------+-----------------------+----------- - parted_replica_idx | f | f | parted_replica_tab | - parted_replica_idx_1 | f | t | parted_replica_tab_1 | - parted_replica_idx_11 | t | f | parted_replica_tab_11 | -(3 rows) - --- parted_replica_idx is not valid yet here, because parted_replica_idx_1 --- is not valid. -alter index parted_replica_idx ATTACH PARTITION parted_replica_idx_1; -select indexrelid::regclass, indisvalid, indisreplident, - indrelid::regclass, inhparent::regclass - from pg_index idx left join - pg_inherits inh on (idx.indexrelid = inh.inhrelid) - where indexrelid::regclass::text like 'parted_replica%' - order by indexrelid::regclass::text collate "C"; - indexrelid | indisvalid | indisreplident | indrelid | inhparent ------------------------+------------+----------------+-----------------------+-------------------- - parted_replica_idx | f | f | parted_replica_tab | - parted_replica_idx_1 | f | t | parted_replica_tab_1 | parted_replica_idx - parted_replica_idx_11 | t | f | parted_replica_tab_11 | -(3 rows) - --- parted_replica_idx becomes valid here. -alter index parted_replica_idx_1 ATTACH PARTITION parted_replica_idx_11; -alter table only parted_replica_tab_1 replica identity - using index parted_replica_idx_1; -commit; -select indexrelid::regclass, indisvalid, indisreplident, - indrelid::regclass, inhparent::regclass - from pg_index idx left join - pg_inherits inh on (idx.indexrelid = inh.inhrelid) - where indexrelid::regclass::text like 'parted_replica%' - order by indexrelid::regclass::text collate "C"; - indexrelid | indisvalid | indisreplident | indrelid | inhparent ------------------------+------------+----------------+-----------------------+---------------------- - parted_replica_idx | t | f | parted_replica_tab | - parted_replica_idx_1 | t | t | parted_replica_tab_1 | parted_replica_idx - parted_replica_idx_11 | t | f | parted_replica_tab_11 | parted_replica_idx_1 -(3 rows) - -drop table parted_replica_tab; +psql: error: connection to server on socket "/tmp/bBabKAIw6z/.s.PGSQL.19533" failed: No such file or directory + Is the server running locally and accepting connections on that socket? diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/partition_aggregate.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/partition_aggregate.out --- /tmp/cirrus-ci-build/src/test/regress/expected/partition_aggregate.out 2024-09-22 06:28:18.733166297 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/partition_aggregate.out 2024-09-22 06:38:09.884217553 +0000 @@ -1,1520 +1,2 @@ --- --- PARTITION_AGGREGATE --- Test partitionwise aggregation on partitioned tables --- --- Note: to ensure plan stability, it's a good idea to make the partitions of --- any one partitioned table in this test all have different numbers of rows. --- --- Enable partitionwise aggregate, which by default is disabled. -SET enable_partitionwise_aggregate TO true; --- Enable partitionwise join, which by default is disabled. -SET enable_partitionwise_join TO true; --- Disable parallel plans. -SET max_parallel_workers_per_gather TO 0; --- Disable incremental sort, which can influence selected plans due to fuzz factor. -SET enable_incremental_sort TO off; --- --- Tests for list partitioned tables. --- -CREATE TABLE pagg_tab (a int, b int, c text, d int) PARTITION BY LIST(c); -CREATE TABLE pagg_tab_p1 PARTITION OF pagg_tab FOR VALUES IN ('0000', '0001', '0002', '0003', '0004'); -CREATE TABLE pagg_tab_p2 PARTITION OF pagg_tab FOR VALUES IN ('0005', '0006', '0007', '0008'); -CREATE TABLE pagg_tab_p3 PARTITION OF pagg_tab FOR VALUES IN ('0009', '0010', '0011'); -INSERT INTO pagg_tab SELECT i % 20, i % 30, to_char(i % 12, 'FM0000'), i % 30 FROM generate_series(0, 2999) i; -ANALYZE pagg_tab; --- When GROUP BY clause matches; full aggregation is performed for each partition. -EXPLAIN (COSTS OFF) -SELECT c, sum(a), avg(b), count(*), min(a), max(b) FROM pagg_tab GROUP BY c HAVING avg(d) < 15 ORDER BY 1, 2, 3; - QUERY PLAN --------------------------------------------------------------- - Sort - Sort Key: pagg_tab.c, (sum(pagg_tab.a)), (avg(pagg_tab.b)) - -> Append - -> HashAggregate - Group Key: pagg_tab.c - Filter: (avg(pagg_tab.d) < '15'::numeric) - -> Seq Scan on pagg_tab_p1 pagg_tab - -> HashAggregate - Group Key: pagg_tab_1.c - Filter: (avg(pagg_tab_1.d) < '15'::numeric) - -> Seq Scan on pagg_tab_p2 pagg_tab_1 - -> HashAggregate - Group Key: pagg_tab_2.c - Filter: (avg(pagg_tab_2.d) < '15'::numeric) - -> Seq Scan on pagg_tab_p3 pagg_tab_2 -(15 rows) - -SELECT c, sum(a), avg(b), count(*), min(a), max(b) FROM pagg_tab GROUP BY c HAVING avg(d) < 15 ORDER BY 1, 2, 3; - c | sum | avg | count | min | max -------+------+---------------------+-------+-----+----- - 0000 | 2000 | 12.0000000000000000 | 250 | 0 | 24 - 0001 | 2250 | 13.0000000000000000 | 250 | 1 | 25 - 0002 | 2500 | 14.0000000000000000 | 250 | 2 | 26 - 0006 | 2500 | 12.0000000000000000 | 250 | 2 | 24 - 0007 | 2750 | 13.0000000000000000 | 250 | 3 | 25 - 0008 | 2000 | 14.0000000000000000 | 250 | 0 | 26 -(6 rows) - --- When GROUP BY clause does not match; partial aggregation is performed for each partition. -EXPLAIN (COSTS OFF) -SELECT a, sum(b), avg(b), count(*), min(a), max(b) FROM pagg_tab GROUP BY a HAVING avg(d) < 15 ORDER BY 1, 2, 3; - QUERY PLAN --------------------------------------------------------------- - Sort - Sort Key: pagg_tab.a, (sum(pagg_tab.b)), (avg(pagg_tab.b)) - -> Finalize HashAggregate - Group Key: pagg_tab.a - Filter: (avg(pagg_tab.d) < '15'::numeric) - -> Append - -> Partial HashAggregate - Group Key: pagg_tab.a - -> Seq Scan on pagg_tab_p1 pagg_tab - -> Partial HashAggregate - Group Key: pagg_tab_1.a - -> Seq Scan on pagg_tab_p2 pagg_tab_1 - -> Partial HashAggregate - Group Key: pagg_tab_2.a - -> Seq Scan on pagg_tab_p3 pagg_tab_2 -(15 rows) - -SELECT a, sum(b), avg(b), count(*), min(a), max(b) FROM pagg_tab GROUP BY a HAVING avg(d) < 15 ORDER BY 1, 2, 3; - a | sum | avg | count | min | max -----+------+---------------------+-------+-----+----- - 0 | 1500 | 10.0000000000000000 | 150 | 0 | 20 - 1 | 1650 | 11.0000000000000000 | 150 | 1 | 21 - 2 | 1800 | 12.0000000000000000 | 150 | 2 | 22 - 3 | 1950 | 13.0000000000000000 | 150 | 3 | 23 - 4 | 2100 | 14.0000000000000000 | 150 | 4 | 24 - 10 | 1500 | 10.0000000000000000 | 150 | 10 | 20 - 11 | 1650 | 11.0000000000000000 | 150 | 11 | 21 - 12 | 1800 | 12.0000000000000000 | 150 | 12 | 22 - 13 | 1950 | 13.0000000000000000 | 150 | 13 | 23 - 14 | 2100 | 14.0000000000000000 | 150 | 14 | 24 -(10 rows) - --- Check with multiple columns in GROUP BY -EXPLAIN (COSTS OFF) -SELECT a, c, count(*) FROM pagg_tab GROUP BY a, c; - QUERY PLAN ------------------------------------------------- - Append - -> HashAggregate - Group Key: pagg_tab.a, pagg_tab.c - -> Seq Scan on pagg_tab_p1 pagg_tab - -> HashAggregate - Group Key: pagg_tab_1.a, pagg_tab_1.c - -> Seq Scan on pagg_tab_p2 pagg_tab_1 - -> HashAggregate - Group Key: pagg_tab_2.a, pagg_tab_2.c - -> Seq Scan on pagg_tab_p3 pagg_tab_2 -(10 rows) - --- Check with multiple columns in GROUP BY, order in GROUP BY is reversed -EXPLAIN (COSTS OFF) -SELECT a, c, count(*) FROM pagg_tab GROUP BY c, a; - QUERY PLAN ------------------------------------------------- - Append - -> HashAggregate - Group Key: pagg_tab.c, pagg_tab.a - -> Seq Scan on pagg_tab_p1 pagg_tab - -> HashAggregate - Group Key: pagg_tab_1.c, pagg_tab_1.a - -> Seq Scan on pagg_tab_p2 pagg_tab_1 - -> HashAggregate - Group Key: pagg_tab_2.c, pagg_tab_2.a - -> Seq Scan on pagg_tab_p3 pagg_tab_2 -(10 rows) - --- Check with multiple columns in GROUP BY, order in target-list is reversed -EXPLAIN (COSTS OFF) -SELECT c, a, count(*) FROM pagg_tab GROUP BY a, c; - QUERY PLAN ------------------------------------------------- - Append - -> HashAggregate - Group Key: pagg_tab.a, pagg_tab.c - -> Seq Scan on pagg_tab_p1 pagg_tab - -> HashAggregate - Group Key: pagg_tab_1.a, pagg_tab_1.c - -> Seq Scan on pagg_tab_p2 pagg_tab_1 - -> HashAggregate - Group Key: pagg_tab_2.a, pagg_tab_2.c - -> Seq Scan on pagg_tab_p3 pagg_tab_2 -(10 rows) - --- Test when input relation for grouping is dummy -EXPLAIN (COSTS OFF) -SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; - QUERY PLAN --------------------------------- - HashAggregate - Group Key: c - -> Result - One-Time Filter: false -(4 rows) - -SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; - c | sum ----+----- -(0 rows) - -EXPLAIN (COSTS OFF) -SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; - QUERY PLAN --------------------------------- - GroupAggregate - -> Result - One-Time Filter: false -(3 rows) - -SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; - c | sum ----+----- -(0 rows) - --- Test GroupAggregate paths by disabling hash aggregates. -SET enable_hashagg TO false; --- When GROUP BY clause matches full aggregation is performed for each partition. -EXPLAIN (COSTS OFF) -SELECT c, sum(a), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3; - QUERY PLAN --------------------------------------------------------------- - Sort - Sort Key: pagg_tab.c, (sum(pagg_tab.a)), (avg(pagg_tab.b)) - -> Append - -> GroupAggregate - Group Key: pagg_tab.c - Filter: (avg(pagg_tab.d) < '15'::numeric) - -> Sort - Sort Key: pagg_tab.c - -> Seq Scan on pagg_tab_p1 pagg_tab - -> GroupAggregate - Group Key: pagg_tab_1.c - Filter: (avg(pagg_tab_1.d) < '15'::numeric) - -> Sort - Sort Key: pagg_tab_1.c - -> Seq Scan on pagg_tab_p2 pagg_tab_1 - -> GroupAggregate - Group Key: pagg_tab_2.c - Filter: (avg(pagg_tab_2.d) < '15'::numeric) - -> Sort - Sort Key: pagg_tab_2.c - -> Seq Scan on pagg_tab_p3 pagg_tab_2 -(21 rows) - -SELECT c, sum(a), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3; - c | sum | avg | count -------+------+---------------------+------- - 0000 | 2000 | 12.0000000000000000 | 250 - 0001 | 2250 | 13.0000000000000000 | 250 - 0002 | 2500 | 14.0000000000000000 | 250 - 0006 | 2500 | 12.0000000000000000 | 250 - 0007 | 2750 | 13.0000000000000000 | 250 - 0008 | 2000 | 14.0000000000000000 | 250 -(6 rows) - --- When GROUP BY clause does not match; partial aggregation is performed for each partition. -EXPLAIN (COSTS OFF) -SELECT a, sum(b), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3; - QUERY PLAN ------------------------------------------------------------------- - Sort - Sort Key: pagg_tab.a, (sum(pagg_tab.b)), (avg(pagg_tab.b)) - -> Finalize GroupAggregate - Group Key: pagg_tab.a - Filter: (avg(pagg_tab.d) < '15'::numeric) - -> Merge Append - Sort Key: pagg_tab.a - -> Partial GroupAggregate - Group Key: pagg_tab.a - -> Sort - Sort Key: pagg_tab.a - -> Seq Scan on pagg_tab_p1 pagg_tab - -> Partial GroupAggregate - Group Key: pagg_tab_1.a - -> Sort - Sort Key: pagg_tab_1.a - -> Seq Scan on pagg_tab_p2 pagg_tab_1 - -> Partial GroupAggregate - Group Key: pagg_tab_2.a - -> Sort - Sort Key: pagg_tab_2.a - -> Seq Scan on pagg_tab_p3 pagg_tab_2 -(22 rows) - -SELECT a, sum(b), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3; - a | sum | avg | count -----+------+---------------------+------- - 0 | 1500 | 10.0000000000000000 | 150 - 1 | 1650 | 11.0000000000000000 | 150 - 2 | 1800 | 12.0000000000000000 | 150 - 3 | 1950 | 13.0000000000000000 | 150 - 4 | 2100 | 14.0000000000000000 | 150 - 10 | 1500 | 10.0000000000000000 | 150 - 11 | 1650 | 11.0000000000000000 | 150 - 12 | 1800 | 12.0000000000000000 | 150 - 13 | 1950 | 13.0000000000000000 | 150 - 14 | 2100 | 14.0000000000000000 | 150 -(10 rows) - --- Test partitionwise grouping without any aggregates -EXPLAIN (COSTS OFF) -SELECT c FROM pagg_tab GROUP BY c ORDER BY 1; - QUERY PLAN ------------------------------------------------------- - Merge Append - Sort Key: pagg_tab.c - -> Group - Group Key: pagg_tab.c - -> Sort - Sort Key: pagg_tab.c - -> Seq Scan on pagg_tab_p1 pagg_tab - -> Group - Group Key: pagg_tab_1.c - -> Sort - Sort Key: pagg_tab_1.c - -> Seq Scan on pagg_tab_p2 pagg_tab_1 - -> Group - Group Key: pagg_tab_2.c - -> Sort - Sort Key: pagg_tab_2.c - -> Seq Scan on pagg_tab_p3 pagg_tab_2 -(17 rows) - -SELECT c FROM pagg_tab GROUP BY c ORDER BY 1; - c ------- - 0000 - 0001 - 0002 - 0003 - 0004 - 0005 - 0006 - 0007 - 0008 - 0009 - 0010 - 0011 -(12 rows) - -EXPLAIN (COSTS OFF) -SELECT a FROM pagg_tab WHERE a < 3 GROUP BY a ORDER BY 1; - QUERY PLAN ------------------------------------------------------------- - Group - Group Key: pagg_tab.a - -> Merge Append - Sort Key: pagg_tab.a - -> Group - Group Key: pagg_tab.a - -> Sort - Sort Key: pagg_tab.a - -> Seq Scan on pagg_tab_p1 pagg_tab - Filter: (a < 3) - -> Group - Group Key: pagg_tab_1.a - -> Sort - Sort Key: pagg_tab_1.a - -> Seq Scan on pagg_tab_p2 pagg_tab_1 - Filter: (a < 3) - -> Group - Group Key: pagg_tab_2.a - -> Sort - Sort Key: pagg_tab_2.a - -> Seq Scan on pagg_tab_p3 pagg_tab_2 - Filter: (a < 3) -(22 rows) - -SELECT a FROM pagg_tab WHERE a < 3 GROUP BY a ORDER BY 1; - a ---- - 0 - 1 - 2 -(3 rows) - -RESET enable_hashagg; --- ROLLUP, partitionwise aggregation does not apply -EXPLAIN (COSTS OFF) -SELECT c, sum(a) FROM pagg_tab GROUP BY rollup(c) ORDER BY 1, 2; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: pagg_tab.c, (sum(pagg_tab.a)) - -> MixedAggregate - Hash Key: pagg_tab.c - Group Key: () - -> Append - -> Seq Scan on pagg_tab_p1 pagg_tab_1 - -> Seq Scan on pagg_tab_p2 pagg_tab_2 - -> Seq Scan on pagg_tab_p3 pagg_tab_3 -(9 rows) - --- ORDERED SET within the aggregate. --- Full aggregation; since all the rows that belong to the same group come --- from the same partition, having an ORDER BY within the aggregate doesn't --- make any difference. -EXPLAIN (COSTS OFF) -SELECT c, sum(b order by a) FROM pagg_tab GROUP BY c ORDER BY 1, 2; - QUERY PLAN ---------------------------------------------------------------- - Sort - Sort Key: pagg_tab.c, (sum(pagg_tab.b ORDER BY pagg_tab.a)) - -> Append - -> GroupAggregate - Group Key: pagg_tab.c - -> Sort - Sort Key: pagg_tab.c, pagg_tab.a - -> Seq Scan on pagg_tab_p1 pagg_tab - -> GroupAggregate - Group Key: pagg_tab_1.c - -> Sort - Sort Key: pagg_tab_1.c, pagg_tab_1.a - -> Seq Scan on pagg_tab_p2 pagg_tab_1 - -> GroupAggregate - Group Key: pagg_tab_2.c - -> Sort - Sort Key: pagg_tab_2.c, pagg_tab_2.a - -> Seq Scan on pagg_tab_p3 pagg_tab_2 -(18 rows) - --- Since GROUP BY clause does not match with PARTITION KEY; we need to do --- partial aggregation. However, ORDERED SET are not partial safe and thus --- partitionwise aggregation plan is not generated. -EXPLAIN (COSTS OFF) -SELECT a, sum(b order by a) FROM pagg_tab GROUP BY a ORDER BY 1, 2; - QUERY PLAN ---------------------------------------------------------------- - Sort - Sort Key: pagg_tab.a, (sum(pagg_tab.b ORDER BY pagg_tab.a)) - -> GroupAggregate - Group Key: pagg_tab.a - -> Sort - Sort Key: pagg_tab.a - -> Append - -> Seq Scan on pagg_tab_p1 pagg_tab_1 - -> Seq Scan on pagg_tab_p2 pagg_tab_2 - -> Seq Scan on pagg_tab_p3 pagg_tab_3 -(10 rows) - --- JOIN query -CREATE TABLE pagg_tab1(x int, y int) PARTITION BY RANGE(x); -CREATE TABLE pagg_tab1_p1 PARTITION OF pagg_tab1 FOR VALUES FROM (0) TO (10); -CREATE TABLE pagg_tab1_p2 PARTITION OF pagg_tab1 FOR VALUES FROM (10) TO (20); -CREATE TABLE pagg_tab1_p3 PARTITION OF pagg_tab1 FOR VALUES FROM (20) TO (30); -CREATE TABLE pagg_tab2(x int, y int) PARTITION BY RANGE(y); -CREATE TABLE pagg_tab2_p1 PARTITION OF pagg_tab2 FOR VALUES FROM (0) TO (10); -CREATE TABLE pagg_tab2_p2 PARTITION OF pagg_tab2 FOR VALUES FROM (10) TO (20); -CREATE TABLE pagg_tab2_p3 PARTITION OF pagg_tab2 FOR VALUES FROM (20) TO (30); -INSERT INTO pagg_tab1 SELECT i % 30, i % 20 FROM generate_series(0, 299, 2) i; -INSERT INTO pagg_tab2 SELECT i % 20, i % 30 FROM generate_series(0, 299, 3) i; -ANALYZE pagg_tab1; -ANALYZE pagg_tab2; --- When GROUP BY clause matches; full aggregation is performed for each partition. -EXPLAIN (COSTS OFF) -SELECT t1.x, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.x ORDER BY 1, 2, 3; - QUERY PLAN -------------------------------------------------------------- - Sort - Sort Key: t1.x, (sum(t1.y)), (count(*)) - -> Append - -> HashAggregate - Group Key: t1.x - -> Hash Join - Hash Cond: (t1.x = t2.y) - -> Seq Scan on pagg_tab1_p1 t1 - -> Hash - -> Seq Scan on pagg_tab2_p1 t2 - -> HashAggregate - Group Key: t1_1.x - -> Hash Join - Hash Cond: (t1_1.x = t2_1.y) - -> Seq Scan on pagg_tab1_p2 t1_1 - -> Hash - -> Seq Scan on pagg_tab2_p2 t2_1 - -> HashAggregate - Group Key: t1_2.x - -> Hash Join - Hash Cond: (t2_2.y = t1_2.x) - -> Seq Scan on pagg_tab2_p3 t2_2 - -> Hash - -> Seq Scan on pagg_tab1_p3 t1_2 -(24 rows) - -SELECT t1.x, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.x ORDER BY 1, 2, 3; - x | sum | count -----+------+------- - 0 | 500 | 100 - 6 | 1100 | 100 - 12 | 700 | 100 - 18 | 1300 | 100 - 24 | 900 | 100 -(5 rows) - --- Check with whole-row reference; partitionwise aggregation does not apply -EXPLAIN (COSTS OFF) -SELECT t1.x, sum(t1.y), count(t1) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.x ORDER BY 1, 2, 3; - QUERY PLAN -------------------------------------------------------------- - Sort - Sort Key: t1.x, (sum(t1.y)), (count(((t1.*)::pagg_tab1))) - -> HashAggregate - Group Key: t1.x - -> Hash Join - Hash Cond: (t1.x = t2.y) - -> Append - -> Seq Scan on pagg_tab1_p1 t1_1 - -> Seq Scan on pagg_tab1_p2 t1_2 - -> Seq Scan on pagg_tab1_p3 t1_3 - -> Hash - -> Append - -> Seq Scan on pagg_tab2_p1 t2_1 - -> Seq Scan on pagg_tab2_p2 t2_2 - -> Seq Scan on pagg_tab2_p3 t2_3 -(15 rows) - -SELECT t1.x, sum(t1.y), count(t1) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.x ORDER BY 1, 2, 3; - x | sum | count -----+------+------- - 0 | 500 | 100 - 6 | 1100 | 100 - 12 | 700 | 100 - 18 | 1300 | 100 - 24 | 900 | 100 -(5 rows) - --- GROUP BY having other matching key -EXPLAIN (COSTS OFF) -SELECT t2.y, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t2.y ORDER BY 1, 2, 3; - QUERY PLAN -------------------------------------------------------------- - Sort - Sort Key: t2.y, (sum(t1.y)), (count(*)) - -> Append - -> HashAggregate - Group Key: t2.y - -> Hash Join - Hash Cond: (t1.x = t2.y) - -> Seq Scan on pagg_tab1_p1 t1 - -> Hash - -> Seq Scan on pagg_tab2_p1 t2 - -> HashAggregate - Group Key: t2_1.y - -> Hash Join - Hash Cond: (t1_1.x = t2_1.y) - -> Seq Scan on pagg_tab1_p2 t1_1 - -> Hash - -> Seq Scan on pagg_tab2_p2 t2_1 - -> HashAggregate - Group Key: t2_2.y - -> Hash Join - Hash Cond: (t2_2.y = t1_2.x) - -> Seq Scan on pagg_tab2_p3 t2_2 - -> Hash - -> Seq Scan on pagg_tab1_p3 t1_2 -(24 rows) - --- When GROUP BY clause does not match; partial aggregation is performed for each partition. --- Also test GroupAggregate paths by disabling hash aggregates. -SET enable_hashagg TO false; -EXPLAIN (COSTS OFF) -SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.y HAVING avg(t1.x) > 10 ORDER BY 1, 2, 3; - QUERY PLAN -------------------------------------------------------------------------- - Sort - Sort Key: t1.y, (sum(t1.x)), (count(*)) - -> Finalize GroupAggregate - Group Key: t1.y - Filter: (avg(t1.x) > '10'::numeric) - -> Merge Append - Sort Key: t1.y - -> Partial GroupAggregate - Group Key: t1.y - -> Sort - Sort Key: t1.y - -> Hash Join - Hash Cond: (t1.x = t2.y) - -> Seq Scan on pagg_tab1_p1 t1 - -> Hash - -> Seq Scan on pagg_tab2_p1 t2 - -> Partial GroupAggregate - Group Key: t1_1.y - -> Sort - Sort Key: t1_1.y - -> Hash Join - Hash Cond: (t1_1.x = t2_1.y) - -> Seq Scan on pagg_tab1_p2 t1_1 - -> Hash - -> Seq Scan on pagg_tab2_p2 t2_1 - -> Partial GroupAggregate - Group Key: t1_2.y - -> Sort - Sort Key: t1_2.y - -> Hash Join - Hash Cond: (t2_2.y = t1_2.x) - -> Seq Scan on pagg_tab2_p3 t2_2 - -> Hash - -> Seq Scan on pagg_tab1_p3 t1_2 -(34 rows) - -SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.y HAVING avg(t1.x) > 10 ORDER BY 1, 2, 3; - y | sum | count -----+------+------- - 2 | 600 | 50 - 4 | 1200 | 50 - 8 | 900 | 50 - 12 | 600 | 50 - 14 | 1200 | 50 - 18 | 900 | 50 -(6 rows) - -RESET enable_hashagg; --- Check with LEFT/RIGHT/FULL OUTER JOINs which produces NULL values for --- aggregation --- LEFT JOIN, should produce partial partitionwise aggregation plan as --- GROUP BY is on nullable column -EXPLAIN (COSTS OFF) -SELECT b.y, sum(a.y) FROM pagg_tab1 a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST; - QUERY PLAN ------------------------------------------------------------------- - Finalize GroupAggregate - Group Key: b.y - -> Sort - Sort Key: b.y - -> Append - -> Partial HashAggregate - Group Key: b.y - -> Hash Left Join - Hash Cond: (a.x = b.y) - -> Seq Scan on pagg_tab1_p1 a - -> Hash - -> Seq Scan on pagg_tab2_p1 b - -> Partial HashAggregate - Group Key: b_1.y - -> Hash Left Join - Hash Cond: (a_1.x = b_1.y) - -> Seq Scan on pagg_tab1_p2 a_1 - -> Hash - -> Seq Scan on pagg_tab2_p2 b_1 - -> Partial HashAggregate - Group Key: b_2.y - -> Hash Right Join - Hash Cond: (b_2.y = a_2.x) - -> Seq Scan on pagg_tab2_p3 b_2 - -> Hash - -> Seq Scan on pagg_tab1_p3 a_2 -(26 rows) - -SELECT b.y, sum(a.y) FROM pagg_tab1 a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST; - y | sum -----+------ - 0 | 500 - 6 | 1100 - 12 | 700 - 18 | 1300 - 24 | 900 - | 900 -(6 rows) - --- RIGHT JOIN, should produce full partitionwise aggregation plan as --- GROUP BY is on non-nullable column -EXPLAIN (COSTS OFF) -SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST; - QUERY PLAN ------------------------------------------------------------- - Sort - Sort Key: b.y - -> Append - -> HashAggregate - Group Key: b.y - -> Hash Right Join - Hash Cond: (a.x = b.y) - -> Seq Scan on pagg_tab1_p1 a - -> Hash - -> Seq Scan on pagg_tab2_p1 b - -> HashAggregate - Group Key: b_1.y - -> Hash Right Join - Hash Cond: (a_1.x = b_1.y) - -> Seq Scan on pagg_tab1_p2 a_1 - -> Hash - -> Seq Scan on pagg_tab2_p2 b_1 - -> HashAggregate - Group Key: b_2.y - -> Hash Left Join - Hash Cond: (b_2.y = a_2.x) - -> Seq Scan on pagg_tab2_p3 b_2 - -> Hash - -> Seq Scan on pagg_tab1_p3 a_2 -(24 rows) - -SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST; - y | sum -----+------ - 0 | 500 - 3 | - 6 | 1100 - 9 | - 12 | 700 - 15 | - 18 | 1300 - 21 | - 24 | 900 - 27 | -(10 rows) - --- FULL JOIN, should produce partial partitionwise aggregation plan as --- GROUP BY is on nullable column -EXPLAIN (COSTS OFF) -SELECT a.x, sum(b.x) FROM pagg_tab1 a FULL OUTER JOIN pagg_tab2 b ON a.x = b.y GROUP BY a.x ORDER BY 1 NULLS LAST; - QUERY PLAN ------------------------------------------------------------------- - Finalize GroupAggregate - Group Key: a.x - -> Sort - Sort Key: a.x - -> Append - -> Partial HashAggregate - Group Key: a.x - -> Hash Full Join - Hash Cond: (a.x = b.y) - -> Seq Scan on pagg_tab1_p1 a - -> Hash - -> Seq Scan on pagg_tab2_p1 b - -> Partial HashAggregate - Group Key: a_1.x - -> Hash Full Join - Hash Cond: (a_1.x = b_1.y) - -> Seq Scan on pagg_tab1_p2 a_1 - -> Hash - -> Seq Scan on pagg_tab2_p2 b_1 - -> Partial HashAggregate - Group Key: a_2.x - -> Hash Full Join - Hash Cond: (b_2.y = a_2.x) - -> Seq Scan on pagg_tab2_p3 b_2 - -> Hash - -> Seq Scan on pagg_tab1_p3 a_2 -(26 rows) - -SELECT a.x, sum(b.x) FROM pagg_tab1 a FULL OUTER JOIN pagg_tab2 b ON a.x = b.y GROUP BY a.x ORDER BY 1 NULLS LAST; - x | sum -----+------ - 0 | 500 - 2 | - 4 | - 6 | 1100 - 8 | - 10 | - 12 | 700 - 14 | - 16 | - 18 | 1300 - 20 | - 22 | - 24 | 900 - 26 | - 28 | - | 500 -(16 rows) - --- LEFT JOIN, with dummy relation on right side, ideally --- should produce full partitionwise aggregation plan as GROUP BY is on --- non-nullable columns. --- But right now we are unable to do partitionwise join in this case. -EXPLAIN (COSTS OFF) -SELECT a.x, b.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x < 20) a LEFT JOIN (SELECT * FROM pagg_tab2 WHERE y > 10) b ON a.x = b.y WHERE a.x > 5 or b.y < 20 GROUP BY a.x, b.y ORDER BY 1, 2; - QUERY PLAN --------------------------------------------------------------------- - Sort - Sort Key: pagg_tab1.x, pagg_tab2.y - -> HashAggregate - Group Key: pagg_tab1.x, pagg_tab2.y - -> Hash Left Join - Hash Cond: (pagg_tab1.x = pagg_tab2.y) - Filter: ((pagg_tab1.x > 5) OR (pagg_tab2.y < 20)) - -> Append - -> Seq Scan on pagg_tab1_p1 pagg_tab1_1 - Filter: (x < 20) - -> Seq Scan on pagg_tab1_p2 pagg_tab1_2 - Filter: (x < 20) - -> Hash - -> Append - -> Seq Scan on pagg_tab2_p2 pagg_tab2_1 - Filter: (y > 10) - -> Seq Scan on pagg_tab2_p3 pagg_tab2_2 - Filter: (y > 10) -(18 rows) - -SELECT a.x, b.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x < 20) a LEFT JOIN (SELECT * FROM pagg_tab2 WHERE y > 10) b ON a.x = b.y WHERE a.x > 5 or b.y < 20 GROUP BY a.x, b.y ORDER BY 1, 2; - x | y | count -----+----+------- - 6 | | 10 - 8 | | 10 - 10 | | 10 - 12 | 12 | 100 - 14 | | 10 - 16 | | 10 - 18 | 18 | 100 -(7 rows) - --- FULL JOIN, with dummy relations on both sides, ideally --- should produce partial partitionwise aggregation plan as GROUP BY is on --- nullable columns. --- But right now we are unable to do partitionwise join in this case. -EXPLAIN (COSTS OFF) -SELECT a.x, b.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x < 20) a FULL JOIN (SELECT * FROM pagg_tab2 WHERE y > 10) b ON a.x = b.y WHERE a.x > 5 or b.y < 20 GROUP BY a.x, b.y ORDER BY 1, 2; - QUERY PLAN --------------------------------------------------------------------- - Sort - Sort Key: pagg_tab1.x, pagg_tab2.y - -> HashAggregate - Group Key: pagg_tab1.x, pagg_tab2.y - -> Hash Full Join - Hash Cond: (pagg_tab1.x = pagg_tab2.y) - Filter: ((pagg_tab1.x > 5) OR (pagg_tab2.y < 20)) - -> Append - -> Seq Scan on pagg_tab1_p1 pagg_tab1_1 - Filter: (x < 20) - -> Seq Scan on pagg_tab1_p2 pagg_tab1_2 - Filter: (x < 20) - -> Hash - -> Append - -> Seq Scan on pagg_tab2_p2 pagg_tab2_1 - Filter: (y > 10) - -> Seq Scan on pagg_tab2_p3 pagg_tab2_2 - Filter: (y > 10) -(18 rows) - -SELECT a.x, b.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x < 20) a FULL JOIN (SELECT * FROM pagg_tab2 WHERE y > 10) b ON a.x = b.y WHERE a.x > 5 or b.y < 20 GROUP BY a.x, b.y ORDER BY 1, 2; - x | y | count -----+----+------- - 6 | | 10 - 8 | | 10 - 10 | | 10 - 12 | 12 | 100 - 14 | | 10 - 16 | | 10 - 18 | 18 | 100 - | 15 | 10 -(8 rows) - --- Empty join relation because of empty outer side, no partitionwise agg plan -EXPLAIN (COSTS OFF) -SELECT a.x, a.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x = 1 AND x = 2) a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY a.x, a.y ORDER BY 1, 2; - QUERY PLAN --------------------------------------- - GroupAggregate - Group Key: pagg_tab1.y - -> Sort - Sort Key: pagg_tab1.y - -> Result - One-Time Filter: false -(6 rows) - -SELECT a.x, a.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x = 1 AND x = 2) a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY a.x, a.y ORDER BY 1, 2; - x | y | count ----+---+------- -(0 rows) - --- Partition by multiple columns -CREATE TABLE pagg_tab_m (a int, b int, c int) PARTITION BY RANGE(a, ((a+b)/2)); -CREATE TABLE pagg_tab_m_p1 PARTITION OF pagg_tab_m FOR VALUES FROM (0, 0) TO (12, 12); -CREATE TABLE pagg_tab_m_p2 PARTITION OF pagg_tab_m FOR VALUES FROM (12, 12) TO (22, 22); -CREATE TABLE pagg_tab_m_p3 PARTITION OF pagg_tab_m FOR VALUES FROM (22, 22) TO (30, 30); -INSERT INTO pagg_tab_m SELECT i % 30, i % 40, i % 50 FROM generate_series(0, 2999) i; -ANALYZE pagg_tab_m; --- Partial aggregation as GROUP BY clause does not match with PARTITION KEY -EXPLAIN (COSTS OFF) -SELECT a, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY a HAVING avg(c) < 22 ORDER BY 1, 2, 3; - QUERY PLAN --------------------------------------------------------------------- - Sort - Sort Key: pagg_tab_m.a, (sum(pagg_tab_m.b)), (avg(pagg_tab_m.c)) - -> Finalize HashAggregate - Group Key: pagg_tab_m.a - Filter: (avg(pagg_tab_m.c) < '22'::numeric) - -> Append - -> Partial HashAggregate - Group Key: pagg_tab_m.a - -> Seq Scan on pagg_tab_m_p1 pagg_tab_m - -> Partial HashAggregate - Group Key: pagg_tab_m_1.a - -> Seq Scan on pagg_tab_m_p2 pagg_tab_m_1 - -> Partial HashAggregate - Group Key: pagg_tab_m_2.a - -> Seq Scan on pagg_tab_m_p3 pagg_tab_m_2 -(15 rows) - -SELECT a, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY a HAVING avg(c) < 22 ORDER BY 1, 2, 3; - a | sum | avg | count -----+------+---------------------+------- - 0 | 1500 | 20.0000000000000000 | 100 - 1 | 1600 | 21.0000000000000000 | 100 - 10 | 1500 | 20.0000000000000000 | 100 - 11 | 1600 | 21.0000000000000000 | 100 - 20 | 1500 | 20.0000000000000000 | 100 - 21 | 1600 | 21.0000000000000000 | 100 -(6 rows) - --- Full aggregation as GROUP BY clause matches with PARTITION KEY -EXPLAIN (COSTS OFF) -SELECT a, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY a, (a+b)/2 HAVING sum(b) < 50 ORDER BY 1, 2, 3; - QUERY PLAN ----------------------------------------------------------------------------------- - Sort - Sort Key: pagg_tab_m.a, (sum(pagg_tab_m.b)), (avg(pagg_tab_m.c)) - -> Append - -> HashAggregate - Group Key: pagg_tab_m.a, ((pagg_tab_m.a + pagg_tab_m.b) / 2) - Filter: (sum(pagg_tab_m.b) < 50) - -> Seq Scan on pagg_tab_m_p1 pagg_tab_m - -> HashAggregate - Group Key: pagg_tab_m_1.a, ((pagg_tab_m_1.a + pagg_tab_m_1.b) / 2) - Filter: (sum(pagg_tab_m_1.b) < 50) - -> Seq Scan on pagg_tab_m_p2 pagg_tab_m_1 - -> HashAggregate - Group Key: pagg_tab_m_2.a, ((pagg_tab_m_2.a + pagg_tab_m_2.b) / 2) - Filter: (sum(pagg_tab_m_2.b) < 50) - -> Seq Scan on pagg_tab_m_p3 pagg_tab_m_2 -(15 rows) - -SELECT a, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY a, (a+b)/2 HAVING sum(b) < 50 ORDER BY 1, 2, 3; - a | sum | avg | count -----+-----+---------------------+------- - 0 | 0 | 20.0000000000000000 | 25 - 1 | 25 | 21.0000000000000000 | 25 - 10 | 0 | 20.0000000000000000 | 25 - 11 | 25 | 21.0000000000000000 | 25 - 20 | 0 | 20.0000000000000000 | 25 - 21 | 25 | 21.0000000000000000 | 25 -(6 rows) - --- Full aggregation as PARTITION KEY is part of GROUP BY clause -EXPLAIN (COSTS OFF) -SELECT a, c, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY (a+b)/2, 2, 1 HAVING sum(b) = 50 AND avg(c) > 25 ORDER BY 1, 2, 3; - QUERY PLAN --------------------------------------------------------------------------------------------------- - Sort - Sort Key: pagg_tab_m.a, pagg_tab_m.c, (sum(pagg_tab_m.b)) - -> Append - -> HashAggregate - Group Key: pagg_tab_m.a, pagg_tab_m.c, ((pagg_tab_m.a + pagg_tab_m.b) / 2) - Filter: ((sum(pagg_tab_m.b) = 50) AND (avg(pagg_tab_m.c) > '25'::numeric)) - -> Seq Scan on pagg_tab_m_p1 pagg_tab_m - -> HashAggregate - Group Key: pagg_tab_m_1.a, pagg_tab_m_1.c, ((pagg_tab_m_1.a + pagg_tab_m_1.b) / 2) - Filter: ((sum(pagg_tab_m_1.b) = 50) AND (avg(pagg_tab_m_1.c) > '25'::numeric)) - -> Seq Scan on pagg_tab_m_p2 pagg_tab_m_1 - -> HashAggregate - Group Key: pagg_tab_m_2.a, pagg_tab_m_2.c, ((pagg_tab_m_2.a + pagg_tab_m_2.b) / 2) - Filter: ((sum(pagg_tab_m_2.b) = 50) AND (avg(pagg_tab_m_2.c) > '25'::numeric)) - -> Seq Scan on pagg_tab_m_p3 pagg_tab_m_2 -(15 rows) - -SELECT a, c, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY (a+b)/2, 2, 1 HAVING sum(b) = 50 AND avg(c) > 25 ORDER BY 1, 2, 3; - a | c | sum | avg | count -----+----+-----+---------------------+------- - 0 | 30 | 50 | 30.0000000000000000 | 5 - 0 | 40 | 50 | 40.0000000000000000 | 5 - 10 | 30 | 50 | 30.0000000000000000 | 5 - 10 | 40 | 50 | 40.0000000000000000 | 5 - 20 | 30 | 50 | 30.0000000000000000 | 5 - 20 | 40 | 50 | 40.0000000000000000 | 5 -(6 rows) - --- Test with multi-level partitioning scheme -CREATE TABLE pagg_tab_ml (a int, b int, c text) PARTITION BY RANGE(a); -CREATE TABLE pagg_tab_ml_p1 PARTITION OF pagg_tab_ml FOR VALUES FROM (0) TO (12); -CREATE TABLE pagg_tab_ml_p2 PARTITION OF pagg_tab_ml FOR VALUES FROM (12) TO (20) PARTITION BY LIST (c); -CREATE TABLE pagg_tab_ml_p2_s1 PARTITION OF pagg_tab_ml_p2 FOR VALUES IN ('0000', '0001', '0002'); -CREATE TABLE pagg_tab_ml_p2_s2 PARTITION OF pagg_tab_ml_p2 FOR VALUES IN ('0003'); --- This level of partitioning has different column positions than the parent -CREATE TABLE pagg_tab_ml_p3(b int, c text, a int) PARTITION BY RANGE (b); -CREATE TABLE pagg_tab_ml_p3_s1(c text, a int, b int); -CREATE TABLE pagg_tab_ml_p3_s2 PARTITION OF pagg_tab_ml_p3 FOR VALUES FROM (7) TO (10); -ALTER TABLE pagg_tab_ml_p3 ATTACH PARTITION pagg_tab_ml_p3_s1 FOR VALUES FROM (0) TO (7); -ALTER TABLE pagg_tab_ml ATTACH PARTITION pagg_tab_ml_p3 FOR VALUES FROM (20) TO (30); -INSERT INTO pagg_tab_ml SELECT i % 30, i % 10, to_char(i % 4, 'FM0000') FROM generate_series(0, 29999) i; -ANALYZE pagg_tab_ml; --- For Parallel Append -SET max_parallel_workers_per_gather TO 2; -SET parallel_setup_cost = 0; --- Full aggregation at level 1 as GROUP BY clause matches with PARTITION KEY --- for level 1 only. For subpartitions, GROUP BY clause does not match with --- PARTITION KEY, but still we do not see a partial aggregation as array_agg() --- is not partial agg safe. -EXPLAIN (COSTS OFF) -SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3; - QUERY PLAN --------------------------------------------------------------------------------------- - Sort - Sort Key: pagg_tab_ml.a, (sum(pagg_tab_ml.b)), (array_agg(DISTINCT pagg_tab_ml.c)) - -> Gather - Workers Planned: 2 - -> Parallel Append - -> GroupAggregate - Group Key: pagg_tab_ml.a - Filter: (avg(pagg_tab_ml.b) < '3'::numeric) - -> Sort - Sort Key: pagg_tab_ml.a, pagg_tab_ml.c - -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml - -> GroupAggregate - Group Key: pagg_tab_ml_5.a - Filter: (avg(pagg_tab_ml_5.b) < '3'::numeric) - -> Sort - Sort Key: pagg_tab_ml_5.a, pagg_tab_ml_5.c - -> Append - -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_5 - -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_6 - -> GroupAggregate - Group Key: pagg_tab_ml_2.a - Filter: (avg(pagg_tab_ml_2.b) < '3'::numeric) - -> Sort - Sort Key: pagg_tab_ml_2.a, pagg_tab_ml_2.c - -> Append - -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_2 - -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_3 -(27 rows) - -SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3; - a | sum | array_agg | count -----+------+-------------+------- - 0 | 0 | {0000,0002} | 1000 - 1 | 1000 | {0001,0003} | 1000 - 2 | 2000 | {0000,0002} | 1000 - 10 | 0 | {0000,0002} | 1000 - 11 | 1000 | {0001,0003} | 1000 - 12 | 2000 | {0000,0002} | 1000 - 20 | 0 | {0000,0002} | 1000 - 21 | 1000 | {0001,0003} | 1000 - 22 | 2000 | {0000,0002} | 1000 -(9 rows) - --- Without ORDER BY clause, to test Gather at top-most path -EXPLAIN (COSTS OFF) -SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3; - QUERY PLAN ---------------------------------------------------------------------------- - Gather - Workers Planned: 2 - -> Parallel Append - -> GroupAggregate - Group Key: pagg_tab_ml.a - Filter: (avg(pagg_tab_ml.b) < '3'::numeric) - -> Sort - Sort Key: pagg_tab_ml.a, pagg_tab_ml.c - -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml - -> GroupAggregate - Group Key: pagg_tab_ml_5.a - Filter: (avg(pagg_tab_ml_5.b) < '3'::numeric) - -> Sort - Sort Key: pagg_tab_ml_5.a, pagg_tab_ml_5.c - -> Append - -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_5 - -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_6 - -> GroupAggregate - Group Key: pagg_tab_ml_2.a - Filter: (avg(pagg_tab_ml_2.b) < '3'::numeric) - -> Sort - Sort Key: pagg_tab_ml_2.a, pagg_tab_ml_2.c - -> Append - -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_2 - -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_3 -(25 rows) - -RESET parallel_setup_cost; --- Full aggregation at level 1 as GROUP BY clause matches with PARTITION KEY --- for level 1 only. For subpartitions, GROUP BY clause does not match with --- PARTITION KEY, thus we will have a partial aggregation for them. -EXPLAIN (COSTS OFF) -SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3; - QUERY PLAN ---------------------------------------------------------------------------------- - Sort - Sort Key: pagg_tab_ml.a, (sum(pagg_tab_ml.b)), (count(*)) - -> Append - -> HashAggregate - Group Key: pagg_tab_ml.a - Filter: (avg(pagg_tab_ml.b) < '3'::numeric) - -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml - -> Finalize GroupAggregate - Group Key: pagg_tab_ml_2.a - Filter: (avg(pagg_tab_ml_2.b) < '3'::numeric) - -> Sort - Sort Key: pagg_tab_ml_2.a - -> Append - -> Partial HashAggregate - Group Key: pagg_tab_ml_2.a - -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_2 - -> Partial HashAggregate - Group Key: pagg_tab_ml_3.a - -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_3 - -> Finalize GroupAggregate - Group Key: pagg_tab_ml_5.a - Filter: (avg(pagg_tab_ml_5.b) < '3'::numeric) - -> Sort - Sort Key: pagg_tab_ml_5.a - -> Append - -> Partial HashAggregate - Group Key: pagg_tab_ml_5.a - -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_5 - -> Partial HashAggregate - Group Key: pagg_tab_ml_6.a - -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_6 -(31 rows) - -SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3; - a | sum | count -----+------+------- - 0 | 0 | 1000 - 1 | 1000 | 1000 - 2 | 2000 | 1000 - 10 | 0 | 1000 - 11 | 1000 | 1000 - 12 | 2000 | 1000 - 20 | 0 | 1000 - 21 | 1000 | 1000 - 22 | 2000 | 1000 -(9 rows) - --- Partial aggregation at all levels as GROUP BY clause does not match with --- PARTITION KEY -EXPLAIN (COSTS OFF) -SELECT b, sum(a), count(*) FROM pagg_tab_ml GROUP BY b ORDER BY 1, 2, 3; - QUERY PLAN ---------------------------------------------------------------------------- - Sort - Sort Key: pagg_tab_ml.b, (sum(pagg_tab_ml.a)), (count(*)) - -> Finalize GroupAggregate - Group Key: pagg_tab_ml.b - -> Sort - Sort Key: pagg_tab_ml.b - -> Append - -> Partial HashAggregate - Group Key: pagg_tab_ml.b - -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml - -> Partial HashAggregate - Group Key: pagg_tab_ml_1.b - -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_1 - -> Partial HashAggregate - Group Key: pagg_tab_ml_2.b - -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_2 - -> Partial HashAggregate - Group Key: pagg_tab_ml_3.b - -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_3 - -> Partial HashAggregate - Group Key: pagg_tab_ml_4.b - -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_4 -(22 rows) - -SELECT b, sum(a), count(*) FROM pagg_tab_ml GROUP BY b HAVING avg(a) < 15 ORDER BY 1, 2, 3; - b | sum | count ----+-------+------- - 0 | 30000 | 3000 - 1 | 33000 | 3000 - 2 | 36000 | 3000 - 3 | 39000 | 3000 - 4 | 42000 | 3000 -(5 rows) - --- Full aggregation at all levels as GROUP BY clause matches with PARTITION KEY -EXPLAIN (COSTS OFF) -SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a, b, c HAVING avg(b) > 7 ORDER BY 1, 2, 3; - QUERY PLAN ----------------------------------------------------------------------------- - Sort - Sort Key: pagg_tab_ml.a, (sum(pagg_tab_ml.b)), (count(*)) - -> Append - -> HashAggregate - Group Key: pagg_tab_ml.a, pagg_tab_ml.b, pagg_tab_ml.c - Filter: (avg(pagg_tab_ml.b) > '7'::numeric) - -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml - -> HashAggregate - Group Key: pagg_tab_ml_1.a, pagg_tab_ml_1.b, pagg_tab_ml_1.c - Filter: (avg(pagg_tab_ml_1.b) > '7'::numeric) - -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_1 - -> HashAggregate - Group Key: pagg_tab_ml_2.a, pagg_tab_ml_2.b, pagg_tab_ml_2.c - Filter: (avg(pagg_tab_ml_2.b) > '7'::numeric) - -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_2 - -> HashAggregate - Group Key: pagg_tab_ml_3.a, pagg_tab_ml_3.b, pagg_tab_ml_3.c - Filter: (avg(pagg_tab_ml_3.b) > '7'::numeric) - -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_3 - -> HashAggregate - Group Key: pagg_tab_ml_4.a, pagg_tab_ml_4.b, pagg_tab_ml_4.c - Filter: (avg(pagg_tab_ml_4.b) > '7'::numeric) - -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_4 -(23 rows) - -SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a, b, c HAVING avg(b) > 7 ORDER BY 1, 2, 3; - a | sum | count -----+------+------- - 8 | 4000 | 500 - 8 | 4000 | 500 - 9 | 4500 | 500 - 9 | 4500 | 500 - 18 | 4000 | 500 - 18 | 4000 | 500 - 19 | 4500 | 500 - 19 | 4500 | 500 - 28 | 4000 | 500 - 28 | 4000 | 500 - 29 | 4500 | 500 - 29 | 4500 | 500 -(12 rows) - --- Parallelism within partitionwise aggregates -SET min_parallel_table_scan_size TO '8kB'; -SET parallel_setup_cost TO 0; --- Full aggregation at level 1 as GROUP BY clause matches with PARTITION KEY --- for level 1 only. For subpartitions, GROUP BY clause does not match with --- PARTITION KEY, thus we will have a partial aggregation for them. -EXPLAIN (COSTS OFF) -SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3; - QUERY PLAN ------------------------------------------------------------------------------------------------- - Sort - Sort Key: pagg_tab_ml.a, (sum(pagg_tab_ml.b)), (count(*)) - -> Append - -> Finalize GroupAggregate - Group Key: pagg_tab_ml.a - Filter: (avg(pagg_tab_ml.b) < '3'::numeric) - -> Gather Merge - Workers Planned: 2 - -> Sort - Sort Key: pagg_tab_ml.a - -> Partial HashAggregate - Group Key: pagg_tab_ml.a - -> Parallel Seq Scan on pagg_tab_ml_p1 pagg_tab_ml - -> Finalize GroupAggregate - Group Key: pagg_tab_ml_2.a - Filter: (avg(pagg_tab_ml_2.b) < '3'::numeric) - -> Gather Merge - Workers Planned: 2 - -> Sort - Sort Key: pagg_tab_ml_2.a - -> Parallel Append - -> Partial HashAggregate - Group Key: pagg_tab_ml_2.a - -> Parallel Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_2 - -> Partial HashAggregate - Group Key: pagg_tab_ml_3.a - -> Parallel Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_3 - -> Finalize GroupAggregate - Group Key: pagg_tab_ml_5.a - Filter: (avg(pagg_tab_ml_5.b) < '3'::numeric) - -> Gather Merge - Workers Planned: 2 - -> Sort - Sort Key: pagg_tab_ml_5.a - -> Parallel Append - -> Partial HashAggregate - Group Key: pagg_tab_ml_5.a - -> Parallel Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_5 - -> Partial HashAggregate - Group Key: pagg_tab_ml_6.a - -> Parallel Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_6 -(41 rows) - -SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3; - a | sum | count -----+------+------- - 0 | 0 | 1000 - 1 | 1000 | 1000 - 2 | 2000 | 1000 - 10 | 0 | 1000 - 11 | 1000 | 1000 - 12 | 2000 | 1000 - 20 | 0 | 1000 - 21 | 1000 | 1000 - 22 | 2000 | 1000 -(9 rows) - --- Partial aggregation at all levels as GROUP BY clause does not match with --- PARTITION KEY -EXPLAIN (COSTS OFF) -SELECT b, sum(a), count(*) FROM pagg_tab_ml GROUP BY b ORDER BY 1, 2, 3; - QUERY PLAN ------------------------------------------------------------------------------------------- - Sort - Sort Key: pagg_tab_ml.b, (sum(pagg_tab_ml.a)), (count(*)) - -> Finalize GroupAggregate - Group Key: pagg_tab_ml.b - -> Gather Merge - Workers Planned: 2 - -> Sort - Sort Key: pagg_tab_ml.b - -> Parallel Append - -> Partial HashAggregate - Group Key: pagg_tab_ml.b - -> Parallel Seq Scan on pagg_tab_ml_p1 pagg_tab_ml - -> Partial HashAggregate - Group Key: pagg_tab_ml_3.b - -> Parallel Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_3 - -> Partial HashAggregate - Group Key: pagg_tab_ml_1.b - -> Parallel Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_1 - -> Partial HashAggregate - Group Key: pagg_tab_ml_4.b - -> Parallel Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_4 - -> Partial HashAggregate - Group Key: pagg_tab_ml_2.b - -> Parallel Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_2 -(24 rows) - -SELECT b, sum(a), count(*) FROM pagg_tab_ml GROUP BY b HAVING avg(a) < 15 ORDER BY 1, 2, 3; - b | sum | count ----+-------+------- - 0 | 30000 | 3000 - 1 | 33000 | 3000 - 2 | 36000 | 3000 - 3 | 39000 | 3000 - 4 | 42000 | 3000 -(5 rows) - --- Full aggregation at all levels as GROUP BY clause matches with PARTITION KEY -EXPLAIN (COSTS OFF) -SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a, b, c HAVING avg(b) > 7 ORDER BY 1, 2, 3; - QUERY PLAN ----------------------------------------------------------------------------------- - Gather Merge - Workers Planned: 2 - -> Sort - Sort Key: pagg_tab_ml.a, (sum(pagg_tab_ml.b)), (count(*)) - -> Parallel Append - -> HashAggregate - Group Key: pagg_tab_ml.a, pagg_tab_ml.b, pagg_tab_ml.c - Filter: (avg(pagg_tab_ml.b) > '7'::numeric) - -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml - -> HashAggregate - Group Key: pagg_tab_ml_3.a, pagg_tab_ml_3.b, pagg_tab_ml_3.c - Filter: (avg(pagg_tab_ml_3.b) > '7'::numeric) - -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_3 - -> HashAggregate - Group Key: pagg_tab_ml_1.a, pagg_tab_ml_1.b, pagg_tab_ml_1.c - Filter: (avg(pagg_tab_ml_1.b) > '7'::numeric) - -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_1 - -> HashAggregate - Group Key: pagg_tab_ml_4.a, pagg_tab_ml_4.b, pagg_tab_ml_4.c - Filter: (avg(pagg_tab_ml_4.b) > '7'::numeric) - -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_4 - -> HashAggregate - Group Key: pagg_tab_ml_2.a, pagg_tab_ml_2.b, pagg_tab_ml_2.c - Filter: (avg(pagg_tab_ml_2.b) > '7'::numeric) - -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_2 -(25 rows) - -SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a, b, c HAVING avg(b) > 7 ORDER BY 1, 2, 3; - a | sum | count -----+------+------- - 8 | 4000 | 500 - 8 | 4000 | 500 - 9 | 4500 | 500 - 9 | 4500 | 500 - 18 | 4000 | 500 - 18 | 4000 | 500 - 19 | 4500 | 500 - 19 | 4500 | 500 - 28 | 4000 | 500 - 28 | 4000 | 500 - 29 | 4500 | 500 - 29 | 4500 | 500 -(12 rows) - --- Parallelism within partitionwise aggregates (single level) --- Add few parallel setup cost, so that we will see a plan which gathers --- partially created paths even for full aggregation and sticks a single Gather --- followed by finalization step. --- Without this, the cost of doing partial aggregation + Gather + finalization --- for each partition and then Append over it turns out to be same and this --- wins as we add it first. This parallel_setup_cost plays a vital role in --- costing such plans. -SET parallel_setup_cost TO 10; -CREATE TABLE pagg_tab_para(x int, y int) PARTITION BY RANGE(x); -CREATE TABLE pagg_tab_para_p1 PARTITION OF pagg_tab_para FOR VALUES FROM (0) TO (12); -CREATE TABLE pagg_tab_para_p2 PARTITION OF pagg_tab_para FOR VALUES FROM (12) TO (22); -CREATE TABLE pagg_tab_para_p3 PARTITION OF pagg_tab_para FOR VALUES FROM (22) TO (30); -INSERT INTO pagg_tab_para SELECT i % 30, i % 20 FROM generate_series(0, 29999) i; -ANALYZE pagg_tab_para; --- When GROUP BY clause matches; full aggregation is performed for each partition. -EXPLAIN (COSTS OFF) -SELECT x, sum(y), avg(y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; - QUERY PLAN -------------------------------------------------------------------------------------------- - Sort - Sort Key: pagg_tab_para.x, (sum(pagg_tab_para.y)), (avg(pagg_tab_para.y)) - -> Finalize GroupAggregate - Group Key: pagg_tab_para.x - Filter: (avg(pagg_tab_para.y) < '7'::numeric) - -> Gather Merge - Workers Planned: 2 - -> Sort - Sort Key: pagg_tab_para.x - -> Parallel Append - -> Partial HashAggregate - Group Key: pagg_tab_para.x - -> Parallel Seq Scan on pagg_tab_para_p1 pagg_tab_para - -> Partial HashAggregate - Group Key: pagg_tab_para_1.x - -> Parallel Seq Scan on pagg_tab_para_p2 pagg_tab_para_1 - -> Partial HashAggregate - Group Key: pagg_tab_para_2.x - -> Parallel Seq Scan on pagg_tab_para_p3 pagg_tab_para_2 -(19 rows) - -SELECT x, sum(y), avg(y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; - x | sum | avg | count -----+------+--------------------+------- - 0 | 5000 | 5.0000000000000000 | 1000 - 1 | 6000 | 6.0000000000000000 | 1000 - 10 | 5000 | 5.0000000000000000 | 1000 - 11 | 6000 | 6.0000000000000000 | 1000 - 20 | 5000 | 5.0000000000000000 | 1000 - 21 | 6000 | 6.0000000000000000 | 1000 -(6 rows) - --- When GROUP BY clause does not match; partial aggregation is performed for each partition. -EXPLAIN (COSTS OFF) -SELECT y, sum(x), avg(x), count(*) FROM pagg_tab_para GROUP BY y HAVING avg(x) < 12 ORDER BY 1, 2, 3; - QUERY PLAN -------------------------------------------------------------------------------------------- - Sort - Sort Key: pagg_tab_para.y, (sum(pagg_tab_para.x)), (avg(pagg_tab_para.x)) - -> Finalize GroupAggregate - Group Key: pagg_tab_para.y - Filter: (avg(pagg_tab_para.x) < '12'::numeric) - -> Gather Merge - Workers Planned: 2 - -> Sort - Sort Key: pagg_tab_para.y - -> Parallel Append - -> Partial HashAggregate - Group Key: pagg_tab_para.y - -> Parallel Seq Scan on pagg_tab_para_p1 pagg_tab_para - -> Partial HashAggregate - Group Key: pagg_tab_para_1.y - -> Parallel Seq Scan on pagg_tab_para_p2 pagg_tab_para_1 - -> Partial HashAggregate - Group Key: pagg_tab_para_2.y - -> Parallel Seq Scan on pagg_tab_para_p3 pagg_tab_para_2 -(19 rows) - -SELECT y, sum(x), avg(x), count(*) FROM pagg_tab_para GROUP BY y HAVING avg(x) < 12 ORDER BY 1, 2, 3; - y | sum | avg | count -----+-------+---------------------+------- - 0 | 15000 | 10.0000000000000000 | 1500 - 1 | 16500 | 11.0000000000000000 | 1500 - 10 | 15000 | 10.0000000000000000 | 1500 - 11 | 16500 | 11.0000000000000000 | 1500 -(4 rows) - --- Test when parent can produce parallel paths but not any (or some) of its children --- (Use one more aggregate to tilt the cost estimates for the plan we want) -ALTER TABLE pagg_tab_para_p1 SET (parallel_workers = 0); -ALTER TABLE pagg_tab_para_p3 SET (parallel_workers = 0); -ANALYZE pagg_tab_para; -EXPLAIN (COSTS OFF) -SELECT x, sum(y), avg(y), sum(x+y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; - QUERY PLAN -------------------------------------------------------------------------------------------- - Sort - Sort Key: pagg_tab_para.x, (sum(pagg_tab_para.y)), (avg(pagg_tab_para.y)) - -> Finalize GroupAggregate - Group Key: pagg_tab_para.x - Filter: (avg(pagg_tab_para.y) < '7'::numeric) - -> Gather Merge - Workers Planned: 2 - -> Sort - Sort Key: pagg_tab_para.x - -> Partial HashAggregate - Group Key: pagg_tab_para.x - -> Parallel Append - -> Seq Scan on pagg_tab_para_p1 pagg_tab_para_1 - -> Seq Scan on pagg_tab_para_p3 pagg_tab_para_3 - -> Parallel Seq Scan on pagg_tab_para_p2 pagg_tab_para_2 -(15 rows) - -SELECT x, sum(y), avg(y), sum(x+y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; - x | sum | avg | sum | count -----+------+--------------------+-------+------- - 0 | 5000 | 5.0000000000000000 | 5000 | 1000 - 1 | 6000 | 6.0000000000000000 | 7000 | 1000 - 10 | 5000 | 5.0000000000000000 | 15000 | 1000 - 11 | 6000 | 6.0000000000000000 | 17000 | 1000 - 20 | 5000 | 5.0000000000000000 | 25000 | 1000 - 21 | 6000 | 6.0000000000000000 | 27000 | 1000 -(6 rows) - -ALTER TABLE pagg_tab_para_p2 SET (parallel_workers = 0); -ANALYZE pagg_tab_para; -EXPLAIN (COSTS OFF) -SELECT x, sum(y), avg(y), sum(x+y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; - QUERY PLAN ----------------------------------------------------------------------------------- - Sort - Sort Key: pagg_tab_para.x, (sum(pagg_tab_para.y)), (avg(pagg_tab_para.y)) - -> Finalize GroupAggregate - Group Key: pagg_tab_para.x - Filter: (avg(pagg_tab_para.y) < '7'::numeric) - -> Gather Merge - Workers Planned: 2 - -> Sort - Sort Key: pagg_tab_para.x - -> Partial HashAggregate - Group Key: pagg_tab_para.x - -> Parallel Append - -> Seq Scan on pagg_tab_para_p1 pagg_tab_para_1 - -> Seq Scan on pagg_tab_para_p2 pagg_tab_para_2 - -> Seq Scan on pagg_tab_para_p3 pagg_tab_para_3 -(15 rows) - -SELECT x, sum(y), avg(y), sum(x+y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; - x | sum | avg | sum | count -----+------+--------------------+-------+------- - 0 | 5000 | 5.0000000000000000 | 5000 | 1000 - 1 | 6000 | 6.0000000000000000 | 7000 | 1000 - 10 | 5000 | 5.0000000000000000 | 15000 | 1000 - 11 | 6000 | 6.0000000000000000 | 17000 | 1000 - 20 | 5000 | 5.0000000000000000 | 25000 | 1000 - 21 | 6000 | 6.0000000000000000 | 27000 | 1000 -(6 rows) - --- Reset parallelism parameters to get partitionwise aggregation plan. -RESET min_parallel_table_scan_size; -RESET parallel_setup_cost; -EXPLAIN (COSTS OFF) -SELECT x, sum(y), avg(y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; - QUERY PLAN ------------------------------------------------------------------------------ - Sort - Sort Key: pagg_tab_para.x, (sum(pagg_tab_para.y)), (avg(pagg_tab_para.y)) - -> Append - -> HashAggregate - Group Key: pagg_tab_para.x - Filter: (avg(pagg_tab_para.y) < '7'::numeric) - -> Seq Scan on pagg_tab_para_p1 pagg_tab_para - -> HashAggregate - Group Key: pagg_tab_para_1.x - Filter: (avg(pagg_tab_para_1.y) < '7'::numeric) - -> Seq Scan on pagg_tab_para_p2 pagg_tab_para_1 - -> HashAggregate - Group Key: pagg_tab_para_2.x - Filter: (avg(pagg_tab_para_2.y) < '7'::numeric) - -> Seq Scan on pagg_tab_para_p3 pagg_tab_para_2 -(15 rows) - -SELECT x, sum(y), avg(y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; - x | sum | avg | count -----+------+--------------------+------- - 0 | 5000 | 5.0000000000000000 | 1000 - 1 | 6000 | 6.0000000000000000 | 1000 - 10 | 5000 | 5.0000000000000000 | 1000 - 11 | 6000 | 6.0000000000000000 | 1000 - 20 | 5000 | 5.0000000000000000 | 1000 - 21 | 6000 | 6.0000000000000000 | 1000 -(6 rows) - +psql: error: connection to server on socket "/tmp/bBabKAIw6z/.s.PGSQL.19533" failed: No such file or directory + Is the server running locally and accepting connections on that socket? diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/partition_info.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/partition_info.out --- /tmp/cirrus-ci-build/src/test/regress/expected/partition_info.out 2024-09-22 06:28:18.733166297 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/partition_info.out 2024-09-22 06:38:09.880217562 +0000 @@ -1,351 +1,2 @@ --- --- Tests for functions providing information about partitions --- -SELECT * FROM pg_partition_tree(NULL); - relid | parentrelid | isleaf | level --------+-------------+--------+------- -(0 rows) - -SELECT * FROM pg_partition_tree(0); - relid | parentrelid | isleaf | level --------+-------------+--------+------- -(0 rows) - -SELECT * FROM pg_partition_ancestors(NULL); - relid -------- -(0 rows) - -SELECT * FROM pg_partition_ancestors(0); - relid -------- -(0 rows) - -SELECT pg_partition_root(NULL); - pg_partition_root -------------------- - -(1 row) - -SELECT pg_partition_root(0); - pg_partition_root -------------------- - -(1 row) - --- Test table partition trees -CREATE TABLE ptif_test (a int, b int) PARTITION BY range (a); -CREATE TABLE ptif_test0 PARTITION OF ptif_test - FOR VALUES FROM (minvalue) TO (0) PARTITION BY list (b); -CREATE TABLE ptif_test01 PARTITION OF ptif_test0 FOR VALUES IN (1); -CREATE TABLE ptif_test1 PARTITION OF ptif_test - FOR VALUES FROM (0) TO (100) PARTITION BY list (b); -CREATE TABLE ptif_test11 PARTITION OF ptif_test1 FOR VALUES IN (1); -CREATE TABLE ptif_test2 PARTITION OF ptif_test - FOR VALUES FROM (100) TO (200); --- This partitioned table should remain with no partitions. -CREATE TABLE ptif_test3 PARTITION OF ptif_test - FOR VALUES FROM (200) TO (maxvalue) PARTITION BY list (b); --- Test pg_partition_root for tables -SELECT pg_partition_root('ptif_test'); - pg_partition_root -------------------- - ptif_test -(1 row) - -SELECT pg_partition_root('ptif_test0'); - pg_partition_root -------------------- - ptif_test -(1 row) - -SELECT pg_partition_root('ptif_test01'); - pg_partition_root -------------------- - ptif_test -(1 row) - -SELECT pg_partition_root('ptif_test3'); - pg_partition_root -------------------- - ptif_test -(1 row) - --- Test index partition tree -CREATE INDEX ptif_test_index ON ONLY ptif_test (a); -CREATE INDEX ptif_test0_index ON ONLY ptif_test0 (a); -ALTER INDEX ptif_test_index ATTACH PARTITION ptif_test0_index; -CREATE INDEX ptif_test01_index ON ptif_test01 (a); -ALTER INDEX ptif_test0_index ATTACH PARTITION ptif_test01_index; -CREATE INDEX ptif_test1_index ON ONLY ptif_test1 (a); -ALTER INDEX ptif_test_index ATTACH PARTITION ptif_test1_index; -CREATE INDEX ptif_test11_index ON ptif_test11 (a); -ALTER INDEX ptif_test1_index ATTACH PARTITION ptif_test11_index; -CREATE INDEX ptif_test2_index ON ptif_test2 (a); -ALTER INDEX ptif_test_index ATTACH PARTITION ptif_test2_index; -CREATE INDEX ptif_test3_index ON ptif_test3 (a); -ALTER INDEX ptif_test_index ATTACH PARTITION ptif_test3_index; --- Test pg_partition_root for indexes -SELECT pg_partition_root('ptif_test_index'); - pg_partition_root -------------------- - ptif_test_index -(1 row) - -SELECT pg_partition_root('ptif_test0_index'); - pg_partition_root -------------------- - ptif_test_index -(1 row) - -SELECT pg_partition_root('ptif_test01_index'); - pg_partition_root -------------------- - ptif_test_index -(1 row) - -SELECT pg_partition_root('ptif_test3_index'); - pg_partition_root -------------------- - ptif_test_index -(1 row) - --- List all tables members of the tree -SELECT relid, parentrelid, level, isleaf - FROM pg_partition_tree('ptif_test'); - relid | parentrelid | level | isleaf --------------+-------------+-------+-------- - ptif_test | | 0 | f - ptif_test0 | ptif_test | 1 | f - ptif_test1 | ptif_test | 1 | f - ptif_test2 | ptif_test | 1 | t - ptif_test3 | ptif_test | 1 | f - ptif_test01 | ptif_test0 | 2 | t - ptif_test11 | ptif_test1 | 2 | t -(7 rows) - --- List tables from an intermediate level -SELECT relid, parentrelid, level, isleaf - FROM pg_partition_tree('ptif_test0') p - JOIN pg_class c ON (p.relid = c.oid); - relid | parentrelid | level | isleaf --------------+-------------+-------+-------- - ptif_test0 | ptif_test | 0 | f - ptif_test01 | ptif_test0 | 1 | t -(2 rows) - --- List from leaf table -SELECT relid, parentrelid, level, isleaf - FROM pg_partition_tree('ptif_test01') p - JOIN pg_class c ON (p.relid = c.oid); - relid | parentrelid | level | isleaf --------------+-------------+-------+-------- - ptif_test01 | ptif_test0 | 0 | t -(1 row) - --- List from partitioned table with no partitions -SELECT relid, parentrelid, level, isleaf - FROM pg_partition_tree('ptif_test3') p - JOIN pg_class c ON (p.relid = c.oid); - relid | parentrelid | level | isleaf -------------+-------------+-------+-------- - ptif_test3 | ptif_test | 0 | f -(1 row) - --- List all ancestors of root and leaf tables -SELECT * FROM pg_partition_ancestors('ptif_test01'); - relid -------------- - ptif_test01 - ptif_test0 - ptif_test -(3 rows) - -SELECT * FROM pg_partition_ancestors('ptif_test'); - relid ------------ - ptif_test -(1 row) - --- List all members using pg_partition_root with leaf table reference -SELECT relid, parentrelid, level, isleaf - FROM pg_partition_tree(pg_partition_root('ptif_test01')) p - JOIN pg_class c ON (p.relid = c.oid); - relid | parentrelid | level | isleaf --------------+-------------+-------+-------- - ptif_test | | 0 | f - ptif_test0 | ptif_test | 1 | f - ptif_test1 | ptif_test | 1 | f - ptif_test2 | ptif_test | 1 | t - ptif_test3 | ptif_test | 1 | f - ptif_test01 | ptif_test0 | 2 | t - ptif_test11 | ptif_test1 | 2 | t -(7 rows) - --- List all indexes members of the tree -SELECT relid, parentrelid, level, isleaf - FROM pg_partition_tree('ptif_test_index'); - relid | parentrelid | level | isleaf --------------------+------------------+-------+-------- - ptif_test_index | | 0 | f - ptif_test0_index | ptif_test_index | 1 | f - ptif_test1_index | ptif_test_index | 1 | f - ptif_test2_index | ptif_test_index | 1 | t - ptif_test3_index | ptif_test_index | 1 | f - ptif_test01_index | ptif_test0_index | 2 | t - ptif_test11_index | ptif_test1_index | 2 | t -(7 rows) - --- List indexes from an intermediate level -SELECT relid, parentrelid, level, isleaf - FROM pg_partition_tree('ptif_test0_index') p - JOIN pg_class c ON (p.relid = c.oid); - relid | parentrelid | level | isleaf --------------------+------------------+-------+-------- - ptif_test0_index | ptif_test_index | 0 | f - ptif_test01_index | ptif_test0_index | 1 | t -(2 rows) - --- List from leaf index -SELECT relid, parentrelid, level, isleaf - FROM pg_partition_tree('ptif_test01_index') p - JOIN pg_class c ON (p.relid = c.oid); - relid | parentrelid | level | isleaf --------------------+------------------+-------+-------- - ptif_test01_index | ptif_test0_index | 0 | t -(1 row) - --- List from partitioned index with no partitions -SELECT relid, parentrelid, level, isleaf - FROM pg_partition_tree('ptif_test3_index') p - JOIN pg_class c ON (p.relid = c.oid); - relid | parentrelid | level | isleaf -------------------+-----------------+-------+-------- - ptif_test3_index | ptif_test_index | 0 | f -(1 row) - --- List all members using pg_partition_root with leaf index reference -SELECT relid, parentrelid, level, isleaf - FROM pg_partition_tree(pg_partition_root('ptif_test01_index')) p - JOIN pg_class c ON (p.relid = c.oid); - relid | parentrelid | level | isleaf --------------------+------------------+-------+-------- - ptif_test_index | | 0 | f - ptif_test0_index | ptif_test_index | 1 | f - ptif_test1_index | ptif_test_index | 1 | f - ptif_test2_index | ptif_test_index | 1 | t - ptif_test3_index | ptif_test_index | 1 | f - ptif_test01_index | ptif_test0_index | 2 | t - ptif_test11_index | ptif_test1_index | 2 | t -(7 rows) - --- List all ancestors of root and leaf indexes -SELECT * FROM pg_partition_ancestors('ptif_test01_index'); - relid -------------------- - ptif_test01_index - ptif_test0_index - ptif_test_index -(3 rows) - -SELECT * FROM pg_partition_ancestors('ptif_test_index'); - relid ------------------ - ptif_test_index -(1 row) - -DROP TABLE ptif_test; --- Table that is not part of any partition tree is not listed. -CREATE TABLE ptif_normal_table(a int); -SELECT relid, parentrelid, level, isleaf - FROM pg_partition_tree('ptif_normal_table'); - relid | parentrelid | level | isleaf --------+-------------+-------+-------- -(0 rows) - -SELECT * FROM pg_partition_ancestors('ptif_normal_table'); - relid -------- -(0 rows) - -SELECT pg_partition_root('ptif_normal_table'); - pg_partition_root -------------------- - -(1 row) - -DROP TABLE ptif_normal_table; --- Various partitioning-related functions return empty/NULL if passed relations --- of types that cannot be part of a partition tree; for example, views, --- materialized views, legacy inheritance children or parents, etc. -CREATE VIEW ptif_test_view AS SELECT 1; -CREATE MATERIALIZED VIEW ptif_test_matview AS SELECT 1; -CREATE TABLE ptif_li_parent (); -CREATE TABLE ptif_li_child () INHERITS (ptif_li_parent); -SELECT * FROM pg_partition_tree('ptif_test_view'); - relid | parentrelid | isleaf | level --------+-------------+--------+------- -(0 rows) - -SELECT * FROM pg_partition_tree('ptif_test_matview'); - relid | parentrelid | isleaf | level --------+-------------+--------+------- -(0 rows) - -SELECT * FROM pg_partition_tree('ptif_li_parent'); - relid | parentrelid | isleaf | level --------+-------------+--------+------- -(0 rows) - -SELECT * FROM pg_partition_tree('ptif_li_child'); - relid | parentrelid | isleaf | level --------+-------------+--------+------- -(0 rows) - -SELECT * FROM pg_partition_ancestors('ptif_test_view'); - relid -------- -(0 rows) - -SELECT * FROM pg_partition_ancestors('ptif_test_matview'); - relid -------- -(0 rows) - -SELECT * FROM pg_partition_ancestors('ptif_li_parent'); - relid -------- -(0 rows) - -SELECT * FROM pg_partition_ancestors('ptif_li_child'); - relid -------- -(0 rows) - -SELECT pg_partition_root('ptif_test_view'); - pg_partition_root -------------------- - -(1 row) - -SELECT pg_partition_root('ptif_test_matview'); - pg_partition_root -------------------- - -(1 row) - -SELECT pg_partition_root('ptif_li_parent'); - pg_partition_root -------------------- - -(1 row) - -SELECT pg_partition_root('ptif_li_child'); - pg_partition_root -------------------- - -(1 row) - -DROP VIEW ptif_test_view; -DROP MATERIALIZED VIEW ptif_test_matview; -DROP TABLE ptif_li_parent, ptif_li_child; +psql: error: connection to server on socket "/tmp/bBabKAIw6z/.s.PGSQL.19533" failed: No such file or directory + Is the server running locally and accepting connections on that socket? diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/tuplesort.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/tuplesort.out --- /tmp/cirrus-ci-build/src/test/regress/expected/tuplesort.out 2024-09-22 06:28:18.773374698 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/tuplesort.out 2024-09-22 06:38:09.880217562 +0000 @@ -1,705 +1,2 @@ --- only use parallelism when explicitly intending to do so -SET max_parallel_maintenance_workers = 0; -SET max_parallel_workers = 0; --- A table with contents that, when sorted, triggers abbreviated --- key aborts. One easy way to achieve that is to use uuids that all --- have the same prefix, as abbreviated keys for uuids just use the --- first sizeof(Datum) bytes. -CREATE TEMP TABLE abbrev_abort_uuids ( - id serial not null, - abort_increasing uuid, - abort_decreasing uuid, - noabort_increasing uuid, - noabort_decreasing uuid); -INSERT INTO abbrev_abort_uuids (abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing) - SELECT - ('00000000-0000-0000-0000-'||to_char(g.i, '000000000000FM'))::uuid abort_increasing, - ('00000000-0000-0000-0000-'||to_char(20000 - g.i, '000000000000FM'))::uuid abort_decreasing, - (to_char(g.i % 10009, '00000000FM')||'-0000-0000-0000-'||to_char(g.i, '000000000000FM'))::uuid noabort_increasing, - (to_char(((20000 - g.i) % 10009), '00000000FM')||'-0000-0000-0000-'||to_char(20000 - g.i, '000000000000FM'))::uuid noabort_decreasing - FROM generate_series(0, 20000, 1) g(i); --- and a few NULLs -INSERT INTO abbrev_abort_uuids(id) VALUES(0); -INSERT INTO abbrev_abort_uuids DEFAULT VALUES; -INSERT INTO abbrev_abort_uuids DEFAULT VALUES; --- add just a few duplicates -INSERT INTO abbrev_abort_uuids (abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing) - SELECT abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing - FROM abbrev_abort_uuids - WHERE (id < 10 OR id > 19990) AND id % 3 = 0 AND abort_increasing is not null; ----- --- Check sort node uses of tuplesort wrt. abbreviated keys ----- --- plain sort triggering abbreviated abort -SELECT abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_increasing OFFSET 20000 - 4; - abort_increasing | abort_decreasing ---------------------------------------+-------------------------------------- - 00000000-0000-0000-0000-000000019992 | 00000000-0000-0000-0000-000000000008 - 00000000-0000-0000-0000-000000019993 | 00000000-0000-0000-0000-000000000007 - 00000000-0000-0000-0000-000000019994 | 00000000-0000-0000-0000-000000000006 - 00000000-0000-0000-0000-000000019994 | 00000000-0000-0000-0000-000000000006 - 00000000-0000-0000-0000-000000019995 | 00000000-0000-0000-0000-000000000005 - 00000000-0000-0000-0000-000000019996 | 00000000-0000-0000-0000-000000000004 - 00000000-0000-0000-0000-000000019997 | 00000000-0000-0000-0000-000000000003 - 00000000-0000-0000-0000-000000019997 | 00000000-0000-0000-0000-000000000003 - 00000000-0000-0000-0000-000000019998 | 00000000-0000-0000-0000-000000000002 - 00000000-0000-0000-0000-000000019999 | 00000000-0000-0000-0000-000000000001 - 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 - 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 - | - | - | -(15 rows) - -SELECT abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_decreasing NULLS FIRST OFFSET 20000 - 4; - abort_increasing | abort_decreasing ---------------------------------------+-------------------------------------- - 00000000-0000-0000-0000-000000000011 | 00000000-0000-0000-0000-000000019989 - 00000000-0000-0000-0000-000000000010 | 00000000-0000-0000-0000-000000019990 - 00000000-0000-0000-0000-000000000009 | 00000000-0000-0000-0000-000000019991 - 00000000-0000-0000-0000-000000000008 | 00000000-0000-0000-0000-000000019992 - 00000000-0000-0000-0000-000000000008 | 00000000-0000-0000-0000-000000019992 - 00000000-0000-0000-0000-000000000007 | 00000000-0000-0000-0000-000000019993 - 00000000-0000-0000-0000-000000000006 | 00000000-0000-0000-0000-000000019994 - 00000000-0000-0000-0000-000000000005 | 00000000-0000-0000-0000-000000019995 - 00000000-0000-0000-0000-000000000005 | 00000000-0000-0000-0000-000000019995 - 00000000-0000-0000-0000-000000000004 | 00000000-0000-0000-0000-000000019996 - 00000000-0000-0000-0000-000000000003 | 00000000-0000-0000-0000-000000019997 - 00000000-0000-0000-0000-000000000002 | 00000000-0000-0000-0000-000000019998 - 00000000-0000-0000-0000-000000000002 | 00000000-0000-0000-0000-000000019998 - 00000000-0000-0000-0000-000000000001 | 00000000-0000-0000-0000-000000019999 - 00000000-0000-0000-0000-000000000000 | 00000000-0000-0000-0000-000000020000 -(15 rows) - --- plain sort not triggering abbreviated abort -SELECT noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_increasing OFFSET 20000 - 4; - noabort_increasing | noabort_decreasing ---------------------------------------+-------------------------------------- - 00009997-0000-0000-0000-000000009997 | 00010003-0000-0000-0000-000000010003 - 00009998-0000-0000-0000-000000009998 | 00010002-0000-0000-0000-000000010002 - 00009999-0000-0000-0000-000000009999 | 00010001-0000-0000-0000-000000010001 - 00010000-0000-0000-0000-000000010000 | 00010000-0000-0000-0000-000000010000 - 00010001-0000-0000-0000-000000010001 | 00009999-0000-0000-0000-000000009999 - 00010002-0000-0000-0000-000000010002 | 00009998-0000-0000-0000-000000009998 - 00010003-0000-0000-0000-000000010003 | 00009997-0000-0000-0000-000000009997 - 00010004-0000-0000-0000-000000010004 | 00009996-0000-0000-0000-000000009996 - 00010005-0000-0000-0000-000000010005 | 00009995-0000-0000-0000-000000009995 - 00010006-0000-0000-0000-000000010006 | 00009994-0000-0000-0000-000000009994 - 00010007-0000-0000-0000-000000010007 | 00009993-0000-0000-0000-000000009993 - 00010008-0000-0000-0000-000000010008 | 00009992-0000-0000-0000-000000009992 - | - | - | -(15 rows) - -SELECT noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing NULLS FIRST OFFSET 20000 - 4; - noabort_increasing | noabort_decreasing ---------------------------------------+-------------------------------------- - 00010006-0000-0000-0000-000000010006 | 00009994-0000-0000-0000-000000009994 - 00010005-0000-0000-0000-000000010005 | 00009995-0000-0000-0000-000000009995 - 00010004-0000-0000-0000-000000010004 | 00009996-0000-0000-0000-000000009996 - 00010003-0000-0000-0000-000000010003 | 00009997-0000-0000-0000-000000009997 - 00010002-0000-0000-0000-000000010002 | 00009998-0000-0000-0000-000000009998 - 00010001-0000-0000-0000-000000010001 | 00009999-0000-0000-0000-000000009999 - 00010000-0000-0000-0000-000000010000 | 00010000-0000-0000-0000-000000010000 - 00009999-0000-0000-0000-000000009999 | 00010001-0000-0000-0000-000000010001 - 00009998-0000-0000-0000-000000009998 | 00010002-0000-0000-0000-000000010002 - 00009997-0000-0000-0000-000000009997 | 00010003-0000-0000-0000-000000010003 - 00009996-0000-0000-0000-000000009996 | 00010004-0000-0000-0000-000000010004 - 00009995-0000-0000-0000-000000009995 | 00010005-0000-0000-0000-000000010005 - 00009994-0000-0000-0000-000000009994 | 00010006-0000-0000-0000-000000010006 - 00009993-0000-0000-0000-000000009993 | 00010007-0000-0000-0000-000000010007 - 00009992-0000-0000-0000-000000009992 | 00010008-0000-0000-0000-000000010008 -(15 rows) - --- bounded sort (disables abbreviated keys) -SELECT abort_increasing, noabort_increasing FROM abbrev_abort_uuids ORDER BY abort_increasing LIMIT 5; - abort_increasing | noabort_increasing ---------------------------------------+-------------------------------------- - 00000000-0000-0000-0000-000000000000 | 00000000-0000-0000-0000-000000000000 - 00000000-0000-0000-0000-000000000001 | 00000001-0000-0000-0000-000000000001 - 00000000-0000-0000-0000-000000000002 | 00000002-0000-0000-0000-000000000002 - 00000000-0000-0000-0000-000000000002 | 00000002-0000-0000-0000-000000000002 - 00000000-0000-0000-0000-000000000003 | 00000003-0000-0000-0000-000000000003 -(5 rows) - -SELECT abort_increasing, noabort_increasing FROM abbrev_abort_uuids ORDER BY noabort_increasing NULLS FIRST LIMIT 5; - abort_increasing | noabort_increasing ---------------------------------------+-------------------------------------- - | - | - | - 00000000-0000-0000-0000-000000000000 | 00000000-0000-0000-0000-000000000000 - 00000000-0000-0000-0000-000000010009 | 00000000-0000-0000-0000-000000010009 -(5 rows) - ----- --- Check index creation uses of tuplesort wrt. abbreviated keys ----- --- index creation using abbreviated keys successfully -CREATE INDEX abbrev_abort_uuids__noabort_increasing_idx ON abbrev_abort_uuids (noabort_increasing); -CREATE INDEX abbrev_abort_uuids__noabort_decreasing_idx ON abbrev_abort_uuids (noabort_decreasing); --- verify -EXPLAIN (COSTS OFF) -SELECT id, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_increasing LIMIT 5; - QUERY PLAN ------------------------------------------------------------------------------------------ - Limit - -> Index Scan using abbrev_abort_uuids__noabort_increasing_idx on abbrev_abort_uuids -(2 rows) - -SELECT id, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_increasing LIMIT 5; - id | noabort_increasing | noabort_decreasing --------+--------------------------------------+-------------------------------------- - 1 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 - 10010 | 00000000-0000-0000-0000-000000010009 | 00009991-0000-0000-0000-000000009991 - 2 | 00000001-0000-0000-0000-000000000001 | 00009990-0000-0000-0000-000000019999 - 10011 | 00000001-0000-0000-0000-000000010010 | 00009990-0000-0000-0000-000000009990 - 3 | 00000002-0000-0000-0000-000000000002 | 00009989-0000-0000-0000-000000019998 -(5 rows) - -EXPLAIN (COSTS OFF) -SELECT id, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing LIMIT 5; - QUERY PLAN ------------------------------------------------------------------------------------------ - Limit - -> Index Scan using abbrev_abort_uuids__noabort_decreasing_idx on abbrev_abort_uuids -(2 rows) - -SELECT id, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing LIMIT 5; - id | noabort_increasing | noabort_decreasing --------+--------------------------------------+-------------------------------------- - 20001 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 - 20010 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 - 9992 | 00009991-0000-0000-0000-000000009991 | 00000000-0000-0000-0000-000000010009 - 20000 | 00009990-0000-0000-0000-000000019999 | 00000001-0000-0000-0000-000000000001 - 9991 | 00009990-0000-0000-0000-000000009990 | 00000001-0000-0000-0000-000000010010 -(5 rows) - --- index creation using abbreviated keys, hitting abort -CREATE INDEX abbrev_abort_uuids__abort_increasing_idx ON abbrev_abort_uuids (abort_increasing); -CREATE INDEX abbrev_abort_uuids__abort_decreasing_idx ON abbrev_abort_uuids (abort_decreasing); --- verify -EXPLAIN (COSTS OFF) -SELECT id, abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_increasing LIMIT 5; - QUERY PLAN ---------------------------------------------------------------------------------------- - Limit - -> Index Scan using abbrev_abort_uuids__abort_increasing_idx on abbrev_abort_uuids -(2 rows) - -SELECT id, abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_increasing LIMIT 5; - id | abort_increasing | abort_decreasing --------+--------------------------------------+-------------------------------------- - 1 | 00000000-0000-0000-0000-000000000000 | 00000000-0000-0000-0000-000000020000 - 2 | 00000000-0000-0000-0000-000000000001 | 00000000-0000-0000-0000-000000019999 - 3 | 00000000-0000-0000-0000-000000000002 | 00000000-0000-0000-0000-000000019998 - 20004 | 00000000-0000-0000-0000-000000000002 | 00000000-0000-0000-0000-000000019998 - 4 | 00000000-0000-0000-0000-000000000003 | 00000000-0000-0000-0000-000000019997 -(5 rows) - -EXPLAIN (COSTS OFF) -SELECT id, abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_decreasing LIMIT 5; - QUERY PLAN ---------------------------------------------------------------------------------------- - Limit - -> Index Scan using abbrev_abort_uuids__abort_decreasing_idx on abbrev_abort_uuids -(2 rows) - -SELECT id, abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_decreasing LIMIT 5; - id | abort_increasing | abort_decreasing --------+--------------------------------------+-------------------------------------- - 20001 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 - 20010 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 - 20000 | 00000000-0000-0000-0000-000000019999 | 00000000-0000-0000-0000-000000000001 - 19999 | 00000000-0000-0000-0000-000000019998 | 00000000-0000-0000-0000-000000000002 - 19998 | 00000000-0000-0000-0000-000000019997 | 00000000-0000-0000-0000-000000000003 -(5 rows) - ----- --- Check CLUSTER uses of tuplesort wrt. abbreviated keys ----- --- when aborting, increasing order -BEGIN; -SET LOCAL enable_indexscan = false; -CLUSTER abbrev_abort_uuids USING abbrev_abort_uuids__abort_increasing_idx; --- head -SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing -FROM abbrev_abort_uuids -ORDER BY ctid LIMIT 5; - id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing --------+--------------------------------------+--------------------------------------+--------------------------------------+-------------------------------------- - 1 | 00000000-0000-0000-0000-000000000000 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 - 2 | 00000000-0000-0000-0000-000000000001 | 00000000-0000-0000-0000-000000019999 | 00000001-0000-0000-0000-000000000001 | 00009990-0000-0000-0000-000000019999 - 3 | 00000000-0000-0000-0000-000000000002 | 00000000-0000-0000-0000-000000019998 | 00000002-0000-0000-0000-000000000002 | 00009989-0000-0000-0000-000000019998 - 20004 | 00000000-0000-0000-0000-000000000002 | 00000000-0000-0000-0000-000000019998 | 00000002-0000-0000-0000-000000000002 | 00009989-0000-0000-0000-000000019998 - 4 | 00000000-0000-0000-0000-000000000003 | 00000000-0000-0000-0000-000000019997 | 00000003-0000-0000-0000-000000000003 | 00009988-0000-0000-0000-000000019997 -(5 rows) - --- tail -SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing -FROM abbrev_abort_uuids -ORDER BY ctid DESC LIMIT 5; - id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing --------+--------------------------------------+--------------------------------------+--------------------------------------+-------------------------------------- - 0 | | | | - 20002 | | | | - 20003 | | | | - 20001 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 - 20010 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 -(5 rows) - -ROLLBACK; --- when aborting, decreasing order -BEGIN; -SET LOCAL enable_indexscan = false; -CLUSTER abbrev_abort_uuids USING abbrev_abort_uuids__abort_decreasing_idx; --- head -SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing -FROM abbrev_abort_uuids -ORDER BY ctid LIMIT 5; - id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing --------+--------------------------------------+--------------------------------------+--------------------------------------+-------------------------------------- - 20010 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 - 20001 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 - 20000 | 00000000-0000-0000-0000-000000019999 | 00000000-0000-0000-0000-000000000001 | 00009990-0000-0000-0000-000000019999 | 00000001-0000-0000-0000-000000000001 - 19999 | 00000000-0000-0000-0000-000000019998 | 00000000-0000-0000-0000-000000000002 | 00009989-0000-0000-0000-000000019998 | 00000002-0000-0000-0000-000000000002 - 20009 | 00000000-0000-0000-0000-000000019997 | 00000000-0000-0000-0000-000000000003 | 00009988-0000-0000-0000-000000019997 | 00000003-0000-0000-0000-000000000003 -(5 rows) - --- tail -SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing -FROM abbrev_abort_uuids -ORDER BY ctid DESC LIMIT 5; - id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing --------+--------------------------------------+--------------------------------------+--------------------------------------+-------------------------------------- - 0 | | | | - 20002 | | | | - 20003 | | | | - 1 | 00000000-0000-0000-0000-000000000000 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 - 2 | 00000000-0000-0000-0000-000000000001 | 00000000-0000-0000-0000-000000019999 | 00000001-0000-0000-0000-000000000001 | 00009990-0000-0000-0000-000000019999 -(5 rows) - -ROLLBACK; --- when not aborting, increasing order -BEGIN; -SET LOCAL enable_indexscan = false; -CLUSTER abbrev_abort_uuids USING abbrev_abort_uuids__noabort_increasing_idx; --- head -SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing -FROM abbrev_abort_uuids -ORDER BY ctid LIMIT 5; - id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing --------+--------------------------------------+--------------------------------------+--------------------------------------+-------------------------------------- - 1 | 00000000-0000-0000-0000-000000000000 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 - 10010 | 00000000-0000-0000-0000-000000010009 | 00000000-0000-0000-0000-000000009991 | 00000000-0000-0000-0000-000000010009 | 00009991-0000-0000-0000-000000009991 - 2 | 00000000-0000-0000-0000-000000000001 | 00000000-0000-0000-0000-000000019999 | 00000001-0000-0000-0000-000000000001 | 00009990-0000-0000-0000-000000019999 - 10011 | 00000000-0000-0000-0000-000000010010 | 00000000-0000-0000-0000-000000009990 | 00000001-0000-0000-0000-000000010010 | 00009990-0000-0000-0000-000000009990 - 20004 | 00000000-0000-0000-0000-000000000002 | 00000000-0000-0000-0000-000000019998 | 00000002-0000-0000-0000-000000000002 | 00009989-0000-0000-0000-000000019998 -(5 rows) - --- tail -SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing -FROM abbrev_abort_uuids -ORDER BY ctid DESC LIMIT 5; - id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing --------+--------------------------------------+--------------------------------------+--------------------------------------+-------------------------------------- - 0 | | | | - 20002 | | | | - 20003 | | | | - 10009 | 00000000-0000-0000-0000-000000010008 | 00000000-0000-0000-0000-000000009992 | 00010008-0000-0000-0000-000000010008 | 00009992-0000-0000-0000-000000009992 - 10008 | 00000000-0000-0000-0000-000000010007 | 00000000-0000-0000-0000-000000009993 | 00010007-0000-0000-0000-000000010007 | 00009993-0000-0000-0000-000000009993 -(5 rows) - -ROLLBACK; --- when no aborting, decreasing order -BEGIN; -SET LOCAL enable_indexscan = false; -CLUSTER abbrev_abort_uuids USING abbrev_abort_uuids__noabort_decreasing_idx; --- head -SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing -FROM abbrev_abort_uuids -ORDER BY ctid LIMIT 5; - id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing --------+--------------------------------------+--------------------------------------+--------------------------------------+-------------------------------------- - 20010 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 - 20001 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 - 9992 | 00000000-0000-0000-0000-000000009991 | 00000000-0000-0000-0000-000000010009 | 00009991-0000-0000-0000-000000009991 | 00000000-0000-0000-0000-000000010009 - 20000 | 00000000-0000-0000-0000-000000019999 | 00000000-0000-0000-0000-000000000001 | 00009990-0000-0000-0000-000000019999 | 00000001-0000-0000-0000-000000000001 - 9991 | 00000000-0000-0000-0000-000000009990 | 00000000-0000-0000-0000-000000010010 | 00009990-0000-0000-0000-000000009990 | 00000001-0000-0000-0000-000000010010 -(5 rows) - --- tail -SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing -FROM abbrev_abort_uuids -ORDER BY ctid DESC LIMIT 5; - id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing --------+--------------------------------------+--------------------------------------+--------------------------------------+-------------------------------------- - 0 | | | | - 20003 | | | | - 20002 | | | | - 9993 | 00000000-0000-0000-0000-000000009992 | 00000000-0000-0000-0000-000000010008 | 00009992-0000-0000-0000-000000009992 | 00010008-0000-0000-0000-000000010008 - 9994 | 00000000-0000-0000-0000-000000009993 | 00000000-0000-0000-0000-000000010007 | 00009993-0000-0000-0000-000000009993 | 00010007-0000-0000-0000-000000010007 -(5 rows) - -ROLLBACK; ----- --- test sorting of large datums VALUES ----- --- Ensure the order is correct and values look intact -SELECT LEFT(a,10),b FROM - (VALUES(REPEAT('a', 512 * 1024),1),(REPEAT('b', 512 * 1024),2)) v(a,b) -ORDER BY v.a DESC; - left | b -------------+--- - bbbbbbbbbb | 2 - aaaaaaaaaa | 1 -(2 rows) - ----- --- test forward and backward scans for in-memory and disk based tuplesort ----- --- in-memory -BEGIN; -SET LOCAL enable_indexscan = false; --- unfortunately can't show analyze output confirming sort method, --- the memory used output wouldn't be stable -EXPLAIN (COSTS OFF) DECLARE c SCROLL CURSOR FOR SELECT noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing; - QUERY PLAN --------------------------------------- - Sort - Sort Key: noabort_decreasing - -> Seq Scan on abbrev_abort_uuids -(3 rows) - -DECLARE c SCROLL CURSOR FOR SELECT noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing; --- first and second -FETCH NEXT FROM c; - noabort_decreasing --------------------------------------- - 00000000-0000-0000-0000-000000000000 -(1 row) - -FETCH NEXT FROM c; - noabort_decreasing --------------------------------------- - 00000000-0000-0000-0000-000000000000 -(1 row) - --- scroll beyond beginning -FETCH BACKWARD FROM c; - noabort_decreasing --------------------------------------- - 00000000-0000-0000-0000-000000000000 -(1 row) - -FETCH BACKWARD FROM c; - noabort_decreasing --------------------- -(0 rows) - -FETCH BACKWARD FROM c; - noabort_decreasing --------------------- -(0 rows) - -FETCH BACKWARD FROM c; - noabort_decreasing --------------------- -(0 rows) - -FETCH NEXT FROM c; - noabort_decreasing --------------------------------------- - 00000000-0000-0000-0000-000000000000 -(1 row) - --- scroll beyond end -FETCH LAST FROM c; - noabort_decreasing --------------------- - -(1 row) - -FETCH BACKWARD FROM c; - noabort_decreasing --------------------- - -(1 row) - -FETCH NEXT FROM c; - noabort_decreasing --------------------- - -(1 row) - -FETCH NEXT FROM c; - noabort_decreasing --------------------- -(0 rows) - -FETCH NEXT FROM c; - noabort_decreasing --------------------- -(0 rows) - -FETCH BACKWARD FROM c; - noabort_decreasing --------------------- - -(1 row) - -FETCH NEXT FROM c; - noabort_decreasing --------------------- -(0 rows) - -COMMIT; --- disk based -BEGIN; -SET LOCAL enable_indexscan = false; -SET LOCAL work_mem = '100kB'; --- unfortunately can't show analyze output confirming sort method, --- the memory used output wouldn't be stable -EXPLAIN (COSTS OFF) DECLARE c SCROLL CURSOR FOR SELECT noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing; - QUERY PLAN --------------------------------------- - Sort - Sort Key: noabort_decreasing - -> Seq Scan on abbrev_abort_uuids -(3 rows) - -DECLARE c SCROLL CURSOR FOR SELECT noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing; --- first and second -FETCH NEXT FROM c; - noabort_decreasing --------------------------------------- - 00000000-0000-0000-0000-000000000000 -(1 row) - -FETCH NEXT FROM c; - noabort_decreasing --------------------------------------- - 00000000-0000-0000-0000-000000000000 -(1 row) - --- scroll beyond beginning -FETCH BACKWARD FROM c; - noabort_decreasing --------------------------------------- - 00000000-0000-0000-0000-000000000000 -(1 row) - -FETCH BACKWARD FROM c; - noabort_decreasing --------------------- -(0 rows) - -FETCH BACKWARD FROM c; - noabort_decreasing --------------------- -(0 rows) - -FETCH BACKWARD FROM c; - noabort_decreasing --------------------- -(0 rows) - -FETCH NEXT FROM c; - noabort_decreasing --------------------------------------- - 00000000-0000-0000-0000-000000000000 -(1 row) - --- scroll beyond end -FETCH LAST FROM c; - noabort_decreasing --------------------- - -(1 row) - -FETCH BACKWARD FROM c; - noabort_decreasing --------------------- - -(1 row) - -FETCH NEXT FROM c; - noabort_decreasing --------------------- - -(1 row) - -FETCH NEXT FROM c; - noabort_decreasing --------------------- -(0 rows) - -FETCH NEXT FROM c; - noabort_decreasing --------------------- -(0 rows) - -FETCH BACKWARD FROM c; - noabort_decreasing --------------------- - -(1 row) - -FETCH NEXT FROM c; - noabort_decreasing --------------------- -(0 rows) - -COMMIT; ----- --- test tuplesort using both in-memory and disk sort ---- --- memory based -SELECT - -- fixed-width by-value datum - (array_agg(id ORDER BY id DESC NULLS FIRST))[0:5], - -- fixed-width by-ref datum - (array_agg(abort_increasing ORDER BY abort_increasing DESC NULLS LAST))[0:5], - -- variable-width datum - (array_agg(id::text ORDER BY id::text DESC NULLS LAST))[0:5], - -- fixed width by-value datum tuplesort - percentile_disc(0.99) WITHIN GROUP (ORDER BY id), - -- ensure state is shared - percentile_disc(0.01) WITHIN GROUP (ORDER BY id), - -- fixed width by-ref datum tuplesort - percentile_disc(0.8) WITHIN GROUP (ORDER BY abort_increasing), - -- variable width by-ref datum tuplesort - percentile_disc(0.2) WITHIN GROUP (ORDER BY id::text), - -- multi-column tuplesort - rank('00000000-0000-0000-0000-000000000000', '2', '2') WITHIN GROUP (ORDER BY noabort_increasing, id, id::text) -FROM ( - SELECT * FROM abbrev_abort_uuids - UNION ALL - SELECT NULL, NULL, NULL, NULL, NULL) s; - array_agg | array_agg | array_agg | percentile_disc | percentile_disc | percentile_disc | percentile_disc | rank ---------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------+-----------------+-----------------+--------------------------------------+-----------------+------ - {NULL,20010,20009,20008,20007} | {00000000-0000-0000-0000-000000020000,00000000-0000-0000-0000-000000020000,00000000-0000-0000-0000-000000019999,00000000-0000-0000-0000-000000019998,00000000-0000-0000-0000-000000019997} | {9999,9998,9997,9996,9995} | 19810 | 200 | 00000000-0000-0000-0000-000000016003 | 136 | 2 -(1 row) - --- disk based (see also above) -BEGIN; -SET LOCAL work_mem = '100kB'; -SELECT - (array_agg(id ORDER BY id DESC NULLS FIRST))[0:5], - (array_agg(abort_increasing ORDER BY abort_increasing DESC NULLS LAST))[0:5], - (array_agg(id::text ORDER BY id::text DESC NULLS LAST))[0:5], - percentile_disc(0.99) WITHIN GROUP (ORDER BY id), - percentile_disc(0.01) WITHIN GROUP (ORDER BY id), - percentile_disc(0.8) WITHIN GROUP (ORDER BY abort_increasing), - percentile_disc(0.2) WITHIN GROUP (ORDER BY id::text), - rank('00000000-0000-0000-0000-000000000000', '2', '2') WITHIN GROUP (ORDER BY noabort_increasing, id, id::text) -FROM ( - SELECT * FROM abbrev_abort_uuids - UNION ALL - SELECT NULL, NULL, NULL, NULL, NULL) s; - array_agg | array_agg | array_agg | percentile_disc | percentile_disc | percentile_disc | percentile_disc | rank ---------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------+-----------------+-----------------+--------------------------------------+-----------------+------ - {NULL,20010,20009,20008,20007} | {00000000-0000-0000-0000-000000020000,00000000-0000-0000-0000-000000020000,00000000-0000-0000-0000-000000019999,00000000-0000-0000-0000-000000019998,00000000-0000-0000-0000-000000019997} | {9999,9998,9997,9996,9995} | 19810 | 200 | 00000000-0000-0000-0000-000000016003 | 136 | 2 -(1 row) - -ROLLBACK; ----- --- test tuplesort mark/restore ---- -CREATE TEMP TABLE test_mark_restore(col1 int, col2 int, col12 int); --- need a few duplicates for mark/restore to matter -INSERT INTO test_mark_restore(col1, col2, col12) - SELECT a.i, b.i, a.i * b.i FROM generate_series(1, 500) a(i), generate_series(1, 5) b(i); -BEGIN; -SET LOCAL enable_nestloop = off; -SET LOCAL enable_hashjoin = off; -SET LOCAL enable_material = off; --- set query into variable once, to avoid repetition of the fairly long query -SELECT $$ - SELECT col12, count(distinct a.col1), count(distinct a.col2), count(distinct b.col1), count(distinct b.col2), count(*) - FROM test_mark_restore a - JOIN test_mark_restore b USING(col12) - GROUP BY 1 - HAVING count(*) > 1 - ORDER BY 2 DESC, 1 DESC, 3 DESC, 4 DESC, 5 DESC, 6 DESC - LIMIT 10 -$$ AS qry \gset --- test mark/restore with in-memory sorts -EXPLAIN (COSTS OFF) :qry; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Limit - -> Sort - Sort Key: (count(DISTINCT a.col1)) DESC, a.col12 DESC, (count(DISTINCT a.col2)) DESC, (count(DISTINCT b.col1)) DESC, (count(DISTINCT b.col2)) DESC, (count(*)) DESC - -> GroupAggregate - Group Key: a.col12 - Filter: (count(*) > 1) - -> Incremental Sort - Sort Key: a.col12 DESC, a.col1 - Presorted Key: a.col12 - -> Merge Join - Merge Cond: (a.col12 = b.col12) - -> Sort - Sort Key: a.col12 DESC - -> Seq Scan on test_mark_restore a - -> Sort - Sort Key: b.col12 DESC - -> Seq Scan on test_mark_restore b -(17 rows) - -:qry; - col12 | count | count | count | count | count --------+-------+-------+-------+-------+------- - 480 | 5 | 5 | 5 | 5 | 25 - 420 | 5 | 5 | 5 | 5 | 25 - 360 | 5 | 5 | 5 | 5 | 25 - 300 | 5 | 5 | 5 | 5 | 25 - 240 | 5 | 5 | 5 | 5 | 25 - 180 | 5 | 5 | 5 | 5 | 25 - 120 | 5 | 5 | 5 | 5 | 25 - 60 | 5 | 5 | 5 | 5 | 25 - 960 | 4 | 4 | 4 | 4 | 16 - 900 | 4 | 4 | 4 | 4 | 16 -(10 rows) - --- test mark/restore with on-disk sorts -SET LOCAL work_mem = '100kB'; -EXPLAIN (COSTS OFF) :qry; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Limit - -> Sort - Sort Key: (count(DISTINCT a.col1)) DESC, a.col12 DESC, (count(DISTINCT a.col2)) DESC, (count(DISTINCT b.col1)) DESC, (count(DISTINCT b.col2)) DESC, (count(*)) DESC - -> GroupAggregate - Group Key: a.col12 - Filter: (count(*) > 1) - -> Incremental Sort - Sort Key: a.col12 DESC, a.col1 - Presorted Key: a.col12 - -> Merge Join - Merge Cond: (a.col12 = b.col12) - -> Sort - Sort Key: a.col12 DESC - -> Seq Scan on test_mark_restore a - -> Sort - Sort Key: b.col12 DESC - -> Seq Scan on test_mark_restore b -(17 rows) - -:qry; - col12 | count | count | count | count | count --------+-------+-------+-------+-------+------- - 480 | 5 | 5 | 5 | 5 | 25 - 420 | 5 | 5 | 5 | 5 | 25 - 360 | 5 | 5 | 5 | 5 | 25 - 300 | 5 | 5 | 5 | 5 | 25 - 240 | 5 | 5 | 5 | 5 | 25 - 180 | 5 | 5 | 5 | 5 | 25 - 120 | 5 | 5 | 5 | 5 | 25 - 60 | 5 | 5 | 5 | 5 | 25 - 960 | 4 | 4 | 4 | 4 | 16 - 900 | 4 | 4 | 4 | 4 | 16 -(10 rows) - -COMMIT; +psql: error: connection to server on socket "/tmp/bBabKAIw6z/.s.PGSQL.19533" failed: No such file or directory + Is the server running locally and accepting connections on that socket? diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/explain.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/explain.out --- /tmp/cirrus-ci-build/src/test/regress/expected/explain.out 2024-09-22 06:28:18.692957897 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/explain.out 2024-09-22 06:38:09.884217553 +0000 @@ -1,731 +1,2 @@ --- --- EXPLAIN --- --- There are many test cases elsewhere that use EXPLAIN as a vehicle for --- checking something else (usually planner behavior). This file is --- concerned with testing EXPLAIN in its own right. --- --- To produce stable regression test output, it's usually necessary to --- ignore details such as exact costs or row counts. These filter --- functions replace changeable output details with fixed strings. -create function explain_filter(text) returns setof text -language plpgsql as -$$ -declare - ln text; -begin - for ln in execute $1 - loop - -- Replace any numeric word with just 'N' - ln := regexp_replace(ln, '-?\m\d+\M', 'N', 'g'); - -- In sort output, the above won't match units-suffixed numbers - ln := regexp_replace(ln, '\m\d+kB', 'NkB', 'g'); - -- Ignore text-mode buffers output because it varies depending - -- on the system state - CONTINUE WHEN (ln ~ ' +Buffers: .*'); - -- Ignore text-mode "Planning:" line because whether it's output - -- varies depending on the system state - CONTINUE WHEN (ln = 'Planning:'); - return next ln; - end loop; -end; -$$; --- To produce valid JSON output, replace numbers with "0" or "0.0" not "N" -create function explain_filter_to_json(text) returns jsonb -language plpgsql as -$$ -declare - data text := ''; - ln text; -begin - for ln in execute $1 - loop - -- Replace any numeric word with just '0' - ln := regexp_replace(ln, '\m\d+\M', '0', 'g'); - data := data || ln; - end loop; - return data::jsonb; -end; -$$; --- Disable JIT, or we'll get different output on machines where that's been --- forced on -set jit = off; --- Similarly, disable track_io_timing, to avoid output differences when --- enabled. -set track_io_timing = off; --- Simple cases -select explain_filter('explain select * from int8_tbl i8'); - explain_filter ---------------------------------------------------------- - Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) -(1 row) - -select explain_filter('explain (analyze) select * from int8_tbl i8'); - explain_filter ------------------------------------------------------------------------------------------------ - Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N) - Planning Time: N.N ms - Execution Time: N.N ms -(3 rows) - -select explain_filter('explain (analyze, verbose) select * from int8_tbl i8'); - explain_filter ------------------------------------------------------------------------------------------------------- - Seq Scan on public.int8_tbl i8 (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N) - Output: q1, q2 - Planning Time: N.N ms - Execution Time: N.N ms -(4 rows) - -select explain_filter('explain (analyze, buffers, format text) select * from int8_tbl i8'); - explain_filter ------------------------------------------------------------------------------------------------ - Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N) - Planning Time: N.N ms - Execution Time: N.N ms -(3 rows) - -select explain_filter('explain (analyze, buffers, format xml) select * from int8_tbl i8'); - explain_filter --------------------------------------------------------- - + - + - + - Seq Scan + - false + - false + - int8_tbl + - i8 + - N.N + - N.N + - N + - N + - N.N + - N.N + - N + - N + - N + - N + - N+ - N+ - N + - N + - N + - N + - N + - N + - + - + - N + - N + - N+ - N+ - N + - N + - N + - N + - N + - N + - + - N.N + - + - + - N.N + - + - -(1 row) - -select explain_filter('explain (analyze, serialize, buffers, format yaml) select * from int8_tbl i8'); - explain_filter -------------------------------- - - Plan: + - Node Type: "Seq Scan" + - Parallel Aware: false + - Async Capable: false + - Relation Name: "int8_tbl"+ - Alias: "i8" + - Startup Cost: N.N + - Total Cost: N.N + - Plan Rows: N + - Plan Width: N + - Actual Startup Time: N.N + - Actual Total Time: N.N + - Actual Rows: N + - Actual Loops: N + - Shared Hit Blocks: N + - Shared Read Blocks: N + - Shared Dirtied Blocks: N + - Shared Written Blocks: N + - Local Hit Blocks: N + - Local Read Blocks: N + - Local Dirtied Blocks: N + - Local Written Blocks: N + - Temp Read Blocks: N + - Temp Written Blocks: N + - Planning: + - Shared Hit Blocks: N + - Shared Read Blocks: N + - Shared Dirtied Blocks: N + - Shared Written Blocks: N + - Local Hit Blocks: N + - Local Read Blocks: N + - Local Dirtied Blocks: N + - Local Written Blocks: N + - Temp Read Blocks: N + - Temp Written Blocks: N + - Planning Time: N.N + - Triggers: + - Serialization: + - Time: N.N + - Output Volume: N + - Format: "text" + - Shared Hit Blocks: N + - Shared Read Blocks: N + - Shared Dirtied Blocks: N + - Shared Written Blocks: N + - Local Hit Blocks: N + - Local Read Blocks: N + - Local Dirtied Blocks: N + - Local Written Blocks: N + - Temp Read Blocks: N + - Temp Written Blocks: N + - Execution Time: N.N -(1 row) - -select explain_filter('explain (buffers, format text) select * from int8_tbl i8'); - explain_filter ---------------------------------------------------------- - Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) -(1 row) - -select explain_filter('explain (buffers, format json) select * from int8_tbl i8'); - explain_filter ------------------------------------- - [ + - { + - "Plan": { + - "Node Type": "Seq Scan", + - "Parallel Aware": false, + - "Async Capable": false, + - "Relation Name": "int8_tbl",+ - "Alias": "i8", + - "Startup Cost": N.N, + - "Total Cost": N.N, + - "Plan Rows": N, + - "Plan Width": N, + - "Shared Hit Blocks": N, + - "Shared Read Blocks": N, + - "Shared Dirtied Blocks": N, + - "Shared Written Blocks": N, + - "Local Hit Blocks": N, + - "Local Read Blocks": N, + - "Local Dirtied Blocks": N, + - "Local Written Blocks": N, + - "Temp Read Blocks": N, + - "Temp Written Blocks": N + - }, + - "Planning": { + - "Shared Hit Blocks": N, + - "Shared Read Blocks": N, + - "Shared Dirtied Blocks": N, + - "Shared Written Blocks": N, + - "Local Hit Blocks": N, + - "Local Read Blocks": N, + - "Local Dirtied Blocks": N, + - "Local Written Blocks": N, + - "Temp Read Blocks": N, + - "Temp Written Blocks": N + - } + - } + - ] -(1 row) - --- Check output including I/O timings. These fields are conditional --- but always set in JSON format, so check them only in this case. -set track_io_timing = on; -select explain_filter('explain (analyze, buffers, format json) select * from int8_tbl i8'); - explain_filter -------------------------------------- - [ + - { + - "Plan": { + - "Node Type": "Seq Scan", + - "Parallel Aware": false, + - "Async Capable": false, + - "Relation Name": "int8_tbl", + - "Alias": "i8", + - "Startup Cost": N.N, + - "Total Cost": N.N, + - "Plan Rows": N, + - "Plan Width": N, + - "Actual Startup Time": N.N, + - "Actual Total Time": N.N, + - "Actual Rows": N, + - "Actual Loops": N, + - "Shared Hit Blocks": N, + - "Shared Read Blocks": N, + - "Shared Dirtied Blocks": N, + - "Shared Written Blocks": N, + - "Local Hit Blocks": N, + - "Local Read Blocks": N, + - "Local Dirtied Blocks": N, + - "Local Written Blocks": N, + - "Temp Read Blocks": N, + - "Temp Written Blocks": N, + - "Shared I/O Read Time": N.N, + - "Shared I/O Write Time": N.N,+ - "Local I/O Read Time": N.N, + - "Local I/O Write Time": N.N, + - "Temp I/O Read Time": N.N, + - "Temp I/O Write Time": N.N + - }, + - "Planning": { + - "Shared Hit Blocks": N, + - "Shared Read Blocks": N, + - "Shared Dirtied Blocks": N, + - "Shared Written Blocks": N, + - "Local Hit Blocks": N, + - "Local Read Blocks": N, + - "Local Dirtied Blocks": N, + - "Local Written Blocks": N, + - "Temp Read Blocks": N, + - "Temp Written Blocks": N, + - "Shared I/O Read Time": N.N, + - "Shared I/O Write Time": N.N,+ - "Local I/O Read Time": N.N, + - "Local I/O Write Time": N.N, + - "Temp I/O Read Time": N.N, + - "Temp I/O Write Time": N.N + - }, + - "Planning Time": N.N, + - "Triggers": [ + - ], + - "Execution Time": N.N + - } + - ] -(1 row) - -set track_io_timing = off; --- SETTINGS option --- We have to ignore other settings that might be imposed by the environment, --- so printing the whole Settings field unfortunately won't do. -begin; -set local plan_cache_mode = force_generic_plan; -select true as "OK" - from explain_filter('explain (settings) select * from int8_tbl i8') ln - where ln ~ '^ *Settings: .*plan_cache_mode = ''force_generic_plan'''; - OK ----- - t -(1 row) - -select explain_filter_to_json('explain (settings, format json) select * from int8_tbl i8') #> '{0,Settings,plan_cache_mode}'; - ?column? ----------------------- - "force_generic_plan" -(1 row) - -rollback; --- GENERIC_PLAN option -select explain_filter('explain (generic_plan) select unique1 from tenk1 where thousand = $1'); - explain_filter ---------------------------------------------------------------------------------- - Bitmap Heap Scan on tenk1 (cost=N.N..N.N rows=N width=N) - Recheck Cond: (thousand = $N) - -> Bitmap Index Scan on tenk1_thous_tenthous (cost=N.N..N.N rows=N width=N) - Index Cond: (thousand = $N) -(4 rows) - --- should fail -select explain_filter('explain (analyze, generic_plan) select unique1 from tenk1 where thousand = $1'); -ERROR: EXPLAIN options ANALYZE and GENERIC_PLAN cannot be used together -CONTEXT: PL/pgSQL function explain_filter(text) line 5 at FOR over EXECUTE statement --- MEMORY option -select explain_filter('explain (memory) select * from int8_tbl i8'); - explain_filter ---------------------------------------------------------- - Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) - Memory: used=NkB allocated=NkB -(2 rows) - -select explain_filter('explain (memory, analyze) select * from int8_tbl i8'); - explain_filter ------------------------------------------------------------------------------------------------ - Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N) - Memory: used=NkB allocated=NkB - Planning Time: N.N ms - Execution Time: N.N ms -(4 rows) - -select explain_filter('explain (memory, summary, format yaml) select * from int8_tbl i8'); - explain_filter -------------------------------- - - Plan: + - Node Type: "Seq Scan" + - Parallel Aware: false + - Async Capable: false + - Relation Name: "int8_tbl"+ - Alias: "i8" + - Startup Cost: N.N + - Total Cost: N.N + - Plan Rows: N + - Plan Width: N + - Planning: + - Memory Used: N + - Memory Allocated: N + - Planning Time: N.N -(1 row) - -select explain_filter('explain (memory, analyze, format json) select * from int8_tbl i8'); - explain_filter ------------------------------------- - [ + - { + - "Plan": { + - "Node Type": "Seq Scan", + - "Parallel Aware": false, + - "Async Capable": false, + - "Relation Name": "int8_tbl",+ - "Alias": "i8", + - "Startup Cost": N.N, + - "Total Cost": N.N, + - "Plan Rows": N, + - "Plan Width": N, + - "Actual Startup Time": N.N, + - "Actual Total Time": N.N, + - "Actual Rows": N, + - "Actual Loops": N + - }, + - "Planning": { + - "Memory Used": N, + - "Memory Allocated": N + - }, + - "Planning Time": N.N, + - "Triggers": [ + - ], + - "Execution Time": N.N + - } + - ] -(1 row) - -prepare int8_query as select * from int8_tbl i8; -select explain_filter('explain (memory) execute int8_query'); - explain_filter ---------------------------------------------------------- - Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) - Memory: used=NkB allocated=NkB -(2 rows) - --- Test EXPLAIN (GENERIC_PLAN) with partition pruning --- partitions should be pruned at plan time, based on constants, --- but there should be no pruning based on parameter placeholders -create table gen_part ( - key1 integer not null, - key2 integer not null -) partition by list (key1); -create table gen_part_1 - partition of gen_part for values in (1) - partition by range (key2); -create table gen_part_1_1 - partition of gen_part_1 for values from (1) to (2); -create table gen_part_1_2 - partition of gen_part_1 for values from (2) to (3); -create table gen_part_2 - partition of gen_part for values in (2); --- should scan gen_part_1_1 and gen_part_1_2, but not gen_part_2 -select explain_filter('explain (generic_plan) select key1, key2 from gen_part where key1 = 1 and key2 = $1'); - explain_filter ---------------------------------------------------------------------------- - Append (cost=N.N..N.N rows=N width=N) - -> Seq Scan on gen_part_1_1 gen_part_1 (cost=N.N..N.N rows=N width=N) - Filter: ((key1 = N) AND (key2 = $N)) - -> Seq Scan on gen_part_1_2 gen_part_2 (cost=N.N..N.N rows=N width=N) - Filter: ((key1 = N) AND (key2 = $N)) -(5 rows) - -drop table gen_part; --- --- Test production of per-worker data --- --- Unfortunately, because we don't know how many worker processes we'll --- actually get (maybe none at all), we can't examine the "Workers" output --- in any detail. We can check that it parses correctly as JSON, and then --- remove it from the displayed results. -begin; --- encourage use of parallel plans -set parallel_setup_cost=0; -set parallel_tuple_cost=0; -set min_parallel_table_scan_size=0; -set max_parallel_workers_per_gather=4; -select jsonb_pretty( - explain_filter_to_json('explain (analyze, verbose, buffers, format json) - select * from tenk1 order by tenthous') - -- remove "Workers" node of the Seq Scan plan node - #- '{0,Plan,Plans,0,Plans,0,Workers}' - -- remove "Workers" node of the Sort plan node - #- '{0,Plan,Plans,0,Workers}' - -- Also remove its sort-type fields, as those aren't 100% stable - #- '{0,Plan,Plans,0,Sort Method}' - #- '{0,Plan,Plans,0,Sort Space Type}' -); - jsonb_pretty -------------------------------------------------------------- - [ + - { + - "Plan": { + - "Plans": [ + - { + - "Plans": [ + - { + - "Alias": "tenk1", + - "Output": [ + - "unique1", + - "unique2", + - "two", + - "four", + - "ten", + - "twenty", + - "hundred", + - "thousand", + - "twothousand", + - "fivethous", + - "tenthous", + - "odd", + - "even", + - "stringu1", + - "stringu2", + - "string4" + - ], + - "Schema": "public", + - "Node Type": "Seq Scan", + - "Plan Rows": 0, + - "Plan Width": 0, + - "Total Cost": 0.0, + - "Actual Rows": 0, + - "Actual Loops": 0, + - "Startup Cost": 0.0, + - "Async Capable": false, + - "Relation Name": "tenk1", + - "Parallel Aware": true, + - "Local Hit Blocks": 0, + - "Temp Read Blocks": 0, + - "Actual Total Time": 0.0, + - "Local Read Blocks": 0, + - "Shared Hit Blocks": 0, + - "Shared Read Blocks": 0, + - "Actual Startup Time": 0.0, + - "Parent Relationship": "Outer",+ - "Temp Written Blocks": 0, + - "Local Dirtied Blocks": 0, + - "Local Written Blocks": 0, + - "Shared Dirtied Blocks": 0, + - "Shared Written Blocks": 0 + - } + - ], + - "Output": [ + - "unique1", + - "unique2", + - "two", + - "four", + - "ten", + - "twenty", + - "hundred", + - "thousand", + - "twothousand", + - "fivethous", + - "tenthous", + - "odd", + - "even", + - "stringu1", + - "stringu2", + - "string4" + - ], + - "Sort Key": [ + - "tenk1.tenthous" + - ], + - "Node Type": "Sort", + - "Plan Rows": 0, + - "Plan Width": 0, + - "Total Cost": 0.0, + - "Actual Rows": 0, + - "Actual Loops": 0, + - "Startup Cost": 0.0, + - "Async Capable": false, + - "Parallel Aware": false, + - "Sort Space Used": 0, + - "Local Hit Blocks": 0, + - "Temp Read Blocks": 0, + - "Actual Total Time": 0.0, + - "Local Read Blocks": 0, + - "Shared Hit Blocks": 0, + - "Shared Read Blocks": 0, + - "Actual Startup Time": 0.0, + - "Parent Relationship": "Outer", + - "Temp Written Blocks": 0, + - "Local Dirtied Blocks": 0, + - "Local Written Blocks": 0, + - "Shared Dirtied Blocks": 0, + - "Shared Written Blocks": 0 + - } + - ], + - "Output": [ + - "unique1", + - "unique2", + - "two", + - "four", + - "ten", + - "twenty", + - "hundred", + - "thousand", + - "twothousand", + - "fivethous", + - "tenthous", + - "odd", + - "even", + - "stringu1", + - "stringu2", + - "string4" + - ], + - "Node Type": "Gather Merge", + - "Plan Rows": 0, + - "Plan Width": 0, + - "Total Cost": 0.0, + - "Actual Rows": 0, + - "Actual Loops": 0, + - "Startup Cost": 0.0, + - "Async Capable": false, + - "Parallel Aware": false, + - "Workers Planned": 0, + - "Local Hit Blocks": 0, + - "Temp Read Blocks": 0, + - "Workers Launched": 0, + - "Actual Total Time": 0.0, + - "Local Read Blocks": 0, + - "Shared Hit Blocks": 0, + - "Shared Read Blocks": 0, + - "Actual Startup Time": 0.0, + - "Temp Written Blocks": 0, + - "Local Dirtied Blocks": 0, + - "Local Written Blocks": 0, + - "Shared Dirtied Blocks": 0, + - "Shared Written Blocks": 0 + - }, + - "Planning": { + - "Local Hit Blocks": 0, + - "Temp Read Blocks": 0, + - "Local Read Blocks": 0, + - "Shared Hit Blocks": 0, + - "Shared Read Blocks": 0, + - "Temp Written Blocks": 0, + - "Local Dirtied Blocks": 0, + - "Local Written Blocks": 0, + - "Shared Dirtied Blocks": 0, + - "Shared Written Blocks": 0 + - }, + - "Triggers": [ + - ], + - "Planning Time": 0.0, + - "Execution Time": 0.0 + - } + - ] -(1 row) - -rollback; --- Test display of temporary objects -create temp table t1(f1 float8); -create function pg_temp.mysin(float8) returns float8 language plpgsql -as 'begin return sin($1); end'; -select explain_filter('explain (verbose) select * from t1 where pg_temp.mysin(f1) < 0.5'); - explain_filter ------------------------------------------------------------- - Seq Scan on pg_temp.t1 (cost=N.N..N.N rows=N width=N) - Output: f1 - Filter: (pg_temp.mysin(t1.f1) < 'N.N'::double precision) -(3 rows) - --- Test compute_query_id -set compute_query_id = on; -select explain_filter('explain (verbose) select * from int8_tbl i8'); - explain_filter ----------------------------------------------------------------- - Seq Scan on public.int8_tbl i8 (cost=N.N..N.N rows=N width=N) - Output: q1, q2 - Query Identifier: N -(3 rows) - --- Test SERIALIZE option -select explain_filter('explain (analyze,serialize) select * from int8_tbl i8'); - explain_filter ------------------------------------------------------------------------------------------------ - Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N) - Planning Time: N.N ms - Serialization: time=N.N ms output=NkB format=text - Execution Time: N.N ms -(4 rows) - -select explain_filter('explain (analyze,serialize text,buffers,timing off) select * from int8_tbl i8'); - explain_filter ---------------------------------------------------------------------------------- - Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) (actual rows=N loops=N) - Planning Time: N.N ms - Serialization: output=NkB format=text - Execution Time: N.N ms -(4 rows) - -select explain_filter('explain (analyze,serialize binary,buffers,timing) select * from int8_tbl i8'); - explain_filter ------------------------------------------------------------------------------------------------ - Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N) - Planning Time: N.N ms - Serialization: time=N.N ms output=NkB format=binary - Execution Time: N.N ms -(4 rows) - --- this tests an edge case where we have no data to return -select explain_filter('explain (analyze,serialize) create temp table explain_temp as select * from int8_tbl i8'); - explain_filter ------------------------------------------------------------------------------------------------ - Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N) - Planning Time: N.N ms - Serialization: time=N.N ms output=NkB format=text - Execution Time: N.N ms -(4 rows) - --- Test tuplestore storage usage in Window aggregate (memory case) -select explain_filter('explain (analyze,costs off) select sum(n) over() from generate_series(1,10) a(n)'); - explain_filter --------------------------------------------------------------------------------- - WindowAgg (actual time=N.N..N.N rows=N loops=N) - Storage: Memory Maximum Storage: NkB - -> Function Scan on generate_series a (actual time=N.N..N.N rows=N loops=N) - Planning Time: N.N ms - Execution Time: N.N ms -(5 rows) - --- Test tuplestore storage usage in Window aggregate (disk case) -set work_mem to 64; -select explain_filter('explain (analyze,costs off) select sum(n) over() from generate_series(1,2000) a(n)'); - explain_filter --------------------------------------------------------------------------------- - WindowAgg (actual time=N.N..N.N rows=N loops=N) - Storage: Disk Maximum Storage: NkB - -> Function Scan on generate_series a (actual time=N.N..N.N rows=N loops=N) - Planning Time: N.N ms - Execution Time: N.N ms -(5 rows) - --- Test tuplestore storage usage in Window aggregate (memory and disk case, final result is disk) -select explain_filter('explain (analyze,costs off) select sum(n) over(partition by m) from (SELECT n < 3 as m, n from generate_series(1,2000) a(n))'); - explain_filter --------------------------------------------------------------------------------------- - WindowAgg (actual time=N.N..N.N rows=N loops=N) - Storage: Disk Maximum Storage: NkB - -> Sort (actual time=N.N..N.N rows=N loops=N) - Sort Key: ((a.n < N)) - Sort Method: external merge Disk: NkB - -> Function Scan on generate_series a (actual time=N.N..N.N rows=N loops=N) - Planning Time: N.N ms - Execution Time: N.N ms -(8 rows) - -reset work_mem; +psql: error: connection to server on socket "/tmp/bBabKAIw6z/.s.PGSQL.19533" failed: No such file or directory + Is the server running locally and accepting connections on that socket? diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/compression_1.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/compression.out --- /tmp/cirrus-ci-build/src/test/regress/expected/compression_1.out 2024-09-22 06:28:18.684916216 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/compression.out 2024-09-22 06:38:09.896217527 +0000 @@ -1,360 +1,2 @@ -\set HIDE_TOAST_COMPRESSION false --- ensure we get stable results regardless of installation's default -SET default_toast_compression = 'pglz'; --- test creating table with compression method -CREATE TABLE cmdata(f1 text COMPRESSION pglz); -CREATE INDEX idx ON cmdata(f1); -INSERT INTO cmdata VALUES(repeat('1234567890', 1000)); -\d+ cmdata - Table "public.cmdata" - Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description ---------+------+-----------+----------+---------+----------+-------------+--------------+------------- - f1 | text | | | | extended | pglz | | -Indexes: - "idx" btree (f1) - -CREATE TABLE cmdata1(f1 TEXT COMPRESSION lz4); -ERROR: compression method lz4 not supported -DETAIL: This functionality requires the server to be built with lz4 support. -INSERT INTO cmdata1 VALUES(repeat('1234567890', 1004)); -ERROR: relation "cmdata1" does not exist -LINE 1: INSERT INTO cmdata1 VALUES(repeat('1234567890', 1004)); - ^ -\d+ cmdata1 --- verify stored compression method in the data -SELECT pg_column_compression(f1) FROM cmdata; - pg_column_compression ------------------------ - pglz -(1 row) - -SELECT pg_column_compression(f1) FROM cmdata1; -ERROR: relation "cmdata1" does not exist -LINE 1: SELECT pg_column_compression(f1) FROM cmdata1; - ^ --- decompress data slice -SELECT SUBSTR(f1, 200, 5) FROM cmdata; - substr --------- - 01234 -(1 row) - -SELECT SUBSTR(f1, 2000, 50) FROM cmdata1; -ERROR: relation "cmdata1" does not exist -LINE 1: SELECT SUBSTR(f1, 2000, 50) FROM cmdata1; - ^ --- copy with table creation -SELECT * INTO cmmove1 FROM cmdata; -\d+ cmmove1 - Table "public.cmmove1" - Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description ---------+------+-----------+----------+---------+----------+-------------+--------------+------------- - f1 | text | | | | extended | | | - -SELECT pg_column_compression(f1) FROM cmmove1; - pg_column_compression ------------------------ - pglz -(1 row) - --- copy to existing table -CREATE TABLE cmmove3(f1 text COMPRESSION pglz); -INSERT INTO cmmove3 SELECT * FROM cmdata; -INSERT INTO cmmove3 SELECT * FROM cmdata1; -ERROR: relation "cmdata1" does not exist -LINE 1: INSERT INTO cmmove3 SELECT * FROM cmdata1; - ^ -SELECT pg_column_compression(f1) FROM cmmove3; - pg_column_compression ------------------------ - pglz -(1 row) - --- test LIKE INCLUDING COMPRESSION -CREATE TABLE cmdata2 (LIKE cmdata1 INCLUDING COMPRESSION); -ERROR: relation "cmdata1" does not exist -LINE 1: CREATE TABLE cmdata2 (LIKE cmdata1 INCLUDING COMPRESSION); - ^ -\d+ cmdata2 -DROP TABLE cmdata2; -ERROR: table "cmdata2" does not exist --- try setting compression for incompressible data type -CREATE TABLE cmdata2 (f1 int COMPRESSION pglz); -ERROR: column data type integer does not support compression --- update using datum from different table -CREATE TABLE cmmove2(f1 text COMPRESSION pglz); -INSERT INTO cmmove2 VALUES (repeat('1234567890', 1004)); -SELECT pg_column_compression(f1) FROM cmmove2; - pg_column_compression ------------------------ - pglz -(1 row) - -UPDATE cmmove2 SET f1 = cmdata1.f1 FROM cmdata1; -ERROR: relation "cmdata1" does not exist -LINE 1: UPDATE cmmove2 SET f1 = cmdata1.f1 FROM cmdata1; - ^ -SELECT pg_column_compression(f1) FROM cmmove2; - pg_column_compression ------------------------ - pglz -(1 row) - --- test externally stored compressed data -CREATE OR REPLACE FUNCTION large_val() RETURNS TEXT LANGUAGE SQL AS -'select array_agg(fipshash(g::text))::text from generate_series(1, 256) g'; -CREATE TABLE cmdata2 (f1 text COMPRESSION pglz); -INSERT INTO cmdata2 SELECT large_val() || repeat('a', 4000); -SELECT pg_column_compression(f1) FROM cmdata2; - pg_column_compression ------------------------ - pglz -(1 row) - -INSERT INTO cmdata1 SELECT large_val() || repeat('a', 4000); -ERROR: relation "cmdata1" does not exist -LINE 1: INSERT INTO cmdata1 SELECT large_val() || repeat('a', 4000); - ^ -SELECT pg_column_compression(f1) FROM cmdata1; -ERROR: relation "cmdata1" does not exist -LINE 1: SELECT pg_column_compression(f1) FROM cmdata1; - ^ -SELECT SUBSTR(f1, 200, 5) FROM cmdata1; -ERROR: relation "cmdata1" does not exist -LINE 1: SELECT SUBSTR(f1, 200, 5) FROM cmdata1; - ^ -SELECT SUBSTR(f1, 200, 5) FROM cmdata2; - substr --------- - 79026 -(1 row) - -DROP TABLE cmdata2; ---test column type update varlena/non-varlena -CREATE TABLE cmdata2 (f1 int); -\d+ cmdata2 - Table "public.cmdata2" - Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description ---------+---------+-----------+----------+---------+---------+-------------+--------------+------------- - f1 | integer | | | | plain | | | - -ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar; -\d+ cmdata2 - Table "public.cmdata2" - Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description ---------+-------------------+-----------+----------+---------+----------+-------------+--------------+------------- - f1 | character varying | | | | extended | | | - -ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer; -\d+ cmdata2 - Table "public.cmdata2" - Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description ---------+---------+-----------+----------+---------+---------+-------------+--------------+------------- - f1 | integer | | | | plain | | | - ---changing column storage should not impact the compression method ---but the data should not be compressed -ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar; -ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION pglz; -\d+ cmdata2 - Table "public.cmdata2" - Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description ---------+-------------------+-----------+----------+---------+----------+-------------+--------------+------------- - f1 | character varying | | | | extended | pglz | | - -ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain; -\d+ cmdata2 - Table "public.cmdata2" - Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description ---------+-------------------+-----------+----------+---------+---------+-------------+--------------+------------- - f1 | character varying | | | | plain | pglz | | - -INSERT INTO cmdata2 VALUES (repeat('123456789', 800)); -SELECT pg_column_compression(f1) FROM cmdata2; - pg_column_compression ------------------------ - -(1 row) - --- test compression with materialized view -CREATE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1; -ERROR: relation "cmdata1" does not exist -LINE 1: ...TE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1; - ^ -\d+ compressmv -SELECT pg_column_compression(f1) FROM cmdata1; -ERROR: relation "cmdata1" does not exist -LINE 1: SELECT pg_column_compression(f1) FROM cmdata1; - ^ -SELECT pg_column_compression(x) FROM compressmv; -ERROR: relation "compressmv" does not exist -LINE 1: SELECT pg_column_compression(x) FROM compressmv; - ^ --- test compression with partition -CREATE TABLE cmpart(f1 text COMPRESSION lz4) PARTITION BY HASH(f1); -ERROR: compression method lz4 not supported -DETAIL: This functionality requires the server to be built with lz4 support. -CREATE TABLE cmpart1 PARTITION OF cmpart FOR VALUES WITH (MODULUS 2, REMAINDER 0); -ERROR: relation "cmpart" does not exist -CREATE TABLE cmpart2(f1 text COMPRESSION pglz); -ALTER TABLE cmpart ATTACH PARTITION cmpart2 FOR VALUES WITH (MODULUS 2, REMAINDER 1); -ERROR: relation "cmpart" does not exist -INSERT INTO cmpart VALUES (repeat('123456789', 1004)); -ERROR: relation "cmpart" does not exist -LINE 1: INSERT INTO cmpart VALUES (repeat('123456789', 1004)); - ^ -INSERT INTO cmpart VALUES (repeat('123456789', 4004)); -ERROR: relation "cmpart" does not exist -LINE 1: INSERT INTO cmpart VALUES (repeat('123456789', 4004)); - ^ -SELECT pg_column_compression(f1) FROM cmpart1; -ERROR: relation "cmpart1" does not exist -LINE 1: SELECT pg_column_compression(f1) FROM cmpart1; - ^ -SELECT pg_column_compression(f1) FROM cmpart2; - pg_column_compression ------------------------ -(0 rows) - --- test compression with inheritance -CREATE TABLE cminh() INHERITS(cmdata, cmdata1); -- error -ERROR: relation "cmdata1" does not exist -CREATE TABLE cminh(f1 TEXT COMPRESSION lz4) INHERITS(cmdata); -- error -NOTICE: merging column "f1" with inherited definition -ERROR: column "f1" has a compression method conflict -DETAIL: pglz versus lz4 -CREATE TABLE cmdata3(f1 text); -CREATE TABLE cminh() INHERITS (cmdata, cmdata3); -NOTICE: merging multiple inherited definitions of column "f1" --- test default_toast_compression GUC -SET default_toast_compression = ''; -ERROR: invalid value for parameter "default_toast_compression": "" -HINT: Available values: pglz. -SET default_toast_compression = 'I do not exist compression'; -ERROR: invalid value for parameter "default_toast_compression": "I do not exist compression" -HINT: Available values: pglz. -SET default_toast_compression = 'lz4'; -ERROR: invalid value for parameter "default_toast_compression": "lz4" -HINT: Available values: pglz. -SET default_toast_compression = 'pglz'; --- test alter compression method -ALTER TABLE cmdata ALTER COLUMN f1 SET COMPRESSION lz4; -ERROR: compression method lz4 not supported -DETAIL: This functionality requires the server to be built with lz4 support. -INSERT INTO cmdata VALUES (repeat('123456789', 4004)); -\d+ cmdata - Table "public.cmdata" - Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description ---------+------+-----------+----------+---------+----------+-------------+--------------+------------- - f1 | text | | | | extended | pglz | | -Indexes: - "idx" btree (f1) -Child tables: cminh - -SELECT pg_column_compression(f1) FROM cmdata; - pg_column_compression ------------------------ - pglz - pglz -(2 rows) - -ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION default; -\d+ cmdata2 - Table "public.cmdata2" - Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description ---------+-------------------+-----------+----------+---------+---------+-------------+--------------+------------- - f1 | character varying | | | | plain | | | - --- test alter compression method for materialized views -ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4; -ERROR: relation "compressmv" does not exist -\d+ compressmv --- test alter compression method for partitioned tables -ALTER TABLE cmpart1 ALTER COLUMN f1 SET COMPRESSION pglz; -ERROR: relation "cmpart1" does not exist -ALTER TABLE cmpart2 ALTER COLUMN f1 SET COMPRESSION lz4; -ERROR: compression method lz4 not supported -DETAIL: This functionality requires the server to be built with lz4 support. --- new data should be compressed with the current compression method -INSERT INTO cmpart VALUES (repeat('123456789', 1004)); -ERROR: relation "cmpart" does not exist -LINE 1: INSERT INTO cmpart VALUES (repeat('123456789', 1004)); - ^ -INSERT INTO cmpart VALUES (repeat('123456789', 4004)); -ERROR: relation "cmpart" does not exist -LINE 1: INSERT INTO cmpart VALUES (repeat('123456789', 4004)); - ^ -SELECT pg_column_compression(f1) FROM cmpart1; -ERROR: relation "cmpart1" does not exist -LINE 1: SELECT pg_column_compression(f1) FROM cmpart1; - ^ -SELECT pg_column_compression(f1) FROM cmpart2; - pg_column_compression ------------------------ -(0 rows) - --- VACUUM FULL does not recompress -SELECT pg_column_compression(f1) FROM cmdata; - pg_column_compression ------------------------ - pglz - pglz -(2 rows) - -VACUUM FULL cmdata; -SELECT pg_column_compression(f1) FROM cmdata; - pg_column_compression ------------------------ - pglz - pglz -(2 rows) - --- test expression index -DROP TABLE cmdata2; -CREATE TABLE cmdata2 (f1 TEXT COMPRESSION pglz, f2 TEXT COMPRESSION lz4); -ERROR: compression method lz4 not supported -DETAIL: This functionality requires the server to be built with lz4 support. -CREATE UNIQUE INDEX idx1 ON cmdata2 ((f1 || f2)); -ERROR: relation "cmdata2" does not exist -INSERT INTO cmdata2 VALUES((SELECT array_agg(fipshash(g::TEXT))::TEXT FROM -generate_series(1, 50) g), VERSION()); -ERROR: relation "cmdata2" does not exist -LINE 1: INSERT INTO cmdata2 VALUES((SELECT array_agg(fipshash(g::TEX... - ^ --- check data is ok -SELECT length(f1) FROM cmdata; - length --------- - 10000 - 36036 -(2 rows) - -SELECT length(f1) FROM cmdata1; -ERROR: relation "cmdata1" does not exist -LINE 1: SELECT length(f1) FROM cmdata1; - ^ -SELECT length(f1) FROM cmmove1; - length --------- - 10000 -(1 row) - -SELECT length(f1) FROM cmmove2; - length --------- - 10040 -(1 row) - -SELECT length(f1) FROM cmmove3; - length --------- - 10000 -(1 row) - -CREATE TABLE badcompresstbl (a text COMPRESSION I_Do_Not_Exist_Compression); -- fails -ERROR: invalid compression method "i_do_not_exist_compression" -CREATE TABLE badcompresstbl (a text); -ALTER TABLE badcompresstbl ALTER a SET COMPRESSION I_Do_Not_Exist_Compression; -- fails -ERROR: invalid compression method "i_do_not_exist_compression" -DROP TABLE badcompresstbl; -\set HIDE_TOAST_COMPRESSION true +psql: error: connection to server on socket "/tmp/bBabKAIw6z/.s.PGSQL.19533" failed: No such file or directory + Is the server running locally and accepting connections on that socket? diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/memoize.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/memoize.out --- /tmp/cirrus-ci-build/src/test/regress/expected/memoize.out 2024-09-22 06:28:18.721103777 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/memoize.out 2024-09-22 06:38:09.892217536 +0000 @@ -1,483 +1,2 @@ --- Perform tests on the Memoize node. --- The cache hits/misses/evictions from the Memoize node can vary between --- machines. Let's just replace the number with an 'N'. In order to allow us --- to perform validation when the measure was zero, we replace a zero value --- with "Zero". All other numbers are replaced with 'N'. -create function explain_memoize(query text, hide_hitmiss bool) returns setof text -language plpgsql as -$$ -declare - ln text; -begin - for ln in - execute format('explain (analyze, costs off, summary off, timing off) %s', - query) - loop - if hide_hitmiss = true then - ln := regexp_replace(ln, 'Hits: 0', 'Hits: Zero'); - ln := regexp_replace(ln, 'Hits: \d+', 'Hits: N'); - ln := regexp_replace(ln, 'Misses: 0', 'Misses: Zero'); - ln := regexp_replace(ln, 'Misses: \d+', 'Misses: N'); - end if; - ln := regexp_replace(ln, 'Evictions: 0', 'Evictions: Zero'); - ln := regexp_replace(ln, 'Evictions: \d+', 'Evictions: N'); - ln := regexp_replace(ln, 'Memory Usage: \d+', 'Memory Usage: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'loops=\d+', 'loops=N'); - return next ln; - end loop; -end; -$$; --- Ensure we get a memoize node on the inner side of the nested loop -SET enable_hashjoin TO off; -SET enable_bitmapscan TO off; -SELECT explain_memoize(' -SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1 -INNER JOIN tenk1 t2 ON t1.unique1 = t2.twenty -WHERE t2.unique1 < 1000;', false); - explain_memoize -------------------------------------------------------------------------------------------- - Aggregate (actual rows=1 loops=N) - -> Nested Loop (actual rows=1000 loops=N) - -> Seq Scan on tenk1 t2 (actual rows=1000 loops=N) - Filter: (unique1 < 1000) - Rows Removed by Filter: 9000 - -> Memoize (actual rows=1 loops=N) - Cache Key: t2.twenty - Cache Mode: logical - Hits: 980 Misses: 20 Evictions: Zero Overflows: 0 Memory Usage: NkB - -> Index Only Scan using tenk1_unique1 on tenk1 t1 (actual rows=1 loops=N) - Index Cond: (unique1 = t2.twenty) - Heap Fetches: N -(12 rows) - --- And check we get the expected results. -SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1 -INNER JOIN tenk1 t2 ON t1.unique1 = t2.twenty -WHERE t2.unique1 < 1000; - count | avg --------+-------------------- - 1000 | 9.5000000000000000 -(1 row) - --- Try with LATERAL joins -SELECT explain_memoize(' -SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1, -LATERAL (SELECT t2.unique1 FROM tenk1 t2 - WHERE t1.twenty = t2.unique1 OFFSET 0) t2 -WHERE t1.unique1 < 1000;', false); - explain_memoize -------------------------------------------------------------------------------------------- - Aggregate (actual rows=1 loops=N) - -> Nested Loop (actual rows=1000 loops=N) - -> Seq Scan on tenk1 t1 (actual rows=1000 loops=N) - Filter: (unique1 < 1000) - Rows Removed by Filter: 9000 - -> Memoize (actual rows=1 loops=N) - Cache Key: t1.twenty - Cache Mode: binary - Hits: 980 Misses: 20 Evictions: Zero Overflows: 0 Memory Usage: NkB - -> Index Only Scan using tenk1_unique1 on tenk1 t2 (actual rows=1 loops=N) - Index Cond: (unique1 = t1.twenty) - Heap Fetches: N -(12 rows) - --- And check we get the expected results. -SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1, -LATERAL (SELECT t2.unique1 FROM tenk1 t2 - WHERE t1.twenty = t2.unique1 OFFSET 0) t2 -WHERE t1.unique1 < 1000; - count | avg --------+-------------------- - 1000 | 9.5000000000000000 -(1 row) - --- Try with LATERAL joins -SELECT explain_memoize(' -SELECT COUNT(*),AVG(t2.t1two) FROM tenk1 t1 LEFT JOIN -LATERAL ( - SELECT t1.two as t1two, * FROM tenk1 t2 WHERE t2.unique1 < 4 OFFSET 0 -) t2 -ON t1.two = t2.two -WHERE t1.unique1 < 10;', false); - explain_memoize ----------------------------------------------------------------------------------------------- - Aggregate (actual rows=1 loops=N) - -> Nested Loop Left Join (actual rows=20 loops=N) - -> Index Scan using tenk1_unique1 on tenk1 t1 (actual rows=10 loops=N) - Index Cond: (unique1 < 10) - -> Memoize (actual rows=2 loops=N) - Cache Key: t1.two - Cache Mode: binary - Hits: 8 Misses: 2 Evictions: Zero Overflows: 0 Memory Usage: NkB - -> Subquery Scan on t2 (actual rows=2 loops=N) - Filter: (t1.two = t2.two) - Rows Removed by Filter: 2 - -> Index Scan using tenk1_unique1 on tenk1 t2_1 (actual rows=4 loops=N) - Index Cond: (unique1 < 4) -(13 rows) - --- And check we get the expected results. -SELECT COUNT(*),AVG(t2.t1two) FROM tenk1 t1 LEFT JOIN -LATERAL ( - SELECT t1.two as t1two, * FROM tenk1 t2 WHERE t2.unique1 < 4 OFFSET 0 -) t2 -ON t1.two = t2.two -WHERE t1.unique1 < 10; - count | avg --------+------------------------ - 20 | 0.50000000000000000000 -(1 row) - --- Try with LATERAL references within PlaceHolderVars -SELECT explain_memoize(' -SELECT COUNT(*), AVG(t1.twenty) FROM tenk1 t1 LEFT JOIN -LATERAL (SELECT t1.two+1 AS c1, t2.unique1 AS c2 FROM tenk1 t2) s ON TRUE -WHERE s.c1 = s.c2 AND t1.unique1 < 1000;', false); - explain_memoize -------------------------------------------------------------------------------------------- - Aggregate (actual rows=1 loops=N) - -> Nested Loop (actual rows=1000 loops=N) - -> Seq Scan on tenk1 t1 (actual rows=1000 loops=N) - Filter: (unique1 < 1000) - Rows Removed by Filter: 9000 - -> Memoize (actual rows=1 loops=N) - Cache Key: (t1.two + 1) - Cache Mode: binary - Hits: 998 Misses: 2 Evictions: Zero Overflows: 0 Memory Usage: NkB - -> Index Only Scan using tenk1_unique1 on tenk1 t2 (actual rows=1 loops=N) - Filter: ((t1.two + 1) = unique1) - Rows Removed by Filter: 9999 - Heap Fetches: N -(13 rows) - --- And check we get the expected results. -SELECT COUNT(*), AVG(t1.twenty) FROM tenk1 t1 LEFT JOIN -LATERAL (SELECT t1.two+1 AS c1, t2.unique1 AS c2 FROM tenk1 t2) s ON TRUE -WHERE s.c1 = s.c2 AND t1.unique1 < 1000; - count | avg --------+-------------------- - 1000 | 9.5000000000000000 -(1 row) - --- Ensure we do not omit the cache keys from PlaceHolderVars -SELECT explain_memoize(' -SELECT COUNT(*), AVG(t1.twenty) FROM tenk1 t1 LEFT JOIN -LATERAL (SELECT t1.twenty AS c1, t2.unique1 AS c2, t2.two FROM tenk1 t2) s -ON t1.two = s.two -WHERE s.c1 = s.c2 AND t1.unique1 < 1000;', false); - explain_memoize ---------------------------------------------------------------------------------------- - Aggregate (actual rows=1 loops=N) - -> Nested Loop (actual rows=1000 loops=N) - -> Seq Scan on tenk1 t1 (actual rows=1000 loops=N) - Filter: (unique1 < 1000) - Rows Removed by Filter: 9000 - -> Memoize (actual rows=1 loops=N) - Cache Key: t1.two, t1.twenty - Cache Mode: binary - Hits: 980 Misses: 20 Evictions: Zero Overflows: 0 Memory Usage: NkB - -> Seq Scan on tenk1 t2 (actual rows=1 loops=N) - Filter: ((t1.twenty = unique1) AND (t1.two = two)) - Rows Removed by Filter: 9999 -(12 rows) - --- And check we get the expected results. -SELECT COUNT(*), AVG(t1.twenty) FROM tenk1 t1 LEFT JOIN -LATERAL (SELECT t1.twenty AS c1, t2.unique1 AS c2, t2.two FROM tenk1 t2) s -ON t1.two = s.two -WHERE s.c1 = s.c2 AND t1.unique1 < 1000; - count | avg --------+-------------------- - 1000 | 9.5000000000000000 -(1 row) - -SET enable_mergejoin TO off; --- Test for varlena datatype with expr evaluation -CREATE TABLE expr_key (x numeric, t text); -INSERT INTO expr_key (x, t) -SELECT d1::numeric, d1::text FROM ( - SELECT round((d / pi())::numeric, 7) AS d1 FROM generate_series(1, 20) AS d -) t; --- duplicate rows so we get some cache hits -INSERT INTO expr_key SELECT * FROM expr_key; -CREATE INDEX expr_key_idx_x_t ON expr_key (x, t); -VACUUM ANALYZE expr_key; --- Ensure we get we get a cache miss and hit for each of the 20 distinct values -SELECT explain_memoize(' -SELECT * FROM expr_key t1 INNER JOIN expr_key t2 -ON t1.x = t2.t::numeric AND t1.t::numeric = t2.x;', false); - explain_memoize -------------------------------------------------------------------------------------------- - Nested Loop (actual rows=80 loops=N) - -> Seq Scan on expr_key t1 (actual rows=40 loops=N) - -> Memoize (actual rows=2 loops=N) - Cache Key: t1.x, (t1.t)::numeric - Cache Mode: logical - Hits: 20 Misses: 20 Evictions: Zero Overflows: 0 Memory Usage: NkB - -> Index Only Scan using expr_key_idx_x_t on expr_key t2 (actual rows=2 loops=N) - Index Cond: (x = (t1.t)::numeric) - Filter: (t1.x = (t)::numeric) - Heap Fetches: N -(10 rows) - -DROP TABLE expr_key; --- Reduce work_mem and hash_mem_multiplier so that we see some cache evictions -SET work_mem TO '64kB'; -SET hash_mem_multiplier TO 1.0; --- Ensure we get some evictions. We're unable to validate the hits and misses --- here as the number of entries that fit in the cache at once will vary --- between different machines. -SELECT explain_memoize(' -SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1 -INNER JOIN tenk1 t2 ON t1.unique1 = t2.thousand -WHERE t2.unique1 < 1200;', true); - explain_memoize -------------------------------------------------------------------------------------------- - Aggregate (actual rows=1 loops=N) - -> Nested Loop (actual rows=1200 loops=N) - -> Seq Scan on tenk1 t2 (actual rows=1200 loops=N) - Filter: (unique1 < 1200) - Rows Removed by Filter: 8800 - -> Memoize (actual rows=1 loops=N) - Cache Key: t2.thousand - Cache Mode: logical - Hits: N Misses: N Evictions: N Overflows: 0 Memory Usage: NkB - -> Index Only Scan using tenk1_unique1 on tenk1 t1 (actual rows=1 loops=N) - Index Cond: (unique1 = t2.thousand) - Heap Fetches: N -(12 rows) - -CREATE TABLE flt (f float); -CREATE INDEX flt_f_idx ON flt (f); -INSERT INTO flt VALUES('-0.0'::float),('+0.0'::float); -ANALYZE flt; -SET enable_seqscan TO off; --- Ensure memoize operates in logical mode -SELECT explain_memoize(' -SELECT * FROM flt f1 INNER JOIN flt f2 ON f1.f = f2.f;', false); - explain_memoize -------------------------------------------------------------------------------- - Nested Loop (actual rows=4 loops=N) - -> Index Only Scan using flt_f_idx on flt f1 (actual rows=2 loops=N) - Heap Fetches: N - -> Memoize (actual rows=2 loops=N) - Cache Key: f1.f - Cache Mode: logical - Hits: 1 Misses: 1 Evictions: Zero Overflows: 0 Memory Usage: NkB - -> Index Only Scan using flt_f_idx on flt f2 (actual rows=2 loops=N) - Index Cond: (f = f1.f) - Heap Fetches: N -(10 rows) - --- Ensure memoize operates in binary mode -SELECT explain_memoize(' -SELECT * FROM flt f1 INNER JOIN flt f2 ON f1.f >= f2.f;', false); - explain_memoize -------------------------------------------------------------------------------- - Nested Loop (actual rows=4 loops=N) - -> Index Only Scan using flt_f_idx on flt f1 (actual rows=2 loops=N) - Heap Fetches: N - -> Memoize (actual rows=2 loops=N) - Cache Key: f1.f - Cache Mode: binary - Hits: 0 Misses: 2 Evictions: Zero Overflows: 0 Memory Usage: NkB - -> Index Only Scan using flt_f_idx on flt f2 (actual rows=2 loops=N) - Index Cond: (f <= f1.f) - Heap Fetches: N -(10 rows) - -DROP TABLE flt; --- Exercise Memoize in binary mode with a large fixed width type and a --- varlena type. -CREATE TABLE strtest (n name, t text); -CREATE INDEX strtest_n_idx ON strtest (n); -CREATE INDEX strtest_t_idx ON strtest (t); -INSERT INTO strtest VALUES('one','one'),('two','two'),('three',repeat(fipshash('three'),100)); --- duplicate rows so we get some cache hits -INSERT INTO strtest SELECT * FROM strtest; -ANALYZE strtest; --- Ensure we get 3 hits and 3 misses -SELECT explain_memoize(' -SELECT * FROM strtest s1 INNER JOIN strtest s2 ON s1.n >= s2.n;', false); - explain_memoize ----------------------------------------------------------------------------------- - Nested Loop (actual rows=24 loops=N) - Disabled Nodes: 1 - -> Seq Scan on strtest s1 (actual rows=6 loops=N) - Disabled Nodes: 1 - -> Memoize (actual rows=4 loops=N) - Cache Key: s1.n - Cache Mode: binary - Hits: 3 Misses: 3 Evictions: Zero Overflows: 0 Memory Usage: NkB - -> Index Scan using strtest_n_idx on strtest s2 (actual rows=4 loops=N) - Index Cond: (n <= s1.n) -(10 rows) - --- Ensure we get 3 hits and 3 misses -SELECT explain_memoize(' -SELECT * FROM strtest s1 INNER JOIN strtest s2 ON s1.t >= s2.t;', false); - explain_memoize ----------------------------------------------------------------------------------- - Nested Loop (actual rows=24 loops=N) - Disabled Nodes: 1 - -> Seq Scan on strtest s1 (actual rows=6 loops=N) - Disabled Nodes: 1 - -> Memoize (actual rows=4 loops=N) - Cache Key: s1.t - Cache Mode: binary - Hits: 3 Misses: 3 Evictions: Zero Overflows: 0 Memory Usage: NkB - -> Index Scan using strtest_t_idx on strtest s2 (actual rows=4 loops=N) - Index Cond: (t <= s1.t) -(10 rows) - -DROP TABLE strtest; --- Ensure memoize works with partitionwise join -SET enable_partitionwise_join TO on; -CREATE TABLE prt (a int) PARTITION BY RANGE(a); -CREATE TABLE prt_p1 PARTITION OF prt FOR VALUES FROM (0) TO (10); -CREATE TABLE prt_p2 PARTITION OF prt FOR VALUES FROM (10) TO (20); -INSERT INTO prt VALUES (0), (0), (0), (0); -INSERT INTO prt VALUES (10), (10), (10), (10); -CREATE INDEX iprt_p1_a ON prt_p1 (a); -CREATE INDEX iprt_p2_a ON prt_p2 (a); -ANALYZE prt; -SELECT explain_memoize(' -SELECT * FROM prt t1 INNER JOIN prt t2 ON t1.a = t2.a;', false); - explain_memoize ------------------------------------------------------------------------------------------- - Append (actual rows=32 loops=N) - -> Nested Loop (actual rows=16 loops=N) - -> Index Only Scan using iprt_p1_a on prt_p1 t1_1 (actual rows=4 loops=N) - Heap Fetches: N - -> Memoize (actual rows=4 loops=N) - Cache Key: t1_1.a - Cache Mode: logical - Hits: 3 Misses: 1 Evictions: Zero Overflows: 0 Memory Usage: NkB - -> Index Only Scan using iprt_p1_a on prt_p1 t2_1 (actual rows=4 loops=N) - Index Cond: (a = t1_1.a) - Heap Fetches: N - -> Nested Loop (actual rows=16 loops=N) - -> Index Only Scan using iprt_p2_a on prt_p2 t1_2 (actual rows=4 loops=N) - Heap Fetches: N - -> Memoize (actual rows=4 loops=N) - Cache Key: t1_2.a - Cache Mode: logical - Hits: 3 Misses: 1 Evictions: Zero Overflows: 0 Memory Usage: NkB - -> Index Only Scan using iprt_p2_a on prt_p2 t2_2 (actual rows=4 loops=N) - Index Cond: (a = t1_2.a) - Heap Fetches: N -(21 rows) - --- Ensure memoize works with parameterized union-all Append path -SET enable_partitionwise_join TO off; -SELECT explain_memoize(' -SELECT * FROM prt_p1 t1 INNER JOIN -(SELECT * FROM prt_p1 UNION ALL SELECT * FROM prt_p2) t2 -ON t1.a = t2.a;', false); - explain_memoize -------------------------------------------------------------------------------------- - Nested Loop (actual rows=16 loops=N) - -> Index Only Scan using iprt_p1_a on prt_p1 t1 (actual rows=4 loops=N) - Heap Fetches: N - -> Memoize (actual rows=4 loops=N) - Cache Key: t1.a - Cache Mode: logical - Hits: 3 Misses: 1 Evictions: Zero Overflows: 0 Memory Usage: NkB - -> Append (actual rows=4 loops=N) - -> Index Only Scan using iprt_p1_a on prt_p1 (actual rows=4 loops=N) - Index Cond: (a = t1.a) - Heap Fetches: N - -> Index Only Scan using iprt_p2_a on prt_p2 (actual rows=0 loops=N) - Index Cond: (a = t1.a) - Heap Fetches: N -(14 rows) - -DROP TABLE prt; -RESET enable_partitionwise_join; --- Exercise Memoize code that flushes the cache when a parameter changes which --- is not part of the cache key. --- Ensure we get a Memoize plan -EXPLAIN (COSTS OFF) -SELECT unique1 FROM tenk1 t0 -WHERE unique1 < 3 - AND EXISTS ( - SELECT 1 FROM tenk1 t1 - INNER JOIN tenk1 t2 ON t1.unique1 = t2.hundred - WHERE t0.ten = t1.twenty AND t0.two <> t2.four OFFSET 0); - QUERY PLAN ----------------------------------------------------------------- - Index Scan using tenk1_unique1 on tenk1 t0 - Index Cond: (unique1 < 3) - Filter: EXISTS(SubPlan 1) - SubPlan 1 - -> Nested Loop - -> Index Scan using tenk1_hundred on tenk1 t2 - Filter: (t0.two <> four) - -> Memoize - Cache Key: t2.hundred - Cache Mode: logical - -> Index Scan using tenk1_unique1 on tenk1 t1 - Index Cond: (unique1 = t2.hundred) - Filter: (t0.ten = twenty) -(13 rows) - --- Ensure the above query returns the correct result -SELECT unique1 FROM tenk1 t0 -WHERE unique1 < 3 - AND EXISTS ( - SELECT 1 FROM tenk1 t1 - INNER JOIN tenk1 t2 ON t1.unique1 = t2.hundred - WHERE t0.ten = t1.twenty AND t0.two <> t2.four OFFSET 0); - unique1 ---------- - 2 -(1 row) - -RESET enable_seqscan; -RESET enable_mergejoin; -RESET work_mem; -RESET hash_mem_multiplier; -RESET enable_bitmapscan; -RESET enable_hashjoin; --- Test parallel plans with Memoize -SET min_parallel_table_scan_size TO 0; -SET parallel_setup_cost TO 0; -SET parallel_tuple_cost TO 0; -SET max_parallel_workers_per_gather TO 2; --- Ensure we get a parallel plan. -EXPLAIN (COSTS OFF) -SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1, -LATERAL (SELECT t2.unique1 FROM tenk1 t2 WHERE t1.twenty = t2.unique1) t2 -WHERE t1.unique1 < 1000; - QUERY PLAN -------------------------------------------------------------------------------- - Finalize Aggregate - -> Gather - Workers Planned: 2 - -> Partial Aggregate - -> Nested Loop - -> Parallel Bitmap Heap Scan on tenk1 t1 - Recheck Cond: (unique1 < 1000) - -> Bitmap Index Scan on tenk1_unique1 - Index Cond: (unique1 < 1000) - -> Memoize - Cache Key: t1.twenty - Cache Mode: logical - -> Index Only Scan using tenk1_unique1 on tenk1 t2 - Index Cond: (unique1 = t1.twenty) -(14 rows) - --- And ensure the parallel plan gives us the correct results. -SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1, -LATERAL (SELECT t2.unique1 FROM tenk1 t2 WHERE t1.twenty = t2.unique1) t2 -WHERE t1.unique1 < 1000; - count | avg --------+-------------------- - 1000 | 9.5000000000000000 -(1 row) - -RESET max_parallel_workers_per_gather; -RESET parallel_tuple_cost; -RESET parallel_setup_cost; -RESET min_parallel_table_scan_size; +psql: error: connection to server on socket "/tmp/bBabKAIw6z/.s.PGSQL.19533" failed: No such file or directory + Is the server running locally and accepting connections on that socket? diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/stats.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/stats.out --- /tmp/cirrus-ci-build/src/test/regress/expected/stats.out 2024-09-22 06:28:18.761312178 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/stats.out 2024-09-22 06:38:09.896217527 +0000 @@ -1,1649 +1,2 @@ --- --- Test cumulative stats system --- --- Must be run after tenk2 has been created (by create_table), --- populated (by create_misc) and indexed (by create_index). --- --- conditio sine qua non -SHOW track_counts; -- must be on - track_counts --------------- - on -(1 row) - --- ensure that both seqscan and indexscan plans are allowed -SET enable_seqscan TO on; -SET enable_indexscan TO on; --- for the moment, we don't want index-only scans here -SET enable_indexonlyscan TO off; --- not enabled by default, but we want to test it... -SET track_functions TO 'all'; --- record dboid for later use -SELECT oid AS dboid from pg_database where datname = current_database() \gset --- save counters -BEGIN; -SET LOCAL stats_fetch_consistency = snapshot; -CREATE TABLE prevstats AS -SELECT t.seq_scan, t.seq_tup_read, t.idx_scan, t.idx_tup_fetch, - (b.heap_blks_read + b.heap_blks_hit) AS heap_blks, - (b.idx_blks_read + b.idx_blks_hit) AS idx_blks, - pg_stat_get_snapshot_timestamp() as snap_ts - FROM pg_catalog.pg_stat_user_tables AS t, - pg_catalog.pg_statio_user_tables AS b - WHERE t.relname='tenk2' AND b.relname='tenk2'; -COMMIT; --- test effects of TRUNCATE on n_live_tup/n_dead_tup counters -CREATE TABLE trunc_stats_test(id serial); -CREATE TABLE trunc_stats_test1(id serial, stuff text); -CREATE TABLE trunc_stats_test2(id serial); -CREATE TABLE trunc_stats_test3(id serial, stuff text); -CREATE TABLE trunc_stats_test4(id serial); --- check that n_live_tup is reset to 0 after truncate -INSERT INTO trunc_stats_test DEFAULT VALUES; -INSERT INTO trunc_stats_test DEFAULT VALUES; -INSERT INTO trunc_stats_test DEFAULT VALUES; -TRUNCATE trunc_stats_test; --- test involving a truncate in a transaction; 4 ins but only 1 live -INSERT INTO trunc_stats_test1 DEFAULT VALUES; -INSERT INTO trunc_stats_test1 DEFAULT VALUES; -INSERT INTO trunc_stats_test1 DEFAULT VALUES; -UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); -DELETE FROM trunc_stats_test1 WHERE id = 3; -BEGIN; -UPDATE trunc_stats_test1 SET id = id + 100; -TRUNCATE trunc_stats_test1; -INSERT INTO trunc_stats_test1 DEFAULT VALUES; -COMMIT; --- use a savepoint: 1 insert, 1 live -BEGIN; -INSERT INTO trunc_stats_test2 DEFAULT VALUES; -INSERT INTO trunc_stats_test2 DEFAULT VALUES; -SAVEPOINT p1; -INSERT INTO trunc_stats_test2 DEFAULT VALUES; -TRUNCATE trunc_stats_test2; -INSERT INTO trunc_stats_test2 DEFAULT VALUES; -RELEASE SAVEPOINT p1; -COMMIT; --- rollback a savepoint: this should count 4 inserts and have 2 --- live tuples after commit (and 2 dead ones due to aborted subxact) -BEGIN; -INSERT INTO trunc_stats_test3 DEFAULT VALUES; -INSERT INTO trunc_stats_test3 DEFAULT VALUES; -SAVEPOINT p1; -INSERT INTO trunc_stats_test3 DEFAULT VALUES; -INSERT INTO trunc_stats_test3 DEFAULT VALUES; -TRUNCATE trunc_stats_test3; -INSERT INTO trunc_stats_test3 DEFAULT VALUES; -ROLLBACK TO SAVEPOINT p1; -COMMIT; --- rollback a truncate: this should count 2 inserts and produce 2 dead tuples -BEGIN; -INSERT INTO trunc_stats_test4 DEFAULT VALUES; -INSERT INTO trunc_stats_test4 DEFAULT VALUES; -TRUNCATE trunc_stats_test4; -INSERT INTO trunc_stats_test4 DEFAULT VALUES; -ROLLBACK; --- do a seqscan -SELECT count(*) FROM tenk2; - count -------- - 10000 -(1 row) - --- do an indexscan --- make sure it is not a bitmap scan, which might skip fetching heap tuples -SET enable_bitmapscan TO off; -SELECT count(*) FROM tenk2 WHERE unique1 = 1; - count -------- - 1 -(1 row) - -RESET enable_bitmapscan; --- ensure pending stats are flushed -SELECT pg_stat_force_next_flush(); - pg_stat_force_next_flush --------------------------- - -(1 row) - --- check effects -BEGIN; -SET LOCAL stats_fetch_consistency = snapshot; -SELECT relname, n_tup_ins, n_tup_upd, n_tup_del, n_live_tup, n_dead_tup - FROM pg_stat_user_tables - WHERE relname like 'trunc_stats_test%' order by relname; - relname | n_tup_ins | n_tup_upd | n_tup_del | n_live_tup | n_dead_tup --------------------+-----------+-----------+-----------+------------+------------ - trunc_stats_test | 3 | 0 | 0 | 0 | 0 - trunc_stats_test1 | 4 | 2 | 1 | 1 | 0 - trunc_stats_test2 | 1 | 0 | 0 | 1 | 0 - trunc_stats_test3 | 4 | 0 | 0 | 2 | 2 - trunc_stats_test4 | 2 | 0 | 0 | 0 | 2 -(5 rows) - -SELECT st.seq_scan >= pr.seq_scan + 1, - st.seq_tup_read >= pr.seq_tup_read + cl.reltuples, - st.idx_scan >= pr.idx_scan + 1, - st.idx_tup_fetch >= pr.idx_tup_fetch + 1 - FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr - WHERE st.relname='tenk2' AND cl.relname='tenk2'; - ?column? | ?column? | ?column? | ?column? -----------+----------+----------+---------- - t | t | t | t -(1 row) - -SELECT st.heap_blks_read + st.heap_blks_hit >= pr.heap_blks + cl.relpages, - st.idx_blks_read + st.idx_blks_hit >= pr.idx_blks + 1 - FROM pg_statio_user_tables AS st, pg_class AS cl, prevstats AS pr - WHERE st.relname='tenk2' AND cl.relname='tenk2'; - ?column? | ?column? -----------+---------- - t | t -(1 row) - -SELECT pr.snap_ts < pg_stat_get_snapshot_timestamp() as snapshot_newer -FROM prevstats AS pr; - snapshot_newer ----------------- - t -(1 row) - -COMMIT; ----- --- Basic tests for track_functions ---- -CREATE FUNCTION stats_test_func1() RETURNS VOID LANGUAGE plpgsql AS $$BEGIN END;$$; -SELECT 'stats_test_func1()'::regprocedure::oid AS stats_test_func1_oid \gset -CREATE FUNCTION stats_test_func2() RETURNS VOID LANGUAGE plpgsql AS $$BEGIN END;$$; -SELECT 'stats_test_func2()'::regprocedure::oid AS stats_test_func2_oid \gset --- test that stats are accumulated -BEGIN; -SET LOCAL stats_fetch_consistency = none; -SELECT pg_stat_get_function_calls(:stats_test_func1_oid); - pg_stat_get_function_calls ----------------------------- - -(1 row) - -SELECT pg_stat_get_xact_function_calls(:stats_test_func1_oid); - pg_stat_get_xact_function_calls ---------------------------------- - -(1 row) - -SELECT stats_test_func1(); - stats_test_func1 ------------------- - -(1 row) - -SELECT pg_stat_get_xact_function_calls(:stats_test_func1_oid); - pg_stat_get_xact_function_calls ---------------------------------- - 1 -(1 row) - -SELECT stats_test_func1(); - stats_test_func1 ------------------- - -(1 row) - -SELECT pg_stat_get_xact_function_calls(:stats_test_func1_oid); - pg_stat_get_xact_function_calls ---------------------------------- - 2 -(1 row) - -SELECT pg_stat_get_function_calls(:stats_test_func1_oid); - pg_stat_get_function_calls ----------------------------- - 0 -(1 row) - -COMMIT; --- Verify that function stats are not transactional --- rolled back savepoint in committing transaction -BEGIN; -SELECT stats_test_func2(); - stats_test_func2 ------------------- - -(1 row) - -SAVEPOINT foo; -SELECT stats_test_func2(); - stats_test_func2 ------------------- - -(1 row) - -ROLLBACK TO SAVEPOINT foo; -SELECT pg_stat_get_xact_function_calls(:stats_test_func2_oid); - pg_stat_get_xact_function_calls ---------------------------------- - 2 -(1 row) - -SELECT stats_test_func2(); - stats_test_func2 ------------------- - -(1 row) - -COMMIT; --- rolled back transaction -BEGIN; -SELECT stats_test_func2(); - stats_test_func2 ------------------- - -(1 row) - -ROLLBACK; -SELECT pg_stat_force_next_flush(); - pg_stat_force_next_flush --------------------------- - -(1 row) - --- check collected stats -SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func1_oid; - funcname | calls -------------------+------- - stats_test_func1 | 2 -(1 row) - -SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func2_oid; - funcname | calls -------------------+------- - stats_test_func2 | 4 -(1 row) - --- check that a rolled back drop function stats leaves stats alive -BEGIN; -SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func1_oid; - funcname | calls -------------------+------- - stats_test_func1 | 2 -(1 row) - -DROP FUNCTION stats_test_func1(); --- shouldn't be visible via view -SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func1_oid; - funcname | calls -----------+------- -(0 rows) - --- but still via oid access -SELECT pg_stat_get_function_calls(:stats_test_func1_oid); - pg_stat_get_function_calls ----------------------------- - 2 -(1 row) - -ROLLBACK; -SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func1_oid; - funcname | calls -------------------+------- - stats_test_func1 | 2 -(1 row) - -SELECT pg_stat_get_function_calls(:stats_test_func1_oid); - pg_stat_get_function_calls ----------------------------- - 2 -(1 row) - --- check that function dropped in main transaction leaves no stats behind -BEGIN; -DROP FUNCTION stats_test_func1(); -COMMIT; -SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func1_oid; - funcname | calls -----------+------- -(0 rows) - -SELECT pg_stat_get_function_calls(:stats_test_func1_oid); - pg_stat_get_function_calls ----------------------------- - -(1 row) - --- check that function dropped in a subtransaction leaves no stats behind -BEGIN; -SELECT stats_test_func2(); - stats_test_func2 ------------------- - -(1 row) - -SAVEPOINT a; -SELECT stats_test_func2(); - stats_test_func2 ------------------- - -(1 row) - -SAVEPOINT b; -DROP FUNCTION stats_test_func2(); -COMMIT; -SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func2_oid; - funcname | calls -----------+------- -(0 rows) - -SELECT pg_stat_get_function_calls(:stats_test_func2_oid); - pg_stat_get_function_calls ----------------------------- - -(1 row) - --- Check that stats for relations are dropped. For that we need to access stats --- by oid after the DROP TABLE. Save oids. -CREATE TABLE drop_stats_test(); -INSERT INTO drop_stats_test DEFAULT VALUES; -SELECT 'drop_stats_test'::regclass::oid AS drop_stats_test_oid \gset -CREATE TABLE drop_stats_test_xact(); -INSERT INTO drop_stats_test_xact DEFAULT VALUES; -SELECT 'drop_stats_test_xact'::regclass::oid AS drop_stats_test_xact_oid \gset -CREATE TABLE drop_stats_test_subxact(); -INSERT INTO drop_stats_test_subxact DEFAULT VALUES; -SELECT 'drop_stats_test_subxact'::regclass::oid AS drop_stats_test_subxact_oid \gset -SELECT pg_stat_force_next_flush(); - pg_stat_force_next_flush --------------------------- - -(1 row) - -SELECT pg_stat_get_live_tuples(:drop_stats_test_oid); - pg_stat_get_live_tuples -------------------------- - 1 -(1 row) - -DROP TABLE drop_stats_test; -SELECT pg_stat_get_live_tuples(:drop_stats_test_oid); - pg_stat_get_live_tuples -------------------------- - 0 -(1 row) - -SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid); - pg_stat_get_xact_tuples_inserted ----------------------------------- - 0 -(1 row) - --- check that rollback protects against having stats dropped and that local --- modifications don't pose a problem -SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid); - pg_stat_get_live_tuples -------------------------- - 1 -(1 row) - -SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid); - pg_stat_get_tuples_inserted ------------------------------ - 1 -(1 row) - -SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid); - pg_stat_get_xact_tuples_inserted ----------------------------------- - 0 -(1 row) - -BEGIN; -INSERT INTO drop_stats_test_xact DEFAULT VALUES; -SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid); - pg_stat_get_xact_tuples_inserted ----------------------------------- - 1 -(1 row) - -DROP TABLE drop_stats_test_xact; -SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid); - pg_stat_get_xact_tuples_inserted ----------------------------------- - 0 -(1 row) - -ROLLBACK; -SELECT pg_stat_force_next_flush(); - pg_stat_force_next_flush --------------------------- - -(1 row) - -SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid); - pg_stat_get_live_tuples -------------------------- - 1 -(1 row) - -SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid); - pg_stat_get_tuples_inserted ------------------------------ - 2 -(1 row) - --- transactional drop -SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid); - pg_stat_get_live_tuples -------------------------- - 1 -(1 row) - -SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid); - pg_stat_get_tuples_inserted ------------------------------ - 2 -(1 row) - -BEGIN; -INSERT INTO drop_stats_test_xact DEFAULT VALUES; -SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid); - pg_stat_get_xact_tuples_inserted ----------------------------------- - 1 -(1 row) - -DROP TABLE drop_stats_test_xact; -SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid); - pg_stat_get_xact_tuples_inserted ----------------------------------- - 0 -(1 row) - -COMMIT; -SELECT pg_stat_force_next_flush(); - pg_stat_force_next_flush --------------------------- - -(1 row) - -SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid); - pg_stat_get_live_tuples -------------------------- - 0 -(1 row) - -SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid); - pg_stat_get_tuples_inserted ------------------------------ - 0 -(1 row) - --- savepoint rollback (2 levels) -SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid); - pg_stat_get_live_tuples -------------------------- - 1 -(1 row) - -BEGIN; -INSERT INTO drop_stats_test_subxact DEFAULT VALUES; -SAVEPOINT sp1; -INSERT INTO drop_stats_test_subxact DEFAULT VALUES; -SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_subxact_oid); - pg_stat_get_xact_tuples_inserted ----------------------------------- - 2 -(1 row) - -SAVEPOINT sp2; -DROP TABLE drop_stats_test_subxact; -ROLLBACK TO SAVEPOINT sp2; -SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_subxact_oid); - pg_stat_get_xact_tuples_inserted ----------------------------------- - 2 -(1 row) - -COMMIT; -SELECT pg_stat_force_next_flush(); - pg_stat_force_next_flush --------------------------- - -(1 row) - -SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid); - pg_stat_get_live_tuples -------------------------- - 3 -(1 row) - --- savepoint rolback (1 level) -SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid); - pg_stat_get_live_tuples -------------------------- - 3 -(1 row) - -BEGIN; -SAVEPOINT sp1; -DROP TABLE drop_stats_test_subxact; -SAVEPOINT sp2; -ROLLBACK TO SAVEPOINT sp1; -COMMIT; -SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid); - pg_stat_get_live_tuples -------------------------- - 3 -(1 row) - --- and now actually drop -SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid); - pg_stat_get_live_tuples -------------------------- - 3 -(1 row) - -BEGIN; -SAVEPOINT sp1; -DROP TABLE drop_stats_test_subxact; -SAVEPOINT sp2; -RELEASE SAVEPOINT sp1; -COMMIT; -SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid); - pg_stat_get_live_tuples -------------------------- - 0 -(1 row) - -DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4; -DROP TABLE prevstats; ------ --- Test that last_seq_scan, last_idx_scan are correctly maintained --- --- Perform test using a temporary table. That way autovacuum etc won't --- interfere. To be able to check that timestamps increase, we sleep for 100ms --- between tests, assuming that there aren't systems with a coarser timestamp --- granularity. ------ -BEGIN; -CREATE TEMPORARY TABLE test_last_scan(idx_col int primary key, noidx_col int); -INSERT INTO test_last_scan(idx_col, noidx_col) VALUES(1, 1); -SELECT pg_stat_force_next_flush(); - pg_stat_force_next_flush --------------------------- - -(1 row) - -SELECT last_seq_scan, last_idx_scan FROM pg_stat_all_tables WHERE relid = 'test_last_scan'::regclass; - last_seq_scan | last_idx_scan ----------------+--------------- - | -(1 row) - -COMMIT; -SELECT pg_stat_reset_single_table_counters('test_last_scan'::regclass); - pg_stat_reset_single_table_counters -------------------------------------- - -(1 row) - -SELECT seq_scan, idx_scan FROM pg_stat_all_tables WHERE relid = 'test_last_scan'::regclass; - seq_scan | idx_scan -----------+---------- - 0 | 0 -(1 row) - --- ensure we start out with exactly one index and sequential scan -BEGIN; -SET LOCAL enable_seqscan TO on; -SET LOCAL enable_indexscan TO on; -SET LOCAL enable_bitmapscan TO off; -EXPLAIN (COSTS off) SELECT count(*) FROM test_last_scan WHERE noidx_col = 1; - QUERY PLAN ----------------------------------- - Aggregate - -> Seq Scan on test_last_scan - Filter: (noidx_col = 1) -(3 rows) - -SELECT count(*) FROM test_last_scan WHERE noidx_col = 1; - count -------- - 1 -(1 row) - -SET LOCAL enable_seqscan TO off; -EXPLAIN (COSTS off) SELECT count(*) FROM test_last_scan WHERE idx_col = 1; - QUERY PLAN --------------------------------------------------------------- - Aggregate - -> Index Scan using test_last_scan_pkey on test_last_scan - Index Cond: (idx_col = 1) -(3 rows) - -SELECT count(*) FROM test_last_scan WHERE idx_col = 1; - count -------- - 1 -(1 row) - -SELECT pg_stat_force_next_flush(); - pg_stat_force_next_flush --------------------------- - -(1 row) - -COMMIT; --- fetch timestamps from before the next test -SELECT last_seq_scan AS test_last_seq, last_idx_scan AS test_last_idx -FROM pg_stat_all_tables WHERE relid = 'test_last_scan'::regclass \gset -SELECT pg_sleep(0.1); -- assume a minimum timestamp granularity of 100ms - pg_sleep ----------- - -(1 row) - --- cause one sequential scan -BEGIN; -SET LOCAL enable_seqscan TO on; -SET LOCAL enable_indexscan TO off; -SET LOCAL enable_bitmapscan TO off; -EXPLAIN (COSTS off) SELECT count(*) FROM test_last_scan WHERE noidx_col = 1; - QUERY PLAN ----------------------------------- - Aggregate - -> Seq Scan on test_last_scan - Filter: (noidx_col = 1) -(3 rows) - -SELECT count(*) FROM test_last_scan WHERE noidx_col = 1; - count -------- - 1 -(1 row) - -SELECT pg_stat_force_next_flush(); - pg_stat_force_next_flush --------------------------- - -(1 row) - -COMMIT; --- check that just sequential scan stats were incremented -SELECT seq_scan, :'test_last_seq' < last_seq_scan AS seq_ok, idx_scan, :'test_last_idx' = last_idx_scan AS idx_ok -FROM pg_stat_all_tables WHERE relid = 'test_last_scan'::regclass; - seq_scan | seq_ok | idx_scan | idx_ok -----------+--------+----------+-------- - 2 | t | 1 | t -(1 row) - --- fetch timestamps from before the next test -SELECT last_seq_scan AS test_last_seq, last_idx_scan AS test_last_idx -FROM pg_stat_all_tables WHERE relid = 'test_last_scan'::regclass \gset -SELECT pg_sleep(0.1); - pg_sleep ----------- - -(1 row) - --- cause one index scan -BEGIN; -SET LOCAL enable_seqscan TO off; -SET LOCAL enable_indexscan TO on; -SET LOCAL enable_bitmapscan TO off; -EXPLAIN (COSTS off) SELECT count(*) FROM test_last_scan WHERE idx_col = 1; - QUERY PLAN --------------------------------------------------------------- - Aggregate - -> Index Scan using test_last_scan_pkey on test_last_scan - Index Cond: (idx_col = 1) -(3 rows) - -SELECT count(*) FROM test_last_scan WHERE idx_col = 1; - count -------- - 1 -(1 row) - -SELECT pg_stat_force_next_flush(); - pg_stat_force_next_flush --------------------------- - -(1 row) - -COMMIT; --- check that just index scan stats were incremented -SELECT seq_scan, :'test_last_seq' = last_seq_scan AS seq_ok, idx_scan, :'test_last_idx' < last_idx_scan AS idx_ok -FROM pg_stat_all_tables WHERE relid = 'test_last_scan'::regclass; - seq_scan | seq_ok | idx_scan | idx_ok -----------+--------+----------+-------- - 2 | t | 2 | t -(1 row) - --- fetch timestamps from before the next test -SELECT last_seq_scan AS test_last_seq, last_idx_scan AS test_last_idx -FROM pg_stat_all_tables WHERE relid = 'test_last_scan'::regclass \gset -SELECT pg_sleep(0.1); - pg_sleep ----------- - -(1 row) - --- cause one bitmap index scan -BEGIN; -SET LOCAL enable_seqscan TO off; -SET LOCAL enable_indexscan TO off; -SET LOCAL enable_bitmapscan TO on; -EXPLAIN (COSTS off) SELECT count(*) FROM test_last_scan WHERE idx_col = 1; - QUERY PLAN ------------------------------------------------------- - Aggregate - -> Bitmap Heap Scan on test_last_scan - Recheck Cond: (idx_col = 1) - -> Bitmap Index Scan on test_last_scan_pkey - Index Cond: (idx_col = 1) -(5 rows) - -SELECT count(*) FROM test_last_scan WHERE idx_col = 1; - count -------- - 1 -(1 row) - -SELECT pg_stat_force_next_flush(); - pg_stat_force_next_flush --------------------------- - -(1 row) - -COMMIT; --- check that just index scan stats were incremented -SELECT seq_scan, :'test_last_seq' = last_seq_scan AS seq_ok, idx_scan, :'test_last_idx' < last_idx_scan AS idx_ok -FROM pg_stat_all_tables WHERE relid = 'test_last_scan'::regclass; - seq_scan | seq_ok | idx_scan | idx_ok -----------+--------+----------+-------- - 2 | t | 3 | t -(1 row) - ------ --- Test reset of some stats for shared table ------ --- This updates the comment of the database currently in use in --- pg_shdescription with a fake value, then sets it back to its --- original value. -SELECT shobj_description(d.oid, 'pg_database') as description_before - FROM pg_database d WHERE datname = current_database() \gset --- force some stats in pg_shdescription. -BEGIN; -SELECT current_database() as datname \gset -COMMENT ON DATABASE :"datname" IS 'This is a test comment'; -SELECT pg_stat_force_next_flush(); - pg_stat_force_next_flush --------------------------- - -(1 row) - -COMMIT; --- check that the stats are reset. -SELECT (n_tup_ins + n_tup_upd) > 0 AS has_data FROM pg_stat_all_tables - WHERE relid = 'pg_shdescription'::regclass; - has_data ----------- - t -(1 row) - -SELECT pg_stat_reset_single_table_counters('pg_shdescription'::regclass); - pg_stat_reset_single_table_counters -------------------------------------- - -(1 row) - -SELECT (n_tup_ins + n_tup_upd) > 0 AS has_data FROM pg_stat_all_tables - WHERE relid = 'pg_shdescription'::regclass; - has_data ----------- - f -(1 row) - --- set back comment -\if :{?description_before} - COMMENT ON DATABASE :"datname" IS :'description_before'; -\else - COMMENT ON DATABASE :"datname" IS NULL; -\endif ------ --- Test that various stats views are being properly populated ------ --- Test that sessions is incremented when a new session is started in pg_stat_database -SELECT sessions AS db_stat_sessions FROM pg_stat_database WHERE datname = (SELECT current_database()) \gset -\c -SELECT pg_stat_force_next_flush(); - pg_stat_force_next_flush --------------------------- - -(1 row) - -SELECT sessions > :db_stat_sessions FROM pg_stat_database WHERE datname = (SELECT current_database()); - ?column? ----------- - t -(1 row) - --- Test pg_stat_checkpointer checkpointer-related stats, together with pg_stat_wal -SELECT num_requested AS rqst_ckpts_before FROM pg_stat_checkpointer \gset --- Test pg_stat_wal (and make a temp table so our temp schema exists) -SELECT wal_bytes AS wal_bytes_before FROM pg_stat_wal \gset -CREATE TEMP TABLE test_stats_temp AS SELECT 17; -DROP TABLE test_stats_temp; --- Checkpoint twice: The checkpointer reports stats after reporting completion --- of the checkpoint. But after a second checkpoint we'll see at least the --- results of the first. -CHECKPOINT; -CHECKPOINT; -SELECT num_requested > :rqst_ckpts_before FROM pg_stat_checkpointer; - ?column? ----------- - t -(1 row) - -SELECT wal_bytes > :wal_bytes_before FROM pg_stat_wal; - ?column? ----------- - t -(1 row) - --- Test pg_stat_get_backend_idset() and some allied functions. --- In particular, verify that their notion of backend ID matches --- our temp schema index. -SELECT (current_schemas(true))[1] = ('pg_temp_' || beid::text) AS match -FROM pg_stat_get_backend_idset() beid -WHERE pg_stat_get_backend_pid(beid) = pg_backend_pid(); - match -------- - t -(1 row) - ------ --- Test that resetting stats works for reset timestamp ------ --- Test that reset_slru with a specified SLRU works. -SELECT stats_reset AS slru_commit_ts_reset_ts FROM pg_stat_slru WHERE name = 'commit_timestamp' \gset -SELECT stats_reset AS slru_notify_reset_ts FROM pg_stat_slru WHERE name = 'notify' \gset -SELECT pg_stat_reset_slru('commit_timestamp'); - pg_stat_reset_slru --------------------- - -(1 row) - -SELECT stats_reset > :'slru_commit_ts_reset_ts'::timestamptz FROM pg_stat_slru WHERE name = 'commit_timestamp'; - ?column? ----------- - t -(1 row) - -SELECT stats_reset AS slru_commit_ts_reset_ts FROM pg_stat_slru WHERE name = 'commit_timestamp' \gset --- Test that multiple SLRUs are reset when no specific SLRU provided to reset function -SELECT pg_stat_reset_slru(); - pg_stat_reset_slru --------------------- - -(1 row) - -SELECT stats_reset > :'slru_commit_ts_reset_ts'::timestamptz FROM pg_stat_slru WHERE name = 'commit_timestamp'; - ?column? ----------- - t -(1 row) - -SELECT stats_reset > :'slru_notify_reset_ts'::timestamptz FROM pg_stat_slru WHERE name = 'notify'; - ?column? ----------- - t -(1 row) - --- Test that reset_shared with archiver specified as the stats type works -SELECT stats_reset AS archiver_reset_ts FROM pg_stat_archiver \gset -SELECT pg_stat_reset_shared('archiver'); - pg_stat_reset_shared ----------------------- - -(1 row) - -SELECT stats_reset > :'archiver_reset_ts'::timestamptz FROM pg_stat_archiver; - ?column? ----------- - t -(1 row) - --- Test that reset_shared with bgwriter specified as the stats type works -SELECT stats_reset AS bgwriter_reset_ts FROM pg_stat_bgwriter \gset -SELECT pg_stat_reset_shared('bgwriter'); - pg_stat_reset_shared ----------------------- - -(1 row) - -SELECT stats_reset > :'bgwriter_reset_ts'::timestamptz FROM pg_stat_bgwriter; - ?column? ----------- - t -(1 row) - --- Test that reset_shared with checkpointer specified as the stats type works -SELECT stats_reset AS checkpointer_reset_ts FROM pg_stat_checkpointer \gset -SELECT pg_stat_reset_shared('checkpointer'); - pg_stat_reset_shared ----------------------- - -(1 row) - -SELECT stats_reset > :'checkpointer_reset_ts'::timestamptz FROM pg_stat_checkpointer; - ?column? ----------- - t -(1 row) - --- Test that reset_shared with recovery_prefetch specified as the stats type works -SELECT stats_reset AS recovery_prefetch_reset_ts FROM pg_stat_recovery_prefetch \gset -SELECT pg_stat_reset_shared('recovery_prefetch'); - pg_stat_reset_shared ----------------------- - -(1 row) - -SELECT stats_reset > :'recovery_prefetch_reset_ts'::timestamptz FROM pg_stat_recovery_prefetch; - ?column? ----------- - t -(1 row) - --- Test that reset_shared with slru specified as the stats type works -SELECT max(stats_reset) AS slru_reset_ts FROM pg_stat_slru \gset -SELECT pg_stat_reset_shared('slru'); - pg_stat_reset_shared ----------------------- - -(1 row) - -SELECT max(stats_reset) > :'slru_reset_ts'::timestamptz FROM pg_stat_slru; - ?column? ----------- - t -(1 row) - --- Test that reset_shared with wal specified as the stats type works -SELECT stats_reset AS wal_reset_ts FROM pg_stat_wal \gset -SELECT pg_stat_reset_shared('wal'); - pg_stat_reset_shared ----------------------- - -(1 row) - -SELECT stats_reset > :'wal_reset_ts'::timestamptz FROM pg_stat_wal; - ?column? ----------- - t -(1 row) - --- Test error case for reset_shared with unknown stats type -SELECT pg_stat_reset_shared('unknown'); -ERROR: unrecognized reset target: "unknown" -HINT: Target must be "archiver", "bgwriter", "checkpointer", "io", "recovery_prefetch", "slru", or "wal". --- Test that reset works for pg_stat_database --- Since pg_stat_database stats_reset starts out as NULL, reset it once first so we have something to compare it to -SELECT pg_stat_reset(); - pg_stat_reset ---------------- - -(1 row) - -SELECT stats_reset AS db_reset_ts FROM pg_stat_database WHERE datname = (SELECT current_database()) \gset -SELECT pg_stat_reset(); - pg_stat_reset ---------------- - -(1 row) - -SELECT stats_reset > :'db_reset_ts'::timestamptz FROM pg_stat_database WHERE datname = (SELECT current_database()); - ?column? ----------- - t -(1 row) - ----- --- pg_stat_get_snapshot_timestamp behavior ----- -BEGIN; -SET LOCAL stats_fetch_consistency = snapshot; --- no snapshot yet, return NULL -SELECT pg_stat_get_snapshot_timestamp(); - pg_stat_get_snapshot_timestamp --------------------------------- - -(1 row) - --- any attempt at accessing stats will build snapshot -SELECT pg_stat_get_function_calls(0); - pg_stat_get_function_calls ----------------------------- - -(1 row) - -SELECT pg_stat_get_snapshot_timestamp() >= NOW(); - ?column? ----------- - t -(1 row) - --- shows NULL again after clearing -SELECT pg_stat_clear_snapshot(); - pg_stat_clear_snapshot ------------------------- - -(1 row) - -SELECT pg_stat_get_snapshot_timestamp(); - pg_stat_get_snapshot_timestamp --------------------------------- - -(1 row) - -COMMIT; ----- --- Changing stats_fetch_consistency in a transaction. ----- -BEGIN; --- Stats filled under the cache mode -SET LOCAL stats_fetch_consistency = cache; -SELECT pg_stat_get_function_calls(0); - pg_stat_get_function_calls ----------------------------- - -(1 row) - -SELECT pg_stat_get_snapshot_timestamp() IS NOT NULL AS snapshot_ok; - snapshot_ok -------------- - f -(1 row) - --- Success in accessing pre-existing snapshot data. -SET LOCAL stats_fetch_consistency = snapshot; -SELECT pg_stat_get_snapshot_timestamp() IS NOT NULL AS snapshot_ok; - snapshot_ok -------------- - f -(1 row) - -SELECT pg_stat_get_function_calls(0); - pg_stat_get_function_calls ----------------------------- - -(1 row) - -SELECT pg_stat_get_snapshot_timestamp() IS NOT NULL AS snapshot_ok; - snapshot_ok -------------- - t -(1 row) - --- Snapshot cleared. -SET LOCAL stats_fetch_consistency = none; -SELECT pg_stat_get_snapshot_timestamp() IS NOT NULL AS snapshot_ok; - snapshot_ok -------------- - f -(1 row) - -SELECT pg_stat_get_function_calls(0); - pg_stat_get_function_calls ----------------------------- - -(1 row) - -SELECT pg_stat_get_snapshot_timestamp() IS NOT NULL AS snapshot_ok; - snapshot_ok -------------- - f -(1 row) - -ROLLBACK; ----- --- pg_stat_have_stats behavior ----- --- fixed-numbered stats exist -SELECT pg_stat_have_stats('bgwriter', 0, 0); - pg_stat_have_stats --------------------- - t -(1 row) - --- unknown stats kinds error out -SELECT pg_stat_have_stats('zaphod', 0, 0); -ERROR: invalid statistics kind: "zaphod" --- db stats have objid 0 -SELECT pg_stat_have_stats('database', :dboid, 1); - pg_stat_have_stats --------------------- - f -(1 row) - -SELECT pg_stat_have_stats('database', :dboid, 0); - pg_stat_have_stats --------------------- - t -(1 row) - --- pg_stat_have_stats returns true for committed index creation -CREATE table stats_test_tab1 as select generate_series(1,10) a; -CREATE index stats_test_idx1 on stats_test_tab1(a); -SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset -SET enable_seqscan TO off; -select a from stats_test_tab1 where a = 3; - a ---- - 3 -(1 row) - -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); - pg_stat_have_stats --------------------- - t -(1 row) - --- pg_stat_have_stats returns false for dropped index with stats -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); - pg_stat_have_stats --------------------- - t -(1 row) - -DROP index stats_test_idx1; -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); - pg_stat_have_stats --------------------- - f -(1 row) - --- pg_stat_have_stats returns false for rolled back index creation -BEGIN; -CREATE index stats_test_idx1 on stats_test_tab1(a); -SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset -select a from stats_test_tab1 where a = 3; - a ---- - 3 -(1 row) - -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); - pg_stat_have_stats --------------------- - t -(1 row) - -ROLLBACK; -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); - pg_stat_have_stats --------------------- - f -(1 row) - --- pg_stat_have_stats returns true for reindex CONCURRENTLY -CREATE index stats_test_idx1 on stats_test_tab1(a); -SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset -select a from stats_test_tab1 where a = 3; - a ---- - 3 -(1 row) - -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); - pg_stat_have_stats --------------------- - t -(1 row) - -REINDEX index CONCURRENTLY stats_test_idx1; --- false for previous oid -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); - pg_stat_have_stats --------------------- - f -(1 row) - --- true for new oid -SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); - pg_stat_have_stats --------------------- - t -(1 row) - --- pg_stat_have_stats returns true for a rolled back drop index with stats -BEGIN; -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); - pg_stat_have_stats --------------------- - t -(1 row) - -DROP index stats_test_idx1; -ROLLBACK; -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); - pg_stat_have_stats --------------------- - t -(1 row) - --- put enable_seqscan back to on -SET enable_seqscan TO on; --- ensure that stats accessors handle NULL input correctly -SELECT pg_stat_get_replication_slot(NULL); - pg_stat_get_replication_slot ------------------------------- - -(1 row) - -SELECT pg_stat_get_subscription_stats(NULL); - pg_stat_get_subscription_stats --------------------------------- - -(1 row) - --- Test that the following operations are tracked in pg_stat_io: --- - reads of target blocks into shared buffers --- - writes of shared buffers to permanent storage --- - extends of relations using shared buffers --- - fsyncs done to ensure the durability of data dirtying shared buffers --- - shared buffer hits --- There is no test for blocks evicted from shared buffers, because we cannot --- be sure of the state of shared buffers at the point the test is run. --- Create a regular table and insert some data to generate IOCONTEXT_NORMAL --- extends. -SELECT sum(extends) AS io_sum_shared_before_extends - FROM pg_stat_io WHERE context = 'normal' AND object = 'relation' \gset -SELECT sum(writes) AS writes, sum(fsyncs) AS fsyncs - FROM pg_stat_io - WHERE object = 'relation' \gset io_sum_shared_before_ -CREATE TABLE test_io_shared(a int); -INSERT INTO test_io_shared SELECT i FROM generate_series(1,100)i; -SELECT pg_stat_force_next_flush(); - pg_stat_force_next_flush --------------------------- - -(1 row) - -SELECT sum(extends) AS io_sum_shared_after_extends - FROM pg_stat_io WHERE context = 'normal' AND object = 'relation' \gset -SELECT :io_sum_shared_after_extends > :io_sum_shared_before_extends; - ?column? ----------- - t -(1 row) - --- After a checkpoint, there should be some additional IOCONTEXT_NORMAL writes --- and fsyncs. --- See comment above for rationale for two explicit CHECKPOINTs. -CHECKPOINT; -CHECKPOINT; -SELECT sum(writes) AS writes, sum(fsyncs) AS fsyncs - FROM pg_stat_io - WHERE object = 'relation' \gset io_sum_shared_after_ -SELECT :io_sum_shared_after_writes > :io_sum_shared_before_writes; - ?column? ----------- - t -(1 row) - -SELECT current_setting('fsync') = 'off' - OR :io_sum_shared_after_fsyncs > :io_sum_shared_before_fsyncs; - ?column? ----------- - t -(1 row) - --- Change the tablespace so that the table is rewritten directly, then SELECT --- from it to cause it to be read back into shared buffers. -SELECT sum(reads) AS io_sum_shared_before_reads - FROM pg_stat_io WHERE context = 'normal' AND object = 'relation' \gset --- Do this in a transaction to prevent spurious failures due to concurrent accesses to our newly --- rewritten table, e.g. by autovacuum. -BEGIN; -ALTER TABLE test_io_shared SET TABLESPACE regress_tblspace; --- SELECT from the table so that the data is read into shared buffers and --- context 'normal', object 'relation' reads are counted. -SELECT COUNT(*) FROM test_io_shared; - count -------- - 100 -(1 row) - -COMMIT; -SELECT pg_stat_force_next_flush(); - pg_stat_force_next_flush --------------------------- - -(1 row) - -SELECT sum(reads) AS io_sum_shared_after_reads - FROM pg_stat_io WHERE context = 'normal' AND object = 'relation' \gset -SELECT :io_sum_shared_after_reads > :io_sum_shared_before_reads; - ?column? ----------- - t -(1 row) - -SELECT sum(hits) AS io_sum_shared_before_hits - FROM pg_stat_io WHERE context = 'normal' AND object = 'relation' \gset --- Select from the table again to count hits. --- Ensure we generate hits by forcing a nested loop self-join with no --- materialize node. The outer side's buffer will stay pinned, preventing its --- eviction, while we loop through the inner side and generate hits. -BEGIN; -SET LOCAL enable_nestloop TO on; SET LOCAL enable_mergejoin TO off; -SET LOCAL enable_hashjoin TO off; SET LOCAL enable_material TO off; --- ensure plan stays as we expect it to -EXPLAIN (COSTS OFF) SELECT COUNT(*) FROM test_io_shared t1 INNER JOIN test_io_shared t2 USING (a); - QUERY PLAN -------------------------------------------- - Aggregate - -> Nested Loop - Join Filter: (t1.a = t2.a) - -> Seq Scan on test_io_shared t1 - -> Seq Scan on test_io_shared t2 -(5 rows) - -SELECT COUNT(*) FROM test_io_shared t1 INNER JOIN test_io_shared t2 USING (a); - count -------- - 100 -(1 row) - -COMMIT; -SELECT pg_stat_force_next_flush(); - pg_stat_force_next_flush --------------------------- - -(1 row) - -SELECT sum(hits) AS io_sum_shared_after_hits - FROM pg_stat_io WHERE context = 'normal' AND object = 'relation' \gset -SELECT :io_sum_shared_after_hits > :io_sum_shared_before_hits; - ?column? ----------- - t -(1 row) - -DROP TABLE test_io_shared; --- Test that the follow IOCONTEXT_LOCAL IOOps are tracked in pg_stat_io: --- - eviction of local buffers in order to reuse them --- - reads of temporary table blocks into local buffers --- - writes of local buffers to permanent storage --- - extends of temporary tables --- Set temp_buffers to its minimum so that we can trigger writes with fewer --- inserted tuples. Do so in a new session in case temporary tables have been --- accessed by previous tests in this session. -\c -SET temp_buffers TO 100; -CREATE TEMPORARY TABLE test_io_local(a int, b TEXT); -SELECT sum(extends) AS extends, sum(evictions) AS evictions, sum(writes) AS writes - FROM pg_stat_io - WHERE context = 'normal' AND object = 'temp relation' \gset io_sum_local_before_ --- Insert tuples into the temporary table, generating extends in the stats. --- Insert enough values that we need to reuse and write out dirty local --- buffers, generating evictions and writes. -INSERT INTO test_io_local SELECT generate_series(1, 5000) as id, repeat('a', 200); --- Ensure the table is large enough to exceed our temp_buffers setting. -SELECT pg_relation_size('test_io_local') / current_setting('block_size')::int8 > 100; - ?column? ----------- - t -(1 row) - -SELECT sum(reads) AS io_sum_local_before_reads - FROM pg_stat_io WHERE context = 'normal' AND object = 'temp relation' \gset --- Read in evicted buffers, generating reads. -SELECT COUNT(*) FROM test_io_local; - count -------- - 5000 -(1 row) - -SELECT pg_stat_force_next_flush(); - pg_stat_force_next_flush --------------------------- - -(1 row) - -SELECT sum(evictions) AS evictions, - sum(reads) AS reads, - sum(writes) AS writes, - sum(extends) AS extends - FROM pg_stat_io - WHERE context = 'normal' AND object = 'temp relation' \gset io_sum_local_after_ -SELECT :io_sum_local_after_evictions > :io_sum_local_before_evictions, - :io_sum_local_after_reads > :io_sum_local_before_reads, - :io_sum_local_after_writes > :io_sum_local_before_writes, - :io_sum_local_after_extends > :io_sum_local_before_extends; - ?column? | ?column? | ?column? | ?column? -----------+----------+----------+---------- - t | t | t | t -(1 row) - --- Change the tablespaces so that the temporary table is rewritten to other --- local buffers, exercising a different codepath than standard local buffer --- writes. -ALTER TABLE test_io_local SET TABLESPACE regress_tblspace; -SELECT pg_stat_force_next_flush(); - pg_stat_force_next_flush --------------------------- - -(1 row) - -SELECT sum(writes) AS io_sum_local_new_tblspc_writes - FROM pg_stat_io WHERE context = 'normal' AND object = 'temp relation' \gset -SELECT :io_sum_local_new_tblspc_writes > :io_sum_local_after_writes; - ?column? ----------- - t -(1 row) - -RESET temp_buffers; --- Test that reuse of strategy buffers and reads of blocks into these reused --- buffers while VACUUMing are tracked in pg_stat_io. If there is sufficient --- demand for shared buffers from concurrent queries, some buffers may be --- pinned by other backends before they can be reused. In such cases, the --- backend will evict a buffer from outside the ring and add it to the --- ring. This is considered an eviction and not a reuse. --- Set wal_skip_threshold smaller than the expected size of --- test_io_vac_strategy so that, even if wal_level is minimal, VACUUM FULL will --- fsync the newly rewritten test_io_vac_strategy instead of writing it to WAL. --- Writing it to WAL will result in the newly written relation pages being in --- shared buffers -- preventing us from testing BAS_VACUUM BufferAccessStrategy --- reads. -SET wal_skip_threshold = '1 kB'; -SELECT sum(reuses) AS reuses, sum(reads) AS reads, sum(evictions) AS evictions - FROM pg_stat_io WHERE context = 'vacuum' \gset io_sum_vac_strategy_before_ -CREATE TABLE test_io_vac_strategy(a int, b int) WITH (autovacuum_enabled = 'false'); -INSERT INTO test_io_vac_strategy SELECT i, i from generate_series(1, 4500)i; --- Ensure that the next VACUUM will need to perform IO by rewriting the table --- first with VACUUM (FULL). -VACUUM (FULL) test_io_vac_strategy; --- Use the minimum BUFFER_USAGE_LIMIT to cause reuses or evictions with the --- smallest table possible. -VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) test_io_vac_strategy; -SELECT pg_stat_force_next_flush(); - pg_stat_force_next_flush --------------------------- - -(1 row) - -SELECT sum(reuses) AS reuses, sum(reads) AS reads, sum(evictions) AS evictions - FROM pg_stat_io WHERE context = 'vacuum' \gset io_sum_vac_strategy_after_ -SELECT :io_sum_vac_strategy_after_reads > :io_sum_vac_strategy_before_reads; - ?column? ----------- - t -(1 row) - -SELECT (:io_sum_vac_strategy_after_reuses + :io_sum_vac_strategy_after_evictions) > - (:io_sum_vac_strategy_before_reuses + :io_sum_vac_strategy_before_evictions); - ?column? ----------- - t -(1 row) - -RESET wal_skip_threshold; --- Test that extends done by a CTAS, which uses a BAS_BULKWRITE --- BufferAccessStrategy, are tracked in pg_stat_io. -SELECT sum(extends) AS io_sum_bulkwrite_strategy_extends_before - FROM pg_stat_io WHERE context = 'bulkwrite' \gset -CREATE TABLE test_io_bulkwrite_strategy AS SELECT i FROM generate_series(1,100)i; -SELECT pg_stat_force_next_flush(); - pg_stat_force_next_flush --------------------------- - -(1 row) - -SELECT sum(extends) AS io_sum_bulkwrite_strategy_extends_after - FROM pg_stat_io WHERE context = 'bulkwrite' \gset -SELECT :io_sum_bulkwrite_strategy_extends_after > :io_sum_bulkwrite_strategy_extends_before; - ?column? ----------- - t -(1 row) - --- Test IO stats reset -SELECT pg_stat_have_stats('io', 0, 0); - pg_stat_have_stats --------------------- - t -(1 row) - -SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS io_stats_pre_reset - FROM pg_stat_io \gset -SELECT pg_stat_reset_shared('io'); - pg_stat_reset_shared ----------------------- - -(1 row) - -SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS io_stats_post_reset - FROM pg_stat_io \gset -SELECT :io_stats_post_reset < :io_stats_pre_reset; - ?column? ----------- - t -(1 row) - --- test BRIN index doesn't block HOT update -CREATE TABLE brin_hot ( - id integer PRIMARY KEY, - val integer NOT NULL -) WITH (autovacuum_enabled = off, fillfactor = 70); -INSERT INTO brin_hot SELECT *, 0 FROM generate_series(1, 235); -CREATE INDEX val_brin ON brin_hot using brin(val); -CREATE FUNCTION wait_for_hot_stats() RETURNS void AS $$ -DECLARE - start_time timestamptz := clock_timestamp(); - updated bool; -BEGIN - -- we don't want to wait forever; loop will exit after 30 seconds - FOR i IN 1 .. 300 LOOP - SELECT (pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid) > 0) INTO updated; - EXIT WHEN updated; - - -- wait a little - PERFORM pg_sleep_for('100 milliseconds'); - -- reset stats snapshot so we can test again - PERFORM pg_stat_clear_snapshot(); - END LOOP; - -- report time waited in postmaster log (where it won't change test output) - RAISE log 'wait_for_hot_stats delayed % seconds', - EXTRACT(epoch FROM clock_timestamp() - start_time); -END -$$ LANGUAGE plpgsql; -UPDATE brin_hot SET val = -3 WHERE id = 42; --- We can't just call wait_for_hot_stats() at this point, because we only --- transmit stats when the session goes idle, and we probably didn't --- transmit the last couple of counts yet thanks to the rate-limiting logic --- in pgstat_report_stat(). But instead of waiting for the rate limiter's --- timeout to elapse, let's just start a new session. The old one will --- then send its stats before dying. -\c - -SELECT wait_for_hot_stats(); - wait_for_hot_stats --------------------- - -(1 row) - -SELECT pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid); - pg_stat_get_tuples_hot_updated --------------------------------- - 1 -(1 row) - -DROP TABLE brin_hot; -DROP FUNCTION wait_for_hot_stats(); --- Test handling of index predicates - updating attributes in precicates --- should not block HOT when summarizing indexes are involved. We update --- a row that was not indexed due to the index predicate, and becomes --- indexable - the HOT-updated tuple is forwarded to the BRIN index. -CREATE TABLE brin_hot_2 (a int, b int); -INSERT INTO brin_hot_2 VALUES (1, 100); -CREATE INDEX ON brin_hot_2 USING brin (b) WHERE a = 2; -UPDATE brin_hot_2 SET a = 2; -EXPLAIN (COSTS OFF) SELECT * FROM brin_hot_2 WHERE a = 2 AND b = 100; - QUERY PLAN ------------------------------------ - Seq Scan on brin_hot_2 - Filter: ((a = 2) AND (b = 100)) -(2 rows) - -SELECT COUNT(*) FROM brin_hot_2 WHERE a = 2 AND b = 100; - count -------- - 1 -(1 row) - -SET enable_seqscan = off; -EXPLAIN (COSTS OFF) SELECT * FROM brin_hot_2 WHERE a = 2 AND b = 100; - QUERY PLAN ---------------------------------------------- - Bitmap Heap Scan on brin_hot_2 - Recheck Cond: ((b = 100) AND (a = 2)) - -> Bitmap Index Scan on brin_hot_2_b_idx - Index Cond: (b = 100) -(4 rows) - -SELECT COUNT(*) FROM brin_hot_2 WHERE a = 2 AND b = 100; - count -------- - 1 -(1 row) - -DROP TABLE brin_hot_2; --- Test that updates to indexed columns are still propagated to the --- BRIN column. --- https://postgr.es/m/05ebcb44-f383-86e3-4f31-0a97a55634cf@enterprisedb.com -CREATE TABLE brin_hot_3 (a int, filler text) WITH (fillfactor = 10); -INSERT INTO brin_hot_3 SELECT 1, repeat(' ', 500) FROM generate_series(1, 20); -CREATE INDEX ON brin_hot_3 USING brin (a) WITH (pages_per_range = 1); -UPDATE brin_hot_3 SET a = 2; -EXPLAIN (COSTS OFF) SELECT * FROM brin_hot_3 WHERE a = 2; - QUERY PLAN ---------------------------------------------- - Bitmap Heap Scan on brin_hot_3 - Recheck Cond: (a = 2) - -> Bitmap Index Scan on brin_hot_3_a_idx - Index Cond: (a = 2) -(4 rows) - -SELECT COUNT(*) FROM brin_hot_3 WHERE a = 2; - count -------- - 20 -(1 row) - -DROP TABLE brin_hot_3; -SET enable_seqscan = on; --- End of Stats Test +psql: error: connection to server on socket "/tmp/bBabKAIw6z/.s.PGSQL.19533" failed: No such file or directory + Is the server running locally and accepting connections on that socket? diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/predicate.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/predicate.out --- /tmp/cirrus-ci-build/src/test/regress/expected/predicate.out 2024-09-22 06:28:18.741207977 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/predicate.out 2024-09-22 06:38:09.896217527 +0000 @@ -1,292 +1,2 @@ --- --- Tests for predicate handling --- --- --- Test that restrictions that are always true are ignored, and that are always --- false are replaced with constant-FALSE --- --- Currently we only check for NullTest quals and OR clauses that include --- NullTest quals. We may extend it in the future. --- -CREATE TABLE pred_tab (a int NOT NULL, b int, c int NOT NULL); --- --- Test restriction clauses --- --- Ensure the IS_NOT_NULL qual is ignored when the column is non-nullable -EXPLAIN (COSTS OFF) -SELECT * FROM pred_tab t WHERE t.a IS NOT NULL; - QUERY PLAN ------------------------- - Seq Scan on pred_tab t -(1 row) - --- Ensure the IS_NOT_NULL qual is not ignored on a nullable column -EXPLAIN (COSTS OFF) -SELECT * FROM pred_tab t WHERE t.b IS NOT NULL; - QUERY PLAN ---------------------------- - Seq Scan on pred_tab t - Filter: (b IS NOT NULL) -(2 rows) - --- Ensure the IS_NULL qual is reduced to constant-FALSE for non-nullable --- columns -EXPLAIN (COSTS OFF) -SELECT * FROM pred_tab t WHERE t.a IS NULL; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - --- Ensure the IS_NULL qual is not reduced to constant-FALSE on nullable --- columns -EXPLAIN (COSTS OFF) -SELECT * FROM pred_tab t WHERE t.b IS NULL; - QUERY PLAN ------------------------- - Seq Scan on pred_tab t - Filter: (b IS NULL) -(2 rows) - --- --- Tests for OR clauses in restriction clauses --- --- Ensure the OR clause is ignored when an OR branch is always true -EXPLAIN (COSTS OFF) -SELECT * FROM pred_tab t WHERE t.a IS NOT NULL OR t.b = 1; - QUERY PLAN ------------------------- - Seq Scan on pred_tab t -(1 row) - --- Ensure the OR clause is not ignored for NullTests that can't be proven --- always true -EXPLAIN (COSTS OFF) -SELECT * FROM pred_tab t WHERE t.b IS NOT NULL OR t.a = 1; - QUERY PLAN ----------------------------------------- - Seq Scan on pred_tab t - Filter: ((b IS NOT NULL) OR (a = 1)) -(2 rows) - --- Ensure the OR clause is reduced to constant-FALSE when all branches are --- provably false -EXPLAIN (COSTS OFF) -SELECT * FROM pred_tab t WHERE t.a IS NULL OR t.c IS NULL; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - --- Ensure the OR clause is not reduced to constant-FALSE when not all branches --- are provably false -EXPLAIN (COSTS OFF) -SELECT * FROM pred_tab t WHERE t.b IS NULL OR t.c IS NULL; - QUERY PLAN ----------------------------------------- - Seq Scan on pred_tab t - Filter: ((b IS NULL) OR (c IS NULL)) -(2 rows) - --- --- Test join clauses --- --- Ensure the IS_NOT_NULL qual is ignored, since a) it's on a NOT NULL column, --- and b) its Var is not nullable by any outer joins -EXPLAIN (COSTS OFF) -SELECT * FROM pred_tab t1 - LEFT JOIN pred_tab t2 ON TRUE - LEFT JOIN pred_tab t3 ON t2.a IS NOT NULL; - QUERY PLAN -------------------------------------------------- - Nested Loop Left Join - -> Seq Scan on pred_tab t1 - -> Materialize - -> Nested Loop Left Join - -> Seq Scan on pred_tab t2 - -> Materialize - -> Seq Scan on pred_tab t3 -(7 rows) - --- Ensure the IS_NOT_NULL qual is not ignored when columns are made nullable --- by an outer join -EXPLAIN (COSTS OFF) -SELECT * FROM pred_tab t1 - LEFT JOIN pred_tab t2 ON t1.a = 1 - LEFT JOIN pred_tab t3 ON t2.a IS NOT NULL; - QUERY PLAN -------------------------------------------- - Nested Loop Left Join - Join Filter: (t2.a IS NOT NULL) - -> Nested Loop Left Join - Join Filter: (t1.a = 1) - -> Seq Scan on pred_tab t1 - -> Materialize - -> Seq Scan on pred_tab t2 - -> Materialize - -> Seq Scan on pred_tab t3 -(9 rows) - --- Ensure the IS_NULL qual is reduced to constant-FALSE, since a) it's on a NOT --- NULL column, and b) its Var is not nullable by any outer joins -EXPLAIN (COSTS OFF) -SELECT * FROM pred_tab t1 - LEFT JOIN pred_tab t2 ON TRUE - LEFT JOIN pred_tab t3 ON t2.a IS NULL AND t2.b = 1; - QUERY PLAN ---------------------------------------------------- - Nested Loop Left Join - -> Seq Scan on pred_tab t1 - -> Materialize - -> Nested Loop Left Join - Join Filter: (false AND (t2.b = 1)) - -> Seq Scan on pred_tab t2 - -> Result - One-Time Filter: false -(8 rows) - --- Ensure the IS_NULL qual is not reduced to constant-FALSE when the column is --- nullable by an outer join -EXPLAIN (COSTS OFF) -SELECT * FROM pred_tab t1 - LEFT JOIN pred_tab t2 ON t1.a = 1 - LEFT JOIN pred_tab t3 ON t2.a IS NULL; - QUERY PLAN -------------------------------------------- - Nested Loop Left Join - Join Filter: (t2.a IS NULL) - -> Nested Loop Left Join - Join Filter: (t1.a = 1) - -> Seq Scan on pred_tab t1 - -> Materialize - -> Seq Scan on pred_tab t2 - -> Materialize - -> Seq Scan on pred_tab t3 -(9 rows) - --- --- Tests for OR clauses in join clauses --- --- Ensure the OR clause is ignored when an OR branch is provably always true -EXPLAIN (COSTS OFF) -SELECT * FROM pred_tab t1 - LEFT JOIN pred_tab t2 ON TRUE - LEFT JOIN pred_tab t3 ON t2.a IS NOT NULL OR t2.b = 1; - QUERY PLAN -------------------------------------------------- - Nested Loop Left Join - -> Seq Scan on pred_tab t1 - -> Materialize - -> Nested Loop Left Join - -> Seq Scan on pred_tab t2 - -> Materialize - -> Seq Scan on pred_tab t3 -(7 rows) - --- Ensure the NullTest is not ignored when the column is nullable by an outer --- join -EXPLAIN (COSTS OFF) -SELECT * FROM pred_tab t1 - LEFT JOIN pred_tab t2 ON t1.a = 1 - LEFT JOIN pred_tab t3 ON t2.a IS NOT NULL OR t2.b = 1; - QUERY PLAN ---------------------------------------------------- - Nested Loop Left Join - Join Filter: ((t2.a IS NOT NULL) OR (t2.b = 1)) - -> Nested Loop Left Join - Join Filter: (t1.a = 1) - -> Seq Scan on pred_tab t1 - -> Materialize - -> Seq Scan on pred_tab t2 - -> Materialize - -> Seq Scan on pred_tab t3 -(9 rows) - --- Ensure the OR clause is reduced to constant-FALSE when all OR branches are --- provably false -EXPLAIN (COSTS OFF) -SELECT * FROM pred_tab t1 - LEFT JOIN pred_tab t2 ON TRUE - LEFT JOIN pred_tab t3 ON (t2.a IS NULL OR t2.c IS NULL) AND t2.b = 1; - QUERY PLAN ---------------------------------------------------- - Nested Loop Left Join - -> Seq Scan on pred_tab t1 - -> Materialize - -> Nested Loop Left Join - Join Filter: (false AND (t2.b = 1)) - -> Seq Scan on pred_tab t2 - -> Result - One-Time Filter: false -(8 rows) - --- Ensure the OR clause is not reduced to constant-FALSE when a column is --- made nullable from an outer join -EXPLAIN (COSTS OFF) -SELECT * FROM pred_tab t1 - LEFT JOIN pred_tab t2 ON t1.a = 1 - LEFT JOIN pred_tab t3 ON t2.a IS NULL OR t2.c IS NULL; - QUERY PLAN ---------------------------------------------------- - Nested Loop Left Join - Join Filter: ((t2.a IS NULL) OR (t2.c IS NULL)) - -> Nested Loop Left Join - Join Filter: (t1.a = 1) - -> Seq Scan on pred_tab t1 - -> Materialize - -> Seq Scan on pred_tab t2 - -> Materialize - -> Seq Scan on pred_tab t3 -(9 rows) - -DROP TABLE pred_tab; --- Validate we handle IS NULL and IS NOT NULL quals correctly with inheritance --- parents. -CREATE TABLE pred_parent (a int); -CREATE TABLE pred_child () INHERITS (pred_parent); -ALTER TABLE ONLY pred_parent ALTER a SET NOT NULL; --- Ensure that the scan on pred_child contains the IS NOT NULL qual. -EXPLAIN (COSTS OFF) -SELECT * FROM pred_parent WHERE a IS NOT NULL; - QUERY PLAN ---------------------------------------------- - Append - -> Seq Scan on pred_parent pred_parent_1 - -> Seq Scan on pred_child pred_parent_2 - Filter: (a IS NOT NULL) -(4 rows) - --- Ensure we only scan pred_child and not pred_parent -EXPLAIN (COSTS OFF) -SELECT * FROM pred_parent WHERE a IS NULL; - QUERY PLAN ------------------------------------- - Seq Scan on pred_child pred_parent - Filter: (a IS NULL) -(2 rows) - -ALTER TABLE pred_parent ALTER a DROP NOT NULL; -ALTER TABLE pred_child ALTER a SET NOT NULL; --- Ensure the IS NOT NULL qual is removed from the pred_child scan. -EXPLAIN (COSTS OFF) -SELECT * FROM pred_parent WHERE a IS NOT NULL; - QUERY PLAN ---------------------------------------------- - Append - -> Seq Scan on pred_parent pred_parent_1 - Filter: (a IS NOT NULL) - -> Seq Scan on pred_child pred_parent_2 -(4 rows) - --- Ensure we only scan pred_parent and not pred_child -EXPLAIN (COSTS OFF) -SELECT * FROM pred_parent WHERE a IS NULL; - QUERY PLAN -------------------------- - Seq Scan on pred_parent - Filter: (a IS NULL) -(2 rows) - -DROP TABLE pred_parent, pred_child; +psql: error: connection to server on socket "/tmp/bBabKAIw6z/.s.PGSQL.19533" failed: No such file or directory + Is the server running locally and accepting connections on that socket? diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/oidjoins.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/oidjoins.out --- /tmp/cirrus-ci-build/src/test/regress/expected/oidjoins.out 2024-09-22 06:28:18.729145457 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/oidjoins.out 2024-09-22 06:38:09.976217351 +0000 @@ -1,272 +1,2 @@ --- --- Verify system catalog foreign key relationships --- -DO $doblock$ -declare - fk record; - nkeys integer; - cmd text; - err record; -begin - for fk in select * from pg_get_catalog_foreign_keys() - loop - raise notice 'checking % % => % %', - fk.fktable, fk.fkcols, fk.pktable, fk.pkcols; - nkeys := array_length(fk.fkcols, 1); - cmd := 'SELECT ctid'; - for i in 1 .. nkeys loop - cmd := cmd || ', ' || quote_ident(fk.fkcols[i]); - end loop; - if fk.is_array then - cmd := cmd || ' FROM (SELECT ctid'; - for i in 1 .. nkeys-1 loop - cmd := cmd || ', ' || quote_ident(fk.fkcols[i]); - end loop; - cmd := cmd || ', unnest(' || quote_ident(fk.fkcols[nkeys]); - cmd := cmd || ') as ' || quote_ident(fk.fkcols[nkeys]); - cmd := cmd || ' FROM ' || fk.fktable::text || ') fk WHERE '; - else - cmd := cmd || ' FROM ' || fk.fktable::text || ' fk WHERE '; - end if; - if fk.is_opt then - for i in 1 .. nkeys loop - cmd := cmd || quote_ident(fk.fkcols[i]) || ' != 0 AND '; - end loop; - end if; - cmd := cmd || 'NOT EXISTS(SELECT 1 FROM ' || fk.pktable::text || ' pk WHERE '; - for i in 1 .. nkeys loop - if i > 1 then cmd := cmd || ' AND '; end if; - cmd := cmd || 'pk.' || quote_ident(fk.pkcols[i]); - cmd := cmd || ' = fk.' || quote_ident(fk.fkcols[i]); - end loop; - cmd := cmd || ')'; - -- raise notice 'cmd = %', cmd; - for err in execute cmd loop - raise warning 'FK VIOLATION IN %(%): %', fk.fktable, fk.fkcols, err; - end loop; - end loop; -end -$doblock$; -NOTICE: checking pg_proc {pronamespace} => pg_namespace {oid} -NOTICE: checking pg_proc {proowner} => pg_authid {oid} -NOTICE: checking pg_proc {prolang} => pg_language {oid} -NOTICE: checking pg_proc {provariadic} => pg_type {oid} -NOTICE: checking pg_proc {prosupport} => pg_proc {oid} -NOTICE: checking pg_proc {prorettype} => pg_type {oid} -NOTICE: checking pg_proc {proargtypes} => pg_type {oid} -NOTICE: checking pg_proc {proallargtypes} => pg_type {oid} -NOTICE: checking pg_proc {protrftypes} => pg_type {oid} -NOTICE: checking pg_type {typnamespace} => pg_namespace {oid} -NOTICE: checking pg_type {typowner} => pg_authid {oid} -NOTICE: checking pg_type {typrelid} => pg_class {oid} -NOTICE: checking pg_type {typsubscript} => pg_proc {oid} -NOTICE: checking pg_type {typelem} => pg_type {oid} -NOTICE: checking pg_type {typarray} => pg_type {oid} -NOTICE: checking pg_type {typinput} => pg_proc {oid} -NOTICE: checking pg_type {typoutput} => pg_proc {oid} -NOTICE: checking pg_type {typreceive} => pg_proc {oid} -NOTICE: checking pg_type {typsend} => pg_proc {oid} -NOTICE: checking pg_type {typmodin} => pg_proc {oid} -NOTICE: checking pg_type {typmodout} => pg_proc {oid} -NOTICE: checking pg_type {typanalyze} => pg_proc {oid} -NOTICE: checking pg_type {typbasetype} => pg_type {oid} -NOTICE: checking pg_type {typcollation} => pg_collation {oid} -NOTICE: checking pg_attribute {attrelid} => pg_class {oid} -NOTICE: checking pg_attribute {atttypid} => pg_type {oid} -NOTICE: checking pg_attribute {attcollation} => pg_collation {oid} -NOTICE: checking pg_class {relnamespace} => pg_namespace {oid} -NOTICE: checking pg_class {reltype} => pg_type {oid} -NOTICE: checking pg_class {reloftype} => pg_type {oid} -NOTICE: checking pg_class {relowner} => pg_authid {oid} -NOTICE: checking pg_class {relam} => pg_am {oid} -NOTICE: checking pg_class {reltablespace} => pg_tablespace {oid} -NOTICE: checking pg_class {reltoastrelid} => pg_class {oid} -NOTICE: checking pg_class {relrewrite} => pg_class {oid} -NOTICE: checking pg_attrdef {adrelid} => pg_class {oid} -NOTICE: checking pg_attrdef {adrelid,adnum} => pg_attribute {attrelid,attnum} -NOTICE: checking pg_constraint {connamespace} => pg_namespace {oid} -NOTICE: checking pg_constraint {conrelid} => pg_class {oid} -NOTICE: checking pg_constraint {contypid} => pg_type {oid} -NOTICE: checking pg_constraint {conindid} => pg_class {oid} -NOTICE: checking pg_constraint {conparentid} => pg_constraint {oid} -NOTICE: checking pg_constraint {confrelid} => pg_class {oid} -NOTICE: checking pg_constraint {conpfeqop} => pg_operator {oid} -NOTICE: checking pg_constraint {conppeqop} => pg_operator {oid} -NOTICE: checking pg_constraint {conffeqop} => pg_operator {oid} -NOTICE: checking pg_constraint {conexclop} => pg_operator {oid} -NOTICE: checking pg_constraint {conrelid,conkey} => pg_attribute {attrelid,attnum} -NOTICE: checking pg_constraint {confrelid,confkey} => pg_attribute {attrelid,attnum} -NOTICE: checking pg_inherits {inhrelid} => pg_class {oid} -NOTICE: checking pg_inherits {inhparent} => pg_class {oid} -NOTICE: checking pg_index {indexrelid} => pg_class {oid} -NOTICE: checking pg_index {indrelid} => pg_class {oid} -NOTICE: checking pg_index {indcollation} => pg_collation {oid} -NOTICE: checking pg_index {indclass} => pg_opclass {oid} -NOTICE: checking pg_index {indrelid,indkey} => pg_attribute {attrelid,attnum} -NOTICE: checking pg_operator {oprnamespace} => pg_namespace {oid} -NOTICE: checking pg_operator {oprowner} => pg_authid {oid} -NOTICE: checking pg_operator {oprleft} => pg_type {oid} -NOTICE: checking pg_operator {oprright} => pg_type {oid} -NOTICE: checking pg_operator {oprresult} => pg_type {oid} -NOTICE: checking pg_operator {oprcom} => pg_operator {oid} -NOTICE: checking pg_operator {oprnegate} => pg_operator {oid} -NOTICE: checking pg_operator {oprcode} => pg_proc {oid} -NOTICE: checking pg_operator {oprrest} => pg_proc {oid} -NOTICE: checking pg_operator {oprjoin} => pg_proc {oid} -NOTICE: checking pg_opfamily {opfmethod} => pg_am {oid} -NOTICE: checking pg_opfamily {opfnamespace} => pg_namespace {oid} -NOTICE: checking pg_opfamily {opfowner} => pg_authid {oid} -NOTICE: checking pg_opclass {opcmethod} => pg_am {oid} -NOTICE: checking pg_opclass {opcnamespace} => pg_namespace {oid} -NOTICE: checking pg_opclass {opcowner} => pg_authid {oid} -NOTICE: checking pg_opclass {opcfamily} => pg_opfamily {oid} -NOTICE: checking pg_opclass {opcintype} => pg_type {oid} -NOTICE: checking pg_opclass {opckeytype} => pg_type {oid} -NOTICE: checking pg_am {amhandler} => pg_proc {oid} -NOTICE: checking pg_amop {amopfamily} => pg_opfamily {oid} -NOTICE: checking pg_amop {amoplefttype} => pg_type {oid} -NOTICE: checking pg_amop {amoprighttype} => pg_type {oid} -NOTICE: checking pg_amop {amopopr} => pg_operator {oid} -NOTICE: checking pg_amop {amopmethod} => pg_am {oid} -NOTICE: checking pg_amop {amopsortfamily} => pg_opfamily {oid} -NOTICE: checking pg_amproc {amprocfamily} => pg_opfamily {oid} -NOTICE: checking pg_amproc {amproclefttype} => pg_type {oid} -NOTICE: checking pg_amproc {amprocrighttype} => pg_type {oid} -NOTICE: checking pg_amproc {amproc} => pg_proc {oid} -NOTICE: checking pg_language {lanowner} => pg_authid {oid} -NOTICE: checking pg_language {lanplcallfoid} => pg_proc {oid} -NOTICE: checking pg_language {laninline} => pg_proc {oid} -NOTICE: checking pg_language {lanvalidator} => pg_proc {oid} -NOTICE: checking pg_largeobject_metadata {lomowner} => pg_authid {oid} -NOTICE: checking pg_largeobject {loid} => pg_largeobject_metadata {oid} -NOTICE: checking pg_aggregate {aggfnoid} => pg_proc {oid} -NOTICE: checking pg_aggregate {aggtransfn} => pg_proc {oid} -NOTICE: checking pg_aggregate {aggfinalfn} => pg_proc {oid} -NOTICE: checking pg_aggregate {aggcombinefn} => pg_proc {oid} -NOTICE: checking pg_aggregate {aggserialfn} => pg_proc {oid} -NOTICE: checking pg_aggregate {aggdeserialfn} => pg_proc {oid} -NOTICE: checking pg_aggregate {aggmtransfn} => pg_proc {oid} -NOTICE: checking pg_aggregate {aggminvtransfn} => pg_proc {oid} -NOTICE: checking pg_aggregate {aggmfinalfn} => pg_proc {oid} -NOTICE: checking pg_aggregate {aggsortop} => pg_operator {oid} -NOTICE: checking pg_aggregate {aggtranstype} => pg_type {oid} -NOTICE: checking pg_aggregate {aggmtranstype} => pg_type {oid} -NOTICE: checking pg_statistic {starelid} => pg_class {oid} -NOTICE: checking pg_statistic {staop1} => pg_operator {oid} -NOTICE: checking pg_statistic {staop2} => pg_operator {oid} -NOTICE: checking pg_statistic {staop3} => pg_operator {oid} -NOTICE: checking pg_statistic {staop4} => pg_operator {oid} -NOTICE: checking pg_statistic {staop5} => pg_operator {oid} -NOTICE: checking pg_statistic {stacoll1} => pg_collation {oid} -NOTICE: checking pg_statistic {stacoll2} => pg_collation {oid} -NOTICE: checking pg_statistic {stacoll3} => pg_collation {oid} -NOTICE: checking pg_statistic {stacoll4} => pg_collation {oid} -NOTICE: checking pg_statistic {stacoll5} => pg_collation {oid} -NOTICE: checking pg_statistic {starelid,staattnum} => pg_attribute {attrelid,attnum} -NOTICE: checking pg_statistic_ext {stxrelid} => pg_class {oid} -NOTICE: checking pg_statistic_ext {stxnamespace} => pg_namespace {oid} -NOTICE: checking pg_statistic_ext {stxowner} => pg_authid {oid} -NOTICE: checking pg_statistic_ext {stxrelid,stxkeys} => pg_attribute {attrelid,attnum} -NOTICE: checking pg_statistic_ext_data {stxoid} => pg_statistic_ext {oid} -NOTICE: checking pg_rewrite {ev_class} => pg_class {oid} -NOTICE: checking pg_trigger {tgrelid} => pg_class {oid} -NOTICE: checking pg_trigger {tgparentid} => pg_trigger {oid} -NOTICE: checking pg_trigger {tgfoid} => pg_proc {oid} -NOTICE: checking pg_trigger {tgconstrrelid} => pg_class {oid} -NOTICE: checking pg_trigger {tgconstrindid} => pg_class {oid} -NOTICE: checking pg_trigger {tgconstraint} => pg_constraint {oid} -NOTICE: checking pg_trigger {tgrelid,tgattr} => pg_attribute {attrelid,attnum} -NOTICE: checking pg_event_trigger {evtowner} => pg_authid {oid} -NOTICE: checking pg_event_trigger {evtfoid} => pg_proc {oid} -NOTICE: checking pg_description {classoid} => pg_class {oid} -NOTICE: checking pg_cast {castsource} => pg_type {oid} -NOTICE: checking pg_cast {casttarget} => pg_type {oid} -NOTICE: checking pg_cast {castfunc} => pg_proc {oid} -NOTICE: checking pg_enum {enumtypid} => pg_type {oid} -NOTICE: checking pg_namespace {nspowner} => pg_authid {oid} -NOTICE: checking pg_conversion {connamespace} => pg_namespace {oid} -NOTICE: checking pg_conversion {conowner} => pg_authid {oid} -NOTICE: checking pg_conversion {conproc} => pg_proc {oid} -NOTICE: checking pg_depend {classid} => pg_class {oid} -NOTICE: checking pg_depend {refclassid} => pg_class {oid} -NOTICE: checking pg_database {datdba} => pg_authid {oid} -NOTICE: checking pg_database {dattablespace} => pg_tablespace {oid} -NOTICE: checking pg_db_role_setting {setdatabase} => pg_database {oid} -NOTICE: checking pg_db_role_setting {setrole} => pg_authid {oid} -NOTICE: checking pg_tablespace {spcowner} => pg_authid {oid} -NOTICE: checking pg_auth_members {roleid} => pg_authid {oid} -NOTICE: checking pg_auth_members {member} => pg_authid {oid} -NOTICE: checking pg_auth_members {grantor} => pg_authid {oid} -NOTICE: checking pg_shdepend {dbid} => pg_database {oid} -NOTICE: checking pg_shdepend {classid} => pg_class {oid} -NOTICE: checking pg_shdepend {refclassid} => pg_class {oid} -NOTICE: checking pg_shdescription {classoid} => pg_class {oid} -NOTICE: checking pg_ts_config {cfgnamespace} => pg_namespace {oid} -NOTICE: checking pg_ts_config {cfgowner} => pg_authid {oid} -NOTICE: checking pg_ts_config {cfgparser} => pg_ts_parser {oid} -NOTICE: checking pg_ts_config_map {mapcfg} => pg_ts_config {oid} -NOTICE: checking pg_ts_config_map {mapdict} => pg_ts_dict {oid} -NOTICE: checking pg_ts_dict {dictnamespace} => pg_namespace {oid} -NOTICE: checking pg_ts_dict {dictowner} => pg_authid {oid} -NOTICE: checking pg_ts_dict {dicttemplate} => pg_ts_template {oid} -NOTICE: checking pg_ts_parser {prsnamespace} => pg_namespace {oid} -NOTICE: checking pg_ts_parser {prsstart} => pg_proc {oid} -NOTICE: checking pg_ts_parser {prstoken} => pg_proc {oid} -NOTICE: checking pg_ts_parser {prsend} => pg_proc {oid} -NOTICE: checking pg_ts_parser {prsheadline} => pg_proc {oid} -NOTICE: checking pg_ts_parser {prslextype} => pg_proc {oid} -NOTICE: checking pg_ts_template {tmplnamespace} => pg_namespace {oid} -NOTICE: checking pg_ts_template {tmplinit} => pg_proc {oid} -NOTICE: checking pg_ts_template {tmpllexize} => pg_proc {oid} -NOTICE: checking pg_extension {extowner} => pg_authid {oid} -NOTICE: checking pg_extension {extnamespace} => pg_namespace {oid} -NOTICE: checking pg_extension {extconfig} => pg_class {oid} -NOTICE: checking pg_foreign_data_wrapper {fdwowner} => pg_authid {oid} -NOTICE: checking pg_foreign_data_wrapper {fdwhandler} => pg_proc {oid} -NOTICE: checking pg_foreign_data_wrapper {fdwvalidator} => pg_proc {oid} -NOTICE: checking pg_foreign_server {srvowner} => pg_authid {oid} -NOTICE: checking pg_foreign_server {srvfdw} => pg_foreign_data_wrapper {oid} -NOTICE: checking pg_user_mapping {umuser} => pg_authid {oid} -NOTICE: checking pg_user_mapping {umserver} => pg_foreign_server {oid} -NOTICE: checking pg_foreign_table {ftrelid} => pg_class {oid} -NOTICE: checking pg_foreign_table {ftserver} => pg_foreign_server {oid} -NOTICE: checking pg_policy {polrelid} => pg_class {oid} -NOTICE: checking pg_policy {polroles} => pg_authid {oid} -NOTICE: checking pg_default_acl {defaclrole} => pg_authid {oid} -NOTICE: checking pg_default_acl {defaclnamespace} => pg_namespace {oid} -NOTICE: checking pg_init_privs {classoid} => pg_class {oid} -NOTICE: checking pg_seclabel {classoid} => pg_class {oid} -NOTICE: checking pg_shseclabel {classoid} => pg_class {oid} -NOTICE: checking pg_collation {collnamespace} => pg_namespace {oid} -NOTICE: checking pg_collation {collowner} => pg_authid {oid} -NOTICE: checking pg_partitioned_table {partrelid} => pg_class {oid} -NOTICE: checking pg_partitioned_table {partdefid} => pg_class {oid} -NOTICE: checking pg_partitioned_table {partclass} => pg_opclass {oid} -NOTICE: checking pg_partitioned_table {partcollation} => pg_collation {oid} -NOTICE: checking pg_partitioned_table {partrelid,partattrs} => pg_attribute {attrelid,attnum} -NOTICE: checking pg_range {rngtypid} => pg_type {oid} -NOTICE: checking pg_range {rngsubtype} => pg_type {oid} -NOTICE: checking pg_range {rngmultitypid} => pg_type {oid} -NOTICE: checking pg_range {rngcollation} => pg_collation {oid} -NOTICE: checking pg_range {rngsubopc} => pg_opclass {oid} -NOTICE: checking pg_range {rngcanonical} => pg_proc {oid} -NOTICE: checking pg_range {rngsubdiff} => pg_proc {oid} -NOTICE: checking pg_transform {trftype} => pg_type {oid} -NOTICE: checking pg_transform {trflang} => pg_language {oid} -NOTICE: checking pg_transform {trffromsql} => pg_proc {oid} -NOTICE: checking pg_transform {trftosql} => pg_proc {oid} -NOTICE: checking pg_sequence {seqrelid} => pg_class {oid} -NOTICE: checking pg_sequence {seqtypid} => pg_type {oid} -NOTICE: checking pg_publication {pubowner} => pg_authid {oid} -NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid} -NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid} -NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid} -NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid} -NOTICE: checking pg_subscription {subdbid} => pg_database {oid} -NOTICE: checking pg_subscription {subowner} => pg_authid {oid} -NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid} -NOTICE: checking pg_subscription_rel {srrelid} => pg_class {oid} -NOTICE: checking pg_variable {vartype} => pg_type {oid} -NOTICE: checking pg_variable {varnamespace} => pg_namespace {oid} -NOTICE: checking pg_variable {varowner} => pg_authid {oid} -NOTICE: checking pg_variable {varcollation} => pg_collation {oid} +psql: error: connection to server on socket "/tmp/bBabKAIw6z/.s.PGSQL.19533" failed: No such file or directory + Is the server running locally and accepting connections on that socket? diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/event_trigger.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/event_trigger.out --- /tmp/cirrus-ci-build/src/test/regress/expected/event_trigger.out 2024-09-22 06:28:18.692957897 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/event_trigger.out 2024-09-22 06:38:09.976217351 +0000 @@ -1,742 +1,2 @@ --- should fail, return type mismatch -create event trigger regress_event_trigger - on ddl_command_start - execute procedure pg_backend_pid(); -ERROR: function pg_backend_pid must return type event_trigger --- OK -create function test_event_trigger() returns event_trigger as $$ -BEGIN - RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag; -END -$$ language plpgsql; --- should fail, can't call it as a plain function -SELECT test_event_trigger(); -ERROR: trigger functions can only be called as triggers -CONTEXT: compilation of PL/pgSQL function "test_event_trigger" near line 1 --- should fail, event triggers cannot have declared arguments -create function test_event_trigger_arg(name text) -returns event_trigger as $$ BEGIN RETURN 1; END $$ language plpgsql; -ERROR: event trigger functions cannot have declared arguments -CONTEXT: compilation of PL/pgSQL function "test_event_trigger_arg" near line 1 --- should fail, SQL functions cannot be event triggers -create function test_event_trigger_sql() returns event_trigger as $$ -SELECT 1 $$ language sql; -ERROR: SQL functions cannot return type event_trigger --- should fail, no elephant_bootstrap entry point -create event trigger regress_event_trigger on elephant_bootstrap - execute procedure test_event_trigger(); -ERROR: unrecognized event name "elephant_bootstrap" --- OK -create event trigger regress_event_trigger on ddl_command_start - execute procedure test_event_trigger(); --- OK -create event trigger regress_event_trigger_end on ddl_command_end - execute function test_event_trigger(); --- should fail, food is not a valid filter variable -create event trigger regress_event_trigger2 on ddl_command_start - when food in ('sandwich') - execute procedure test_event_trigger(); -ERROR: unrecognized filter variable "food" --- should fail, sandwich is not a valid command tag -create event trigger regress_event_trigger2 on ddl_command_start - when tag in ('sandwich') - execute procedure test_event_trigger(); -ERROR: filter value "sandwich" not recognized for filter variable "tag" --- should fail, create skunkcabbage is not a valid command tag -create event trigger regress_event_trigger2 on ddl_command_start - when tag in ('create table', 'create skunkcabbage') - execute procedure test_event_trigger(); -ERROR: filter value "create skunkcabbage" not recognized for filter variable "tag" --- should fail, can't have event triggers on event triggers -create event trigger regress_event_trigger2 on ddl_command_start - when tag in ('DROP EVENT TRIGGER') - execute procedure test_event_trigger(); -ERROR: event triggers are not supported for DROP EVENT TRIGGER --- should fail, can't have event triggers on global objects -create event trigger regress_event_trigger2 on ddl_command_start - when tag in ('CREATE ROLE') - execute procedure test_event_trigger(); -ERROR: event triggers are not supported for CREATE ROLE --- should fail, can't have event triggers on global objects -create event trigger regress_event_trigger2 on ddl_command_start - when tag in ('CREATE DATABASE') - execute procedure test_event_trigger(); -ERROR: event triggers are not supported for CREATE DATABASE --- should fail, can't have event triggers on global objects -create event trigger regress_event_trigger2 on ddl_command_start - when tag in ('CREATE TABLESPACE') - execute procedure test_event_trigger(); -ERROR: event triggers are not supported for CREATE TABLESPACE --- should fail, can't have same filter variable twice -create event trigger regress_event_trigger2 on ddl_command_start - when tag in ('create table') and tag in ('CREATE FUNCTION') - execute procedure test_event_trigger(); -ERROR: filter variable "tag" specified more than once --- should fail, can't have arguments -create event trigger regress_event_trigger2 on ddl_command_start - execute procedure test_event_trigger('argument not allowed'); -ERROR: syntax error at or near "'argument not allowed'" -LINE 2: execute procedure test_event_trigger('argument not allowe... - ^ --- OK -create event trigger regress_event_trigger2 on ddl_command_start - when tag in ('create table', 'CREATE FUNCTION') - execute procedure test_event_trigger(); --- OK -comment on event trigger regress_event_trigger is 'test comment'; --- drop as non-superuser should fail -create role regress_evt_user; -set role regress_evt_user; -create event trigger regress_event_trigger_noperms on ddl_command_start - execute procedure test_event_trigger(); -ERROR: permission denied to create event trigger "regress_event_trigger_noperms" -HINT: Must be superuser to create an event trigger. -reset role; --- test enabling and disabling -alter event trigger regress_event_trigger disable; --- fires _trigger2 and _trigger_end should fire, but not _trigger -create table event_trigger_fire1 (a int); -NOTICE: test_event_trigger: ddl_command_start CREATE TABLE -NOTICE: test_event_trigger: ddl_command_end CREATE TABLE -alter event trigger regress_event_trigger enable; -set session_replication_role = replica; --- fires nothing -create table event_trigger_fire2 (a int); -alter event trigger regress_event_trigger enable replica; --- fires only _trigger -create table event_trigger_fire3 (a int); -NOTICE: test_event_trigger: ddl_command_start CREATE TABLE -alter event trigger regress_event_trigger enable always; --- fires only _trigger -create table event_trigger_fire4 (a int); -NOTICE: test_event_trigger: ddl_command_start CREATE TABLE -reset session_replication_role; --- fires all three -create table event_trigger_fire5 (a int); -NOTICE: test_event_trigger: ddl_command_start CREATE TABLE -NOTICE: test_event_trigger: ddl_command_start CREATE TABLE -NOTICE: test_event_trigger: ddl_command_end CREATE TABLE --- non-top-level command -create function f1() returns int -language plpgsql -as $$ -begin - create table event_trigger_fire6 (a int); - return 0; -end $$; -NOTICE: test_event_trigger: ddl_command_start CREATE FUNCTION -NOTICE: test_event_trigger: ddl_command_start CREATE FUNCTION -NOTICE: test_event_trigger: ddl_command_end CREATE FUNCTION -select f1(); -NOTICE: test_event_trigger: ddl_command_start CREATE TABLE -NOTICE: test_event_trigger: ddl_command_start CREATE TABLE -NOTICE: test_event_trigger: ddl_command_end CREATE TABLE - f1 ----- - 0 -(1 row) - --- non-top-level command -create procedure p1() -language plpgsql -as $$ -begin - create table event_trigger_fire7 (a int); -end $$; -NOTICE: test_event_trigger: ddl_command_start CREATE PROCEDURE -NOTICE: test_event_trigger: ddl_command_end CREATE PROCEDURE -call p1(); -NOTICE: test_event_trigger: ddl_command_start CREATE TABLE -NOTICE: test_event_trigger: ddl_command_start CREATE TABLE -NOTICE: test_event_trigger: ddl_command_end CREATE TABLE --- clean up -alter event trigger regress_event_trigger disable; -drop table event_trigger_fire2, event_trigger_fire3, event_trigger_fire4, event_trigger_fire5, event_trigger_fire6, event_trigger_fire7; -NOTICE: test_event_trigger: ddl_command_end DROP TABLE -drop routine f1(), p1(); -NOTICE: test_event_trigger: ddl_command_end DROP ROUTINE --- regress_event_trigger_end should fire on these commands -grant all on table event_trigger_fire1 to public; -NOTICE: test_event_trigger: ddl_command_end GRANT -comment on table event_trigger_fire1 is 'here is a comment'; -NOTICE: test_event_trigger: ddl_command_end COMMENT -revoke all on table event_trigger_fire1 from public; -NOTICE: test_event_trigger: ddl_command_end REVOKE -drop table event_trigger_fire1; -NOTICE: test_event_trigger: ddl_command_end DROP TABLE -create foreign data wrapper useless; -NOTICE: test_event_trigger: ddl_command_end CREATE FOREIGN DATA WRAPPER -create server useless_server foreign data wrapper useless; -NOTICE: test_event_trigger: ddl_command_end CREATE SERVER -create user mapping for regress_evt_user server useless_server; -NOTICE: test_event_trigger: ddl_command_end CREATE USER MAPPING -alter default privileges for role regress_evt_user - revoke delete on tables from regress_evt_user; -NOTICE: test_event_trigger: ddl_command_end ALTER DEFAULT PRIVILEGES --- alter owner to non-superuser should fail -alter event trigger regress_event_trigger owner to regress_evt_user; -ERROR: permission denied to change owner of event trigger "regress_event_trigger" -HINT: The owner of an event trigger must be a superuser. --- alter owner to superuser should work -alter role regress_evt_user superuser; -alter event trigger regress_event_trigger owner to regress_evt_user; --- should fail, name collision -alter event trigger regress_event_trigger rename to regress_event_trigger2; -ERROR: event trigger "regress_event_trigger2" already exists --- OK -alter event trigger regress_event_trigger rename to regress_event_trigger3; --- should fail, doesn't exist any more -drop event trigger regress_event_trigger; -ERROR: event trigger "regress_event_trigger" does not exist --- should fail, regress_evt_user owns some objects -drop role regress_evt_user; -ERROR: role "regress_evt_user" cannot be dropped because some objects depend on it -DETAIL: owner of event trigger regress_event_trigger3 -owner of user mapping for regress_evt_user on server useless_server -owner of default privileges on new relations belonging to role regress_evt_user --- cleanup before next test --- these are all OK; the second one should emit a NOTICE -drop event trigger if exists regress_event_trigger2; -drop event trigger if exists regress_event_trigger2; -NOTICE: event trigger "regress_event_trigger2" does not exist, skipping -drop event trigger regress_event_trigger3; -drop event trigger regress_event_trigger_end; --- test support for dropped objects -CREATE SCHEMA schema_one authorization regress_evt_user; -CREATE SCHEMA schema_two authorization regress_evt_user; -CREATE SCHEMA audit_tbls authorization regress_evt_user; -CREATE TEMP TABLE a_temp_tbl (); -SET SESSION AUTHORIZATION regress_evt_user; -CREATE TABLE schema_one.table_one(a int); -CREATE TABLE schema_one."table two"(a int); -CREATE TABLE schema_one.table_three(a int); -CREATE TABLE audit_tbls.schema_one_table_two(the_value text); -CREATE TABLE schema_two.table_two(a int); -CREATE TABLE schema_two.table_three(a int, b text); -CREATE TABLE audit_tbls.schema_two_table_three(the_value text); -CREATE OR REPLACE FUNCTION schema_two.add(int, int) RETURNS int LANGUAGE plpgsql - CALLED ON NULL INPUT - AS $$ BEGIN RETURN coalesce($1,0) + coalesce($2,0); END; $$; -CREATE AGGREGATE schema_two.newton - (BASETYPE = int, SFUNC = schema_two.add, STYPE = int); -RESET SESSION AUTHORIZATION; -CREATE TABLE undroppable_objs ( - object_type text, - object_identity text -); -INSERT INTO undroppable_objs VALUES -('table', 'schema_one.table_three'), -('table', 'audit_tbls.schema_two_table_three'); -CREATE TABLE dropped_objects ( - type text, - schema text, - object text -); --- This tests errors raised within event triggers; the one in audit_tbls --- uses 2nd-level recursive invocation via test_evtrig_dropped_objects(). -CREATE OR REPLACE FUNCTION undroppable() RETURNS event_trigger -LANGUAGE plpgsql AS $$ -DECLARE - obj record; -BEGIN - PERFORM 1 FROM pg_tables WHERE tablename = 'undroppable_objs'; - IF NOT FOUND THEN - RAISE NOTICE 'table undroppable_objs not found, skipping'; - RETURN; - END IF; - FOR obj IN - SELECT * FROM pg_event_trigger_dropped_objects() JOIN - undroppable_objs USING (object_type, object_identity) - LOOP - RAISE EXCEPTION 'object % of type % cannot be dropped', - obj.object_identity, obj.object_type; - END LOOP; -END; -$$; -CREATE EVENT TRIGGER undroppable ON sql_drop - EXECUTE PROCEDURE undroppable(); -CREATE OR REPLACE FUNCTION test_evtrig_dropped_objects() RETURNS event_trigger -LANGUAGE plpgsql AS $$ -DECLARE - obj record; -BEGIN - FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects() - LOOP - IF obj.object_type = 'table' THEN - EXECUTE format('DROP TABLE IF EXISTS audit_tbls.%I', - format('%s_%s', obj.schema_name, obj.object_name)); - END IF; - - INSERT INTO dropped_objects - (type, schema, object) VALUES - (obj.object_type, obj.schema_name, obj.object_identity); - END LOOP; -END -$$; -CREATE EVENT TRIGGER regress_event_trigger_drop_objects ON sql_drop - WHEN TAG IN ('drop table', 'drop function', 'drop view', - 'drop owned', 'drop schema', 'alter table') - EXECUTE PROCEDURE test_evtrig_dropped_objects(); -ALTER TABLE schema_one.table_one DROP COLUMN a; -DROP SCHEMA schema_one, schema_two CASCADE; -NOTICE: drop cascades to 7 other objects -DETAIL: drop cascades to table schema_two.table_two -drop cascades to table schema_two.table_three -drop cascades to function schema_two.add(integer,integer) -drop cascades to function schema_two.newton(integer) -drop cascades to table schema_one.table_one -drop cascades to table schema_one."table two" -drop cascades to table schema_one.table_three -NOTICE: table "schema_two_table_two" does not exist, skipping -NOTICE: table "audit_tbls_schema_two_table_three" does not exist, skipping -ERROR: object audit_tbls.schema_two_table_three of type table cannot be dropped -CONTEXT: PL/pgSQL function undroppable() line 14 at RAISE -SQL statement "DROP TABLE IF EXISTS audit_tbls.schema_two_table_three" -PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE -DELETE FROM undroppable_objs WHERE object_identity = 'audit_tbls.schema_two_table_three'; -DROP SCHEMA schema_one, schema_two CASCADE; -NOTICE: drop cascades to 7 other objects -DETAIL: drop cascades to table schema_two.table_two -drop cascades to table schema_two.table_three -drop cascades to function schema_two.add(integer,integer) -drop cascades to function schema_two.newton(integer) -drop cascades to table schema_one.table_one -drop cascades to table schema_one."table two" -drop cascades to table schema_one.table_three -NOTICE: table "schema_two_table_two" does not exist, skipping -NOTICE: table "audit_tbls_schema_two_table_three" does not exist, skipping -NOTICE: table "schema_one_table_one" does not exist, skipping -NOTICE: table "schema_one_table two" does not exist, skipping -NOTICE: table "schema_one_table_three" does not exist, skipping -ERROR: object schema_one.table_three of type table cannot be dropped -CONTEXT: PL/pgSQL function undroppable() line 14 at RAISE -DELETE FROM undroppable_objs WHERE object_identity = 'schema_one.table_three'; -DROP SCHEMA schema_one, schema_two CASCADE; -NOTICE: drop cascades to 7 other objects -DETAIL: drop cascades to table schema_two.table_two -drop cascades to table schema_two.table_three -drop cascades to function schema_two.add(integer,integer) -drop cascades to function schema_two.newton(integer) -drop cascades to table schema_one.table_one -drop cascades to table schema_one."table two" -drop cascades to table schema_one.table_three -NOTICE: table "schema_two_table_two" does not exist, skipping -NOTICE: table "audit_tbls_schema_two_table_three" does not exist, skipping -NOTICE: table "schema_one_table_one" does not exist, skipping -NOTICE: table "schema_one_table two" does not exist, skipping -NOTICE: table "schema_one_table_three" does not exist, skipping -SELECT * FROM dropped_objects WHERE schema IS NULL OR schema <> 'pg_toast'; - type | schema | object ---------------+------------+------------------------------------- - table column | schema_one | schema_one.table_one.a - schema | | schema_two - table | schema_two | schema_two.table_two - type | schema_two | schema_two.table_two - type | schema_two | schema_two.table_two[] - table | audit_tbls | audit_tbls.schema_two_table_three - type | audit_tbls | audit_tbls.schema_two_table_three - type | audit_tbls | audit_tbls.schema_two_table_three[] - table | schema_two | schema_two.table_three - type | schema_two | schema_two.table_three - type | schema_two | schema_two.table_three[] - function | schema_two | schema_two.add(integer,integer) - aggregate | schema_two | schema_two.newton(integer) - schema | | schema_one - table | schema_one | schema_one.table_one - type | schema_one | schema_one.table_one - type | schema_one | schema_one.table_one[] - table | schema_one | schema_one."table two" - type | schema_one | schema_one."table two" - type | schema_one | schema_one."table two"[] - table | schema_one | schema_one.table_three - type | schema_one | schema_one.table_three - type | schema_one | schema_one.table_three[] -(23 rows) - -DROP OWNED BY regress_evt_user; -NOTICE: schema "audit_tbls" does not exist, skipping -SELECT * FROM dropped_objects WHERE type = 'schema'; - type | schema | object ---------+--------+------------ - schema | | schema_two - schema | | schema_one - schema | | audit_tbls -(3 rows) - -DROP ROLE regress_evt_user; -DROP EVENT TRIGGER regress_event_trigger_drop_objects; -DROP EVENT TRIGGER undroppable; --- Event triggers on relations. -CREATE OR REPLACE FUNCTION event_trigger_report_dropped() - RETURNS event_trigger - LANGUAGE plpgsql -AS $$ -DECLARE r record; -BEGIN - FOR r IN SELECT * from pg_event_trigger_dropped_objects() - LOOP - IF NOT r.normal AND NOT r.original THEN - CONTINUE; - END IF; - RAISE NOTICE 'NORMAL: orig=% normal=% istemp=% type=% identity=% name=% args=%', - r.original, r.normal, r.is_temporary, r.object_type, - r.object_identity, r.address_names, r.address_args; - END LOOP; -END; $$; -CREATE EVENT TRIGGER regress_event_trigger_report_dropped ON sql_drop - EXECUTE PROCEDURE event_trigger_report_dropped(); -CREATE OR REPLACE FUNCTION event_trigger_report_end() - RETURNS event_trigger - LANGUAGE plpgsql -AS $$ -DECLARE r RECORD; -BEGIN - FOR r IN SELECT * FROM pg_event_trigger_ddl_commands() - LOOP - RAISE NOTICE 'END: command_tag=% type=% identity=%', - r.command_tag, r.object_type, r.object_identity; - END LOOP; -END; $$; -CREATE EVENT TRIGGER regress_event_trigger_report_end ON ddl_command_end - EXECUTE PROCEDURE event_trigger_report_end(); -CREATE SCHEMA evttrig - CREATE TABLE one (col_a SERIAL PRIMARY KEY, col_b text DEFAULT 'forty two', col_c SERIAL) - CREATE INDEX one_idx ON one (col_b) - CREATE TABLE two (col_c INTEGER CHECK (col_c > 0) REFERENCES one DEFAULT 42) - CREATE TABLE id (col_d int NOT NULL GENERATED ALWAYS AS IDENTITY); -NOTICE: END: command_tag=CREATE SCHEMA type=schema identity=evttrig -NOTICE: END: command_tag=CREATE SEQUENCE type=sequence identity=evttrig.one_col_a_seq -NOTICE: END: command_tag=CREATE SEQUENCE type=sequence identity=evttrig.one_col_c_seq -NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.one -NOTICE: END: command_tag=CREATE INDEX type=index identity=evttrig.one_pkey -NOTICE: END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_a_seq -NOTICE: END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_c_seq -NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.two -NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.two -NOTICE: END: command_tag=CREATE SEQUENCE type=sequence identity=evttrig.id_col_d_seq -NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.id -NOTICE: END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.id_col_d_seq -NOTICE: END: command_tag=CREATE INDEX type=index identity=evttrig.one_idx --- Partitioned tables with a partitioned index -CREATE TABLE evttrig.parted ( - id int PRIMARY KEY) - PARTITION BY RANGE (id); -NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.parted -NOTICE: END: command_tag=CREATE INDEX type=index identity=evttrig.parted_pkey -CREATE TABLE evttrig.part_1_10 PARTITION OF evttrig.parted (id) - FOR VALUES FROM (1) TO (10); -NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.part_1_10 -CREATE TABLE evttrig.part_10_20 PARTITION OF evttrig.parted (id) - FOR VALUES FROM (10) TO (20) PARTITION BY RANGE (id); -NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.part_10_20 -CREATE TABLE evttrig.part_10_15 PARTITION OF evttrig.part_10_20 (id) - FOR VALUES FROM (10) TO (15); -NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.part_10_15 -CREATE TABLE evttrig.part_15_20 PARTITION OF evttrig.part_10_20 (id) - FOR VALUES FROM (15) TO (20); -NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.part_15_20 -ALTER TABLE evttrig.two DROP COLUMN col_c; -NOTICE: NORMAL: orig=t normal=f istemp=f type=table column identity=evttrig.two.col_c name={evttrig,two,col_c} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=table constraint identity=two_col_c_check on evttrig.two name={evttrig,two,two_col_c_check} args={} -NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.two -ALTER TABLE evttrig.one ALTER COLUMN col_b DROP DEFAULT; -NOTICE: NORMAL: orig=t normal=f istemp=f type=default value identity=for evttrig.one.col_b name={evttrig,one,col_b} args={} -NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.one -ALTER TABLE evttrig.one DROP CONSTRAINT one_pkey; -NOTICE: NORMAL: orig=t normal=f istemp=f type=table constraint identity=one_pkey on evttrig.one name={evttrig,one,one_pkey} args={} -NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.one -ALTER TABLE evttrig.one DROP COLUMN col_c; -NOTICE: NORMAL: orig=t normal=f istemp=f type=table column identity=evttrig.one.col_c name={evttrig,one,col_c} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=default value identity=for evttrig.one.col_c name={evttrig,one,col_c} args={} -NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.one -ALTER TABLE evttrig.id ALTER COLUMN col_d SET DATA TYPE bigint; -NOTICE: END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.id_col_d_seq -NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.id -ALTER TABLE evttrig.id ALTER COLUMN col_d DROP IDENTITY, - ALTER COLUMN col_d SET DATA TYPE int; -NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.id -DROP INDEX evttrig.one_idx; -NOTICE: NORMAL: orig=t normal=f istemp=f type=index identity=evttrig.one_idx name={evttrig,one_idx} args={} -DROP SCHEMA evttrig CASCADE; -NOTICE: drop cascades to 4 other objects -DETAIL: drop cascades to table evttrig.one -drop cascades to table evttrig.two -drop cascades to table evttrig.id -drop cascades to table evttrig.parted -NOTICE: NORMAL: orig=t normal=f istemp=f type=schema identity=evttrig name={evttrig} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.one name={evttrig,one} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=sequence identity=evttrig.one_col_a_seq name={evttrig,one_col_a_seq} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=default value identity=for evttrig.one.col_a name={evttrig,one,col_a} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.two name={evttrig,two} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.id name={evttrig,id} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.parted name={evttrig,parted} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.part_1_10 name={evttrig,part_1_10} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.part_10_20 name={evttrig,part_10_20} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.part_10_15 name={evttrig,part_10_15} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.part_15_20 name={evttrig,part_15_20} args={} -DROP TABLE a_temp_tbl; -NOTICE: NORMAL: orig=t normal=f istemp=t type=table identity=pg_temp.a_temp_tbl name={pg_temp,a_temp_tbl} args={} --- CREATE OPERATOR CLASS without FAMILY clause should report --- both CREATE OPERATOR FAMILY and CREATE OPERATOR CLASS -CREATE OPERATOR CLASS evttrigopclass FOR TYPE int USING btree AS STORAGE int; -NOTICE: END: command_tag=CREATE OPERATOR FAMILY type=operator family identity=public.evttrigopclass USING btree -NOTICE: END: command_tag=CREATE OPERATOR CLASS type=operator class identity=public.evttrigopclass USING btree -DROP EVENT TRIGGER regress_event_trigger_report_dropped; -DROP EVENT TRIGGER regress_event_trigger_report_end; --- only allowed from within an event trigger function, should fail -select pg_event_trigger_table_rewrite_oid(); -ERROR: pg_event_trigger_table_rewrite_oid() can only be called in a table_rewrite event trigger function --- test Table Rewrite Event Trigger -CREATE OR REPLACE FUNCTION test_evtrig_no_rewrite() RETURNS event_trigger -LANGUAGE plpgsql AS $$ -BEGIN - RAISE EXCEPTION 'rewrites not allowed'; -END; -$$; -create event trigger no_rewrite_allowed on table_rewrite - execute procedure test_evtrig_no_rewrite(); -create table rewriteme (id serial primary key, foo float, bar timestamptz); -insert into rewriteme - select x * 1.001 from generate_series(1, 500) as t(x); -alter table rewriteme alter column foo type numeric; -ERROR: rewrites not allowed -CONTEXT: PL/pgSQL function test_evtrig_no_rewrite() line 3 at RAISE -alter table rewriteme add column baz int default 0; --- test with more than one reason to rewrite a single table -CREATE OR REPLACE FUNCTION test_evtrig_no_rewrite() RETURNS event_trigger -LANGUAGE plpgsql AS $$ -BEGIN - RAISE NOTICE 'Table ''%'' is being rewritten (reason = %)', - pg_event_trigger_table_rewrite_oid()::regclass, - pg_event_trigger_table_rewrite_reason(); -END; -$$; -alter table rewriteme - add column onemore int default 0, - add column another int default -1, - alter column foo type numeric(10,4); -NOTICE: Table 'rewriteme' is being rewritten (reason = 4) --- matview rewrite when changing access method -CREATE MATERIALIZED VIEW heapmv USING heap AS SELECT 1 AS a; -ALTER MATERIALIZED VIEW heapmv SET ACCESS METHOD heap2; -NOTICE: Table 'heapmv' is being rewritten (reason = 8) -DROP MATERIALIZED VIEW heapmv; --- shouldn't trigger a table_rewrite event -alter table rewriteme alter column foo type numeric(12,4); -begin; -set timezone to 'UTC'; -alter table rewriteme alter column bar type timestamp; -set timezone to '0'; -alter table rewriteme alter column bar type timestamptz; -set timezone to 'Europe/London'; -alter table rewriteme alter column bar type timestamp; -- does rewrite -NOTICE: Table 'rewriteme' is being rewritten (reason = 4) -rollback; --- typed tables are rewritten when their type changes. Don't emit table --- name, because firing order is not stable. -CREATE OR REPLACE FUNCTION test_evtrig_no_rewrite() RETURNS event_trigger -LANGUAGE plpgsql AS $$ -BEGIN - RAISE NOTICE 'Table is being rewritten (reason = %)', - pg_event_trigger_table_rewrite_reason(); -END; -$$; -create type rewritetype as (a int); -create table rewritemetoo1 of rewritetype; -create table rewritemetoo2 of rewritetype; -alter type rewritetype alter attribute a type text cascade; -NOTICE: Table is being rewritten (reason = 4) -NOTICE: Table is being rewritten (reason = 4) --- but this doesn't work -create table rewritemetoo3 (a rewritetype); -alter type rewritetype alter attribute a type varchar cascade; -ERROR: cannot alter type "rewritetype" because column "rewritemetoo3.a" uses it -drop table rewriteme; -drop event trigger no_rewrite_allowed; -drop function test_evtrig_no_rewrite(); --- Tests for REINDEX -CREATE OR REPLACE FUNCTION reindex_start_command() -RETURNS event_trigger AS $$ -BEGIN - RAISE NOTICE 'REINDEX START: % %', tg_event, tg_tag; -END; -$$ LANGUAGE plpgsql; -CREATE EVENT TRIGGER regress_reindex_start ON ddl_command_start - WHEN TAG IN ('REINDEX') - EXECUTE PROCEDURE reindex_start_command(); -CREATE FUNCTION reindex_end_command() -RETURNS event_trigger AS $$ -DECLARE - obj record; -BEGIN - FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands() - LOOP - RAISE NOTICE 'REINDEX END: command_tag=% type=% identity=%', - obj.command_tag, obj.object_type, obj.object_identity; - END LOOP; -END; -$$ LANGUAGE plpgsql; -CREATE EVENT TRIGGER regress_reindex_end ON ddl_command_end - WHEN TAG IN ('REINDEX') - EXECUTE PROCEDURE reindex_end_command(); --- Extra event to force the use of a snapshot. -CREATE FUNCTION reindex_end_command_snap() RETURNS EVENT_TRIGGER - AS $$ BEGIN PERFORM 1; END $$ LANGUAGE plpgsql; -CREATE EVENT TRIGGER regress_reindex_end_snap ON ddl_command_end - EXECUTE FUNCTION reindex_end_command_snap(); --- With simple relation -CREATE TABLE concur_reindex_tab (c1 int); -CREATE INDEX concur_reindex_ind ON concur_reindex_tab (c1); --- Both start and end triggers enabled. -REINDEX INDEX concur_reindex_ind; -NOTICE: REINDEX START: ddl_command_start REINDEX -NOTICE: REINDEX END: command_tag=REINDEX type=index identity=public.concur_reindex_ind -REINDEX TABLE concur_reindex_tab; -NOTICE: REINDEX START: ddl_command_start REINDEX -NOTICE: REINDEX END: command_tag=REINDEX type=index identity=public.concur_reindex_ind -REINDEX INDEX CONCURRENTLY concur_reindex_ind; -NOTICE: REINDEX START: ddl_command_start REINDEX -NOTICE: REINDEX END: command_tag=REINDEX type=index identity=public.concur_reindex_ind -REINDEX TABLE CONCURRENTLY concur_reindex_tab; -NOTICE: REINDEX START: ddl_command_start REINDEX -NOTICE: REINDEX END: command_tag=REINDEX type=index identity=public.concur_reindex_ind --- with start trigger disabled. -ALTER EVENT TRIGGER regress_reindex_start DISABLE; -REINDEX INDEX concur_reindex_ind; -NOTICE: REINDEX END: command_tag=REINDEX type=index identity=public.concur_reindex_ind -REINDEX INDEX CONCURRENTLY concur_reindex_ind; -NOTICE: REINDEX END: command_tag=REINDEX type=index identity=public.concur_reindex_ind --- without an index -DROP INDEX concur_reindex_ind; -REINDEX TABLE concur_reindex_tab; -NOTICE: table "concur_reindex_tab" has no indexes to reindex -REINDEX TABLE CONCURRENTLY concur_reindex_tab; -NOTICE: table "concur_reindex_tab" has no indexes that can be reindexed concurrently --- With a Schema -CREATE SCHEMA concur_reindex_schema; --- No indexes -REINDEX SCHEMA concur_reindex_schema; -REINDEX SCHEMA CONCURRENTLY concur_reindex_schema; -CREATE TABLE concur_reindex_schema.tab (a int); -CREATE INDEX ind ON concur_reindex_schema.tab (a); --- One index reported -REINDEX SCHEMA concur_reindex_schema; -NOTICE: REINDEX END: command_tag=REINDEX type=index identity=concur_reindex_schema.ind -REINDEX SCHEMA CONCURRENTLY concur_reindex_schema; -NOTICE: REINDEX END: command_tag=REINDEX type=index identity=concur_reindex_schema.ind --- One table on schema but no indexes -DROP INDEX concur_reindex_schema.ind; -REINDEX SCHEMA concur_reindex_schema; -REINDEX SCHEMA CONCURRENTLY concur_reindex_schema; -DROP SCHEMA concur_reindex_schema CASCADE; -NOTICE: drop cascades to table concur_reindex_schema.tab --- With a partitioned table, and nothing else. -CREATE TABLE concur_reindex_part (id int) PARTITION BY RANGE (id); -REINDEX TABLE concur_reindex_part; -REINDEX TABLE CONCURRENTLY concur_reindex_part; --- Partition that would be reindexed, still nothing. -CREATE TABLE concur_reindex_child PARTITION OF concur_reindex_part - FOR VALUES FROM (0) TO (10); -REINDEX TABLE concur_reindex_part; -REINDEX TABLE CONCURRENTLY concur_reindex_part; --- Now add some indexes. -CREATE INDEX concur_reindex_partidx ON concur_reindex_part (id); -REINDEX INDEX concur_reindex_partidx; -NOTICE: REINDEX END: command_tag=REINDEX type=index identity=public.concur_reindex_child_id_idx -REINDEX INDEX CONCURRENTLY concur_reindex_partidx; -NOTICE: REINDEX END: command_tag=REINDEX type=index identity=public.concur_reindex_child_id_idx -REINDEX TABLE concur_reindex_part; -NOTICE: REINDEX END: command_tag=REINDEX type=index identity=public.concur_reindex_child_id_idx -REINDEX TABLE CONCURRENTLY concur_reindex_part; -NOTICE: REINDEX END: command_tag=REINDEX type=index identity=public.concur_reindex_child_id_idx -DROP TABLE concur_reindex_part; --- Clean up -DROP EVENT TRIGGER regress_reindex_start; -DROP EVENT TRIGGER regress_reindex_end; -DROP EVENT TRIGGER regress_reindex_end_snap; -DROP FUNCTION reindex_end_command(); -DROP FUNCTION reindex_end_command_snap(); -DROP FUNCTION reindex_start_command(); -DROP TABLE concur_reindex_tab; --- test Row Security Event Trigger -RESET SESSION AUTHORIZATION; -CREATE TABLE event_trigger_test (a integer, b text); -CREATE OR REPLACE FUNCTION start_command() -RETURNS event_trigger AS $$ -BEGIN -RAISE NOTICE '% - ddl_command_start', tg_tag; -END; -$$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION end_command() -RETURNS event_trigger AS $$ -BEGIN -RAISE NOTICE '% - ddl_command_end', tg_tag; -END; -$$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION drop_sql_command() -RETURNS event_trigger AS $$ -BEGIN -RAISE NOTICE '% - sql_drop', tg_tag; -END; -$$ LANGUAGE plpgsql; -CREATE EVENT TRIGGER start_rls_command ON ddl_command_start - WHEN TAG IN ('CREATE POLICY', 'ALTER POLICY', 'DROP POLICY') EXECUTE PROCEDURE start_command(); -CREATE EVENT TRIGGER end_rls_command ON ddl_command_end - WHEN TAG IN ('CREATE POLICY', 'ALTER POLICY', 'DROP POLICY') EXECUTE PROCEDURE end_command(); -CREATE EVENT TRIGGER sql_drop_command ON sql_drop - WHEN TAG IN ('DROP POLICY') EXECUTE PROCEDURE drop_sql_command(); -CREATE POLICY p1 ON event_trigger_test USING (FALSE); -NOTICE: CREATE POLICY - ddl_command_start -NOTICE: CREATE POLICY - ddl_command_end -ALTER POLICY p1 ON event_trigger_test USING (TRUE); -NOTICE: ALTER POLICY - ddl_command_start -NOTICE: ALTER POLICY - ddl_command_end -ALTER POLICY p1 ON event_trigger_test RENAME TO p2; -NOTICE: ALTER POLICY - ddl_command_start -NOTICE: ALTER POLICY - ddl_command_end -DROP POLICY p2 ON event_trigger_test; -NOTICE: DROP POLICY - ddl_command_start -NOTICE: DROP POLICY - sql_drop -NOTICE: DROP POLICY - ddl_command_end --- Check the object addresses of all the event triggers. -SELECT - e.evtname, - pg_describe_object('pg_event_trigger'::regclass, e.oid, 0) as descr, - b.type, b.object_names, b.object_args, - pg_identify_object(a.classid, a.objid, a.objsubid) as ident - FROM pg_event_trigger as e, - LATERAL pg_identify_object_as_address('pg_event_trigger'::regclass, e.oid, 0) as b, - LATERAL pg_get_object_address(b.type, b.object_names, b.object_args) as a - ORDER BY e.evtname; - evtname | descr | type | object_names | object_args | ident --------------------+---------------------------------+---------------+---------------------+-------------+-------------------------------------------------------- - end_rls_command | event trigger end_rls_command | event trigger | {end_rls_command} | {} | ("event trigger",,end_rls_command,end_rls_command) - sql_drop_command | event trigger sql_drop_command | event trigger | {sql_drop_command} | {} | ("event trigger",,sql_drop_command,sql_drop_command) - start_rls_command | event trigger start_rls_command | event trigger | {start_rls_command} | {} | ("event trigger",,start_rls_command,start_rls_command) -(3 rows) - -DROP EVENT TRIGGER start_rls_command; -DROP EVENT TRIGGER end_rls_command; -DROP EVENT TRIGGER sql_drop_command; --- Check the GUC for disabling event triggers -CREATE FUNCTION test_event_trigger_guc() RETURNS event_trigger -LANGUAGE plpgsql AS $$ -DECLARE - obj record; -BEGIN - FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects() - LOOP - RAISE NOTICE '% dropped %', tg_tag, obj.object_type; - END LOOP; -END; -$$; -CREATE EVENT TRIGGER test_event_trigger_guc - ON sql_drop - WHEN TAG IN ('DROP POLICY') EXECUTE FUNCTION test_event_trigger_guc(); -SET event_triggers = 'on'; -CREATE POLICY pguc ON event_trigger_test USING (FALSE); -DROP POLICY pguc ON event_trigger_test; -NOTICE: DROP POLICY dropped policy -CREATE POLICY pguc ON event_trigger_test USING (FALSE); -SET event_triggers = 'off'; -DROP POLICY pguc ON event_trigger_test; +psql: error: connection to server on socket "/tmp/bBabKAIw6z/.s.PGSQL.19533" failed: No such file or directory + Is the server running locally and accepting connections on that socket? diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/event_trigger_login.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/event_trigger_login.out --- /tmp/cirrus-ci-build/src/test/regress/expected/event_trigger_login.out 2024-09-22 06:28:18.692957897 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/event_trigger_login.out 2024-09-22 06:38:10.004217291 +0000 @@ -1,39 +1,2 @@ --- Login event triggers -CREATE TABLE user_logins(id serial, who text); -GRANT SELECT ON user_logins TO public; -CREATE FUNCTION on_login_proc() RETURNS event_trigger AS $$ -BEGIN - INSERT INTO user_logins (who) VALUES (SESSION_USER); - RAISE NOTICE 'You are welcome!'; -END; -$$ LANGUAGE plpgsql; -CREATE EVENT TRIGGER on_login_trigger ON login EXECUTE PROCEDURE on_login_proc(); -ALTER EVENT TRIGGER on_login_trigger ENABLE ALWAYS; -\c -NOTICE: You are welcome! -SELECT COUNT(*) FROM user_logins; - count -------- - 1 -(1 row) - -\c -NOTICE: You are welcome! -SELECT COUNT(*) FROM user_logins; - count -------- - 2 -(1 row) - --- Check dathasloginevt in system catalog -SELECT dathasloginevt FROM pg_database WHERE datname= :'DBNAME'; - dathasloginevt ----------------- - t -(1 row) - --- Cleanup -DROP TABLE user_logins; -DROP EVENT TRIGGER on_login_trigger; -DROP FUNCTION on_login_proc(); -\c +psql: error: connection to server on socket "/tmp/bBabKAIw6z/.s.PGSQL.19533" failed: No such file or directory + Is the server running locally and accepting connections on that socket? diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/fast_default.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/fast_default.out --- /tmp/cirrus-ci-build/src/test/regress/expected/fast_default.out 2024-09-22 06:28:18.692957897 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/fast_default.out 2024-09-22 06:38:10.024217252 +0000 @@ -1,861 +1,2 @@ --- --- ALTER TABLE ADD COLUMN DEFAULT test --- -SET search_path = fast_default; -CREATE SCHEMA fast_default; -CREATE TABLE m(id OID); -INSERT INTO m VALUES (NULL::OID); -CREATE FUNCTION set(tabname name) RETURNS VOID -AS $$ -BEGIN - UPDATE m - SET id = (SELECT c.relfilenode - FROM pg_class AS c, pg_namespace AS s - WHERE c.relname = tabname - AND c.relnamespace = s.oid - AND s.nspname = 'fast_default'); -END; -$$ LANGUAGE 'plpgsql'; -CREATE FUNCTION comp() RETURNS TEXT -AS $$ -BEGIN - RETURN (SELECT CASE - WHEN m.id = c.relfilenode THEN 'Unchanged' - ELSE 'Rewritten' - END - FROM m, pg_class AS c, pg_namespace AS s - WHERE c.relname = 't' - AND c.relnamespace = s.oid - AND s.nspname = 'fast_default'); -END; -$$ LANGUAGE 'plpgsql'; -CREATE FUNCTION log_rewrite() RETURNS event_trigger -LANGUAGE plpgsql as -$func$ - -declare - this_schema text; -begin - select into this_schema relnamespace::regnamespace::text - from pg_class - where oid = pg_event_trigger_table_rewrite_oid(); - if this_schema = 'fast_default' - then - RAISE NOTICE 'rewriting table % for reason %', - pg_event_trigger_table_rewrite_oid()::regclass, - pg_event_trigger_table_rewrite_reason(); - end if; -end; -$func$; -CREATE TABLE has_volatile AS -SELECT * FROM generate_series(1,10) id; -CREATE EVENT TRIGGER has_volatile_rewrite - ON table_rewrite - EXECUTE PROCEDURE log_rewrite(); --- only the last of these should trigger a rewrite -ALTER TABLE has_volatile ADD col1 int; -ALTER TABLE has_volatile ADD col2 int DEFAULT 1; -ALTER TABLE has_volatile ADD col3 timestamptz DEFAULT current_timestamp; -ALTER TABLE has_volatile ADD col4 int DEFAULT (random() * 10000)::int; -NOTICE: rewriting table has_volatile for reason 2 --- Test a large sample of different datatypes -CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT 1); -SELECT set('t'); - set ------ - -(1 row) - -INSERT INTO T VALUES (1), (2); -ALTER TABLE T ADD COLUMN c_bpchar BPCHAR(5) DEFAULT 'hello', - ALTER COLUMN c_int SET DEFAULT 2; -INSERT INTO T VALUES (3), (4); -ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'world', - ALTER COLUMN c_bpchar SET DEFAULT 'dog'; -INSERT INTO T VALUES (5), (6); -ALTER TABLE T ADD COLUMN c_date DATE DEFAULT '2016-06-02', - ALTER COLUMN c_text SET DEFAULT 'cat'; -INSERT INTO T VALUES (7), (8); -ALTER TABLE T ADD COLUMN c_timestamp TIMESTAMP DEFAULT '2016-09-01 12:00:00', - ADD COLUMN c_timestamp_null TIMESTAMP, - ALTER COLUMN c_date SET DEFAULT '2010-01-01'; -INSERT INTO T VALUES (9), (10); -ALTER TABLE T ADD COLUMN c_array TEXT[] - DEFAULT '{"This", "is", "the", "real", "world"}', - ALTER COLUMN c_timestamp SET DEFAULT '1970-12-31 11:12:13', - ALTER COLUMN c_timestamp_null SET DEFAULT '2016-09-29 12:00:00'; -INSERT INTO T VALUES (11), (12); -ALTER TABLE T ADD COLUMN c_small SMALLINT DEFAULT -5, - ADD COLUMN c_small_null SMALLINT, - ALTER COLUMN c_array - SET DEFAULT '{"This", "is", "no", "fantasy"}'; -INSERT INTO T VALUES (13), (14); -ALTER TABLE T ADD COLUMN c_big BIGINT DEFAULT 180000000000018, - ALTER COLUMN c_small SET DEFAULT 9, - ALTER COLUMN c_small_null SET DEFAULT 13; -INSERT INTO T VALUES (15), (16); -ALTER TABLE T ADD COLUMN c_num NUMERIC DEFAULT 1.00000000001, - ALTER COLUMN c_big SET DEFAULT -9999999999999999; -INSERT INTO T VALUES (17), (18); -ALTER TABLE T ADD COLUMN c_time TIME DEFAULT '12:00:00', - ALTER COLUMN c_num SET DEFAULT 2.000000000000002; -INSERT INTO T VALUES (19), (20); -ALTER TABLE T ADD COLUMN c_interval INTERVAL DEFAULT '1 day', - ALTER COLUMN c_time SET DEFAULT '23:59:59'; -INSERT INTO T VALUES (21), (22); -ALTER TABLE T ADD COLUMN c_hugetext TEXT DEFAULT repeat('abcdefg',1000), - ALTER COLUMN c_interval SET DEFAULT '3 hours'; -INSERT INTO T VALUES (23), (24); -ALTER TABLE T ALTER COLUMN c_interval DROP DEFAULT, - ALTER COLUMN c_hugetext SET DEFAULT repeat('poiuyt', 1000); -INSERT INTO T VALUES (25), (26); -ALTER TABLE T ALTER COLUMN c_bpchar DROP DEFAULT, - ALTER COLUMN c_date DROP DEFAULT, - ALTER COLUMN c_text DROP DEFAULT, - ALTER COLUMN c_timestamp DROP DEFAULT, - ALTER COLUMN c_array DROP DEFAULT, - ALTER COLUMN c_small DROP DEFAULT, - ALTER COLUMN c_big DROP DEFAULT, - ALTER COLUMN c_num DROP DEFAULT, - ALTER COLUMN c_time DROP DEFAULT, - ALTER COLUMN c_hugetext DROP DEFAULT; -INSERT INTO T VALUES (27), (28); -SELECT pk, c_int, c_bpchar, c_text, c_date, c_timestamp, - c_timestamp_null, c_array, c_small, c_small_null, - c_big, c_num, c_time, c_interval, - c_hugetext = repeat('abcdefg',1000) as c_hugetext_origdef, - c_hugetext = repeat('poiuyt', 1000) as c_hugetext_newdef -FROM T ORDER BY pk; - pk | c_int | c_bpchar | c_text | c_date | c_timestamp | c_timestamp_null | c_array | c_small | c_small_null | c_big | c_num | c_time | c_interval | c_hugetext_origdef | c_hugetext_newdef -----+-------+----------+--------+------------+--------------------------+--------------------------+--------------------------+---------+--------------+-------------------+-------------------+----------+------------+--------------------+------------------- - 1 | 1 | hello | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f - 2 | 1 | hello | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f - 3 | 2 | hello | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f - 4 | 2 | hello | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f - 5 | 2 | dog | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f - 6 | 2 | dog | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f - 7 | 2 | dog | cat | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f - 8 | 2 | dog | cat | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f - 9 | 2 | dog | cat | 01-01-2010 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f - 10 | 2 | dog | cat | 01-01-2010 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f - 11 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f - 12 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f - 13 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f - 14 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f - 15 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f - 16 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f - 17 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 1.00000000001 | 12:00:00 | @ 1 day | t | f - 18 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 1.00000000001 | 12:00:00 | @ 1 day | t | f - 19 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 12:00:00 | @ 1 day | t | f - 20 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 12:00:00 | @ 1 day | t | f - 21 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | @ 1 day | t | f - 22 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | @ 1 day | t | f - 23 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | @ 3 hours | t | f - 24 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | @ 3 hours | t | f - 25 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | | f | t - 26 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | | f | t - 27 | 2 | | | | | Thu Sep 29 12:00:00 2016 | | | 13 | | | | | | - 28 | 2 | | | | | Thu Sep 29 12:00:00 2016 | | | 13 | | | | | | -(28 rows) - -SELECT comp(); - comp ------------ - Unchanged -(1 row) - -DROP TABLE T; --- Test expressions in the defaults -CREATE OR REPLACE FUNCTION foo(a INT) RETURNS TEXT AS $$ -DECLARE res TEXT := ''; - i INT; -BEGIN - i := 0; - WHILE (i < a) LOOP - res := res || chr(ascii('a') + i); - i := i + 1; - END LOOP; - RETURN res; -END; $$ LANGUAGE PLPGSQL STABLE; -CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT LENGTH(foo(6))); -SELECT set('t'); - set ------ - -(1 row) - -INSERT INTO T VALUES (1), (2); -ALTER TABLE T ADD COLUMN c_bpchar BPCHAR(5) DEFAULT foo(4), - ALTER COLUMN c_int SET DEFAULT LENGTH(foo(8)); -INSERT INTO T VALUES (3), (4); -ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT foo(6), - ALTER COLUMN c_bpchar SET DEFAULT foo(3); -INSERT INTO T VALUES (5), (6); -ALTER TABLE T ADD COLUMN c_date DATE - DEFAULT '2016-06-02'::DATE + LENGTH(foo(10)), - ALTER COLUMN c_text SET DEFAULT foo(12); -INSERT INTO T VALUES (7), (8); -ALTER TABLE T ADD COLUMN c_timestamp TIMESTAMP - DEFAULT '2016-09-01'::DATE + LENGTH(foo(10)), - ALTER COLUMN c_date - SET DEFAULT '2010-01-01'::DATE - LENGTH(foo(4)); -INSERT INTO T VALUES (9), (10); -ALTER TABLE T ADD COLUMN c_array TEXT[] - DEFAULT ('{"This", "is", "' || foo(4) || - '","the", "real", "world"}')::TEXT[], - ALTER COLUMN c_timestamp - SET DEFAULT '1970-12-31'::DATE + LENGTH(foo(30)); -INSERT INTO T VALUES (11), (12); -ALTER TABLE T ALTER COLUMN c_int DROP DEFAULT, - ALTER COLUMN c_array - SET DEFAULT ('{"This", "is", "' || foo(1) || - '", "fantasy"}')::text[]; -INSERT INTO T VALUES (13), (14); -ALTER TABLE T ALTER COLUMN c_bpchar DROP DEFAULT, - ALTER COLUMN c_date DROP DEFAULT, - ALTER COLUMN c_text DROP DEFAULT, - ALTER COLUMN c_timestamp DROP DEFAULT, - ALTER COLUMN c_array DROP DEFAULT; -INSERT INTO T VALUES (15), (16); -SELECT * FROM T; - pk | c_int | c_bpchar | c_text | c_date | c_timestamp | c_array -----+-------+----------+--------------+------------+--------------------------+------------------------------- - 1 | 6 | abcd | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world} - 2 | 6 | abcd | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world} - 3 | 8 | abcd | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world} - 4 | 8 | abcd | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world} - 5 | 8 | abc | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world} - 6 | 8 | abc | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world} - 7 | 8 | abc | abcdefghijkl | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world} - 8 | 8 | abc | abcdefghijkl | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world} - 9 | 8 | abc | abcdefghijkl | 12-28-2009 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world} - 10 | 8 | abc | abcdefghijkl | 12-28-2009 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world} - 11 | 8 | abc | abcdefghijkl | 12-28-2009 | Sat Jan 30 00:00:00 1971 | {This,is,abcd,the,real,world} - 12 | 8 | abc | abcdefghijkl | 12-28-2009 | Sat Jan 30 00:00:00 1971 | {This,is,abcd,the,real,world} - 13 | | abc | abcdefghijkl | 12-28-2009 | Sat Jan 30 00:00:00 1971 | {This,is,a,fantasy} - 14 | | abc | abcdefghijkl | 12-28-2009 | Sat Jan 30 00:00:00 1971 | {This,is,a,fantasy} - 15 | | | | | | - 16 | | | | | | -(16 rows) - -SELECT comp(); - comp ------------ - Unchanged -(1 row) - -DROP TABLE T; -DROP FUNCTION foo(INT); --- Fall back to full rewrite for volatile expressions -CREATE TABLE T(pk INT NOT NULL PRIMARY KEY); -INSERT INTO T VALUES (1); -SELECT set('t'); - set ------ - -(1 row) - --- now() is stable, because it returns the transaction timestamp -ALTER TABLE T ADD COLUMN c1 TIMESTAMP DEFAULT now(); -SELECT comp(); - comp ------------ - Unchanged -(1 row) - --- clock_timestamp() is volatile -ALTER TABLE T ADD COLUMN c2 TIMESTAMP DEFAULT clock_timestamp(); -NOTICE: rewriting table t for reason 2 -SELECT comp(); - comp ------------ - Rewritten -(1 row) - --- check that we notice insertion of a volatile default argument -CREATE FUNCTION foolme(timestamptz DEFAULT clock_timestamp()) - RETURNS timestamptz - IMMUTABLE AS 'select $1' LANGUAGE sql; -ALTER TABLE T ADD COLUMN c3 timestamptz DEFAULT foolme(); -NOTICE: rewriting table t for reason 2 -SELECT attname, atthasmissing, attmissingval FROM pg_attribute - WHERE attrelid = 't'::regclass AND attnum > 0 - ORDER BY attnum; - attname | atthasmissing | attmissingval ----------+---------------+--------------- - pk | f | - c1 | f | - c2 | f | - c3 | f | -(4 rows) - -DROP TABLE T; -DROP FUNCTION foolme(timestamptz); --- Simple querie -CREATE TABLE T (pk INT NOT NULL PRIMARY KEY); -SELECT set('t'); - set ------ - -(1 row) - -INSERT INTO T SELECT * FROM generate_series(1, 10) a; -ALTER TABLE T ADD COLUMN c_bigint BIGINT NOT NULL DEFAULT -1; -INSERT INTO T SELECT b, b - 10 FROM generate_series(11, 20) a(b); -ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'hello'; -INSERT INTO T SELECT b, b - 10, (b + 10)::text FROM generate_series(21, 30) a(b); --- WHERE clause -SELECT c_bigint, c_text FROM T WHERE c_bigint = -1 LIMIT 1; - c_bigint | c_text -----------+-------- - -1 | hello -(1 row) - -EXPLAIN (VERBOSE TRUE, COSTS FALSE) -SELECT c_bigint, c_text FROM T WHERE c_bigint = -1 LIMIT 1; - QUERY PLAN ----------------------------------------------- - Limit - Output: c_bigint, c_text - -> Seq Scan on fast_default.t - Output: c_bigint, c_text - Filter: (t.c_bigint = '-1'::integer) -(5 rows) - -SELECT c_bigint, c_text FROM T WHERE c_text = 'hello' LIMIT 1; - c_bigint | c_text -----------+-------- - -1 | hello -(1 row) - -EXPLAIN (VERBOSE TRUE, COSTS FALSE) SELECT c_bigint, c_text FROM T WHERE c_text = 'hello' LIMIT 1; - QUERY PLAN --------------------------------------------- - Limit - Output: c_bigint, c_text - -> Seq Scan on fast_default.t - Output: c_bigint, c_text - Filter: (t.c_text = 'hello'::text) -(5 rows) - --- COALESCE -SELECT COALESCE(c_bigint, pk), COALESCE(c_text, pk::text) -FROM T -ORDER BY pk LIMIT 10; - coalesce | coalesce -----------+---------- - -1 | hello - -1 | hello - -1 | hello - -1 | hello - -1 | hello - -1 | hello - -1 | hello - -1 | hello - -1 | hello - -1 | hello -(10 rows) - --- Aggregate function -SELECT SUM(c_bigint), MAX(c_text COLLATE "C" ), MIN(c_text COLLATE "C") FROM T; - sum | max | min ------+-------+----- - 200 | hello | 31 -(1 row) - --- ORDER BY -SELECT * FROM T ORDER BY c_bigint, c_text, pk LIMIT 10; - pk | c_bigint | c_text -----+----------+-------- - 1 | -1 | hello - 2 | -1 | hello - 3 | -1 | hello - 4 | -1 | hello - 5 | -1 | hello - 6 | -1 | hello - 7 | -1 | hello - 8 | -1 | hello - 9 | -1 | hello - 10 | -1 | hello -(10 rows) - -EXPLAIN (VERBOSE TRUE, COSTS FALSE) -SELECT * FROM T ORDER BY c_bigint, c_text, pk LIMIT 10; - QUERY PLAN ----------------------------------------------- - Limit - Output: pk, c_bigint, c_text - -> Sort - Output: pk, c_bigint, c_text - Sort Key: t.c_bigint, t.c_text, t.pk - -> Seq Scan on fast_default.t - Output: pk, c_bigint, c_text -(7 rows) - --- LIMIT -SELECT * FROM T WHERE c_bigint > -1 ORDER BY c_bigint, c_text, pk LIMIT 10; - pk | c_bigint | c_text -----+----------+-------- - 11 | 1 | hello - 12 | 2 | hello - 13 | 3 | hello - 14 | 4 | hello - 15 | 5 | hello - 16 | 6 | hello - 17 | 7 | hello - 18 | 8 | hello - 19 | 9 | hello - 20 | 10 | hello -(10 rows) - -EXPLAIN (VERBOSE TRUE, COSTS FALSE) -SELECT * FROM T WHERE c_bigint > -1 ORDER BY c_bigint, c_text, pk LIMIT 10; - QUERY PLAN ----------------------------------------------------- - Limit - Output: pk, c_bigint, c_text - -> Sort - Output: pk, c_bigint, c_text - Sort Key: t.c_bigint, t.c_text, t.pk - -> Seq Scan on fast_default.t - Output: pk, c_bigint, c_text - Filter: (t.c_bigint > '-1'::integer) -(8 rows) - --- DELETE with RETURNING -DELETE FROM T WHERE pk BETWEEN 10 AND 20 RETURNING *; - pk | c_bigint | c_text -----+----------+-------- - 10 | -1 | hello - 11 | 1 | hello - 12 | 2 | hello - 13 | 3 | hello - 14 | 4 | hello - 15 | 5 | hello - 16 | 6 | hello - 17 | 7 | hello - 18 | 8 | hello - 19 | 9 | hello - 20 | 10 | hello -(11 rows) - -EXPLAIN (VERBOSE TRUE, COSTS FALSE) -DELETE FROM T WHERE pk BETWEEN 10 AND 20 RETURNING *; - QUERY PLAN ------------------------------------------------------------ - Delete on fast_default.t - Output: pk, c_bigint, c_text - -> Bitmap Heap Scan on fast_default.t - Output: ctid - Recheck Cond: ((t.pk >= 10) AND (t.pk <= 20)) - -> Bitmap Index Scan on t_pkey - Index Cond: ((t.pk >= 10) AND (t.pk <= 20)) -(7 rows) - --- UPDATE -UPDATE T SET c_text = '"' || c_text || '"' WHERE pk < 10; -SELECT * FROM T WHERE c_text LIKE '"%"' ORDER BY PK; - pk | c_bigint | c_text -----+----------+--------- - 1 | -1 | "hello" - 2 | -1 | "hello" - 3 | -1 | "hello" - 4 | -1 | "hello" - 5 | -1 | "hello" - 6 | -1 | "hello" - 7 | -1 | "hello" - 8 | -1 | "hello" - 9 | -1 | "hello" -(9 rows) - -SELECT comp(); - comp ------------ - Unchanged -(1 row) - -DROP TABLE T; --- Combine with other DDL -CREATE TABLE T(pk INT NOT NULL PRIMARY KEY); -SELECT set('t'); - set ------ - -(1 row) - -INSERT INTO T VALUES (1), (2); -ALTER TABLE T ADD COLUMN c_int INT NOT NULL DEFAULT -1; -INSERT INTO T VALUES (3), (4); -ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'Hello'; -INSERT INTO T VALUES (5), (6); -ALTER TABLE T ALTER COLUMN c_text SET DEFAULT 'world', - ALTER COLUMN c_int SET DEFAULT 1; -INSERT INTO T VALUES (7), (8); -SELECT * FROM T ORDER BY pk; - pk | c_int | c_text -----+-------+-------- - 1 | -1 | Hello - 2 | -1 | Hello - 3 | -1 | Hello - 4 | -1 | Hello - 5 | -1 | Hello - 6 | -1 | Hello - 7 | 1 | world - 8 | 1 | world -(8 rows) - --- Add an index -CREATE INDEX i ON T(c_int, c_text); -SELECT c_text FROM T WHERE c_int = -1; - c_text --------- - Hello - Hello - Hello - Hello - Hello - Hello -(6 rows) - -SELECT comp(); - comp ------------ - Unchanged -(1 row) - --- query to exercise expand_tuple function -CREATE TABLE t1 AS -SELECT 1::int AS a , 2::int AS b -FROM generate_series(1,20) q; -ALTER TABLE t1 ADD COLUMN c text; -SELECT a, - stddev(cast((SELECT sum(1) FROM generate_series(1,20) x) AS float4)) - OVER (PARTITION BY a,b,c ORDER BY b) - AS z -FROM t1; - a | z ----+--- - 1 | 0 - 1 | 0 - 1 | 0 - 1 | 0 - 1 | 0 - 1 | 0 - 1 | 0 - 1 | 0 - 1 | 0 - 1 | 0 - 1 | 0 - 1 | 0 - 1 | 0 - 1 | 0 - 1 | 0 - 1 | 0 - 1 | 0 - 1 | 0 - 1 | 0 - 1 | 0 -(20 rows) - -DROP TABLE T; --- test that we account for missing columns without defaults correctly --- in expand_tuple, and that rows are correctly expanded for triggers -CREATE FUNCTION test_trigger() -RETURNS trigger -LANGUAGE plpgsql -AS $$ - -begin - raise notice 'old tuple: %', to_json(OLD)::text; - if TG_OP = 'DELETE' - then - return OLD; - else - return NEW; - end if; -end; - -$$; --- 2 new columns, both have defaults -CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int); -INSERT INTO t (a,b,c) VALUES (1,2,3); -ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4; -ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5; -CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger(); -SELECT * FROM t; - id | a | b | c | x | y -----+---+---+---+---+--- - 1 | 1 | 2 | 3 | 4 | 5 -(1 row) - -UPDATE t SET y = 2; -NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":3,"x":4,"y":5} -SELECT * FROM t; - id | a | b | c | x | y -----+---+---+---+---+--- - 1 | 1 | 2 | 3 | 4 | 2 -(1 row) - -DROP TABLE t; --- 2 new columns, first has default -CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int); -INSERT INTO t (a,b,c) VALUES (1,2,3); -ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4; -ALTER TABLE t ADD COLUMN y int; -CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger(); -SELECT * FROM t; - id | a | b | c | x | y -----+---+---+---+---+--- - 1 | 1 | 2 | 3 | 4 | -(1 row) - -UPDATE t SET y = 2; -NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":3,"x":4,"y":null} -SELECT * FROM t; - id | a | b | c | x | y -----+---+---+---+---+--- - 1 | 1 | 2 | 3 | 4 | 2 -(1 row) - -DROP TABLE t; --- 2 new columns, second has default -CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int); -INSERT INTO t (a,b,c) VALUES (1,2,3); -ALTER TABLE t ADD COLUMN x int; -ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5; -CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger(); -SELECT * FROM t; - id | a | b | c | x | y -----+---+---+---+---+--- - 1 | 1 | 2 | 3 | | 5 -(1 row) - -UPDATE t SET y = 2; -NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":3,"x":null,"y":5} -SELECT * FROM t; - id | a | b | c | x | y -----+---+---+---+---+--- - 1 | 1 | 2 | 3 | | 2 -(1 row) - -DROP TABLE t; --- 2 new columns, neither has default -CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int); -INSERT INTO t (a,b,c) VALUES (1,2,3); -ALTER TABLE t ADD COLUMN x int; -ALTER TABLE t ADD COLUMN y int; -CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger(); -SELECT * FROM t; - id | a | b | c | x | y -----+---+---+---+---+--- - 1 | 1 | 2 | 3 | | -(1 row) - -UPDATE t SET y = 2; -NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":3,"x":null,"y":null} -SELECT * FROM t; - id | a | b | c | x | y -----+---+---+---+---+--- - 1 | 1 | 2 | 3 | | 2 -(1 row) - -DROP TABLE t; --- same as last 4 tests but here the last original column has a NULL value --- 2 new columns, both have defaults -CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int); -INSERT INTO t (a,b,c) VALUES (1,2,NULL); -ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4; -ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5; -CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger(); -SELECT * FROM t; - id | a | b | c | x | y -----+---+---+---+---+--- - 1 | 1 | 2 | | 4 | 5 -(1 row) - -UPDATE t SET y = 2; -NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":null,"x":4,"y":5} -SELECT * FROM t; - id | a | b | c | x | y -----+---+---+---+---+--- - 1 | 1 | 2 | | 4 | 2 -(1 row) - -DROP TABLE t; --- 2 new columns, first has default -CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int); -INSERT INTO t (a,b,c) VALUES (1,2,NULL); -ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4; -ALTER TABLE t ADD COLUMN y int; -CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger(); -SELECT * FROM t; - id | a | b | c | x | y -----+---+---+---+---+--- - 1 | 1 | 2 | | 4 | -(1 row) - -UPDATE t SET y = 2; -NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":null,"x":4,"y":null} -SELECT * FROM t; - id | a | b | c | x | y -----+---+---+---+---+--- - 1 | 1 | 2 | | 4 | 2 -(1 row) - -DROP TABLE t; --- 2 new columns, second has default -CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int); -INSERT INTO t (a,b,c) VALUES (1,2,NULL); -ALTER TABLE t ADD COLUMN x int; -ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5; -CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger(); -SELECT * FROM t; - id | a | b | c | x | y -----+---+---+---+---+--- - 1 | 1 | 2 | | | 5 -(1 row) - -UPDATE t SET y = 2; -NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":null,"x":null,"y":5} -SELECT * FROM t; - id | a | b | c | x | y -----+---+---+---+---+--- - 1 | 1 | 2 | | | 2 -(1 row) - -DROP TABLE t; --- 2 new columns, neither has default -CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int); -INSERT INTO t (a,b,c) VALUES (1,2,NULL); -ALTER TABLE t ADD COLUMN x int; -ALTER TABLE t ADD COLUMN y int; -CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger(); -SELECT * FROM t; - id | a | b | c | x | y -----+---+---+---+---+--- - 1 | 1 | 2 | | | -(1 row) - -UPDATE t SET y = 2; -NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":null,"x":null,"y":null} -SELECT * FROM t; - id | a | b | c | x | y -----+---+---+---+---+--- - 1 | 1 | 2 | | | 2 -(1 row) - -DROP TABLE t; --- make sure expanded tuple has correct self pointer --- it will be required by the RI trigger doing the cascading delete -CREATE TABLE leader (a int PRIMARY KEY, b int); -CREATE TABLE follower (a int REFERENCES leader ON DELETE CASCADE, b int); -INSERT INTO leader VALUES (1, 1), (2, 2); -ALTER TABLE leader ADD c int; -ALTER TABLE leader DROP c; -DELETE FROM leader; --- check that ALTER TABLE ... ALTER TYPE does the right thing -CREATE TABLE vtype( a integer); -INSERT INTO vtype VALUES (1); -ALTER TABLE vtype ADD COLUMN b DOUBLE PRECISION DEFAULT 0.2; -ALTER TABLE vtype ADD COLUMN c BOOLEAN DEFAULT true; -SELECT * FROM vtype; - a | b | c ----+-----+--- - 1 | 0.2 | t -(1 row) - -ALTER TABLE vtype - ALTER b TYPE text USING b::text, - ALTER c TYPE text USING c::text; -NOTICE: rewriting table vtype for reason 4 -SELECT * FROM vtype; - a | b | c ----+-----+------ - 1 | 0.2 | true -(1 row) - --- also check the case that doesn't rewrite the table -CREATE TABLE vtype2 (a int); -INSERT INTO vtype2 VALUES (1); -ALTER TABLE vtype2 ADD COLUMN b varchar(10) DEFAULT 'xxx'; -ALTER TABLE vtype2 ALTER COLUMN b SET DEFAULT 'yyy'; -INSERT INTO vtype2 VALUES (2); -ALTER TABLE vtype2 ALTER COLUMN b TYPE varchar(20) USING b::varchar(20); -SELECT * FROM vtype2; - a | b ----+----- - 1 | xxx - 2 | yyy -(2 rows) - --- Ensure that defaults are checked when evaluating whether HOT update --- is possible, this was broken for a while: --- https://postgr.es/m/20190202133521.ylauh3ckqa7colzj%40alap3.anarazel.de -BEGIN; -CREATE TABLE t(); -INSERT INTO t DEFAULT VALUES; -ALTER TABLE t ADD COLUMN a int DEFAULT 1; -CREATE INDEX ON t(a); --- set column with a default 1 to NULL, due to a bug that wasn't --- noticed has heap_getattr buggily returned NULL for default columns -UPDATE t SET a = NULL; --- verify that index and non-index scans show the same result -SET LOCAL enable_seqscan = true; -SELECT * FROM t WHERE a IS NULL; - a ---- - -(1 row) - -SET LOCAL enable_seqscan = false; -SELECT * FROM t WHERE a IS NULL; - a ---- - -(1 row) - -ROLLBACK; --- verify that a default set on a non-plain table doesn't set a missing --- value on the attribute -CREATE FOREIGN DATA WRAPPER dummy; -CREATE SERVER s0 FOREIGN DATA WRAPPER dummy; -CREATE FOREIGN TABLE ft1 (c1 integer NOT NULL) SERVER s0; -ALTER FOREIGN TABLE ft1 ADD COLUMN c8 integer DEFAULT 0; -ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE char(10); -SELECT count(*) - FROM pg_attribute - WHERE attrelid = 'ft1'::regclass AND - (attmissingval IS NOT NULL OR atthasmissing); - count -------- - 0 -(1 row) - --- cleanup -DROP FOREIGN TABLE ft1; -DROP SERVER s0; -DROP FOREIGN DATA WRAPPER dummy; -DROP TABLE vtype; -DROP TABLE vtype2; -DROP TABLE follower; -DROP TABLE leader; -DROP FUNCTION test_trigger(); -DROP TABLE t1; -DROP FUNCTION set(name); -DROP FUNCTION comp(); -DROP TABLE m; -DROP TABLE has_volatile; -DROP EVENT TRIGGER has_volatile_rewrite; -DROP FUNCTION log_rewrite; -DROP SCHEMA fast_default; --- Leave a table with an active fast default in place, for pg_upgrade testing -set search_path = public; -create table has_fast_default(f1 int); -insert into has_fast_default values(1); -alter table has_fast_default add column f2 int default 42; -table has_fast_default; - f1 | f2 -----+---- - 1 | 42 -(1 row) - +psql: error: connection to server on socket "/tmp/bBabKAIw6z/.s.PGSQL.19533" failed: No such file or directory + Is the server running locally and accepting connections on that socket? diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/tablespace.out /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/tablespace.out --- /tmp/cirrus-ci-build/src/test/regress/expected/tablespace.out 2024-09-22 06:28:18.765333018 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/tablespace.out 2024-09-22 06:38:10.044217213 +0000 @@ -1,968 +1,2 @@ --- relative tablespace locations are not allowed -CREATE TABLESPACE regress_tblspace LOCATION 'relative'; -- fail -ERROR: tablespace location must be an absolute path --- empty tablespace locations are not usually allowed -CREATE TABLESPACE regress_tblspace LOCATION ''; -- fail -ERROR: tablespace location must be an absolute path --- as a special developer-only option to allow us to use tablespaces --- with streaming replication on the same server, an empty location --- can be allowed as a way to say that the tablespace should be created --- as a directory in pg_tblspc, rather than being a symlink -SET allow_in_place_tablespaces = true; --- create a tablespace using WITH clause -CREATE TABLESPACE regress_tblspacewith LOCATION '' WITH (some_nonexistent_parameter = true); -- fail -ERROR: unrecognized parameter "some_nonexistent_parameter" -CREATE TABLESPACE regress_tblspacewith LOCATION '' WITH (random_page_cost = 3.0); -- ok --- check to see the parameter was used -SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith'; - spcoptions ------------------------- - {random_page_cost=3.0} -(1 row) - --- drop the tablespace so we can re-use the location -DROP TABLESPACE regress_tblspacewith; --- This returns a relative path as of an effect of allow_in_place_tablespaces, --- masking the tablespace OID used in the path name. -SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN') - FROM pg_tablespace WHERE spcname = 'regress_tblspace'; - regexp_replace ----------------- - pg_tblspc/NNN -(1 row) - --- try setting and resetting some properties for the new tablespace -ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1); -ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail -ERROR: unrecognized parameter "some_nonexistent_parameter" -ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail -ERROR: RESET must not include values for parameters -ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok --- REINDEX (TABLESPACE) --- catalogs and system tablespaces --- system catalog, fail -REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -ERROR: cannot move system relation "pg_am_name_index" -REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -ERROR: cannot reindex system catalogs concurrently --- shared catalog, fail -REINDEX (TABLESPACE regress_tblspace) TABLE pg_authid; -ERROR: cannot move system relation "pg_authid_rolname_index" -REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_authid; -ERROR: cannot reindex system catalogs concurrently --- toast relations, fail -REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1262_index; -ERROR: cannot move system relation "pg_toast_1262_index" -REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY pg_toast.pg_toast_1262_index; -ERROR: cannot reindex system catalogs concurrently -REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1262; -ERROR: cannot move system relation "pg_toast_1262_index" -REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_toast.pg_toast_1262; -ERROR: cannot reindex system catalogs concurrently --- system catalog, fail -REINDEX (TABLESPACE pg_global) TABLE pg_authid; -ERROR: cannot move system relation "pg_authid_rolname_index" -REINDEX (TABLESPACE pg_global) TABLE CONCURRENTLY pg_authid; -ERROR: cannot reindex system catalogs concurrently --- table with toast relation -CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, t text); -INSERT INTO regress_tblspace_test_tbl (num1, num2, t) - SELECT round(random()*100), random(), 'text' - FROM generate_series(1, 10) s(i); -CREATE INDEX regress_tblspace_test_tbl_idx ON regress_tblspace_test_tbl (num1); --- move to global tablespace, fail -REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -ERROR: only shared relations can be placed in pg_global tablespace -REINDEX (TABLESPACE pg_global) INDEX CONCURRENTLY regress_tblspace_test_tbl_idx; -ERROR: cannot move non-shared relation to tablespace "pg_global" --- check transactional behavior of REINDEX (TABLESPACE) -BEGIN; -REINDEX (TABLESPACE regress_tblspace) INDEX regress_tblspace_test_tbl_idx; -REINDEX (TABLESPACE regress_tblspace) TABLE regress_tblspace_test_tbl; -ROLLBACK; --- no relation moved to the new tablespace -SELECT c.relname FROM pg_class c, pg_tablespace s - WHERE c.reltablespace = s.oid AND s.spcname = 'regress_tblspace'; - relname ---------- -(0 rows) - --- check that all indexes are moved to a new tablespace with different --- relfilenode. --- Save first the existing relfilenode for the toast and main relations. -SELECT relfilenode as main_filenode FROM pg_class - WHERE relname = 'regress_tblspace_test_tbl_idx' \gset -SELECT relfilenode as toast_filenode FROM pg_class - WHERE oid = - (SELECT i.indexrelid - FROM pg_class c, - pg_index i - WHERE i.indrelid = c.reltoastrelid AND - c.relname = 'regress_tblspace_test_tbl') \gset -REINDEX (TABLESPACE regress_tblspace) TABLE regress_tblspace_test_tbl; -SELECT c.relname FROM pg_class c, pg_tablespace s - WHERE c.reltablespace = s.oid AND s.spcname = 'regress_tblspace' - ORDER BY c.relname; - relname -------------------------------- - regress_tblspace_test_tbl_idx -(1 row) - -ALTER TABLE regress_tblspace_test_tbl SET TABLESPACE regress_tblspace; -ALTER TABLE regress_tblspace_test_tbl SET TABLESPACE pg_default; -SELECT c.relname FROM pg_class c, pg_tablespace s - WHERE c.reltablespace = s.oid AND s.spcname = 'regress_tblspace' - ORDER BY c.relname; - relname -------------------------------- - regress_tblspace_test_tbl_idx -(1 row) - --- Move back to the default tablespace. -ALTER INDEX regress_tblspace_test_tbl_idx SET TABLESPACE pg_default; -SELECT c.relname FROM pg_class c, pg_tablespace s - WHERE c.reltablespace = s.oid AND s.spcname = 'regress_tblspace' - ORDER BY c.relname; - relname ---------- -(0 rows) - -REINDEX (TABLESPACE regress_tblspace, CONCURRENTLY) TABLE regress_tblspace_test_tbl; -SELECT c.relname FROM pg_class c, pg_tablespace s - WHERE c.reltablespace = s.oid AND s.spcname = 'regress_tblspace' - ORDER BY c.relname; - relname -------------------------------- - regress_tblspace_test_tbl_idx -(1 row) - -SELECT relfilenode = :main_filenode AS main_same FROM pg_class - WHERE relname = 'regress_tblspace_test_tbl_idx'; - main_same ------------ - f -(1 row) - -SELECT relfilenode = :toast_filenode as toast_same FROM pg_class - WHERE oid = - (SELECT i.indexrelid - FROM pg_class c, - pg_index i - WHERE i.indrelid = c.reltoastrelid AND - c.relname = 'regress_tblspace_test_tbl'); - toast_same ------------- - f -(1 row) - -DROP TABLE regress_tblspace_test_tbl; --- REINDEX (TABLESPACE) with partitions --- Create a partition tree and check the set of relations reindexed --- with their new tablespace. -CREATE TABLE tbspace_reindex_part (c1 int, c2 int) PARTITION BY RANGE (c1); -CREATE TABLE tbspace_reindex_part_0 PARTITION OF tbspace_reindex_part - FOR VALUES FROM (0) TO (10) PARTITION BY list (c2); -CREATE TABLE tbspace_reindex_part_0_1 PARTITION OF tbspace_reindex_part_0 - FOR VALUES IN (1); -CREATE TABLE tbspace_reindex_part_0_2 PARTITION OF tbspace_reindex_part_0 - FOR VALUES IN (2); --- This partitioned table will have no partitions. -CREATE TABLE tbspace_reindex_part_10 PARTITION OF tbspace_reindex_part - FOR VALUES FROM (10) TO (20) PARTITION BY list (c2); --- Create some partitioned indexes -CREATE INDEX tbspace_reindex_part_index ON ONLY tbspace_reindex_part (c1); -CREATE INDEX tbspace_reindex_part_index_0 ON ONLY tbspace_reindex_part_0 (c1); -ALTER INDEX tbspace_reindex_part_index ATTACH PARTITION tbspace_reindex_part_index_0; --- This partitioned index will have no partitions. -CREATE INDEX tbspace_reindex_part_index_10 ON ONLY tbspace_reindex_part_10 (c1); -ALTER INDEX tbspace_reindex_part_index ATTACH PARTITION tbspace_reindex_part_index_10; -CREATE INDEX tbspace_reindex_part_index_0_1 ON ONLY tbspace_reindex_part_0_1 (c1); -ALTER INDEX tbspace_reindex_part_index_0 ATTACH PARTITION tbspace_reindex_part_index_0_1; -CREATE INDEX tbspace_reindex_part_index_0_2 ON ONLY tbspace_reindex_part_0_2 (c1); -ALTER INDEX tbspace_reindex_part_index_0 ATTACH PARTITION tbspace_reindex_part_index_0_2; -SELECT relid, parentrelid, level FROM pg_partition_tree('tbspace_reindex_part_index') - ORDER BY relid, level; - relid | parentrelid | level ---------------------------------+------------------------------+------- - tbspace_reindex_part_index | | 0 - tbspace_reindex_part_index_0 | tbspace_reindex_part_index | 1 - tbspace_reindex_part_index_10 | tbspace_reindex_part_index | 1 - tbspace_reindex_part_index_0_1 | tbspace_reindex_part_index_0 | 2 - tbspace_reindex_part_index_0_2 | tbspace_reindex_part_index_0 | 2 -(5 rows) - --- Track the original tablespace, relfilenode and OID of each index --- in the tree. -CREATE TEMP TABLE reindex_temp_before AS - SELECT oid, relname, relfilenode, reltablespace - FROM pg_class - WHERE relname ~ 'tbspace_reindex_part_index'; -REINDEX (TABLESPACE regress_tblspace, CONCURRENTLY) TABLE tbspace_reindex_part; --- REINDEX CONCURRENTLY changes the OID of the old relation, hence a check --- based on the relation name below. -SELECT b.relname, - CASE WHEN a.relfilenode = b.relfilenode THEN 'relfilenode is unchanged' - ELSE 'relfilenode has changed' END AS filenode, - CASE WHEN a.reltablespace = b.reltablespace THEN 'reltablespace is unchanged' - ELSE 'reltablespace has changed' END AS tbspace - FROM reindex_temp_before b JOIN pg_class a ON b.relname = a.relname - ORDER BY 1; - relname | filenode | tbspace ---------------------------------+--------------------------+---------------------------- - tbspace_reindex_part_index | relfilenode is unchanged | reltablespace is unchanged - tbspace_reindex_part_index_0 | relfilenode is unchanged | reltablespace is unchanged - tbspace_reindex_part_index_0_1 | relfilenode has changed | reltablespace has changed - tbspace_reindex_part_index_0_2 | relfilenode has changed | reltablespace has changed - tbspace_reindex_part_index_10 | relfilenode is unchanged | reltablespace is unchanged -(5 rows) - -DROP TABLE tbspace_reindex_part; --- create a schema we can use -CREATE SCHEMA testschema; --- try a table -CREATE TABLE testschema.foo (i int) TABLESPACE regress_tblspace; -SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c - where c.reltablespace = t.oid AND c.relname = 'foo'; - relname | spcname ----------+------------------ - foo | regress_tblspace -(1 row) - -INSERT INTO testschema.foo VALUES(1); -INSERT INTO testschema.foo VALUES(2); --- tables from dynamic sources -CREATE TABLE testschema.asselect TABLESPACE regress_tblspace AS SELECT 1; -SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c - where c.reltablespace = t.oid AND c.relname = 'asselect'; - relname | spcname -----------+------------------ - asselect | regress_tblspace -(1 row) - -PREPARE selectsource(int) AS SELECT $1; -CREATE TABLE testschema.asexecute TABLESPACE regress_tblspace - AS EXECUTE selectsource(2); -SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c - where c.reltablespace = t.oid AND c.relname = 'asexecute'; - relname | spcname ------------+------------------ - asexecute | regress_tblspace -(1 row) - --- index -CREATE INDEX foo_idx on testschema.foo(i) TABLESPACE regress_tblspace; -SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c - where c.reltablespace = t.oid AND c.relname = 'foo_idx'; - relname | spcname ----------+------------------ - foo_idx | regress_tblspace -(1 row) - --- check \d output -\d testschema.foo - Table "testschema.foo" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - i | integer | | | -Indexes: - "foo_idx" btree (i), tablespace "regress_tblspace" -Tablespace: "regress_tblspace" - -\d testschema.foo_idx - Index "testschema.foo_idx" - Column | Type | Key? | Definition ---------+---------+------+------------ - i | integer | yes | i -btree, for table "testschema.foo" -Tablespace: "regress_tblspace" - --- --- partitioned table --- -CREATE TABLE testschema.part (a int) PARTITION BY LIST (a); -SET default_tablespace TO pg_global; -CREATE TABLE testschema.part_1 PARTITION OF testschema.part FOR VALUES IN (1); -ERROR: only shared relations can be placed in pg_global tablespace -RESET default_tablespace; -CREATE TABLE testschema.part_1 PARTITION OF testschema.part FOR VALUES IN (1); -SET default_tablespace TO regress_tblspace; -CREATE TABLE testschema.part_2 PARTITION OF testschema.part FOR VALUES IN (2); -SET default_tablespace TO pg_global; -CREATE TABLE testschema.part_3 PARTITION OF testschema.part FOR VALUES IN (3); -ERROR: only shared relations can be placed in pg_global tablespace -ALTER TABLE testschema.part SET TABLESPACE regress_tblspace; -CREATE TABLE testschema.part_3 PARTITION OF testschema.part FOR VALUES IN (3); -CREATE TABLE testschema.part_4 PARTITION OF testschema.part FOR VALUES IN (4) - TABLESPACE pg_default; -CREATE TABLE testschema.part_56 PARTITION OF testschema.part FOR VALUES IN (5, 6) - PARTITION BY LIST (a); -ALTER TABLE testschema.part SET TABLESPACE pg_default; -CREATE TABLE testschema.part_78 PARTITION OF testschema.part FOR VALUES IN (7, 8) - PARTITION BY LIST (a); -ERROR: only shared relations can be placed in pg_global tablespace -CREATE TABLE testschema.part_910 PARTITION OF testschema.part FOR VALUES IN (9, 10) - PARTITION BY LIST (a) TABLESPACE regress_tblspace; -RESET default_tablespace; -CREATE TABLE testschema.part_78 PARTITION OF testschema.part FOR VALUES IN (7, 8) - PARTITION BY LIST (a); -SELECT relname, spcname FROM pg_catalog.pg_class c - JOIN pg_catalog.pg_namespace n ON (c.relnamespace = n.oid) - LEFT JOIN pg_catalog.pg_tablespace t ON c.reltablespace = t.oid - where c.relname LIKE 'part%' AND n.nspname = 'testschema' order by relname; - relname | spcname -----------+------------------ - part | - part_1 | - part_2 | regress_tblspace - part_3 | regress_tblspace - part_4 | - part_56 | regress_tblspace - part_78 | - part_910 | regress_tblspace -(8 rows) - -RESET default_tablespace; -DROP TABLE testschema.part; --- partitioned index -CREATE TABLE testschema.part (a int) PARTITION BY LIST (a); -CREATE TABLE testschema.part1 PARTITION OF testschema.part FOR VALUES IN (1); -CREATE INDEX part_a_idx ON testschema.part (a) TABLESPACE regress_tblspace; -CREATE TABLE testschema.part2 PARTITION OF testschema.part FOR VALUES IN (2); -SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c - where c.reltablespace = t.oid AND c.relname LIKE 'part%_idx' ORDER BY relname; - relname | spcname --------------+------------------ - part1_a_idx | regress_tblspace - part2_a_idx | regress_tblspace - part_a_idx | regress_tblspace -(3 rows) - -\d testschema.part - Partitioned table "testschema.part" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | -Partition key: LIST (a) -Indexes: - "part_a_idx" btree (a), tablespace "regress_tblspace" -Number of partitions: 2 (Use \d+ to list them.) - -\d+ testschema.part - Partitioned table "testschema.part" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+---------+---------+--------------+------------- - a | integer | | | | plain | | -Partition key: LIST (a) -Indexes: - "part_a_idx" btree (a), tablespace "regress_tblspace" -Partitions: testschema.part1 FOR VALUES IN (1), - testschema.part2 FOR VALUES IN (2) - -\d testschema.part1 - Table "testschema.part1" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | -Partition of: testschema.part FOR VALUES IN (1) -Indexes: - "part1_a_idx" btree (a), tablespace "regress_tblspace" - -\d+ testschema.part1 - Table "testschema.part1" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+---------+---------+--------------+------------- - a | integer | | | | plain | | -Partition of: testschema.part FOR VALUES IN (1) -Partition constraint: ((a IS NOT NULL) AND (a = 1)) -Indexes: - "part1_a_idx" btree (a), tablespace "regress_tblspace" - -\d testschema.part_a_idx -Partitioned index "testschema.part_a_idx" - Column | Type | Key? | Definition ---------+---------+------+------------ - a | integer | yes | a -btree, for table "testschema.part" -Number of partitions: 2 (Use \d+ to list them.) -Tablespace: "regress_tblspace" - -\d+ testschema.part_a_idx - Partitioned index "testschema.part_a_idx" - Column | Type | Key? | Definition | Storage | Stats target ---------+---------+------+------------+---------+-------------- - a | integer | yes | a | plain | -btree, for table "testschema.part" -Partitions: testschema.part1_a_idx, - testschema.part2_a_idx -Tablespace: "regress_tblspace" - --- partitioned rels cannot specify the default tablespace. These fail: -CREATE TABLE testschema.dflt (a int PRIMARY KEY) PARTITION BY LIST (a) TABLESPACE pg_default; -ERROR: cannot specify default tablespace for partitioned relations -CREATE TABLE testschema.dflt (a int PRIMARY KEY USING INDEX TABLESPACE pg_default) PARTITION BY LIST (a); -ERROR: cannot specify default tablespace for partitioned relations -SET default_tablespace TO 'pg_default'; -CREATE TABLE testschema.dflt (a int PRIMARY KEY) PARTITION BY LIST (a) TABLESPACE regress_tblspace; -ERROR: cannot specify default tablespace for partitioned relations -CREATE TABLE testschema.dflt (a int PRIMARY KEY USING INDEX TABLESPACE regress_tblspace) PARTITION BY LIST (a); -ERROR: cannot specify default tablespace for partitioned relations --- but these work: -CREATE TABLE testschema.dflt (a int PRIMARY KEY USING INDEX TABLESPACE regress_tblspace) PARTITION BY LIST (a) TABLESPACE regress_tblspace; -SET default_tablespace TO ''; -CREATE TABLE testschema.dflt2 (a int PRIMARY KEY) PARTITION BY LIST (a); -DROP TABLE testschema.dflt, testschema.dflt2; --- check that default_tablespace doesn't affect ALTER TABLE index rebuilds -CREATE TABLE testschema.test_default_tab(id bigint) TABLESPACE regress_tblspace; -INSERT INTO testschema.test_default_tab VALUES (1); -CREATE INDEX test_index1 on testschema.test_default_tab (id); -CREATE INDEX test_index2 on testschema.test_default_tab (id) TABLESPACE regress_tblspace; -ALTER TABLE testschema.test_default_tab ADD CONSTRAINT test_index3 PRIMARY KEY (id); -ALTER TABLE testschema.test_default_tab ADD CONSTRAINT test_index4 UNIQUE (id) USING INDEX TABLESPACE regress_tblspace; -\d testschema.test_index1 - Index "testschema.test_index1" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -btree, for table "testschema.test_default_tab" - -\d testschema.test_index2 - Index "testschema.test_index2" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -btree, for table "testschema.test_default_tab" -Tablespace: "regress_tblspace" - -\d testschema.test_index3 - Index "testschema.test_index3" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -primary key, btree, for table "testschema.test_default_tab" - -\d testschema.test_index4 - Index "testschema.test_index4" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -unique, btree, for table "testschema.test_default_tab" -Tablespace: "regress_tblspace" - --- use a custom tablespace for default_tablespace -SET default_tablespace TO regress_tblspace; --- tablespace should not change if no rewrite -ALTER TABLE testschema.test_default_tab ALTER id TYPE bigint; -\d testschema.test_index1 - Index "testschema.test_index1" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -btree, for table "testschema.test_default_tab" - -\d testschema.test_index2 - Index "testschema.test_index2" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -btree, for table "testschema.test_default_tab" -Tablespace: "regress_tblspace" - -\d testschema.test_index3 - Index "testschema.test_index3" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -primary key, btree, for table "testschema.test_default_tab" - -\d testschema.test_index4 - Index "testschema.test_index4" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -unique, btree, for table "testschema.test_default_tab" -Tablespace: "regress_tblspace" - -SELECT * FROM testschema.test_default_tab; - id ----- - 1 -(1 row) - --- tablespace should not change even if there is an index rewrite -ALTER TABLE testschema.test_default_tab ALTER id TYPE int; -\d testschema.test_index1 - Index "testschema.test_index1" - Column | Type | Key? | Definition ---------+---------+------+------------ - id | integer | yes | id -btree, for table "testschema.test_default_tab" - -\d testschema.test_index2 - Index "testschema.test_index2" - Column | Type | Key? | Definition ---------+---------+------+------------ - id | integer | yes | id -btree, for table "testschema.test_default_tab" -Tablespace: "regress_tblspace" - -\d testschema.test_index3 - Index "testschema.test_index3" - Column | Type | Key? | Definition ---------+---------+------+------------ - id | integer | yes | id -primary key, btree, for table "testschema.test_default_tab" - -\d testschema.test_index4 - Index "testschema.test_index4" - Column | Type | Key? | Definition ---------+---------+------+------------ - id | integer | yes | id -unique, btree, for table "testschema.test_default_tab" -Tablespace: "regress_tblspace" - -SELECT * FROM testschema.test_default_tab; - id ----- - 1 -(1 row) - --- now use the default tablespace for default_tablespace -SET default_tablespace TO ''; --- tablespace should not change if no rewrite -ALTER TABLE testschema.test_default_tab ALTER id TYPE int; -\d testschema.test_index1 - Index "testschema.test_index1" - Column | Type | Key? | Definition ---------+---------+------+------------ - id | integer | yes | id -btree, for table "testschema.test_default_tab" - -\d testschema.test_index2 - Index "testschema.test_index2" - Column | Type | Key? | Definition ---------+---------+------+------------ - id | integer | yes | id -btree, for table "testschema.test_default_tab" -Tablespace: "regress_tblspace" - -\d testschema.test_index3 - Index "testschema.test_index3" - Column | Type | Key? | Definition ---------+---------+------+------------ - id | integer | yes | id -primary key, btree, for table "testschema.test_default_tab" - -\d testschema.test_index4 - Index "testschema.test_index4" - Column | Type | Key? | Definition ---------+---------+------+------------ - id | integer | yes | id -unique, btree, for table "testschema.test_default_tab" -Tablespace: "regress_tblspace" - --- tablespace should not change even if there is an index rewrite -ALTER TABLE testschema.test_default_tab ALTER id TYPE bigint; -\d testschema.test_index1 - Index "testschema.test_index1" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -btree, for table "testschema.test_default_tab" - -\d testschema.test_index2 - Index "testschema.test_index2" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -btree, for table "testschema.test_default_tab" -Tablespace: "regress_tblspace" - -\d testschema.test_index3 - Index "testschema.test_index3" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -primary key, btree, for table "testschema.test_default_tab" - -\d testschema.test_index4 - Index "testschema.test_index4" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -unique, btree, for table "testschema.test_default_tab" -Tablespace: "regress_tblspace" - -DROP TABLE testschema.test_default_tab; --- check that default_tablespace doesn't affect ALTER TABLE index rebuilds --- (this time with a partitioned table) -CREATE TABLE testschema.test_default_tab_p(id bigint, val bigint) - PARTITION BY LIST (id) TABLESPACE regress_tblspace; -CREATE TABLE testschema.test_default_tab_p1 PARTITION OF testschema.test_default_tab_p - FOR VALUES IN (1); -INSERT INTO testschema.test_default_tab_p VALUES (1); -CREATE INDEX test_index1 on testschema.test_default_tab_p (val); -CREATE INDEX test_index2 on testschema.test_default_tab_p (val) TABLESPACE regress_tblspace; -ALTER TABLE testschema.test_default_tab_p ADD CONSTRAINT test_index3 PRIMARY KEY (id); -ALTER TABLE testschema.test_default_tab_p ADD CONSTRAINT test_index4 UNIQUE (id) USING INDEX TABLESPACE regress_tblspace; -\d testschema.test_index1 -Partitioned index "testschema.test_index1" - Column | Type | Key? | Definition ---------+--------+------+------------ - val | bigint | yes | val -btree, for table "testschema.test_default_tab_p" -Number of partitions: 1 (Use \d+ to list them.) - -\d testschema.test_index2 -Partitioned index "testschema.test_index2" - Column | Type | Key? | Definition ---------+--------+------+------------ - val | bigint | yes | val -btree, for table "testschema.test_default_tab_p" -Number of partitions: 1 (Use \d+ to list them.) -Tablespace: "regress_tblspace" - -\d testschema.test_index3 -Partitioned index "testschema.test_index3" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -primary key, btree, for table "testschema.test_default_tab_p" -Number of partitions: 1 (Use \d+ to list them.) - -\d testschema.test_index4 -Partitioned index "testschema.test_index4" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -unique, btree, for table "testschema.test_default_tab_p" -Number of partitions: 1 (Use \d+ to list them.) -Tablespace: "regress_tblspace" - --- use a custom tablespace for default_tablespace -SET default_tablespace TO regress_tblspace; --- tablespace should not change if no rewrite -ALTER TABLE testschema.test_default_tab_p ALTER val TYPE bigint; -\d testschema.test_index1 -Partitioned index "testschema.test_index1" - Column | Type | Key? | Definition ---------+--------+------+------------ - val | bigint | yes | val -btree, for table "testschema.test_default_tab_p" -Number of partitions: 1 (Use \d+ to list them.) - -\d testschema.test_index2 -Partitioned index "testschema.test_index2" - Column | Type | Key? | Definition ---------+--------+------+------------ - val | bigint | yes | val -btree, for table "testschema.test_default_tab_p" -Number of partitions: 1 (Use \d+ to list them.) -Tablespace: "regress_tblspace" - -\d testschema.test_index3 -Partitioned index "testschema.test_index3" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -primary key, btree, for table "testschema.test_default_tab_p" -Number of partitions: 1 (Use \d+ to list them.) - -\d testschema.test_index4 -Partitioned index "testschema.test_index4" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -unique, btree, for table "testschema.test_default_tab_p" -Number of partitions: 1 (Use \d+ to list them.) -Tablespace: "regress_tblspace" - -SELECT * FROM testschema.test_default_tab_p; - id | val -----+----- - 1 | -(1 row) - --- tablespace should not change even if there is an index rewrite -ALTER TABLE testschema.test_default_tab_p ALTER val TYPE int; -\d testschema.test_index1 -Partitioned index "testschema.test_index1" - Column | Type | Key? | Definition ---------+---------+------+------------ - val | integer | yes | val -btree, for table "testschema.test_default_tab_p" -Number of partitions: 1 (Use \d+ to list them.) - -\d testschema.test_index2 -Partitioned index "testschema.test_index2" - Column | Type | Key? | Definition ---------+---------+------+------------ - val | integer | yes | val -btree, for table "testschema.test_default_tab_p" -Number of partitions: 1 (Use \d+ to list them.) -Tablespace: "regress_tblspace" - -\d testschema.test_index3 -Partitioned index "testschema.test_index3" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -primary key, btree, for table "testschema.test_default_tab_p" -Number of partitions: 1 (Use \d+ to list them.) - -\d testschema.test_index4 -Partitioned index "testschema.test_index4" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -unique, btree, for table "testschema.test_default_tab_p" -Number of partitions: 1 (Use \d+ to list them.) -Tablespace: "regress_tblspace" - -SELECT * FROM testschema.test_default_tab_p; - id | val -----+----- - 1 | -(1 row) - --- now use the default tablespace for default_tablespace -SET default_tablespace TO ''; --- tablespace should not change if no rewrite -ALTER TABLE testschema.test_default_tab_p ALTER val TYPE int; -\d testschema.test_index1 -Partitioned index "testschema.test_index1" - Column | Type | Key? | Definition ---------+---------+------+------------ - val | integer | yes | val -btree, for table "testschema.test_default_tab_p" -Number of partitions: 1 (Use \d+ to list them.) - -\d testschema.test_index2 -Partitioned index "testschema.test_index2" - Column | Type | Key? | Definition ---------+---------+------+------------ - val | integer | yes | val -btree, for table "testschema.test_default_tab_p" -Number of partitions: 1 (Use \d+ to list them.) -Tablespace: "regress_tblspace" - -\d testschema.test_index3 -Partitioned index "testschema.test_index3" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -primary key, btree, for table "testschema.test_default_tab_p" -Number of partitions: 1 (Use \d+ to list them.) - -\d testschema.test_index4 -Partitioned index "testschema.test_index4" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -unique, btree, for table "testschema.test_default_tab_p" -Number of partitions: 1 (Use \d+ to list them.) -Tablespace: "regress_tblspace" - --- tablespace should not change even if there is an index rewrite -ALTER TABLE testschema.test_default_tab_p ALTER val TYPE bigint; -\d testschema.test_index1 -Partitioned index "testschema.test_index1" - Column | Type | Key? | Definition ---------+--------+------+------------ - val | bigint | yes | val -btree, for table "testschema.test_default_tab_p" -Number of partitions: 1 (Use \d+ to list them.) - -\d testschema.test_index2 -Partitioned index "testschema.test_index2" - Column | Type | Key? | Definition ---------+--------+------+------------ - val | bigint | yes | val -btree, for table "testschema.test_default_tab_p" -Number of partitions: 1 (Use \d+ to list them.) -Tablespace: "regress_tblspace" - -\d testschema.test_index3 -Partitioned index "testschema.test_index3" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -primary key, btree, for table "testschema.test_default_tab_p" -Number of partitions: 1 (Use \d+ to list them.) - -\d testschema.test_index4 -Partitioned index "testschema.test_index4" - Column | Type | Key? | Definition ---------+--------+------+------------ - id | bigint | yes | id -unique, btree, for table "testschema.test_default_tab_p" -Number of partitions: 1 (Use \d+ to list them.) -Tablespace: "regress_tblspace" - -DROP TABLE testschema.test_default_tab_p; --- check that default_tablespace affects index additions in ALTER TABLE -CREATE TABLE testschema.test_tab(id int) TABLESPACE regress_tblspace; -INSERT INTO testschema.test_tab VALUES (1); -SET default_tablespace TO regress_tblspace; -ALTER TABLE testschema.test_tab ADD CONSTRAINT test_tab_unique UNIQUE (id); -SET default_tablespace TO ''; -ALTER TABLE testschema.test_tab ADD CONSTRAINT test_tab_pkey PRIMARY KEY (id); -\d testschema.test_tab_unique - Index "testschema.test_tab_unique" - Column | Type | Key? | Definition ---------+---------+------+------------ - id | integer | yes | id -unique, btree, for table "testschema.test_tab" -Tablespace: "regress_tblspace" - -\d testschema.test_tab_pkey - Index "testschema.test_tab_pkey" - Column | Type | Key? | Definition ---------+---------+------+------------ - id | integer | yes | id -primary key, btree, for table "testschema.test_tab" - -SELECT * FROM testschema.test_tab; - id ----- - 1 -(1 row) - -DROP TABLE testschema.test_tab; --- check that default_tablespace is handled correctly by multi-command --- ALTER TABLE that includes a tablespace-preserving rewrite -CREATE TABLE testschema.test_tab(a int, b int, c int); -SET default_tablespace TO regress_tblspace; -ALTER TABLE testschema.test_tab ADD CONSTRAINT test_tab_unique UNIQUE (a); -CREATE INDEX test_tab_a_idx ON testschema.test_tab (a); -SET default_tablespace TO ''; -CREATE INDEX test_tab_b_idx ON testschema.test_tab (b); -\d testschema.test_tab_unique - Index "testschema.test_tab_unique" - Column | Type | Key? | Definition ---------+---------+------+------------ - a | integer | yes | a -unique, btree, for table "testschema.test_tab" -Tablespace: "regress_tblspace" - -\d testschema.test_tab_a_idx - Index "testschema.test_tab_a_idx" - Column | Type | Key? | Definition ---------+---------+------+------------ - a | integer | yes | a -btree, for table "testschema.test_tab" -Tablespace: "regress_tblspace" - -\d testschema.test_tab_b_idx - Index "testschema.test_tab_b_idx" - Column | Type | Key? | Definition ---------+---------+------+------------ - b | integer | yes | b -btree, for table "testschema.test_tab" - -ALTER TABLE testschema.test_tab ALTER b TYPE bigint, ADD UNIQUE (c); -\d testschema.test_tab_unique - Index "testschema.test_tab_unique" - Column | Type | Key? | Definition ---------+---------+------+------------ - a | integer | yes | a -unique, btree, for table "testschema.test_tab" -Tablespace: "regress_tblspace" - -\d testschema.test_tab_a_idx - Index "testschema.test_tab_a_idx" - Column | Type | Key? | Definition ---------+---------+------+------------ - a | integer | yes | a -btree, for table "testschema.test_tab" -Tablespace: "regress_tblspace" - -\d testschema.test_tab_b_idx - Index "testschema.test_tab_b_idx" - Column | Type | Key? | Definition ---------+--------+------+------------ - b | bigint | yes | b -btree, for table "testschema.test_tab" - -DROP TABLE testschema.test_tab; --- let's try moving a table from one place to another -CREATE TABLE testschema.atable AS VALUES (1), (2); -CREATE UNIQUE INDEX anindex ON testschema.atable(column1); -ALTER TABLE testschema.atable SET TABLESPACE regress_tblspace; -ALTER INDEX testschema.anindex SET TABLESPACE regress_tblspace; -ALTER INDEX testschema.part_a_idx SET TABLESPACE pg_global; -ERROR: only shared relations can be placed in pg_global tablespace -ALTER INDEX testschema.part_a_idx SET TABLESPACE pg_default; -ALTER INDEX testschema.part_a_idx SET TABLESPACE regress_tblspace; -INSERT INTO testschema.atable VALUES(3); -- ok -INSERT INTO testschema.atable VALUES(1); -- fail (checks index) -ERROR: duplicate key value violates unique constraint "anindex" -DETAIL: Key (column1)=(1) already exists. -SELECT COUNT(*) FROM testschema.atable; -- checks heap - count -------- - 3 -(1 row) - --- let's try moving a materialized view from one place to another -CREATE MATERIALIZED VIEW testschema.amv AS SELECT * FROM testschema.atable; -ALTER MATERIALIZED VIEW testschema.amv SET TABLESPACE regress_tblspace; -REFRESH MATERIALIZED VIEW testschema.amv; -SELECT COUNT(*) FROM testschema.amv; - count -------- - 3 -(1 row) - --- Will fail with bad path -CREATE TABLESPACE regress_badspace LOCATION '/no/such/location'; -ERROR: directory "/no/such/location" does not exist --- No such tablespace -CREATE TABLE bar (i int) TABLESPACE regress_nosuchspace; -ERROR: tablespace "regress_nosuchspace" does not exist --- Fail, in use for some partitioned object -DROP TABLESPACE regress_tblspace; -ERROR: tablespace "regress_tblspace" cannot be dropped because some objects depend on it -DETAIL: tablespace for index testschema.part_a_idx -ALTER INDEX testschema.part_a_idx SET TABLESPACE pg_default; --- Fail, not empty -DROP TABLESPACE regress_tblspace; -ERROR: tablespace "regress_tblspace" is not empty -CREATE ROLE regress_tablespace_user1 login; -CREATE ROLE regress_tablespace_user2 login; -GRANT USAGE ON SCHEMA testschema TO regress_tablespace_user2; -ALTER TABLESPACE regress_tblspace OWNER TO regress_tablespace_user1; -CREATE TABLE testschema.tablespace_acl (c int); --- new owner lacks permission to create this index from scratch -CREATE INDEX k ON testschema.tablespace_acl (c) TABLESPACE regress_tblspace; -ALTER TABLE testschema.tablespace_acl OWNER TO regress_tablespace_user2; -SET SESSION ROLE regress_tablespace_user2; -CREATE TABLE tablespace_table (i int) TABLESPACE regress_tblspace; -- fail -ERROR: permission denied for tablespace regress_tblspace -ALTER TABLE testschema.tablespace_acl ALTER c TYPE bigint; -REINDEX (TABLESPACE regress_tblspace) TABLE tablespace_table; -- fail -ERROR: permission denied for tablespace regress_tblspace -REINDEX (TABLESPACE regress_tblspace, CONCURRENTLY) TABLE tablespace_table; -- fail -ERROR: permission denied for tablespace regress_tblspace -RESET ROLE; -ALTER TABLESPACE regress_tblspace RENAME TO regress_tblspace_renamed; -ALTER TABLE ALL IN TABLESPACE regress_tblspace_renamed SET TABLESPACE pg_default; -ALTER INDEX ALL IN TABLESPACE regress_tblspace_renamed SET TABLESPACE pg_default; -ALTER MATERIALIZED VIEW ALL IN TABLESPACE regress_tblspace_renamed SET TABLESPACE pg_default; --- Should show notice that nothing was done -ALTER TABLE ALL IN TABLESPACE regress_tblspace_renamed SET TABLESPACE pg_default; -NOTICE: no matching relations in tablespace "regress_tblspace_renamed" found -ALTER MATERIALIZED VIEW ALL IN TABLESPACE regress_tblspace_renamed SET TABLESPACE pg_default; -NOTICE: no matching relations in tablespace "regress_tblspace_renamed" found --- Should succeed -DROP TABLESPACE regress_tblspace_renamed; -DROP SCHEMA testschema CASCADE; -NOTICE: drop cascades to 7 other objects -DETAIL: drop cascades to table testschema.foo -drop cascades to table testschema.asselect -drop cascades to table testschema.asexecute -drop cascades to table testschema.part -drop cascades to table testschema.atable -drop cascades to materialized view testschema.amv -drop cascades to table testschema.tablespace_acl -DROP ROLE regress_tablespace_user1; -DROP ROLE regress_tablespace_user2; +psql: error: connection to server on socket "/tmp/bBabKAIw6z/.s.PGSQL.19533" failed: No such file or directory + Is the server running locally and accepting connections on that socket?