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-20 04:07:19.054812578 +0000
+++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/plpgsql.out 2024-09-20 04:17:08.985564893 +0000
@@ -3979,1876 +3979,10 @@
end;
$$ language plpgsql;
select raise_test();
-ERROR: 1234F
-CONTEXT: PL/pgSQL function raise_test() line 3 at RAISE
-create or replace function raise_test() returns void as $$
-begin
- raise division_by_zero using message = 'custom' || ' message';
-end;
-$$ language plpgsql;
-select raise_test();
-ERROR: custom message
-CONTEXT: PL/pgSQL function raise_test() line 3 at RAISE
-create or replace function raise_test() returns void as $$
-begin
- raise using message = 'custom' || ' message', errcode = '22012';
-end;
-$$ language plpgsql;
-select raise_test();
-ERROR: custom message
-CONTEXT: PL/pgSQL function raise_test() line 3 at RAISE
--- conflict on message
-create or replace function raise_test() returns void as $$
-begin
- raise notice 'some message' using message = 'custom' || ' message', errcode = '22012';
-end;
-$$ language plpgsql;
-select raise_test();
-ERROR: RAISE option already specified: MESSAGE
-CONTEXT: PL/pgSQL function raise_test() line 3 at RAISE
--- conflict on errcode
-create or replace function raise_test() returns void as $$
-begin
- raise division_by_zero using message = 'custom' || ' message', errcode = '22012';
-end;
-$$ language plpgsql;
-select raise_test();
-ERROR: RAISE option already specified: ERRCODE
-CONTEXT: PL/pgSQL function raise_test() line 3 at RAISE
--- nothing to re-RAISE
-create or replace function raise_test() returns void as $$
-begin
- 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-20 04:07:18.994684689 +0000
+++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/alter_table.out 2024-09-20 04:17:09.005564873 +0000
@@ -1687,3020 +1687,10 @@
alter table renameColumnChild rename column a to d;
ERROR: cannot rename inherited column "a"
alter table only renameColumnChild rename column a to d;
-ERROR: inherited column "a" must be renamed in child tables too
-alter table only renameColumn rename column a to d;
-ERROR: inherited column "a" must be renamed in child tables too
--- these should work
-alter table renameColumn rename column a to d;
-alter table renameColumnChild rename column b to a;
--- these should work
-alter table if exists doesnt_exist_tab rename column a to d;
-NOTICE: relation "doesnt_exist_tab" does not exist, skipping
-alter table if exists doesnt_exist_tab rename column b to a;
-NOTICE: relation "doesnt_exist_tab" does not exist, skipping
--- this should work
-alter table renameColumn add column w int;
--- this should fail
-alter table only renameColumn add column x int;
-ERROR: column must be added to child tables too
--- Test corner cases in dropping of inherited columns
-create table p1 (f1 int, f2 int);
-create table c1 (f1 int not null) inherits(p1);
-NOTICE: merging column "f1" with inherited definition
--- should be rejected since c1.f1 is inherited
-alter table c1 drop column f1;
-ERROR: cannot drop inherited column "f1"
--- should work
-alter table p1 drop column f1;
--- c1.f1 is still there, but no longer inherited
-select f1 from c1;
- f1
-----
-(0 rows)
-
-alter table c1 drop column f1;
-select f1 from c1;
-ERROR: column or variable "f1" does not exist
-LINE 1: select f1 from c1;
- ^
-HINT: Perhaps you meant to reference the column "c1.f2".
-drop table p1 cascade;
-NOTICE: drop cascades to table c1
-create table p1 (f1 int, f2 int);
-create table c1 () inherits(p1);
--- should be rejected since c1.f1 is inherited
-alter table c1 drop column f1;
-ERROR: cannot drop inherited column "f1"
-alter table p1 drop column f1;
--- c1.f1 is dropped now, since there is no local definition for it
-select f1 from c1;
-ERROR: column or variable "f1" does not exist
-LINE 1: select f1 from c1;
- ^
-HINT: Perhaps you meant to reference the column "c1.f2".
-drop table p1 cascade;
-NOTICE: drop cascades to table c1
-create table p1 (f1 int, f2 int);
-create table c1 () inherits(p1);
--- should be rejected since c1.f1 is inherited
-alter table c1 drop column f1;
-ERROR: cannot drop inherited column "f1"
-alter table only p1 drop column f1;
--- c1.f1 is NOT dropped, but must now be considered non-inherited
-alter table c1 drop column f1;
-drop table p1 cascade;
-NOTICE: drop cascades to table c1
-create table p1 (f1 int, f2 int);
-create table c1 (f1 int not null) inherits(p1);
-NOTICE: merging column "f1" with inherited definition
--- should be rejected since c1.f1 is inherited
-alter table c1 drop column f1;
-ERROR: cannot drop inherited column "f1"
-alter table only p1 drop column f1;
--- c1.f1 is still there, but no longer inherited
-alter table c1 drop column f1;
-drop table p1 cascade;
-NOTICE: drop cascades to table c1
-create table p1(id int, name text);
-create table p2(id2 int, name text, height int);
-create table c1(age int) inherits(p1,p2);
-NOTICE: merging multiple inherited definitions of column "name"
-create table gc1() inherits (c1);
-select relname, attname, attinhcount, attislocal
-from pg_class join pg_attribute on (pg_class.oid = pg_attribute.attrelid)
-where relname in ('p1','p2','c1','gc1') and attnum > 0 and not attisdropped
-order by relname, attnum;
- relname | attname | attinhcount | attislocal
----------+---------+-------------+------------
- c1 | id | 1 | f
- c1 | name | 2 | f
- c1 | id2 | 1 | f
- c1 | height | 1 | f
- c1 | age | 0 | t
- gc1 | id | 1 | f
- gc1 | name | 1 | f
- gc1 | id2 | 1 | f
- gc1 | height | 1 | f
- gc1 | age | 1 | f
- p1 | id | 0 | t
- p1 | name | 0 | t
- p2 | id2 | 0 | t
- p2 | name | 0 | t
- p2 | height | 0 | t
-(15 rows)
-
--- should work
-alter table only p1 drop column name;
--- should work. Now c1.name is local and inhcount is 0.
-alter table p2 drop column name;
--- should be rejected since its inherited
-alter table gc1 drop column name;
-ERROR: cannot drop inherited column "name"
--- should work, and drop gc1.name along
-alter table c1 drop column name;
--- should fail: column does not exist
-alter table gc1 drop column name;
-ERROR: column "name" of relation "gc1" does not exist
--- should work and drop the attribute in all tables
-alter table p2 drop column height;
--- IF EXISTS test
-create table dropColumnExists ();
-alter table dropColumnExists drop column non_existing; --fail
-ERROR: column "non_existing" of relation "dropcolumnexists" does not exist
-alter table dropColumnExists drop column if exists non_existing; --succeed
-NOTICE: column "non_existing" of relation "dropcolumnexists" does not exist, skipping
-select relname, attname, attinhcount, attislocal
-from pg_class join pg_attribute on (pg_class.oid = pg_attribute.attrelid)
-where relname in ('p1','p2','c1','gc1') and attnum > 0 and not attisdropped
-order by relname, attnum;
- relname | attname | attinhcount | attislocal
----------+---------+-------------+------------
- c1 | id | 1 | f
- c1 | id2 | 1 | f
- c1 | age | 0 | t
- gc1 | id | 1 | f
- gc1 | id2 | 1 | f
- gc1 | age | 1 | f
- p1 | id | 0 | t
- p2 | id2 | 0 | t
-(8 rows)
-
-drop table p1, p2 cascade;
-NOTICE: drop cascades to 2 other objects
-DETAIL: drop cascades to table c1
-drop cascades to table gc1
--- test attinhcount tracking with merged columns
-create table depth0();
-create table depth1(c text) inherits (depth0);
-create table depth2() inherits (depth1);
-alter table depth0 add c text;
-NOTICE: merging definition of column "c" for child "depth1"
-select attrelid::regclass, attname, attinhcount, attislocal
-from pg_attribute
-where attnum > 0 and attrelid::regclass in ('depth0', 'depth1', 'depth2')
-order by attrelid::regclass::text, attnum;
- attrelid | attname | attinhcount | attislocal
-----------+---------+-------------+------------
- depth0 | c | 0 | t
- depth1 | c | 1 | t
- depth2 | c | 1 | f
-(3 rows)
-
--- test renumbering of child-table columns in inherited operations
-create table p1 (f1 int);
-create table c1 (f2 text, f3 int) inherits (p1);
-alter table p1 add column a1 int check (a1 > 0);
-alter table p1 add column f2 text;
-NOTICE: merging definition of column "f2" for child "c1"
-insert into p1 values (1,2,'abc');
-insert into c1 values(11,'xyz',33,0); -- should fail
-ERROR: new row for relation "c1" violates check constraint "p1_a1_check"
-DETAIL: Failing row contains (11, xyz, 33, 0).
-insert into c1 values(11,'xyz',33,22);
-select * from p1;
- f1 | a1 | f2
-----+----+-----
- 1 | 2 | abc
- 11 | 22 | xyz
-(2 rows)
-
-update p1 set a1 = a1 + 1, f2 = upper(f2);
-select * from p1;
- f1 | a1 | f2
-----+----+-----
- 1 | 3 | ABC
- 11 | 23 | XYZ
-(2 rows)
-
-drop table p1 cascade;
-NOTICE: drop cascades to table c1
--- test that operations with a dropped column do not try to reference
--- its datatype
-create domain mytype as text;
-create temp table foo (f1 text, f2 mytype, f3 text);
-insert into foo values('bb','cc','dd');
-select * from foo;
- f1 | f2 | f3
-----+----+----
- bb | cc | dd
-(1 row)
-
-drop domain mytype cascade;
-NOTICE: drop cascades to column f2 of table foo
-select * from foo;
- f1 | f3
-----+----
- bb | dd
-(1 row)
-
-insert into foo values('qq','rr');
-select * from foo;
- f1 | f3
-----+----
- bb | dd
- qq | rr
-(2 rows)
-
-update foo set f3 = 'zz';
-select * from foo;
- f1 | f3
-----+----
- bb | zz
- qq | zz
-(2 rows)
-
-select f3,max(f1) from foo group by f3;
- f3 | max
-----+-----
- zz | qq
-(1 row)
-
--- Simple tests for alter table column type
-alter table foo alter f1 TYPE integer; -- fails
-ERROR: column "f1" cannot be cast automatically to type integer
-HINT: You might need to specify "USING f1::integer".
-alter table foo alter f1 TYPE varchar(10);
-create table anothertab (atcol1 serial8, atcol2 boolean,
- constraint anothertab_chk check (atcol1 <= 3));
-insert into anothertab (atcol1, atcol2) values (default, true);
-insert into anothertab (atcol1, atcol2) values (default, false);
-select * from anothertab;
- atcol1 | atcol2
---------+--------
- 1 | t
- 2 | f
-(2 rows)
-
-alter table anothertab alter column atcol1 type boolean; -- fails
-ERROR: column "atcol1" cannot be cast automatically to type boolean
-HINT: You might need to specify "USING atcol1::boolean".
-alter table anothertab alter column atcol1 type boolean using atcol1::int; -- fails
-ERROR: result of USING clause for column "atcol1" cannot be cast automatically to type boolean
-HINT: You might need to add an explicit cast.
-alter table anothertab alter column atcol1 type integer;
-select * from anothertab;
- atcol1 | atcol2
---------+--------
- 1 | t
- 2 | f
-(2 rows)
-
-insert into anothertab (atcol1, atcol2) values (45, null); -- fails
-ERROR: new row for relation "anothertab" violates check constraint "anothertab_chk"
-DETAIL: Failing row contains (45, null).
-insert into anothertab (atcol1, atcol2) values (default, null);
-select * from anothertab;
- atcol1 | atcol2
---------+--------
- 1 | t
- 2 | f
- 3 |
-(3 rows)
-
-alter table anothertab alter column atcol2 type text
- using case when atcol2 is true then 'IT WAS TRUE'
- when atcol2 is false then 'IT WAS FALSE'
- else 'IT WAS NULL!' end;
-select * from anothertab;
- atcol1 | atcol2
---------+--------------
- 1 | IT WAS TRUE
- 2 | IT WAS FALSE
- 3 | IT WAS NULL!
-(3 rows)
-
-alter table anothertab alter column atcol1 type boolean
- using case when atcol1 % 2 = 0 then true else false end; -- fails
-ERROR: default for column "atcol1" cannot be cast automatically to type boolean
-alter table anothertab alter column atcol1 drop default;
-alter table anothertab alter column atcol1 type boolean
- using case when atcol1 % 2 = 0 then true else false end; -- fails
-ERROR: operator does not exist: boolean <= integer
-HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
-alter table anothertab drop constraint anothertab_chk;
-alter table anothertab drop constraint anothertab_chk; -- fails
-ERROR: constraint "anothertab_chk" of relation "anothertab" does not exist
-alter table anothertab drop constraint IF EXISTS anothertab_chk; -- succeeds
-NOTICE: constraint "anothertab_chk" of relation "anothertab" does not exist, skipping
-alter table anothertab alter column atcol1 type boolean
- using case when atcol1 % 2 = 0 then true else false end;
-select * from anothertab;
- atcol1 | atcol2
---------+--------------
- f | IT WAS TRUE
- t | IT WAS FALSE
- f | IT WAS NULL!
-(3 rows)
-
-drop table anothertab;
--- Test index handling in alter table column type (cf. bugs #15835, #15865)
-create table anothertab(f1 int primary key, f2 int unique,
- f3 int, f4 int, f5 int);
-alter table anothertab
- add exclude using btree (f3 with =);
-alter table anothertab
- add exclude using btree (f4 with =) where (f4 is not null);
-alter table anothertab
- add exclude using btree (f4 with =) where (f5 > 0);
-alter table anothertab
- add unique(f1,f4);
-create index on anothertab(f2,f3);
-create unique index on anothertab(f4);
-\d anothertab
- Table "public.anothertab"
- Column | Type | Collation | Nullable | Default
---------+---------+-----------+----------+---------
- f1 | integer | | not null |
- f2 | integer | | |
- f3 | integer | | |
- f4 | integer | | |
- f5 | integer | | |
-Indexes:
- "anothertab_pkey" PRIMARY KEY, btree (f1)
- "anothertab_f1_f4_key" UNIQUE CONSTRAINT, btree (f1, f4)
- "anothertab_f2_f3_idx" btree (f2, f3)
- "anothertab_f2_key" UNIQUE CONSTRAINT, btree (f2)
- "anothertab_f3_excl" EXCLUDE USING btree (f3 WITH =)
- "anothertab_f4_excl" EXCLUDE USING btree (f4 WITH =) WHERE (f4 IS NOT NULL)
- "anothertab_f4_excl1" EXCLUDE USING btree (f4 WITH =) WHERE (f5 > 0)
- "anothertab_f4_idx" UNIQUE, btree (f4)
-
-alter table anothertab alter column f1 type bigint;
-alter table anothertab
- alter column f2 type bigint,
- alter column f3 type bigint,
- alter column f4 type bigint;
-alter table anothertab alter column f5 type bigint;
-\d anothertab
- Table "public.anothertab"
- Column | Type | Collation | Nullable | Default
---------+--------+-----------+----------+---------
- f1 | bigint | | not null |
- f2 | bigint | | |
- f3 | bigint | | |
- f4 | bigint | | |
- f5 | bigint | | |
-Indexes:
- "anothertab_pkey" PRIMARY KEY, btree (f1)
- "anothertab_f1_f4_key" UNIQUE CONSTRAINT, btree (f1, f4)
- "anothertab_f2_f3_idx" btree (f2, f3)
- "anothertab_f2_key" UNIQUE CONSTRAINT, btree (f2)
- "anothertab_f3_excl" EXCLUDE USING btree (f3 WITH =)
- "anothertab_f4_excl" EXCLUDE USING btree (f4 WITH =) WHERE (f4 IS NOT NULL)
- "anothertab_f4_excl1" EXCLUDE USING btree (f4 WITH =) WHERE (f5 > 0)
- "anothertab_f4_idx" UNIQUE, btree (f4)
-
-drop table anothertab;
--- test that USING expressions are parsed before column alter type / drop steps
-create table another (f1 int, f2 text, f3 text);
-insert into another values(1, 'one', 'uno');
-insert into another values(2, 'two', 'due');
-insert into another values(3, 'three', 'tre');
-select * from another;
- f1 | f2 | f3
-----+-------+-----
- 1 | one | uno
- 2 | two | due
- 3 | three | tre
-(3 rows)
-
-alter table another
- alter f1 type text using f2 || ' and ' || f3 || ' more',
- alter f2 type bigint using f1 * 10,
- drop column f3;
-select * from another;
- f1 | f2
---------------------+----
- one and uno more | 10
- two and due more | 20
- three and tre more | 30
-(3 rows)
-
-drop table another;
--- Create an index that skips WAL, then perform a SET DATA TYPE that skips
--- rewriting the index.
-begin;
-create table skip_wal_skip_rewrite_index (c varchar(10) primary key);
-alter table skip_wal_skip_rewrite_index alter c type varchar(20);
-commit;
--- We disallow changing table's row type if it's used for storage
-create table at_tab1 (a int, b text);
-create table at_tab2 (x int, y at_tab1);
-alter table at_tab1 alter column b type varchar; -- fails
-ERROR: cannot alter table "at_tab1" because column "at_tab2.y" uses its row type
-drop table at_tab2;
--- Use of row type in an expression is defended differently
-create table at_tab2 (x int, y text, check((x,y)::at_tab1 = (1,'42')::at_tab1));
-alter table at_tab1 alter column b type varchar; -- allowed, but ...
-insert into at_tab2 values(1,'42'); -- ... this will fail
-ERROR: ROW() column has type text instead of type character varying
-drop table at_tab1, at_tab2;
--- Check it for a partitioned table, too
-create table at_tab1 (a int, b text) partition by list(a);
-create table at_tab2 (x int, y at_tab1);
-alter table at_tab1 alter column b type varchar; -- fails
-ERROR: cannot alter table "at_tab1" because column "at_tab2.y" uses its row type
-drop table at_tab1, at_tab2;
--- Alter column type that's part of a partitioned index
-create table at_partitioned (a int, b text) partition by range (a);
-create table at_part_1 partition of at_partitioned for values from (0) to (1000);
-insert into at_partitioned values (512, '0.123');
-create table at_part_2 (b text, a int);
-insert into at_part_2 values ('1.234', 1024);
-create index on at_partitioned (b);
-create index on at_partitioned (a);
-\d at_part_1
- Table "public.at_part_1"
- Column | Type | Collation | Nullable | Default
---------+---------+-----------+----------+---------
- a | integer | | |
- b | text | | |
-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 | 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-20 04:07:19.098903800 +0000
+++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/xml.out 2024-09-20 04:17:09.041564838 +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('
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-20 04:07:19.074854042 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/session_variables.out 2024-09-20 04:17:08.581565316 +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-20 04:07:19.050804285 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/partition_join.out 2024-09-20 04:17:09.121564759 +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/f_SDOOqtof/.s.PGSQL.16412" 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-20 04:07:19.054812578 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/partition_prune.out 2024-09-20 04:17:09.125564756 +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/f_SDOOqtof/.s.PGSQL.16412" 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-20 04:07:19.066837457 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/reloptions.out 2024-09-20 04:17:09.121564759 +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/f_SDOOqtof/.s.PGSQL.16412" 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-20 04:07:19.022746235 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/hash_part.out 2024-09-20 04:17:09.161564720 +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/f_SDOOqtof/.s.PGSQL.16412" 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-20 04:07:19.022746235 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/indexing.out 2024-09-20 04:17:09.153564728 +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/f_SDOOqtof/.s.PGSQL.16412" 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-20 04:07:19.050804285 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/partition_aggregate.out 2024-09-20 04:17:09.157564724 +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/f_SDOOqtof/.s.PGSQL.16412" 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-20 04:07:19.050804285 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/partition_info.out 2024-09-20 04:17:09.145564736 +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/f_SDOOqtof/.s.PGSQL.16412" 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-20 04:07:19.090887213 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/tuplesort.out 2024-09-20 04:17:09.137564744 +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/f_SDOOqtof/.s.PGSQL.16412" 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-20 04:07:19.010721357 +0000 +++ /tmp/cirrus-ci-build/src/test/recovery/tmp_check/results/explain.out 2024-09-20 04:17:09.137564744 +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 --------------------------------------------------------- -