diff --git a/.gitignore b/.gitignore index f627990d..1bc422a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .deps -isolation_output results/* regression.diffs regression.out diff --git a/.travis.yml b/.travis.yml index b020780b..411c98aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,6 @@ -sudo: required +os: linux + +dist: focal language: c @@ -18,20 +20,15 @@ notifications: on_failure: always env: + - PG_VERSION=16 LEVEL=hardcore + - PG_VERSION=16 + - PG_VERSION=15 LEVEL=hardcore + - PG_VERSION=15 + - PG_VERSION=14 LEVEL=hardcore + - PG_VERSION=14 - PG_VERSION=13 LEVEL=hardcore - PG_VERSION=13 - PG_VERSION=12 LEVEL=hardcore - PG_VERSION=12 - PG_VERSION=11 LEVEL=hardcore - PG_VERSION=11 - - PG_VERSION=10 LEVEL=hardcore - - PG_VERSION=10 - - PG_VERSION=9.6 LEVEL=hardcore - - PG_VERSION=9.6 - - PG_VERSION=9.5 LEVEL=hardcore - - PG_VERSION=9.5 - -matrix: - allow_failures: - - env: PG_VERSION=10 LEVEL=nightmare - - env: PG_VERSION=9.6 LEVEL=nightmare diff --git a/Dockerfile.tmpl b/Dockerfile.tmpl index 85b159cf..4dd24ca5 100644 --- a/Dockerfile.tmpl +++ b/Dockerfile.tmpl @@ -2,14 +2,14 @@ FROM postgres:${PG_VERSION}-alpine # Install dependencies RUN apk add --no-cache \ - openssl curl \ + openssl curl git patch \ cmocka-dev \ perl perl-ipc-run \ python3 python3-dev py3-virtualenv \ coreutils linux-headers \ make musl-dev gcc bison flex \ zlib-dev libedit-dev \ - clang clang-analyzer; + pkgconf icu-dev clang clang15 clang-analyzer; # Install fresh valgrind RUN apk add valgrind \ @@ -29,8 +29,8 @@ ADD . /pg/testdir # Grant privileges RUN chown -R postgres:postgres ${PGDATA} && \ chown -R postgres:postgres /pg/testdir && \ - chmod a+rwx /usr/local/lib/postgresql && \ - chmod a+rwx /usr/local/share/postgresql/extension + chmod a+rwx /usr/local/share/postgresql/extension && \ + find /usr/local/lib/postgresql -type d -print0 | xargs -0 chmod a+rwx COPY run_tests.sh /run.sh RUN chmod 755 /run.sh diff --git a/Makefile b/Makefile index c3fe4038..f6780044 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,7 @@ DATA = pg_pathman--1.0--1.1.sql \ PGFILEDESC = "pg_pathman - partitioning tool for PostgreSQL" +ifneq (pg_pathman,$(filter pg_pathman,$(PG_TEST_SKIP))) REGRESS = pathman_array_qual \ pathman_basic \ pathman_bgw \ @@ -63,12 +64,15 @@ REGRESS = pathman_array_qual \ pathman_utility_stmt \ pathman_views \ pathman_CVE-2020-14350 +endif +ISOLATION = insert_nodes for_update rollback_on_create_partitions -EXTRA_REGRESS_OPTS=--temp-config=$(top_srcdir)/$(subdir)/conf.add +REGRESS_OPTS = --temp-config $(top_srcdir)/$(subdir)/conf.add +ISOLATION_OPTS = --temp-config $(top_srcdir)/$(subdir)/conf.add CMOCKA_EXTRA_CLEAN = missing_basic.o missing_list.o missing_stringinfo.o missing_bitmapset.o rangeset_tests.o rangeset_tests -EXTRA_CLEAN = ./isolation_output $(patsubst %,tests/cmocka/%, $(CMOCKA_EXTRA_CLEAN)) +EXTRA_CLEAN = $(patsubst %,tests/cmocka/%, $(CMOCKA_EXTRA_CLEAN)) ifdef USE_PGXS PG_CONFIG=pg_config @@ -83,6 +87,14 @@ OBJS += src/declarative.o override PG_CPPFLAGS += -DENABLE_DECLARATIVE endif +# We cannot run isolation test for versions 12,13 in PGXS case +# because 'pg_isolation_regress' is not copied to install +# directory, see src/test/isolation/Makefile +ifeq ($(VNUM),$(filter 12% 13%,$(VNUM))) +undefine ISOLATION +undefine ISOLATION_OPTS +endif + include $(PGXS) else subdir = contrib/pg_pathman @@ -94,18 +106,6 @@ endif $(EXTENSION)--$(EXTVERSION).sql: init.sql hash.sql range.sql cat $^ > $@ -ISOLATIONCHECKS=insert_nodes for_update rollback_on_create_partitions - -submake-isolation: - $(MAKE) -C $(top_builddir)/src/test/isolation all - -isolationcheck: | submake-isolation - $(MKDIR_P) isolation_output - $(pg_isolation_regress_check) \ - --temp-config=$(top_srcdir)/$(subdir)/conf.add \ - --outputdir=./isolation_output \ - $(ISOLATIONCHECKS) - python_tests: $(MAKE) -C tests/python partitioning_tests CASE=$(CASE) diff --git a/README.md b/README.md index 94133b32..1394bc6f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -[![Build Status](https://travis-ci.org/postgrespro/pg_pathman.svg?branch=master)](https://travis-ci.org/postgrespro/pg_pathman) +[![Build Status](https://travis-ci.com/postgrespro/pg_pathman.svg?branch=master)](https://travis-ci.com/postgrespro/pg_pathman) [![PGXN version](https://badge.fury.io/pg/pg_pathman.svg)](https://badge.fury.io/pg/pg_pathman) [![codecov](https://codecov.io/gh/postgrespro/pg_pathman/branch/master/graph/badge.svg)](https://codecov.io/gh/postgrespro/pg_pathman) [![GitHub license](https://img.shields.io/badge/license-PostgreSQL-blue.svg)](https://raw.githubusercontent.com/postgrespro/pg_pathman/master/LICENSE) ### NOTE: this project is not under development anymore -`pg_pathman` supports Postgres versions [9.5..13], but most probably it won't be ported to 14 and later releases. [Native partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) is pretty mature now and has almost everything implemented in `pg_pathman`'; we encourage users switching to it. We are still maintaining the project (fixing bugs in supported versions), but no new development is going to happen here. +`pg_pathman` supports Postgres versions [11..15], but most probably it won't be ported to later releases. [Native partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) is pretty mature now and has almost everything implemented in `pg_pathman`'; we encourage users switching to it. We are still maintaining the project (fixing bugs in supported versions), but no new development is going to happen here. # pg_pathman @@ -13,8 +13,9 @@ The `pg_pathman` module provides optimized partitioning mechanism and functions The extension is compatible with: - * PostgreSQL 9.5, 9.6, 10, 11, 12, 13; - * Postgres Pro Standard 9.5, 9.6, 10, 11, 12; + * PostgreSQL 12, 13; + * PostgreSQL with core-patch: 11, 14, 15; + * Postgres Pro Standard 11, 12, 13, 14, 15; * Postgres Pro Enterprise; Take a look at our Wiki [out there](https://github.com/postgrespro/pg_pathman/wiki). @@ -789,7 +790,7 @@ Do not hesitate to post your issues, questions and new ideas at the [issues](htt ## Authors [Ildar Musin](https://github.com/zilder) -Alexander Korotkov Postgres Professional Ltd., Russia +[Alexander Korotkov](https://github.com/akorotkov) [Dmitry Ivanov](https://github.com/funbringer) -Maksim Milyutin Postgres Professional Ltd., Russia +[Maksim Milyutin](https://github.com/maksm90) [Ildus Kurbangaliev](https://github.com/ildus) diff --git a/docker-compose.yml b/docker-compose.yml index 471ab779..0544d859 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,2 +1,3 @@ -tests: +services: + tests: build: . diff --git a/expected/for_update.out b/expected/for_update.out index 3e41031e..ffd425e4 100644 --- a/expected/for_update.out +++ b/expected/for_update.out @@ -2,37 +2,49 @@ Parsed test spec with 2 sessions starting permutation: s1_b s1_update s2_select s1_r create_range_partitions +----------------------- + 10 +(1 row) -10 step s1_b: begin; step s1_update: update test_tbl set id = 2 where id = 1; step s2_select: select * from test_tbl where id = 1; -id val +id|val +--+--- + 1| 1 +(1 row) -1 1 step s1_r: rollback; starting permutation: s1_b s1_update s2_select_locked s1_r create_range_partitions +----------------------- + 10 +(1 row) -10 step s1_b: begin; step s1_update: update test_tbl set id = 2 where id = 1; step s2_select_locked: select * from test_tbl where id = 1 for share; step s1_r: rollback; step s2_select_locked: <... completed> -id val +id|val +--+--- + 1| 1 +(1 row) -1 1 starting permutation: s1_b s1_update s2_select_locked s1_c create_range_partitions +----------------------- + 10 +(1 row) -10 step s1_b: begin; step s1_update: update test_tbl set id = 2 where id = 1; step s2_select_locked: select * from test_tbl where id = 1 for share; step s1_c: commit; step s2_select_locked: <... completed> -id val +id|val +--+--- +(0 rows) diff --git a/expected/insert_nodes.out b/expected/insert_nodes.out index 64758aef..8f725216 100644 --- a/expected/insert_nodes.out +++ b/expected/insert_nodes.out @@ -2,122 +2,126 @@ Parsed test spec with 2 sessions starting permutation: s1b s1_insert_150 s1r s1_show_partitions s2b s2_insert_150 s2c s2_show_partitions set_spawn_using_bgw +------------------- + +(1 row) - step s1b: BEGIN; step s1_insert_150: INSERT INTO range_rel SELECT generate_series(1, 150); step s1r: ROLLBACK; -step s1_show_partitions: SELECT c.consrc FROM pg_inherits i LEFT JOIN pg_constraint c +step s1_show_partitions: SELECT pg_get_constraintdef(c.oid) FROM pg_inherits i LEFT JOIN pg_constraint c ON c.conrelid = i.inhrelid - WHERE i.inhparent = 'range_rel'::regclass + WHERE i.inhparent = 'range_rel'::regclass AND c.contype = 'c' ORDER BY c.oid; -consrc +pg_get_constraintdef +------------------------------------ +CHECK (((id >= 1) AND (id < 101))) +CHECK (((id >= 101) AND (id < 201))) +(2 rows) - -((id >= 1) AND (id < 101)) - -((id >= 101) AND (id < 201)) step s2b: BEGIN; step s2_insert_150: INSERT INTO range_rel SELECT generate_series(1, 150); step s2c: COMMIT; -step s2_show_partitions: SELECT c.consrc FROM pg_inherits i LEFT JOIN pg_constraint c +step s2_show_partitions: SELECT pg_get_constraintdef(c.oid) FROM pg_inherits i LEFT JOIN pg_constraint c ON c.conrelid = i.inhrelid - WHERE i.inhparent = 'range_rel'::regclass + WHERE i.inhparent = 'range_rel'::regclass AND c.contype = 'c' ORDER BY c.oid; -consrc +pg_get_constraintdef +------------------------------------ +CHECK (((id >= 1) AND (id < 101))) +CHECK (((id >= 101) AND (id < 201))) +(2 rows) - -((id >= 1) AND (id < 101)) - -((id >= 101) AND (id < 201)) starting permutation: s1b s1_insert_150 s1r s1_show_partitions s2b s2_insert_300 s2c s2_show_partitions set_spawn_using_bgw +------------------- + +(1 row) - step s1b: BEGIN; step s1_insert_150: INSERT INTO range_rel SELECT generate_series(1, 150); step s1r: ROLLBACK; -step s1_show_partitions: SELECT c.consrc FROM pg_inherits i LEFT JOIN pg_constraint c +step s1_show_partitions: SELECT pg_get_constraintdef(c.oid) FROM pg_inherits i LEFT JOIN pg_constraint c ON c.conrelid = i.inhrelid - WHERE i.inhparent = 'range_rel'::regclass + WHERE i.inhparent = 'range_rel'::regclass AND c.contype = 'c' ORDER BY c.oid; -consrc +pg_get_constraintdef +------------------------------------ +CHECK (((id >= 1) AND (id < 101))) +CHECK (((id >= 101) AND (id < 201))) +(2 rows) - -((id >= 1) AND (id < 101)) - -((id >= 101) AND (id < 201)) step s2b: BEGIN; step s2_insert_300: INSERT INTO range_rel SELECT generate_series(151, 300); step s2c: COMMIT; -step s2_show_partitions: SELECT c.consrc FROM pg_inherits i LEFT JOIN pg_constraint c +step s2_show_partitions: SELECT pg_get_constraintdef(c.oid) FROM pg_inherits i LEFT JOIN pg_constraint c ON c.conrelid = i.inhrelid - WHERE i.inhparent = 'range_rel'::regclass + WHERE i.inhparent = 'range_rel'::regclass AND c.contype = 'c' ORDER BY c.oid; -consrc +pg_get_constraintdef +------------------------------------ +CHECK (((id >= 1) AND (id < 101))) +CHECK (((id >= 101) AND (id < 201))) +CHECK (((id >= 201) AND (id < 301))) +(3 rows) - -((id >= 1) AND (id < 101)) - -((id >= 101) AND (id < 201)) - -((id >= 201) AND (id < 301)) starting permutation: s1b s1_insert_300 s1r s1_show_partitions s2b s2_insert_150 s2c s2_show_partitions set_spawn_using_bgw +------------------- + +(1 row) - step s1b: BEGIN; step s1_insert_300: INSERT INTO range_rel SELECT generate_series(151, 300); step s1r: ROLLBACK; -step s1_show_partitions: SELECT c.consrc FROM pg_inherits i LEFT JOIN pg_constraint c +step s1_show_partitions: SELECT pg_get_constraintdef(c.oid) FROM pg_inherits i LEFT JOIN pg_constraint c ON c.conrelid = i.inhrelid - WHERE i.inhparent = 'range_rel'::regclass + WHERE i.inhparent = 'range_rel'::regclass AND c.contype = 'c' ORDER BY c.oid; -consrc +pg_get_constraintdef +------------------------------------ +CHECK (((id >= 1) AND (id < 101))) +CHECK (((id >= 101) AND (id < 201))) +CHECK (((id >= 201) AND (id < 301))) +(3 rows) - -((id >= 1) AND (id < 101)) - -((id >= 101) AND (id < 201)) - -((id >= 201) AND (id < 301)) step s2b: BEGIN; step s2_insert_150: INSERT INTO range_rel SELECT generate_series(1, 150); step s2c: COMMIT; -step s2_show_partitions: SELECT c.consrc FROM pg_inherits i LEFT JOIN pg_constraint c +step s2_show_partitions: SELECT pg_get_constraintdef(c.oid) FROM pg_inherits i LEFT JOIN pg_constraint c ON c.conrelid = i.inhrelid - WHERE i.inhparent = 'range_rel'::regclass + WHERE i.inhparent = 'range_rel'::regclass AND c.contype = 'c' ORDER BY c.oid; -consrc +pg_get_constraintdef +------------------------------------ +CHECK (((id >= 1) AND (id < 101))) +CHECK (((id >= 101) AND (id < 201))) +CHECK (((id >= 201) AND (id < 301))) +(3 rows) - -((id >= 1) AND (id < 101)) - -((id >= 101) AND (id < 201)) - -((id >= 201) AND (id < 301)) starting permutation: s1b s1_insert_150 s2b s2_insert_300 s1r s2r s2_show_partitions set_spawn_using_bgw +------------------- + +(1 row) - step s1b: BEGIN; step s1_insert_150: INSERT INTO range_rel SELECT generate_series(1, 150); step s2b: BEGIN; step s2_insert_300: INSERT INTO range_rel SELECT generate_series(151, 300); step s1r: ROLLBACK; step s2r: ROLLBACK; -step s2_show_partitions: SELECT c.consrc FROM pg_inherits i LEFT JOIN pg_constraint c +step s2_show_partitions: SELECT pg_get_constraintdef(c.oid) FROM pg_inherits i LEFT JOIN pg_constraint c ON c.conrelid = i.inhrelid - WHERE i.inhparent = 'range_rel'::regclass + WHERE i.inhparent = 'range_rel'::regclass AND c.contype = 'c' ORDER BY c.oid; -consrc +pg_get_constraintdef +------------------------------------ +CHECK (((id >= 1) AND (id < 101))) +CHECK (((id >= 101) AND (id < 201))) +CHECK (((id >= 201) AND (id < 301))) +(3 rows) - -((id >= 1) AND (id < 101)) - -((id >= 101) AND (id < 201)) - -((id >= 201) AND (id < 301)) diff --git a/expected/pathman_CVE-2020-14350.out b/expected/pathman_CVE-2020-14350.out index c91a280f..a48e182f 100644 --- a/expected/pathman_CVE-2020-14350.out +++ b/expected/pathman_CVE-2020-14350.out @@ -7,14 +7,15 @@ DROP FUNCTION IF EXISTS _partition_data_concurrent(oid,integer); DROP FUNCTION IF EXISTS create_single_range_partition(TEXT,ANYELEMENT,ANYELEMENT,TEXT); DROP TABLE IF EXISTS test1 CASCADE; DROP TABLE IF EXISTS test2 CASCADE; -DROP ROLE IF EXISTS regress_hacker; +DROP ROLE IF EXISTS pathman_regress_hacker; SET client_min_messages = 'notice'; +GRANT CREATE ON SCHEMA public TO PUBLIC; CREATE EXTENSION pg_pathman; -CREATE ROLE regress_hacker LOGIN; +CREATE ROLE pathman_regress_hacker LOGIN; -- Test 1 RESET ROLE; -ALTER ROLE regress_hacker NOSUPERUSER; -SET ROLE regress_hacker; +ALTER ROLE pathman_regress_hacker NOSUPERUSER; +SET ROLE pathman_regress_hacker; SHOW is_superuser; is_superuser -------------- @@ -25,7 +26,7 @@ CREATE FUNCTION _partition_data_concurrent(relation oid, p_limit INT, OUT p_tota RETURNS bigint AS $$ BEGIN - ALTER ROLE regress_hacker SUPERUSER; + ALTER ROLE pathman_regress_hacker SUPERUSER; SELECT _partition_data_concurrent(relation, NULL::text, NULL::text, p_limit) INTO p_total; END $$ LANGUAGE plpgsql; @@ -52,7 +53,7 @@ SELECT pg_sleep(1); (1 row) -- Test result (must be 'off') -SET ROLE regress_hacker; +SET ROLE pathman_regress_hacker; SHOW is_superuser; is_superuser -------------- @@ -61,8 +62,8 @@ SHOW is_superuser; -- Test 2 RESET ROLE; -ALTER ROLE regress_hacker NOSUPERUSER; -SET ROLE regress_hacker; +ALTER ROLE pathman_regress_hacker NOSUPERUSER; +SET ROLE pathman_regress_hacker; SHOW is_superuser; is_superuser -------------- @@ -73,7 +74,7 @@ CREATE FUNCTION create_single_range_partition(parent_relid TEXT, start_value ANY RETURNS REGCLASS AS $$ BEGIN - ALTER ROLE regress_hacker SUPERUSER; + ALTER ROLE pathman_regress_hacker SUPERUSER; RETURN create_single_range_partition(parent_relid, start_value, end_value, partition_name, NULL::text); END $$ LANGUAGE plpgsql; @@ -88,7 +89,7 @@ SELECT create_range_partitions('test2', 'i', 0, 1); INSERT INTO test2 values(1); -- Test result (must be 'off') -SET ROLE regress_hacker; +SET ROLE pathman_regress_hacker; SHOW is_superuser; is_superuser -------------- @@ -111,5 +112,5 @@ NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to sequence test2_seq drop cascades to table test2_1 drop cascades to table test2_2 -DROP ROLE regress_hacker; +DROP ROLE pathman_regress_hacker; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_array_qual.out b/expected/pathman_array_qual.out index 49dca03a..0587a1c8 100644 --- a/expected/pathman_array_qual.out +++ b/expected/pathman_array_qual.out @@ -2402,6 +2402,7 @@ EXECUTE q(100); (1 row) DEALLOCATE q; -DROP SCHEMA array_qual CASCADE; -NOTICE: drop cascades to 12 other objects +DROP TABLE array_qual.test CASCADE; +NOTICE: drop cascades to 11 other objects +DROP SCHEMA array_qual; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_array_qual_1.out b/expected/pathman_array_qual_1.out index 6c8def94..dd7d2485 100644 --- a/expected/pathman_array_qual_1.out +++ b/expected/pathman_array_qual_1.out @@ -2392,6 +2392,7 @@ EXECUTE q(100); (1 row) DEALLOCATE q; -DROP SCHEMA array_qual CASCADE; -NOTICE: drop cascades to 12 other objects +DROP TABLE array_qual.test CASCADE; +NOTICE: drop cascades to 11 other objects +DROP SCHEMA array_qual; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_array_qual_2.out b/expected/pathman_array_qual_2.out new file mode 100644 index 00000000..ab504858 --- /dev/null +++ b/expected/pathman_array_qual_2.out @@ -0,0 +1,2398 @@ +/* + * Since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated, + * causing different output. + */ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE EXTENSION pg_pathman; +CREATE SCHEMA array_qual; +CREATE TABLE array_qual.test(val TEXT NOT NULL); +CREATE SEQUENCE array_qual.test_seq; +SELECT add_to_pathman_config('array_qual.test', 'val', NULL); + add_to_pathman_config +----------------------- + t +(1 row) + +SELECT add_range_partition('array_qual.test', 'a'::TEXT, 'b'); + add_range_partition +--------------------- + array_qual.test_1 +(1 row) + +SELECT add_range_partition('array_qual.test', 'b'::TEXT, 'c'); + add_range_partition +--------------------- + array_qual.test_2 +(1 row) + +SELECT add_range_partition('array_qual.test', 'c'::TEXT, 'd'); + add_range_partition +--------------------- + array_qual.test_3 +(1 row) + +SELECT add_range_partition('array_qual.test', 'd'::TEXT, 'e'); + add_range_partition +--------------------- + array_qual.test_4 +(1 row) + +INSERT INTO array_qual.test VALUES ('aaaa'); +INSERT INTO array_qual.test VALUES ('bbbb'); +INSERT INTO array_qual.test VALUES ('cccc'); +ANALYZE; +/* + * Test expr op ANY (...) + */ +/* matching collations */ +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE val < ANY (array['a', 'b']); + QUERY PLAN +------------------------- + Seq Scan on test_1 test +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE val < ANY (array['a', 'z']); + QUERY PLAN +-------------------------- + Append + -> Seq Scan on test_1 + -> Seq Scan on test_2 + -> Seq Scan on test_3 + -> Seq Scan on test_4 +(5 rows) + +/* different collations */ +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE val COLLATE "POSIX" < ANY (array['a', 'b']); + QUERY PLAN +----------------------------------------------------------- + Append + -> Seq Scan on test_1 + Filter: ((val)::text < 'b'::text COLLATE "POSIX") + -> Seq Scan on test_2 + Filter: ((val)::text < 'b'::text COLLATE "POSIX") + -> Seq Scan on test_3 + Filter: ((val)::text < 'b'::text COLLATE "POSIX") + -> Seq Scan on test_4 + Filter: ((val)::text < 'b'::text COLLATE "POSIX") +(9 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE val < ANY (array['a', 'b' COLLATE "POSIX"]); + QUERY PLAN +--------------------------------------------------- + Append + -> Seq Scan on test_1 + Filter: (val < 'b'::text COLLATE "POSIX") + -> Seq Scan on test_2 + Filter: (val < 'b'::text COLLATE "POSIX") + -> Seq Scan on test_3 + Filter: (val < 'b'::text COLLATE "POSIX") + -> Seq Scan on test_4 + Filter: (val < 'b'::text COLLATE "POSIX") +(9 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE val COLLATE "C" < ANY (array['a', 'b' COLLATE "POSIX"]); +ERROR: collation mismatch between explicit collations "C" and "POSIX" at character 95 +/* different collations (pruning should work) */ +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE val COLLATE "POSIX" = ANY (array['a', 'b']); + QUERY PLAN +------------------------------------------------------- + Append + -> Seq Scan on test_1 + Filter: ((val)::text = ANY ('{a,b}'::text[])) + -> Seq Scan on test_2 + Filter: ((val)::text = ANY ('{a,b}'::text[])) +(5 rows) + +/* non-btree operator */ +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE val ~~ ANY (array['a', 'b']); + QUERY PLAN +------------------------------------------------ + Append + -> Seq Scan on test_1 + Filter: (val ~~ ANY ('{a,b}'::text[])) + -> Seq Scan on test_2 + Filter: (val ~~ ANY ('{a,b}'::text[])) + -> Seq Scan on test_3 + Filter: (val ~~ ANY ('{a,b}'::text[])) + -> Seq Scan on test_4 + Filter: (val ~~ ANY ('{a,b}'::text[])) +(9 rows) + +DROP TABLE array_qual.test CASCADE; +NOTICE: drop cascades to 5 other objects +CREATE TABLE array_qual.test(a INT4 NOT NULL, b INT4); +SELECT create_range_partitions('array_qual.test', 'a', 1, 100, 10); + create_range_partitions +------------------------- + 10 +(1 row) + +INSERT INTO array_qual.test SELECT i, i FROM generate_series(1, 1000) g(i); +ANALYZE; +/* + * Test expr IN (...) + */ +/* a IN (...) - pruning should work */ +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a IN (1, 2, 3, 4); + QUERY PLAN +---------------------------------------------- + Seq Scan on test_1 test + Filter: (a = ANY ('{1,2,3,4}'::integer[])) +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a IN (100, 200, 300, 400); + QUERY PLAN +------------------------------------------------------------ + Append + -> Seq Scan on test_1 + Filter: (a = ANY ('{100,200,300,400}'::integer[])) + -> Seq Scan on test_2 + Filter: (a = ANY ('{100,200,300,400}'::integer[])) + -> Seq Scan on test_3 + Filter: (a = ANY ('{100,200,300,400}'::integer[])) + -> Seq Scan on test_4 + Filter: (a = ANY ('{100,200,300,400}'::integer[])) +(9 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a IN (-100, 100); + QUERY PLAN +----------------------------------------------- + Seq Scan on test_1 test + Filter: (a = ANY ('{-100,100}'::integer[])) +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a IN (-100, -200, -300); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a IN (-100, -200, -300, NULL); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a IN (NULL, NULL, NULL, NULL); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +/* b IN (...) - pruning should not work */ +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE b IN (1, 2, 3, 4); + QUERY PLAN +---------------------------------------------------- + Append + -> Seq Scan on test_1 + Filter: (b = ANY ('{1,2,3,4}'::integer[])) + -> Seq Scan on test_2 + Filter: (b = ANY ('{1,2,3,4}'::integer[])) + -> Seq Scan on test_3 + Filter: (b = ANY ('{1,2,3,4}'::integer[])) + -> Seq Scan on test_4 + Filter: (b = ANY ('{1,2,3,4}'::integer[])) + -> Seq Scan on test_5 + Filter: (b = ANY ('{1,2,3,4}'::integer[])) + -> Seq Scan on test_6 + Filter: (b = ANY ('{1,2,3,4}'::integer[])) + -> Seq Scan on test_7 + Filter: (b = ANY ('{1,2,3,4}'::integer[])) + -> Seq Scan on test_8 + Filter: (b = ANY ('{1,2,3,4}'::integer[])) + -> Seq Scan on test_9 + Filter: (b = ANY ('{1,2,3,4}'::integer[])) + -> Seq Scan on test_10 + Filter: (b = ANY ('{1,2,3,4}'::integer[])) +(21 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE b IN (100, 200, 300, 400); + QUERY PLAN +------------------------------------------------------------ + Append + -> Seq Scan on test_1 + Filter: (b = ANY ('{100,200,300,400}'::integer[])) + -> Seq Scan on test_2 + Filter: (b = ANY ('{100,200,300,400}'::integer[])) + -> Seq Scan on test_3 + Filter: (b = ANY ('{100,200,300,400}'::integer[])) + -> Seq Scan on test_4 + Filter: (b = ANY ('{100,200,300,400}'::integer[])) + -> Seq Scan on test_5 + Filter: (b = ANY ('{100,200,300,400}'::integer[])) + -> Seq Scan on test_6 + Filter: (b = ANY ('{100,200,300,400}'::integer[])) + -> Seq Scan on test_7 + Filter: (b = ANY ('{100,200,300,400}'::integer[])) + -> Seq Scan on test_8 + Filter: (b = ANY ('{100,200,300,400}'::integer[])) + -> Seq Scan on test_9 + Filter: (b = ANY ('{100,200,300,400}'::integer[])) + -> Seq Scan on test_10 + Filter: (b = ANY ('{100,200,300,400}'::integer[])) +(21 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE b IN (-100, 100); + QUERY PLAN +----------------------------------------------------- + Append + -> Seq Scan on test_1 + Filter: (b = ANY ('{-100,100}'::integer[])) + -> Seq Scan on test_2 + Filter: (b = ANY ('{-100,100}'::integer[])) + -> Seq Scan on test_3 + Filter: (b = ANY ('{-100,100}'::integer[])) + -> Seq Scan on test_4 + Filter: (b = ANY ('{-100,100}'::integer[])) + -> Seq Scan on test_5 + Filter: (b = ANY ('{-100,100}'::integer[])) + -> Seq Scan on test_6 + Filter: (b = ANY ('{-100,100}'::integer[])) + -> Seq Scan on test_7 + Filter: (b = ANY ('{-100,100}'::integer[])) + -> Seq Scan on test_8 + Filter: (b = ANY ('{-100,100}'::integer[])) + -> Seq Scan on test_9 + Filter: (b = ANY ('{-100,100}'::integer[])) + -> Seq Scan on test_10 + Filter: (b = ANY ('{-100,100}'::integer[])) +(21 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE b IN (-100, -200, -300); + QUERY PLAN +----------------------------------------------------------- + Append + -> Seq Scan on test_1 + Filter: (b = ANY ('{-100,-200,-300}'::integer[])) + -> Seq Scan on test_2 + Filter: (b = ANY ('{-100,-200,-300}'::integer[])) + -> Seq Scan on test_3 + Filter: (b = ANY ('{-100,-200,-300}'::integer[])) + -> Seq Scan on test_4 + Filter: (b = ANY ('{-100,-200,-300}'::integer[])) + -> Seq Scan on test_5 + Filter: (b = ANY ('{-100,-200,-300}'::integer[])) + -> Seq Scan on test_6 + Filter: (b = ANY ('{-100,-200,-300}'::integer[])) + -> Seq Scan on test_7 + Filter: (b = ANY ('{-100,-200,-300}'::integer[])) + -> Seq Scan on test_8 + Filter: (b = ANY ('{-100,-200,-300}'::integer[])) + -> Seq Scan on test_9 + Filter: (b = ANY ('{-100,-200,-300}'::integer[])) + -> Seq Scan on test_10 + Filter: (b = ANY ('{-100,-200,-300}'::integer[])) +(21 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE b IN (-100, -200, -300, NULL); + QUERY PLAN +---------------------------------------------------------------- + Append + -> Seq Scan on test_1 + Filter: (b = ANY ('{-100,-200,-300,NULL}'::integer[])) + -> Seq Scan on test_2 + Filter: (b = ANY ('{-100,-200,-300,NULL}'::integer[])) + -> Seq Scan on test_3 + Filter: (b = ANY ('{-100,-200,-300,NULL}'::integer[])) + -> Seq Scan on test_4 + Filter: (b = ANY ('{-100,-200,-300,NULL}'::integer[])) + -> Seq Scan on test_5 + Filter: (b = ANY ('{-100,-200,-300,NULL}'::integer[])) + -> Seq Scan on test_6 + Filter: (b = ANY ('{-100,-200,-300,NULL}'::integer[])) + -> Seq Scan on test_7 + Filter: (b = ANY ('{-100,-200,-300,NULL}'::integer[])) + -> Seq Scan on test_8 + Filter: (b = ANY ('{-100,-200,-300,NULL}'::integer[])) + -> Seq Scan on test_9 + Filter: (b = ANY ('{-100,-200,-300,NULL}'::integer[])) + -> Seq Scan on test_10 + Filter: (b = ANY ('{-100,-200,-300,NULL}'::integer[])) +(21 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE b IN (NULL, NULL, NULL, NULL); + QUERY PLAN +---------------------------------------------------------------- + Append + -> Seq Scan on test_1 + Filter: (b = ANY ('{NULL,NULL,NULL,NULL}'::integer[])) + -> Seq Scan on test_2 + Filter: (b = ANY ('{NULL,NULL,NULL,NULL}'::integer[])) + -> Seq Scan on test_3 + Filter: (b = ANY ('{NULL,NULL,NULL,NULL}'::integer[])) + -> Seq Scan on test_4 + Filter: (b = ANY ('{NULL,NULL,NULL,NULL}'::integer[])) + -> Seq Scan on test_5 + Filter: (b = ANY ('{NULL,NULL,NULL,NULL}'::integer[])) + -> Seq Scan on test_6 + Filter: (b = ANY ('{NULL,NULL,NULL,NULL}'::integer[])) + -> Seq Scan on test_7 + Filter: (b = ANY ('{NULL,NULL,NULL,NULL}'::integer[])) + -> Seq Scan on test_8 + Filter: (b = ANY ('{NULL,NULL,NULL,NULL}'::integer[])) + -> Seq Scan on test_9 + Filter: (b = ANY ('{NULL,NULL,NULL,NULL}'::integer[])) + -> Seq Scan on test_10 + Filter: (b = ANY ('{NULL,NULL,NULL,NULL}'::integer[])) +(21 rows) + +/* + * Test expr = ANY (...) + */ +/* a = ANY (...) - pruning should work */ +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a = ANY (NULL); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a = ANY (array[]::int4[]); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a = ANY (array[100, 100]); + QUERY PLAN +---------------------------------------------- + Seq Scan on test_1 test + Filter: (a = ANY ('{100,100}'::integer[])) +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a = ANY (array[100, 200, 300, 400]); + QUERY PLAN +------------------------------------------------------------ + Append + -> Seq Scan on test_1 + Filter: (a = ANY ('{100,200,300,400}'::integer[])) + -> Seq Scan on test_2 + Filter: (a = ANY ('{100,200,300,400}'::integer[])) + -> Seq Scan on test_3 + Filter: (a = ANY ('{100,200,300,400}'::integer[])) + -> Seq Scan on test_4 + Filter: (a = ANY ('{100,200,300,400}'::integer[])) +(9 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a = ANY (array[array[100, 200], array[300, 400]]); + QUERY PLAN +---------------------------------------------------------------- + Append + -> Seq Scan on test_1 + Filter: (a = ANY ('{{100,200},{300,400}}'::integer[])) + -> Seq Scan on test_2 + Filter: (a = ANY ('{{100,200},{300,400}}'::integer[])) + -> Seq Scan on test_3 + Filter: (a = ANY ('{{100,200},{300,400}}'::integer[])) + -> Seq Scan on test_4 + Filter: (a = ANY ('{{100,200},{300,400}}'::integer[])) +(9 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a = ANY (array[array[100, 200], array[300, 400], array[NULL, NULL]::int4[]]); + QUERY PLAN +---------------------------------------------------------------------------- + Append + -> Seq Scan on test_1 + Filter: (a = ANY ('{{100,200},{300,400},{NULL,NULL}}'::integer[])) + -> Seq Scan on test_2 + Filter: (a = ANY ('{{100,200},{300,400},{NULL,NULL}}'::integer[])) + -> Seq Scan on test_3 + Filter: (a = ANY ('{{100,200},{300,400},{NULL,NULL}}'::integer[])) + -> Seq Scan on test_4 + Filter: (a = ANY ('{{100,200},{300,400},{NULL,NULL}}'::integer[])) +(9 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a = ANY (array[array[100, 200], array[300, NULL]]); + QUERY PLAN +----------------------------------------------------------------- + Append + -> Seq Scan on test_1 + Filter: (a = ANY ('{{100,200},{300,NULL}}'::integer[])) + -> Seq Scan on test_2 + Filter: (a = ANY ('{{100,200},{300,NULL}}'::integer[])) + -> Seq Scan on test_3 + Filter: (a = ANY ('{{100,200},{300,NULL}}'::integer[])) +(7 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a = ANY (array[NULL, NULL]::int4[]); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +/* + * Test expr = ALL (...) + */ +/* a = ALL (...) - pruning should work */ +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a = ALL (NULL); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a = ALL (array[]::int4[]); + QUERY PLAN +--------------------------------------------- + Append + -> Seq Scan on test_1 + Filter: (a = ALL ('{}'::integer[])) + -> Seq Scan on test_2 + Filter: (a = ALL ('{}'::integer[])) + -> Seq Scan on test_3 + Filter: (a = ALL ('{}'::integer[])) + -> Seq Scan on test_4 + Filter: (a = ALL ('{}'::integer[])) + -> Seq Scan on test_5 + Filter: (a = ALL ('{}'::integer[])) + -> Seq Scan on test_6 + Filter: (a = ALL ('{}'::integer[])) + -> Seq Scan on test_7 + Filter: (a = ALL ('{}'::integer[])) + -> Seq Scan on test_8 + Filter: (a = ALL ('{}'::integer[])) + -> Seq Scan on test_9 + Filter: (a = ALL ('{}'::integer[])) + -> Seq Scan on test_10 + Filter: (a = ALL ('{}'::integer[])) +(21 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a = ALL (array[100, 100]); + QUERY PLAN +---------------------------------------------- + Seq Scan on test_1 test + Filter: (a = ALL ('{100,100}'::integer[])) +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a = ALL (array[100, 200, 300, 400]); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a = ALL (array[array[100, 200], array[300, 400]]); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a = ALL (array[array[100, 200], array[300, 400], array[NULL, NULL]::int4[]]); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a = ALL (array[array[100, 200], array[300, NULL]]); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a = ALL (array[NULL, NULL]::int4[]); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +/* + * Test expr < ANY (...) + */ +/* a < ANY (...) - pruning should work */ +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a < ANY (NULL); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a < ANY (array[]::int4[]); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a < ANY (array[100, 100]); + QUERY PLAN +------------------------- + Seq Scan on test_1 test + Filter: (a < 100) +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a < ANY (array[99, 100, 101]); + QUERY PLAN +------------------------- + Seq Scan on test_1 test +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a < ANY (array[500, 550]); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on test_1 + -> Seq Scan on test_2 + -> Seq Scan on test_3 + -> Seq Scan on test_4 + -> Seq Scan on test_5 + -> Seq Scan on test_6 + Filter: (a < 550) +(8 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a < ANY (array[100, 700]); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on test_1 + -> Seq Scan on test_2 + -> Seq Scan on test_3 + -> Seq Scan on test_4 + -> Seq Scan on test_5 + -> Seq Scan on test_6 + -> Seq Scan on test_7 + Filter: (a < 700) +(9 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a < ANY (array[NULL, 700]); + QUERY PLAN +----------------------------------------------------- + Append + -> Seq Scan on test_1 + -> Seq Scan on test_2 + -> Seq Scan on test_3 + -> Seq Scan on test_4 + -> Seq Scan on test_5 + -> Seq Scan on test_6 + -> Seq Scan on test_7 + Filter: (a < ANY ('{NULL,700}'::integer[])) +(9 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a < ANY (array[NULL, NULL]::int4[]); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +SET pg_pathman.enable = f; +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been disabled +SELECT count(*) FROM array_qual.test WHERE a < ANY (array[NULL, 700]); + count +------- + 699 +(1 row) + +SET pg_pathman.enable = t; +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been enabled +SELECT count(*) FROM array_qual.test WHERE a < ANY (array[NULL, 700]); + count +------- + 699 +(1 row) + +/* + * Test expr < ALL (...) + */ +/* a < ALL (...) - pruning should work */ +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a < ALL (NULL); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a < ALL (array[]::int4[]); + QUERY PLAN +--------------------------------------------- + Append + -> Seq Scan on test_1 + Filter: (a < ALL ('{}'::integer[])) + -> Seq Scan on test_2 + Filter: (a < ALL ('{}'::integer[])) + -> Seq Scan on test_3 + Filter: (a < ALL ('{}'::integer[])) + -> Seq Scan on test_4 + Filter: (a < ALL ('{}'::integer[])) + -> Seq Scan on test_5 + Filter: (a < ALL ('{}'::integer[])) + -> Seq Scan on test_6 + Filter: (a < ALL ('{}'::integer[])) + -> Seq Scan on test_7 + Filter: (a < ALL ('{}'::integer[])) + -> Seq Scan on test_8 + Filter: (a < ALL ('{}'::integer[])) + -> Seq Scan on test_9 + Filter: (a < ALL ('{}'::integer[])) + -> Seq Scan on test_10 + Filter: (a < ALL ('{}'::integer[])) +(21 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a < ALL (array[100, 100]); + QUERY PLAN +------------------------- + Seq Scan on test_1 test + Filter: (a < 100) +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a < ALL (array[99, 100, 101]); + QUERY PLAN +------------------------- + Seq Scan on test_1 test + Filter: (a < 99) +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a < ALL (array[500, 550]); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on test_1 + -> Seq Scan on test_2 + -> Seq Scan on test_3 + -> Seq Scan on test_4 + -> Seq Scan on test_5 + Filter: (a < 500) +(7 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a < ALL (array[100, 700]); + QUERY PLAN +------------------------- + Seq Scan on test_1 test + Filter: (a < 100) +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a < ALL (array[NULL, 700]); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a < ALL (array[NULL, NULL]::int4[]); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +SET pg_pathman.enable = f; +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been disabled +SELECT count(*) FROM array_qual.test WHERE a < ALL (array[NULL, 700]); + count +------- + 0 +(1 row) + +SET pg_pathman.enable = t; +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been enabled +SELECT count(*) FROM array_qual.test WHERE a < ALL (array[NULL, 700]); + count +------- + 0 +(1 row) + +/* + * Test expr > ANY (...) + */ +/* a > ANY (...) - pruning should work */ +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a > ANY (NULL); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a > ANY (array[]::int4[]); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a > ANY (array[100, 100]); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on test_1 + Filter: (a > 100) + -> Seq Scan on test_2 + -> Seq Scan on test_3 + -> Seq Scan on test_4 + -> Seq Scan on test_5 + -> Seq Scan on test_6 + -> Seq Scan on test_7 + -> Seq Scan on test_8 + -> Seq Scan on test_9 + -> Seq Scan on test_10 +(12 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a > ANY (array[99, 100, 101]); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on test_1 + Filter: (a > 99) + -> Seq Scan on test_2 + -> Seq Scan on test_3 + -> Seq Scan on test_4 + -> Seq Scan on test_5 + -> Seq Scan on test_6 + -> Seq Scan on test_7 + -> Seq Scan on test_8 + -> Seq Scan on test_9 + -> Seq Scan on test_10 +(12 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a > ANY (array[500, 550]); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_5 test_1 + Filter: (a > 500) + -> Seq Scan on test_6 test_2 + -> Seq Scan on test_7 test_3 + -> Seq Scan on test_8 test_4 + -> Seq Scan on test_9 test_5 + -> Seq Scan on test_10 test_6 +(8 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a > ANY (array[100, 700]); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on test_1 + Filter: (a > 100) + -> Seq Scan on test_2 + -> Seq Scan on test_3 + -> Seq Scan on test_4 + -> Seq Scan on test_5 + -> Seq Scan on test_6 + -> Seq Scan on test_7 + -> Seq Scan on test_8 + -> Seq Scan on test_9 + -> Seq Scan on test_10 +(12 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a > ANY (array[NULL, 700]); + QUERY PLAN +----------------------------------------------------- + Append + -> Seq Scan on test_7 test_1 + Filter: (a > ANY ('{NULL,700}'::integer[])) + -> Seq Scan on test_8 test_2 + -> Seq Scan on test_9 test_3 + -> Seq Scan on test_10 test_4 +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a > ANY (array[NULL, NULL]::int4[]); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +SET pg_pathman.enable = f; +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been disabled +SELECT count(*) FROM array_qual.test WHERE a > ANY (array[NULL, 700]); + count +------- + 300 +(1 row) + +SET pg_pathman.enable = t; +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been enabled +SELECT count(*) FROM array_qual.test WHERE a > ANY (array[NULL, 700]); + count +------- + 300 +(1 row) + +/* + * Test expr > ALL (...) + */ +/* a > ALL (...) - pruning should work */ +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a > ALL (NULL); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a > ALL (array[]::int4[]); + QUERY PLAN +--------------------------------------------- + Append + -> Seq Scan on test_1 + Filter: (a > ALL ('{}'::integer[])) + -> Seq Scan on test_2 + Filter: (a > ALL ('{}'::integer[])) + -> Seq Scan on test_3 + Filter: (a > ALL ('{}'::integer[])) + -> Seq Scan on test_4 + Filter: (a > ALL ('{}'::integer[])) + -> Seq Scan on test_5 + Filter: (a > ALL ('{}'::integer[])) + -> Seq Scan on test_6 + Filter: (a > ALL ('{}'::integer[])) + -> Seq Scan on test_7 + Filter: (a > ALL ('{}'::integer[])) + -> Seq Scan on test_8 + Filter: (a > ALL ('{}'::integer[])) + -> Seq Scan on test_9 + Filter: (a > ALL ('{}'::integer[])) + -> Seq Scan on test_10 + Filter: (a > ALL ('{}'::integer[])) +(21 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a > ALL (array[100, 100]); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on test_1 + Filter: (a > 100) + -> Seq Scan on test_2 + -> Seq Scan on test_3 + -> Seq Scan on test_4 + -> Seq Scan on test_5 + -> Seq Scan on test_6 + -> Seq Scan on test_7 + -> Seq Scan on test_8 + -> Seq Scan on test_9 + -> Seq Scan on test_10 +(12 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a > ALL (array[99, 100, 101]); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_2 test_1 + Filter: (a > 101) + -> Seq Scan on test_3 test_2 + -> Seq Scan on test_4 test_3 + -> Seq Scan on test_5 test_4 + -> Seq Scan on test_6 test_5 + -> Seq Scan on test_7 test_6 + -> Seq Scan on test_8 test_7 + -> Seq Scan on test_9 test_8 + -> Seq Scan on test_10 test_9 +(11 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a > ALL (array[500, 550]); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 550) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a > ALL (array[100, 700]); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_7 test_1 + Filter: (a > 700) + -> Seq Scan on test_8 test_2 + -> Seq Scan on test_9 test_3 + -> Seq Scan on test_10 test_4 +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a > ALL (array[NULL, 700]); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM array_qual.test WHERE a > ALL (array[NULL, NULL]::int4[]); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +SET pg_pathman.enable = f; +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been disabled +SELECT count(*) FROM array_qual.test WHERE a > ALL (array[NULL, 700]); + count +------- + 0 +(1 row) + +SET pg_pathman.enable = t; +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been enabled +SELECT count(*) FROM array_qual.test WHERE a > ALL (array[NULL, 700]); + count +------- + 0 +(1 row) + +/* + * Test expr > ANY (... $1 ...) + */ +PREPARE q(int4) AS SELECT * FROM array_qual.test WHERE a > ANY (array[$1, 100, 600]); +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on test_1 + Filter: (a > 1) + -> Seq Scan on test_2 + -> Seq Scan on test_3 + -> Seq Scan on test_4 + -> Seq Scan on test_5 + -> Seq Scan on test_6 + -> Seq Scan on test_7 + -> Seq Scan on test_8 + -> Seq Scan on test_9 + -> Seq Scan on test_10 +(12 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on test_1 + Filter: (a > 1) + -> Seq Scan on test_2 + -> Seq Scan on test_3 + -> Seq Scan on test_4 + -> Seq Scan on test_5 + -> Seq Scan on test_6 + -> Seq Scan on test_7 + -> Seq Scan on test_8 + -> Seq Scan on test_9 + -> Seq Scan on test_10 +(12 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on test_1 + Filter: (a > 1) + -> Seq Scan on test_2 + -> Seq Scan on test_3 + -> Seq Scan on test_4 + -> Seq Scan on test_5 + -> Seq Scan on test_6 + -> Seq Scan on test_7 + -> Seq Scan on test_8 + -> Seq Scan on test_9 + -> Seq Scan on test_10 +(12 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on test_1 + Filter: (a > 1) + -> Seq Scan on test_2 + -> Seq Scan on test_3 + -> Seq Scan on test_4 + -> Seq Scan on test_5 + -> Seq Scan on test_6 + -> Seq Scan on test_7 + -> Seq Scan on test_8 + -> Seq Scan on test_9 + -> Seq Scan on test_10 +(12 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on test_1 + Filter: (a > 1) + -> Seq Scan on test_2 + -> Seq Scan on test_3 + -> Seq Scan on test_4 + -> Seq Scan on test_5 + -> Seq Scan on test_6 + -> Seq Scan on test_7 + -> Seq Scan on test_8 + -> Seq Scan on test_9 + -> Seq Scan on test_10 +(12 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ANY (ARRAY[$1, 100, 600])) + -> Seq Scan on test_1 test + Filter: (a > ANY (ARRAY[$1, 100, 600])) + -> Seq Scan on test_2 test + Filter: (a > ANY (ARRAY[$1, 100, 600])) + -> Seq Scan on test_3 test + Filter: (a > ANY (ARRAY[$1, 100, 600])) + -> Seq Scan on test_4 test + Filter: (a > ANY (ARRAY[$1, 100, 600])) + -> Seq Scan on test_5 test + Filter: (a > ANY (ARRAY[$1, 100, 600])) + -> Seq Scan on test_6 test + Filter: (a > ANY (ARRAY[$1, 100, 600])) + -> Seq Scan on test_7 test + Filter: (a > ANY (ARRAY[$1, 100, 600])) + -> Seq Scan on test_8 test + Filter: (a > ANY (ARRAY[$1, 100, 600])) + -> Seq Scan on test_9 test + Filter: (a > ANY (ARRAY[$1, 100, 600])) + -> Seq Scan on test_10 test + Filter: (a > ANY (ARRAY[$1, 100, 600])) +(22 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ANY (ARRAY[$1, 100, 600])) + -> Seq Scan on test_1 test + Filter: (a > ANY (ARRAY[$1, 100, 600])) + -> Seq Scan on test_2 test + Filter: (a > ANY (ARRAY[$1, 100, 600])) + -> Seq Scan on test_3 test + Filter: (a > ANY (ARRAY[$1, 100, 600])) + -> Seq Scan on test_4 test + Filter: (a > ANY (ARRAY[$1, 100, 600])) + -> Seq Scan on test_5 test + Filter: (a > ANY (ARRAY[$1, 100, 600])) + -> Seq Scan on test_6 test + Filter: (a > ANY (ARRAY[$1, 100, 600])) + -> Seq Scan on test_7 test + Filter: (a > ANY (ARRAY[$1, 100, 600])) + -> Seq Scan on test_8 test + Filter: (a > ANY (ARRAY[$1, 100, 600])) + -> Seq Scan on test_9 test + Filter: (a > ANY (ARRAY[$1, 100, 600])) + -> Seq Scan on test_10 test + Filter: (a > ANY (ARRAY[$1, 100, 600])) +(22 rows) + +DEALLOCATE q; +PREPARE q(int4) AS SELECT * FROM array_qual.test WHERE a > ANY (array[100, 600, $1]); +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on test_1 + Filter: (a > 1) + -> Seq Scan on test_2 + -> Seq Scan on test_3 + -> Seq Scan on test_4 + -> Seq Scan on test_5 + -> Seq Scan on test_6 + -> Seq Scan on test_7 + -> Seq Scan on test_8 + -> Seq Scan on test_9 + -> Seq Scan on test_10 +(12 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on test_1 + Filter: (a > 1) + -> Seq Scan on test_2 + -> Seq Scan on test_3 + -> Seq Scan on test_4 + -> Seq Scan on test_5 + -> Seq Scan on test_6 + -> Seq Scan on test_7 + -> Seq Scan on test_8 + -> Seq Scan on test_9 + -> Seq Scan on test_10 +(12 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on test_1 + Filter: (a > 1) + -> Seq Scan on test_2 + -> Seq Scan on test_3 + -> Seq Scan on test_4 + -> Seq Scan on test_5 + -> Seq Scan on test_6 + -> Seq Scan on test_7 + -> Seq Scan on test_8 + -> Seq Scan on test_9 + -> Seq Scan on test_10 +(12 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on test_1 + Filter: (a > 1) + -> Seq Scan on test_2 + -> Seq Scan on test_3 + -> Seq Scan on test_4 + -> Seq Scan on test_5 + -> Seq Scan on test_6 + -> Seq Scan on test_7 + -> Seq Scan on test_8 + -> Seq Scan on test_9 + -> Seq Scan on test_10 +(12 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on test_1 + Filter: (a > 1) + -> Seq Scan on test_2 + -> Seq Scan on test_3 + -> Seq Scan on test_4 + -> Seq Scan on test_5 + -> Seq Scan on test_6 + -> Seq Scan on test_7 + -> Seq Scan on test_8 + -> Seq Scan on test_9 + -> Seq Scan on test_10 +(12 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ANY (ARRAY[100, 600, $1])) + -> Seq Scan on test_1 test + Filter: (a > ANY (ARRAY[100, 600, $1])) + -> Seq Scan on test_2 test + Filter: (a > ANY (ARRAY[100, 600, $1])) + -> Seq Scan on test_3 test + Filter: (a > ANY (ARRAY[100, 600, $1])) + -> Seq Scan on test_4 test + Filter: (a > ANY (ARRAY[100, 600, $1])) + -> Seq Scan on test_5 test + Filter: (a > ANY (ARRAY[100, 600, $1])) + -> Seq Scan on test_6 test + Filter: (a > ANY (ARRAY[100, 600, $1])) + -> Seq Scan on test_7 test + Filter: (a > ANY (ARRAY[100, 600, $1])) + -> Seq Scan on test_8 test + Filter: (a > ANY (ARRAY[100, 600, $1])) + -> Seq Scan on test_9 test + Filter: (a > ANY (ARRAY[100, 600, $1])) + -> Seq Scan on test_10 test + Filter: (a > ANY (ARRAY[100, 600, $1])) +(22 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ANY (ARRAY[100, 600, $1])) + -> Seq Scan on test_1 test + Filter: (a > ANY (ARRAY[100, 600, $1])) + -> Seq Scan on test_2 test + Filter: (a > ANY (ARRAY[100, 600, $1])) + -> Seq Scan on test_3 test + Filter: (a > ANY (ARRAY[100, 600, $1])) + -> Seq Scan on test_4 test + Filter: (a > ANY (ARRAY[100, 600, $1])) + -> Seq Scan on test_5 test + Filter: (a > ANY (ARRAY[100, 600, $1])) + -> Seq Scan on test_6 test + Filter: (a > ANY (ARRAY[100, 600, $1])) + -> Seq Scan on test_7 test + Filter: (a > ANY (ARRAY[100, 600, $1])) + -> Seq Scan on test_8 test + Filter: (a > ANY (ARRAY[100, 600, $1])) + -> Seq Scan on test_9 test + Filter: (a > ANY (ARRAY[100, 600, $1])) + -> Seq Scan on test_10 test + Filter: (a > ANY (ARRAY[100, 600, $1])) +(22 rows) + +DEALLOCATE q; +PREPARE q(int4) AS SELECT * FROM array_qual.test WHERE a > ANY (array[NULL, $1]); +EXPLAIN (COSTS OFF) EXECUTE q(500); + QUERY PLAN +----------------------------------------------------- + Append + -> Seq Scan on test_5 test_1 + Filter: (a > ANY ('{NULL,500}'::integer[])) + -> Seq Scan on test_6 test_2 + -> Seq Scan on test_7 test_3 + -> Seq Scan on test_8 test_4 + -> Seq Scan on test_9 test_5 + -> Seq Scan on test_10 test_6 +(8 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(500); + QUERY PLAN +----------------------------------------------------- + Append + -> Seq Scan on test_5 test_1 + Filter: (a > ANY ('{NULL,500}'::integer[])) + -> Seq Scan on test_6 test_2 + -> Seq Scan on test_7 test_3 + -> Seq Scan on test_8 test_4 + -> Seq Scan on test_9 test_5 + -> Seq Scan on test_10 test_6 +(8 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(500); + QUERY PLAN +----------------------------------------------------- + Append + -> Seq Scan on test_5 test_1 + Filter: (a > ANY ('{NULL,500}'::integer[])) + -> Seq Scan on test_6 test_2 + -> Seq Scan on test_7 test_3 + -> Seq Scan on test_8 test_4 + -> Seq Scan on test_9 test_5 + -> Seq Scan on test_10 test_6 +(8 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(500); + QUERY PLAN +----------------------------------------------------- + Append + -> Seq Scan on test_5 test_1 + Filter: (a > ANY ('{NULL,500}'::integer[])) + -> Seq Scan on test_6 test_2 + -> Seq Scan on test_7 test_3 + -> Seq Scan on test_8 test_4 + -> Seq Scan on test_9 test_5 + -> Seq Scan on test_10 test_6 +(8 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(500); + QUERY PLAN +----------------------------------------------------- + Append + -> Seq Scan on test_5 test_1 + Filter: (a > ANY ('{NULL,500}'::integer[])) + -> Seq Scan on test_6 test_2 + -> Seq Scan on test_7 test_3 + -> Seq Scan on test_8 test_4 + -> Seq Scan on test_9 test_5 + -> Seq Scan on test_10 test_6 +(8 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(500); + QUERY PLAN +------------------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ANY (ARRAY[NULL::integer, $1])) + -> Seq Scan on test_1 test + Filter: (a > ANY (ARRAY[NULL::integer, $1])) + -> Seq Scan on test_2 test + Filter: (a > ANY (ARRAY[NULL::integer, $1])) + -> Seq Scan on test_3 test + Filter: (a > ANY (ARRAY[NULL::integer, $1])) + -> Seq Scan on test_4 test + Filter: (a > ANY (ARRAY[NULL::integer, $1])) + -> Seq Scan on test_5 test + Filter: (a > ANY (ARRAY[NULL::integer, $1])) + -> Seq Scan on test_6 test + Filter: (a > ANY (ARRAY[NULL::integer, $1])) + -> Seq Scan on test_7 test + Filter: (a > ANY (ARRAY[NULL::integer, $1])) + -> Seq Scan on test_8 test + Filter: (a > ANY (ARRAY[NULL::integer, $1])) + -> Seq Scan on test_9 test + Filter: (a > ANY (ARRAY[NULL::integer, $1])) + -> Seq Scan on test_10 test + Filter: (a > ANY (ARRAY[NULL::integer, $1])) +(22 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(500); + QUERY PLAN +------------------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ANY (ARRAY[NULL::integer, $1])) + -> Seq Scan on test_1 test + Filter: (a > ANY (ARRAY[NULL::integer, $1])) + -> Seq Scan on test_2 test + Filter: (a > ANY (ARRAY[NULL::integer, $1])) + -> Seq Scan on test_3 test + Filter: (a > ANY (ARRAY[NULL::integer, $1])) + -> Seq Scan on test_4 test + Filter: (a > ANY (ARRAY[NULL::integer, $1])) + -> Seq Scan on test_5 test + Filter: (a > ANY (ARRAY[NULL::integer, $1])) + -> Seq Scan on test_6 test + Filter: (a > ANY (ARRAY[NULL::integer, $1])) + -> Seq Scan on test_7 test + Filter: (a > ANY (ARRAY[NULL::integer, $1])) + -> Seq Scan on test_8 test + Filter: (a > ANY (ARRAY[NULL::integer, $1])) + -> Seq Scan on test_9 test + Filter: (a > ANY (ARRAY[NULL::integer, $1])) + -> Seq Scan on test_10 test + Filter: (a > ANY (ARRAY[NULL::integer, $1])) +(22 rows) + +EXECUTE q(NULL); + a | b +---+--- +(0 rows) + +DEALLOCATE q; +/* + * Test expr > ALL (... $1 ...) + */ +PREPARE q(int4) AS SELECT * FROM array_qual.test WHERE a > ALL (array[$1, 1000000, 600]); +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +DEALLOCATE q; +PREPARE q(int4) AS SELECT * FROM array_qual.test WHERE a > ALL (array[$1, NULL, 600]); +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +DEALLOCATE q; +PREPARE q(int4) AS SELECT * FROM array_qual.test WHERE a > ALL (array[NULL, $1, NULL]); +EXPLAIN (COSTS OFF) EXECUTE q(500); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(500); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(500); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(500); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(500); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(500); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(500); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXECUTE q(NULL); + a | b +---+--- +(0 rows) + +DEALLOCATE q; +PREPARE q(int4) AS SELECT * FROM array_qual.test WHERE a > ALL (array[$1, 100, 600]); +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ALL (ARRAY[$1, 100, 600])) + -> Seq Scan on test_6 test + Filter: (a > ALL (ARRAY[$1, 100, 600])) + -> Seq Scan on test_7 test + Filter: (a > ALL (ARRAY[$1, 100, 600])) + -> Seq Scan on test_8 test + Filter: (a > ALL (ARRAY[$1, 100, 600])) + -> Seq Scan on test_9 test + Filter: (a > ALL (ARRAY[$1, 100, 600])) + -> Seq Scan on test_10 test + Filter: (a > ALL (ARRAY[$1, 100, 600])) +(12 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ALL (ARRAY[$1, 100, 600])) + -> Seq Scan on test_6 test + Filter: (a > ALL (ARRAY[$1, 100, 600])) + -> Seq Scan on test_7 test + Filter: (a > ALL (ARRAY[$1, 100, 600])) + -> Seq Scan on test_8 test + Filter: (a > ALL (ARRAY[$1, 100, 600])) + -> Seq Scan on test_9 test + Filter: (a > ALL (ARRAY[$1, 100, 600])) + -> Seq Scan on test_10 test + Filter: (a > ALL (ARRAY[$1, 100, 600])) +(12 rows) + +DEALLOCATE q; +PREPARE q(int4) AS SELECT * FROM array_qual.test WHERE a > ALL (array[100, $1, 600]); +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ALL (ARRAY[100, $1, 600])) + -> Seq Scan on test_6 test + Filter: (a > ALL (ARRAY[100, $1, 600])) + -> Seq Scan on test_7 test + Filter: (a > ALL (ARRAY[100, $1, 600])) + -> Seq Scan on test_8 test + Filter: (a > ALL (ARRAY[100, $1, 600])) + -> Seq Scan on test_9 test + Filter: (a > ALL (ARRAY[100, $1, 600])) + -> Seq Scan on test_10 test + Filter: (a > ALL (ARRAY[100, $1, 600])) +(12 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ALL (ARRAY[100, $1, 600])) + -> Seq Scan on test_6 test + Filter: (a > ALL (ARRAY[100, $1, 600])) + -> Seq Scan on test_7 test + Filter: (a > ALL (ARRAY[100, $1, 600])) + -> Seq Scan on test_8 test + Filter: (a > ALL (ARRAY[100, $1, 600])) + -> Seq Scan on test_9 test + Filter: (a > ALL (ARRAY[100, $1, 600])) + -> Seq Scan on test_10 test + Filter: (a > ALL (ARRAY[100, $1, 600])) +(12 rows) + +DEALLOCATE q; +PREPARE q(int4) AS SELECT * FROM array_qual.test WHERE a > ALL (array[100, 600, $1]); +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ALL (ARRAY[100, 600, $1])) + -> Seq Scan on test_6 test + Filter: (a > ALL (ARRAY[100, 600, $1])) + -> Seq Scan on test_7 test + Filter: (a > ALL (ARRAY[100, 600, $1])) + -> Seq Scan on test_8 test + Filter: (a > ALL (ARRAY[100, 600, $1])) + -> Seq Scan on test_9 test + Filter: (a > ALL (ARRAY[100, 600, $1])) + -> Seq Scan on test_10 test + Filter: (a > ALL (ARRAY[100, 600, $1])) +(12 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ALL (ARRAY[100, 600, $1])) + -> Seq Scan on test_6 test + Filter: (a > ALL (ARRAY[100, 600, $1])) + -> Seq Scan on test_7 test + Filter: (a > ALL (ARRAY[100, 600, $1])) + -> Seq Scan on test_8 test + Filter: (a > ALL (ARRAY[100, 600, $1])) + -> Seq Scan on test_9 test + Filter: (a > ALL (ARRAY[100, 600, $1])) + -> Seq Scan on test_10 test + Filter: (a > ALL (ARRAY[100, 600, $1])) +(12 rows) + +DEALLOCATE q; +PREPARE q(int4) AS SELECT * FROM array_qual.test WHERE a > ALL (array[array[100, NULL], array[1, $1]]); +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +DEALLOCATE q; +PREPARE q(int4) AS SELECT * FROM array_qual.test WHERE a > ALL (array[array[100, 600], array[1, $1]]); +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------------------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ALL (ARRAY['{100,600}'::integer[], ARRAY[1, $1]])) + -> Seq Scan on test_6 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], ARRAY[1, $1]])) + -> Seq Scan on test_7 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], ARRAY[1, $1]])) + -> Seq Scan on test_8 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], ARRAY[1, $1]])) + -> Seq Scan on test_9 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], ARRAY[1, $1]])) + -> Seq Scan on test_10 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], ARRAY[1, $1]])) +(12 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------------------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ALL (ARRAY['{100,600}'::integer[], ARRAY[1, $1]])) + -> Seq Scan on test_6 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], ARRAY[1, $1]])) + -> Seq Scan on test_7 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], ARRAY[1, $1]])) + -> Seq Scan on test_8 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], ARRAY[1, $1]])) + -> Seq Scan on test_9 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], ARRAY[1, $1]])) + -> Seq Scan on test_10 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], ARRAY[1, $1]])) +(12 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(999); + QUERY PLAN +-------------------------------------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ALL (ARRAY['{100,600}'::integer[], ARRAY[1, $1]])) + -> Seq Scan on test_6 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], ARRAY[1, $1]])) + -> Seq Scan on test_7 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], ARRAY[1, $1]])) + -> Seq Scan on test_8 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], ARRAY[1, $1]])) + -> Seq Scan on test_9 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], ARRAY[1, $1]])) + -> Seq Scan on test_10 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], ARRAY[1, $1]])) +(12 rows) + +/* check query plan: EXECUTE q(999) */ +DO language plpgsql +$$ + DECLARE + query text; + result jsonb; + num int; + + BEGIN + query := 'EXECUTE q(999)'; + + EXECUTE format('EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, FORMAT JSON) %s', query) + INTO result; + + SELECT count(*) FROM jsonb_array_elements_text(result->0->'Plan'->'Plans') INTO num; + + RAISE notice '%: number of partitions: %', query, num; + END +$$; +NOTICE: EXECUTE q(999): number of partitions: 5 +DEALLOCATE q; +PREPARE q(int4[]) AS SELECT * FROM array_qual.test WHERE a > ALL (array[array[100, 600], $1]); +EXPLAIN (COSTS OFF) EXECUTE q('{1, 1}'); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q('{1, 1}'); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q('{1, 1}'); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q('{1, 1}'); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q('{1, 1}'); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_6 test_1 + Filter: (a > 600) + -> Seq Scan on test_7 test_2 + -> Seq Scan on test_8 test_3 + -> Seq Scan on test_9 test_4 + -> Seq Scan on test_10 test_5 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE q('{1, 1}'); + QUERY PLAN +---------------------------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ALL (ARRAY['{100,600}'::integer[], $1])) + -> Seq Scan on test_6 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], $1])) + -> Seq Scan on test_7 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], $1])) + -> Seq Scan on test_8 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], $1])) + -> Seq Scan on test_9 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], $1])) + -> Seq Scan on test_10 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], $1])) +(12 rows) + +EXPLAIN (COSTS OFF) EXECUTE q('{1, 1}'); + QUERY PLAN +---------------------------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ALL (ARRAY['{100,600}'::integer[], $1])) + -> Seq Scan on test_6 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], $1])) + -> Seq Scan on test_7 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], $1])) + -> Seq Scan on test_8 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], $1])) + -> Seq Scan on test_9 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], $1])) + -> Seq Scan on test_10 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], $1])) +(12 rows) + +EXPLAIN (COSTS OFF) EXECUTE q('{1, 999}'); + QUERY PLAN +---------------------------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ALL (ARRAY['{100,600}'::integer[], $1])) + -> Seq Scan on test_6 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], $1])) + -> Seq Scan on test_7 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], $1])) + -> Seq Scan on test_8 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], $1])) + -> Seq Scan on test_9 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], $1])) + -> Seq Scan on test_10 test + Filter: (a > ALL (ARRAY['{100,600}'::integer[], $1])) +(12 rows) + +/* check query plan: EXECUTE q('{1, 999}') */ +DO language plpgsql +$$ + DECLARE + query text; + result jsonb; + num int; + + BEGIN + query := 'EXECUTE q(''{1, 999}'')'; + + EXECUTE format('EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, FORMAT JSON) %s', query) + INTO result; + + SELECT count(*) FROM jsonb_array_elements_text(result->0->'Plan'->'Plans') INTO num; + + RAISE notice '%: number of partitions: %', query, num; + END +$$; +NOTICE: EXECUTE q('{1, 999}'): number of partitions: 1 +DEALLOCATE q; +PREPARE q(int4) AS SELECT * FROM array_qual.test WHERE a > ALL (array[$1, 898]); +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_9 test_1 + Filter: (a > 898) + -> Seq Scan on test_10 test_2 +(4 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_9 test_1 + Filter: (a > 898) + -> Seq Scan on test_10 test_2 +(4 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_9 test_1 + Filter: (a > 898) + -> Seq Scan on test_10 test_2 +(4 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_9 test_1 + Filter: (a > 898) + -> Seq Scan on test_10 test_2 +(4 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on test_9 test_1 + Filter: (a > 898) + -> Seq Scan on test_10 test_2 +(4 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +--------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ALL (ARRAY[$1, 898])) + -> Seq Scan on test_9 test + Filter: (a > ALL (ARRAY[$1, 898])) + -> Seq Scan on test_10 test + Filter: (a > ALL (ARRAY[$1, 898])) +(6 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +--------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ALL (ARRAY[$1, 898])) + -> Seq Scan on test_9 test + Filter: (a > ALL (ARRAY[$1, 898])) + -> Seq Scan on test_10 test + Filter: (a > ALL (ARRAY[$1, 898])) +(6 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(900); /* check quals optimization */ + QUERY PLAN +--------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a > ALL (ARRAY[$1, 898])) + -> Seq Scan on test_9 test + Filter: (a > ALL (ARRAY[$1, 898])) + -> Seq Scan on test_10 test + Filter: (a > ALL (ARRAY[$1, 898])) +(6 rows) + +EXECUTE q(1000); + a | b +---+--- +(0 rows) + +/* check query plan: EXECUTE q(999) */ +DO language plpgsql +$$ + DECLARE + query text; + result jsonb; + num int; + + BEGIN + query := 'EXECUTE q(999)'; + + EXECUTE format('EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, FORMAT JSON) %s', query) + INTO result; + + SELECT count(*) FROM jsonb_array_elements_text(result->0->'Plan'->'Plans') INTO num; + + RAISE notice '%: number of partitions: %', query, num; + END +$$; +NOTICE: EXECUTE q(999): number of partitions: 1 +DEALLOCATE q; +/* + * Test expr = ALL (... $1 ...) + */ +PREPARE q(int4) AS SELECT * FROM array_qual.test WHERE a = ALL (array[$1, 100, 600]); +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +DEALLOCATE q; +PREPARE q(int4) AS SELECT * FROM array_qual.test WHERE a = ALL (array[100, 600, $1]); +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +DEALLOCATE q; +PREPARE q(int4) AS SELECT * FROM array_qual.test WHERE a = ALL (array[100, $1]); +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +--------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a = ALL (ARRAY[100, $1])) + -> Seq Scan on test_1 test + Filter: (a = ALL (ARRAY[100, $1])) +(4 rows) + +EXPLAIN (COSTS OFF) EXECUTE q(1); + QUERY PLAN +--------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (test.a = ALL (ARRAY[100, $1])) + -> Seq Scan on test_1 test + Filter: (a = ALL (ARRAY[100, $1])) +(4 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(100); + a | b +-----+----- + 100 | 100 +(1 row) + +DEALLOCATE q; +DROP TABLE array_qual.test CASCADE; +NOTICE: drop cascades to 11 other objects +DROP SCHEMA array_qual; +DROP EXTENSION pg_pathman; diff --git a/expected/pathman_basic.out b/expected/pathman_basic.out index 4117a00c..3afde299 100644 --- a/expected/pathman_basic.out +++ b/expected/pathman_basic.out @@ -1830,7 +1830,22 @@ SELECT * FROM test.mixinh_child1; SELECT * FROM test.mixinh_parent; ERROR: could not expand partitioned table "mixinh_child1" -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 32 other objects +DROP TABLE test.hash_rel CASCADE; +NOTICE: drop cascades to 3 other objects +DROP TABLE test.index_on_childs CASCADE; +NOTICE: drop cascades to 6 other objects +DROP TABLE test.mixinh_child1 CASCADE; +NOTICE: drop cascades to 2 other objects +DROP TABLE test.mixinh_parent CASCADE; +DROP TABLE test.num_range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test.hash_rel_wrong CASCADE; +DROP TABLE test.range_rel CASCADE; +DROP TABLE test.range_rel_archive CASCADE; +DROP TABLE test.special_case_1_ind_o_s CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test.range_rel_test1 CASCADE; +DROP TABLE test.range_rel_test2 CASCADE; +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_basic_1.out b/expected/pathman_basic_1.out index 702f9027..92a86727 100644 --- a/expected/pathman_basic_1.out +++ b/expected/pathman_basic_1.out @@ -1813,7 +1813,22 @@ SELECT * FROM test.mixinh_child1; SELECT * FROM test.mixinh_parent; ERROR: could not expand partitioned table "mixinh_child1" -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 32 other objects +DROP TABLE test.hash_rel CASCADE; +NOTICE: drop cascades to 3 other objects +DROP TABLE test.index_on_childs CASCADE; +NOTICE: drop cascades to 6 other objects +DROP TABLE test.mixinh_child1 CASCADE; +NOTICE: drop cascades to 2 other objects +DROP TABLE test.mixinh_parent CASCADE; +DROP TABLE test.num_range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test.hash_rel_wrong CASCADE; +DROP TABLE test.range_rel CASCADE; +DROP TABLE test.range_rel_archive CASCADE; +DROP TABLE test.special_case_1_ind_o_s CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test.range_rel_test1 CASCADE; +DROP TABLE test.range_rel_test2 CASCADE; +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_basic_2.out b/expected/pathman_basic_2.out index 28e46c14..ec180fdb 100644 --- a/expected/pathman_basic_2.out +++ b/expected/pathman_basic_2.out @@ -36,13 +36,13 @@ SELECT pathman.create_hash_partitions('test.hash_rel', 'value', 3, partition_dat (1 row) EXPLAIN (COSTS OFF) SELECT * FROM test.hash_rel; - QUERY PLAN -------------------------------------------- + QUERY PLAN +----------------------------------------- Append -> Seq Scan on hash_rel hash_rel_1 - -> Seq Scan on hash_rel_0 - -> Seq Scan on hash_rel_1 hash_rel_1_1 - -> Seq Scan on hash_rel_2 + -> Seq Scan on hash_rel_0 hash_rel_2 + -> Seq Scan on hash_rel_1 hash_rel_3 + -> Seq Scan on hash_rel_2 hash_rel_4 (5 rows) SELECT * FROM test.hash_rel; @@ -60,12 +60,12 @@ SELECT pathman.set_enable_parent('test.hash_rel', false); (1 row) EXPLAIN (COSTS OFF) SELECT * FROM test.hash_rel; - QUERY PLAN ------------------------------- + QUERY PLAN +----------------------------------------- Append - -> Seq Scan on hash_rel_0 - -> Seq Scan on hash_rel_1 - -> Seq Scan on hash_rel_2 + -> Seq Scan on hash_rel_0 hash_rel_1 + -> Seq Scan on hash_rel_1 hash_rel_2 + -> Seq Scan on hash_rel_2 hash_rel_3 (4 rows) SELECT * FROM test.hash_rel; @@ -80,13 +80,13 @@ SELECT pathman.set_enable_parent('test.hash_rel', true); (1 row) EXPLAIN (COSTS OFF) SELECT * FROM test.hash_rel; - QUERY PLAN -------------------------------------------- + QUERY PLAN +----------------------------------------- Append -> Seq Scan on hash_rel hash_rel_1 - -> Seq Scan on hash_rel_0 - -> Seq Scan on hash_rel_1 hash_rel_1_1 - -> Seq Scan on hash_rel_2 + -> Seq Scan on hash_rel_0 hash_rel_2 + -> Seq Scan on hash_rel_1 hash_rel_3 + -> Seq Scan on hash_rel_2 hash_rel_4 (5 rows) SELECT * FROM test.hash_rel; @@ -224,12 +224,12 @@ SELECT pathman.create_range_partitions('test.improved_dummy', 'id', 1, 10); INSERT INTO test.improved_dummy (name) VALUES ('test'); /* spawns new partition */ EXPLAIN (COSTS OFF) SELECT * FROM test.improved_dummy WHERE id = 101 OR id = 5 AND name = 'ib'; - QUERY PLAN ----------------------------------------------------- + QUERY PLAN +------------------------------------------------------ Append -> Seq Scan on improved_dummy_1 Filter: ((id = 5) AND (name = 'ib'::text)) - -> Seq Scan on improved_dummy_11 + -> Seq Scan on improved_dummy_11 improved_dummy_2 Filter: (id = 101) (5 rows) @@ -245,9 +245,9 @@ EXPLAIN (COSTS OFF) SELECT * FROM test.improved_dummy WHERE id = 101 OR id = 5 A Append -> Seq Scan on improved_dummy improved_dummy_1 Filter: ((id = 101) OR ((id = 5) AND (name = 'ib'::text))) - -> Seq Scan on improved_dummy_1 improved_dummy_1_1 + -> Seq Scan on improved_dummy_1 improved_dummy_2 Filter: ((id = 5) AND (name = 'ib'::text)) - -> Seq Scan on improved_dummy_11 + -> Seq Scan on improved_dummy_11 improved_dummy_3 Filter: (id = 101) (7 rows) @@ -259,9 +259,9 @@ SELECT pathman.set_enable_parent('test.improved_dummy', false); /* disable paren ALTER TABLE test.improved_dummy_1 ADD CHECK (name != 'ib'); /* make test.improved_dummy_1 disappear */ EXPLAIN (COSTS OFF) SELECT * FROM test.improved_dummy WHERE id = 101 OR id = 5 AND name = 'ib'; - QUERY PLAN -------------------------------- - Seq Scan on improved_dummy_11 + QUERY PLAN +---------------------------------------------- + Seq Scan on improved_dummy_11 improved_dummy Filter: (id = 101) (2 rows) @@ -277,7 +277,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM test.improved_dummy WHERE id = 101 OR id = 5 A Append -> Seq Scan on improved_dummy improved_dummy_1 Filter: ((id = 101) OR ((id = 5) AND (name = 'ib'::text))) - -> Seq Scan on improved_dummy_11 + -> Seq Scan on improved_dummy_11 improved_dummy_2 Filter: (id = 101) (5 rows) @@ -389,16 +389,16 @@ EXPLAIN (COSTS OFF) INSERT INTO test.insert_into_select_copy SELECT * FROM test.insert_into_select WHERE val <= 80; - QUERY PLAN ---------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------- Insert on insert_into_select_copy -> Append -> Seq Scan on insert_into_select insert_into_select_1 Filter: (val <= 80) - -> Seq Scan on insert_into_select_1 insert_into_select_1_1 - -> Seq Scan on insert_into_select_2 - -> Seq Scan on insert_into_select_3 - -> Seq Scan on insert_into_select_4 + -> Seq Scan on insert_into_select_1 insert_into_select_2 + -> Seq Scan on insert_into_select_2 insert_into_select_3 + -> Seq Scan on insert_into_select_3 insert_into_select_4 + -> Seq Scan on insert_into_select_4 insert_into_select_5 Filter: (val <= 80) (9 rows) @@ -418,12 +418,12 @@ SET enable_indexscan = OFF; SET enable_bitmapscan = OFF; SET enable_seqscan = ON; EXPLAIN (COSTS OFF) SELECT * FROM test.hash_rel; - QUERY PLAN ------------------------------- + QUERY PLAN +----------------------------------------- Append - -> Seq Scan on hash_rel_0 - -> Seq Scan on hash_rel_1 - -> Seq Scan on hash_rel_2 + -> Seq Scan on hash_rel_0 hash_rel_1 + -> Seq Scan on hash_rel_1 hash_rel_2 + -> Seq Scan on hash_rel_2 hash_rel_3 (4 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.hash_rel WHERE false; @@ -441,16 +441,16 @@ EXPLAIN (COSTS OFF) SELECT * FROM test.hash_rel WHERE value = NULL; (2 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.hash_rel WHERE value = 2; - QUERY PLAN ------------------------- - Seq Scan on hash_rel_1 + QUERY PLAN +--------------------------------- + Seq Scan on hash_rel_1 hash_rel Filter: (value = 2) (2 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.hash_rel WHERE 2 = value; /* test commutator */ - QUERY PLAN ------------------------- - Seq Scan on hash_rel_1 + QUERY PLAN +--------------------------------- + Seq Scan on hash_rel_1 hash_rel Filter: (2 = value) (2 rows) @@ -465,45 +465,45 @@ EXPLAIN (COSTS OFF) SELECT * FROM test.hash_rel WHERE value = 2 OR value = 1; (5 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.num_range_rel WHERE 2500 = id; /* test commutator */ - QUERY PLAN ------------------------------ - Seq Scan on num_range_rel_3 + QUERY PLAN +------------------------------------------- + Seq Scan on num_range_rel_3 num_range_rel Filter: (2500 = id) (2 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.num_range_rel WHERE 2500 < id; /* test commutator */ - QUERY PLAN ------------------------------------ + QUERY PLAN +--------------------------------------------------- Append - -> Seq Scan on num_range_rel_3 + -> Seq Scan on num_range_rel_3 num_range_rel_1 Filter: (2500 < id) - -> Seq Scan on num_range_rel_4 + -> Seq Scan on num_range_rel_4 num_range_rel_2 (4 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.num_range_rel WHERE id > 2500; - QUERY PLAN ------------------------------------ + QUERY PLAN +--------------------------------------------------- Append - -> Seq Scan on num_range_rel_3 + -> Seq Scan on num_range_rel_3 num_range_rel_1 Filter: (id > 2500) - -> Seq Scan on num_range_rel_4 + -> Seq Scan on num_range_rel_4 num_range_rel_2 (4 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.num_range_rel WHERE id >= 1000 AND id < 3000; - QUERY PLAN ------------------------------------ + QUERY PLAN +--------------------------------------------------- Append - -> Seq Scan on num_range_rel_2 - -> Seq Scan on num_range_rel_3 + -> Seq Scan on num_range_rel_2 num_range_rel_1 + -> Seq Scan on num_range_rel_3 num_range_rel_2 (3 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.num_range_rel WHERE id >= 1500 AND id < 2500; - QUERY PLAN ------------------------------------ + QUERY PLAN +--------------------------------------------------- Append - -> Seq Scan on num_range_rel_2 + -> Seq Scan on num_range_rel_2 num_range_rel_1 Filter: (id >= 1500) - -> Seq Scan on num_range_rel_3 + -> Seq Scan on num_range_rel_3 num_range_rel_2 Filter: (id < 2500) (5 rows) @@ -524,35 +524,35 @@ EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel WHERE dt > '2015-02-15'; QUERY PLAN -------------------------------------------------------------------------------- Append - -> Seq Scan on range_rel_2 + -> Seq Scan on range_rel_2 range_rel_1 Filter: (dt > 'Sun Feb 15 00:00:00 2015'::timestamp without time zone) - -> Seq Scan on range_rel_3 - -> Seq Scan on range_rel_4 + -> Seq Scan on range_rel_3 range_rel_2 + -> Seq Scan on range_rel_4 range_rel_3 (5 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel WHERE '2015-02-15' < dt; /* test commutator */ QUERY PLAN -------------------------------------------------------------------------------- Append - -> Seq Scan on range_rel_2 + -> Seq Scan on range_rel_2 range_rel_1 Filter: ('Sun Feb 15 00:00:00 2015'::timestamp without time zone < dt) - -> Seq Scan on range_rel_3 - -> Seq Scan on range_rel_4 + -> Seq Scan on range_rel_3 range_rel_2 + -> Seq Scan on range_rel_4 range_rel_3 (5 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel WHERE dt >= '2015-02-01' AND dt < '2015-03-01'; - QUERY PLAN -------------------------- - Seq Scan on range_rel_2 + QUERY PLAN +----------------------------------- + Seq Scan on range_rel_2 range_rel (1 row) EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel WHERE dt >= '2015-02-15' AND dt < '2015-03-15'; QUERY PLAN --------------------------------------------------------------------------------- Append - -> Seq Scan on range_rel_2 + -> Seq Scan on range_rel_2 range_rel_1 Filter: (dt >= 'Sun Feb 15 00:00:00 2015'::timestamp without time zone) - -> Seq Scan on range_rel_3 + -> Seq Scan on range_rel_3 range_rel_2 Filter: (dt < 'Sun Mar 15 00:00:00 2015'::timestamp without time zone) (5 rows) @@ -573,12 +573,12 @@ SET enable_indexscan = ON; SET enable_bitmapscan = OFF; SET enable_seqscan = OFF; EXPLAIN (COSTS OFF) SELECT * FROM test.hash_rel; - QUERY PLAN ------------------------------- + QUERY PLAN +----------------------------------------- Append - -> Seq Scan on hash_rel_0 - -> Seq Scan on hash_rel_1 - -> Seq Scan on hash_rel_2 + -> Seq Scan on hash_rel_0 hash_rel_1 + -> Seq Scan on hash_rel_1 hash_rel_2 + -> Seq Scan on hash_rel_2 hash_rel_3 (4 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.hash_rel WHERE false; @@ -596,16 +596,16 @@ EXPLAIN (COSTS OFF) SELECT * FROM test.hash_rel WHERE value = NULL; (2 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.hash_rel WHERE value = 2; - QUERY PLAN ------------------------- - Seq Scan on hash_rel_1 + QUERY PLAN +--------------------------------- + Seq Scan on hash_rel_1 hash_rel Filter: (value = 2) (2 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.hash_rel WHERE 2 = value; /* test commutator */ - QUERY PLAN ------------------------- - Seq Scan on hash_rel_1 + QUERY PLAN +--------------------------------- + Seq Scan on hash_rel_1 hash_rel Filter: (2 = value) (2 rows) @@ -620,45 +620,45 @@ EXPLAIN (COSTS OFF) SELECT * FROM test.hash_rel WHERE value = 2 OR value = 1; (5 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.num_range_rel WHERE 2500 = id; /* test commutator */ - QUERY PLAN ----------------------------------------------------------- - Index Scan using num_range_rel_3_pkey on num_range_rel_3 + QUERY PLAN +------------------------------------------------------------------------ + Index Scan using num_range_rel_3_pkey on num_range_rel_3 num_range_rel Index Cond: (id = 2500) (2 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.num_range_rel WHERE 2500 < id; /* test commutator */ - QUERY PLAN ----------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------------------------- Append - -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 + -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 num_range_rel_1 Index Cond: (id > 2500) - -> Seq Scan on num_range_rel_4 + -> Seq Scan on num_range_rel_4 num_range_rel_2 (4 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.num_range_rel WHERE id > 2500; - QUERY PLAN ----------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------------------------- Append - -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 + -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 num_range_rel_1 Index Cond: (id > 2500) - -> Seq Scan on num_range_rel_4 + -> Seq Scan on num_range_rel_4 num_range_rel_2 (4 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.num_range_rel WHERE id >= 1000 AND id < 3000; - QUERY PLAN ------------------------------------ + QUERY PLAN +--------------------------------------------------- Append - -> Seq Scan on num_range_rel_2 - -> Seq Scan on num_range_rel_3 + -> Seq Scan on num_range_rel_2 num_range_rel_1 + -> Seq Scan on num_range_rel_3 num_range_rel_2 (3 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.num_range_rel WHERE id >= 1500 AND id < 2500; - QUERY PLAN ----------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------------------------- Append - -> Index Scan using num_range_rel_2_pkey on num_range_rel_2 + -> Index Scan using num_range_rel_2_pkey on num_range_rel_2 num_range_rel_1 Index Cond: (id >= 1500) - -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 + -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 num_range_rel_2 Index Cond: (id < 2500) (5 rows) @@ -699,35 +699,35 @@ EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel WHERE dt > '2015-02-15'; QUERY PLAN ------------------------------------------------------------------------------------ Append - -> Index Scan using range_rel_2_dt_idx on range_rel_2 + -> Index Scan using range_rel_2_dt_idx on range_rel_2 range_rel_1 Index Cond: (dt > 'Sun Feb 15 00:00:00 2015'::timestamp without time zone) - -> Seq Scan on range_rel_3 - -> Seq Scan on range_rel_4 + -> Seq Scan on range_rel_3 range_rel_2 + -> Seq Scan on range_rel_4 range_rel_3 (5 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel WHERE '2015-02-15' < dt; /* test commutator */ QUERY PLAN ------------------------------------------------------------------------------------ Append - -> Index Scan using range_rel_2_dt_idx on range_rel_2 + -> Index Scan using range_rel_2_dt_idx on range_rel_2 range_rel_1 Index Cond: (dt > 'Sun Feb 15 00:00:00 2015'::timestamp without time zone) - -> Seq Scan on range_rel_3 - -> Seq Scan on range_rel_4 + -> Seq Scan on range_rel_3 range_rel_2 + -> Seq Scan on range_rel_4 range_rel_3 (5 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel WHERE dt >= '2015-02-01' AND dt < '2015-03-01'; - QUERY PLAN -------------------------- - Seq Scan on range_rel_2 + QUERY PLAN +----------------------------------- + Seq Scan on range_rel_2 range_rel (1 row) EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel WHERE dt >= '2015-02-15' AND dt < '2015-03-15'; QUERY PLAN ------------------------------------------------------------------------------------- Append - -> Index Scan using range_rel_2_dt_idx on range_rel_2 + -> Index Scan using range_rel_2_dt_idx on range_rel_2 range_rel_1 Index Cond: (dt >= 'Sun Feb 15 00:00:00 2015'::timestamp without time zone) - -> Index Scan using range_rel_3_dt_idx on range_rel_3 + -> Index Scan using range_rel_3_dt_idx on range_rel_3 range_rel_2 Index Cond: (dt < 'Sun Mar 15 00:00:00 2015'::timestamp without time zone) (5 rows) @@ -774,7 +774,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel WHERE dt < '2015-03-01' ORDER B QUERY PLAN ------------------------------------- Sort - Sort Key: range_rel_1.dt + Sort Key: range_rel.dt -> Append -> Seq Scan on range_rel_1 -> Seq Scan on range_rel_2 @@ -823,18 +823,18 @@ CREATE OR REPLACE FUNCTION test.sql_inline_func(i_id int) RETURNS SETOF INT AS $ select * from test.sql_inline where id = i_id limit 1; $$ LANGUAGE sql STABLE; EXPLAIN (COSTS OFF) SELECT * FROM test.sql_inline_func(5); - QUERY PLAN --------------------------------- + QUERY PLAN +------------------------------------------- Limit - -> Seq Scan on sql_inline_0 + -> Seq Scan on sql_inline_0 sql_inline Filter: (id = 5) (3 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.sql_inline_func(1); - QUERY PLAN --------------------------------- + QUERY PLAN +------------------------------------------- Limit - -> Seq Scan on sql_inline_2 + -> Seq Scan on sql_inline_2 sql_inline Filter: (id = 1) (3 rows) @@ -876,12 +876,12 @@ SELECT pathman.split_range_partition('test.num_range_rel_1', 500); (1 row) EXPLAIN (COSTS OFF) SELECT * FROM test.num_range_rel WHERE id BETWEEN 100 AND 700; - QUERY PLAN ----------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------------------------- Append -> Index Scan using num_range_rel_1_pkey on num_range_rel_1 Index Cond: (id >= 100) - -> Index Scan using num_range_rel_5_pkey on num_range_rel_5 + -> Index Scan using num_range_rel_5_pkey on num_range_rel_5 num_range_rel_2 Index Cond: (id <= 700) (5 rows) @@ -907,9 +907,9 @@ SELECT pathman.merge_range_partitions('test.num_range_rel_1', 'test.num_range_re (1 row) EXPLAIN (COSTS OFF) SELECT * FROM test.num_range_rel WHERE id BETWEEN 100 AND 700; - QUERY PLAN ----------------------------------------------------------- - Index Scan using num_range_rel_1_pkey on num_range_rel_1 + QUERY PLAN +------------------------------------------------------------------------ + Index Scan using num_range_rel_1_pkey on num_range_rel_1 num_range_rel Index Cond: ((id >= 100) AND (id <= 700)) (2 rows) @@ -927,9 +927,9 @@ SELECT pathman.append_range_partition('test.num_range_rel'); (1 row) EXPLAIN (COSTS OFF) SELECT * FROM test.num_range_rel WHERE id >= 4000; - QUERY PLAN ------------------------------ - Seq Scan on num_range_rel_6 + QUERY PLAN +------------------------------------------- + Seq Scan on num_range_rel_6 num_range_rel (1 row) SELECT pathman.prepend_range_partition('test.num_range_rel'); @@ -939,9 +939,9 @@ SELECT pathman.prepend_range_partition('test.num_range_rel'); (1 row) EXPLAIN (COSTS OFF) SELECT * FROM test.num_range_rel WHERE id < 0; - QUERY PLAN ------------------------------ - Seq Scan on num_range_rel_7 + QUERY PLAN +------------------------------------------- + Seq Scan on num_range_rel_7 num_range_rel (1 row) SELECT pathman.drop_range_partition('test.num_range_rel_7'); @@ -995,9 +995,9 @@ EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel WHERE dt BETWEEN '2014-12-15' A QUERY PLAN ------------------------------------------------------------------------------------- Append - -> Index Scan using range_rel_7_dt_idx on range_rel_7 + -> Index Scan using range_rel_7_dt_idx on range_rel_7 range_rel_1 Index Cond: (dt >= 'Mon Dec 15 00:00:00 2014'::timestamp without time zone) - -> Index Scan using range_rel_1_dt_idx on range_rel_1 + -> Index Scan using range_rel_1_dt_idx on range_rel_1 range_rel_2 Index Cond: (dt <= 'Thu Jan 15 00:00:00 2015'::timestamp without time zone) (5 rows) @@ -1010,7 +1010,7 @@ SELECT pathman.drop_range_partition('test.range_rel_7'); EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel WHERE dt BETWEEN '2014-12-15' AND '2015-01-15'; QUERY PLAN ------------------------------------------------------------------------------- - Index Scan using range_rel_1_dt_idx on range_rel_1 + Index Scan using range_rel_1_dt_idx on range_rel_1 range_rel Index Cond: (dt <= 'Thu Jan 15 00:00:00 2015'::timestamp without time zone) (2 rows) @@ -1026,9 +1026,9 @@ EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel WHERE dt BETWEEN '2014-12-15' A QUERY PLAN ------------------------------------------------------------------------------------- Append - -> Index Scan using range_rel_8_dt_idx on range_rel_8 + -> Index Scan using range_rel_8_dt_idx on range_rel_8 range_rel_1 Index Cond: (dt >= 'Mon Dec 15 00:00:00 2014'::timestamp without time zone) - -> Index Scan using range_rel_1_dt_idx on range_rel_1 + -> Index Scan using range_rel_1_dt_idx on range_rel_1 range_rel_2 Index Cond: (dt <= 'Thu Jan 15 00:00:00 2015'::timestamp without time zone) (5 rows) @@ -1045,10 +1045,10 @@ EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel WHERE dt BETWEEN '2014-11-15' A QUERY PLAN ------------------------------------------------------------------------------------- Append - -> Index Scan using range_rel_archive_dt_idx on range_rel_archive + -> Index Scan using range_rel_archive_dt_idx on range_rel_archive range_rel_1 Index Cond: (dt >= 'Sat Nov 15 00:00:00 2014'::timestamp without time zone) - -> Seq Scan on range_rel_8 - -> Index Scan using range_rel_1_dt_idx on range_rel_1 + -> Seq Scan on range_rel_8 range_rel_2 + -> Index Scan using range_rel_1_dt_idx on range_rel_1 range_rel_3 Index Cond: (dt <= 'Thu Jan 15 00:00:00 2015'::timestamp without time zone) (6 rows) @@ -1062,8 +1062,8 @@ EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel WHERE dt BETWEEN '2014-11-15' A QUERY PLAN ------------------------------------------------------------------------------------- Append - -> Seq Scan on range_rel_8 - -> Index Scan using range_rel_1_dt_idx on range_rel_1 + -> Seq Scan on range_rel_8 range_rel_1 + -> Index Scan using range_rel_1_dt_idx on range_rel_1 range_rel_2 Index Cond: (dt <= 'Thu Jan 15 00:00:00 2015'::timestamp without time zone) (4 rows) @@ -1120,19 +1120,19 @@ SELECT * FROM pathman.pathman_partition_list WHERE parent = 'test.range_rel'::RE INSERT INTO test.range_rel (dt) VALUES ('2012-06-15'); INSERT INTO test.range_rel (dt) VALUES ('2015-12-15'); EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel WHERE dt < '2015-01-01'; - QUERY PLAN --------------------------------------------- + QUERY PLAN +-------------------------------------------------------- Append - -> Seq Scan on range_rel_minus_infinity - -> Seq Scan on range_rel_8 + -> Seq Scan on range_rel_minus_infinity range_rel_1 + -> Seq Scan on range_rel_8 range_rel_2 (3 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel WHERE dt >= '2015-05-01'; - QUERY PLAN -------------------------------------------- + QUERY PLAN +------------------------------------------------------- Append - -> Seq Scan on range_rel_6 - -> Seq Scan on range_rel_plus_infinity + -> Seq Scan on range_rel_6 range_rel_1 + -> Seq Scan on range_rel_plus_infinity range_rel_2 (3 rows) /* @@ -1199,12 +1199,12 @@ SELECT pathman.replace_hash_partition('test.hash_rel_0', 'test.hash_rel_extern') /* Check the consistency of test.hash_rel_0 and test.hash_rel_extern relations */ EXPLAIN(COSTS OFF) SELECT * FROM test.hash_rel; - QUERY PLAN ------------------------------------ + QUERY PLAN +---------------------------------------------- Append - -> Seq Scan on hash_rel_extern - -> Seq Scan on hash_rel_1 - -> Seq Scan on hash_rel_2 + -> Seq Scan on hash_rel_extern hash_rel_1 + -> Seq Scan on hash_rel_1 hash_rel_2 + -> Seq Scan on hash_rel_2 hash_rel_3 (4 rows) SELECT parent, partition, parttype @@ -1247,12 +1247,12 @@ CREATE TABLE test.hash_rel_wrong( SELECT pathman.replace_hash_partition('test.hash_rel_1', 'test.hash_rel_wrong'); ERROR: column "value" in child table must be marked NOT NULL EXPLAIN (COSTS OFF) SELECT * FROM test.hash_rel; - QUERY PLAN ------------------------------------ + QUERY PLAN +---------------------------------------------- Append - -> Seq Scan on hash_rel_extern - -> Seq Scan on hash_rel_1 - -> Seq Scan on hash_rel_2 + -> Seq Scan on hash_rel_extern hash_rel_1 + -> Seq Scan on hash_rel_1 hash_rel_2 + -> Seq Scan on hash_rel_2 hash_rel_3 (4 rows) /* @@ -1350,7 +1350,7 @@ SELECT generate_series('2014-12-31', '2014-12-01', '-1 day'::interval); EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel WHERE dt = '2014-12-15'; QUERY PLAN -------------------------------------------------------------------------- - Seq Scan on range_rel_14 + Seq Scan on range_rel_14 range_rel Filter: (dt = 'Mon Dec 15 00:00:00 2014'::timestamp without time zone) (2 rows) @@ -1363,7 +1363,7 @@ SELECT * FROM test.range_rel WHERE dt = '2014-12-15'; EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel WHERE dt = '2015-03-15'; QUERY PLAN -------------------------------------------------------------------------- - Seq Scan on range_rel_8 + Seq Scan on range_rel_8 range_rel Filter: (dt = 'Sun Mar 15 00:00:00 2015'::timestamp without time zone) (2 rows) @@ -1532,14 +1532,14 @@ SELECT create_hash_partitions('test.hash_rel', 'value', 3); (1 row) EXPLAIN (COSTS OFF) SELECT * FROM test.hash_rel WHERE id = 1234; - QUERY PLAN ------------------------------------------------------- + QUERY PLAN +----------------------------------------------------------------- Append - -> Index Scan using hash_rel_0_pkey on hash_rel_0 + -> Index Scan using hash_rel_0_pkey on hash_rel_0 hash_rel_1 Index Cond: (id = 1234) - -> Index Scan using hash_rel_1_pkey on hash_rel_1 + -> Index Scan using hash_rel_1_pkey on hash_rel_1 hash_rel_2 Index Cond: (id = 1234) - -> Index Scan using hash_rel_2_pkey on hash_rel_2 + -> Index Scan using hash_rel_2_pkey on hash_rel_2 hash_rel_3 Index Cond: (id = 1234) (7 rows) @@ -1580,21 +1580,21 @@ SELECT prepend_range_partition('test.range_rel'); (1 row) EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel WHERE dt < '2010-03-01'; - QUERY PLAN --------------------------------- + QUERY PLAN +-------------------------------------------- Append - -> Seq Scan on range_rel_15 - -> Seq Scan on range_rel_1 - -> Seq Scan on range_rel_13 + -> Seq Scan on range_rel_15 range_rel_1 + -> Seq Scan on range_rel_1 range_rel_2 + -> Seq Scan on range_rel_13 range_rel_3 (4 rows) EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel WHERE dt > '2010-12-15'; QUERY PLAN -------------------------------------------------------------------------------- Append - -> Seq Scan on range_rel_12 + -> Seq Scan on range_rel_12 range_rel_1 Filter: (dt > 'Wed Dec 15 00:00:00 2010'::timestamp without time zone) - -> Seq Scan on range_rel_14 + -> Seq Scan on range_rel_14 range_rel_2 (4 rows) /* Create range partitions from whole range */ @@ -1682,14 +1682,14 @@ SELECT set_enable_parent('test.special_case_1_ind_o_s', true); (1 row) EXPLAIN (COSTS OFF) SELECT * FROM test.special_case_1_ind_o_s WHERE val < 75 AND comment = 'a'; - QUERY PLAN --------------------------------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------- Append -> Seq Scan on special_case_1_ind_o_s special_case_1_ind_o_s_1 Filter: ((val < 75) AND (comment = 'a'::text)) - -> Seq Scan on special_case_1_ind_o_s_1 special_case_1_ind_o_s_1_1 + -> Seq Scan on special_case_1_ind_o_s_1 special_case_1_ind_o_s_2 Filter: (comment = 'a'::text) - -> Index Only Scan using special_case_1_ind_o_s_2_val_comment_idx on special_case_1_ind_o_s_2 + -> Index Only Scan using special_case_1_ind_o_s_2_val_comment_idx on special_case_1_ind_o_s_2 special_case_1_ind_o_s_3 Index Cond: ((val < 75) AND (comment = 'a'::text)) (7 rows) @@ -1757,18 +1757,18 @@ SELECT set_enable_parent('test.index_on_childs', true); VACUUM ANALYZE test.index_on_childs; EXPLAIN (COSTS OFF) SELECT * FROM test.index_on_childs WHERE c1 > 100 AND c1 < 2500 AND c2 = 500; - QUERY PLAN ------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------ Append -> Index Scan using index_on_childs_c2_idx on index_on_childs index_on_childs_1 Index Cond: (c2 = 500) Filter: ((c1 > 100) AND (c1 < 2500)) - -> Index Scan using index_on_childs_1_1k_c2_idx on index_on_childs_1_1k + -> Index Scan using index_on_childs_1_1k_c2_idx on index_on_childs_1_1k index_on_childs_2 Index Cond: (c2 = 500) Filter: (c1 > 100) - -> Index Scan using index_on_childs_1k_2k_c2_idx on index_on_childs_1k_2k + -> Index Scan using index_on_childs_1k_2k_c2_idx on index_on_childs_1k_2k index_on_childs_3 Index Cond: (c2 = 500) - -> Index Scan using index_on_childs_2k_3k_c2_idx on index_on_childs_2k_3k + -> Index Scan using index_on_childs_2k_3k_c2_idx on index_on_childs_2k_3k index_on_childs_4 Index Cond: (c2 = 500) Filter: (c1 < 2500) (12 rows) @@ -1813,7 +1813,22 @@ SELECT * FROM test.mixinh_child1; SELECT * FROM test.mixinh_parent; ERROR: could not expand partitioned table "mixinh_child1" -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 32 other objects +DROP TABLE test.hash_rel CASCADE; +NOTICE: drop cascades to 3 other objects +DROP TABLE test.index_on_childs CASCADE; +NOTICE: drop cascades to 6 other objects +DROP TABLE test.mixinh_child1 CASCADE; +NOTICE: drop cascades to 2 other objects +DROP TABLE test.mixinh_parent CASCADE; +DROP TABLE test.num_range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test.hash_rel_wrong CASCADE; +DROP TABLE test.range_rel CASCADE; +DROP TABLE test.range_rel_archive CASCADE; +DROP TABLE test.special_case_1_ind_o_s CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test.range_rel_test1 CASCADE; +DROP TABLE test.range_rel_test2 CASCADE; +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_bgw.out b/expected/pathman_bgw.out index 5d5d2b21..4f2ad6b8 100644 --- a/expected/pathman_bgw.out +++ b/expected/pathman_bgw.out @@ -242,5 +242,5 @@ SELECT count(*) FROM test_bgw.conc_part; DROP TABLE test_bgw.conc_part CASCADE; NOTICE: drop cascades to 5 other objects -DROP SCHEMA test_bgw CASCADE; +DROP SCHEMA test_bgw; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_cache_pranks.out b/expected/pathman_cache_pranks.out index 5493ae96..278643ff 100644 --- a/expected/pathman_cache_pranks.out +++ b/expected/pathman_cache_pranks.out @@ -76,5 +76,155 @@ ERROR: can't partition table "part_test" with existing children DROP TABLE part_test CASCADE; NOTICE: drop cascades to 302 other objects -- +-- +-- PGPRO-7870 +-- Added error for case executing prepared query after DROP/CREATE EXTENSION. +-- +-- DROP/CREATE extension +CREATE TABLE part_test(a INT4 NOT NULL, b INT4); +PREPARE q(int4) AS SELECT * FROM part_test WHERE a > ALL (array[$1, 898]); +SELECT create_range_partitions('part_test', 'a', 1, 100, 10); + create_range_partitions +------------------------- + 10 +(1 row) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +DROP EXTENSION pg_pathman; +CREATE EXTENSION pg_pathman; +EXECUTE q(1); +ERROR: table "part_test" is not partitioned +DEALLOCATE q; +DROP TABLE part_test CASCADE; +NOTICE: drop cascades to 11 other objects +-- DROP/CREATE disabled extension +CREATE TABLE part_test(a INT4 NOT NULL, b INT4); +PREPARE q(int4) AS SELECT * FROM part_test WHERE a > ALL (array[$1, 898]); +SELECT create_range_partitions('part_test', 'a', 1, 100, 10); + create_range_partitions +------------------------- + 10 +(1 row) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +SET pg_pathman.enable = f; +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been disabled +DROP EXTENSION pg_pathman; +CREATE EXTENSION pg_pathman; +SET pg_pathman.enable = t; +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been enabled +EXECUTE q(1); +ERROR: table "part_test" is not partitioned +DEALLOCATE q; +DROP TABLE part_test CASCADE; +NOTICE: drop cascades to 11 other objects +-- DROP/CREATE extension in autonomous transaction +CREATE TABLE part_test(a INT4 NOT NULL, b INT4); +PREPARE q(int4) AS SELECT * FROM part_test WHERE a > ALL (array[$1, 198]); +SELECT create_range_partitions('part_test', 'a', 1, 100, 2); + create_range_partitions +------------------------- + 2 +(1 row) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +BEGIN; + BEGIN AUTONOMOUS; + DROP EXTENSION pg_pathman; + CREATE EXTENSION pg_pathman; + COMMIT; +COMMIT; +EXECUTE q(1); +ERROR: table "part_test" is not partitioned +DEALLOCATE q; +DROP TABLE part_test CASCADE; +NOTICE: drop cascades to 3 other objects -- finalize DROP EXTENSION pg_pathman; diff --git a/expected/pathman_cache_pranks_1.out b/expected/pathman_cache_pranks_1.out new file mode 100644 index 00000000..4a3982a6 --- /dev/null +++ b/expected/pathman_cache_pranks_1.out @@ -0,0 +1,237 @@ +\set VERBOSITY terse +-- is pathman (caches, in particular) strong enough to carry out this? +SET search_path = 'public'; +-- make sure nothing breaks on disable/enable when nothing was initialized yet +SET pg_pathman.enable = false; +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been disabled +SET pg_pathman.enable = true; +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been enabled +-- wobble with create-drop ext: tests cached relids sanity +CREATE EXTENSION pg_pathman; +SET pg_pathman.enable = f; +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been disabled +DROP EXTENSION pg_pathman; +CREATE EXTENSION pg_pathman; +SET pg_pathman.enable = true; +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been enabled +DROP EXTENSION pg_pathman; +CREATE EXTENSION pg_pathman; +DROP EXTENSION pg_pathman; +-- create it for further tests +CREATE EXTENSION pg_pathman; +-- 079797e0d5 +CREATE TABLE part_test(val serial); +INSERT INTO part_test SELECT generate_series(1, 30); +SELECT create_range_partitions('part_test', 'val', 1, 10); + create_range_partitions +------------------------- + 3 +(1 row) + +SELECT set_interval('part_test', 100); + set_interval +-------------- + +(1 row) + +DELETE FROM pathman_config WHERE partrel = 'part_test'::REGCLASS; +SELECT drop_partitions('part_test'); +ERROR: table "part_test" has no partitions +SELECT disable_pathman_for('part_test'); + disable_pathman_for +--------------------- + +(1 row) + +CREATE TABLE wrong_partition (LIKE part_test) INHERITS (part_test); +NOTICE: merging column "val" with inherited definition +SELECT add_to_pathman_config('part_test', 'val', '10'); +ERROR: constraint "pathman_wrong_partition_check" of partition "wrong_partition" does not exist +SELECT add_to_pathman_config('part_test', 'val'); +ERROR: wrong constraint format for HASH partition "part_test_1" +DROP TABLE part_test CASCADE; +NOTICE: drop cascades to 5 other objects +-- +-- 85fc5ccf121 +CREATE TABLE part_test(val serial); +INSERT INTO part_test SELECT generate_series(1, 3000); +SELECT create_range_partitions('part_test', 'val', 1, 10); + create_range_partitions +------------------------- + 300 +(1 row) + +SELECT append_range_partition('part_test'); + append_range_partition +------------------------ + part_test_301 +(1 row) + +DELETE FROM part_test; +SELECT create_single_range_partition('part_test', NULL::INT4, NULL); /* not ok */ +ERROR: cannot create partition with range (-inf, +inf) +DELETE FROM pathman_config WHERE partrel = 'part_test'::REGCLASS; +SELECT create_hash_partitions('part_test', 'val', 2, partition_names := ARRAY[]::TEXT[]); /* not ok */ +ERROR: can't partition table "part_test" with existing children +DROP TABLE part_test CASCADE; +NOTICE: drop cascades to 302 other objects +-- +-- +-- PGPRO-7870 +-- Added error for case executing prepared query after DROP/CREATE EXTENSION. +-- +-- DROP/CREATE extension +CREATE TABLE part_test(a INT4 NOT NULL, b INT4); +PREPARE q(int4) AS SELECT * FROM part_test WHERE a > ALL (array[$1, 898]); +SELECT create_range_partitions('part_test', 'a', 1, 100, 10); + create_range_partitions +------------------------- + 10 +(1 row) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +DROP EXTENSION pg_pathman; +CREATE EXTENSION pg_pathman; +EXECUTE q(1); +ERROR: table "part_test" is not partitioned +DEALLOCATE q; +DROP TABLE part_test CASCADE; +NOTICE: drop cascades to 11 other objects +-- DROP/CREATE disabled extension +CREATE TABLE part_test(a INT4 NOT NULL, b INT4); +PREPARE q(int4) AS SELECT * FROM part_test WHERE a > ALL (array[$1, 898]); +SELECT create_range_partitions('part_test', 'a', 1, 100, 10); + create_range_partitions +------------------------- + 10 +(1 row) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +SET pg_pathman.enable = f; +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been disabled +DROP EXTENSION pg_pathman; +CREATE EXTENSION pg_pathman; +SET pg_pathman.enable = t; +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been enabled +EXECUTE q(1); +ERROR: table "part_test" is not partitioned +DEALLOCATE q; +DROP TABLE part_test CASCADE; +NOTICE: drop cascades to 11 other objects +-- DROP/CREATE extension in autonomous transaction +CREATE TABLE part_test(a INT4 NOT NULL, b INT4); +PREPARE q(int4) AS SELECT * FROM part_test WHERE a > ALL (array[$1, 198]); +SELECT create_range_partitions('part_test', 'a', 1, 100, 2); + create_range_partitions +------------------------- + 2 +(1 row) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +EXECUTE q(1); + a | b +---+--- +(0 rows) + +BEGIN; + BEGIN AUTONOMOUS; +ERROR: syntax error at or near "AUTONOMOUS" at character 7 + DROP EXTENSION pg_pathman; +ERROR: current transaction is aborted, commands ignored until end of transaction block + CREATE EXTENSION pg_pathman; +ERROR: current transaction is aborted, commands ignored until end of transaction block + COMMIT; +COMMIT; +WARNING: there is no transaction in progress +EXECUTE q(1); + a | b +---+--- +(0 rows) + +DEALLOCATE q; +DROP TABLE part_test CASCADE; +NOTICE: drop cascades to 3 other objects +-- finalize +DROP EXTENSION pg_pathman; diff --git a/expected/pathman_calamity.out b/expected/pathman_calamity.out index 7e794a72..b9421bde 100644 --- a/expected/pathman_calamity.out +++ b/expected/pathman_calamity.out @@ -320,7 +320,7 @@ SELECT validate_relname(NULL); ERROR: relation should not be NULL /* check function validate_expression() */ SELECT validate_expression(1::regclass, NULL); /* not ok */ -ERROR: relation "1" does not exist +ERROR: identifier "1" must be normal Oid SELECT validate_expression(NULL::regclass, NULL); /* not ok */ ERROR: 'relid' should not be NULL SELECT validate_expression('calamity.part_test', NULL); /* not ok */ @@ -426,19 +426,19 @@ SELECT build_sequence_name(NULL) IS NULL; /* check function partition_table_concurrently() */ SELECT partition_table_concurrently(1::REGCLASS); /* not ok */ -ERROR: relation "1" has no partitions +ERROR: identifier "1" must be normal Oid SELECT partition_table_concurrently('pg_class', 0); /* not ok */ ERROR: 'batch_size' should not be less than 1 or greater than 10000 SELECT partition_table_concurrently('pg_class', 1, 1E-5); /* not ok */ ERROR: 'sleep_time' should not be less than 0.5 SELECT partition_table_concurrently('pg_class'); /* not ok */ -ERROR: relation "pg_class" has no partitions +ERROR: identifier "1259" must be normal Oid /* check function stop_concurrent_part_task() */ SELECT stop_concurrent_part_task(1::REGCLASS); /* not ok */ ERROR: cannot find worker for relation "1" /* check function drop_range_partition_expand_next() */ SELECT drop_range_partition_expand_next('pg_class'); /* not ok */ -ERROR: relation "pg_class" is not a partition +ERROR: identifier "1259" must be normal Oid SELECT drop_range_partition_expand_next(NULL) IS NULL; ?column? ---------- @@ -560,7 +560,7 @@ DROP FUNCTION calamity.dummy_cb(arg jsonb); SELECT add_to_pathman_config(NULL, 'val'); /* no table */ ERROR: 'parent_relid' should not be NULL SELECT add_to_pathman_config(0::REGCLASS, 'val'); /* no table (oid) */ -ERROR: relation "0" does not exist +ERROR: identifier "0" must be normal Oid SELECT add_to_pathman_config('calamity.part_test', NULL); /* no expr */ ERROR: 'expression' should not be NULL SELECT add_to_pathman_config('calamity.part_test', 'V_A_L'); /* wrong expr */ @@ -779,8 +779,16 @@ SELECT merge_range_partitions('calamity.merge_test_a_1', ERROR: cannot merge partitions DROP TABLE calamity.merge_test_a,calamity.merge_test_b CASCADE; NOTICE: drop cascades to 6 other objects -DROP SCHEMA calamity CASCADE; -NOTICE: drop cascades to 15 other objects +DROP DOMAIN calamity.test_domain; +DROP TABLE calamity.part_test CASCADE; +NOTICE: drop cascades to table calamity.wrong_partition +DROP TABLE calamity.part_ok CASCADE; +NOTICE: drop cascades to 4 other objects +DROP TABLE calamity.hash_two_times CASCADE; +NOTICE: drop cascades to 2 other objects +DROP TABLE calamity.to_be_disabled CASCADE; +NOTICE: drop cascades to 3 other objects +DROP SCHEMA calamity; DROP EXTENSION pg_pathman; /* * ------------------------------- @@ -816,25 +824,25 @@ SELECT create_range_partitions('calamity.test_pathman_cache_stats', 'val', 1, 10 10 (1 row) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 0 partition parents cache | 0 - partition status cache | 2 -(4 rows) +(3 rows) DROP TABLE calamity.test_pathman_cache_stats CASCADE; NOTICE: drop cascades to 11 other objects -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 0 partition parents cache | 0 - partition status cache | 2 -(4 rows) +(3 rows) /* Change this setting for code coverage */ SET pg_pathman.enable_bounds_cache = false; @@ -862,25 +870,25 @@ EXPLAIN (COSTS OFF) SELECT * FROM calamity.test_pathman_cache_stats; -> Seq Scan on test_pathman_cache_stats_10 (11 rows) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 0 partition parents cache | 10 - partition status cache | 3 -(4 rows) +(3 rows) DROP TABLE calamity.test_pathman_cache_stats CASCADE; NOTICE: drop cascades to 11 other objects -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 0 partition parents cache | 0 - partition status cache | 2 -(4 rows) +(3 rows) /* Restore this GUC */ SET pg_pathman.enable_bounds_cache = true; @@ -908,25 +916,25 @@ EXPLAIN (COSTS OFF) SELECT * FROM calamity.test_pathman_cache_stats; -> Seq Scan on test_pathman_cache_stats_10 (11 rows) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 10 partition parents cache | 10 - partition status cache | 3 -(4 rows) +(3 rows) DROP TABLE calamity.test_pathman_cache_stats CASCADE; NOTICE: drop cascades to 11 other objects -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 0 partition parents cache | 0 - partition status cache | 2 -(4 rows) +(3 rows) /* check that parents cache has been flushed after partition was dropped */ CREATE TABLE calamity.test_pathman_cache_stats(val NUMERIC NOT NULL); @@ -952,14 +960,14 @@ EXPLAIN (COSTS OFF) SELECT * FROM calamity.test_pathman_cache_stats; -> Seq Scan on test_pathman_cache_stats_10 (11 rows) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 10 partition parents cache | 10 - partition status cache | 3 -(4 rows) +(3 rows) SELECT drop_range_partition('calamity.test_pathman_cache_stats_1'); drop_range_partition @@ -967,27 +975,27 @@ SELECT drop_range_partition('calamity.test_pathman_cache_stats_1'); calamity.test_pathman_cache_stats_1 (1 row) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 9 partition parents cache | 9 - partition status cache | 2 -(4 rows) +(3 rows) DROP TABLE calamity.test_pathman_cache_stats CASCADE; NOTICE: drop cascades to 10 other objects -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 0 partition parents cache | 0 - partition status cache | 2 -(4 rows) +(3 rows) -DROP SCHEMA calamity CASCADE; +DROP SCHEMA calamity; DROP EXTENSION pg_pathman; /* * ------------------------------------------ @@ -1060,5 +1068,5 @@ EXPLAIN (COSTS OFF) SELECT * FROM calamity.survivor; /* OK */ DROP TABLE calamity.survivor CASCADE; NOTICE: drop cascades to 3 other objects -DROP SCHEMA calamity CASCADE; +DROP SCHEMA calamity; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_calamity_1.out b/expected/pathman_calamity_1.out index 60313bfd..6ca2e7dd 100644 --- a/expected/pathman_calamity_1.out +++ b/expected/pathman_calamity_1.out @@ -320,7 +320,7 @@ SELECT validate_relname(NULL); ERROR: relation should not be NULL /* check function validate_expression() */ SELECT validate_expression(1::regclass, NULL); /* not ok */ -ERROR: relation "1" does not exist +ERROR: identifier "1" must be normal Oid SELECT validate_expression(NULL::regclass, NULL); /* not ok */ ERROR: 'relid' should not be NULL SELECT validate_expression('calamity.part_test', NULL); /* not ok */ @@ -426,19 +426,19 @@ SELECT build_sequence_name(NULL) IS NULL; /* check function partition_table_concurrently() */ SELECT partition_table_concurrently(1::REGCLASS); /* not ok */ -ERROR: relation "1" has no partitions +ERROR: identifier "1" must be normal Oid SELECT partition_table_concurrently('pg_class', 0); /* not ok */ ERROR: 'batch_size' should not be less than 1 or greater than 10000 SELECT partition_table_concurrently('pg_class', 1, 1E-5); /* not ok */ ERROR: 'sleep_time' should not be less than 0.5 SELECT partition_table_concurrently('pg_class'); /* not ok */ -ERROR: relation "pg_class" has no partitions +ERROR: identifier "1259" must be normal Oid /* check function stop_concurrent_part_task() */ SELECT stop_concurrent_part_task(1::REGCLASS); /* not ok */ ERROR: cannot find worker for relation "1" /* check function drop_range_partition_expand_next() */ SELECT drop_range_partition_expand_next('pg_class'); /* not ok */ -ERROR: relation "pg_class" is not a partition +ERROR: identifier "1259" must be normal Oid SELECT drop_range_partition_expand_next(NULL) IS NULL; ?column? ---------- @@ -560,7 +560,7 @@ DROP FUNCTION calamity.dummy_cb(arg jsonb); SELECT add_to_pathman_config(NULL, 'val'); /* no table */ ERROR: 'parent_relid' should not be NULL SELECT add_to_pathman_config(0::REGCLASS, 'val'); /* no table (oid) */ -ERROR: relation "0" does not exist +ERROR: identifier "0" must be normal Oid SELECT add_to_pathman_config('calamity.part_test', NULL); /* no expr */ ERROR: 'expression' should not be NULL SELECT add_to_pathman_config('calamity.part_test', 'V_A_L'); /* wrong expr */ @@ -779,8 +779,16 @@ SELECT merge_range_partitions('calamity.merge_test_a_1', ERROR: cannot merge partitions DROP TABLE calamity.merge_test_a,calamity.merge_test_b CASCADE; NOTICE: drop cascades to 6 other objects -DROP SCHEMA calamity CASCADE; -NOTICE: drop cascades to 15 other objects +DROP DOMAIN calamity.test_domain; +DROP TABLE calamity.part_test CASCADE; +NOTICE: drop cascades to table calamity.wrong_partition +DROP TABLE calamity.part_ok CASCADE; +NOTICE: drop cascades to 4 other objects +DROP TABLE calamity.hash_two_times CASCADE; +NOTICE: drop cascades to 2 other objects +DROP TABLE calamity.to_be_disabled CASCADE; +NOTICE: drop cascades to 3 other objects +DROP SCHEMA calamity; DROP EXTENSION pg_pathman; /* * ------------------------------- @@ -816,25 +824,25 @@ SELECT create_range_partitions('calamity.test_pathman_cache_stats', 'val', 1, 10 10 (1 row) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 0 partition parents cache | 0 - partition status cache | 2 -(4 rows) +(3 rows) DROP TABLE calamity.test_pathman_cache_stats CASCADE; NOTICE: drop cascades to 11 other objects -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 0 partition parents cache | 0 - partition status cache | 2 -(4 rows) +(3 rows) /* Change this setting for code coverage */ SET pg_pathman.enable_bounds_cache = false; @@ -862,25 +870,25 @@ EXPLAIN (COSTS OFF) SELECT * FROM calamity.test_pathman_cache_stats; -> Seq Scan on test_pathman_cache_stats_10 (11 rows) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 0 partition parents cache | 10 - partition status cache | 3 -(4 rows) +(3 rows) DROP TABLE calamity.test_pathman_cache_stats CASCADE; NOTICE: drop cascades to 11 other objects -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 0 partition parents cache | 0 - partition status cache | 2 -(4 rows) +(3 rows) /* Restore this GUC */ SET pg_pathman.enable_bounds_cache = true; @@ -908,25 +916,25 @@ EXPLAIN (COSTS OFF) SELECT * FROM calamity.test_pathman_cache_stats; -> Seq Scan on test_pathman_cache_stats_10 (11 rows) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 10 partition parents cache | 10 - partition status cache | 3 -(4 rows) +(3 rows) DROP TABLE calamity.test_pathman_cache_stats CASCADE; NOTICE: drop cascades to 11 other objects -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 0 partition parents cache | 0 - partition status cache | 2 -(4 rows) +(3 rows) /* check that parents cache has been flushed after partition was dropped */ CREATE TABLE calamity.test_pathman_cache_stats(val NUMERIC NOT NULL); @@ -952,14 +960,14 @@ EXPLAIN (COSTS OFF) SELECT * FROM calamity.test_pathman_cache_stats; -> Seq Scan on test_pathman_cache_stats_10 (11 rows) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 10 partition parents cache | 10 - partition status cache | 3 -(4 rows) +(3 rows) SELECT drop_range_partition('calamity.test_pathman_cache_stats_1'); drop_range_partition @@ -967,27 +975,27 @@ SELECT drop_range_partition('calamity.test_pathman_cache_stats_1'); calamity.test_pathman_cache_stats_1 (1 row) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 9 partition parents cache | 9 - partition status cache | 2 -(4 rows) +(3 rows) DROP TABLE calamity.test_pathman_cache_stats CASCADE; NOTICE: drop cascades to 10 other objects -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 0 partition parents cache | 0 - partition status cache | 2 -(4 rows) +(3 rows) -DROP SCHEMA calamity CASCADE; +DROP SCHEMA calamity; DROP EXTENSION pg_pathman; /* * ------------------------------------------ @@ -1060,5 +1068,5 @@ EXPLAIN (COSTS OFF) SELECT * FROM calamity.survivor; /* OK */ DROP TABLE calamity.survivor CASCADE; NOTICE: drop cascades to 3 other objects -DROP SCHEMA calamity CASCADE; +DROP SCHEMA calamity; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_calamity_2.out b/expected/pathman_calamity_2.out index e621831b..fa3295f6 100644 --- a/expected/pathman_calamity_2.out +++ b/expected/pathman_calamity_2.out @@ -320,7 +320,7 @@ SELECT validate_relname(NULL); ERROR: relation should not be NULL /* check function validate_expression() */ SELECT validate_expression(1::regclass, NULL); /* not ok */ -ERROR: relation "1" does not exist +ERROR: identifier "1" must be normal Oid SELECT validate_expression(NULL::regclass, NULL); /* not ok */ ERROR: 'relid' should not be NULL SELECT validate_expression('calamity.part_test', NULL); /* not ok */ @@ -426,19 +426,19 @@ SELECT build_sequence_name(NULL) IS NULL; /* check function partition_table_concurrently() */ SELECT partition_table_concurrently(1::REGCLASS); /* not ok */ -ERROR: relation "1" has no partitions +ERROR: identifier "1" must be normal Oid SELECT partition_table_concurrently('pg_class', 0); /* not ok */ ERROR: 'batch_size' should not be less than 1 or greater than 10000 SELECT partition_table_concurrently('pg_class', 1, 1E-5); /* not ok */ ERROR: 'sleep_time' should not be less than 0.5 SELECT partition_table_concurrently('pg_class'); /* not ok */ -ERROR: relation "pg_class" has no partitions +ERROR: identifier "1259" must be normal Oid /* check function stop_concurrent_part_task() */ SELECT stop_concurrent_part_task(1::REGCLASS); /* not ok */ ERROR: cannot find worker for relation "1" /* check function drop_range_partition_expand_next() */ SELECT drop_range_partition_expand_next('pg_class'); /* not ok */ -ERROR: relation "pg_class" is not a partition +ERROR: identifier "1259" must be normal Oid SELECT drop_range_partition_expand_next(NULL) IS NULL; ?column? ---------- @@ -560,7 +560,7 @@ DROP FUNCTION calamity.dummy_cb(arg jsonb); SELECT add_to_pathman_config(NULL, 'val'); /* no table */ ERROR: 'parent_relid' should not be NULL SELECT add_to_pathman_config(0::REGCLASS, 'val'); /* no table (oid) */ -ERROR: relation "0" does not exist +ERROR: identifier "0" must be normal Oid SELECT add_to_pathman_config('calamity.part_test', NULL); /* no expr */ ERROR: 'expression' should not be NULL SELECT add_to_pathman_config('calamity.part_test', 'V_A_L'); /* wrong expr */ @@ -603,25 +603,25 @@ NOTICE: merging column "val" with inherited definition SELECT add_to_pathman_config('calamity.part_test', 'val'); ERROR: constraint "pathman_wrong_partition_check" of partition "wrong_partition" does not exist EXPLAIN (COSTS OFF) SELECT * FROM calamity.part_ok; /* check that pathman is enabled */ - QUERY PLAN ------------------------------ + QUERY PLAN +--------------------------------------- Append - -> Seq Scan on part_ok_0 - -> Seq Scan on part_ok_1 - -> Seq Scan on part_ok_2 - -> Seq Scan on part_ok_3 + -> Seq Scan on part_ok_0 part_ok_1 + -> Seq Scan on part_ok_1 part_ok_2 + -> Seq Scan on part_ok_2 part_ok_3 + -> Seq Scan on part_ok_3 part_ok_4 (5 rows) SELECT add_to_pathman_config('calamity.part_test', 'val', '10'); ERROR: constraint "pathman_wrong_partition_check" of partition "wrong_partition" does not exist EXPLAIN (COSTS OFF) SELECT * FROM calamity.part_ok; /* check that pathman is enabled */ - QUERY PLAN ------------------------------ + QUERY PLAN +--------------------------------------- Append - -> Seq Scan on part_ok_0 - -> Seq Scan on part_ok_1 - -> Seq Scan on part_ok_2 - -> Seq Scan on part_ok_3 + -> Seq Scan on part_ok_0 part_ok_1 + -> Seq Scan on part_ok_1 part_ok_2 + -> Seq Scan on part_ok_2 part_ok_3 + -> Seq Scan on part_ok_3 part_ok_4 (5 rows) ALTER TABLE calamity.wrong_partition @@ -630,13 +630,13 @@ CHECK (val = 1 OR val = 2); /* wrong constraint */ SELECT add_to_pathman_config('calamity.part_test', 'val', '10'); ERROR: wrong constraint format for RANGE partition "wrong_partition" EXPLAIN (COSTS OFF) SELECT * FROM calamity.part_ok; /* check that pathman is enabled */ - QUERY PLAN ------------------------------ + QUERY PLAN +--------------------------------------- Append - -> Seq Scan on part_ok_0 - -> Seq Scan on part_ok_1 - -> Seq Scan on part_ok_2 - -> Seq Scan on part_ok_3 + -> Seq Scan on part_ok_0 part_ok_1 + -> Seq Scan on part_ok_1 part_ok_2 + -> Seq Scan on part_ok_2 part_ok_3 + -> Seq Scan on part_ok_3 part_ok_4 (5 rows) ALTER TABLE calamity.wrong_partition DROP CONSTRAINT pathman_wrong_partition_check; @@ -646,13 +646,13 @@ CHECK (val >= 10 AND val = 2); /* wrong constraint */ SELECT add_to_pathman_config('calamity.part_test', 'val', '10'); ERROR: wrong constraint format for RANGE partition "wrong_partition" EXPLAIN (COSTS OFF) SELECT * FROM calamity.part_ok; /* check that pathman is enabled */ - QUERY PLAN ------------------------------ + QUERY PLAN +--------------------------------------- Append - -> Seq Scan on part_ok_0 - -> Seq Scan on part_ok_1 - -> Seq Scan on part_ok_2 - -> Seq Scan on part_ok_3 + -> Seq Scan on part_ok_0 part_ok_1 + -> Seq Scan on part_ok_1 part_ok_2 + -> Seq Scan on part_ok_2 part_ok_3 + -> Seq Scan on part_ok_3 part_ok_4 (5 rows) ALTER TABLE calamity.wrong_partition DROP CONSTRAINT pathman_wrong_partition_check; @@ -779,8 +779,16 @@ SELECT merge_range_partitions('calamity.merge_test_a_1', ERROR: cannot merge partitions DROP TABLE calamity.merge_test_a,calamity.merge_test_b CASCADE; NOTICE: drop cascades to 6 other objects -DROP SCHEMA calamity CASCADE; -NOTICE: drop cascades to 15 other objects +DROP DOMAIN calamity.test_domain; +DROP TABLE calamity.part_test CASCADE; +NOTICE: drop cascades to table calamity.wrong_partition +DROP TABLE calamity.part_ok CASCADE; +NOTICE: drop cascades to 4 other objects +DROP TABLE calamity.hash_two_times CASCADE; +NOTICE: drop cascades to 2 other objects +DROP TABLE calamity.to_be_disabled CASCADE; +NOTICE: drop cascades to 3 other objects +DROP SCHEMA calamity; DROP EXTENSION pg_pathman; /* * ------------------------------- @@ -816,25 +824,25 @@ SELECT create_range_partitions('calamity.test_pathman_cache_stats', 'val', 1, 10 10 (1 row) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 0 partition parents cache | 0 - partition status cache | 2 -(4 rows) +(3 rows) DROP TABLE calamity.test_pathman_cache_stats CASCADE; NOTICE: drop cascades to 11 other objects -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 0 partition parents cache | 0 - partition status cache | 2 -(4 rows) +(3 rows) /* Change this setting for code coverage */ SET pg_pathman.enable_bounds_cache = false; @@ -862,25 +870,25 @@ EXPLAIN (COSTS OFF) SELECT * FROM calamity.test_pathman_cache_stats; -> Seq Scan on test_pathman_cache_stats_10 (11 rows) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 0 partition parents cache | 10 - partition status cache | 3 -(4 rows) +(3 rows) DROP TABLE calamity.test_pathman_cache_stats CASCADE; NOTICE: drop cascades to 11 other objects -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 0 partition parents cache | 0 - partition status cache | 2 -(4 rows) +(3 rows) /* Restore this GUC */ SET pg_pathman.enable_bounds_cache = true; @@ -908,25 +916,25 @@ EXPLAIN (COSTS OFF) SELECT * FROM calamity.test_pathman_cache_stats; -> Seq Scan on test_pathman_cache_stats_10 (11 rows) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 10 partition parents cache | 10 - partition status cache | 3 -(4 rows) +(3 rows) DROP TABLE calamity.test_pathman_cache_stats CASCADE; NOTICE: drop cascades to 11 other objects -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 0 partition parents cache | 0 - partition status cache | 2 -(4 rows) +(3 rows) /* check that parents cache has been flushed after partition was dropped */ CREATE TABLE calamity.test_pathman_cache_stats(val NUMERIC NOT NULL); @@ -952,14 +960,14 @@ EXPLAIN (COSTS OFF) SELECT * FROM calamity.test_pathman_cache_stats; -> Seq Scan on test_pathman_cache_stats_10 (11 rows) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 10 partition parents cache | 10 - partition status cache | 3 -(4 rows) +(3 rows) SELECT drop_range_partition('calamity.test_pathman_cache_stats_1'); drop_range_partition @@ -967,27 +975,27 @@ SELECT drop_range_partition('calamity.test_pathman_cache_stats_1'); calamity.test_pathman_cache_stats_1 (1 row) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 9 partition parents cache | 9 - partition status cache | 2 -(4 rows) +(3 rows) DROP TABLE calamity.test_pathman_cache_stats CASCADE; NOTICE: drop cascades to 10 other objects -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 0 partition parents cache | 0 - partition status cache | 2 -(4 rows) +(3 rows) -DROP SCHEMA calamity CASCADE; +DROP SCHEMA calamity; DROP EXTENSION pg_pathman; /* * ------------------------------------------ @@ -1060,5 +1068,5 @@ EXPLAIN (COSTS OFF) SELECT * FROM calamity.survivor; /* OK */ DROP TABLE calamity.survivor CASCADE; NOTICE: drop cascades to 3 other objects -DROP SCHEMA calamity CASCADE; +DROP SCHEMA calamity; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_calamity_3.out b/expected/pathman_calamity_3.out new file mode 100644 index 00000000..a8879ef7 --- /dev/null +++ b/expected/pathman_calamity_3.out @@ -0,0 +1,1076 @@ +/* + * pathman_calamity.out and pathman_calamity_1.out differ only in that since + * 12 we get + * ERROR: invalid input syntax for type integer: "abc" + * instead of + * ERROR: invalid input syntax for integer: "15.6" + * + * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, + * now it includes aliases for inherited tables. + */ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE EXTENSION pg_pathman; +CREATE SCHEMA calamity; +/* call for coverage test */ +set client_min_messages = ERROR; +SELECT debug_capture(); + debug_capture +--------------- + +(1 row) + +SELECT pathman_version(); + pathman_version +----------------- + 1.5.12 +(1 row) + +set client_min_messages = NOTICE; +/* create table to be partitioned */ +CREATE TABLE calamity.part_test(val serial); +/* test pg_pathman's cache */ +INSERT INTO calamity.part_test SELECT generate_series(1, 30); +SELECT create_range_partitions('calamity.part_test', 'val', 1, 10); + create_range_partitions +------------------------- + 3 +(1 row) + +SELECT drop_partitions('calamity.part_test'); +NOTICE: 10 rows copied from calamity.part_test_1 +NOTICE: 10 rows copied from calamity.part_test_2 +NOTICE: 10 rows copied from calamity.part_test_3 + drop_partitions +----------------- + 3 +(1 row) + +SELECT create_range_partitions('calamity.part_test', 'val', 1, 10); + create_range_partitions +------------------------- + 3 +(1 row) + +SELECT drop_partitions('calamity.part_test'); +NOTICE: 10 rows copied from calamity.part_test_1 +NOTICE: 10 rows copied from calamity.part_test_2 +NOTICE: 10 rows copied from calamity.part_test_3 + drop_partitions +----------------- + 3 +(1 row) + +SELECT create_range_partitions('calamity.part_test', 'val', 1, 10); + create_range_partitions +------------------------- + 3 +(1 row) + +SELECT append_range_partition('calamity.part_test'); + append_range_partition +------------------------ + calamity.part_test_4 +(1 row) + +SELECT drop_partitions('calamity.part_test'); +NOTICE: 10 rows copied from calamity.part_test_1 +NOTICE: 10 rows copied from calamity.part_test_2 +NOTICE: 10 rows copied from calamity.part_test_3 +NOTICE: 0 rows copied from calamity.part_test_4 + drop_partitions +----------------- + 4 +(1 row) + +SELECT create_range_partitions('calamity.part_test', 'val', 1, 10); + create_range_partitions +------------------------- + 3 +(1 row) + +SELECT append_range_partition('calamity.part_test'); + append_range_partition +------------------------ + calamity.part_test_4 +(1 row) + +SELECT drop_partitions('calamity.part_test'); +NOTICE: 10 rows copied from calamity.part_test_1 +NOTICE: 10 rows copied from calamity.part_test_2 +NOTICE: 10 rows copied from calamity.part_test_3 +NOTICE: 0 rows copied from calamity.part_test_4 + drop_partitions +----------------- + 4 +(1 row) + +SELECT count(*) FROM calamity.part_test; + count +------- + 30 +(1 row) + +DELETE FROM calamity.part_test; +/* test function create_single_range_partition() */ +SELECT create_single_range_partition(NULL, NULL::INT4, NULL); /* not ok */ +ERROR: 'parent_relid' should not be NULL +SELECT create_single_range_partition('pg_class', NULL::INT4, NULL); /* not ok */ +ERROR: table "pg_class" is not partitioned by RANGE +SELECT add_to_pathman_config('calamity.part_test', 'val'); + add_to_pathman_config +----------------------- + t +(1 row) + +SELECT create_single_range_partition('calamity.part_test', NULL::INT4, NULL); /* not ok */ +ERROR: table "part_test" is not partitioned by RANGE +DELETE FROM pathman_config WHERE partrel = 'calamity.part_test'::REGCLASS; +/* test function create_range_partitions_internal() */ +SELECT create_range_partitions_internal(NULL, '{}'::INT[], NULL, NULL); /* not ok */ +ERROR: 'parent_relid' should not be NULL +SELECT create_range_partitions_internal('calamity.part_test', + NULL::INT[], NULL, NULL); /* not ok */ +ERROR: 'bounds' should not be NULL +SELECT create_range_partitions_internal('calamity.part_test', '{1}'::INT[], + '{part_1}'::TEXT[], NULL); /* not ok */ +ERROR: wrong length of 'partition_names' array +SELECT create_range_partitions_internal('calamity.part_test', '{1}'::INT[], + NULL, '{tblspc_1}'::TEXT[]); /* not ok */ +ERROR: wrong length of 'tablespaces' array +SELECT create_range_partitions_internal('calamity.part_test', + '{1, NULL}'::INT[], NULL, NULL); /* not ok */ +ERROR: only first bound can be NULL +SELECT create_range_partitions_internal('calamity.part_test', + '{2, 1}'::INT[], NULL, NULL); /* not ok */ +ERROR: 'bounds' array must be ascending +/* test function create_hash_partitions() */ +SELECT create_hash_partitions('calamity.part_test', 'val', 2, + partition_names := ARRAY[]::TEXT[]); /* not ok */ +ERROR: array should not be empty +SELECT create_hash_partitions('calamity.part_test', 'val', 2, + partition_names := ARRAY[ 'p1', NULL ]::TEXT[]); /* not ok */ +ERROR: array should not contain NULLs +SELECT create_hash_partitions('calamity.part_test', 'val', 2, + partition_names := ARRAY[ ['p1'], ['p2'] ]::TEXT[]); /* not ok */ +ERROR: array should contain only 1 dimension +SELECT create_hash_partitions('calamity.part_test', 'val', 2, + partition_names := ARRAY['calamity.p1']::TEXT[]); /* not ok */ +ERROR: size of 'partition_names' must be equal to 'partitions_count' +SELECT create_hash_partitions('calamity.part_test', 'val', 2, + tablespaces := ARRAY['abcd']::TEXT[]); /* not ok */ +ERROR: size of 'tablespaces' must be equal to 'partitions_count' +/* test case when naming sequence does not exist */ +CREATE TABLE calamity.no_naming_seq(val INT4 NOT NULL); +SELECT add_to_pathman_config('calamity.no_naming_seq', 'val', '100'); + add_to_pathman_config +----------------------- + t +(1 row) + +select add_range_partition(' calamity.no_naming_seq', 10, 20); +ERROR: auto naming sequence "no_naming_seq_seq" does not exist +DROP TABLE calamity.no_naming_seq CASCADE; +/* test (-inf, +inf) partition creation */ +CREATE TABLE calamity.double_inf(val INT4 NOT NULL); +SELECT add_to_pathman_config('calamity.double_inf', 'val', '10'); + add_to_pathman_config +----------------------- + t +(1 row) + +select add_range_partition('calamity.double_inf', NULL::INT4, NULL::INT4, + partition_name := 'double_inf_part'); +ERROR: cannot create partition with range (-inf, +inf) +DROP TABLE calamity.double_inf CASCADE; +/* test stub 'enable_parent' value for PATHMAN_CONFIG_PARAMS */ +INSERT INTO calamity.part_test SELECT generate_series(1, 30); +SELECT create_range_partitions('calamity.part_test', 'val', 1, 10); + create_range_partitions +------------------------- + 3 +(1 row) + +DELETE FROM pathman_config_params WHERE partrel = 'calamity.part_test'::regclass; +SELECT append_range_partition('calamity.part_test'); + append_range_partition +------------------------ + calamity.part_test_4 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM calamity.part_test; + QUERY PLAN +------------------------------- + Append + -> Seq Scan on part_test_1 + -> Seq Scan on part_test_2 + -> Seq Scan on part_test_3 + -> Seq Scan on part_test_4 +(5 rows) + +SELECT drop_partitions('calamity.part_test', true); + drop_partitions +----------------- + 4 +(1 row) + +DELETE FROM calamity.part_test; +/* check function validate_interval_value() */ +SELECT set_interval('pg_catalog.pg_class', 100); /* not ok */ +ERROR: table "pg_class" is not partitioned by RANGE +INSERT INTO calamity.part_test SELECT generate_series(1, 30); +SELECT create_range_partitions('calamity.part_test', 'val', 1, 10); + create_range_partitions +------------------------- + 3 +(1 row) + +SELECT set_interval('calamity.part_test', 100); /* ok */ + set_interval +-------------- + +(1 row) + +SELECT set_interval('calamity.part_test', 15.6); /* not ok */ +ERROR: invalid input syntax for type integer: "15.6" +SELECT set_interval('calamity.part_test', 'abc'::text); /* not ok */ +ERROR: invalid input syntax for type integer: "abc" +SELECT drop_partitions('calamity.part_test', true); + drop_partitions +----------------- + 3 +(1 row) + +DELETE FROM calamity.part_test; +/* check function build_hash_condition() */ +SELECT build_hash_condition('int4', 'val', 10, 1); + build_hash_condition +------------------------------------------------- + public.get_hash_part_idx(hashint4(val), 10) = 1 +(1 row) + +SELECT build_hash_condition('text', 'val', 10, 1); + build_hash_condition +------------------------------------------------- + public.get_hash_part_idx(hashtext(val), 10) = 1 +(1 row) + +SELECT build_hash_condition('int4', 'val', 1, 1); +ERROR: 'partition_index' must be lower than 'partitions_count' +SELECT build_hash_condition('int4', 'val', 10, 20); +ERROR: 'partition_index' must be lower than 'partitions_count' +SELECT build_hash_condition('text', 'val', 10, NULL) IS NULL; + ?column? +---------- + t +(1 row) + +SELECT build_hash_condition('calamity.part_test', 'val', 10, 1); + build_hash_condition +---------------------------------------------------- + public.get_hash_part_idx(hash_record(val), 10) = 1 +(1 row) + +/* check function build_range_condition() */ +SELECT build_range_condition(NULL, 'val', 10, 20); /* not ok */ +ERROR: 'partition_relid' should not be NULL +SELECT build_range_condition('calamity.part_test', NULL, 10, 20); /* not ok */ +ERROR: 'expression' should not be NULL +SELECT build_range_condition('calamity.part_test', 'val', 10, 20); /* OK */ + build_range_condition +------------------------------ + ((val >= 10) AND (val < 20)) +(1 row) + +SELECT build_range_condition('calamity.part_test', 'val', 10, NULL); /* OK */ + build_range_condition +----------------------- + ((val >= 10)) +(1 row) + +SELECT build_range_condition('calamity.part_test', 'val', NULL, 10); /* OK */ + build_range_condition +----------------------- + ((val < 10)) +(1 row) + +/* check function validate_interval_value() */ +SELECT validate_interval_value(1::REGCLASS, 'expr', 2, '1 mon'); /* not ok */ +ERROR: relation "1" does not exist +SELECT validate_interval_value(NULL, 'expr', 2, '1 mon'); /* not ok */ +ERROR: 'partrel' should not be NULL +SELECT validate_interval_value('pg_class', NULL, 2, '1 mon'); /* not ok */ +ERROR: 'expression' should not be NULL +SELECT validate_interval_value('pg_class', 'relname', NULL, '1 mon'); /* not ok */ +ERROR: 'parttype' should not be NULL +SELECT validate_interval_value('pg_class', 'relname', 1, 'HASH'); /* not ok */ +ERROR: interval should be NULL for HASH partitioned table +SELECT validate_interval_value('pg_class', 'expr', 2, '1 mon'); /* not ok */ +ERROR: failed to analyze partitioning expression "expr" +SELECT validate_interval_value('pg_class', 'expr', 2, NULL); /* not ok */ +ERROR: failed to analyze partitioning expression "expr" +SELECT validate_interval_value('pg_class', 'EXPR', 1, 'HASH'); /* not ok */ +ERROR: failed to analyze partitioning expression "EXPR" +/* check function validate_relname() */ +SELECT validate_relname('calamity.part_test'); + validate_relname +------------------ + +(1 row) + +SELECT validate_relname(1::REGCLASS); +ERROR: relation "1" does not exist +SELECT validate_relname(NULL); +ERROR: relation should not be NULL +/* check function validate_expression() */ +SELECT validate_expression(1::regclass, NULL); /* not ok */ +ERROR: identifier "1" must be normal Oid +SELECT validate_expression(NULL::regclass, NULL); /* not ok */ +ERROR: 'relid' should not be NULL +SELECT validate_expression('calamity.part_test', NULL); /* not ok */ +ERROR: 'expression' should not be NULL +SELECT validate_expression('calamity.part_test', 'valval'); /* not ok */ +ERROR: failed to analyze partitioning expression "valval" +SELECT validate_expression('calamity.part_test', 'random()'); /* not ok */ +ERROR: failed to analyze partitioning expression "random()" +SELECT validate_expression('calamity.part_test', 'val'); /* OK */ + validate_expression +--------------------- + +(1 row) + +SELECT validate_expression('calamity.part_test', 'VaL'); /* OK */ + validate_expression +--------------------- + +(1 row) + +/* check function get_number_of_partitions() */ +SELECT get_number_of_partitions('calamity.part_test'); + get_number_of_partitions +-------------------------- + 0 +(1 row) + +SELECT get_number_of_partitions(NULL) IS NULL; + ?column? +---------- + t +(1 row) + +/* check function get_parent_of_partition() */ +SELECT get_parent_of_partition('calamity.part_test'); +ERROR: "part_test" is not a partition +SELECT get_parent_of_partition(NULL) IS NULL; + ?column? +---------- + t +(1 row) + +/* check function get_base_type() */ +CREATE DOMAIN calamity.test_domain AS INT4; +SELECT get_base_type('int4'::regtype); + get_base_type +--------------- + integer +(1 row) + +SELECT get_base_type('calamity.test_domain'::regtype); + get_base_type +--------------- + integer +(1 row) + +SELECT get_base_type(NULL) IS NULL; + ?column? +---------- + t +(1 row) + +/* check function get_partition_key_type() */ +SELECT get_partition_key_type('calamity.part_test'); +ERROR: relation "part_test" has no partitions +SELECT get_partition_key_type(0::regclass); +ERROR: relation "0" has no partitions +SELECT get_partition_key_type(NULL) IS NULL; + ?column? +---------- + t +(1 row) + +/* check function build_check_constraint_name() */ +SELECT build_check_constraint_name('calamity.part_test'); /* OK */ + build_check_constraint_name +----------------------------- + pathman_part_test_check +(1 row) + +SELECT build_check_constraint_name(0::REGCLASS); /* not ok */ +ERROR: relation "0" does not exist +SELECT build_check_constraint_name(NULL) IS NULL; + ?column? +---------- + t +(1 row) + +/* check function build_sequence_name() */ +SELECT build_sequence_name('calamity.part_test'); /* OK */ + build_sequence_name +------------------------ + calamity.part_test_seq +(1 row) + +SELECT build_sequence_name(1::REGCLASS); /* not ok */ +ERROR: relation "1" does not exist +SELECT build_sequence_name(NULL) IS NULL; + ?column? +---------- + t +(1 row) + +/* check function partition_table_concurrently() */ +SELECT partition_table_concurrently(1::REGCLASS); /* not ok */ +ERROR: identifier "1" must be normal Oid +SELECT partition_table_concurrently('pg_class', 0); /* not ok */ +ERROR: 'batch_size' should not be less than 1 or greater than 10000 +SELECT partition_table_concurrently('pg_class', 1, 1E-5); /* not ok */ +ERROR: 'sleep_time' should not be less than 0.5 +SELECT partition_table_concurrently('pg_class'); /* not ok */ +ERROR: identifier "1259" must be normal Oid +/* check function stop_concurrent_part_task() */ +SELECT stop_concurrent_part_task(1::REGCLASS); /* not ok */ +ERROR: cannot find worker for relation "1" +/* check function drop_range_partition_expand_next() */ +SELECT drop_range_partition_expand_next('pg_class'); /* not ok */ +ERROR: identifier "1259" must be normal Oid +SELECT drop_range_partition_expand_next(NULL) IS NULL; + ?column? +---------- + t +(1 row) + +/* check function generate_range_bounds() */ +SELECT generate_range_bounds(NULL, 100, 10) IS NULL; + ?column? +---------- + t +(1 row) + +SELECT generate_range_bounds(0, NULL::INT4, 10) IS NULL; + ?column? +---------- + t +(1 row) + +SELECT generate_range_bounds(0, 100, NULL) IS NULL; + ?column? +---------- + t +(1 row) + +SELECT generate_range_bounds(0, 100, 0); /* not ok */ +ERROR: 'p_count' must be greater than zero +SELECT generate_range_bounds('a'::TEXT, 'test'::TEXT, 10); /* not ok */ +ERROR: cannot find operator +(text, text) +SELECT generate_range_bounds('a'::TEXT, '1 mon'::INTERVAL, 10); /* not ok */ +ERROR: cannot find operator +(text, interval) +SELECT generate_range_bounds(0::NUMERIC, 1::NUMERIC, 10); /* OK */ + generate_range_bounds +-------------------------- + {0,1,2,3,4,5,6,7,8,9,10} +(1 row) + +SELECT generate_range_bounds('1-jan-2017'::DATE, + '1 day'::INTERVAL, + 4); /* OK */ + generate_range_bounds +---------------------------------------------------------- + {01-01-2017,01-02-2017,01-03-2017,01-04-2017,01-05-2017} +(1 row) + +SELECT check_range_available(NULL, NULL::INT4, NULL); /* not ok */ +ERROR: 'parent_relid' should not be NULL +SELECT check_range_available('pg_class', 1, 10); /* OK (not partitioned) */ +WARNING: table "pg_class" is not partitioned + check_range_available +----------------------- + +(1 row) + +/* check invoke_on_partition_created_callback() */ +CREATE FUNCTION calamity.dummy_cb(arg jsonb) RETURNS void AS $$ + begin + raise warning 'arg: %', arg::text; + end +$$ LANGUAGE plpgsql; +/* Invalid args */ +SELECT invoke_on_partition_created_callback(NULL, 'calamity.part_test', 1); +ERROR: 'parent_relid' should not be NULL +SELECT invoke_on_partition_created_callback('calamity.part_test', NULL, 1); +ERROR: 'partition_relid' should not be NULL +SELECT invoke_on_partition_created_callback('calamity.part_test', 'calamity.part_test', 0); + invoke_on_partition_created_callback +-------------------------------------- + +(1 row) + +SELECT invoke_on_partition_created_callback('calamity.part_test', 'calamity.part_test', 1); +ERROR: callback function 1 does not exist +SELECT invoke_on_partition_created_callback('calamity.part_test', 'calamity.part_test', NULL); + invoke_on_partition_created_callback +-------------------------------------- + +(1 row) + +/* HASH */ +SELECT invoke_on_partition_created_callback(0::regclass, 1::regclass, 'calamity.dummy_cb(jsonb)'::regprocedure); +WARNING: arg: {"parent": null, "parttype": "1", "partition": null, "parent_schema": null, "partition_schema": null} + invoke_on_partition_created_callback +-------------------------------------- + +(1 row) + +/* RANGE */ +SELECT invoke_on_partition_created_callback('calamity.part_test'::regclass, 'pg_class'::regclass, 'calamity.dummy_cb(jsonb)'::regprocedure, NULL::int, NULL); +WARNING: arg: {"parent": "part_test", "parttype": "2", "partition": "pg_class", "range_max": null, "range_min": null, "parent_schema": "calamity", "partition_schema": "pg_catalog"} + invoke_on_partition_created_callback +-------------------------------------- + +(1 row) + +SELECT invoke_on_partition_created_callback(0::regclass, 1::regclass, 'calamity.dummy_cb(jsonb)'::regprocedure, NULL::int, NULL); +WARNING: arg: {"parent": null, "parttype": "2", "partition": null, "range_max": null, "range_min": null, "parent_schema": null, "partition_schema": null} + invoke_on_partition_created_callback +-------------------------------------- + +(1 row) + +SELECT invoke_on_partition_created_callback(0::regclass, 1::regclass, 'calamity.dummy_cb(jsonb)'::regprocedure, 1, NULL); +WARNING: arg: {"parent": null, "parttype": "2", "partition": null, "range_max": null, "range_min": "1", "parent_schema": null, "partition_schema": null} + invoke_on_partition_created_callback +-------------------------------------- + +(1 row) + +SELECT invoke_on_partition_created_callback(0::regclass, 1::regclass, 'calamity.dummy_cb(jsonb)'::regprocedure, NULL, 1); +WARNING: arg: {"parent": null, "parttype": "2", "partition": null, "range_max": "1", "range_min": null, "parent_schema": null, "partition_schema": null} + invoke_on_partition_created_callback +-------------------------------------- + +(1 row) + +DROP FUNCTION calamity.dummy_cb(arg jsonb); +/* check function add_to_pathman_config() -- PHASE #1 */ +SELECT add_to_pathman_config(NULL, 'val'); /* no table */ +ERROR: 'parent_relid' should not be NULL +SELECT add_to_pathman_config(0::REGCLASS, 'val'); /* no table (oid) */ +ERROR: identifier "0" must be normal Oid +SELECT add_to_pathman_config('calamity.part_test', NULL); /* no expr */ +ERROR: 'expression' should not be NULL +SELECT add_to_pathman_config('calamity.part_test', 'V_A_L'); /* wrong expr */ +ERROR: failed to analyze partitioning expression "V_A_L" +SELECT add_to_pathman_config('calamity.part_test', 'val'); /* OK */ + add_to_pathman_config +----------------------- + t +(1 row) + +SELECT disable_pathman_for('calamity.part_test'); + disable_pathman_for +--------------------- + +(1 row) + +SELECT add_to_pathman_config('calamity.part_test', 'val', '10'); /* OK */ + add_to_pathman_config +----------------------- + t +(1 row) + +SELECT disable_pathman_for('calamity.part_test'); + disable_pathman_for +--------------------- + +(1 row) + +/* check function add_to_pathman_config() -- PHASE #2 */ +CREATE TABLE calamity.part_ok(val serial); +INSERT INTO calamity.part_ok SELECT generate_series(1, 2); +SELECT create_hash_partitions('calamity.part_ok', 'val', 4); + create_hash_partitions +------------------------ + 4 +(1 row) + +CREATE TABLE calamity.wrong_partition (LIKE calamity.part_test) INHERITS (calamity.part_test); /* wrong partition w\o constraints */ +NOTICE: merging column "val" with inherited definition +SELECT add_to_pathman_config('calamity.part_test', 'val'); +ERROR: constraint "pathman_wrong_partition_check" of partition "wrong_partition" does not exist +EXPLAIN (COSTS OFF) SELECT * FROM calamity.part_ok; /* check that pathman is enabled */ + QUERY PLAN +--------------------------------------- + Append + -> Seq Scan on part_ok_0 part_ok_1 + -> Seq Scan on part_ok_1 part_ok_2 + -> Seq Scan on part_ok_2 part_ok_3 + -> Seq Scan on part_ok_3 part_ok_4 +(5 rows) + +SELECT add_to_pathman_config('calamity.part_test', 'val', '10'); +ERROR: constraint "pathman_wrong_partition_check" of partition "wrong_partition" does not exist +EXPLAIN (COSTS OFF) SELECT * FROM calamity.part_ok; /* check that pathman is enabled */ + QUERY PLAN +--------------------------------------- + Append + -> Seq Scan on part_ok_0 part_ok_1 + -> Seq Scan on part_ok_1 part_ok_2 + -> Seq Scan on part_ok_2 part_ok_3 + -> Seq Scan on part_ok_3 part_ok_4 +(5 rows) + +ALTER TABLE calamity.wrong_partition +ADD CONSTRAINT pathman_wrong_partition_check +CHECK (val = 1 OR val = 2); /* wrong constraint */ +SELECT add_to_pathman_config('calamity.part_test', 'val', '10'); +ERROR: wrong constraint format for RANGE partition "wrong_partition" +EXPLAIN (COSTS OFF) SELECT * FROM calamity.part_ok; /* check that pathman is enabled */ + QUERY PLAN +--------------------------------------- + Append + -> Seq Scan on part_ok_0 part_ok_1 + -> Seq Scan on part_ok_1 part_ok_2 + -> Seq Scan on part_ok_2 part_ok_3 + -> Seq Scan on part_ok_3 part_ok_4 +(5 rows) + +ALTER TABLE calamity.wrong_partition DROP CONSTRAINT pathman_wrong_partition_check; +ALTER TABLE calamity.wrong_partition +ADD CONSTRAINT pathman_wrong_partition_check +CHECK (val >= 10 AND val = 2); /* wrong constraint */ +SELECT add_to_pathman_config('calamity.part_test', 'val', '10'); +ERROR: wrong constraint format for RANGE partition "wrong_partition" +EXPLAIN (COSTS OFF) SELECT * FROM calamity.part_ok; /* check that pathman is enabled */ + QUERY PLAN +--------------------------------------- + Append + -> Seq Scan on part_ok_0 part_ok_1 + -> Seq Scan on part_ok_1 part_ok_2 + -> Seq Scan on part_ok_2 part_ok_3 + -> Seq Scan on part_ok_3 part_ok_4 +(5 rows) + +ALTER TABLE calamity.wrong_partition DROP CONSTRAINT pathman_wrong_partition_check; +/* check GUC variable */ +SHOW pg_pathman.enable; + pg_pathman.enable +------------------- + on +(1 row) + +/* check function create_hash_partitions_internal() (called for the 2nd time) */ +CREATE TABLE calamity.hash_two_times(val serial); +SELECT create_hash_partitions_internal('calamity.hash_two_times', 'val', 2); +ERROR: table "hash_two_times" is not partitioned +SELECT create_hash_partitions('calamity.hash_two_times', 'val', 2); + create_hash_partitions +------------------------ + 2 +(1 row) + +SELECT create_hash_partitions_internal('calamity.hash_two_times', 'val', 2); +ERROR: cannot add new HASH partitions +/* check function disable_pathman_for() */ +CREATE TABLE calamity.to_be_disabled(val INT NOT NULL); +SELECT create_hash_partitions('calamity.to_be_disabled', 'val', 3); /* add row to main config */ + create_hash_partitions +------------------------ + 3 +(1 row) + +SELECT set_enable_parent('calamity.to_be_disabled', true); /* add row to params */ + set_enable_parent +------------------- + +(1 row) + +SELECT disable_pathman_for('calamity.to_be_disabled'); /* should delete both rows */ + disable_pathman_for +--------------------- + +(1 row) + +SELECT count(*) FROM pathman_config WHERE partrel = 'calamity.to_be_disabled'::REGCLASS; + count +------- + 0 +(1 row) + +SELECT count(*) FROM pathman_config_params WHERE partrel = 'calamity.to_be_disabled'::REGCLASS; + count +------- + 0 +(1 row) + +/* check function get_part_range_by_idx() */ +CREATE TABLE calamity.test_range_idx(val INT4 NOT NULL); +SELECT create_range_partitions('calamity.test_range_idx', 'val', 1, 10, 1); + create_range_partitions +------------------------- + 1 +(1 row) + +SELECT get_part_range(NULL, 1, NULL::INT4); /* not ok */ +ERROR: 'parent_relid' should not be NULL +SELECT get_part_range('calamity.test_range_idx', NULL, NULL::INT4); /* not ok */ +ERROR: 'partition_idx' should not be NULL +SELECT get_part_range('calamity.test_range_idx', 0, NULL::INT2); /* not ok */ +ERROR: pg_typeof(dummy) should be integer +SELECT get_part_range('calamity.test_range_idx', -2, NULL::INT4); /* not ok */ +ERROR: negative indices other than -1 (last partition) are not allowed +SELECT get_part_range('calamity.test_range_idx', 4, NULL::INT4); /* not ok */ +ERROR: partition #4 does not exist (total amount is 1) +SELECT get_part_range('calamity.test_range_idx', 0, NULL::INT4); /* OK */ + get_part_range +---------------- + {1,11} +(1 row) + +DROP TABLE calamity.test_range_idx CASCADE; +NOTICE: drop cascades to 2 other objects +/* check function get_part_range_by_oid() */ +CREATE TABLE calamity.test_range_oid(val INT4 NOT NULL); +SELECT create_range_partitions('calamity.test_range_oid', 'val', 1, 10, 1); + create_range_partitions +------------------------- + 1 +(1 row) + +SELECT get_part_range(NULL, NULL::INT4); /* not ok */ +ERROR: 'partition_relid' should not be NULL +SELECT get_part_range('pg_class', NULL::INT4); /* not ok */ +ERROR: relation "pg_class" is not a partition +SELECT get_part_range('calamity.test_range_oid_1', NULL::INT2); /* not ok */ +ERROR: pg_typeof(dummy) should be integer +SELECT get_part_range('calamity.test_range_oid_1', NULL::INT4); /* OK */ + get_part_range +---------------- + {1,11} +(1 row) + +DROP TABLE calamity.test_range_oid CASCADE; +NOTICE: drop cascades to 2 other objects +/* check function merge_range_partitions() */ +SELECT merge_range_partitions('pg_class'); /* not ok */ +ERROR: cannot merge partitions +SELECT merge_range_partitions('pg_class', 'pg_inherits'); /* not ok */ +ERROR: cannot merge partitions +CREATE TABLE calamity.merge_test_a(val INT4 NOT NULL); +CREATE TABLE calamity.merge_test_b(val INT4 NOT NULL); +SELECT create_range_partitions('calamity.merge_test_a', 'val', 1, 10, 2); + create_range_partitions +------------------------- + 2 +(1 row) + +SELECT create_range_partitions('calamity.merge_test_b', 'val', 1, 10, 2); + create_range_partitions +------------------------- + 2 +(1 row) + +SELECT merge_range_partitions('calamity.merge_test_a_1', + 'calamity.merge_test_b_1'); /* not ok */ +ERROR: cannot merge partitions +DROP TABLE calamity.merge_test_a,calamity.merge_test_b CASCADE; +NOTICE: drop cascades to 6 other objects +DROP DOMAIN calamity.test_domain; +DROP TABLE calamity.part_test CASCADE; +NOTICE: drop cascades to table calamity.wrong_partition +DROP TABLE calamity.part_ok CASCADE; +NOTICE: drop cascades to 4 other objects +DROP TABLE calamity.hash_two_times CASCADE; +NOTICE: drop cascades to 2 other objects +DROP TABLE calamity.to_be_disabled CASCADE; +NOTICE: drop cascades to 3 other objects +DROP SCHEMA calamity; +DROP EXTENSION pg_pathman; +/* + * ------------------------------- + * Special tests (SET statement) + * ------------------------------- + */ +CREATE EXTENSION pg_pathman; +SET pg_pathman.enable = false; +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been disabled +SET pg_pathman.enable = true; +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been enabled +SET pg_pathman.enable = false; +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been disabled +RESET pg_pathman.enable; +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been enabled +RESET ALL; +BEGIN; ROLLBACK; +BEGIN ISOLATION LEVEL SERIALIZABLE; ROLLBACK; +BEGIN; SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; ROLLBACK; +DROP EXTENSION pg_pathman; +/* + * ------------------------------------- + * Special tests (pathman_cache_stats) + * ------------------------------------- + */ +CREATE SCHEMA calamity; +CREATE EXTENSION pg_pathman; +/* check that cache loading is lazy */ +CREATE TABLE calamity.test_pathman_cache_stats(val NUMERIC NOT NULL); +SELECT create_range_partitions('calamity.test_pathman_cache_stats', 'val', 1, 10, 10); + create_range_partitions +------------------------- + 10 +(1 row) + +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ + context | entries +-------------------------+--------- + maintenance | 0 + partition bounds cache | 0 + partition parents cache | 0 +(3 rows) + +DROP TABLE calamity.test_pathman_cache_stats CASCADE; +NOTICE: drop cascades to 11 other objects +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ + context | entries +-------------------------+--------- + maintenance | 0 + partition bounds cache | 0 + partition parents cache | 0 +(3 rows) + +/* Change this setting for code coverage */ +SET pg_pathman.enable_bounds_cache = false; +/* check view pathman_cache_stats (bounds cache disabled) */ +CREATE TABLE calamity.test_pathman_cache_stats(val NUMERIC NOT NULL); +SELECT create_range_partitions('calamity.test_pathman_cache_stats', 'val', 1, 10, 10); + create_range_partitions +------------------------- + 10 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM calamity.test_pathman_cache_stats; + QUERY PLAN +----------------------------------------------- + Append + -> Seq Scan on test_pathman_cache_stats_1 + -> Seq Scan on test_pathman_cache_stats_2 + -> Seq Scan on test_pathman_cache_stats_3 + -> Seq Scan on test_pathman_cache_stats_4 + -> Seq Scan on test_pathman_cache_stats_5 + -> Seq Scan on test_pathman_cache_stats_6 + -> Seq Scan on test_pathman_cache_stats_7 + -> Seq Scan on test_pathman_cache_stats_8 + -> Seq Scan on test_pathman_cache_stats_9 + -> Seq Scan on test_pathman_cache_stats_10 +(11 rows) + +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ + context | entries +-------------------------+--------- + maintenance | 0 + partition bounds cache | 0 + partition parents cache | 10 +(3 rows) + +DROP TABLE calamity.test_pathman_cache_stats CASCADE; +NOTICE: drop cascades to 11 other objects +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ + context | entries +-------------------------+--------- + maintenance | 0 + partition bounds cache | 0 + partition parents cache | 0 +(3 rows) + +/* Restore this GUC */ +SET pg_pathman.enable_bounds_cache = true; +/* check view pathman_cache_stats (bounds cache enabled) */ +CREATE TABLE calamity.test_pathman_cache_stats(val NUMERIC NOT NULL); +SELECT create_range_partitions('calamity.test_pathman_cache_stats', 'val', 1, 10, 10); + create_range_partitions +------------------------- + 10 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM calamity.test_pathman_cache_stats; + QUERY PLAN +----------------------------------------------- + Append + -> Seq Scan on test_pathman_cache_stats_1 + -> Seq Scan on test_pathman_cache_stats_2 + -> Seq Scan on test_pathman_cache_stats_3 + -> Seq Scan on test_pathman_cache_stats_4 + -> Seq Scan on test_pathman_cache_stats_5 + -> Seq Scan on test_pathman_cache_stats_6 + -> Seq Scan on test_pathman_cache_stats_7 + -> Seq Scan on test_pathman_cache_stats_8 + -> Seq Scan on test_pathman_cache_stats_9 + -> Seq Scan on test_pathman_cache_stats_10 +(11 rows) + +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ + context | entries +-------------------------+--------- + maintenance | 0 + partition bounds cache | 10 + partition parents cache | 10 +(3 rows) + +DROP TABLE calamity.test_pathman_cache_stats CASCADE; +NOTICE: drop cascades to 11 other objects +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ + context | entries +-------------------------+--------- + maintenance | 0 + partition bounds cache | 0 + partition parents cache | 0 +(3 rows) + +/* check that parents cache has been flushed after partition was dropped */ +CREATE TABLE calamity.test_pathman_cache_stats(val NUMERIC NOT NULL); +SELECT create_range_partitions('calamity.test_pathman_cache_stats', 'val', 1, 10, 10); + create_range_partitions +------------------------- + 10 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM calamity.test_pathman_cache_stats; + QUERY PLAN +----------------------------------------------- + Append + -> Seq Scan on test_pathman_cache_stats_1 + -> Seq Scan on test_pathman_cache_stats_2 + -> Seq Scan on test_pathman_cache_stats_3 + -> Seq Scan on test_pathman_cache_stats_4 + -> Seq Scan on test_pathman_cache_stats_5 + -> Seq Scan on test_pathman_cache_stats_6 + -> Seq Scan on test_pathman_cache_stats_7 + -> Seq Scan on test_pathman_cache_stats_8 + -> Seq Scan on test_pathman_cache_stats_9 + -> Seq Scan on test_pathman_cache_stats_10 +(11 rows) + +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ + context | entries +-------------------------+--------- + maintenance | 0 + partition bounds cache | 10 + partition parents cache | 10 +(3 rows) + +SELECT drop_range_partition('calamity.test_pathman_cache_stats_1'); + drop_range_partition +------------------------------------- + calamity.test_pathman_cache_stats_1 +(1 row) + +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ + context | entries +-------------------------+--------- + maintenance | 0 + partition bounds cache | 9 + partition parents cache | 9 +(3 rows) + +DROP TABLE calamity.test_pathman_cache_stats CASCADE; +NOTICE: drop cascades to 10 other objects +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ + context | entries +-------------------------+--------- + maintenance | 0 + partition bounds cache | 0 + partition parents cache | 0 +(3 rows) + +DROP SCHEMA calamity; +DROP EXTENSION pg_pathman; +/* + * ------------------------------------------ + * Special tests (uninitialized pg_pathman) + * ------------------------------------------ + */ +CREATE SCHEMA calamity; +CREATE EXTENSION pg_pathman; +/* check function pathman_cache_search_relid() */ +CREATE TABLE calamity.survivor(val INT NOT NULL); +SELECT create_range_partitions('calamity.survivor', 'val', 1, 10, 2); + create_range_partitions +------------------------- + 2 +(1 row) + +DROP EXTENSION pg_pathman CASCADE; +SET pg_pathman.enable = f; /* DON'T LOAD CONFIG */ +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been disabled +CREATE EXTENSION pg_pathman; +SHOW pg_pathman.enable; + pg_pathman.enable +------------------- + off +(1 row) + +SELECT add_to_pathman_config('calamity.survivor', 'val', '10'); /* not ok */ +ERROR: pg_pathman is disabled +SELECT * FROM pathman_partition_list; /* not ok */ +ERROR: pg_pathman is not initialized yet +SELECT get_part_range('calamity.survivor', 0, NULL::INT); /* not ok */ +ERROR: pg_pathman is disabled +EXPLAIN (COSTS OFF) SELECT * FROM calamity.survivor; /* OK */ + QUERY PLAN +----------------------------------------- + Append + -> Seq Scan on survivor survivor_1 + -> Seq Scan on survivor_1 survivor_2 + -> Seq Scan on survivor_2 survivor_3 +(4 rows) + +SET pg_pathman.enable = t; /* LOAD CONFIG */ +NOTICE: RuntimeAppend, RuntimeMergeAppend and PartitionFilter nodes and some other options have been enabled +SELECT add_to_pathman_config('calamity.survivor', 'val', '10'); /* OK */ + add_to_pathman_config +----------------------- + t +(1 row) + +SELECT * FROM pathman_partition_list; /* OK */ + parent | partition | parttype | expr | range_min | range_max +-------------------+---------------------+----------+------+-----------+----------- + calamity.survivor | calamity.survivor_1 | 2 | val | 1 | 11 + calamity.survivor | calamity.survivor_2 | 2 | val | 11 | 21 +(2 rows) + +SELECT get_part_range('calamity.survivor', 0, NULL::INT); /* OK */ + get_part_range +---------------- + {1,11} +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM calamity.survivor; /* OK */ + QUERY PLAN +------------------------------ + Append + -> Seq Scan on survivor_1 + -> Seq Scan on survivor_2 +(3 rows) + +DROP TABLE calamity.survivor CASCADE; +NOTICE: drop cascades to 3 other objects +DROP SCHEMA calamity; +DROP EXTENSION pg_pathman; diff --git a/expected/pathman_callbacks.out b/expected/pathman_callbacks.out index 3eea2049..8427dae7 100644 --- a/expected/pathman_callbacks.out +++ b/expected/pathman_callbacks.out @@ -411,6 +411,8 @@ ORDER BY range_min::INT4; DROP TABLE callbacks.abc CASCADE; NOTICE: drop cascades to 5 other objects -DROP SCHEMA callbacks CASCADE; -NOTICE: drop cascades to 2 other objects +DROP FUNCTION callbacks.abc_on_part_created_callback(jsonb); +DROP FUNCTION public.dummy_cb(jsonb); +DROP FUNCTION callbacks.rotation_callback(jsonb); +DROP SCHEMA callbacks; DROP EXTENSION pg_pathman CASCADE; diff --git a/expected/pathman_column_type.out b/expected/pathman_column_type.out index 4e2f3ff6..c77acbb2 100644 --- a/expected/pathman_column_type.out +++ b/expected/pathman_column_type.out @@ -23,14 +23,14 @@ SELECT * FROM test_column_type.test; ----- (0 rows) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 10 partition parents cache | 10 - partition status cache | 3 -(4 rows) +(3 rows) /* * Get parsed and analyzed expression. @@ -84,14 +84,14 @@ SELECT * FROM test_column_type.test; ----- (0 rows) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 10 partition parents cache | 10 - partition status cache | 3 -(4 rows) +(3 rows) /* check insert dispatching */ INSERT INTO test_column_type.test VALUES (1); @@ -135,14 +135,14 @@ SELECT * FROM test_column_type.test; ----+----- (0 rows) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 5 partition parents cache | 5 - partition status cache | 3 -(4 rows) +(3 rows) /* change column's type (should NOT work) */ ALTER TABLE test_column_type.test ALTER id TYPE NUMERIC; @@ -153,14 +153,14 @@ SELECT * FROM test_column_type.test; ----+----- (0 rows) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 5 partition parents cache | 5 - partition status cache | 3 -(4 rows) +(3 rows) /* change column's type (should flush caches) */ ALTER TABLE test_column_type.test ALTER val TYPE NUMERIC; @@ -170,14 +170,14 @@ SELECT * FROM test_column_type.test; ----+----- (0 rows) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 5 partition parents cache | 5 - partition status cache | 3 -(4 rows) +(3 rows) /* check insert dispatching */ INSERT INTO test_column_type.test VALUES (1); @@ -199,5 +199,5 @@ NOTICE: 0 rows copied from test_column_type.test_4 (1 row) DROP TABLE test_column_type.test CASCADE; -DROP SCHEMA test_column_type CASCADE; +DROP SCHEMA test_column_type; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_column_type_1.out b/expected/pathman_column_type_1.out index d169719d..06b61387 100644 --- a/expected/pathman_column_type_1.out +++ b/expected/pathman_column_type_1.out @@ -23,14 +23,14 @@ SELECT * FROM test_column_type.test; ----- (0 rows) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 10 partition parents cache | 10 - partition status cache | 3 -(4 rows) +(3 rows) /* * Get parsed and analyzed expression. @@ -84,14 +84,14 @@ SELECT * FROM test_column_type.test; ----- (0 rows) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 10 partition parents cache | 10 - partition status cache | 3 -(4 rows) +(3 rows) /* check insert dispatching */ INSERT INTO test_column_type.test VALUES (1); @@ -135,14 +135,14 @@ SELECT * FROM test_column_type.test; ----+----- (0 rows) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 5 partition parents cache | 5 - partition status cache | 3 -(4 rows) +(3 rows) /* change column's type (should NOT work) */ ALTER TABLE test_column_type.test ALTER id TYPE NUMERIC; @@ -153,14 +153,14 @@ SELECT * FROM test_column_type.test; ----+----- (0 rows) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 5 partition parents cache | 5 - partition status cache | 3 -(4 rows) +(3 rows) /* change column's type (should flush caches) */ ALTER TABLE test_column_type.test ALTER val TYPE NUMERIC; @@ -170,14 +170,14 @@ SELECT * FROM test_column_type.test; ----+----- (0 rows) -SELECT context, entries FROM pathman_cache_stats ORDER BY context; +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; context | entries -------------------------+--------- maintenance | 0 partition bounds cache | 5 partition parents cache | 5 - partition status cache | 3 -(4 rows) +(3 rows) /* check insert dispatching */ INSERT INTO test_column_type.test VALUES (1); @@ -199,5 +199,5 @@ NOTICE: 0 rows copied from test_column_type.test_4 (1 row) DROP TABLE test_column_type.test CASCADE; -DROP SCHEMA test_column_type CASCADE; +DROP SCHEMA test_column_type; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_column_type_2.out b/expected/pathman_column_type_2.out new file mode 100644 index 00000000..0fbd0793 --- /dev/null +++ b/expected/pathman_column_type_2.out @@ -0,0 +1,203 @@ +/* + * In 9ce77d75c5a (>= 13) struct Var was changed, which caused the output + * of get_partition_cooked_key to change. + */ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE EXTENSION pg_pathman; +CREATE SCHEMA test_column_type; +/* + * RANGE partitioning. + */ +/* create new table (val int) */ +CREATE TABLE test_column_type.test(val INT4 NOT NULL); +SELECT create_range_partitions('test_column_type.test', 'val', 1, 10, 10); + create_range_partitions +------------------------- + 10 +(1 row) + +/* make sure that bounds and dispatch info has been cached */ +SELECT * FROM test_column_type.test; + val +----- +(0 rows) + +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; + context | entries +-------------------------+--------- + maintenance | 0 + partition bounds cache | 10 + partition parents cache | 10 +(3 rows) + +/* + * Get parsed and analyzed expression. + */ +CREATE FUNCTION get_cached_partition_cooked_key(REGCLASS) +RETURNS TEXT AS 'pg_pathman', 'get_cached_partition_cooked_key_pl' +LANGUAGE C STRICT; +SELECT get_partition_cooked_key('test_column_type.test'::REGCLASS); + get_partition_cooked_key +--------------------------------------------------------------------------------------------------------------------------------------------- + {VAR :varno 1 :varattno 1 :vartype 23 :vartypmod -1 :varcollid 0 :varnullingrels (b) :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location 8} +(1 row) + +SELECT get_cached_partition_cooked_key('test_column_type.test'::REGCLASS); + get_cached_partition_cooked_key +--------------------------------------------------------------------------------------------------------------------------------------------- + {VAR :varno 1 :varattno 1 :vartype 23 :vartypmod -1 :varcollid 0 :varnullingrels (b) :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location 8} +(1 row) + +SELECT get_partition_key_type('test_column_type.test'::REGCLASS); + get_partition_key_type +------------------------ + integer +(1 row) + +/* change column's type (should also flush caches) */ +ALTER TABLE test_column_type.test ALTER val TYPE NUMERIC; +/* check that correct expression has been built */ +SELECT get_partition_key_type('test_column_type.test'::REGCLASS); + get_partition_key_type +------------------------ + numeric +(1 row) + +SELECT get_partition_cooked_key('test_column_type.test'::REGCLASS); + get_partition_cooked_key +----------------------------------------------------------------------------------------------------------------------------------------------- + {VAR :varno 1 :varattno 1 :vartype 1700 :vartypmod -1 :varcollid 0 :varnullingrels (b) :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location 8} +(1 row) + +SELECT get_cached_partition_cooked_key('test_column_type.test'::REGCLASS); + get_cached_partition_cooked_key +----------------------------------------------------------------------------------------------------------------------------------------------- + {VAR :varno 1 :varattno 1 :vartype 1700 :vartypmod -1 :varcollid 0 :varnullingrels (b) :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location 8} +(1 row) + +DROP FUNCTION get_cached_partition_cooked_key(REGCLASS); +/* make sure that everything works properly */ +SELECT * FROM test_column_type.test; + val +----- +(0 rows) + +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; + context | entries +-------------------------+--------- + maintenance | 0 + partition bounds cache | 10 + partition parents cache | 10 +(3 rows) + +/* check insert dispatching */ +INSERT INTO test_column_type.test VALUES (1); +SELECT tableoid::regclass, * FROM test_column_type.test; + tableoid | val +-------------------------+----- + test_column_type.test_1 | 1 +(1 row) + +SELECT drop_partitions('test_column_type.test'); +NOTICE: 1 rows copied from test_column_type.test_1 +NOTICE: 0 rows copied from test_column_type.test_2 +NOTICE: 0 rows copied from test_column_type.test_3 +NOTICE: 0 rows copied from test_column_type.test_4 +NOTICE: 0 rows copied from test_column_type.test_5 +NOTICE: 0 rows copied from test_column_type.test_6 +NOTICE: 0 rows copied from test_column_type.test_7 +NOTICE: 0 rows copied from test_column_type.test_8 +NOTICE: 0 rows copied from test_column_type.test_9 +NOTICE: 0 rows copied from test_column_type.test_10 + drop_partitions +----------------- + 10 +(1 row) + +DROP TABLE test_column_type.test CASCADE; +/* + * HASH partitioning. + */ +/* create new table (id int, val int) */ +CREATE TABLE test_column_type.test(id INT4 NOT NULL, val INT4); +SELECT create_hash_partitions('test_column_type.test', 'id', 5); + create_hash_partitions +------------------------ + 5 +(1 row) + +/* make sure that bounds and dispatch info has been cached */ +SELECT * FROM test_column_type.test; + id | val +----+----- +(0 rows) + +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; + context | entries +-------------------------+--------- + maintenance | 0 + partition bounds cache | 5 + partition parents cache | 5 +(3 rows) + +/* change column's type (should NOT work) */ +ALTER TABLE test_column_type.test ALTER id TYPE NUMERIC; +ERROR: cannot change type of column "id" of table "test" partitioned by HASH +/* make sure that everything works properly */ +SELECT * FROM test_column_type.test; + id | val +----+----- +(0 rows) + +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; + context | entries +-------------------------+--------- + maintenance | 0 + partition bounds cache | 5 + partition parents cache | 5 +(3 rows) + +/* change column's type (should flush caches) */ +ALTER TABLE test_column_type.test ALTER val TYPE NUMERIC; +/* make sure that everything works properly */ +SELECT * FROM test_column_type.test; + id | val +----+----- +(0 rows) + +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; + context | entries +-------------------------+--------- + maintenance | 0 + partition bounds cache | 5 + partition parents cache | 5 +(3 rows) + +/* check insert dispatching */ +INSERT INTO test_column_type.test VALUES (1); +SELECT tableoid::regclass, * FROM test_column_type.test; + tableoid | id | val +-------------------------+----+----- + test_column_type.test_0 | 1 | +(1 row) + +SELECT drop_partitions('test_column_type.test'); +NOTICE: 1 rows copied from test_column_type.test_0 +NOTICE: 0 rows copied from test_column_type.test_1 +NOTICE: 0 rows copied from test_column_type.test_2 +NOTICE: 0 rows copied from test_column_type.test_3 +NOTICE: 0 rows copied from test_column_type.test_4 + drop_partitions +----------------- + 5 +(1 row) + +DROP TABLE test_column_type.test CASCADE; +DROP SCHEMA test_column_type; +DROP EXTENSION pg_pathman; diff --git a/expected/pathman_cte.out b/expected/pathman_cte.out index ce818a36..33821ac0 100644 --- a/expected/pathman_cte.out +++ b/expected/pathman_cte.out @@ -271,6 +271,7 @@ SELECT * FROM test; (4 rows) -DROP SCHEMA test_cte CASCADE; -NOTICE: drop cascades to 3 other objects +DROP TABLE test_cte.recursive_cte_test_tbl CASCADE; +NOTICE: drop cascades to 2 other objects +DROP SCHEMA test_cte; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_cte_1.out b/expected/pathman_cte_1.out index 70a9ee88..5e30e188 100644 --- a/expected/pathman_cte_1.out +++ b/expected/pathman_cte_1.out @@ -260,6 +260,7 @@ SELECT * FROM test; (4 rows) -DROP SCHEMA test_cte CASCADE; -NOTICE: drop cascades to 3 other objects +DROP TABLE test_cte.recursive_cte_test_tbl CASCADE; +NOTICE: drop cascades to 2 other objects +DROP SCHEMA test_cte; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_cte_2.out b/expected/pathman_cte_2.out new file mode 100644 index 00000000..b9bf8730 --- /dev/null +++ b/expected/pathman_cte_2.out @@ -0,0 +1,253 @@ +/* + * Test simple CTE queries. + * Since 12 (608b167f9f), CTEs which are scanned once are no longer an + * optimization fence, which changes practically all plans here. There is + * an option to forcibly make them MATERIALIZED, but we also need to run tests + * on older versions, so create pathman_cte_1.out instead. + */ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE EXTENSION pg_pathman; +CREATE SCHEMA test_cte; +CREATE TABLE test_cte.range_rel ( + id INT4, + dt TIMESTAMP NOT NULL, + txt TEXT); +INSERT INTO test_cte.range_rel (dt, txt) +SELECT g, md5(g::TEXT) +FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) AS g; +SELECT create_range_partitions('test_cte.range_rel', 'DT', '2015-01-01'::DATE, '1 month'::INTERVAL); + create_range_partitions +------------------------- + 4 +(1 row) + +/* perform a query */ +EXPLAIN (COSTS OFF) + WITH ttt AS (SELECT * FROM test_cte.range_rel WHERE dt >= '2015-02-01' AND dt < '2015-03-15') +SELECT * FROM ttt; + QUERY PLAN +-------------------------------------------------------------------------------- + Append + -> Seq Scan on range_rel_2 range_rel_1 + -> Seq Scan on range_rel_3 range_rel_2 + Filter: (dt < 'Sun Mar 15 00:00:00 2015'::timestamp without time zone) +(4 rows) + +DROP TABLE test_cte.range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +CREATE TABLE test_cte.hash_rel ( + id INT4, + value INTEGER NOT NULL); +INSERT INTO test_cte.hash_rel VALUES (1, 1); +INSERT INTO test_cte.hash_rel VALUES (2, 2); +INSERT INTO test_cte.hash_rel VALUES (3, 3); +SELECT create_hash_partitions('test_cte.hash_rel', 'value', 3); + create_hash_partitions +------------------------ + 3 +(1 row) + +/* perform a query */ +EXPLAIN (COSTS OFF) + WITH ttt AS (SELECT * FROM test_cte.hash_rel WHERE value = 2) +SELECT * FROM ttt; + QUERY PLAN +--------------------------------- + Seq Scan on hash_rel_1 hash_rel + Filter: (value = 2) +(2 rows) + +DROP TABLE test_cte.hash_rel CASCADE; +NOTICE: drop cascades to 3 other objects +/* + * Test CTE query - by @parihaaraka (add varno to WalkerContext) + */ +CREATE TABLE test_cte.cte_del_xacts (id BIGSERIAL PRIMARY KEY, pdate DATE NOT NULL); +INSERT INTO test_cte.cte_del_xacts (pdate) +SELECT gen_date +FROM generate_series('2016-01-01'::date, '2016-04-9'::date, '1 day') AS gen_date; +CREATE TABLE test_cte.cte_del_xacts_specdata +( + tid BIGINT PRIMARY KEY, + test_mode SMALLINT, + state_code SMALLINT NOT NULL DEFAULT 8, + regtime TIMESTAMP WITHOUT TIME ZONE NOT NULL +); +INSERT INTO test_cte.cte_del_xacts_specdata VALUES (1, 1, 1, current_timestamp); /* for subquery test */ +/* create 2 partitions */ +SELECT create_range_partitions('test_cte.cte_del_xacts'::regclass, 'pdate', + '2016-01-01'::date, '50 days'::interval); + create_range_partitions +------------------------- + 2 +(1 row) + +EXPLAIN (COSTS OFF) +WITH tmp AS ( + SELECT tid, test_mode, regtime::DATE AS pdate, state_code + FROM test_cte.cte_del_xacts_specdata) +DELETE FROM test_cte.cte_del_xacts t USING tmp +WHERE t.id = tmp.tid AND t.pdate = tmp.pdate AND tmp.test_mode > 0; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------- + Delete on cte_del_xacts t + Delete on cte_del_xacts t_1 + Delete on cte_del_xacts_1 t_2 + Delete on cte_del_xacts_2 t_3 + -> Hash Join + Hash Cond: ((t.id = cte_del_xacts_specdata.tid) AND (t.pdate = (cte_del_xacts_specdata.regtime)::date)) + -> Append + -> Seq Scan on cte_del_xacts t_1 + -> Seq Scan on cte_del_xacts_1 t_2 + -> Seq Scan on cte_del_xacts_2 t_3 + -> Hash + -> Seq Scan on cte_del_xacts_specdata + Filter: (test_mode > 0) +(13 rows) + +SELECT drop_partitions('test_cte.cte_del_xacts'); /* now drop partitions */ +NOTICE: 50 rows copied from test_cte.cte_del_xacts_1 +NOTICE: 50 rows copied from test_cte.cte_del_xacts_2 + drop_partitions +----------------- + 2 +(1 row) + +/* create 1 partition */ +SELECT create_range_partitions('test_cte.cte_del_xacts'::regclass, 'pdate', + '2016-01-01'::date, '1 year'::interval); + create_range_partitions +------------------------- + 1 +(1 row) + +/* parent enabled! */ +SELECT set_enable_parent('test_cte.cte_del_xacts', true); + set_enable_parent +------------------- + +(1 row) + +EXPLAIN (COSTS OFF) +WITH tmp AS ( + SELECT tid, test_mode, regtime::DATE AS pdate, state_code + FROM test_cte.cte_del_xacts_specdata) +DELETE FROM test_cte.cte_del_xacts t USING tmp +WHERE t.id = tmp.tid AND t.pdate = tmp.pdate AND tmp.test_mode > 0; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------- + Delete on cte_del_xacts t + Delete on cte_del_xacts t_1 + Delete on cte_del_xacts_1 t_2 + -> Hash Join + Hash Cond: ((t.id = cte_del_xacts_specdata.tid) AND (t.pdate = (cte_del_xacts_specdata.regtime)::date)) + -> Append + -> Seq Scan on cte_del_xacts t_1 + -> Seq Scan on cte_del_xacts_1 t_2 + -> Hash + -> Seq Scan on cte_del_xacts_specdata + Filter: (test_mode > 0) +(11 rows) + +/* parent disabled! */ +SELECT set_enable_parent('test_cte.cte_del_xacts', false); + set_enable_parent +------------------- + +(1 row) + +EXPLAIN (COSTS OFF) +WITH tmp AS ( + SELECT tid, test_mode, regtime::DATE AS pdate, state_code + FROM test_cte.cte_del_xacts_specdata) +DELETE FROM test_cte.cte_del_xacts t USING tmp +WHERE t.id = tmp.tid AND t.pdate = tmp.pdate AND tmp.test_mode > 0; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------- + Delete on cte_del_xacts_1 t + -> Hash Join + Hash Cond: ((t.id = cte_del_xacts_specdata.tid) AND (t.pdate = (cte_del_xacts_specdata.regtime)::date)) + -> Seq Scan on cte_del_xacts_1 t + -> Hash + -> Seq Scan on cte_del_xacts_specdata + Filter: (test_mode > 0) +(7 rows) + +/* create stub pl/PgSQL function */ +CREATE OR REPLACE FUNCTION test_cte.cte_del_xacts_stab(name TEXT) +RETURNS smallint AS +$$ +begin + return 2::smallint; +end +$$ +LANGUAGE plpgsql STABLE; +/* test subquery planning */ +WITH tmp AS ( + SELECT tid FROM test_cte.cte_del_xacts_specdata + WHERE state_code != test_cte.cte_del_xacts_stab('test')) +SELECT * FROM test_cte.cte_del_xacts t JOIN tmp ON t.id = tmp.tid; + id | pdate | tid +----+------------+----- + 1 | 01-01-2016 | 1 +(1 row) + +/* test subquery planning (one more time) */ +WITH tmp AS ( + SELECT tid FROM test_cte.cte_del_xacts_specdata + WHERE state_code != test_cte.cte_del_xacts_stab('test')) +SELECT * FROM test_cte.cte_del_xacts t JOIN tmp ON t.id = tmp.tid; + id | pdate | tid +----+------------+----- + 1 | 01-01-2016 | 1 +(1 row) + +DROP FUNCTION test_cte.cte_del_xacts_stab(TEXT); +DROP TABLE test_cte.cte_del_xacts, test_cte.cte_del_xacts_specdata CASCADE; +NOTICE: drop cascades to 2 other objects +/* Test recursive CTE */ +CREATE TABLE test_cte.recursive_cte_test_tbl(id INT NOT NULL, name TEXT NOT NULL); +SELECT create_hash_partitions('test_cte.recursive_cte_test_tbl', 'id', 2); + create_hash_partitions +------------------------ + 2 +(1 row) + +INSERT INTO test_cte.recursive_cte_test_tbl (id, name) +SELECT id, 'name'||id FROM generate_series(1,100) f(id); +INSERT INTO test_cte.recursive_cte_test_tbl (id, name) +SELECT id, 'name'||(id + 1) FROM generate_series(1,100) f(id); +INSERT INTO test_cte.recursive_cte_test_tbl (id, name) +SELECT id, 'name'||(id + 2) FROM generate_series(1,100) f(id); +SELECT * FROM test_cte.recursive_cte_test_tbl WHERE id = 5; + id | name +----+------- + 5 | name5 + 5 | name6 + 5 | name7 +(3 rows) + +WITH RECURSIVE test AS ( + SELECT min(name) AS name + FROM test_cte.recursive_cte_test_tbl + WHERE id = 5 + UNION ALL + SELECT (SELECT min(name) + FROM test_cte.recursive_cte_test_tbl + WHERE id = 5 AND name > test.name) + FROM test + WHERE name IS NOT NULL) +SELECT * FROM test; + name +------- + name5 + name6 + name7 + +(4 rows) + +DROP TABLE test_cte.recursive_cte_test_tbl CASCADE; +NOTICE: drop cascades to 2 other objects +DROP SCHEMA test_cte; +DROP EXTENSION pg_pathman; diff --git a/expected/pathman_cte_3.out b/expected/pathman_cte_3.out new file mode 100644 index 00000000..a7f3acd0 --- /dev/null +++ b/expected/pathman_cte_3.out @@ -0,0 +1,266 @@ +/* + * Test simple CTE queries. + * Since 12 (608b167f9f), CTEs which are scanned once are no longer an + * optimization fence, which changes practically all plans here. There is + * an option to forcibly make them MATERIALIZED, but we also need to run tests + * on older versions, so create pathman_cte_1.out instead. + */ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE EXTENSION pg_pathman; +CREATE SCHEMA test_cte; +CREATE TABLE test_cte.range_rel ( + id INT4, + dt TIMESTAMP NOT NULL, + txt TEXT); +INSERT INTO test_cte.range_rel (dt, txt) +SELECT g, md5(g::TEXT) +FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) AS g; +SELECT create_range_partitions('test_cte.range_rel', 'DT', '2015-01-01'::DATE, '1 month'::INTERVAL); + create_range_partitions +------------------------- + 4 +(1 row) + +/* perform a query */ +EXPLAIN (COSTS OFF) + WITH ttt AS (SELECT * FROM test_cte.range_rel WHERE dt >= '2015-02-01' AND dt < '2015-03-15') +SELECT * FROM ttt; + QUERY PLAN +-------------------------------------------------------------------------------- + Append + -> Seq Scan on range_rel_2 range_rel_1 + -> Seq Scan on range_rel_3 range_rel_2 + Filter: (dt < 'Sun Mar 15 00:00:00 2015'::timestamp without time zone) +(4 rows) + +DROP TABLE test_cte.range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +CREATE TABLE test_cte.hash_rel ( + id INT4, + value INTEGER NOT NULL); +INSERT INTO test_cte.hash_rel VALUES (1, 1); +INSERT INTO test_cte.hash_rel VALUES (2, 2); +INSERT INTO test_cte.hash_rel VALUES (3, 3); +SELECT create_hash_partitions('test_cte.hash_rel', 'value', 3); + create_hash_partitions +------------------------ + 3 +(1 row) + +/* perform a query */ +EXPLAIN (COSTS OFF) + WITH ttt AS (SELECT * FROM test_cte.hash_rel WHERE value = 2) +SELECT * FROM ttt; + QUERY PLAN +--------------------------------- + Seq Scan on hash_rel_1 hash_rel + Filter: (value = 2) +(2 rows) + +DROP TABLE test_cte.hash_rel CASCADE; +NOTICE: drop cascades to 3 other objects +/* + * Test CTE query - by @parihaaraka (add varno to WalkerContext) + */ +CREATE TABLE test_cte.cte_del_xacts (id BIGSERIAL PRIMARY KEY, pdate DATE NOT NULL); +INSERT INTO test_cte.cte_del_xacts (pdate) +SELECT gen_date +FROM generate_series('2016-01-01'::date, '2016-04-9'::date, '1 day') AS gen_date; +CREATE TABLE test_cte.cte_del_xacts_specdata +( + tid BIGINT PRIMARY KEY, + test_mode SMALLINT, + state_code SMALLINT NOT NULL DEFAULT 8, + regtime TIMESTAMP WITHOUT TIME ZONE NOT NULL +); +INSERT INTO test_cte.cte_del_xacts_specdata VALUES (1, 1, 1, current_timestamp); /* for subquery test */ +/* create 2 partitions */ +SELECT create_range_partitions('test_cte.cte_del_xacts'::regclass, 'pdate', + '2016-01-01'::date, '50 days'::interval); + create_range_partitions +------------------------- + 2 +(1 row) + +EXPLAIN (COSTS OFF) +WITH tmp AS ( + SELECT tid, test_mode, regtime::DATE AS pdate, state_code + FROM test_cte.cte_del_xacts_specdata) +DELETE FROM test_cte.cte_del_xacts t USING tmp +WHERE t.id = tmp.tid AND t.pdate = tmp.pdate AND tmp.test_mode > 0; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------- + Delete on cte_del_xacts t + Delete on cte_del_xacts t + Delete on cte_del_xacts_1 t_1 + Delete on cte_del_xacts_2 t_2 + -> Hash Join + Hash Cond: ((cte_del_xacts_specdata.tid = t.id) AND ((cte_del_xacts_specdata.regtime)::date = t.pdate)) + -> Seq Scan on cte_del_xacts_specdata + Filter: (test_mode > 0) + -> Hash + -> Seq Scan on cte_del_xacts t + -> Hash Join + Hash Cond: ((t_1.id = cte_del_xacts_specdata.tid) AND (t_1.pdate = (cte_del_xacts_specdata.regtime)::date)) + -> Seq Scan on cte_del_xacts_1 t_1 + -> Hash + -> Seq Scan on cte_del_xacts_specdata + Filter: (test_mode > 0) + -> Hash Join + Hash Cond: ((t_2.id = cte_del_xacts_specdata.tid) AND (t_2.pdate = (cte_del_xacts_specdata.regtime)::date)) + -> Seq Scan on cte_del_xacts_2 t_2 + -> Hash + -> Seq Scan on cte_del_xacts_specdata + Filter: (test_mode > 0) +(22 rows) + +SELECT drop_partitions('test_cte.cte_del_xacts'); /* now drop partitions */ +NOTICE: 50 rows copied from test_cte.cte_del_xacts_1 +NOTICE: 50 rows copied from test_cte.cte_del_xacts_2 + drop_partitions +----------------- + 2 +(1 row) + +/* create 1 partition */ +SELECT create_range_partitions('test_cte.cte_del_xacts'::regclass, 'pdate', + '2016-01-01'::date, '1 year'::interval); + create_range_partitions +------------------------- + 1 +(1 row) + +/* parent enabled! */ +SELECT set_enable_parent('test_cte.cte_del_xacts', true); + set_enable_parent +------------------- + +(1 row) + +EXPLAIN (COSTS OFF) +WITH tmp AS ( + SELECT tid, test_mode, regtime::DATE AS pdate, state_code + FROM test_cte.cte_del_xacts_specdata) +DELETE FROM test_cte.cte_del_xacts t USING tmp +WHERE t.id = tmp.tid AND t.pdate = tmp.pdate AND tmp.test_mode > 0; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------- + Delete on cte_del_xacts t + Delete on cte_del_xacts t + Delete on cte_del_xacts_1 t_1 + -> Hash Join + Hash Cond: ((cte_del_xacts_specdata.tid = t.id) AND ((cte_del_xacts_specdata.regtime)::date = t.pdate)) + -> Seq Scan on cte_del_xacts_specdata + Filter: (test_mode > 0) + -> Hash + -> Seq Scan on cte_del_xacts t + -> Hash Join + Hash Cond: ((t_1.id = cte_del_xacts_specdata.tid) AND (t_1.pdate = (cte_del_xacts_specdata.regtime)::date)) + -> Seq Scan on cte_del_xacts_1 t_1 + -> Hash + -> Seq Scan on cte_del_xacts_specdata + Filter: (test_mode > 0) +(15 rows) + +/* parent disabled! */ +SELECT set_enable_parent('test_cte.cte_del_xacts', false); + set_enable_parent +------------------- + +(1 row) + +EXPLAIN (COSTS OFF) +WITH tmp AS ( + SELECT tid, test_mode, regtime::DATE AS pdate, state_code + FROM test_cte.cte_del_xacts_specdata) +DELETE FROM test_cte.cte_del_xacts t USING tmp +WHERE t.id = tmp.tid AND t.pdate = tmp.pdate AND tmp.test_mode > 0; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------- + Delete on cte_del_xacts_1 t + -> Hash Join + Hash Cond: ((t.id = cte_del_xacts_specdata.tid) AND (t.pdate = (cte_del_xacts_specdata.regtime)::date)) + -> Seq Scan on cte_del_xacts_1 t + -> Hash + -> Seq Scan on cte_del_xacts_specdata + Filter: (test_mode > 0) +(7 rows) + +/* create stub pl/PgSQL function */ +CREATE OR REPLACE FUNCTION test_cte.cte_del_xacts_stab(name TEXT) +RETURNS smallint AS +$$ +begin + return 2::smallint; +end +$$ +LANGUAGE plpgsql STABLE; +/* test subquery planning */ +WITH tmp AS ( + SELECT tid FROM test_cte.cte_del_xacts_specdata + WHERE state_code != test_cte.cte_del_xacts_stab('test')) +SELECT * FROM test_cte.cte_del_xacts t JOIN tmp ON t.id = tmp.tid; + id | pdate | tid +----+------------+----- + 1 | 01-01-2016 | 1 +(1 row) + +/* test subquery planning (one more time) */ +WITH tmp AS ( + SELECT tid FROM test_cte.cte_del_xacts_specdata + WHERE state_code != test_cte.cte_del_xacts_stab('test')) +SELECT * FROM test_cte.cte_del_xacts t JOIN tmp ON t.id = tmp.tid; + id | pdate | tid +----+------------+----- + 1 | 01-01-2016 | 1 +(1 row) + +DROP FUNCTION test_cte.cte_del_xacts_stab(TEXT); +DROP TABLE test_cte.cte_del_xacts, test_cte.cte_del_xacts_specdata CASCADE; +NOTICE: drop cascades to 2 other objects +/* Test recursive CTE */ +CREATE TABLE test_cte.recursive_cte_test_tbl(id INT NOT NULL, name TEXT NOT NULL); +SELECT create_hash_partitions('test_cte.recursive_cte_test_tbl', 'id', 2); + create_hash_partitions +------------------------ + 2 +(1 row) + +INSERT INTO test_cte.recursive_cte_test_tbl (id, name) +SELECT id, 'name'||id FROM generate_series(1,100) f(id); +INSERT INTO test_cte.recursive_cte_test_tbl (id, name) +SELECT id, 'name'||(id + 1) FROM generate_series(1,100) f(id); +INSERT INTO test_cte.recursive_cte_test_tbl (id, name) +SELECT id, 'name'||(id + 2) FROM generate_series(1,100) f(id); +SELECT * FROM test_cte.recursive_cte_test_tbl WHERE id = 5; + id | name +----+------- + 5 | name5 + 5 | name6 + 5 | name7 +(3 rows) + +WITH RECURSIVE test AS ( + SELECT min(name) AS name + FROM test_cte.recursive_cte_test_tbl + WHERE id = 5 + UNION ALL + SELECT (SELECT min(name) + FROM test_cte.recursive_cte_test_tbl + WHERE id = 5 AND name > test.name) + FROM test + WHERE name IS NOT NULL) +SELECT * FROM test; + name +------- + name5 + name6 + name7 + +(4 rows) + +DROP TABLE test_cte.recursive_cte_test_tbl CASCADE; +NOTICE: drop cascades to 2 other objects +DROP SCHEMA test_cte; +DROP EXTENSION pg_pathman; diff --git a/expected/pathman_declarative.out b/expected/pathman_declarative.out index 011a0f71..2915ecfb 100644 --- a/expected/pathman_declarative.out +++ b/expected/pathman_declarative.out @@ -94,7 +94,14 @@ Check constraints: "pathman_r4_check" CHECK (dt >= '06-01-2015'::date AND dt < '01-01-2016'::date) Inherits: test.range_rel -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 8 other objects +/* Note: PG-10 doesn't support ATTACH PARTITION ... DEFAULT */ +ALTER TABLE IF EXISTS test.nonexistent_table ATTACH PARTITION baz FOR VALUES IN (42); +NOTICE: relation "nonexistent_table" does not exist, skipping +ALTER TABLE IF EXISTS test.nonexistent_table DETACH PARTITION baz; +NOTICE: relation "nonexistent_table" does not exist, skipping +DROP TABLE test.r2 CASCADE; +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 6 other objects +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_declarative_1.out b/expected/pathman_declarative_1.out index 8ef4e556..dede4941 100644 --- a/expected/pathman_declarative_1.out +++ b/expected/pathman_declarative_1.out @@ -94,7 +94,14 @@ Check constraints: "pathman_r4_check" CHECK (dt >= '06-01-2015'::date AND dt < '01-01-2016'::date) Inherits: test.range_rel -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 8 other objects +/* Note: PG-10 doesn't support ATTACH PARTITION ... DEFAULT */ +ALTER TABLE IF EXISTS test.nonexistent_table ATTACH PARTITION baz FOR VALUES IN (42); +NOTICE: relation "nonexistent_table" does not exist, skipping +ALTER TABLE IF EXISTS test.nonexistent_table DETACH PARTITION baz; +NOTICE: relation "nonexistent_table" does not exist, skipping +DROP TABLE test.r2 CASCADE; +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 6 other objects +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_domains.out b/expected/pathman_domains.out index 41c8bfbb..cc32ce0c 100644 --- a/expected/pathman_domains.out +++ b/expected/pathman_domains.out @@ -124,6 +124,8 @@ ORDER BY "partition"::TEXT; domains.dom_table | domains.dom_table_4 | 1 | val | | (5 rows) -DROP SCHEMA domains CASCADE; -NOTICE: drop cascades to 7 other objects +DROP TABLE domains.dom_table CASCADE; +NOTICE: drop cascades to 5 other objects +DROP DOMAIN domains.dom_test CASCADE; +DROP SCHEMA domains; DROP EXTENSION pg_pathman CASCADE; diff --git a/expected/pathman_domains_1.out b/expected/pathman_domains_1.out new file mode 100644 index 00000000..aaa0867f --- /dev/null +++ b/expected/pathman_domains_1.out @@ -0,0 +1,131 @@ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE EXTENSION pg_pathman; +CREATE SCHEMA domains; +CREATE DOMAIN domains.dom_test AS numeric CHECK (value < 1200); +CREATE TABLE domains.dom_table(val domains.dom_test NOT NULL); +INSERT INTO domains.dom_table SELECT generate_series(1, 999); +SELECT create_range_partitions('domains.dom_table', 'val', 1, 100); + create_range_partitions +------------------------- + 10 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT * FROM domains.dom_table +WHERE val < 250; + QUERY PLAN +--------------------------------------------------- + Append + -> Seq Scan on dom_table_1 + -> Seq Scan on dom_table_2 + -> Seq Scan on dom_table_3 + Filter: ((val)::numeric < '250'::numeric) +(5 rows) + +INSERT INTO domains.dom_table VALUES(1500); +ERROR: value for domain domains.dom_test violates check constraint "dom_test_check" +INSERT INTO domains.dom_table VALUES(-10); +SELECT append_range_partition('domains.dom_table'); + append_range_partition +------------------------ + domains.dom_table_12 +(1 row) + +SELECT prepend_range_partition('domains.dom_table'); + prepend_range_partition +------------------------- + domains.dom_table_13 +(1 row) + +SELECT merge_range_partitions('domains.dom_table_1', 'domains.dom_table_2'); + merge_range_partitions +------------------------ + domains.dom_table_1 +(1 row) + +SELECT split_range_partition('domains.dom_table_1', 50); + split_range_partition +----------------------- + domains.dom_table_14 +(1 row) + +INSERT INTO domains.dom_table VALUES(1101); +EXPLAIN (COSTS OFF) +SELECT * FROM domains.dom_table +WHERE val < 450; + QUERY PLAN +--------------------------------------------------- + Append + -> Seq Scan on dom_table_13 dom_table_1 + -> Seq Scan on dom_table_11 dom_table_2 + -> Seq Scan on dom_table_1 dom_table_3 + -> Seq Scan on dom_table_14 dom_table_4 + -> Seq Scan on dom_table_3 dom_table_5 + -> Seq Scan on dom_table_4 dom_table_6 + -> Seq Scan on dom_table_5 dom_table_7 + Filter: ((val)::numeric < '450'::numeric) +(9 rows) + +SELECT * FROM pathman_partition_list +ORDER BY range_min::INT, range_max::INT; + parent | partition | parttype | expr | range_min | range_max +-------------------+----------------------+----------+------+-----------+----------- + domains.dom_table | domains.dom_table_13 | 2 | val | -199 | -99 + domains.dom_table | domains.dom_table_11 | 2 | val | -99 | 1 + domains.dom_table | domains.dom_table_1 | 2 | val | 1 | 50 + domains.dom_table | domains.dom_table_14 | 2 | val | 50 | 201 + domains.dom_table | domains.dom_table_3 | 2 | val | 201 | 301 + domains.dom_table | domains.dom_table_4 | 2 | val | 301 | 401 + domains.dom_table | domains.dom_table_5 | 2 | val | 401 | 501 + domains.dom_table | domains.dom_table_6 | 2 | val | 501 | 601 + domains.dom_table | domains.dom_table_7 | 2 | val | 601 | 701 + domains.dom_table | domains.dom_table_8 | 2 | val | 701 | 801 + domains.dom_table | domains.dom_table_9 | 2 | val | 801 | 901 + domains.dom_table | domains.dom_table_10 | 2 | val | 901 | 1001 + domains.dom_table | domains.dom_table_12 | 2 | val | 1001 | 1101 + domains.dom_table | domains.dom_table_15 | 2 | val | 1101 | 1201 +(14 rows) + +SELECT drop_partitions('domains.dom_table'); +NOTICE: 49 rows copied from domains.dom_table_1 +NOTICE: 100 rows copied from domains.dom_table_3 +NOTICE: 100 rows copied from domains.dom_table_4 +NOTICE: 100 rows copied from domains.dom_table_5 +NOTICE: 100 rows copied from domains.dom_table_6 +NOTICE: 100 rows copied from domains.dom_table_7 +NOTICE: 100 rows copied from domains.dom_table_8 +NOTICE: 100 rows copied from domains.dom_table_9 +NOTICE: 99 rows copied from domains.dom_table_10 +NOTICE: 1 rows copied from domains.dom_table_11 +NOTICE: 0 rows copied from domains.dom_table_12 +NOTICE: 0 rows copied from domains.dom_table_13 +NOTICE: 151 rows copied from domains.dom_table_14 +NOTICE: 1 rows copied from domains.dom_table_15 + drop_partitions +----------------- + 14 +(1 row) + +SELECT create_hash_partitions('domains.dom_table', 'val', 5); + create_hash_partitions +------------------------ + 5 +(1 row) + +SELECT * FROM pathman_partition_list +ORDER BY "partition"::TEXT; + parent | partition | parttype | expr | range_min | range_max +-------------------+---------------------+----------+------+-----------+----------- + domains.dom_table | domains.dom_table_0 | 1 | val | | + domains.dom_table | domains.dom_table_1 | 1 | val | | + domains.dom_table | domains.dom_table_2 | 1 | val | | + domains.dom_table | domains.dom_table_3 | 1 | val | | + domains.dom_table | domains.dom_table_4 | 1 | val | | +(5 rows) + +DROP TABLE domains.dom_table CASCADE; +NOTICE: drop cascades to 5 other objects +DROP DOMAIN domains.dom_test CASCADE; +DROP SCHEMA domains; +DROP EXTENSION pg_pathman CASCADE; diff --git a/expected/pathman_dropped_cols.out b/expected/pathman_dropped_cols.out index 79e781b2..826931d3 100644 --- a/expected/pathman_dropped_cols.out +++ b/expected/pathman_dropped_cols.out @@ -183,27 +183,27 @@ EXECUTE getbyroot(2); 4 | 2 | 10-10-2010 | num_2 | 1 | | | 3 (2 rows) -EXPLAIN EXECUTE getbyroot(2); - QUERY PLAN --------------------------------------------------------------------------------------------- - Custom Scan (RuntimeAppend) (cost=4.17..11.28 rows=3 width=128) +EXPLAIN (COSTS OFF) EXECUTE getbyroot(2); + QUERY PLAN +---------------------------------------------------------- + Custom Scan (RuntimeAppend) Prune by: (root_dict.root_id = $1) - -> Bitmap Heap Scan on root_dict_0 root_dict (cost=4.17..11.28 rows=3 width=128) + -> Bitmap Heap Scan on root_dict_0 root_dict Recheck Cond: (root_id = $1) - -> Bitmap Index Scan on root_dict_0_root_id_idx (cost=0.00..4.17 rows=3 width=0) + -> Bitmap Index Scan on root_dict_0_root_id_idx Index Cond: (root_id = $1) - -> Bitmap Heap Scan on root_dict_1 root_dict (cost=4.17..11.28 rows=3 width=128) + -> Bitmap Heap Scan on root_dict_1 root_dict Recheck Cond: (root_id = $1) - -> Bitmap Index Scan on root_dict_1_root_id_idx (cost=0.00..4.17 rows=3 width=0) + -> Bitmap Index Scan on root_dict_1_root_id_idx Index Cond: (root_id = $1) - -> Bitmap Heap Scan on root_dict_2 root_dict (cost=4.17..11.28 rows=3 width=128) + -> Bitmap Heap Scan on root_dict_2 root_dict Recheck Cond: (root_id = $1) - -> Bitmap Index Scan on root_dict_2_root_id_idx (cost=0.00..4.17 rows=3 width=0) + -> Bitmap Index Scan on root_dict_2_root_id_idx Index Cond: (root_id = $1) (14 rows) DEALLOCATE getbyroot; DROP TABLE root_dict CASCADE; NOTICE: drop cascades to 3 other objects -DROP SCHEMA dropped_cols CASCADE; +DROP SCHEMA dropped_cols; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_expressions.out b/expected/pathman_expressions.out index 1db38acb..cd629b8e 100644 --- a/expected/pathman_expressions.out +++ b/expected/pathman_expressions.out @@ -430,6 +430,12 @@ EXPLAIN (COSTS OFF) SELECT * FROM test_exprs.range_rel WHERE (AGE(dt, '2000-01-0 Filter: (age(dt, 'Sat Jan 01 00:00:00 2000'::timestamp without time zone) = '@ 18 years'::interval) (3 rows) -DROP SCHEMA test_exprs CASCADE; -NOTICE: drop cascades to 24 other objects +DROP TABLE test_exprs.canary CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test_exprs.canary_copy CASCADE; +DROP TABLE test_exprs.range_rel CASCADE; +NOTICE: drop cascades to 11 other objects +DROP TABLE test_exprs.hash_rel CASCADE; +NOTICE: drop cascades to 4 other objects +DROP SCHEMA test_exprs; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_expressions_1.out b/expected/pathman_expressions_1.out index 126534a0..66e3ea75 100644 --- a/expected/pathman_expressions_1.out +++ b/expected/pathman_expressions_1.out @@ -434,6 +434,12 @@ EXPLAIN (COSTS OFF) SELECT * FROM test_exprs.range_rel WHERE (AGE(dt, '2000-01-0 Filter: (age(dt, 'Sat Jan 01 00:00:00 2000'::timestamp without time zone) = '@ 18 years'::interval) (3 rows) -DROP SCHEMA test_exprs CASCADE; -NOTICE: drop cascades to 24 other objects +DROP TABLE test_exprs.canary CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test_exprs.canary_copy CASCADE; +DROP TABLE test_exprs.range_rel CASCADE; +NOTICE: drop cascades to 11 other objects +DROP TABLE test_exprs.hash_rel CASCADE; +NOTICE: drop cascades to 4 other objects +DROP SCHEMA test_exprs; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_expressions_2.out b/expected/pathman_expressions_2.out index 83b0c7b0..89bf24ef 100644 --- a/expected/pathman_expressions_2.out +++ b/expected/pathman_expressions_2.out @@ -425,6 +425,12 @@ EXPLAIN (COSTS OFF) SELECT * FROM test_exprs.range_rel WHERE (AGE(dt, '2000-01-0 Filter: (age(dt, 'Sat Jan 01 00:00:00 2000'::timestamp without time zone) = '@ 18 years'::interval) (2 rows) -DROP SCHEMA test_exprs CASCADE; -NOTICE: drop cascades to 24 other objects +DROP TABLE test_exprs.canary CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test_exprs.canary_copy CASCADE; +DROP TABLE test_exprs.range_rel CASCADE; +NOTICE: drop cascades to 11 other objects +DROP TABLE test_exprs.hash_rel CASCADE; +NOTICE: drop cascades to 4 other objects +DROP SCHEMA test_exprs; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_expressions_3.out b/expected/pathman_expressions_3.out new file mode 100644 index 00000000..eacb1009 --- /dev/null +++ b/expected/pathman_expressions_3.out @@ -0,0 +1,436 @@ +/* + * ------------------------------------------- + * NOTE: This test behaves differenly on < 11 because planner now turns + * Row(Const, Const) into just Const of record type, apparently since 3decd150 + * ------------------------------------------- + * + * Also since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated, + * causing different output; pathman_expressions_2.out is the updated version. + */ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE EXTENSION pg_pathman; +CREATE SCHEMA test_exprs; +/* + * Test partitioning expression canonicalization process + */ +CREATE TABLE test_exprs.canon(c JSONB NOT NULL); +SELECT create_range_partitions('test_exprs.canon', '(C->>''key'')::int8', 1, 10, 2); + create_range_partitions +------------------------- + 2 +(1 row) + +SELECT expr FROM pathman_config; /* check expression */ + expr +------------------------------- + ((c ->> 'key'::text))::bigint +(1 row) + +INSERT INTO test_exprs.canon VALUES ('{ "key": 2, "value": 0 }'); +SELECT *, tableoid::REGCLASS FROM test_exprs.canon; + c | tableoid +------------------------+-------------------- + {"key": 2, "value": 0} | test_exprs.canon_1 +(1 row) + +DROP TABLE test_exprs.canon CASCADE; +NOTICE: drop cascades to 3 other objects +CREATE TABLE test_exprs.canon(val TEXT NOT NULL); +CREATE SEQUENCE test_exprs.canon_seq; +SELECT add_to_pathman_config('test_exprs.canon', 'VAL collate "C"', NULL); + add_to_pathman_config +----------------------- + t +(1 row) + +SELECT add_range_partition('test_exprs.canon', 'a'::TEXT, 'b'); + add_range_partition +--------------------- + test_exprs.canon_1 +(1 row) + +SELECT add_range_partition('test_exprs.canon', 'b'::TEXT, 'c'); + add_range_partition +--------------------- + test_exprs.canon_2 +(1 row) + +SELECT add_range_partition('test_exprs.canon', 'c'::TEXT, 'd'); + add_range_partition +--------------------- + test_exprs.canon_3 +(1 row) + +SELECT add_range_partition('test_exprs.canon', 'd'::TEXT, 'e'); + add_range_partition +--------------------- + test_exprs.canon_4 +(1 row) + +SELECT expr FROM pathman_config; /* check expression */ + expr +------------------- + (val COLLATE "C") +(1 row) + +INSERT INTO test_exprs.canon VALUES ('b'); +SELECT *, tableoid::REGCLASS FROM test_exprs.canon; + val | tableoid +-----+-------------------- + b | test_exprs.canon_2 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM test_exprs.canon WHERE val COLLATE "C" < ALL (array['b', 'c']); + QUERY PLAN +--------------------------- + Seq Scan on canon_1 canon +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM test_exprs.canon WHERE val COLLATE "POSIX" < ALL (array['b', 'c']); + QUERY PLAN +----------------------------------------------------------- + Append + -> Seq Scan on canon_1 + Filter: ((val)::text < 'b'::text COLLATE "POSIX") + -> Seq Scan on canon_2 + Filter: ((val)::text < 'b'::text COLLATE "POSIX") + -> Seq Scan on canon_3 + Filter: ((val)::text < 'b'::text COLLATE "POSIX") + -> Seq Scan on canon_4 + Filter: ((val)::text < 'b'::text COLLATE "POSIX") +(9 rows) + +DROP TABLE test_exprs.canon CASCADE; +NOTICE: drop cascades to 5 other objects +/* + * Test composite key. + */ +CREATE TABLE test_exprs.composite(a INT4 NOT NULL, b TEXT NOT NULL); +CREATE SEQUENCE test_exprs.composite_seq; +SELECT add_to_pathman_config('test_exprs.composite', + '(a, b)::test_exprs.composite', + NULL); + add_to_pathman_config +----------------------- + t +(1 row) + +SELECT add_range_partition('test_exprs.composite', + '(1,a)'::test_exprs.composite, + '(10,a)'::test_exprs.composite); + add_range_partition +------------------------ + test_exprs.composite_1 +(1 row) + +SELECT add_range_partition('test_exprs.composite', + '(10,a)'::test_exprs.composite, + '(20,a)'::test_exprs.composite); + add_range_partition +------------------------ + test_exprs.composite_2 +(1 row) + +SELECT add_range_partition('test_exprs.composite', + '(20,a)'::test_exprs.composite, + '(30,a)'::test_exprs.composite); + add_range_partition +------------------------ + test_exprs.composite_3 +(1 row) + +SELECT add_range_partition('test_exprs.composite', + '(30,a)'::test_exprs.composite, + '(40,a)'::test_exprs.composite); + add_range_partition +------------------------ + test_exprs.composite_4 +(1 row) + +SELECT expr FROM pathman_config; /* check expression */ + expr +--------------------------------- + ROW(a, b)::test_exprs.composite +(1 row) + +INSERT INTO test_exprs.composite VALUES(2, 'a'); +INSERT INTO test_exprs.composite VALUES(11, 'a'); +INSERT INTO test_exprs.composite VALUES(2, 'b'); +INSERT INTO test_exprs.composite VALUES(50, 'b'); +ERROR: cannot spawn new partition for key '(50,b)' +SELECT *, tableoid::REGCLASS FROM test_exprs.composite; + a | b | tableoid +----+---+------------------------ + 2 | a | test_exprs.composite_1 + 2 | b | test_exprs.composite_1 + 11 | a | test_exprs.composite_2 +(3 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_exprs.composite WHERE (a, b)::test_exprs.composite < (21, 0)::test_exprs.composite; + QUERY PLAN +------------------------------------------------------------------------------------ + Append + -> Seq Scan on composite_1 + -> Seq Scan on composite_2 + -> Seq Scan on composite_3 + Filter: (ROW(a, b)::test_exprs.composite < '(21,0)'::test_exprs.composite) +(5 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_exprs.composite WHERE (a, b) < (21, 0)::test_exprs.composite; + QUERY PLAN +-------------------------------------------------------------- + Append + -> Seq Scan on composite_1 + Filter: (ROW(a, b) < '(21,0)'::test_exprs.composite) + -> Seq Scan on composite_2 + Filter: (ROW(a, b) < '(21,0)'::test_exprs.composite) + -> Seq Scan on composite_3 + Filter: (ROW(a, b) < '(21,0)'::test_exprs.composite) + -> Seq Scan on composite_4 + Filter: (ROW(a, b) < '(21,0)'::test_exprs.composite) +(9 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_exprs.composite WHERE (a, b)::test_exprs.composite < (21, 0); + QUERY PLAN +---------------------------------------------------------------------- + Append + -> Seq Scan on composite_1 + -> Seq Scan on composite_2 + -> Seq Scan on composite_3 + Filter: (ROW(a, b)::test_exprs.composite < '(21,0)'::record) +(5 rows) + +DROP TABLE test_exprs.composite CASCADE; +NOTICE: drop cascades to 5 other objects +/* We use this rel to check 'pathman_hooks_enabled' */ +CREATE TABLE test_exprs.canary(val INT4 NOT NULL); +CREATE TABLE test_exprs.canary_copy (LIKE test_exprs.canary); +SELECT create_hash_partitions('test_exprs.canary', 'val', 5); + create_hash_partitions +------------------------ + 5 +(1 row) + +/* + * Test HASH + */ +CREATE TABLE test_exprs.hash_rel ( + id SERIAL PRIMARY KEY, + value INTEGER NOT NULL, + value2 INTEGER NOT NULL +); +INSERT INTO test_exprs.hash_rel (value, value2) + SELECT val, val * 2 FROM generate_series(1, 5) val; +SELECT COUNT(*) FROM test_exprs.hash_rel; + count +------- + 5 +(1 row) + +\set VERBOSITY default +/* Try using constant expression */ +SELECT create_hash_partitions('test_exprs.hash_rel', '1 + 1', 4); +ERROR: failed to analyze partitioning expression "1 + 1" +DETAIL: partitioning expression should reference table "hash_rel" +CONTEXT: SQL statement "SELECT public.validate_expression(parent_relid, expression)" +PL/pgSQL function prepare_for_partitioning(regclass,text,boolean) line 9 at PERFORM +SQL statement "SELECT public.prepare_for_partitioning(parent_relid, + expression, + partition_data)" +PL/pgSQL function create_hash_partitions(regclass,text,integer,boolean,text[],text[]) line 3 at PERFORM +/* Try using system attributes */ +SELECT create_hash_partitions('test_exprs.hash_rel', 'xmin', 4); +ERROR: failed to analyze partitioning expression "xmin" +DETAIL: system attributes are not supported +CONTEXT: SQL statement "SELECT public.validate_expression(parent_relid, expression)" +PL/pgSQL function prepare_for_partitioning(regclass,text,boolean) line 9 at PERFORM +SQL statement "SELECT public.prepare_for_partitioning(parent_relid, + expression, + partition_data)" +PL/pgSQL function create_hash_partitions(regclass,text,integer,boolean,text[],text[]) line 3 at PERFORM +/* Try using subqueries */ +SELECT create_hash_partitions('test_exprs.hash_rel', + 'value, (select oid from pg_class limit 1)', + 4); +ERROR: failed to analyze partitioning expression "value, (select oid from pg_class limit 1)" +DETAIL: subqueries are not allowed in partitioning expression +CONTEXT: SQL statement "SELECT public.validate_expression(parent_relid, expression)" +PL/pgSQL function prepare_for_partitioning(regclass,text,boolean) line 9 at PERFORM +SQL statement "SELECT public.prepare_for_partitioning(parent_relid, + expression, + partition_data)" +PL/pgSQL function create_hash_partitions(regclass,text,integer,boolean,text[],text[]) line 3 at PERFORM +/* Try using mutable expression */ +SELECT create_hash_partitions('test_exprs.hash_rel', 'random()', 4); +ERROR: failed to analyze partitioning expression "random()" +DETAIL: functions in partitioning expression must be marked IMMUTABLE +CONTEXT: SQL statement "SELECT public.validate_expression(parent_relid, expression)" +PL/pgSQL function prepare_for_partitioning(regclass,text,boolean) line 9 at PERFORM +SQL statement "SELECT public.prepare_for_partitioning(parent_relid, + expression, + partition_data)" +PL/pgSQL function create_hash_partitions(regclass,text,integer,boolean,text[],text[]) line 3 at PERFORM +/* Try using broken parentheses */ +SELECT create_hash_partitions('test_exprs.hash_rel', 'value * value2))', 4); +ERROR: failed to parse partitioning expression "value * value2))" +DETAIL: syntax error at or near ")" +QUERY: SELECT public.validate_expression(parent_relid, expression) +CONTEXT: PL/pgSQL function prepare_for_partitioning(regclass,text,boolean) line 9 at PERFORM +SQL statement "SELECT public.prepare_for_partitioning(parent_relid, + expression, + partition_data)" +PL/pgSQL function create_hash_partitions(regclass,text,integer,boolean,text[],text[]) line 3 at PERFORM +/* Try using missing columns */ +SELECT create_hash_partitions('test_exprs.hash_rel', 'value * value3', 4); +ERROR: failed to analyze partitioning expression "value * value3" +DETAIL: column "value3" does not exist +HINT: Perhaps you meant to reference the column "hash_rel.value" or the column "hash_rel.value2". +QUERY: SELECT public.validate_expression(parent_relid, expression) +CONTEXT: PL/pgSQL function prepare_for_partitioning(regclass,text,boolean) line 9 at PERFORM +SQL statement "SELECT public.prepare_for_partitioning(parent_relid, + expression, + partition_data)" +PL/pgSQL function create_hash_partitions(regclass,text,integer,boolean,text[],text[]) line 3 at PERFORM +/* Check that 'pathman_hooks_enabled' is true (1 partition in plan) */ +EXPLAIN (COSTS OFF) INSERT INTO test_exprs.canary_copy +SELECT * FROM test_exprs.canary WHERE val = 1; + QUERY PLAN +----------------------------------- + Insert on canary_copy + -> Seq Scan on canary_0 canary + Filter: (val = 1) +(3 rows) + +\set VERBOSITY terse +SELECT create_hash_partitions('test_exprs.hash_rel', 'value * value2', 4); + create_hash_partitions +------------------------ + 4 +(1 row) + +SELECT COUNT(*) FROM ONLY test_exprs.hash_rel; + count +------- + 0 +(1 row) + +SELECT COUNT(*) FROM test_exprs.hash_rel; + count +------- + 5 +(1 row) + +INSERT INTO test_exprs.hash_rel (value, value2) + SELECT val, val * 2 FROM generate_series(6, 10) val; +SELECT COUNT(*) FROM ONLY test_exprs.hash_rel; + count +------- + 0 +(1 row) + +SELECT COUNT(*) FROM test_exprs.hash_rel; + count +------- + 10 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM test_exprs.hash_rel WHERE value = 5; + QUERY PLAN +----------------------------------------- + Append + -> Seq Scan on hash_rel_0 hash_rel_1 + Filter: (value = 5) + -> Seq Scan on hash_rel_1 hash_rel_2 + Filter: (value = 5) + -> Seq Scan on hash_rel_2 hash_rel_3 + Filter: (value = 5) + -> Seq Scan on hash_rel_3 hash_rel_4 + Filter: (value = 5) +(9 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_exprs.hash_rel WHERE (value * value2) = 5; + QUERY PLAN +---------------------------------- + Seq Scan on hash_rel_0 hash_rel + Filter: ((value * value2) = 5) +(2 rows) + +/* + * Test RANGE + */ +CREATE TABLE test_exprs.range_rel (id SERIAL PRIMARY KEY, dt TIMESTAMP NOT NULL, txt TEXT); +INSERT INTO test_exprs.range_rel (dt, txt) +SELECT g, md5(g::TEXT) FROM generate_series('2015-01-01', '2020-04-30', '1 month'::interval) as g; +\set VERBOSITY default +/* Try using constant expression */ +SELECT create_range_partitions('test_exprs.range_rel', '''16 years''::interval', + '15 years'::INTERVAL, '1 year'::INTERVAL, 10); +ERROR: failed to analyze partitioning expression "'16 years'::interval" +DETAIL: partitioning expression should reference table "range_rel" +CONTEXT: SQL statement "SELECT public.validate_expression(parent_relid, expression)" +PL/pgSQL function prepare_for_partitioning(regclass,text,boolean) line 9 at PERFORM +SQL statement "SELECT public.prepare_for_partitioning(parent_relid, + expression, + partition_data)" +PL/pgSQL function create_range_partitions(regclass,text,anyelement,interval,integer,boolean) line 11 at PERFORM +/* Try using mutable expression */ +SELECT create_range_partitions('test_exprs.range_rel', 'RANDOM()', + '15 years'::INTERVAL, '1 year'::INTERVAL, 10); +ERROR: failed to analyze partitioning expression "RANDOM()" +DETAIL: functions in partitioning expression must be marked IMMUTABLE +CONTEXT: SQL statement "SELECT public.validate_expression(parent_relid, expression)" +PL/pgSQL function prepare_for_partitioning(regclass,text,boolean) line 9 at PERFORM +SQL statement "SELECT public.prepare_for_partitioning(parent_relid, + expression, + partition_data)" +PL/pgSQL function create_range_partitions(regclass,text,anyelement,interval,integer,boolean) line 11 at PERFORM +/* Check that 'pathman_hooks_enabled' is true (1 partition in plan) */ +EXPLAIN (COSTS OFF) INSERT INTO test_exprs.canary_copy +SELECT * FROM test_exprs.canary WHERE val = 1; + QUERY PLAN +----------------------------------- + Insert on canary_copy + -> Seq Scan on canary_0 canary + Filter: (val = 1) +(3 rows) + +\set VERBOSITY terse +SELECT create_range_partitions('test_exprs.range_rel', 'AGE(dt, ''2000-01-01''::DATE)', + '15 years'::INTERVAL, '1 year'::INTERVAL, 10); + create_range_partitions +------------------------- + 10 +(1 row) + +INSERT INTO test_exprs.range_rel_1 (dt, txt) VALUES ('2020-01-01'::DATE, md5('asdf')); +ERROR: new row for relation "range_rel_1" violates check constraint "pathman_range_rel_1_check" +SELECT COUNT(*) FROM test_exprs.range_rel_6; + count +------- + 4 +(1 row) + +INSERT INTO test_exprs.range_rel_6 (dt, txt) VALUES ('2020-01-01'::DATE, md5('asdf')); +SELECT COUNT(*) FROM test_exprs.range_rel_6; + count +------- + 5 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM test_exprs.range_rel WHERE (AGE(dt, '2000-01-01'::DATE)) = '18 years'::interval; + QUERY PLAN +------------------------------------------------------------------------------------------------------- + Seq Scan on range_rel_4 range_rel + Filter: (age(dt, 'Sat Jan 01 00:00:00 2000'::timestamp without time zone) = '@ 18 years'::interval) +(2 rows) + +DROP TABLE test_exprs.canary CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test_exprs.canary_copy CASCADE; +DROP TABLE test_exprs.range_rel CASCADE; +NOTICE: drop cascades to 11 other objects +DROP TABLE test_exprs.hash_rel CASCADE; +NOTICE: drop cascades to 4 other objects +DROP SCHEMA test_exprs; +DROP EXTENSION pg_pathman; diff --git a/expected/pathman_foreign_keys.out b/expected/pathman_foreign_keys.out index 2ff12279..34fc75ad 100644 --- a/expected/pathman_foreign_keys.out +++ b/expected/pathman_foreign_keys.out @@ -90,6 +90,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM fkeys.messages; DROP TABLE fkeys.messages, fkeys.replies CASCADE; NOTICE: drop cascades to 3 other objects -DROP SCHEMA fkeys CASCADE; -NOTICE: drop cascades to 2 other objects +DROP TABLE fkeys.test_fkey CASCADE; +DROP TABLE fkeys.test_ref CASCADE; +DROP SCHEMA fkeys; DROP EXTENSION pg_pathman CASCADE; diff --git a/expected/pathman_gaps.out b/expected/pathman_gaps.out index 1d9b1f33..530beca9 100644 --- a/expected/pathman_gaps.out +++ b/expected/pathman_gaps.out @@ -822,6 +822,13 @@ EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val >= 51; -> Seq Scan on test_4_11 (7 rows) -DROP SCHEMA gaps CASCADE; -NOTICE: drop cascades to 30 other objects +DROP TABLE gaps.test_1 CASCADE; +NOTICE: drop cascades to 3 other objects +DROP TABLE gaps.test_2 CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE gaps.test_3 CASCADE; +NOTICE: drop cascades to 8 other objects +DROP TABLE gaps.test_4 CASCADE; +NOTICE: drop cascades to 10 other objects +DROP SCHEMA gaps; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_gaps_1.out b/expected/pathman_gaps_1.out index d6e1973d..b1c0ac34 100644 --- a/expected/pathman_gaps_1.out +++ b/expected/pathman_gaps_1.out @@ -807,6 +807,13 @@ EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val >= 51; -> Seq Scan on test_4_11 (7 rows) -DROP SCHEMA gaps CASCADE; -NOTICE: drop cascades to 30 other objects +DROP TABLE gaps.test_1 CASCADE; +NOTICE: drop cascades to 3 other objects +DROP TABLE gaps.test_2 CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE gaps.test_3 CASCADE; +NOTICE: drop cascades to 8 other objects +DROP TABLE gaps.test_4 CASCADE; +NOTICE: drop cascades to 10 other objects +DROP SCHEMA gaps; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_gaps_2.out b/expected/pathman_gaps_2.out new file mode 100644 index 00000000..b229be66 --- /dev/null +++ b/expected/pathman_gaps_2.out @@ -0,0 +1,819 @@ +/* + * Since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated, + * causing different output; pathman_gaps_1.out is the updated version. + */ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE EXTENSION pg_pathman; +CREATE SCHEMA gaps; +CREATE TABLE gaps.test_1(val INT8 NOT NULL); +SELECT create_range_partitions('gaps.test_1', 'val', 1, 10, 3); + create_range_partitions +------------------------- + 3 +(1 row) + +DROP TABLE gaps.test_1_2; +CREATE TABLE gaps.test_2(val INT8 NOT NULL); +SELECT create_range_partitions('gaps.test_2', 'val', 1, 10, 5); + create_range_partitions +------------------------- + 5 +(1 row) + +DROP TABLE gaps.test_2_3; +CREATE TABLE gaps.test_3(val INT8 NOT NULL); +SELECT create_range_partitions('gaps.test_3', 'val', 1, 10, 8); + create_range_partitions +------------------------- + 8 +(1 row) + +DROP TABLE gaps.test_3_4; +CREATE TABLE gaps.test_4(val INT8 NOT NULL); +SELECT create_range_partitions('gaps.test_4', 'val', 1, 10, 11); + create_range_partitions +------------------------- + 11 +(1 row) + +DROP TABLE gaps.test_4_4; +DROP TABLE gaps.test_4_5; +/* Check existing partitions */ +SELECT * FROM pathman_partition_list ORDER BY parent, partition; + parent | partition | parttype | expr | range_min | range_max +-------------+----------------+----------+------+-----------+----------- + gaps.test_1 | gaps.test_1_1 | 2 | val | 1 | 11 + gaps.test_1 | gaps.test_1_3 | 2 | val | 21 | 31 + gaps.test_2 | gaps.test_2_1 | 2 | val | 1 | 11 + gaps.test_2 | gaps.test_2_2 | 2 | val | 11 | 21 + gaps.test_2 | gaps.test_2_4 | 2 | val | 31 | 41 + gaps.test_2 | gaps.test_2_5 | 2 | val | 41 | 51 + gaps.test_3 | gaps.test_3_1 | 2 | val | 1 | 11 + gaps.test_3 | gaps.test_3_2 | 2 | val | 11 | 21 + gaps.test_3 | gaps.test_3_3 | 2 | val | 21 | 31 + gaps.test_3 | gaps.test_3_5 | 2 | val | 41 | 51 + gaps.test_3 | gaps.test_3_6 | 2 | val | 51 | 61 + gaps.test_3 | gaps.test_3_7 | 2 | val | 61 | 71 + gaps.test_3 | gaps.test_3_8 | 2 | val | 71 | 81 + gaps.test_4 | gaps.test_4_1 | 2 | val | 1 | 11 + gaps.test_4 | gaps.test_4_2 | 2 | val | 11 | 21 + gaps.test_4 | gaps.test_4_3 | 2 | val | 21 | 31 + gaps.test_4 | gaps.test_4_6 | 2 | val | 51 | 61 + gaps.test_4 | gaps.test_4_7 | 2 | val | 61 | 71 + gaps.test_4 | gaps.test_4_8 | 2 | val | 71 | 81 + gaps.test_4 | gaps.test_4_9 | 2 | val | 81 | 91 + gaps.test_4 | gaps.test_4_10 | 2 | val | 91 | 101 + gaps.test_4 | gaps.test_4_11 | 2 | val | 101 | 111 +(22 rows) + +/* Pivot values */ +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_1 WHERE val = 11; + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_1 WHERE val = 16; + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_1 WHERE val = 21; + QUERY PLAN +----------------------------- + Seq Scan on test_1_3 test_1 + Filter: (val = 21) +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_1 WHERE val < 11; + QUERY PLAN +----------------------------- + Seq Scan on test_1_1 test_1 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_1 WHERE val <= 11; + QUERY PLAN +----------------------------- + Seq Scan on test_1_1 test_1 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_1 WHERE val < 16; + QUERY PLAN +----------------------------- + Seq Scan on test_1_1 test_1 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_1 WHERE val <= 16; + QUERY PLAN +----------------------------- + Seq Scan on test_1_1 test_1 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_1 WHERE val < 21; + QUERY PLAN +----------------------------- + Seq Scan on test_1_1 test_1 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_1 WHERE val <= 21; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_1_1 + -> Seq Scan on test_1_3 test_1_2 + Filter: (val <= 21) +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_1 WHERE val > 11; + QUERY PLAN +----------------------------- + Seq Scan on test_1_3 test_1 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_1 WHERE val >= 11; + QUERY PLAN +----------------------------- + Seq Scan on test_1_3 test_1 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_1 WHERE val > 16; + QUERY PLAN +----------------------------- + Seq Scan on test_1_3 test_1 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_1 WHERE val >= 16; + QUERY PLAN +----------------------------- + Seq Scan on test_1_3 test_1 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_1 WHERE val > 21; + QUERY PLAN +----------------------------- + Seq Scan on test_1_3 test_1 + Filter: (val > 21) +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_1 WHERE val >= 21; + QUERY PLAN +----------------------------- + Seq Scan on test_1_3 test_1 +(1 row) + +/* Pivot values */ +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_2 WHERE val = 21; + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_2 WHERE val = 26; + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_2 WHERE val = 31; + QUERY PLAN +----------------------------- + Seq Scan on test_2_4 test_2 + Filter: (val = 31) +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_2 WHERE val < 21; + QUERY PLAN +---------------------------- + Append + -> Seq Scan on test_2_1 + -> Seq Scan on test_2_2 +(3 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_2 WHERE val <= 21; + QUERY PLAN +---------------------------- + Append + -> Seq Scan on test_2_1 + -> Seq Scan on test_2_2 +(3 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_2 WHERE val < 26; + QUERY PLAN +---------------------------- + Append + -> Seq Scan on test_2_1 + -> Seq Scan on test_2_2 +(3 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_2 WHERE val <= 26; + QUERY PLAN +---------------------------- + Append + -> Seq Scan on test_2_1 + -> Seq Scan on test_2_2 +(3 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_2 WHERE val < 31; + QUERY PLAN +---------------------------- + Append + -> Seq Scan on test_2_1 + -> Seq Scan on test_2_2 +(3 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_2 WHERE val <= 31; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_2_1 + -> Seq Scan on test_2_2 + -> Seq Scan on test_2_4 test_2_3 + Filter: (val <= 31) +(5 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_2 WHERE val < 41; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_2_1 + -> Seq Scan on test_2_2 + -> Seq Scan on test_2_4 test_2_3 +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_2 WHERE val <= 41; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_2_1 + -> Seq Scan on test_2_2 + -> Seq Scan on test_2_4 test_2_3 + -> Seq Scan on test_2_5 test_2_4 + Filter: (val <= 41) +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_2 WHERE val > 11; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_2_2 test_2_1 + Filter: (val > 11) + -> Seq Scan on test_2_4 test_2_2 + -> Seq Scan on test_2_5 test_2_3 +(5 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_2 WHERE val >= 11; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_2_2 test_2_1 + -> Seq Scan on test_2_4 test_2_2 + -> Seq Scan on test_2_5 test_2_3 +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_2 WHERE val > 21; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_2_4 test_2_1 + -> Seq Scan on test_2_5 test_2_2 +(3 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_2 WHERE val >= 21; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_2_4 test_2_1 + -> Seq Scan on test_2_5 test_2_2 +(3 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_2 WHERE val > 26; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_2_4 test_2_1 + -> Seq Scan on test_2_5 test_2_2 +(3 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_2 WHERE val >= 26; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_2_4 test_2_1 + -> Seq Scan on test_2_5 test_2_2 +(3 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_2 WHERE val > 31; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_2_4 test_2_1 + Filter: (val > 31) + -> Seq Scan on test_2_5 test_2_2 +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_2 WHERE val >= 31; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_2_4 test_2_1 + -> Seq Scan on test_2_5 test_2_2 +(3 rows) + +/* Pivot values */ +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_3 WHERE val = 31; + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_3 WHERE val = 36; + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_3 WHERE val = 41; + QUERY PLAN +----------------------------- + Seq Scan on test_3_5 test_3 + Filter: (val = 41) +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_3 WHERE val < 31; + QUERY PLAN +---------------------------- + Append + -> Seq Scan on test_3_1 + -> Seq Scan on test_3_2 + -> Seq Scan on test_3_3 +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_3 WHERE val <= 31; + QUERY PLAN +---------------------------- + Append + -> Seq Scan on test_3_1 + -> Seq Scan on test_3_2 + -> Seq Scan on test_3_3 +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_3 WHERE val < 36; + QUERY PLAN +---------------------------- + Append + -> Seq Scan on test_3_1 + -> Seq Scan on test_3_2 + -> Seq Scan on test_3_3 +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_3 WHERE val <= 36; + QUERY PLAN +---------------------------- + Append + -> Seq Scan on test_3_1 + -> Seq Scan on test_3_2 + -> Seq Scan on test_3_3 +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_3 WHERE val < 41; + QUERY PLAN +---------------------------- + Append + -> Seq Scan on test_3_1 + -> Seq Scan on test_3_2 + -> Seq Scan on test_3_3 +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_3 WHERE val <= 41; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_3_1 + -> Seq Scan on test_3_2 + -> Seq Scan on test_3_3 + -> Seq Scan on test_3_5 test_3_4 + Filter: (val <= 41) +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_3 WHERE val < 51; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_3_1 + -> Seq Scan on test_3_2 + -> Seq Scan on test_3_3 + -> Seq Scan on test_3_5 test_3_4 +(5 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_3 WHERE val <= 51; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_3_1 + -> Seq Scan on test_3_2 + -> Seq Scan on test_3_3 + -> Seq Scan on test_3_5 test_3_4 + -> Seq Scan on test_3_6 test_3_5 + Filter: (val <= 51) +(7 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_3 WHERE val > 21; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_3_3 test_3_1 + Filter: (val > 21) + -> Seq Scan on test_3_5 test_3_2 + -> Seq Scan on test_3_6 test_3_3 + -> Seq Scan on test_3_7 test_3_4 + -> Seq Scan on test_3_8 test_3_5 +(7 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_3 WHERE val >= 21; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_3_3 test_3_1 + -> Seq Scan on test_3_5 test_3_2 + -> Seq Scan on test_3_6 test_3_3 + -> Seq Scan on test_3_7 test_3_4 + -> Seq Scan on test_3_8 test_3_5 +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_3 WHERE val > 31; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_3_5 test_3_1 + -> Seq Scan on test_3_6 test_3_2 + -> Seq Scan on test_3_7 test_3_3 + -> Seq Scan on test_3_8 test_3_4 +(5 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_3 WHERE val >= 31; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_3_5 test_3_1 + -> Seq Scan on test_3_6 test_3_2 + -> Seq Scan on test_3_7 test_3_3 + -> Seq Scan on test_3_8 test_3_4 +(5 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_3 WHERE val > 36; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_3_5 test_3_1 + -> Seq Scan on test_3_6 test_3_2 + -> Seq Scan on test_3_7 test_3_3 + -> Seq Scan on test_3_8 test_3_4 +(5 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_3 WHERE val >= 36; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_3_5 test_3_1 + -> Seq Scan on test_3_6 test_3_2 + -> Seq Scan on test_3_7 test_3_3 + -> Seq Scan on test_3_8 test_3_4 +(5 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_3 WHERE val > 41; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_3_5 test_3_1 + Filter: (val > 41) + -> Seq Scan on test_3_6 test_3_2 + -> Seq Scan on test_3_7 test_3_3 + -> Seq Scan on test_3_8 test_3_4 +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_3 WHERE val >= 41; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_3_5 test_3_1 + -> Seq Scan on test_3_6 test_3_2 + -> Seq Scan on test_3_7 test_3_3 + -> Seq Scan on test_3_8 test_3_4 +(5 rows) + +/* Pivot values */ +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val = 31; + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val = 36; + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val = 41; + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val = 46; + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val = 51; + QUERY PLAN +----------------------------- + Seq Scan on test_4_6 test_4 + Filter: (val = 51) +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val < 31; + QUERY PLAN +---------------------------- + Append + -> Seq Scan on test_4_1 + -> Seq Scan on test_4_2 + -> Seq Scan on test_4_3 +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val <= 31; + QUERY PLAN +---------------------------- + Append + -> Seq Scan on test_4_1 + -> Seq Scan on test_4_2 + -> Seq Scan on test_4_3 +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val < 36; + QUERY PLAN +---------------------------- + Append + -> Seq Scan on test_4_1 + -> Seq Scan on test_4_2 + -> Seq Scan on test_4_3 +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val <= 36; + QUERY PLAN +---------------------------- + Append + -> Seq Scan on test_4_1 + -> Seq Scan on test_4_2 + -> Seq Scan on test_4_3 +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val < 41; + QUERY PLAN +---------------------------- + Append + -> Seq Scan on test_4_1 + -> Seq Scan on test_4_2 + -> Seq Scan on test_4_3 +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val <= 41; + QUERY PLAN +---------------------------- + Append + -> Seq Scan on test_4_1 + -> Seq Scan on test_4_2 + -> Seq Scan on test_4_3 +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val < 46; + QUERY PLAN +---------------------------- + Append + -> Seq Scan on test_4_1 + -> Seq Scan on test_4_2 + -> Seq Scan on test_4_3 +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val <= 46; + QUERY PLAN +---------------------------- + Append + -> Seq Scan on test_4_1 + -> Seq Scan on test_4_2 + -> Seq Scan on test_4_3 +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val < 51; + QUERY PLAN +---------------------------- + Append + -> Seq Scan on test_4_1 + -> Seq Scan on test_4_2 + -> Seq Scan on test_4_3 +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val <= 51; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_4_1 + -> Seq Scan on test_4_2 + -> Seq Scan on test_4_3 + -> Seq Scan on test_4_6 test_4_4 + Filter: (val <= 51) +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val < 61; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_4_1 + -> Seq Scan on test_4_2 + -> Seq Scan on test_4_3 + -> Seq Scan on test_4_6 test_4_4 +(5 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val <= 61; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on test_4_1 + -> Seq Scan on test_4_2 + -> Seq Scan on test_4_3 + -> Seq Scan on test_4_6 test_4_4 + -> Seq Scan on test_4_7 test_4_5 + Filter: (val <= 61) +(7 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val > 21; + QUERY PLAN +-------------------------------------- + Append + -> Seq Scan on test_4_3 test_4_1 + Filter: (val > 21) + -> Seq Scan on test_4_6 test_4_2 + -> Seq Scan on test_4_7 test_4_3 + -> Seq Scan on test_4_8 test_4_4 + -> Seq Scan on test_4_9 test_4_5 + -> Seq Scan on test_4_10 test_4_6 + -> Seq Scan on test_4_11 test_4_7 +(9 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val >= 21; + QUERY PLAN +-------------------------------------- + Append + -> Seq Scan on test_4_3 test_4_1 + -> Seq Scan on test_4_6 test_4_2 + -> Seq Scan on test_4_7 test_4_3 + -> Seq Scan on test_4_8 test_4_4 + -> Seq Scan on test_4_9 test_4_5 + -> Seq Scan on test_4_10 test_4_6 + -> Seq Scan on test_4_11 test_4_7 +(8 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val > 31; + QUERY PLAN +-------------------------------------- + Append + -> Seq Scan on test_4_6 test_4_1 + -> Seq Scan on test_4_7 test_4_2 + -> Seq Scan on test_4_8 test_4_3 + -> Seq Scan on test_4_9 test_4_4 + -> Seq Scan on test_4_10 test_4_5 + -> Seq Scan on test_4_11 test_4_6 +(7 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val >= 31; + QUERY PLAN +-------------------------------------- + Append + -> Seq Scan on test_4_6 test_4_1 + -> Seq Scan on test_4_7 test_4_2 + -> Seq Scan on test_4_8 test_4_3 + -> Seq Scan on test_4_9 test_4_4 + -> Seq Scan on test_4_10 test_4_5 + -> Seq Scan on test_4_11 test_4_6 +(7 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val > 36; + QUERY PLAN +-------------------------------------- + Append + -> Seq Scan on test_4_6 test_4_1 + -> Seq Scan on test_4_7 test_4_2 + -> Seq Scan on test_4_8 test_4_3 + -> Seq Scan on test_4_9 test_4_4 + -> Seq Scan on test_4_10 test_4_5 + -> Seq Scan on test_4_11 test_4_6 +(7 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val >= 36; + QUERY PLAN +-------------------------------------- + Append + -> Seq Scan on test_4_6 test_4_1 + -> Seq Scan on test_4_7 test_4_2 + -> Seq Scan on test_4_8 test_4_3 + -> Seq Scan on test_4_9 test_4_4 + -> Seq Scan on test_4_10 test_4_5 + -> Seq Scan on test_4_11 test_4_6 +(7 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val > 41; + QUERY PLAN +-------------------------------------- + Append + -> Seq Scan on test_4_6 test_4_1 + -> Seq Scan on test_4_7 test_4_2 + -> Seq Scan on test_4_8 test_4_3 + -> Seq Scan on test_4_9 test_4_4 + -> Seq Scan on test_4_10 test_4_5 + -> Seq Scan on test_4_11 test_4_6 +(7 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val >= 41; + QUERY PLAN +-------------------------------------- + Append + -> Seq Scan on test_4_6 test_4_1 + -> Seq Scan on test_4_7 test_4_2 + -> Seq Scan on test_4_8 test_4_3 + -> Seq Scan on test_4_9 test_4_4 + -> Seq Scan on test_4_10 test_4_5 + -> Seq Scan on test_4_11 test_4_6 +(7 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val > 46; + QUERY PLAN +-------------------------------------- + Append + -> Seq Scan on test_4_6 test_4_1 + -> Seq Scan on test_4_7 test_4_2 + -> Seq Scan on test_4_8 test_4_3 + -> Seq Scan on test_4_9 test_4_4 + -> Seq Scan on test_4_10 test_4_5 + -> Seq Scan on test_4_11 test_4_6 +(7 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val >= 46; + QUERY PLAN +-------------------------------------- + Append + -> Seq Scan on test_4_6 test_4_1 + -> Seq Scan on test_4_7 test_4_2 + -> Seq Scan on test_4_8 test_4_3 + -> Seq Scan on test_4_9 test_4_4 + -> Seq Scan on test_4_10 test_4_5 + -> Seq Scan on test_4_11 test_4_6 +(7 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val > 51; + QUERY PLAN +-------------------------------------- + Append + -> Seq Scan on test_4_6 test_4_1 + Filter: (val > 51) + -> Seq Scan on test_4_7 test_4_2 + -> Seq Scan on test_4_8 test_4_3 + -> Seq Scan on test_4_9 test_4_4 + -> Seq Scan on test_4_10 test_4_5 + -> Seq Scan on test_4_11 test_4_6 +(8 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val >= 51; + QUERY PLAN +-------------------------------------- + Append + -> Seq Scan on test_4_6 test_4_1 + -> Seq Scan on test_4_7 test_4_2 + -> Seq Scan on test_4_8 test_4_3 + -> Seq Scan on test_4_9 test_4_4 + -> Seq Scan on test_4_10 test_4_5 + -> Seq Scan on test_4_11 test_4_6 +(7 rows) + +DROP TABLE gaps.test_1 CASCADE; +NOTICE: drop cascades to 3 other objects +DROP TABLE gaps.test_2 CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE gaps.test_3 CASCADE; +NOTICE: drop cascades to 8 other objects +DROP TABLE gaps.test_4 CASCADE; +NOTICE: drop cascades to 10 other objects +DROP SCHEMA gaps; +DROP EXTENSION pg_pathman; diff --git a/expected/pathman_hashjoin.out b/expected/pathman_hashjoin.out index 779efe3d..f5ebabdd 100644 --- a/expected/pathman_hashjoin.out +++ b/expected/pathman_hashjoin.out @@ -75,7 +75,10 @@ WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; -> Index Scan using range_rel_4_dt_idx on range_rel_4 j2_2 (20 rows) -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 12 other objects +DROP TABLE test.num_range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_hashjoin_1.out b/expected/pathman_hashjoin_1.out index ae1edda6..df6c0174 100644 --- a/expected/pathman_hashjoin_1.out +++ b/expected/pathman_hashjoin_1.out @@ -75,7 +75,10 @@ WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; -> Index Scan using range_rel_2_pkey on range_rel_2 j1_1 (20 rows) -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 12 other objects +DROP TABLE test.num_range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_hashjoin_2.out b/expected/pathman_hashjoin_2.out index 21cd1883..69ea5762 100644 --- a/expected/pathman_hashjoin_2.out +++ b/expected/pathman_hashjoin_2.out @@ -68,7 +68,10 @@ WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; Filter: (id IS NOT NULL) (13 rows) -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 12 other objects +DROP TABLE test.num_range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_hashjoin_3.out b/expected/pathman_hashjoin_3.out index 106e8c0e..e2c8903a 100644 --- a/expected/pathman_hashjoin_3.out +++ b/expected/pathman_hashjoin_3.out @@ -67,7 +67,10 @@ WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; Filter: (id IS NOT NULL) (12 rows) -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 12 other objects +DROP TABLE test.num_range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_hashjoin_4.out b/expected/pathman_hashjoin_4.out index ad4b5651..e827628f 100644 --- a/expected/pathman_hashjoin_4.out +++ b/expected/pathman_hashjoin_4.out @@ -54,11 +54,11 @@ WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; QUERY PLAN --------------------------------------------------------------------------------------- Sort - Sort Key: j2_1.dt + Sort Key: j2.dt -> Hash Join - Hash Cond: (j1_1.id = j2_1.id) + Hash Cond: (j1.id = j2.id) -> Hash Join - Hash Cond: (j3_1.id = j1_1.id) + Hash Cond: (j3.id = j1.id) -> Append -> Index Scan using num_range_rel_1_pkey on num_range_rel_1 j3_1 -> Index Scan using num_range_rel_2_pkey on num_range_rel_2 j3_2 @@ -75,7 +75,10 @@ WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; -> Index Scan using range_rel_4_dt_idx on range_rel_4 j2_3 (20 rows) -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 12 other objects +DROP TABLE test.num_range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_hashjoin_5.out b/expected/pathman_hashjoin_5.out index 7bbea061..c66a9306 100644 --- a/expected/pathman_hashjoin_5.out +++ b/expected/pathman_hashjoin_5.out @@ -56,7 +56,7 @@ WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; Sort Sort Key: j2.dt -> Hash Join - Hash Cond: (j3_1.id = j2.id) + Hash Cond: (j3.id = j2.id) -> Append -> Index Scan using num_range_rel_1_pkey on num_range_rel_1 j3_1 -> Index Scan using num_range_rel_2_pkey on num_range_rel_2 j3_2 @@ -67,7 +67,10 @@ WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; Filter: (id IS NOT NULL) (12 rows) -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 12 other objects +DROP TABLE test.num_range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_hashjoin_6.out b/expected/pathman_hashjoin_6.out new file mode 100644 index 00000000..1c57f49b --- /dev/null +++ b/expected/pathman_hashjoin_6.out @@ -0,0 +1,75 @@ +/* + * pathman_hashjoin_1.out and pathman_hashjoin_2.out seem to deal with pgpro's + * different behaviour. 8edd0e794 (>= 12) Append nodes with single subplan + * are eliminated, hence pathman_hashjoin_3.out + * + * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, + * now it includes aliases for inherited tables. + */ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE SCHEMA pathman; +CREATE EXTENSION pg_pathman SCHEMA pathman; +CREATE SCHEMA test; +CREATE TABLE test.range_rel ( + id SERIAL PRIMARY KEY, + dt TIMESTAMP NOT NULL, + txt TEXT); +CREATE INDEX ON test.range_rel (dt); +INSERT INTO test.range_rel (dt, txt) + SELECT g, md5(g::TEXT) FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) as g; +SELECT pathman.create_range_partitions('test.range_rel', 'DT', '2015-01-01'::DATE, '1 month'::INTERVAL); + create_range_partitions +------------------------- + 4 +(1 row) + +CREATE TABLE test.num_range_rel ( + id SERIAL PRIMARY KEY, + txt TEXT); +SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); + create_range_partitions +------------------------- + 4 +(1 row) + +INSERT INTO test.num_range_rel + SELECT g, md5(g::TEXT) FROM generate_series(1, 3000) as g; +SET pg_pathman.enable_runtimeappend = OFF; +SET pg_pathman.enable_runtimemergeappend = OFF; +VACUUM; +/* + * Hash join + */ +SET enable_indexscan = ON; +SET enable_seqscan = OFF; +SET enable_nestloop = OFF; +SET enable_hashjoin = ON; +SET enable_mergejoin = OFF; +EXPLAIN (COSTS OFF) +SELECT * FROM test.range_rel j1 +JOIN test.range_rel j2 on j2.id = j1.id +JOIN test.num_range_rel j3 on j3.id = j1.id +WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; + QUERY PLAN +--------------------------------------------------------------------------------- + Sort + Sort Key: j2.dt + -> Hash Join + Hash Cond: (j3.id = j2.id) + -> Append + -> Index Scan using num_range_rel_1_pkey on num_range_rel_1 j3_1 + -> Index Scan using num_range_rel_2_pkey on num_range_rel_2 j3_2 + -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 j3_3 + -> Index Scan using num_range_rel_4_pkey on num_range_rel_4 j3_4 + -> Hash + -> Index Scan using range_rel_2_dt_idx on range_rel_2 j2 +(11 rows) + +DROP TABLE test.num_range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP SCHEMA test; +DROP EXTENSION pg_pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_inserts.out b/expected/pathman_inserts.out index 225604c5..16656f18 100644 --- a/expected/pathman_inserts.out +++ b/expected/pathman_inserts.out @@ -1066,6 +1066,10 @@ SELECT count(*) FROM test_inserts.special_2; DROP TABLE test_inserts.special_2; DROP TABLE test_inserts.test_special_only CASCADE; NOTICE: drop cascades to 4 other objects -DROP SCHEMA test_inserts CASCADE; -NOTICE: drop cascades to 19 other objects +DROP TABLE test_inserts.storage CASCADE; +NOTICE: drop cascades to 15 other objects +DROP FUNCTION test_inserts.set_triggers(jsonb); +DROP FUNCTION test_inserts.print_cols_before_change(); +DROP FUNCTION test_inserts.print_cols_after_change(); +DROP SCHEMA test_inserts; DROP EXTENSION pg_pathman CASCADE; diff --git a/expected/pathman_inserts_1.out b/expected/pathman_inserts_1.out index a6634edd..3479c12d 100644 --- a/expected/pathman_inserts_1.out +++ b/expected/pathman_inserts_1.out @@ -1066,6 +1066,10 @@ SELECT count(*) FROM test_inserts.special_2; DROP TABLE test_inserts.special_2; DROP TABLE test_inserts.test_special_only CASCADE; NOTICE: drop cascades to 4 other objects -DROP SCHEMA test_inserts CASCADE; -NOTICE: drop cascades to 19 other objects +DROP TABLE test_inserts.storage CASCADE; +NOTICE: drop cascades to 15 other objects +DROP FUNCTION test_inserts.set_triggers(jsonb); +DROP FUNCTION test_inserts.print_cols_before_change(); +DROP FUNCTION test_inserts.print_cols_after_change(); +DROP SCHEMA test_inserts; DROP EXTENSION pg_pathman CASCADE; diff --git a/expected/pathman_inserts_2.out b/expected/pathman_inserts_2.out index 9a439010..3c31fc53 100644 --- a/expected/pathman_inserts_2.out +++ b/expected/pathman_inserts_2.out @@ -902,124 +902,124 @@ FROM generate_series(1, 10) i; EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO test_inserts.storage (b, d, e) SELECT b, d, e FROM test_inserts.storage; - QUERY PLAN ----------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------- Insert on test_inserts.storage -> Custom Scan (PartitionFilter) Output: NULL::integer, storage.b, NULL::integer, storage.d, storage.e -> Result - Output: NULL::integer, storage_11.b, NULL::integer, storage_11.d, storage_11.e + Output: NULL::integer, storage_1.b, NULL::integer, storage_1.d, storage_1.e -> Append - -> Seq Scan on test_inserts.storage_11 - Output: storage_11.b, storage_11.d, storage_11.e - -> Seq Scan on test_inserts.storage_1 storage_1_1 - Output: storage_1_1.b, storage_1_1.d, storage_1_1.e - -> Seq Scan on test_inserts.storage_2 + -> Seq Scan on test_inserts.storage_11 storage_2 Output: storage_2.b, storage_2.d, storage_2.e - -> Seq Scan on test_inserts.storage_3 + -> Seq Scan on test_inserts.storage_1 storage_3 Output: storage_3.b, storage_3.d, storage_3.e - -> Seq Scan on test_inserts.storage_4 + -> Seq Scan on test_inserts.storage_2 storage_4 Output: storage_4.b, storage_4.d, storage_4.e - -> Seq Scan on test_inserts.storage_5 + -> Seq Scan on test_inserts.storage_3 storage_5 Output: storage_5.b, storage_5.d, storage_5.e - -> Seq Scan on test_inserts.storage_6 + -> Seq Scan on test_inserts.storage_4 storage_6 Output: storage_6.b, storage_6.d, storage_6.e - -> Seq Scan on test_inserts.storage_7 + -> Seq Scan on test_inserts.storage_5 storage_7 Output: storage_7.b, storage_7.d, storage_7.e - -> Seq Scan on test_inserts.storage_8 + -> Seq Scan on test_inserts.storage_6 storage_8 Output: storage_8.b, storage_8.d, storage_8.e - -> Seq Scan on test_inserts.storage_9 + -> Seq Scan on test_inserts.storage_7 storage_9 Output: storage_9.b, storage_9.d, storage_9.e - -> Seq Scan on test_inserts.storage_10 + -> Seq Scan on test_inserts.storage_8 storage_10 Output: storage_10.b, storage_10.d, storage_10.e - -> Seq Scan on test_inserts.storage_12 + -> Seq Scan on test_inserts.storage_9 storage_11 + Output: storage_11.b, storage_11.d, storage_11.e + -> Seq Scan on test_inserts.storage_10 storage_12 Output: storage_12.b, storage_12.d, storage_12.e - -> Seq Scan on test_inserts.storage_13 + -> Seq Scan on test_inserts.storage_12 storage_13 Output: storage_13.b, storage_13.d, storage_13.e - -> Seq Scan on test_inserts.storage_14 + -> Seq Scan on test_inserts.storage_13 storage_14 Output: storage_14.b, storage_14.d, storage_14.e + -> Seq Scan on test_inserts.storage_14 storage_15 + Output: storage_15.b, storage_15.d, storage_15.e (34 rows) EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO test_inserts.storage (b, d) SELECT b, d FROM test_inserts.storage; - QUERY PLAN ----------------------------------------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------------------------------------- Insert on test_inserts.storage -> Custom Scan (PartitionFilter) Output: NULL::integer, storage.b, NULL::integer, storage.d, NULL::bigint -> Result - Output: NULL::integer, storage_11.b, NULL::integer, storage_11.d, NULL::bigint + Output: NULL::integer, storage_1.b, NULL::integer, storage_1.d, NULL::bigint -> Append - -> Seq Scan on test_inserts.storage_11 - Output: storage_11.b, storage_11.d - -> Seq Scan on test_inserts.storage_1 storage_1_1 - Output: storage_1_1.b, storage_1_1.d - -> Seq Scan on test_inserts.storage_2 + -> Seq Scan on test_inserts.storage_11 storage_2 Output: storage_2.b, storage_2.d - -> Seq Scan on test_inserts.storage_3 + -> Seq Scan on test_inserts.storage_1 storage_3 Output: storage_3.b, storage_3.d - -> Seq Scan on test_inserts.storage_4 + -> Seq Scan on test_inserts.storage_2 storage_4 Output: storage_4.b, storage_4.d - -> Seq Scan on test_inserts.storage_5 + -> Seq Scan on test_inserts.storage_3 storage_5 Output: storage_5.b, storage_5.d - -> Seq Scan on test_inserts.storage_6 + -> Seq Scan on test_inserts.storage_4 storage_6 Output: storage_6.b, storage_6.d - -> Seq Scan on test_inserts.storage_7 + -> Seq Scan on test_inserts.storage_5 storage_7 Output: storage_7.b, storage_7.d - -> Seq Scan on test_inserts.storage_8 + -> Seq Scan on test_inserts.storage_6 storage_8 Output: storage_8.b, storage_8.d - -> Seq Scan on test_inserts.storage_9 + -> Seq Scan on test_inserts.storage_7 storage_9 Output: storage_9.b, storage_9.d - -> Seq Scan on test_inserts.storage_10 + -> Seq Scan on test_inserts.storage_8 storage_10 Output: storage_10.b, storage_10.d - -> Seq Scan on test_inserts.storage_12 + -> Seq Scan on test_inserts.storage_9 storage_11 + Output: storage_11.b, storage_11.d + -> Seq Scan on test_inserts.storage_10 storage_12 Output: storage_12.b, storage_12.d - -> Seq Scan on test_inserts.storage_13 + -> Seq Scan on test_inserts.storage_12 storage_13 Output: storage_13.b, storage_13.d - -> Seq Scan on test_inserts.storage_14 + -> Seq Scan on test_inserts.storage_13 storage_14 Output: storage_14.b, storage_14.d + -> Seq Scan on test_inserts.storage_14 storage_15 + Output: storage_15.b, storage_15.d (34 rows) EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO test_inserts.storage (b) SELECT b FROM test_inserts.storage; - QUERY PLAN --------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------- Insert on test_inserts.storage -> Custom Scan (PartitionFilter) Output: NULL::integer, storage.b, NULL::integer, NULL::text, NULL::bigint -> Result - Output: NULL::integer, storage_11.b, NULL::integer, NULL::text, NULL::bigint + Output: NULL::integer, storage_1.b, NULL::integer, NULL::text, NULL::bigint -> Append - -> Seq Scan on test_inserts.storage_11 - Output: storage_11.b - -> Seq Scan on test_inserts.storage_1 storage_1_1 - Output: storage_1_1.b - -> Seq Scan on test_inserts.storage_2 + -> Seq Scan on test_inserts.storage_11 storage_2 Output: storage_2.b - -> Seq Scan on test_inserts.storage_3 + -> Seq Scan on test_inserts.storage_1 storage_3 Output: storage_3.b - -> Seq Scan on test_inserts.storage_4 + -> Seq Scan on test_inserts.storage_2 storage_4 Output: storage_4.b - -> Seq Scan on test_inserts.storage_5 + -> Seq Scan on test_inserts.storage_3 storage_5 Output: storage_5.b - -> Seq Scan on test_inserts.storage_6 + -> Seq Scan on test_inserts.storage_4 storage_6 Output: storage_6.b - -> Seq Scan on test_inserts.storage_7 + -> Seq Scan on test_inserts.storage_5 storage_7 Output: storage_7.b - -> Seq Scan on test_inserts.storage_8 + -> Seq Scan on test_inserts.storage_6 storage_8 Output: storage_8.b - -> Seq Scan on test_inserts.storage_9 + -> Seq Scan on test_inserts.storage_7 storage_9 Output: storage_9.b - -> Seq Scan on test_inserts.storage_10 + -> Seq Scan on test_inserts.storage_8 storage_10 Output: storage_10.b - -> Seq Scan on test_inserts.storage_12 + -> Seq Scan on test_inserts.storage_9 storage_11 + Output: storage_11.b + -> Seq Scan on test_inserts.storage_10 storage_12 Output: storage_12.b - -> Seq Scan on test_inserts.storage_13 + -> Seq Scan on test_inserts.storage_12 storage_13 Output: storage_13.b - -> Seq Scan on test_inserts.storage_14 + -> Seq Scan on test_inserts.storage_13 storage_14 Output: storage_14.b + -> Seq Scan on test_inserts.storage_14 storage_15 + Output: storage_15.b (34 rows) /* test gap case (missing partition in between) */ @@ -1066,6 +1066,10 @@ SELECT count(*) FROM test_inserts.special_2; DROP TABLE test_inserts.special_2; DROP TABLE test_inserts.test_special_only CASCADE; NOTICE: drop cascades to 4 other objects -DROP SCHEMA test_inserts CASCADE; -NOTICE: drop cascades to 19 other objects +DROP TABLE test_inserts.storage CASCADE; +NOTICE: drop cascades to 15 other objects +DROP FUNCTION test_inserts.set_triggers(jsonb); +DROP FUNCTION test_inserts.print_cols_before_change(); +DROP FUNCTION test_inserts.print_cols_after_change(); +DROP SCHEMA test_inserts; DROP EXTENSION pg_pathman CASCADE; diff --git a/expected/pathman_interval.out b/expected/pathman_interval.out index 72dc4e01..e4741522 100644 --- a/expected/pathman_interval.out +++ b/expected/pathman_interval.out @@ -271,5 +271,5 @@ SELECT set_interval('test_interval.abc', NULL::INTEGER); ERROR: table "test_interval.abc" is not partitioned by RANGE DROP TABLE test_interval.abc CASCADE; NOTICE: drop cascades to 3 other objects -DROP SCHEMA test_interval CASCADE; +DROP SCHEMA test_interval; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_join_clause.out b/expected/pathman_join_clause.out index ed822543..7654d4ca 100644 --- a/expected/pathman_join_clause.out +++ b/expected/pathman_join_clause.out @@ -171,7 +171,13 @@ WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); 4 | 3 | | (2 rows) -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 15 other objects +DROP TABLE test.child CASCADE; +NOTICE: drop cascades to 2 other objects +DROP TABLE test.child_nopart CASCADE; +DROP TABLE test.mytbl CASCADE; +NOTICE: drop cascades to 8 other objects +DROP TABLE test.fk CASCADE; +DROP TABLE test.parent CASCADE; +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_join_clause_1.out b/expected/pathman_join_clause_1.out index 09b9a00c..d65131c7 100644 --- a/expected/pathman_join_clause_1.out +++ b/expected/pathman_join_clause_1.out @@ -170,7 +170,13 @@ WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); 4 | 3 | | (2 rows) -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 15 other objects +DROP TABLE test.child CASCADE; +NOTICE: drop cascades to 2 other objects +DROP TABLE test.child_nopart CASCADE; +DROP TABLE test.mytbl CASCADE; +NOTICE: drop cascades to 8 other objects +DROP TABLE test.fk CASCADE; +DROP TABLE test.parent CASCADE; +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_join_clause_2.out b/expected/pathman_join_clause_2.out new file mode 100644 index 00000000..df2ea0a5 --- /dev/null +++ b/expected/pathman_join_clause_2.out @@ -0,0 +1,161 @@ +/* + * Since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated, + * causing different output; pathman_gaps_1.out is the updated version. + */ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE SCHEMA pathman; +CREATE EXTENSION pg_pathman SCHEMA pathman; +CREATE SCHEMA test; +/* + * Test push down a join clause into child nodes of append + */ +/* create test tables */ +CREATE TABLE test.fk ( + id1 INT NOT NULL, + id2 INT NOT NULL, + start_key INT, + end_key INT, + PRIMARY KEY (id1, id2)); +CREATE TABLE test.mytbl ( + id1 INT NOT NULL, + id2 INT NOT NULL, + key INT NOT NULL, + CONSTRAINT fk_fk FOREIGN KEY (id1, id2) REFERENCES test.fk(id1, id2), + PRIMARY KEY (id1, key)); +SELECT pathman.create_hash_partitions('test.mytbl', 'id1', 8); + create_hash_partitions +------------------------ + 8 +(1 row) + +/* ...fill out with test data */ +INSERT INTO test.fk VALUES (1, 1); +INSERT INTO test.mytbl VALUES (1, 1, 5), (1, 1, 6); +/* gather statistics on test tables to have deterministic plans */ +ANALYZE; +/* run test queries */ +EXPLAIN (COSTS OFF) /* test plan */ +SELECT m.tableoid::regclass, id1, id2, key, start_key, end_key +FROM test.mytbl m JOIN test.fk USING(id1, id2) +WHERE NOT key <@ int4range(6, end_key); + QUERY PLAN +------------------------------------------------------------------------------------------------------- + Nested Loop + -> Seq Scan on fk + -> Custom Scan (RuntimeAppend) + Prune by: (fk.id1 = m.id1) + -> Seq Scan on mytbl_0 m + Filter: ((fk.id1 = id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_1 m + Filter: ((fk.id1 = id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_2 m + Filter: ((fk.id1 = id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_3 m + Filter: ((fk.id1 = id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_4 m + Filter: ((fk.id1 = id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_5 m + Filter: ((fk.id1 = id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_6 m + Filter: ((fk.id1 = id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_7 m + Filter: ((fk.id1 = id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) +(20 rows) + +/* test joint data */ +SELECT m.tableoid::regclass, id1, id2, key, start_key, end_key +FROM test.mytbl m JOIN test.fk USING(id1, id2) +WHERE NOT key <@ int4range(6, end_key); + tableoid | id1 | id2 | key | start_key | end_key +--------------+-----+-----+-----+-----------+--------- + test.mytbl_6 | 1 | 1 | 5 | | +(1 row) + +/* + * Test case by @dimarick + */ +CREATE TABLE test.parent ( + id SERIAL NOT NULL, + owner_id INTEGER NOT NULL +); +CREATE TABLE test.child ( + parent_id INTEGER NOT NULL, + owner_id INTEGER NOT NULL +); +CREATE TABLE test.child_nopart ( + parent_id INTEGER NOT NULL, + owner_id INTEGER NOT NULL +); +INSERT INTO test.parent (owner_id) VALUES (1), (2), (3), (3); +INSERT INTO test.child (parent_id, owner_id) VALUES (1, 1), (2, 2), (3, 3), (5, 3); +INSERT INTO test.child_nopart (parent_id, owner_id) VALUES (1, 1), (2, 2), (3, 3), (5, 3); +SELECT pathman.create_hash_partitions('test.child', 'owner_id', 2); + create_hash_partitions +------------------------ + 2 +(1 row) + +/* gather statistics on test tables to have deterministic plans */ +ANALYZE; +/* Query #1 */ +EXPLAIN (COSTS OFF) SELECT * FROM test.parent +LEFT JOIN test.child ON test.child.parent_id = test.parent.id AND + test.child.owner_id = test.parent.owner_id +WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); + QUERY PLAN +----------------------------------------------------------------------------------------------------- + Nested Loop Left Join + -> Seq Scan on parent + Filter: ((id = ANY ('{3,4}'::integer[])) AND (owner_id = 3)) + -> Custom Scan (RuntimeAppend) + Prune by: ((child.owner_id = 3) AND (child.owner_id = parent.owner_id)) + -> Seq Scan on child_1 child + Filter: ((owner_id = 3) AND (owner_id = parent.owner_id) AND (parent_id = parent.id)) +(7 rows) + +SELECT * FROM test.parent +LEFT JOIN test.child ON test.child.parent_id = test.parent.id AND + test.child.owner_id = test.parent.owner_id +WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); + id | owner_id | parent_id | owner_id +----+----------+-----------+---------- + 3 | 3 | 3 | 3 + 4 | 3 | | +(2 rows) + +/* Query #2 */ +EXPLAIN (COSTS OFF) SELECT * FROM test.parent +LEFT JOIN test.child ON test.child.parent_id = test.parent.id AND + test.child.owner_id = 3 +WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); + QUERY PLAN +---------------------------------------------------------------------- + Nested Loop Left Join + Join Filter: (child.parent_id = parent.id) + -> Seq Scan on parent + Filter: ((id = ANY ('{3,4}'::integer[])) AND (owner_id = 3)) + -> Seq Scan on child_1 child + Filter: (owner_id = 3) +(6 rows) + +SELECT * FROM test.parent +LEFT JOIN test.child ON test.child.parent_id = test.parent.id AND + test.child.owner_id = 3 +WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); + id | owner_id | parent_id | owner_id +----+----------+-----------+---------- + 3 | 3 | 3 | 3 + 4 | 3 | | +(2 rows) + +DROP TABLE test.child CASCADE; +NOTICE: drop cascades to 2 other objects +DROP TABLE test.child_nopart CASCADE; +DROP TABLE test.mytbl CASCADE; +NOTICE: drop cascades to 8 other objects +DROP TABLE test.fk CASCADE; +DROP TABLE test.parent CASCADE; +DROP SCHEMA test; +DROP EXTENSION pg_pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_join_clause_3.out b/expected/pathman_join_clause_3.out new file mode 100644 index 00000000..80b8de4c --- /dev/null +++ b/expected/pathman_join_clause_3.out @@ -0,0 +1,182 @@ +/* + * Since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated, + * causing different output; pathman_gaps_1.out is the updated version. + */ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE SCHEMA pathman; +CREATE EXTENSION pg_pathman SCHEMA pathman; +CREATE SCHEMA test; +/* + * Test push down a join clause into child nodes of append + */ +/* create test tables */ +CREATE TABLE test.fk ( + id1 INT NOT NULL, + id2 INT NOT NULL, + start_key INT, + end_key INT, + PRIMARY KEY (id1, id2)); +CREATE TABLE test.mytbl ( + id1 INT NOT NULL, + id2 INT NOT NULL, + key INT NOT NULL, + CONSTRAINT fk_fk FOREIGN KEY (id1, id2) REFERENCES test.fk(id1, id2), + PRIMARY KEY (id1, key)); +SELECT pathman.create_hash_partitions('test.mytbl', 'id1', 8); + create_hash_partitions +------------------------ + 8 +(1 row) + +/* ...fill out with test data */ +INSERT INTO test.fk VALUES (1, 1); +INSERT INTO test.mytbl VALUES (1, 1, 5), (1, 1, 6); +/* gather statistics on test tables to have deterministic plans */ +ANALYZE; +/* run test queries */ +EXPLAIN (COSTS OFF) /* test plan */ +SELECT m.tableoid::regclass, id1, id2, key, start_key, end_key +FROM test.mytbl m JOIN test.fk USING(id1, id2) +WHERE NOT key <@ int4range(6, end_key); + QUERY PLAN +------------------------------------------------------------------------------------------------------- + Nested Loop + -> Seq Scan on fk + -> Custom Scan (RuntimeAppend) + Prune by: (fk.id1 = m.id1) + -> Bitmap Heap Scan on mytbl_0 m + Recheck Cond: (id1 = fk.id1) + Filter: ((fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Bitmap Index Scan on mytbl_0_pkey + Index Cond: (id1 = fk.id1) + -> Bitmap Heap Scan on mytbl_1 m + Recheck Cond: (id1 = fk.id1) + Filter: ((fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Bitmap Index Scan on mytbl_1_pkey + Index Cond: (id1 = fk.id1) + -> Bitmap Heap Scan on mytbl_2 m + Recheck Cond: (id1 = fk.id1) + Filter: ((fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Bitmap Index Scan on mytbl_2_pkey + Index Cond: (id1 = fk.id1) + -> Bitmap Heap Scan on mytbl_3 m + Recheck Cond: (id1 = fk.id1) + Filter: ((fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Bitmap Index Scan on mytbl_3_pkey + Index Cond: (id1 = fk.id1) + -> Bitmap Heap Scan on mytbl_4 m + Recheck Cond: (id1 = fk.id1) + Filter: ((fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Bitmap Index Scan on mytbl_4_pkey + Index Cond: (id1 = fk.id1) + -> Bitmap Heap Scan on mytbl_5 m + Recheck Cond: (id1 = fk.id1) + Filter: ((fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Bitmap Index Scan on mytbl_5_pkey + Index Cond: (id1 = fk.id1) + -> Seq Scan on mytbl_6 m + Filter: ((fk.id1 = id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Bitmap Heap Scan on mytbl_7 m + Recheck Cond: (id1 = fk.id1) + Filter: ((fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Bitmap Index Scan on mytbl_7_pkey + Index Cond: (id1 = fk.id1) +(41 rows) + +/* test joint data */ +SELECT m.tableoid::regclass, id1, id2, key, start_key, end_key +FROM test.mytbl m JOIN test.fk USING(id1, id2) +WHERE NOT key <@ int4range(6, end_key); + tableoid | id1 | id2 | key | start_key | end_key +--------------+-----+-----+-----+-----------+--------- + test.mytbl_6 | 1 | 1 | 5 | | +(1 row) + +/* + * Test case by @dimarick + */ +CREATE TABLE test.parent ( + id SERIAL NOT NULL, + owner_id INTEGER NOT NULL +); +CREATE TABLE test.child ( + parent_id INTEGER NOT NULL, + owner_id INTEGER NOT NULL +); +CREATE TABLE test.child_nopart ( + parent_id INTEGER NOT NULL, + owner_id INTEGER NOT NULL +); +INSERT INTO test.parent (owner_id) VALUES (1), (2), (3), (3); +INSERT INTO test.child (parent_id, owner_id) VALUES (1, 1), (2, 2), (3, 3), (5, 3); +INSERT INTO test.child_nopart (parent_id, owner_id) VALUES (1, 1), (2, 2), (3, 3), (5, 3); +SELECT pathman.create_hash_partitions('test.child', 'owner_id', 2); + create_hash_partitions +------------------------ + 2 +(1 row) + +/* gather statistics on test tables to have deterministic plans */ +ANALYZE; +/* Query #1 */ +EXPLAIN (COSTS OFF) SELECT * FROM test.parent +LEFT JOIN test.child ON test.child.parent_id = test.parent.id AND + test.child.owner_id = test.parent.owner_id +WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); + QUERY PLAN +----------------------------------------------------------------------------------------------------- + Nested Loop Left Join + -> Seq Scan on parent + Filter: ((id = ANY ('{3,4}'::integer[])) AND (owner_id = 3)) + -> Custom Scan (RuntimeAppend) + Prune by: ((child.owner_id = 3) AND (child.owner_id = parent.owner_id)) + -> Seq Scan on child_1 child + Filter: ((owner_id = 3) AND (owner_id = parent.owner_id) AND (parent_id = parent.id)) +(7 rows) + +SELECT * FROM test.parent +LEFT JOIN test.child ON test.child.parent_id = test.parent.id AND + test.child.owner_id = test.parent.owner_id +WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); + id | owner_id | parent_id | owner_id +----+----------+-----------+---------- + 3 | 3 | 3 | 3 + 4 | 3 | | +(2 rows) + +/* Query #2 */ +EXPLAIN (COSTS OFF) SELECT * FROM test.parent +LEFT JOIN test.child ON test.child.parent_id = test.parent.id AND + test.child.owner_id = 3 +WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); + QUERY PLAN +---------------------------------------------------------------------- + Nested Loop Left Join + Join Filter: (child.parent_id = parent.id) + -> Seq Scan on parent + Filter: ((id = ANY ('{3,4}'::integer[])) AND (owner_id = 3)) + -> Seq Scan on child_1 child + Filter: (owner_id = 3) +(6 rows) + +SELECT * FROM test.parent +LEFT JOIN test.child ON test.child.parent_id = test.parent.id AND + test.child.owner_id = 3 +WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); + id | owner_id | parent_id | owner_id +----+----------+-----------+---------- + 3 | 3 | 3 | 3 + 4 | 3 | | +(2 rows) + +DROP TABLE test.child CASCADE; +NOTICE: drop cascades to 2 other objects +DROP TABLE test.child_nopart CASCADE; +DROP TABLE test.mytbl CASCADE; +NOTICE: drop cascades to 8 other objects +DROP TABLE test.fk CASCADE; +DROP TABLE test.parent CASCADE; +DROP SCHEMA test; +DROP EXTENSION pg_pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_join_clause_4.out b/expected/pathman_join_clause_4.out new file mode 100644 index 00000000..17791fb9 --- /dev/null +++ b/expected/pathman_join_clause_4.out @@ -0,0 +1,161 @@ +/* + * Since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated, + * causing different output; pathman_gaps_1.out is the updated version. + */ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE SCHEMA pathman; +CREATE EXTENSION pg_pathman SCHEMA pathman; +CREATE SCHEMA test; +/* + * Test push down a join clause into child nodes of append + */ +/* create test tables */ +CREATE TABLE test.fk ( + id1 INT NOT NULL, + id2 INT NOT NULL, + start_key INT, + end_key INT, + PRIMARY KEY (id1, id2)); +CREATE TABLE test.mytbl ( + id1 INT NOT NULL, + id2 INT NOT NULL, + key INT NOT NULL, + CONSTRAINT fk_fk FOREIGN KEY (id1, id2) REFERENCES test.fk(id1, id2), + PRIMARY KEY (id1, key)); +SELECT pathman.create_hash_partitions('test.mytbl', 'id1', 8); + create_hash_partitions +------------------------ + 8 +(1 row) + +/* ...fill out with test data */ +INSERT INTO test.fk VALUES (1, 1); +INSERT INTO test.mytbl VALUES (1, 1, 5), (1, 1, 6); +/* gather statistics on test tables to have deterministic plans */ +ANALYZE; +/* run test queries */ +EXPLAIN (COSTS OFF) /* test plan */ +SELECT m.tableoid::regclass, id1, id2, key, start_key, end_key +FROM test.mytbl m JOIN test.fk USING(id1, id2) +WHERE NOT key <@ int4range(6, end_key); + QUERY PLAN +------------------------------------------------------------------------------------------------------- + Nested Loop + -> Seq Scan on fk + -> Custom Scan (RuntimeAppend) + Prune by: (m.id1 = fk.id1) + -> Seq Scan on mytbl_0 m + Filter: ((id1 = fk.id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_1 m + Filter: ((id1 = fk.id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_2 m + Filter: ((id1 = fk.id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_3 m + Filter: ((id1 = fk.id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_4 m + Filter: ((id1 = fk.id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_5 m + Filter: ((id1 = fk.id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_6 m + Filter: ((id1 = fk.id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_7 m + Filter: ((id1 = fk.id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) +(20 rows) + +/* test joint data */ +SELECT m.tableoid::regclass, id1, id2, key, start_key, end_key +FROM test.mytbl m JOIN test.fk USING(id1, id2) +WHERE NOT key <@ int4range(6, end_key); + tableoid | id1 | id2 | key | start_key | end_key +--------------+-----+-----+-----+-----------+--------- + test.mytbl_6 | 1 | 1 | 5 | | +(1 row) + +/* + * Test case by @dimarick + */ +CREATE TABLE test.parent ( + id SERIAL NOT NULL, + owner_id INTEGER NOT NULL +); +CREATE TABLE test.child ( + parent_id INTEGER NOT NULL, + owner_id INTEGER NOT NULL +); +CREATE TABLE test.child_nopart ( + parent_id INTEGER NOT NULL, + owner_id INTEGER NOT NULL +); +INSERT INTO test.parent (owner_id) VALUES (1), (2), (3), (3); +INSERT INTO test.child (parent_id, owner_id) VALUES (1, 1), (2, 2), (3, 3), (5, 3); +INSERT INTO test.child_nopart (parent_id, owner_id) VALUES (1, 1), (2, 2), (3, 3), (5, 3); +SELECT pathman.create_hash_partitions('test.child', 'owner_id', 2); + create_hash_partitions +------------------------ + 2 +(1 row) + +/* gather statistics on test tables to have deterministic plans */ +ANALYZE; +/* Query #1 */ +EXPLAIN (COSTS OFF) SELECT * FROM test.parent +LEFT JOIN test.child ON test.child.parent_id = test.parent.id AND + test.child.owner_id = test.parent.owner_id +WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); + QUERY PLAN +----------------------------------------------------------------------------------------------------- + Nested Loop Left Join + -> Seq Scan on parent + Filter: ((id = ANY ('{3,4}'::integer[])) AND (owner_id = 3)) + -> Custom Scan (RuntimeAppend) + Prune by: ((child.owner_id = 3) AND (child.owner_id = parent.owner_id)) + -> Seq Scan on child_1 child + Filter: ((owner_id = 3) AND (owner_id = parent.owner_id) AND (parent_id = parent.id)) +(7 rows) + +SELECT * FROM test.parent +LEFT JOIN test.child ON test.child.parent_id = test.parent.id AND + test.child.owner_id = test.parent.owner_id +WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); + id | owner_id | parent_id | owner_id +----+----------+-----------+---------- + 3 | 3 | 3 | 3 + 4 | 3 | | +(2 rows) + +/* Query #2 */ +EXPLAIN (COSTS OFF) SELECT * FROM test.parent +LEFT JOIN test.child ON test.child.parent_id = test.parent.id AND + test.child.owner_id = 3 +WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); + QUERY PLAN +---------------------------------------------------------------------- + Nested Loop Left Join + Join Filter: (child.parent_id = parent.id) + -> Seq Scan on parent + Filter: ((id = ANY ('{3,4}'::integer[])) AND (owner_id = 3)) + -> Seq Scan on child_1 child + Filter: (owner_id = 3) +(6 rows) + +SELECT * FROM test.parent +LEFT JOIN test.child ON test.child.parent_id = test.parent.id AND + test.child.owner_id = 3 +WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); + id | owner_id | parent_id | owner_id +----+----------+-----------+---------- + 3 | 3 | 3 | 3 + 4 | 3 | | +(2 rows) + +DROP TABLE test.child CASCADE; +NOTICE: drop cascades to 2 other objects +DROP TABLE test.child_nopart CASCADE; +DROP TABLE test.mytbl CASCADE; +NOTICE: drop cascades to 8 other objects +DROP TABLE test.fk CASCADE; +DROP TABLE test.parent CASCADE; +DROP SCHEMA test; +DROP EXTENSION pg_pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_join_clause_5.out b/expected/pathman_join_clause_5.out new file mode 100644 index 00000000..179f50f7 --- /dev/null +++ b/expected/pathman_join_clause_5.out @@ -0,0 +1,160 @@ +/* + * Since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated, + * causing different output; pathman_gaps_1.out is the updated version. + */ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE SCHEMA pathman; +CREATE EXTENSION pg_pathman SCHEMA pathman; +CREATE SCHEMA test; +/* + * Test push down a join clause into child nodes of append + */ +/* create test tables */ +CREATE TABLE test.fk ( + id1 INT NOT NULL, + id2 INT NOT NULL, + start_key INT, + end_key INT, + PRIMARY KEY (id1, id2)); +CREATE TABLE test.mytbl ( + id1 INT NOT NULL, + id2 INT NOT NULL, + key INT NOT NULL, + CONSTRAINT fk_fk FOREIGN KEY (id1, id2) REFERENCES test.fk(id1, id2), + PRIMARY KEY (id1, key)); +SELECT pathman.create_hash_partitions('test.mytbl', 'id1', 8); + create_hash_partitions +------------------------ + 8 +(1 row) + +/* ...fill out with test data */ +INSERT INTO test.fk VALUES (1, 1); +INSERT INTO test.mytbl VALUES (1, 1, 5), (1, 1, 6); +/* gather statistics on test tables to have deterministic plans */ +ANALYZE; +/* run test queries */ +EXPLAIN (COSTS OFF) /* test plan */ +SELECT m.tableoid::regclass, id1, id2, key, start_key, end_key +FROM test.mytbl m JOIN test.fk USING(id1, id2) +WHERE NOT key <@ int4range(6, end_key); + QUERY PLAN +------------------------------------------------------------------------------------------------------- + Nested Loop + -> Seq Scan on fk + -> Custom Scan (RuntimeAppend) + Prune by: (m.id1 = fk.id1) + -> Seq Scan on mytbl_0 m + Filter: ((id1 = fk.id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_1 m + Filter: ((id1 = fk.id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_2 m + Filter: ((id1 = fk.id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_3 m + Filter: ((id1 = fk.id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_4 m + Filter: ((id1 = fk.id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_5 m + Filter: ((id1 = fk.id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_6 m + Filter: ((id1 = fk.id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) + -> Seq Scan on mytbl_7 m + Filter: ((id1 = fk.id1) AND (fk.id2 = id2) AND (NOT (key <@ int4range(6, fk.end_key)))) +(20 rows) + +/* test joint data */ +SELECT m.tableoid::regclass, id1, id2, key, start_key, end_key +FROM test.mytbl m JOIN test.fk USING(id1, id2) +WHERE NOT key <@ int4range(6, end_key); + tableoid | id1 | id2 | key | start_key | end_key +--------------+-----+-----+-----+-----------+--------- + test.mytbl_6 | 1 | 1 | 5 | | +(1 row) + +/* + * Test case by @dimarick + */ +CREATE TABLE test.parent ( + id SERIAL NOT NULL, + owner_id INTEGER NOT NULL +); +CREATE TABLE test.child ( + parent_id INTEGER NOT NULL, + owner_id INTEGER NOT NULL +); +CREATE TABLE test.child_nopart ( + parent_id INTEGER NOT NULL, + owner_id INTEGER NOT NULL +); +INSERT INTO test.parent (owner_id) VALUES (1), (2), (3), (3); +INSERT INTO test.child (parent_id, owner_id) VALUES (1, 1), (2, 2), (3, 3), (5, 3); +INSERT INTO test.child_nopart (parent_id, owner_id) VALUES (1, 1), (2, 2), (3, 3), (5, 3); +SELECT pathman.create_hash_partitions('test.child', 'owner_id', 2); + create_hash_partitions +------------------------ + 2 +(1 row) + +/* gather statistics on test tables to have deterministic plans */ +ANALYZE; +/* Query #1 */ +EXPLAIN (COSTS OFF) SELECT * FROM test.parent +LEFT JOIN test.child ON test.child.parent_id = test.parent.id AND + test.child.owner_id = test.parent.owner_id +WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); + QUERY PLAN +---------------------------------------------------------------------- + Nested Loop Left Join + Join Filter: (child.parent_id = parent.id) + -> Seq Scan on parent + Filter: ((id = ANY ('{3,4}'::integer[])) AND (owner_id = 3)) + -> Seq Scan on child_1 child + Filter: (owner_id = 3) +(6 rows) + +SELECT * FROM test.parent +LEFT JOIN test.child ON test.child.parent_id = test.parent.id AND + test.child.owner_id = test.parent.owner_id +WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); + id | owner_id | parent_id | owner_id +----+----------+-----------+---------- + 3 | 3 | 3 | 3 + 4 | 3 | | +(2 rows) + +/* Query #2 */ +EXPLAIN (COSTS OFF) SELECT * FROM test.parent +LEFT JOIN test.child ON test.child.parent_id = test.parent.id AND + test.child.owner_id = 3 +WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); + QUERY PLAN +---------------------------------------------------------------------- + Nested Loop Left Join + Join Filter: (child.parent_id = parent.id) + -> Seq Scan on parent + Filter: ((id = ANY ('{3,4}'::integer[])) AND (owner_id = 3)) + -> Seq Scan on child_1 child + Filter: (owner_id = 3) +(6 rows) + +SELECT * FROM test.parent +LEFT JOIN test.child ON test.child.parent_id = test.parent.id AND + test.child.owner_id = 3 +WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); + id | owner_id | parent_id | owner_id +----+----------+-----------+---------- + 3 | 3 | 3 | 3 + 4 | 3 | | +(2 rows) + +DROP TABLE test.child CASCADE; +NOTICE: drop cascades to 2 other objects +DROP TABLE test.child_nopart CASCADE; +DROP TABLE test.mytbl CASCADE; +NOTICE: drop cascades to 8 other objects +DROP TABLE test.fk CASCADE; +DROP TABLE test.parent CASCADE; +DROP SCHEMA test; +DROP EXTENSION pg_pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_lateral.out b/expected/pathman_lateral.out index 0cb1a864..53edc3d2 100644 --- a/expected/pathman_lateral.out +++ b/expected/pathman_lateral.out @@ -122,6 +122,7 @@ select * from set enable_hashjoin = on; set enable_mergejoin = on; -DROP SCHEMA test_lateral CASCADE; -NOTICE: drop cascades to 11 other objects +DROP TABLE test_lateral.data CASCADE; +NOTICE: drop cascades to 10 other objects +DROP SCHEMA test_lateral; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_lateral_1.out b/expected/pathman_lateral_1.out index 1dc67fe2..12995290 100644 --- a/expected/pathman_lateral_1.out +++ b/expected/pathman_lateral_1.out @@ -116,6 +116,7 @@ select * from set enable_hashjoin = on; set enable_mergejoin = on; -DROP SCHEMA test_lateral CASCADE; -NOTICE: drop cascades to 11 other objects +DROP TABLE test_lateral.data CASCADE; +NOTICE: drop cascades to 10 other objects +DROP SCHEMA test_lateral; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_lateral_2.out b/expected/pathman_lateral_2.out index 5ee4104c..e4a64a56 100644 --- a/expected/pathman_lateral_2.out +++ b/expected/pathman_lateral_2.out @@ -32,13 +32,13 @@ select * from t1.id > t2.id and exists(select * from test_lateral.data t where t1.id = t2.id and t.id = t3.id); - QUERY PLAN --------------------------------------------------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------------------------------------- Nested Loop -> Nested Loop - Join Filter: ((t2_1.id + t1_1.id) = t_1.id) + Join Filter: ((t2.id + t1.id) = t.id) -> HashAggregate - Group Key: t_1.id + Group Key: t.id -> Append -> Seq Scan on data_0 t_1 -> Seq Scan on data_1 t_2 @@ -52,7 +52,7 @@ select * from -> Seq Scan on data_9 t_10 -> Materialize -> Nested Loop - Join Filter: ((t2_1.id > t1_1.id) AND (t1_1.id > t2_1.id) AND (t1_1.id = t2_1.id)) + Join Filter: ((t2.id > t1.id) AND (t1.id > t2.id) AND (t1.id = t2.id)) -> Append -> Seq Scan on data_0 t2_1 Filter: ((id >= 2) AND (id <= 299)) @@ -97,31 +97,32 @@ select * from -> Seq Scan on data_9 t1_10 Filter: ((id >= 1) AND (id <= 100)) -> Custom Scan (RuntimeAppend) - Prune by: (t_1.id = t3.id) + Prune by: (t.id = t3.id) -> Seq Scan on data_0 t3 - Filter: (t_1.id = id) + Filter: (t.id = id) -> Seq Scan on data_1 t3 - Filter: (t_1.id = id) + Filter: (t.id = id) -> Seq Scan on data_2 t3 - Filter: (t_1.id = id) + Filter: (t.id = id) -> Seq Scan on data_3 t3 - Filter: (t_1.id = id) + Filter: (t.id = id) -> Seq Scan on data_4 t3 - Filter: (t_1.id = id) + Filter: (t.id = id) -> Seq Scan on data_5 t3 - Filter: (t_1.id = id) + Filter: (t.id = id) -> Seq Scan on data_6 t3 - Filter: (t_1.id = id) + Filter: (t.id = id) -> Seq Scan on data_7 t3 - Filter: (t_1.id = id) + Filter: (t.id = id) -> Seq Scan on data_8 t3 - Filter: (t_1.id = id) + Filter: (t.id = id) -> Seq Scan on data_9 t3 - Filter: (t_1.id = id) + Filter: (t.id = id) (84 rows) set enable_hashjoin = on; set enable_mergejoin = on; -DROP SCHEMA test_lateral CASCADE; -NOTICE: drop cascades to 11 other objects +DROP TABLE test_lateral.data CASCADE; +NOTICE: drop cascades to 10 other objects +DROP SCHEMA test_lateral; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_lateral_3.out b/expected/pathman_lateral_3.out index dd64819d..4bc385de 100644 --- a/expected/pathman_lateral_3.out +++ b/expected/pathman_lateral_3.out @@ -121,6 +121,7 @@ select * from set enable_hashjoin = on; set enable_mergejoin = on; -DROP SCHEMA test_lateral CASCADE; -NOTICE: drop cascades to 11 other objects +DROP TABLE test_lateral.data CASCADE; +NOTICE: drop cascades to 10 other objects +DROP SCHEMA test_lateral; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_lateral_4.out b/expected/pathman_lateral_4.out new file mode 100644 index 00000000..d35da608 --- /dev/null +++ b/expected/pathman_lateral_4.out @@ -0,0 +1,128 @@ +/* + * Sometimes join selectivity improvements patches in pgpro force nested loop + * members swap -- in pathman_lateral_1.out and pathman_lateral_3.out + * + * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, + * now it includes aliases for inherited tables. + */ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE EXTENSION pg_pathman; +CREATE SCHEMA test_lateral; +/* create table partitioned by HASH */ +create table test_lateral.data(id int8 not null); +select create_hash_partitions('test_lateral.data', 'id', 10); + create_hash_partitions +------------------------ + 10 +(1 row) + +insert into test_lateral.data select generate_series(1, 10000); +VACUUM ANALYZE; +set enable_hashjoin = off; +set enable_mergejoin = off; +/* all credits go to Ivan Frolkov */ +explain (costs off) +select * from + test_lateral.data as t1, + lateral(select * from test_lateral.data as t2 where t2.id > t1.id) t2, + lateral(select * from test_lateral.data as t3 where t3.id = t2.id + t1.id) t3 + where t1.id between 1 and 100 and + t2.id between 2 and 299 and + t1.id > t2.id and + exists(select * from test_lateral.data t + where t1.id = t2.id and t.id = t3.id); + QUERY PLAN +-------------------------------------------------------------------------------------------- + Nested Loop + -> Nested Loop + Join Filter: ((t2.id + t1.id) = t.id) + -> HashAggregate + Group Key: t.id + -> Append + -> Seq Scan on data_0 t_1 + -> Seq Scan on data_1 t_2 + -> Seq Scan on data_2 t_3 + -> Seq Scan on data_3 t_4 + -> Seq Scan on data_4 t_5 + -> Seq Scan on data_5 t_6 + -> Seq Scan on data_6 t_7 + -> Seq Scan on data_7 t_8 + -> Seq Scan on data_8 t_9 + -> Seq Scan on data_9 t_10 + -> Materialize + -> Nested Loop + Join Filter: ((t2.id > t1.id) AND (t1.id > t2.id) AND (t1.id = t2.id)) + -> Append + -> Seq Scan on data_0 t2_1 + Filter: ((id >= 2) AND (id <= 299)) + -> Seq Scan on data_1 t2_2 + Filter: ((id >= 2) AND (id <= 299)) + -> Seq Scan on data_2 t2_3 + Filter: ((id >= 2) AND (id <= 299)) + -> Seq Scan on data_3 t2_4 + Filter: ((id >= 2) AND (id <= 299)) + -> Seq Scan on data_4 t2_5 + Filter: ((id >= 2) AND (id <= 299)) + -> Seq Scan on data_5 t2_6 + Filter: ((id >= 2) AND (id <= 299)) + -> Seq Scan on data_6 t2_7 + Filter: ((id >= 2) AND (id <= 299)) + -> Seq Scan on data_7 t2_8 + Filter: ((id >= 2) AND (id <= 299)) + -> Seq Scan on data_8 t2_9 + Filter: ((id >= 2) AND (id <= 299)) + -> Seq Scan on data_9 t2_10 + Filter: ((id >= 2) AND (id <= 299)) + -> Materialize + -> Append + -> Seq Scan on data_0 t1_1 + Filter: ((id >= 1) AND (id <= 100)) + -> Seq Scan on data_1 t1_2 + Filter: ((id >= 1) AND (id <= 100)) + -> Seq Scan on data_2 t1_3 + Filter: ((id >= 1) AND (id <= 100)) + -> Seq Scan on data_3 t1_4 + Filter: ((id >= 1) AND (id <= 100)) + -> Seq Scan on data_4 t1_5 + Filter: ((id >= 1) AND (id <= 100)) + -> Seq Scan on data_5 t1_6 + Filter: ((id >= 1) AND (id <= 100)) + -> Seq Scan on data_6 t1_7 + Filter: ((id >= 1) AND (id <= 100)) + -> Seq Scan on data_7 t1_8 + Filter: ((id >= 1) AND (id <= 100)) + -> Seq Scan on data_8 t1_9 + Filter: ((id >= 1) AND (id <= 100)) + -> Seq Scan on data_9 t1_10 + Filter: ((id >= 1) AND (id <= 100)) + -> Custom Scan (RuntimeAppend) + Prune by: (t3.id = t.id) + -> Seq Scan on data_0 t3 + Filter: (t.id = id) + -> Seq Scan on data_1 t3 + Filter: (t.id = id) + -> Seq Scan on data_2 t3 + Filter: (t.id = id) + -> Seq Scan on data_3 t3 + Filter: (t.id = id) + -> Seq Scan on data_4 t3 + Filter: (t.id = id) + -> Seq Scan on data_5 t3 + Filter: (t.id = id) + -> Seq Scan on data_6 t3 + Filter: (t.id = id) + -> Seq Scan on data_7 t3 + Filter: (t.id = id) + -> Seq Scan on data_8 t3 + Filter: (t.id = id) + -> Seq Scan on data_9 t3 + Filter: (t.id = id) +(84 rows) + +set enable_hashjoin = on; +set enable_mergejoin = on; +DROP TABLE test_lateral.data CASCADE; +NOTICE: drop cascades to 10 other objects +DROP SCHEMA test_lateral; +DROP EXTENSION pg_pathman; diff --git a/expected/pathman_mergejoin.out b/expected/pathman_mergejoin.out index ca3a3d9d..d8a14371 100644 --- a/expected/pathman_mergejoin.out +++ b/expected/pathman_mergejoin.out @@ -47,6 +47,8 @@ SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); SET enable_hashjoin = OFF; SET enable_nestloop = OFF; SET enable_mergejoin = ON; +SET enable_indexscan = ON; +SET enable_seqscan = OFF; EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel j1 JOIN test.range_rel j2 on j2.id = j1.id @@ -80,7 +82,11 @@ WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; SET enable_hashjoin = ON; SET enable_nestloop = ON; -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 12 other objects +SET enable_seqscan = ON; +DROP TABLE test.num_range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP SCHEMA test; DROP EXTENSION pg_pathman; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_mergejoin_1.out b/expected/pathman_mergejoin_1.out index 31da465a..bcd6c272 100644 --- a/expected/pathman_mergejoin_1.out +++ b/expected/pathman_mergejoin_1.out @@ -47,6 +47,8 @@ SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); SET enable_hashjoin = OFF; SET enable_nestloop = OFF; SET enable_mergejoin = ON; +SET enable_indexscan = ON; +SET enable_seqscan = OFF; EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel j1 JOIN test.range_rel j2 on j2.id = j1.id @@ -78,7 +80,11 @@ WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; SET enable_hashjoin = ON; SET enable_nestloop = ON; -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 12 other objects +SET enable_seqscan = ON; +DROP TABLE test.num_range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP SCHEMA test; DROP EXTENSION pg_pathman; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_mergejoin_2.out b/expected/pathman_mergejoin_2.out index 4b614ad6..aed697d2 100644 --- a/expected/pathman_mergejoin_2.out +++ b/expected/pathman_mergejoin_2.out @@ -47,6 +47,8 @@ SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); SET enable_hashjoin = OFF; SET enable_nestloop = OFF; SET enable_mergejoin = ON; +SET enable_indexscan = ON; +SET enable_seqscan = OFF; EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel j1 JOIN test.range_rel j2 on j2.id = j1.id @@ -71,7 +73,11 @@ WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; SET enable_hashjoin = ON; SET enable_nestloop = ON; -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 12 other objects +SET enable_seqscan = ON; +DROP TABLE test.num_range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP SCHEMA test; DROP EXTENSION pg_pathman; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_mergejoin_3.out b/expected/pathman_mergejoin_3.out index 7003205f..85414544 100644 --- a/expected/pathman_mergejoin_3.out +++ b/expected/pathman_mergejoin_3.out @@ -47,6 +47,8 @@ SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); SET enable_hashjoin = OFF; SET enable_nestloop = OFF; SET enable_mergejoin = ON; +SET enable_indexscan = ON; +SET enable_seqscan = OFF; EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel j1 JOIN test.range_rel j2 on j2.id = j1.id @@ -69,7 +71,11 @@ WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; SET enable_hashjoin = ON; SET enable_nestloop = ON; -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 12 other objects +SET enable_seqscan = ON; +DROP TABLE test.num_range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP SCHEMA test; DROP EXTENSION pg_pathman; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_mergejoin_4.out b/expected/pathman_mergejoin_4.out index 185aa3d1..fc9bc95f 100644 --- a/expected/pathman_mergejoin_4.out +++ b/expected/pathman_mergejoin_4.out @@ -47,6 +47,8 @@ SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); SET enable_hashjoin = OFF; SET enable_nestloop = OFF; SET enable_mergejoin = ON; +SET enable_indexscan = ON; +SET enable_seqscan = OFF; EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel j1 JOIN test.range_rel j2 on j2.id = j1.id @@ -55,17 +57,17 @@ WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; QUERY PLAN --------------------------------------------------------------------------------- Sort - Sort Key: j2_1.dt + Sort Key: j2.dt -> Merge Join - Merge Cond: (j2_1.id = j3_1.id) + Merge Cond: (j2.id = j3.id) -> Merge Join - Merge Cond: (j1_1.id = j2_1.id) + Merge Cond: (j1.id = j2.id) -> Merge Append - Sort Key: j1_1.id + Sort Key: j1.id -> Index Scan using range_rel_1_pkey on range_rel_1 j1_1 -> Index Scan using range_rel_2_pkey on range_rel_2 j1_2 -> Merge Append - Sort Key: j2_1.id + Sort Key: j2.id -> Index Scan using range_rel_2_pkey on range_rel_2 j2_1 -> Index Scan using range_rel_3_pkey on range_rel_3 j2_2 -> Index Scan using range_rel_4_pkey on range_rel_4 j2_3 @@ -78,7 +80,11 @@ WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; SET enable_hashjoin = ON; SET enable_nestloop = ON; -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 12 other objects +SET enable_seqscan = ON; +DROP TABLE test.num_range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP SCHEMA test; DROP EXTENSION pg_pathman; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_mergejoin_5.out b/expected/pathman_mergejoin_5.out index 6ffe89cd..b99e40db 100644 --- a/expected/pathman_mergejoin_5.out +++ b/expected/pathman_mergejoin_5.out @@ -47,6 +47,8 @@ SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); SET enable_hashjoin = OFF; SET enable_nestloop = OFF; SET enable_mergejoin = ON; +SET enable_indexscan = ON; +SET enable_seqscan = OFF; EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel j1 JOIN test.range_rel j2 on j2.id = j1.id @@ -57,7 +59,7 @@ WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; Sort Sort Key: j2.dt -> Merge Join - Merge Cond: (j2.id = j3_1.id) + Merge Cond: (j2.id = j3.id) -> Index Scan using range_rel_2_pkey on range_rel_2 j2 Index Cond: (id IS NOT NULL) -> Append @@ -69,7 +71,11 @@ WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; SET enable_hashjoin = ON; SET enable_nestloop = ON; -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 12 other objects +SET enable_seqscan = ON; +DROP TABLE test.num_range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP SCHEMA test; DROP EXTENSION pg_pathman; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_mergejoin_6.out b/expected/pathman_mergejoin_6.out new file mode 100644 index 00000000..0cca2aef --- /dev/null +++ b/expected/pathman_mergejoin_6.out @@ -0,0 +1,80 @@ +/* + * pathman_mergejoin_1.out and pathman_mergejoin_2.out seem to deal with pgpro's + * different behaviour. 8edd0e794 (>= 12) Append nodes with single subplan + * are eliminated, hence pathman_mergejoin_3.out + * + * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, + * now it includes aliases for inherited tables. + * + * --------------------------------------------- + * NOTE: This test behaves differenly on PgPro + * --------------------------------------------- + */ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE SCHEMA pathman; +CREATE EXTENSION pg_pathman SCHEMA pathman; +CREATE SCHEMA test; +CREATE TABLE test.range_rel ( + id SERIAL PRIMARY KEY, + dt TIMESTAMP NOT NULL, + txt TEXT); +CREATE INDEX ON test.range_rel (dt); +INSERT INTO test.range_rel (dt, txt) +SELECT g, md5(g::TEXT) FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) as g; +SELECT pathman.create_range_partitions('test.range_rel', 'DT', '2015-01-01'::DATE, '1 month'::INTERVAL); + create_range_partitions +------------------------- + 4 +(1 row) + +CREATE TABLE test.num_range_rel ( + id SERIAL PRIMARY KEY, + txt TEXT); +INSERT INTO test.num_range_rel SELECT g, md5(g::TEXT) FROM generate_series(1, 3000) as g; +SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); + create_range_partitions +------------------------- + 4 +(1 row) + +/* + * Merge join between 3 partitioned tables + * + * test case for the fix of sorting, merge append and index scan issues + * details in commit 54dd0486fc55b2d25cf7d095f83dee6ff4adee06 + */ +SET enable_hashjoin = OFF; +SET enable_nestloop = OFF; +SET enable_mergejoin = ON; +SET enable_indexscan = ON; +SET enable_seqscan = OFF; +EXPLAIN (COSTS OFF) +SELECT * FROM test.range_rel j1 +JOIN test.range_rel j2 on j2.id = j1.id +JOIN test.num_range_rel j3 on j3.id = j1.id +WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; + QUERY PLAN +--------------------------------------------------------------------------------- + Sort + Sort Key: j2.dt + -> Merge Join + Merge Cond: (j2.id = j3.id) + -> Index Scan using range_rel_2_pkey on range_rel_2 j2 + -> Append + -> Index Scan using num_range_rel_1_pkey on num_range_rel_1 j3_1 + -> Index Scan using num_range_rel_2_pkey on num_range_rel_2 j3_2 + -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 j3_3 + -> Index Scan using num_range_rel_4_pkey on num_range_rel_4 j3_4 +(10 rows) + +SET enable_hashjoin = ON; +SET enable_nestloop = ON; +SET enable_seqscan = ON; +DROP TABLE test.num_range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 5 other objects +DROP SCHEMA test; +DROP EXTENSION pg_pathman; +DROP SCHEMA pathman; diff --git a/expected/pathman_only.out b/expected/pathman_only.out index 83425632..f44f2256 100644 --- a/expected/pathman_only.out +++ b/expected/pathman_only.out @@ -3,13 +3,31 @@ * NOTE: This test behaves differenly on PgPro * --------------------------------------------- * - * Since 12 (608b167f9f), CTEs which are scanned once are no longer an - * optimization fence, which changes practically all plans here. There is + * -------------------- + * pathman_only_1.sql + * -------------------- + * Since 608b167f9f in PostgreSQL 12, CTEs which are scanned once are no longer + * an optimization fence, which changes practically all plans here. There is * an option to forcibly make them MATERIALIZED, but we also need to run tests * on older versions, so create pathman_only_1.out instead. * - * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, - * now it includes aliases for inherited tables. + * -------------------- + * pathman_only_2.sql + * -------------------- + * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13, output of EXPLAIN was + * changed, now it includes aliases for inherited tables. + * + * -------------------- + * pathman_only_3.sql + * -------------------- + * Since a5fc46414de in PostgreSQL 16, the order of the operands was changed, + * which affected the output of the "Prune by" in EXPLAIN. + * + * -------------------- + * pathman_only_4.sql + * -------------------- + * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was + * changed, now it displays SubPlan nodes and output parameters. */ \set VERBOSITY terse SET search_path = 'public'; @@ -272,6 +290,7 @@ WHERE val = (SELECT val FROM ONLY test_only.from_only_test Filter: (val = $0) (27 rows) -DROP SCHEMA test_only CASCADE; -NOTICE: drop cascades to 12 other objects +DROP TABLE test_only.from_only_test CASCADE; +NOTICE: drop cascades to 11 other objects +DROP SCHEMA test_only; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_only_1.out b/expected/pathman_only_1.out index da913e54..ce6fd127 100644 --- a/expected/pathman_only_1.out +++ b/expected/pathman_only_1.out @@ -3,13 +3,31 @@ * NOTE: This test behaves differenly on PgPro * --------------------------------------------- * - * Since 12 (608b167f9f), CTEs which are scanned once are no longer an - * optimization fence, which changes practically all plans here. There is + * -------------------- + * pathman_only_1.sql + * -------------------- + * Since 608b167f9f in PostgreSQL 12, CTEs which are scanned once are no longer + * an optimization fence, which changes practically all plans here. There is * an option to forcibly make them MATERIALIZED, but we also need to run tests * on older versions, so create pathman_only_1.out instead. * - * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, - * now it includes aliases for inherited tables. + * -------------------- + * pathman_only_2.sql + * -------------------- + * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13, output of EXPLAIN was + * changed, now it includes aliases for inherited tables. + * + * -------------------- + * pathman_only_3.sql + * -------------------- + * Since a5fc46414de in PostgreSQL 16, the order of the operands was changed, + * which affected the output of the "Prune by" in EXPLAIN. + * + * -------------------- + * pathman_only_4.sql + * -------------------- + * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was + * changed, now it displays SubPlan nodes and output parameters. */ \set VERBOSITY terse SET search_path = 'public'; @@ -275,6 +293,7 @@ WHERE val = (SELECT val FROM ONLY test_only.from_only_test Filter: (val = $0) (27 rows) -DROP SCHEMA test_only CASCADE; -NOTICE: drop cascades to 12 other objects +DROP TABLE test_only.from_only_test CASCADE; +NOTICE: drop cascades to 11 other objects +DROP SCHEMA test_only; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_only_2.out b/expected/pathman_only_2.out index 39b8f199..6aeadb76 100644 --- a/expected/pathman_only_2.out +++ b/expected/pathman_only_2.out @@ -3,13 +3,31 @@ * NOTE: This test behaves differenly on PgPro * --------------------------------------------- * - * Since 12 (608b167f9f), CTEs which are scanned once are no longer an - * optimization fence, which changes practically all plans here. There is + * -------------------- + * pathman_only_1.sql + * -------------------- + * Since 608b167f9f in PostgreSQL 12, CTEs which are scanned once are no longer + * an optimization fence, which changes practically all plans here. There is * an option to forcibly make them MATERIALIZED, but we also need to run tests * on older versions, so create pathman_only_1.out instead. * - * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, - * now it includes aliases for inherited tables. + * -------------------- + * pathman_only_2.sql + * -------------------- + * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13, output of EXPLAIN was + * changed, now it includes aliases for inherited tables. + * + * -------------------- + * pathman_only_3.sql + * -------------------- + * Since a5fc46414de in PostgreSQL 16, the order of the operands was changed, + * which affected the output of the "Prune by" in EXPLAIN. + * + * -------------------- + * pathman_only_4.sql + * -------------------- + * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was + * changed, now it displays SubPlan nodes and output parameters. */ \set VERBOSITY terse SET search_path = 'public'; @@ -36,16 +54,16 @@ UNION SELECT * FROM test_only.from_only_test; -> Append -> Seq Scan on from_only_test -> Append - -> Seq Scan on from_only_test_1 from_only_test_1_1 - -> Seq Scan on from_only_test_2 - -> Seq Scan on from_only_test_3 - -> Seq Scan on from_only_test_4 - -> Seq Scan on from_only_test_5 - -> Seq Scan on from_only_test_6 - -> Seq Scan on from_only_test_7 - -> Seq Scan on from_only_test_8 - -> Seq Scan on from_only_test_9 - -> Seq Scan on from_only_test_10 + -> Seq Scan on from_only_test_1 from_only_test_2 + -> Seq Scan on from_only_test_2 from_only_test_3 + -> Seq Scan on from_only_test_3 from_only_test_4 + -> Seq Scan on from_only_test_4 from_only_test_5 + -> Seq Scan on from_only_test_5 from_only_test_6 + -> Seq Scan on from_only_test_6 from_only_test_7 + -> Seq Scan on from_only_test_7 from_only_test_8 + -> Seq Scan on from_only_test_8 from_only_test_9 + -> Seq Scan on from_only_test_9 from_only_test_10 + -> Seq Scan on from_only_test_10 from_only_test_11 (15 rows) /* should be OK */ @@ -55,7 +73,7 @@ UNION SELECT * FROM ONLY test_only.from_only_test; QUERY PLAN ---------------------------------------------------------- HashAggregate - Group Key: from_only_test_1.val + Group Key: from_only_test.val -> Append -> Append -> Seq Scan on from_only_test_1 @@ -76,10 +94,10 @@ EXPLAIN (COSTS OFF) SELECT * FROM test_only.from_only_test UNION SELECT * FROM test_only.from_only_test UNION SELECT * FROM ONLY test_only.from_only_test; - QUERY PLAN ---------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------- HashAggregate - Group Key: from_only_test_1.val + Group Key: from_only_test.val -> Append -> Append -> Seq Scan on from_only_test_1 @@ -93,17 +111,17 @@ UNION SELECT * FROM ONLY test_only.from_only_test; -> Seq Scan on from_only_test_9 -> Seq Scan on from_only_test_10 -> Append - -> Seq Scan on from_only_test_1 from_only_test_1_1 - -> Seq Scan on from_only_test_2 from_only_test_2_1 - -> Seq Scan on from_only_test_3 from_only_test_3_1 - -> Seq Scan on from_only_test_4 from_only_test_4_1 - -> Seq Scan on from_only_test_5 from_only_test_5_1 - -> Seq Scan on from_only_test_6 from_only_test_6_1 - -> Seq Scan on from_only_test_7 from_only_test_7_1 - -> Seq Scan on from_only_test_8 from_only_test_8_1 - -> Seq Scan on from_only_test_9 from_only_test_9_1 - -> Seq Scan on from_only_test_10 from_only_test_10_1 - -> Seq Scan on from_only_test from_only_test_12 + -> Seq Scan on from_only_test_1 from_only_test_12 + -> Seq Scan on from_only_test_2 from_only_test_13 + -> Seq Scan on from_only_test_3 from_only_test_14 + -> Seq Scan on from_only_test_4 from_only_test_15 + -> Seq Scan on from_only_test_5 from_only_test_16 + -> Seq Scan on from_only_test_6 from_only_test_17 + -> Seq Scan on from_only_test_7 from_only_test_18 + -> Seq Scan on from_only_test_8 from_only_test_19 + -> Seq Scan on from_only_test_9 from_only_test_20 + -> Seq Scan on from_only_test_10 from_only_test_21 + -> Seq Scan on from_only_test from_only_test_22 (26 rows) /* should be OK */ @@ -111,34 +129,34 @@ EXPLAIN (COSTS OFF) SELECT * FROM ONLY test_only.from_only_test UNION SELECT * FROM test_only.from_only_test UNION SELECT * FROM test_only.from_only_test; - QUERY PLAN ---------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------- HashAggregate Group Key: from_only_test.val -> Append -> Seq Scan on from_only_test -> Append - -> Seq Scan on from_only_test_1 from_only_test_1_1 - -> Seq Scan on from_only_test_2 - -> Seq Scan on from_only_test_3 - -> Seq Scan on from_only_test_4 - -> Seq Scan on from_only_test_5 - -> Seq Scan on from_only_test_6 - -> Seq Scan on from_only_test_7 - -> Seq Scan on from_only_test_8 - -> Seq Scan on from_only_test_9 - -> Seq Scan on from_only_test_10 + -> Seq Scan on from_only_test_1 from_only_test_2 + -> Seq Scan on from_only_test_2 from_only_test_3 + -> Seq Scan on from_only_test_3 from_only_test_4 + -> Seq Scan on from_only_test_4 from_only_test_5 + -> Seq Scan on from_only_test_5 from_only_test_6 + -> Seq Scan on from_only_test_6 from_only_test_7 + -> Seq Scan on from_only_test_7 from_only_test_8 + -> Seq Scan on from_only_test_8 from_only_test_9 + -> Seq Scan on from_only_test_9 from_only_test_10 + -> Seq Scan on from_only_test_10 from_only_test_11 -> Append - -> Seq Scan on from_only_test_1 from_only_test_1_2 - -> Seq Scan on from_only_test_2 from_only_test_2_1 - -> Seq Scan on from_only_test_3 from_only_test_3_1 - -> Seq Scan on from_only_test_4 from_only_test_4_1 - -> Seq Scan on from_only_test_5 from_only_test_5_1 - -> Seq Scan on from_only_test_6 from_only_test_6_1 - -> Seq Scan on from_only_test_7 from_only_test_7_1 - -> Seq Scan on from_only_test_8 from_only_test_8_1 - -> Seq Scan on from_only_test_9 from_only_test_9_1 - -> Seq Scan on from_only_test_10 from_only_test_10_1 + -> Seq Scan on from_only_test_1 from_only_test_13 + -> Seq Scan on from_only_test_2 from_only_test_14 + -> Seq Scan on from_only_test_3 from_only_test_15 + -> Seq Scan on from_only_test_4 from_only_test_16 + -> Seq Scan on from_only_test_5 from_only_test_17 + -> Seq Scan on from_only_test_6 from_only_test_18 + -> Seq Scan on from_only_test_7 from_only_test_19 + -> Seq Scan on from_only_test_8 from_only_test_20 + -> Seq Scan on from_only_test_9 from_only_test_21 + -> Seq Scan on from_only_test_10 from_only_test_22 (26 rows) /* not ok, ONLY|non-ONLY in one query (this is not the case for PgPro) */ @@ -275,6 +293,7 @@ WHERE val = (SELECT val FROM ONLY test_only.from_only_test Filter: (val = $0) (27 rows) -DROP SCHEMA test_only CASCADE; -NOTICE: drop cascades to 12 other objects +DROP TABLE test_only.from_only_test CASCADE; +NOTICE: drop cascades to 11 other objects +DROP SCHEMA test_only; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_only_3.out b/expected/pathman_only_3.out new file mode 100644 index 00000000..1999309d --- /dev/null +++ b/expected/pathman_only_3.out @@ -0,0 +1,299 @@ +/* + * --------------------------------------------- + * NOTE: This test behaves differenly on PgPro + * --------------------------------------------- + * + * -------------------- + * pathman_only_1.sql + * -------------------- + * Since 608b167f9f in PostgreSQL 12, CTEs which are scanned once are no longer + * an optimization fence, which changes practically all plans here. There is + * an option to forcibly make them MATERIALIZED, but we also need to run tests + * on older versions, so create pathman_only_1.out instead. + * + * -------------------- + * pathman_only_2.sql + * -------------------- + * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13, output of EXPLAIN was + * changed, now it includes aliases for inherited tables. + * + * -------------------- + * pathman_only_3.sql + * -------------------- + * Since a5fc46414de in PostgreSQL 16, the order of the operands was changed, + * which affected the output of the "Prune by" in EXPLAIN. + * + * -------------------- + * pathman_only_4.sql + * -------------------- + * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was + * changed, now it displays SubPlan nodes and output parameters. + */ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE EXTENSION pg_pathman; +CREATE SCHEMA test_only; +/* Test special case: ONLY statement with not-ONLY for partitioned table */ +CREATE TABLE test_only.from_only_test(val INT NOT NULL); +INSERT INTO test_only.from_only_test SELECT generate_series(1, 20); +SELECT create_range_partitions('test_only.from_only_test', 'val', 1, 2); + create_range_partitions +------------------------- + 10 +(1 row) + +VACUUM ANALYZE; +/* should be OK */ +EXPLAIN (COSTS OFF) +SELECT * FROM ONLY test_only.from_only_test +UNION SELECT * FROM test_only.from_only_test; + QUERY PLAN +------------------------------------------------------------------- + HashAggregate + Group Key: from_only_test.val + -> Append + -> Seq Scan on from_only_test + -> Append + -> Seq Scan on from_only_test_1 from_only_test_2 + -> Seq Scan on from_only_test_2 from_only_test_3 + -> Seq Scan on from_only_test_3 from_only_test_4 + -> Seq Scan on from_only_test_4 from_only_test_5 + -> Seq Scan on from_only_test_5 from_only_test_6 + -> Seq Scan on from_only_test_6 from_only_test_7 + -> Seq Scan on from_only_test_7 from_only_test_8 + -> Seq Scan on from_only_test_8 from_only_test_9 + -> Seq Scan on from_only_test_9 from_only_test_10 + -> Seq Scan on from_only_test_10 from_only_test_11 +(15 rows) + +/* should be OK */ +EXPLAIN (COSTS OFF) +SELECT * FROM test_only.from_only_test +UNION SELECT * FROM ONLY test_only.from_only_test; + QUERY PLAN +---------------------------------------------------------- + HashAggregate + Group Key: from_only_test.val + -> Append + -> Append + -> Seq Scan on from_only_test_1 + -> Seq Scan on from_only_test_2 + -> Seq Scan on from_only_test_3 + -> Seq Scan on from_only_test_4 + -> Seq Scan on from_only_test_5 + -> Seq Scan on from_only_test_6 + -> Seq Scan on from_only_test_7 + -> Seq Scan on from_only_test_8 + -> Seq Scan on from_only_test_9 + -> Seq Scan on from_only_test_10 + -> Seq Scan on from_only_test from_only_test_11 +(15 rows) + +/* should be OK */ +EXPLAIN (COSTS OFF) +SELECT * FROM test_only.from_only_test +UNION SELECT * FROM test_only.from_only_test +UNION SELECT * FROM ONLY test_only.from_only_test; + QUERY PLAN +------------------------------------------------------------------- + HashAggregate + Group Key: from_only_test.val + -> Append + -> Append + -> Seq Scan on from_only_test_1 + -> Seq Scan on from_only_test_2 + -> Seq Scan on from_only_test_3 + -> Seq Scan on from_only_test_4 + -> Seq Scan on from_only_test_5 + -> Seq Scan on from_only_test_6 + -> Seq Scan on from_only_test_7 + -> Seq Scan on from_only_test_8 + -> Seq Scan on from_only_test_9 + -> Seq Scan on from_only_test_10 + -> Append + -> Seq Scan on from_only_test_1 from_only_test_12 + -> Seq Scan on from_only_test_2 from_only_test_13 + -> Seq Scan on from_only_test_3 from_only_test_14 + -> Seq Scan on from_only_test_4 from_only_test_15 + -> Seq Scan on from_only_test_5 from_only_test_16 + -> Seq Scan on from_only_test_6 from_only_test_17 + -> Seq Scan on from_only_test_7 from_only_test_18 + -> Seq Scan on from_only_test_8 from_only_test_19 + -> Seq Scan on from_only_test_9 from_only_test_20 + -> Seq Scan on from_only_test_10 from_only_test_21 + -> Seq Scan on from_only_test from_only_test_22 +(26 rows) + +/* should be OK */ +EXPLAIN (COSTS OFF) +SELECT * FROM ONLY test_only.from_only_test +UNION SELECT * FROM test_only.from_only_test +UNION SELECT * FROM test_only.from_only_test; + QUERY PLAN +------------------------------------------------------------------- + HashAggregate + Group Key: from_only_test.val + -> Append + -> Seq Scan on from_only_test + -> Append + -> Seq Scan on from_only_test_1 from_only_test_2 + -> Seq Scan on from_only_test_2 from_only_test_3 + -> Seq Scan on from_only_test_3 from_only_test_4 + -> Seq Scan on from_only_test_4 from_only_test_5 + -> Seq Scan on from_only_test_5 from_only_test_6 + -> Seq Scan on from_only_test_6 from_only_test_7 + -> Seq Scan on from_only_test_7 from_only_test_8 + -> Seq Scan on from_only_test_8 from_only_test_9 + -> Seq Scan on from_only_test_9 from_only_test_10 + -> Seq Scan on from_only_test_10 from_only_test_11 + -> Append + -> Seq Scan on from_only_test_1 from_only_test_13 + -> Seq Scan on from_only_test_2 from_only_test_14 + -> Seq Scan on from_only_test_3 from_only_test_15 + -> Seq Scan on from_only_test_4 from_only_test_16 + -> Seq Scan on from_only_test_5 from_only_test_17 + -> Seq Scan on from_only_test_6 from_only_test_18 + -> Seq Scan on from_only_test_7 from_only_test_19 + -> Seq Scan on from_only_test_8 from_only_test_20 + -> Seq Scan on from_only_test_9 from_only_test_21 + -> Seq Scan on from_only_test_10 from_only_test_22 +(26 rows) + +/* not ok, ONLY|non-ONLY in one query (this is not the case for PgPro) */ +EXPLAIN (COSTS OFF) +SELECT * FROM test_only.from_only_test a +JOIN ONLY test_only.from_only_test b USING(val); + QUERY PLAN +--------------------------------------------- + Nested Loop + -> Seq Scan on from_only_test b + -> Custom Scan (RuntimeAppend) + Prune by: (a.val = b.val) + -> Seq Scan on from_only_test_1 a + Filter: (b.val = val) + -> Seq Scan on from_only_test_2 a + Filter: (b.val = val) + -> Seq Scan on from_only_test_3 a + Filter: (b.val = val) + -> Seq Scan on from_only_test_4 a + Filter: (b.val = val) + -> Seq Scan on from_only_test_5 a + Filter: (b.val = val) + -> Seq Scan on from_only_test_6 a + Filter: (b.val = val) + -> Seq Scan on from_only_test_7 a + Filter: (b.val = val) + -> Seq Scan on from_only_test_8 a + Filter: (b.val = val) + -> Seq Scan on from_only_test_9 a + Filter: (b.val = val) + -> Seq Scan on from_only_test_10 a + Filter: (b.val = val) +(24 rows) + +/* should be OK */ +EXPLAIN (COSTS OFF) +WITH q1 AS (SELECT * FROM test_only.from_only_test), + q2 AS (SELECT * FROM ONLY test_only.from_only_test) +SELECT * FROM q1 JOIN q2 USING(val); + QUERY PLAN +--------------------------------------------------------------- + Nested Loop + -> Seq Scan on from_only_test from_only_test_1 + -> Custom Scan (RuntimeAppend) + Prune by: (from_only_test.val = from_only_test_1.val) + -> Seq Scan on from_only_test_1 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_2 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_3 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_4 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_5 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_6 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_7 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_8 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_9 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_10 from_only_test + Filter: (from_only_test_1.val = val) +(24 rows) + +/* should be OK */ +EXPLAIN (COSTS OFF) +WITH q1 AS (SELECT * FROM ONLY test_only.from_only_test) +SELECT * FROM test_only.from_only_test JOIN q1 USING(val); + QUERY PLAN +--------------------------------------------------------------- + Nested Loop + -> Seq Scan on from_only_test from_only_test_1 + -> Custom Scan (RuntimeAppend) + Prune by: (from_only_test.val = from_only_test_1.val) + -> Seq Scan on from_only_test_1 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_2 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_3 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_4 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_5 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_6 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_7 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_8 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_9 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_10 from_only_test + Filter: (from_only_test_1.val = val) +(24 rows) + +/* should be OK */ +EXPLAIN (COSTS OFF) +SELECT * FROM test_only.from_only_test +WHERE val = (SELECT val FROM ONLY test_only.from_only_test + ORDER BY val ASC + LIMIT 1); + QUERY PLAN +----------------------------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (from_only_test.val = $0) + InitPlan 1 (returns $0) + -> Limit + -> Sort + Sort Key: from_only_test_1.val + -> Seq Scan on from_only_test from_only_test_1 + -> Seq Scan on from_only_test_1 from_only_test + Filter: (val = $0) + -> Seq Scan on from_only_test_2 from_only_test + Filter: (val = $0) + -> Seq Scan on from_only_test_3 from_only_test + Filter: (val = $0) + -> Seq Scan on from_only_test_4 from_only_test + Filter: (val = $0) + -> Seq Scan on from_only_test_5 from_only_test + Filter: (val = $0) + -> Seq Scan on from_only_test_6 from_only_test + Filter: (val = $0) + -> Seq Scan on from_only_test_7 from_only_test + Filter: (val = $0) + -> Seq Scan on from_only_test_8 from_only_test + Filter: (val = $0) + -> Seq Scan on from_only_test_9 from_only_test + Filter: (val = $0) + -> Seq Scan on from_only_test_10 from_only_test + Filter: (val = $0) +(27 rows) + +DROP TABLE test_only.from_only_test CASCADE; +NOTICE: drop cascades to 11 other objects +DROP SCHEMA test_only; +DROP EXTENSION pg_pathman; diff --git a/expected/pathman_only_4.out b/expected/pathman_only_4.out new file mode 100644 index 00000000..fbcc397c --- /dev/null +++ b/expected/pathman_only_4.out @@ -0,0 +1,299 @@ +/* + * --------------------------------------------- + * NOTE: This test behaves differenly on PgPro + * --------------------------------------------- + * + * -------------------- + * pathman_only_1.sql + * -------------------- + * Since 608b167f9f in PostgreSQL 12, CTEs which are scanned once are no longer + * an optimization fence, which changes practically all plans here. There is + * an option to forcibly make them MATERIALIZED, but we also need to run tests + * on older versions, so create pathman_only_1.out instead. + * + * -------------------- + * pathman_only_2.sql + * -------------------- + * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13, output of EXPLAIN was + * changed, now it includes aliases for inherited tables. + * + * -------------------- + * pathman_only_3.sql + * -------------------- + * Since a5fc46414de in PostgreSQL 16, the order of the operands was changed, + * which affected the output of the "Prune by" in EXPLAIN. + * + * -------------------- + * pathman_only_4.sql + * -------------------- + * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was + * changed, now it displays SubPlan nodes and output parameters. + */ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE EXTENSION pg_pathman; +CREATE SCHEMA test_only; +/* Test special case: ONLY statement with not-ONLY for partitioned table */ +CREATE TABLE test_only.from_only_test(val INT NOT NULL); +INSERT INTO test_only.from_only_test SELECT generate_series(1, 20); +SELECT create_range_partitions('test_only.from_only_test', 'val', 1, 2); + create_range_partitions +------------------------- + 10 +(1 row) + +VACUUM ANALYZE; +/* should be OK */ +EXPLAIN (COSTS OFF) +SELECT * FROM ONLY test_only.from_only_test +UNION SELECT * FROM test_only.from_only_test; + QUERY PLAN +------------------------------------------------------------------- + HashAggregate + Group Key: from_only_test.val + -> Append + -> Seq Scan on from_only_test + -> Append + -> Seq Scan on from_only_test_1 from_only_test_2 + -> Seq Scan on from_only_test_2 from_only_test_3 + -> Seq Scan on from_only_test_3 from_only_test_4 + -> Seq Scan on from_only_test_4 from_only_test_5 + -> Seq Scan on from_only_test_5 from_only_test_6 + -> Seq Scan on from_only_test_6 from_only_test_7 + -> Seq Scan on from_only_test_7 from_only_test_8 + -> Seq Scan on from_only_test_8 from_only_test_9 + -> Seq Scan on from_only_test_9 from_only_test_10 + -> Seq Scan on from_only_test_10 from_only_test_11 +(15 rows) + +/* should be OK */ +EXPLAIN (COSTS OFF) +SELECT * FROM test_only.from_only_test +UNION SELECT * FROM ONLY test_only.from_only_test; + QUERY PLAN +---------------------------------------------------------- + HashAggregate + Group Key: from_only_test.val + -> Append + -> Append + -> Seq Scan on from_only_test_1 + -> Seq Scan on from_only_test_2 + -> Seq Scan on from_only_test_3 + -> Seq Scan on from_only_test_4 + -> Seq Scan on from_only_test_5 + -> Seq Scan on from_only_test_6 + -> Seq Scan on from_only_test_7 + -> Seq Scan on from_only_test_8 + -> Seq Scan on from_only_test_9 + -> Seq Scan on from_only_test_10 + -> Seq Scan on from_only_test from_only_test_11 +(15 rows) + +/* should be OK */ +EXPLAIN (COSTS OFF) +SELECT * FROM test_only.from_only_test +UNION SELECT * FROM test_only.from_only_test +UNION SELECT * FROM ONLY test_only.from_only_test; + QUERY PLAN +------------------------------------------------------------------- + HashAggregate + Group Key: from_only_test.val + -> Append + -> Append + -> Seq Scan on from_only_test_1 + -> Seq Scan on from_only_test_2 + -> Seq Scan on from_only_test_3 + -> Seq Scan on from_only_test_4 + -> Seq Scan on from_only_test_5 + -> Seq Scan on from_only_test_6 + -> Seq Scan on from_only_test_7 + -> Seq Scan on from_only_test_8 + -> Seq Scan on from_only_test_9 + -> Seq Scan on from_only_test_10 + -> Append + -> Seq Scan on from_only_test_1 from_only_test_12 + -> Seq Scan on from_only_test_2 from_only_test_13 + -> Seq Scan on from_only_test_3 from_only_test_14 + -> Seq Scan on from_only_test_4 from_only_test_15 + -> Seq Scan on from_only_test_5 from_only_test_16 + -> Seq Scan on from_only_test_6 from_only_test_17 + -> Seq Scan on from_only_test_7 from_only_test_18 + -> Seq Scan on from_only_test_8 from_only_test_19 + -> Seq Scan on from_only_test_9 from_only_test_20 + -> Seq Scan on from_only_test_10 from_only_test_21 + -> Seq Scan on from_only_test from_only_test_22 +(26 rows) + +/* should be OK */ +EXPLAIN (COSTS OFF) +SELECT * FROM ONLY test_only.from_only_test +UNION SELECT * FROM test_only.from_only_test +UNION SELECT * FROM test_only.from_only_test; + QUERY PLAN +------------------------------------------------------------------- + HashAggregate + Group Key: from_only_test.val + -> Append + -> Seq Scan on from_only_test + -> Append + -> Seq Scan on from_only_test_1 from_only_test_2 + -> Seq Scan on from_only_test_2 from_only_test_3 + -> Seq Scan on from_only_test_3 from_only_test_4 + -> Seq Scan on from_only_test_4 from_only_test_5 + -> Seq Scan on from_only_test_5 from_only_test_6 + -> Seq Scan on from_only_test_6 from_only_test_7 + -> Seq Scan on from_only_test_7 from_only_test_8 + -> Seq Scan on from_only_test_8 from_only_test_9 + -> Seq Scan on from_only_test_9 from_only_test_10 + -> Seq Scan on from_only_test_10 from_only_test_11 + -> Append + -> Seq Scan on from_only_test_1 from_only_test_13 + -> Seq Scan on from_only_test_2 from_only_test_14 + -> Seq Scan on from_only_test_3 from_only_test_15 + -> Seq Scan on from_only_test_4 from_only_test_16 + -> Seq Scan on from_only_test_5 from_only_test_17 + -> Seq Scan on from_only_test_6 from_only_test_18 + -> Seq Scan on from_only_test_7 from_only_test_19 + -> Seq Scan on from_only_test_8 from_only_test_20 + -> Seq Scan on from_only_test_9 from_only_test_21 + -> Seq Scan on from_only_test_10 from_only_test_22 +(26 rows) + +/* not ok, ONLY|non-ONLY in one query (this is not the case for PgPro) */ +EXPLAIN (COSTS OFF) +SELECT * FROM test_only.from_only_test a +JOIN ONLY test_only.from_only_test b USING(val); + QUERY PLAN +--------------------------------------------- + Nested Loop + -> Seq Scan on from_only_test b + -> Custom Scan (RuntimeAppend) + Prune by: (a.val = b.val) + -> Seq Scan on from_only_test_1 a + Filter: (b.val = val) + -> Seq Scan on from_only_test_2 a + Filter: (b.val = val) + -> Seq Scan on from_only_test_3 a + Filter: (b.val = val) + -> Seq Scan on from_only_test_4 a + Filter: (b.val = val) + -> Seq Scan on from_only_test_5 a + Filter: (b.val = val) + -> Seq Scan on from_only_test_6 a + Filter: (b.val = val) + -> Seq Scan on from_only_test_7 a + Filter: (b.val = val) + -> Seq Scan on from_only_test_8 a + Filter: (b.val = val) + -> Seq Scan on from_only_test_9 a + Filter: (b.val = val) + -> Seq Scan on from_only_test_10 a + Filter: (b.val = val) +(24 rows) + +/* should be OK */ +EXPLAIN (COSTS OFF) +WITH q1 AS (SELECT * FROM test_only.from_only_test), + q2 AS (SELECT * FROM ONLY test_only.from_only_test) +SELECT * FROM q1 JOIN q2 USING(val); + QUERY PLAN +--------------------------------------------------------------- + Nested Loop + -> Seq Scan on from_only_test from_only_test_1 + -> Custom Scan (RuntimeAppend) + Prune by: (from_only_test.val = from_only_test_1.val) + -> Seq Scan on from_only_test_1 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_2 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_3 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_4 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_5 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_6 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_7 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_8 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_9 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_10 from_only_test + Filter: (from_only_test_1.val = val) +(24 rows) + +/* should be OK */ +EXPLAIN (COSTS OFF) +WITH q1 AS (SELECT * FROM ONLY test_only.from_only_test) +SELECT * FROM test_only.from_only_test JOIN q1 USING(val); + QUERY PLAN +--------------------------------------------------------------- + Nested Loop + -> Seq Scan on from_only_test from_only_test_1 + -> Custom Scan (RuntimeAppend) + Prune by: (from_only_test.val = from_only_test_1.val) + -> Seq Scan on from_only_test_1 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_2 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_3 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_4 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_5 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_6 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_7 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_8 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_9 from_only_test + Filter: (from_only_test_1.val = val) + -> Seq Scan on from_only_test_10 from_only_test + Filter: (from_only_test_1.val = val) +(24 rows) + +/* should be OK */ +EXPLAIN (COSTS OFF) +SELECT * FROM test_only.from_only_test +WHERE val = (SELECT val FROM ONLY test_only.from_only_test + ORDER BY val ASC + LIMIT 1); + QUERY PLAN +----------------------------------------------------------------- + Custom Scan (RuntimeAppend) + Prune by: (from_only_test.val = (InitPlan 1).col1) + InitPlan 1 + -> Limit + -> Sort + Sort Key: from_only_test_1.val + -> Seq Scan on from_only_test from_only_test_1 + -> Seq Scan on from_only_test_1 from_only_test + Filter: (val = (InitPlan 1).col1) + -> Seq Scan on from_only_test_2 from_only_test + Filter: (val = (InitPlan 1).col1) + -> Seq Scan on from_only_test_3 from_only_test + Filter: (val = (InitPlan 1).col1) + -> Seq Scan on from_only_test_4 from_only_test + Filter: (val = (InitPlan 1).col1) + -> Seq Scan on from_only_test_5 from_only_test + Filter: (val = (InitPlan 1).col1) + -> Seq Scan on from_only_test_6 from_only_test + Filter: (val = (InitPlan 1).col1) + -> Seq Scan on from_only_test_7 from_only_test + Filter: (val = (InitPlan 1).col1) + -> Seq Scan on from_only_test_8 from_only_test + Filter: (val = (InitPlan 1).col1) + -> Seq Scan on from_only_test_9 from_only_test + Filter: (val = (InitPlan 1).col1) + -> Seq Scan on from_only_test_10 from_only_test + Filter: (val = (InitPlan 1).col1) +(27 rows) + +DROP TABLE test_only.from_only_test CASCADE; +NOTICE: drop cascades to 11 other objects +DROP SCHEMA test_only; +DROP EXTENSION pg_pathman; diff --git a/expected/pathman_param_upd_del.out b/expected/pathman_param_upd_del.out index ad935579..28fa616d 100644 --- a/expected/pathman_param_upd_del.out +++ b/expected/pathman_param_upd_del.out @@ -185,6 +185,7 @@ EXPLAIN (COSTS OFF) EXECUTE del(11); (3 rows) DEALLOCATE del; -DROP SCHEMA param_upd_del CASCADE; -NOTICE: drop cascades to 11 other objects +DROP TABLE param_upd_del.test CASCADE; +NOTICE: drop cascades to 10 other objects +DROP SCHEMA param_upd_del; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_permissions.out b/expected/pathman_permissions.out index d03588c7..a29865d0 100644 --- a/expected/pathman_permissions.out +++ b/expected/pathman_permissions.out @@ -2,107 +2,107 @@ SET search_path = 'public'; CREATE EXTENSION pg_pathman; CREATE SCHEMA permissions; -CREATE ROLE user1 LOGIN; -CREATE ROLE user2 LOGIN; -GRANT USAGE, CREATE ON SCHEMA permissions TO user1; -GRANT USAGE, CREATE ON SCHEMA permissions TO user2; +CREATE ROLE pathman_user1 LOGIN; +CREATE ROLE pathman_user2 LOGIN; +GRANT USAGE, CREATE ON SCHEMA permissions TO pathman_user1; +GRANT USAGE, CREATE ON SCHEMA permissions TO pathman_user2; /* Switch to #1 */ -SET ROLE user1; -CREATE TABLE permissions.user1_table(id serial, a int); -INSERT INTO permissions.user1_table SELECT g, g FROM generate_series(1, 20) as g; +SET ROLE pathman_user1; +CREATE TABLE permissions.pathman_user1_table(id serial, a int); +INSERT INTO permissions.pathman_user1_table SELECT g, g FROM generate_series(1, 20) as g; /* Should fail (can't SELECT) */ -SET ROLE user2; +SET ROLE pathman_user2; DO $$ BEGIN - SELECT create_range_partitions('permissions.user1_table', 'id', 1, 10, 2); + SELECT create_range_partitions('permissions.pathman_user1_table', 'id', 1, 10, 2); EXCEPTION WHEN insufficient_privilege THEN RAISE NOTICE 'Insufficient priviliges'; END$$; NOTICE: Insufficient priviliges -/* Grant SELECT to user2 */ -SET ROLE user1; -GRANT SELECT ON permissions.user1_table TO user2; +/* Grant SELECT to pathman_user2 */ +SET ROLE pathman_user1; +GRANT SELECT ON permissions.pathman_user1_table TO pathman_user2; /* Should fail (don't own parent) */ -SET ROLE user2; +SET ROLE pathman_user2; DO $$ BEGIN - SELECT create_range_partitions('permissions.user1_table', 'id', 1, 10, 2); + SELECT create_range_partitions('permissions.pathman_user1_table', 'id', 1, 10, 2); EXCEPTION WHEN insufficient_privilege THEN RAISE NOTICE 'Insufficient priviliges'; END$$; NOTICE: Insufficient priviliges /* Should be ok */ -SET ROLE user1; -SELECT create_range_partitions('permissions.user1_table', 'id', 1, 10, 2); +SET ROLE pathman_user1; +SELECT create_range_partitions('permissions.pathman_user1_table', 'id', 1, 10, 2); create_range_partitions ------------------------- 2 (1 row) /* Should be able to see */ -SET ROLE user2; +SET ROLE pathman_user2; SELECT * FROM pathman_config; - partrel | expr | parttype | range_interval --------------------------+------+----------+---------------- - permissions.user1_table | id | 2 | 10 + partrel | expr | parttype | range_interval +---------------------------------+------+----------+---------------- + permissions.pathman_user1_table | id | 2 | 10 (1 row) SELECT * FROM pathman_config_params; - partrel | enable_parent | auto | init_callback | spawn_using_bgw --------------------------+---------------+------+---------------+----------------- - permissions.user1_table | f | t | | f + partrel | enable_parent | auto | init_callback | spawn_using_bgw +---------------------------------+---------------+------+---------------+----------------- + permissions.pathman_user1_table | f | t | | f (1 row) /* Should fail */ -SET ROLE user2; -SELECT set_enable_parent('permissions.user1_table', true); -WARNING: only the owner or superuser can change partitioning configuration of table "user1_table" +SET ROLE pathman_user2; +SELECT set_enable_parent('permissions.pathman_user1_table', true); +WARNING: only the owner or superuser can change partitioning configuration of table "pathman_user1_table" ERROR: new row violates row-level security policy for table "pathman_config_params" -SELECT set_auto('permissions.user1_table', false); -WARNING: only the owner or superuser can change partitioning configuration of table "user1_table" +SELECT set_auto('permissions.pathman_user1_table', false); +WARNING: only the owner or superuser can change partitioning configuration of table "pathman_user1_table" ERROR: new row violates row-level security policy for table "pathman_config_params" /* Should fail */ -SET ROLE user2; +SET ROLE pathman_user2; DELETE FROM pathman_config -WHERE partrel = 'permissions.user1_table'::regclass; -WARNING: only the owner or superuser can change partitioning configuration of table "user1_table" +WHERE partrel = 'permissions.pathman_user1_table'::regclass; +WARNING: only the owner or superuser can change partitioning configuration of table "pathman_user1_table" /* No rights to insert, should fail */ -SET ROLE user2; +SET ROLE pathman_user2; DO $$ BEGIN - INSERT INTO permissions.user1_table (id, a) VALUES (35, 0); + INSERT INTO permissions.pathman_user1_table (id, a) VALUES (35, 0); EXCEPTION WHEN insufficient_privilege THEN RAISE NOTICE 'Insufficient priviliges'; END$$; NOTICE: Insufficient priviliges /* No rights to create partitions (need INSERT privilege) */ -SET ROLE user2; -SELECT prepend_range_partition('permissions.user1_table'); -ERROR: permission denied for parent relation "user1_table" -/* Allow user2 to create partitions */ -SET ROLE user1; -GRANT INSERT ON permissions.user1_table TO user2; -GRANT UPDATE(a) ON permissions.user1_table TO user2; /* per-column ACL */ +SET ROLE pathman_user2; +SELECT prepend_range_partition('permissions.pathman_user1_table'); +ERROR: permission denied for parent relation "pathman_user1_table" +/* Allow pathman_user2 to create partitions */ +SET ROLE pathman_user1; +GRANT INSERT ON permissions.pathman_user1_table TO pathman_user2; +GRANT UPDATE(a) ON permissions.pathman_user1_table TO pathman_user2; /* per-column ACL */ /* Should be able to prepend a partition */ -SET ROLE user2; -SELECT prepend_range_partition('permissions.user1_table'); - prepend_range_partition ---------------------------- - permissions.user1_table_4 +SET ROLE pathman_user2; +SELECT prepend_range_partition('permissions.pathman_user1_table'); + prepend_range_partition +----------------------------------- + permissions.pathman_user1_table_4 (1 row) SELECT attname, attacl FROM pg_attribute WHERE attrelid = (SELECT "partition" FROM pathman_partition_list - WHERE parent = 'permissions.user1_table'::REGCLASS + WHERE parent = 'permissions.pathman_user1_table'::REGCLASS ORDER BY range_min::int ASC /* prepend */ LIMIT 1) ORDER BY attname; /* check ACL for each column */ - attname | attacl -----------+----------------- - a | {user2=w/user1} + attname | attacl +----------+--------------------------------- + a | {pathman_user2=w/pathman_user1} cmax | cmin | ctid | @@ -113,8 +113,8 @@ ORDER BY attname; /* check ACL for each column */ (8 rows) /* Have rights, should be ok (parent's ACL is shared by new children) */ -SET ROLE user2; -INSERT INTO permissions.user1_table (id, a) VALUES (35, 0) RETURNING *; +SET ROLE pathman_user2; +INSERT INTO permissions.pathman_user1_table (id, a) VALUES (35, 0) RETURNING *; id | a ----+--- 35 | 0 @@ -122,76 +122,76 @@ INSERT INTO permissions.user1_table (id, a) VALUES (35, 0) RETURNING *; SELECT relname, relacl FROM pg_class WHERE oid = ANY (SELECT "partition" FROM pathman_partition_list - WHERE parent = 'permissions.user1_table'::REGCLASS + WHERE parent = 'permissions.pathman_user1_table'::REGCLASS ORDER BY range_max::int DESC /* append */ LIMIT 3) -ORDER BY relname; /* we also check ACL for "user1_table_2" */ - relname | relacl ----------------+-------------------------------------- - user1_table_2 | {user1=arwdDxt/user1,user2=r/user1} - user1_table_5 | {user1=arwdDxt/user1,user2=ar/user1} - user1_table_6 | {user1=arwdDxt/user1,user2=ar/user1} +ORDER BY relname; /* we also check ACL for "pathman_user1_table_2" */ + relname | relacl +-----------------------+---------------------------------------------------------------------- + pathman_user1_table_2 | {pathman_user1=arwdDxt/pathman_user1,pathman_user2=r/pathman_user1} + pathman_user1_table_5 | {pathman_user1=arwdDxt/pathman_user1,pathman_user2=ar/pathman_user1} + pathman_user1_table_6 | {pathman_user1=arwdDxt/pathman_user1,pathman_user2=ar/pathman_user1} (3 rows) /* Try to drop partition, should fail */ DO $$ BEGIN - SELECT drop_range_partition('permissions.user1_table_4'); + SELECT drop_range_partition('permissions.pathman_user1_table_4'); EXCEPTION WHEN insufficient_privilege THEN RAISE NOTICE 'Insufficient priviliges'; END$$; NOTICE: Insufficient priviliges /* Disable automatic partition creation */ -SET ROLE user1; -SELECT set_auto('permissions.user1_table', false); +SET ROLE pathman_user1; +SELECT set_auto('permissions.pathman_user1_table', false); set_auto ---------- (1 row) /* Partition creation, should fail */ -SET ROLE user2; -INSERT INTO permissions.user1_table (id, a) VALUES (55, 0) RETURNING *; +SET ROLE pathman_user2; +INSERT INTO permissions.pathman_user1_table (id, a) VALUES (55, 0) RETURNING *; ERROR: no suitable partition for key '55' /* Finally drop partitions */ -SET ROLE user1; -SELECT drop_partitions('permissions.user1_table'); -NOTICE: 10 rows copied from permissions.user1_table_1 -NOTICE: 10 rows copied from permissions.user1_table_2 -NOTICE: 0 rows copied from permissions.user1_table_4 -NOTICE: 0 rows copied from permissions.user1_table_5 -NOTICE: 1 rows copied from permissions.user1_table_6 +SET ROLE pathman_user1; +SELECT drop_partitions('permissions.pathman_user1_table'); +NOTICE: 10 rows copied from permissions.pathman_user1_table_1 +NOTICE: 10 rows copied from permissions.pathman_user1_table_2 +NOTICE: 0 rows copied from permissions.pathman_user1_table_4 +NOTICE: 0 rows copied from permissions.pathman_user1_table_5 +NOTICE: 1 rows copied from permissions.pathman_user1_table_6 drop_partitions ----------------- 5 (1 row) /* Switch to #2 */ -SET ROLE user2; +SET ROLE pathman_user2; /* Test ddl event trigger */ -CREATE TABLE permissions.user2_table(id serial); -SELECT create_hash_partitions('permissions.user2_table', 'id', 3); +CREATE TABLE permissions.pathman_user2_table(id serial); +SELECT create_hash_partitions('permissions.pathman_user2_table', 'id', 3); create_hash_partitions ------------------------ 3 (1 row) -INSERT INTO permissions.user2_table SELECT generate_series(1, 30); -SELECT drop_partitions('permissions.user2_table'); -NOTICE: 9 rows copied from permissions.user2_table_0 -NOTICE: 11 rows copied from permissions.user2_table_1 -NOTICE: 10 rows copied from permissions.user2_table_2 +INSERT INTO permissions.pathman_user2_table SELECT generate_series(1, 30); +SELECT drop_partitions('permissions.pathman_user2_table'); +NOTICE: 9 rows copied from permissions.pathman_user2_table_0 +NOTICE: 11 rows copied from permissions.pathman_user2_table_1 +NOTICE: 10 rows copied from permissions.pathman_user2_table_2 drop_partitions ----------------- 3 (1 row) /* Switch to #1 */ -SET ROLE user1; +SET ROLE pathman_user1; CREATE TABLE permissions.dropped_column(a int, val int not null, b int, c int); INSERT INTO permissions.dropped_column SELECT i,i,i,i FROM generate_series(1, 30) i; -GRANT SELECT(val), INSERT(val) ON permissions.dropped_column TO user2; +GRANT SELECT(val), INSERT(val) ON permissions.dropped_column TO pathman_user2; SELECT create_range_partitions('permissions.dropped_column', 'val', 1, 10); create_range_partitions ------------------------- @@ -203,11 +203,11 @@ WHERE attrelid = ANY (SELECT "partition" FROM pathman_partition_list WHERE parent = 'permissions.dropped_column'::REGCLASS) AND attacl IS NOT NULL ORDER BY attrelid::regclass::text; /* check ACL for each column */ - attrelid | attname | attacl -------------------------------+---------+------------------ - permissions.dropped_column_1 | val | {user2=ar/user1} - permissions.dropped_column_2 | val | {user2=ar/user1} - permissions.dropped_column_3 | val | {user2=ar/user1} + attrelid | attname | attacl +------------------------------+---------+---------------------------------- + permissions.dropped_column_1 | val | {pathman_user2=ar/pathman_user1} + permissions.dropped_column_2 | val | {pathman_user2=ar/pathman_user1} + permissions.dropped_column_3 | val | {pathman_user2=ar/pathman_user1} (3 rows) ALTER TABLE permissions.dropped_column DROP COLUMN a; /* DROP "a" */ @@ -222,12 +222,12 @@ WHERE attrelid = ANY (SELECT "partition" FROM pathman_partition_list WHERE parent = 'permissions.dropped_column'::REGCLASS) AND attacl IS NOT NULL ORDER BY attrelid::regclass::text; /* check ACL for each column (+1 partition) */ - attrelid | attname | attacl -------------------------------+---------+------------------ - permissions.dropped_column_1 | val | {user2=ar/user1} - permissions.dropped_column_2 | val | {user2=ar/user1} - permissions.dropped_column_3 | val | {user2=ar/user1} - permissions.dropped_column_4 | val | {user2=ar/user1} + attrelid | attname | attacl +------------------------------+---------+---------------------------------- + permissions.dropped_column_1 | val | {pathman_user2=ar/pathman_user1} + permissions.dropped_column_2 | val | {pathman_user2=ar/pathman_user1} + permissions.dropped_column_3 | val | {pathman_user2=ar/pathman_user1} + permissions.dropped_column_4 | val | {pathman_user2=ar/pathman_user1} (4 rows) ALTER TABLE permissions.dropped_column DROP COLUMN b; /* DROP "b" */ @@ -242,22 +242,22 @@ WHERE attrelid = ANY (SELECT "partition" FROM pathman_partition_list WHERE parent = 'permissions.dropped_column'::REGCLASS) AND attacl IS NOT NULL ORDER BY attrelid::regclass::text; /* check ACL for each column (+1 partition) */ - attrelid | attname | attacl -------------------------------+---------+------------------ - permissions.dropped_column_1 | val | {user2=ar/user1} - permissions.dropped_column_2 | val | {user2=ar/user1} - permissions.dropped_column_3 | val | {user2=ar/user1} - permissions.dropped_column_4 | val | {user2=ar/user1} - permissions.dropped_column_5 | val | {user2=ar/user1} + attrelid | attname | attacl +------------------------------+---------+---------------------------------- + permissions.dropped_column_1 | val | {pathman_user2=ar/pathman_user1} + permissions.dropped_column_2 | val | {pathman_user2=ar/pathman_user1} + permissions.dropped_column_3 | val | {pathman_user2=ar/pathman_user1} + permissions.dropped_column_4 | val | {pathman_user2=ar/pathman_user1} + permissions.dropped_column_5 | val | {pathman_user2=ar/pathman_user1} (5 rows) DROP TABLE permissions.dropped_column CASCADE; NOTICE: drop cascades to 6 other objects /* Finally reset user */ RESET ROLE; -DROP OWNED BY user1; -DROP OWNED BY user2; -DROP USER user1; -DROP USER user2; -DROP SCHEMA permissions CASCADE; +DROP OWNED BY pathman_user1; +DROP OWNED BY pathman_user2; +DROP USER pathman_user1; +DROP USER pathman_user2; +DROP SCHEMA permissions; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_permissions_1.out b/expected/pathman_permissions_1.out new file mode 100644 index 00000000..dc976aae --- /dev/null +++ b/expected/pathman_permissions_1.out @@ -0,0 +1,263 @@ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE EXTENSION pg_pathman; +CREATE SCHEMA permissions; +CREATE ROLE pathman_user1 LOGIN; +CREATE ROLE pathman_user2 LOGIN; +GRANT USAGE, CREATE ON SCHEMA permissions TO pathman_user1; +GRANT USAGE, CREATE ON SCHEMA permissions TO pathman_user2; +/* Switch to #1 */ +SET ROLE pathman_user1; +CREATE TABLE permissions.pathman_user1_table(id serial, a int); +INSERT INTO permissions.pathman_user1_table SELECT g, g FROM generate_series(1, 20) as g; +/* Should fail (can't SELECT) */ +SET ROLE pathman_user2; +DO $$ +BEGIN + SELECT create_range_partitions('permissions.pathman_user1_table', 'id', 1, 10, 2); +EXCEPTION + WHEN insufficient_privilege THEN + RAISE NOTICE 'Insufficient priviliges'; +END$$; +NOTICE: Insufficient priviliges +/* Grant SELECT to pathman_user2 */ +SET ROLE pathman_user1; +GRANT SELECT ON permissions.pathman_user1_table TO pathman_user2; +/* Should fail (don't own parent) */ +SET ROLE pathman_user2; +DO $$ +BEGIN + SELECT create_range_partitions('permissions.pathman_user1_table', 'id', 1, 10, 2); +EXCEPTION + WHEN insufficient_privilege THEN + RAISE NOTICE 'Insufficient priviliges'; +END$$; +NOTICE: Insufficient priviliges +/* Should be ok */ +SET ROLE pathman_user1; +SELECT create_range_partitions('permissions.pathman_user1_table', 'id', 1, 10, 2); + create_range_partitions +------------------------- + 2 +(1 row) + +/* Should be able to see */ +SET ROLE pathman_user2; +SELECT * FROM pathman_config; + partrel | expr | parttype | range_interval +---------------------------------+------+----------+---------------- + permissions.pathman_user1_table | id | 2 | 10 +(1 row) + +SELECT * FROM pathman_config_params; + partrel | enable_parent | auto | init_callback | spawn_using_bgw +---------------------------------+---------------+------+---------------+----------------- + permissions.pathman_user1_table | f | t | | f +(1 row) + +/* Should fail */ +SET ROLE pathman_user2; +SELECT set_enable_parent('permissions.pathman_user1_table', true); +WARNING: only the owner or superuser can change partitioning configuration of table "pathman_user1_table" +ERROR: new row violates row-level security policy for table "pathman_config_params" +SELECT set_auto('permissions.pathman_user1_table', false); +WARNING: only the owner or superuser can change partitioning configuration of table "pathman_user1_table" +ERROR: new row violates row-level security policy for table "pathman_config_params" +/* Should fail */ +SET ROLE pathman_user2; +DELETE FROM pathman_config +WHERE partrel = 'permissions.pathman_user1_table'::regclass; +WARNING: only the owner or superuser can change partitioning configuration of table "pathman_user1_table" +/* No rights to insert, should fail */ +SET ROLE pathman_user2; +DO $$ +BEGIN + INSERT INTO permissions.pathman_user1_table (id, a) VALUES (35, 0); +EXCEPTION + WHEN insufficient_privilege THEN + RAISE NOTICE 'Insufficient priviliges'; +END$$; +NOTICE: Insufficient priviliges +/* No rights to create partitions (need INSERT privilege) */ +SET ROLE pathman_user2; +SELECT prepend_range_partition('permissions.pathman_user1_table'); +ERROR: permission denied for parent relation "pathman_user1_table" +/* Allow pathman_user2 to create partitions */ +SET ROLE pathman_user1; +GRANT INSERT ON permissions.pathman_user1_table TO pathman_user2; +GRANT UPDATE(a) ON permissions.pathman_user1_table TO pathman_user2; /* per-column ACL */ +/* Should be able to prepend a partition */ +SET ROLE pathman_user2; +SELECT prepend_range_partition('permissions.pathman_user1_table'); + prepend_range_partition +----------------------------------- + permissions.pathman_user1_table_4 +(1 row) + +SELECT attname, attacl FROM pg_attribute +WHERE attrelid = (SELECT "partition" FROM pathman_partition_list + WHERE parent = 'permissions.pathman_user1_table'::REGCLASS + ORDER BY range_min::int ASC /* prepend */ + LIMIT 1) +ORDER BY attname; /* check ACL for each column */ + attname | attacl +----------+--------------------------------- + a | {pathman_user2=w/pathman_user1} + cmax | + cmin | + ctid | + id | + tableoid | + xmax | + xmin | +(8 rows) + +/* Have rights, should be ok (parent's ACL is shared by new children) */ +SET ROLE pathman_user2; +INSERT INTO permissions.pathman_user1_table (id, a) VALUES (35, 0) RETURNING *; + id | a +----+--- + 35 | 0 +(1 row) + +SELECT relname, relacl FROM pg_class +WHERE oid = ANY (SELECT "partition" FROM pathman_partition_list + WHERE parent = 'permissions.pathman_user1_table'::REGCLASS + ORDER BY range_max::int DESC /* append */ + LIMIT 3) +ORDER BY relname; /* we also check ACL for "pathman_user1_table_2" */ + relname | relacl +-----------------------+----------------------------------------------------------------------- + pathman_user1_table_2 | {pathman_user1=arwdDxtm/pathman_user1,pathman_user2=r/pathman_user1} + pathman_user1_table_5 | {pathman_user1=arwdDxtm/pathman_user1,pathman_user2=ar/pathman_user1} + pathman_user1_table_6 | {pathman_user1=arwdDxtm/pathman_user1,pathman_user2=ar/pathman_user1} +(3 rows) + +/* Try to drop partition, should fail */ +DO $$ +BEGIN + SELECT drop_range_partition('permissions.pathman_user1_table_4'); +EXCEPTION + WHEN insufficient_privilege THEN + RAISE NOTICE 'Insufficient priviliges'; +END$$; +NOTICE: Insufficient priviliges +/* Disable automatic partition creation */ +SET ROLE pathman_user1; +SELECT set_auto('permissions.pathman_user1_table', false); + set_auto +---------- + +(1 row) + +/* Partition creation, should fail */ +SET ROLE pathman_user2; +INSERT INTO permissions.pathman_user1_table (id, a) VALUES (55, 0) RETURNING *; +ERROR: no suitable partition for key '55' +/* Finally drop partitions */ +SET ROLE pathman_user1; +SELECT drop_partitions('permissions.pathman_user1_table'); +NOTICE: 10 rows copied from permissions.pathman_user1_table_1 +NOTICE: 10 rows copied from permissions.pathman_user1_table_2 +NOTICE: 0 rows copied from permissions.pathman_user1_table_4 +NOTICE: 0 rows copied from permissions.pathman_user1_table_5 +NOTICE: 1 rows copied from permissions.pathman_user1_table_6 + drop_partitions +----------------- + 5 +(1 row) + +/* Switch to #2 */ +SET ROLE pathman_user2; +/* Test ddl event trigger */ +CREATE TABLE permissions.pathman_user2_table(id serial); +SELECT create_hash_partitions('permissions.pathman_user2_table', 'id', 3); + create_hash_partitions +------------------------ + 3 +(1 row) + +INSERT INTO permissions.pathman_user2_table SELECT generate_series(1, 30); +SELECT drop_partitions('permissions.pathman_user2_table'); +NOTICE: 9 rows copied from permissions.pathman_user2_table_0 +NOTICE: 11 rows copied from permissions.pathman_user2_table_1 +NOTICE: 10 rows copied from permissions.pathman_user2_table_2 + drop_partitions +----------------- + 3 +(1 row) + +/* Switch to #1 */ +SET ROLE pathman_user1; +CREATE TABLE permissions.dropped_column(a int, val int not null, b int, c int); +INSERT INTO permissions.dropped_column SELECT i,i,i,i FROM generate_series(1, 30) i; +GRANT SELECT(val), INSERT(val) ON permissions.dropped_column TO pathman_user2; +SELECT create_range_partitions('permissions.dropped_column', 'val', 1, 10); + create_range_partitions +------------------------- + 3 +(1 row) + +SELECT attrelid::regclass, attname, attacl FROM pg_attribute +WHERE attrelid = ANY (SELECT "partition" FROM pathman_partition_list + WHERE parent = 'permissions.dropped_column'::REGCLASS) + AND attacl IS NOT NULL +ORDER BY attrelid::regclass::text; /* check ACL for each column */ + attrelid | attname | attacl +------------------------------+---------+---------------------------------- + permissions.dropped_column_1 | val | {pathman_user2=ar/pathman_user1} + permissions.dropped_column_2 | val | {pathman_user2=ar/pathman_user1} + permissions.dropped_column_3 | val | {pathman_user2=ar/pathman_user1} +(3 rows) + +ALTER TABLE permissions.dropped_column DROP COLUMN a; /* DROP "a" */ +SELECT append_range_partition('permissions.dropped_column'); + append_range_partition +------------------------------ + permissions.dropped_column_4 +(1 row) + +SELECT attrelid::regclass, attname, attacl FROM pg_attribute +WHERE attrelid = ANY (SELECT "partition" FROM pathman_partition_list + WHERE parent = 'permissions.dropped_column'::REGCLASS) + AND attacl IS NOT NULL +ORDER BY attrelid::regclass::text; /* check ACL for each column (+1 partition) */ + attrelid | attname | attacl +------------------------------+---------+---------------------------------- + permissions.dropped_column_1 | val | {pathman_user2=ar/pathman_user1} + permissions.dropped_column_2 | val | {pathman_user2=ar/pathman_user1} + permissions.dropped_column_3 | val | {pathman_user2=ar/pathman_user1} + permissions.dropped_column_4 | val | {pathman_user2=ar/pathman_user1} +(4 rows) + +ALTER TABLE permissions.dropped_column DROP COLUMN b; /* DROP "b" */ +SELECT append_range_partition('permissions.dropped_column'); + append_range_partition +------------------------------ + permissions.dropped_column_5 +(1 row) + +SELECT attrelid::regclass, attname, attacl FROM pg_attribute +WHERE attrelid = ANY (SELECT "partition" FROM pathman_partition_list + WHERE parent = 'permissions.dropped_column'::REGCLASS) + AND attacl IS NOT NULL +ORDER BY attrelid::regclass::text; /* check ACL for each column (+1 partition) */ + attrelid | attname | attacl +------------------------------+---------+---------------------------------- + permissions.dropped_column_1 | val | {pathman_user2=ar/pathman_user1} + permissions.dropped_column_2 | val | {pathman_user2=ar/pathman_user1} + permissions.dropped_column_3 | val | {pathman_user2=ar/pathman_user1} + permissions.dropped_column_4 | val | {pathman_user2=ar/pathman_user1} + permissions.dropped_column_5 | val | {pathman_user2=ar/pathman_user1} +(5 rows) + +DROP TABLE permissions.dropped_column CASCADE; +NOTICE: drop cascades to 6 other objects +/* Finally reset user */ +RESET ROLE; +DROP OWNED BY pathman_user1; +DROP OWNED BY pathman_user2; +DROP USER pathman_user1; +DROP USER pathman_user2; +DROP SCHEMA permissions; +DROP EXTENSION pg_pathman; diff --git a/expected/pathman_rebuild_deletes.out b/expected/pathman_rebuild_deletes.out index b19d700a..a5edc242 100644 --- a/expected/pathman_rebuild_deletes.out +++ b/expected/pathman_rebuild_deletes.out @@ -100,6 +100,7 @@ RETURNING *, tableoid::REGCLASS; (3 rows) DROP TABLE test_deletes.test_dummy; -DROP SCHEMA test_deletes CASCADE; -NOTICE: drop cascades to 13 other objects +DROP TABLE test_deletes.test CASCADE; +NOTICE: drop cascades to 12 other objects +DROP SCHEMA test_deletes; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_rebuild_deletes_1.out b/expected/pathman_rebuild_deletes_1.out index d1c4b69e..eb2f5001 100644 --- a/expected/pathman_rebuild_deletes_1.out +++ b/expected/pathman_rebuild_deletes_1.out @@ -100,6 +100,7 @@ RETURNING *, tableoid::REGCLASS; (3 rows) DROP TABLE test_deletes.test_dummy; -DROP SCHEMA test_deletes CASCADE; -NOTICE: drop cascades to 13 other objects +DROP TABLE test_deletes.test CASCADE; +NOTICE: drop cascades to 12 other objects +DROP SCHEMA test_deletes; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_rebuild_updates.out b/expected/pathman_rebuild_updates.out index eb078303..40c5b048 100644 --- a/expected/pathman_rebuild_updates.out +++ b/expected/pathman_rebuild_updates.out @@ -155,6 +155,46 @@ UPDATE test_updates.test SET val = -1 WHERE val = 95 RETURNING *, tableoid::RE -1 | 105 | test_updates.test_13 (1 row) -DROP SCHEMA test_updates CASCADE; -NOTICE: drop cascades to 15 other objects +/* basic check for 'ALTER TABLE ... ADD COLUMN'; PGPRO-5113 */ +create table test_updates.test_5113(val int4 not null); +insert into test_updates.test_5113 values (1); +select create_range_partitions('test_updates.test_5113', 'val', 1, 10); + create_range_partitions +------------------------- + 1 +(1 row) + +update test_updates.test_5113 set val = 11 where val = 1; +alter table test_updates.test_5113 add column x varchar; +/* no error here: */ +select * from test_updates.test_5113 where val = 11; + val | x +-----+--- + 11 | +(1 row) + +drop table test_updates.test_5113 cascade; +NOTICE: drop cascades to 3 other objects +create table test_updates.test_5113(val int4 not null); +insert into test_updates.test_5113 values (1); +select create_range_partitions('test_updates.test_5113', 'val', 1, 10); + create_range_partitions +------------------------- + 1 +(1 row) + +update test_updates.test_5113 set val = 11 where val = 1; +alter table test_updates.test_5113 add column x int8; +/* no extra data in column 'x' here: */ +select * from test_updates.test_5113 where val = 11; + val | x +-----+--- + 11 | +(1 row) + +drop table test_updates.test_5113 cascade; +NOTICE: drop cascades to 3 other objects +DROP TABLE test_updates.test CASCADE; +NOTICE: drop cascades to 14 other objects +DROP SCHEMA test_updates; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_rebuild_updates_1.out b/expected/pathman_rebuild_updates_1.out index 10ec256e..57b3297a 100644 --- a/expected/pathman_rebuild_updates_1.out +++ b/expected/pathman_rebuild_updates_1.out @@ -155,6 +155,46 @@ UPDATE test_updates.test SET val = -1 WHERE val = 95 RETURNING *, tableoid::RE -1 | 105 | test_updates.test_13 (1 row) -DROP SCHEMA test_updates CASCADE; -NOTICE: drop cascades to 15 other objects +/* basic check for 'ALTER TABLE ... ADD COLUMN'; PGPRO-5113 */ +create table test_updates.test_5113(val int4 not null); +insert into test_updates.test_5113 values (1); +select create_range_partitions('test_updates.test_5113', 'val', 1, 10); + create_range_partitions +------------------------- + 1 +(1 row) + +update test_updates.test_5113 set val = 11 where val = 1; +alter table test_updates.test_5113 add column x varchar; +/* no error here: */ +select * from test_updates.test_5113 where val = 11; + val | x +-----+--- + 11 | +(1 row) + +drop table test_updates.test_5113 cascade; +NOTICE: drop cascades to 3 other objects +create table test_updates.test_5113(val int4 not null); +insert into test_updates.test_5113 values (1); +select create_range_partitions('test_updates.test_5113', 'val', 1, 10); + create_range_partitions +------------------------- + 1 +(1 row) + +update test_updates.test_5113 set val = 11 where val = 1; +alter table test_updates.test_5113 add column x int8; +/* no extra data in column 'x' here: */ +select * from test_updates.test_5113 where val = 11; + val | x +-----+--- + 11 | +(1 row) + +drop table test_updates.test_5113 cascade; +NOTICE: drop cascades to 3 other objects +DROP TABLE test_updates.test CASCADE; +NOTICE: drop cascades to 14 other objects +DROP SCHEMA test_updates; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_rowmarks.out b/expected/pathman_rowmarks.out index f9ef8114..6d4611ee 100644 --- a/expected/pathman_rowmarks.out +++ b/expected/pathman_rowmarks.out @@ -1,13 +1,30 @@ /* * ------------------------------------------- - * NOTE: This test behaves differenly on 9.5 + * NOTE: This test behaves differenly on PgPro * ------------------------------------------- * - * Also since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated, - * causing different output; pathman_rowmarks_2.out is the updated version. + * ------------------------ + * pathman_rowmarks_1.sql + * ------------------------ + * Since PostgreSQL 9.5, output of EXPLAIN was changed. * - * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, - * now it includes aliases for inherited tables. + * ------------------------ + * pathman_rowmarks_2.sql + * ------------------------ + * Since 8edd0e794 in PostgreSQL 12, append nodes with single subplan are + * eliminated, causing different output. + * + * ------------------------ + * pathman_rowmarks_3.sql + * ------------------------ + * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13 output of EXPLAIN was + * changed, now it includes aliases for inherited tables. + * + * ------------------------ + * pathman_rowmarks_3.sql + * ------------------------ + * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was + * changed, now it displays SubPlan nodes and output parameters. */ SET search_path = 'public'; CREATE EXTENSION pg_pathman; @@ -381,13 +398,13 @@ WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1 OR id = SET enable_hashjoin = t; SET enable_mergejoin = t; -DROP SCHEMA rowmarks CASCADE; -NOTICE: drop cascades to 7 other objects -DETAIL: drop cascades to table rowmarks.first -drop cascades to table rowmarks.second -drop cascades to table rowmarks.first_0 +DROP TABLE rowmarks.first CASCADE; +NOTICE: drop cascades to 5 other objects +DETAIL: drop cascades to table rowmarks.first_0 drop cascades to table rowmarks.first_1 drop cascades to table rowmarks.first_2 drop cascades to table rowmarks.first_3 drop cascades to table rowmarks.first_4 +DROP TABLE rowmarks.second CASCADE; +DROP SCHEMA rowmarks; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_rowmarks_1.out b/expected/pathman_rowmarks_1.out index e0877333..063fca8d 100644 --- a/expected/pathman_rowmarks_1.out +++ b/expected/pathman_rowmarks_1.out @@ -1,13 +1,30 @@ /* * ------------------------------------------- - * NOTE: This test behaves differenly on 9.5 + * NOTE: This test behaves differenly on PgPro * ------------------------------------------- * - * Also since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated, - * causing different output; pathman_rowmarks_2.out is the updated version. + * ------------------------ + * pathman_rowmarks_1.sql + * ------------------------ + * Since PostgreSQL 9.5, output of EXPLAIN was changed. * - * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, - * now it includes aliases for inherited tables. + * ------------------------ + * pathman_rowmarks_2.sql + * ------------------------ + * Since 8edd0e794 in PostgreSQL 12, append nodes with single subplan are + * eliminated, causing different output. + * + * ------------------------ + * pathman_rowmarks_3.sql + * ------------------------ + * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13 output of EXPLAIN was + * changed, now it includes aliases for inherited tables. + * + * ------------------------ + * pathman_rowmarks_3.sql + * ------------------------ + * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was + * changed, now it displays SubPlan nodes and output parameters. */ SET search_path = 'public'; CREATE EXTENSION pg_pathman; @@ -436,13 +453,13 @@ WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1 OR id = SET enable_hashjoin = t; SET enable_mergejoin = t; -DROP SCHEMA rowmarks CASCADE; -NOTICE: drop cascades to 7 other objects -DETAIL: drop cascades to table rowmarks.first -drop cascades to table rowmarks.second -drop cascades to table rowmarks.first_0 +DROP TABLE rowmarks.first CASCADE; +NOTICE: drop cascades to 5 other objects +DETAIL: drop cascades to table rowmarks.first_0 drop cascades to table rowmarks.first_1 drop cascades to table rowmarks.first_2 drop cascades to table rowmarks.first_3 drop cascades to table rowmarks.first_4 +DROP TABLE rowmarks.second CASCADE; +DROP SCHEMA rowmarks; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_rowmarks_2.out b/expected/pathman_rowmarks_2.out index 7436b081..91d7804e 100644 --- a/expected/pathman_rowmarks_2.out +++ b/expected/pathman_rowmarks_2.out @@ -1,13 +1,30 @@ /* * ------------------------------------------- - * NOTE: This test behaves differenly on 9.5 + * NOTE: This test behaves differenly on PgPro * ------------------------------------------- * - * Also since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated, - * causing different output; pathman_rowmarks_2.out is the updated version. + * ------------------------ + * pathman_rowmarks_1.sql + * ------------------------ + * Since PostgreSQL 9.5, output of EXPLAIN was changed. * - * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, - * now it includes aliases for inherited tables. + * ------------------------ + * pathman_rowmarks_2.sql + * ------------------------ + * Since 8edd0e794 in PostgreSQL 12, append nodes with single subplan are + * eliminated, causing different output. + * + * ------------------------ + * pathman_rowmarks_3.sql + * ------------------------ + * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13 output of EXPLAIN was + * changed, now it includes aliases for inherited tables. + * + * ------------------------ + * pathman_rowmarks_3.sql + * ------------------------ + * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was + * changed, now it displays SubPlan nodes and output parameters. */ SET search_path = 'public'; CREATE EXTENSION pg_pathman; @@ -378,13 +395,13 @@ WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1 OR id = SET enable_hashjoin = t; SET enable_mergejoin = t; -DROP SCHEMA rowmarks CASCADE; -NOTICE: drop cascades to 7 other objects -DETAIL: drop cascades to table rowmarks.first -drop cascades to table rowmarks.second -drop cascades to table rowmarks.first_0 +DROP TABLE rowmarks.first CASCADE; +NOTICE: drop cascades to 5 other objects +DETAIL: drop cascades to table rowmarks.first_0 drop cascades to table rowmarks.first_1 drop cascades to table rowmarks.first_2 drop cascades to table rowmarks.first_3 drop cascades to table rowmarks.first_4 +DROP TABLE rowmarks.second CASCADE; +DROP SCHEMA rowmarks; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_rowmarks_3.out b/expected/pathman_rowmarks_3.out index 6179ff94..e8644292 100644 --- a/expected/pathman_rowmarks_3.out +++ b/expected/pathman_rowmarks_3.out @@ -1,13 +1,30 @@ /* * ------------------------------------------- - * NOTE: This test behaves differenly on 9.5 + * NOTE: This test behaves differenly on PgPro * ------------------------------------------- * - * Also since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated, - * causing different output; pathman_rowmarks_2.out is the updated version. + * ------------------------ + * pathman_rowmarks_1.sql + * ------------------------ + * Since PostgreSQL 9.5, output of EXPLAIN was changed. * - * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, - * now it includes aliases for inherited tables. + * ------------------------ + * pathman_rowmarks_2.sql + * ------------------------ + * Since 8edd0e794 in PostgreSQL 12, append nodes with single subplan are + * eliminated, causing different output. + * + * ------------------------ + * pathman_rowmarks_3.sql + * ------------------------ + * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13 output of EXPLAIN was + * changed, now it includes aliases for inherited tables. + * + * ------------------------ + * pathman_rowmarks_3.sql + * ------------------------ + * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was + * changed, now it displays SubPlan nodes and output parameters. */ SET search_path = 'public'; CREATE EXTENSION pg_pathman; @@ -42,17 +59,17 @@ SELECT * FROM rowmarks.second ORDER BY id FOR UPDATE; /* Simple case (plan) */ EXPLAIN (COSTS OFF) SELECT * FROM rowmarks.first ORDER BY id FOR UPDATE; - QUERY PLAN ---------------------------------------- + QUERY PLAN +----------------------------------------------- LockRows -> Sort - Sort Key: first_0.id + Sort Key: first.id -> Append - -> Seq Scan on first_0 - -> Seq Scan on first_1 - -> Seq Scan on first_2 - -> Seq Scan on first_3 - -> Seq Scan on first_4 + -> Seq Scan on first_0 first_1 + -> Seq Scan on first_1 first_2 + -> Seq Scan on first_2 first_3 + -> Seq Scan on first_3 first_4 + -> Seq Scan on first_4 first_5 (9 rows) /* Simple case (execution) */ @@ -98,20 +115,20 @@ WHERE id = (SELECT id FROM rowmarks.first OFFSET 10 LIMIT 1 FOR UPDATE) FOR SHARE; - QUERY PLAN ---------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------- LockRows InitPlan 1 (returns $1) -> Limit -> LockRows -> Sort - Sort Key: first_0.id + Sort Key: first_1.id -> Append - -> Seq Scan on first_0 - -> Seq Scan on first_1 first_1_1 - -> Seq Scan on first_2 - -> Seq Scan on first_3 - -> Seq Scan on first_4 + -> Seq Scan on first_0 first_2 + -> Seq Scan on first_1 first_3 + -> Seq Scan on first_2 first_4 + -> Seq Scan on first_3 first_5 + -> Seq Scan on first_4 first_6 -> Custom Scan (RuntimeAppend) Prune by: (first.id = $1) -> Seq Scan on first_0 first @@ -187,19 +204,19 @@ SELECT * FROM rowmarks.first JOIN rowmarks.second USING(id) ORDER BY id FOR UPDATE; - QUERY PLAN ---------------------------------------------------- + QUERY PLAN +----------------------------------------------------- LockRows -> Sort - Sort Key: first_0.id + Sort Key: first.id -> Hash Join - Hash Cond: (first_0.id = second.id) + Hash Cond: (first.id = second.id) -> Append - -> Seq Scan on first_0 - -> Seq Scan on first_1 - -> Seq Scan on first_2 - -> Seq Scan on first_3 - -> Seq Scan on first_4 + -> Seq Scan on first_0 first_1 + -> Seq Scan on first_1 first_2 + -> Seq Scan on first_2 first_3 + -> Seq Scan on first_3 first_4 + -> Seq Scan on first_4 first_5 -> Hash -> Seq Scan on second (13 rows) @@ -244,53 +261,53 @@ SET enable_mergejoin = f; /* Merge Semi Join on 10 vs Merge Join on 9.6 */ EXPLAIN (COSTS OFF) UPDATE rowmarks.second SET id = 2 WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1); - QUERY PLAN ---------------------------------- + QUERY PLAN +--------------------------------------- Update on second -> Nested Loop Semi Join -> Seq Scan on second Filter: (id = 1) - -> Seq Scan on first_0 + -> Seq Scan on first_0 first Filter: (id = 1) (6 rows) EXPLAIN (COSTS OFF) UPDATE rowmarks.second SET id = 2 WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id < 1); - QUERY PLAN ------------------------------------------------ + QUERY PLAN +----------------------------------------------------- Update on second -> Nested Loop Semi Join - Join Filter: (second.id = first_0.id) + Join Filter: (second.id = first.id) -> Seq Scan on second -> Materialize -> Append - -> Seq Scan on first_0 + -> Seq Scan on first_0 first_1 Filter: (id < 1) - -> Seq Scan on first_1 + -> Seq Scan on first_1 first_2 Filter: (id < 1) - -> Seq Scan on first_2 + -> Seq Scan on first_2 first_3 Filter: (id < 1) - -> Seq Scan on first_3 + -> Seq Scan on first_3 first_4 Filter: (id < 1) - -> Seq Scan on first_4 + -> Seq Scan on first_4 first_5 Filter: (id < 1) (16 rows) EXPLAIN (COSTS OFF) UPDATE rowmarks.second SET id = 2 WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1 OR id = 2); - QUERY PLAN ------------------------------------------------ + QUERY PLAN +----------------------------------------------------- Update on second -> Nested Loop Semi Join - Join Filter: (second.id = first_0.id) + Join Filter: (second.id = first.id) -> Seq Scan on second -> Materialize -> Append - -> Seq Scan on first_0 + -> Seq Scan on first_0 first_1 Filter: (id = 1) - -> Seq Scan on first_1 + -> Seq Scan on first_1 first_2 Filter: (id = 2) (10 rows) @@ -298,13 +315,13 @@ EXPLAIN (COSTS OFF) UPDATE rowmarks.second SET id = 2 WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1) RETURNING *, tableoid::regclass; - QUERY PLAN ---------------------------------- + QUERY PLAN +--------------------------------------- Update on second -> Nested Loop Semi Join -> Seq Scan on second Filter: (id = 1) - -> Seq Scan on first_0 + -> Seq Scan on first_0 first Filter: (id = 1) (6 rows) @@ -326,65 +343,65 @@ SET enable_mergejoin = f; /* Merge Semi Join on 10 vs Merge Join on 9.6 */ EXPLAIN (COSTS OFF) DELETE FROM rowmarks.second WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1); - QUERY PLAN ---------------------------------- + QUERY PLAN +--------------------------------------- Delete on second -> Nested Loop Semi Join -> Seq Scan on second Filter: (id = 1) - -> Seq Scan on first_0 + -> Seq Scan on first_0 first Filter: (id = 1) (6 rows) EXPLAIN (COSTS OFF) DELETE FROM rowmarks.second WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id < 1); - QUERY PLAN ------------------------------------------------ + QUERY PLAN +----------------------------------------------------- Delete on second -> Nested Loop Semi Join - Join Filter: (second.id = first_0.id) + Join Filter: (second.id = first.id) -> Seq Scan on second -> Materialize -> Append - -> Seq Scan on first_0 + -> Seq Scan on first_0 first_1 Filter: (id < 1) - -> Seq Scan on first_1 + -> Seq Scan on first_1 first_2 Filter: (id < 1) - -> Seq Scan on first_2 + -> Seq Scan on first_2 first_3 Filter: (id < 1) - -> Seq Scan on first_3 + -> Seq Scan on first_3 first_4 Filter: (id < 1) - -> Seq Scan on first_4 + -> Seq Scan on first_4 first_5 Filter: (id < 1) (16 rows) EXPLAIN (COSTS OFF) DELETE FROM rowmarks.second WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1 OR id = 2); - QUERY PLAN ------------------------------------------------ + QUERY PLAN +----------------------------------------------------- Delete on second -> Nested Loop Semi Join - Join Filter: (second.id = first_0.id) + Join Filter: (second.id = first.id) -> Seq Scan on second -> Materialize -> Append - -> Seq Scan on first_0 + -> Seq Scan on first_0 first_1 Filter: (id = 1) - -> Seq Scan on first_1 + -> Seq Scan on first_1 first_2 Filter: (id = 2) (10 rows) SET enable_hashjoin = t; SET enable_mergejoin = t; -DROP SCHEMA rowmarks CASCADE; -NOTICE: drop cascades to 7 other objects -DETAIL: drop cascades to table rowmarks.first -drop cascades to table rowmarks.second -drop cascades to table rowmarks.first_0 +DROP TABLE rowmarks.first CASCADE; +NOTICE: drop cascades to 5 other objects +DETAIL: drop cascades to table rowmarks.first_0 drop cascades to table rowmarks.first_1 drop cascades to table rowmarks.first_2 drop cascades to table rowmarks.first_3 drop cascades to table rowmarks.first_4 +DROP TABLE rowmarks.second CASCADE; +DROP SCHEMA rowmarks; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_rowmarks_4.out b/expected/pathman_rowmarks_4.out new file mode 100644 index 00000000..5fbec84d --- /dev/null +++ b/expected/pathman_rowmarks_4.out @@ -0,0 +1,407 @@ +/* + * ------------------------------------------- + * NOTE: This test behaves differenly on PgPro + * ------------------------------------------- + * + * ------------------------ + * pathman_rowmarks_1.sql + * ------------------------ + * Since PostgreSQL 9.5, output of EXPLAIN was changed. + * + * ------------------------ + * pathman_rowmarks_2.sql + * ------------------------ + * Since 8edd0e794 in PostgreSQL 12, append nodes with single subplan are + * eliminated, causing different output. + * + * ------------------------ + * pathman_rowmarks_3.sql + * ------------------------ + * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13 output of EXPLAIN was + * changed, now it includes aliases for inherited tables. + * + * ------------------------ + * pathman_rowmarks_3.sql + * ------------------------ + * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was + * changed, now it displays SubPlan nodes and output parameters. + */ +SET search_path = 'public'; +CREATE EXTENSION pg_pathman; +CREATE SCHEMA rowmarks; +CREATE TABLE rowmarks.first(id int NOT NULL); +CREATE TABLE rowmarks.second(id int NOT NULL); +INSERT INTO rowmarks.first SELECT generate_series(1, 10); +INSERT INTO rowmarks.second SELECT generate_series(1, 10); +SELECT create_hash_partitions('rowmarks.first', 'id', 5); + create_hash_partitions +------------------------ + 5 +(1 row) + +VACUUM ANALYZE; +/* Not partitioned */ +SELECT * FROM rowmarks.second ORDER BY id FOR UPDATE; + id +---- + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 +(10 rows) + +/* Simple case (plan) */ +EXPLAIN (COSTS OFF) +SELECT * FROM rowmarks.first ORDER BY id FOR UPDATE; + QUERY PLAN +----------------------------------------------- + LockRows + -> Sort + Sort Key: first.id + -> Append + -> Seq Scan on first_0 first_1 + -> Seq Scan on first_1 first_2 + -> Seq Scan on first_2 first_3 + -> Seq Scan on first_3 first_4 + -> Seq Scan on first_4 first_5 +(9 rows) + +/* Simple case (execution) */ +SELECT * FROM rowmarks.first ORDER BY id FOR UPDATE; + id +---- + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 +(10 rows) + +SELECT FROM rowmarks.first ORDER BY id FOR UPDATE; +-- +(10 rows) + +SELECT tableoid > 0 FROM rowmarks.first ORDER BY id FOR UPDATE; + ?column? +---------- + t + t + t + t + t + t + t + t + t + t +(10 rows) + +/* A little harder (plan) */ +EXPLAIN (COSTS OFF) +SELECT * FROM rowmarks.first +WHERE id = (SELECT id FROM rowmarks.first + ORDER BY id + OFFSET 10 LIMIT 1 + FOR UPDATE) +FOR SHARE; + QUERY PLAN +------------------------------------------------------------- + LockRows + InitPlan 1 + -> Limit + -> LockRows + -> Sort + Sort Key: first_1.id + -> Append + -> Seq Scan on first_0 first_2 + -> Seq Scan on first_1 first_3 + -> Seq Scan on first_2 first_4 + -> Seq Scan on first_3 first_5 + -> Seq Scan on first_4 first_6 + -> Custom Scan (RuntimeAppend) + Prune by: (first.id = (InitPlan 1).col1) + -> Seq Scan on first_0 first + Filter: (id = (InitPlan 1).col1) + -> Seq Scan on first_1 first + Filter: (id = (InitPlan 1).col1) + -> Seq Scan on first_2 first + Filter: (id = (InitPlan 1).col1) + -> Seq Scan on first_3 first + Filter: (id = (InitPlan 1).col1) + -> Seq Scan on first_4 first + Filter: (id = (InitPlan 1).col1) +(24 rows) + +/* A little harder (execution) */ +SELECT * FROM rowmarks.first +WHERE id = (SELECT id FROM rowmarks.first + ORDER BY id + OFFSET 5 LIMIT 1 + FOR UPDATE) +FOR SHARE; + id +---- + 6 +(1 row) + +/* Two tables (plan) */ +EXPLAIN (COSTS OFF) +SELECT * FROM rowmarks.first +WHERE id = (SELECT id FROM rowmarks.second + ORDER BY id + OFFSET 5 LIMIT 1 + FOR UPDATE) +FOR SHARE; + QUERY PLAN +-------------------------------------------------- + LockRows + InitPlan 1 + -> Limit + -> LockRows + -> Sort + Sort Key: second.id + -> Seq Scan on second + -> Custom Scan (RuntimeAppend) + Prune by: (first.id = (InitPlan 1).col1) + -> Seq Scan on first_0 first + Filter: (id = (InitPlan 1).col1) + -> Seq Scan on first_1 first + Filter: (id = (InitPlan 1).col1) + -> Seq Scan on first_2 first + Filter: (id = (InitPlan 1).col1) + -> Seq Scan on first_3 first + Filter: (id = (InitPlan 1).col1) + -> Seq Scan on first_4 first + Filter: (id = (InitPlan 1).col1) +(19 rows) + +/* Two tables (execution) */ +SELECT * FROM rowmarks.first +WHERE id = (SELECT id FROM rowmarks.second + ORDER BY id + OFFSET 5 LIMIT 1 + FOR UPDATE) +FOR SHARE; + id +---- + 6 +(1 row) + +/* JOIN (plan) */ +EXPLAIN (COSTS OFF) +SELECT * FROM rowmarks.first +JOIN rowmarks.second USING(id) +ORDER BY id +FOR UPDATE; + QUERY PLAN +----------------------------------------------------- + LockRows + -> Sort + Sort Key: first.id + -> Hash Join + Hash Cond: (first.id = second.id) + -> Append + -> Seq Scan on first_0 first_1 + -> Seq Scan on first_1 first_2 + -> Seq Scan on first_2 first_3 + -> Seq Scan on first_3 first_4 + -> Seq Scan on first_4 first_5 + -> Hash + -> Seq Scan on second +(13 rows) + +/* JOIN (execution) */ +SELECT * FROM rowmarks.first +JOIN rowmarks.second USING(id) +ORDER BY id +FOR UPDATE; + id +---- + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 +(10 rows) + +/* ONLY (plan) */ +EXPLAIN (COSTS OFF) +SELECT * FROM ONLY rowmarks.first FOR SHARE; + QUERY PLAN +------------------------- + LockRows + -> Seq Scan on first +(2 rows) + +/* ONLY (execution) */ +SELECT * FROM ONLY rowmarks.first FOR SHARE; + id +---- +(0 rows) + +/* Check updates (plan) */ +SET enable_hashjoin = f; /* Hash Semi Join on 10 vs Hash Join on 9.6 */ +SET enable_mergejoin = f; /* Merge Semi Join on 10 vs Merge Join on 9.6 */ +EXPLAIN (COSTS OFF) +UPDATE rowmarks.second SET id = 2 +WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1); + QUERY PLAN +--------------------------------------- + Update on second + -> Nested Loop Semi Join + -> Seq Scan on second + Filter: (id = 1) + -> Seq Scan on first_0 first + Filter: (id = 1) +(6 rows) + +EXPLAIN (COSTS OFF) +UPDATE rowmarks.second SET id = 2 +WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id < 1); + QUERY PLAN +----------------------------------------------------- + Update on second + -> Nested Loop Semi Join + Join Filter: (second.id = first.id) + -> Seq Scan on second + -> Materialize + -> Append + -> Seq Scan on first_0 first_1 + Filter: (id < 1) + -> Seq Scan on first_1 first_2 + Filter: (id < 1) + -> Seq Scan on first_2 first_3 + Filter: (id < 1) + -> Seq Scan on first_3 first_4 + Filter: (id < 1) + -> Seq Scan on first_4 first_5 + Filter: (id < 1) +(16 rows) + +EXPLAIN (COSTS OFF) +UPDATE rowmarks.second SET id = 2 +WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1 OR id = 2); + QUERY PLAN +----------------------------------------------------- + Update on second + -> Nested Loop Semi Join + Join Filter: (second.id = first.id) + -> Seq Scan on second + -> Materialize + -> Append + -> Seq Scan on first_0 first_1 + Filter: (id = 1) + -> Seq Scan on first_1 first_2 + Filter: (id = 2) +(10 rows) + +EXPLAIN (COSTS OFF) +UPDATE rowmarks.second SET id = 2 +WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1) +RETURNING *, tableoid::regclass; + QUERY PLAN +--------------------------------------- + Update on second + -> Nested Loop Semi Join + -> Seq Scan on second + Filter: (id = 1) + -> Seq Scan on first_0 first + Filter: (id = 1) +(6 rows) + +SET enable_hashjoin = t; +SET enable_mergejoin = t; +/* Check updates (execution) */ +UPDATE rowmarks.second SET id = 1 +WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1 OR id = 2) +RETURNING *, tableoid::regclass; + id | tableoid +----+----------------- + 1 | rowmarks.second + 1 | rowmarks.second +(2 rows) + +/* Check deletes (plan) */ +SET enable_hashjoin = f; /* Hash Semi Join on 10 vs Hash Join on 9.6 */ +SET enable_mergejoin = f; /* Merge Semi Join on 10 vs Merge Join on 9.6 */ +EXPLAIN (COSTS OFF) +DELETE FROM rowmarks.second +WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1); + QUERY PLAN +--------------------------------------- + Delete on second + -> Nested Loop Semi Join + -> Seq Scan on second + Filter: (id = 1) + -> Seq Scan on first_0 first + Filter: (id = 1) +(6 rows) + +EXPLAIN (COSTS OFF) +DELETE FROM rowmarks.second +WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id < 1); + QUERY PLAN +----------------------------------------------------- + Delete on second + -> Nested Loop Semi Join + Join Filter: (second.id = first.id) + -> Seq Scan on second + -> Materialize + -> Append + -> Seq Scan on first_0 first_1 + Filter: (id < 1) + -> Seq Scan on first_1 first_2 + Filter: (id < 1) + -> Seq Scan on first_2 first_3 + Filter: (id < 1) + -> Seq Scan on first_3 first_4 + Filter: (id < 1) + -> Seq Scan on first_4 first_5 + Filter: (id < 1) +(16 rows) + +EXPLAIN (COSTS OFF) +DELETE FROM rowmarks.second +WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1 OR id = 2); + QUERY PLAN +----------------------------------------------------- + Delete on second + -> Nested Loop Semi Join + Join Filter: (second.id = first.id) + -> Seq Scan on second + -> Materialize + -> Append + -> Seq Scan on first_0 first_1 + Filter: (id = 1) + -> Seq Scan on first_1 first_2 + Filter: (id = 2) +(10 rows) + +SET enable_hashjoin = t; +SET enable_mergejoin = t; +DROP TABLE rowmarks.first CASCADE; +NOTICE: drop cascades to 5 other objects +DETAIL: drop cascades to table rowmarks.first_0 +drop cascades to table rowmarks.first_1 +drop cascades to table rowmarks.first_2 +drop cascades to table rowmarks.first_3 +drop cascades to table rowmarks.first_4 +DROP TABLE rowmarks.second CASCADE; +DROP SCHEMA rowmarks; +DROP EXTENSION pg_pathman; diff --git a/expected/pathman_runtime_nodes.out b/expected/pathman_runtime_nodes.out index f364cfb4..f699ddeb 100644 --- a/expected/pathman_runtime_nodes.out +++ b/expected/pathman_runtime_nodes.out @@ -58,7 +58,6 @@ begin return 'ok'; end; $$ language plpgsql -set pg_pathman.enable = true set enable_mergejoin = off set enable_hashjoin = off; create or replace function test.pathman_test_2() returns text as $$ @@ -100,7 +99,6 @@ begin return 'ok'; end; $$ language plpgsql -set pg_pathman.enable = true set enable_mergejoin = off set enable_hashjoin = off; create or replace function test.pathman_test_3() returns text as $$ @@ -133,7 +131,6 @@ begin return 'ok'; end; $$ language plpgsql -set pg_pathman.enable = true set enable_mergejoin = off set enable_hashjoin = off; create or replace function test.pathman_test_4() returns text as $$ @@ -172,7 +169,6 @@ begin return 'ok'; end; $$ language plpgsql -set pg_pathman.enable = true set enable_mergejoin = off set enable_hashjoin = off; create or replace function test.pathman_test_5() returns text as $$ @@ -233,7 +229,6 @@ begin return 'ok'; end; $$ language plpgsql -set pg_pathman.enable = true set enable_hashjoin = off set enable_mergejoin = off; create table test.run_values as select generate_series(1, 10000) val; @@ -444,7 +439,67 @@ where id = any (select generate_series(-10, -1)); /* should be empty */ set enable_hashjoin = on; set enable_mergejoin = on; -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 37 other objects +DROP TABLE test.vals CASCADE; +DROP TABLE test.category CASCADE; +DROP TABLE test.run_values CASCADE; +DROP TABLE test.runtime_test_1 CASCADE; +NOTICE: drop cascades to 6 other objects +DROP TABLE test.runtime_test_2 CASCADE; +NOTICE: drop cascades to 6 other objects +DROP TABLE test.runtime_test_3 CASCADE; +NOTICE: drop cascades to 4 other objects +DROP TABLE test.runtime_test_4 CASCADE; +NOTICE: drop cascades to 6 other objects +DROP FUNCTION test.pathman_assert(bool, text); +DROP FUNCTION test.pathman_equal(text, text, text); +DROP FUNCTION test.pathman_test(text); +DROP FUNCTION test.pathman_test_1(); +DROP FUNCTION test.pathman_test_2(); +DROP FUNCTION test.pathman_test_3(); +DROP FUNCTION test.pathman_test_4(); +DROP FUNCTION test.pathman_test_5(); +DROP SCHEMA test; +-- +-- +-- PGPRO-7928 +-- Variable pg_pathman.enable must be called before any query. +-- +CREATE TABLE part_test (val int NOT NULL); +SELECT pathman.create_hash_partitions('part_test', 'val', 2, partition_names := array['part_test_1','pg_pathman']); + create_hash_partitions +------------------------ + 2 +(1 row) + +CREATE OR REPLACE FUNCTION part_test_trigger() RETURNS TRIGGER AS $$ +BEGIN + RAISE NOTICE '%', format('%s %s %s (%s)', TG_WHEN, TG_OP, TG_LEVEL, TG_TABLE_NAME); + IF TG_OP::text = 'DELETE'::text then + SET pg_pathman.enable = f; + RETURN new; + END IF; +END; +$$ LANGUAGE PLPGSQL; +SET pg_pathman.enable_partitionrouter = t; +CREATE TRIGGER ad AFTER DELETE ON part_test_1 FOR EACH ROW EXECUTE PROCEDURE part_test_trigger (); +INSERT INTO part_test VALUES (1); +UPDATE part_test SET val = val + 1 RETURNING *, tableoid::regclass; + val | tableoid +-----+------------- + 2 | part_test_1 +(1 row) + +UPDATE part_test SET val = val + 1 RETURNING *, tableoid::regclass; +NOTICE: AFTER DELETE ROW (part_test_1) +WARNING: "pg_pathman.enable" must be called before any query, ignored + val | tableoid +-----+------------ + 3 | pg_pathman +(1 row) + +RESET pg_pathman.enable_partitionrouter; +DROP TABLE part_test CASCADE; +NOTICE: drop cascades to 2 other objects +DROP FUNCTION part_test_trigger(); DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_runtime_nodes_1.out b/expected/pathman_runtime_nodes_1.out new file mode 100644 index 00000000..e975c761 --- /dev/null +++ b/expected/pathman_runtime_nodes_1.out @@ -0,0 +1,505 @@ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE SCHEMA pathman; +CREATE EXTENSION pg_pathman SCHEMA pathman; +CREATE SCHEMA test; +/* + * Test RuntimeAppend + */ +create or replace function test.pathman_assert(smt bool, error_msg text) returns text as $$ +begin + if not smt then + raise exception '%', error_msg; + end if; + + return 'ok'; +end; +$$ language plpgsql; +create or replace function test.pathman_equal(a text, b text, error_msg text) returns text as $$ +begin + if a != b then + raise exception '''%'' is not equal to ''%'', %', a, b, error_msg; + end if; + + return 'equal'; +end; +$$ language plpgsql; +create or replace function test.pathman_test(query text) returns jsonb as $$ +declare + plan jsonb; +begin + execute 'explain (analyze, format json)' || query into plan; + + return plan; +end; +$$ language plpgsql; +create or replace function test.pathman_test_1() returns text as $$ +declare + plan jsonb; + num int; +begin + plan = test.pathman_test('select * from test.runtime_test_1 where id = (select * from test.run_values limit 1)'); + + perform test.pathman_equal((plan->0->'Plan'->'Node Type')::text, + '"Custom Scan"', + 'wrong plan type'); + + perform test.pathman_equal((plan->0->'Plan'->'Custom Plan Provider')::text, + '"RuntimeAppend"', + 'wrong plan provider'); + + perform test.pathman_equal((plan->0->'Plan'->'Plans'->1->'Relation Name')::text, + format('"runtime_test_1_%s"', pathman.get_hash_part_idx(hashint4(1), 6)), + 'wrong partition'); + + select count(*) from jsonb_array_elements_text(plan->0->'Plan'->'Plans') into num; + perform test.pathman_equal(num::text, '2', 'expected 2 child plans for custom scan'); + + return 'ok'; +end; +$$ language plpgsql +set enable_mergejoin = off +set enable_hashjoin = off; +create or replace function test.pathman_test_2() returns text as $$ +declare + plan jsonb; + num int; + c text; +begin + plan = test.pathman_test('select * from test.runtime_test_1 where id = any (select * from test.run_values limit 4)'); + + perform test.pathman_equal((plan->0->'Plan'->'Node Type')::text, + '"Nested Loop"', + 'wrong plan type'); + + perform test.pathman_equal((plan->0->'Plan'->'Plans'->1->'Node Type')::text, + '"Custom Scan"', + 'wrong plan type'); + + perform test.pathman_equal((plan->0->'Plan'->'Plans'->1->'Custom Plan Provider')::text, + '"RuntimeAppend"', + 'wrong plan provider'); + + select count(*) from jsonb_array_elements_text(plan->0->'Plan'->'Plans'->1->'Plans') into num; + perform test.pathman_equal(num::text, '4', 'expected 4 child plans for custom scan'); + + execute 'select string_agg(y.z, '','') from + (select (x->''Relation Name'')::text as z from + jsonb_array_elements($1->0->''Plan''->''Plans''->1->''Plans'') x + order by x->''Relation Name'') y' + into c using plan; + perform test.pathman_equal(c, '"runtime_test_1_2","runtime_test_1_3","runtime_test_1_4","runtime_test_1_5"', + 'wrong partitions'); + + for i in 0..3 loop + num = plan->0->'Plan'->'Plans'->1->'Plans'->i->'Actual Loops'; + perform test.pathman_equal(num::text, '1', 'expected 1 loop'); + end loop; + + return 'ok'; +end; +$$ language plpgsql +set enable_mergejoin = off +set enable_hashjoin = off; +create or replace function test.pathman_test_3() returns text as $$ +declare + plan jsonb; + num int; +begin + plan = test.pathman_test('select * from test.runtime_test_1 a join test.run_values b on a.id = b.val'); + + perform test.pathman_equal((plan->0->'Plan'->'Node Type')::text, + '"Nested Loop"', + 'wrong plan type'); + + perform test.pathman_equal((plan->0->'Plan'->'Plans'->1->'Node Type')::text, + '"Custom Scan"', + 'wrong plan type'); + + perform test.pathman_equal((plan->0->'Plan'->'Plans'->1->'Custom Plan Provider')::text, + '"RuntimeAppend"', + 'wrong plan provider'); + + select count(*) from jsonb_array_elements_text(plan->0->'Plan'->'Plans'->1->'Plans') into num; + perform test.pathman_equal(num::text, '6', 'expected 6 child plans for custom scan'); + + for i in 0..5 loop + num = plan->0->'Plan'->'Plans'->1->'Plans'->i->'Actual Loops'; + perform test.pathman_assert(num > 0 and num <= 1718, 'expected no more than 1718 loops'); + end loop; + + return 'ok'; +end; +$$ language plpgsql +set enable_mergejoin = off +set enable_hashjoin = off; +create or replace function test.pathman_test_4() returns text as $$ +declare + plan jsonb; + num int; +begin + plan = test.pathman_test('select * from test.category c, lateral' || + '(select * from test.runtime_test_2 g where g.category_id = c.id order by rating limit 4) as tg'); + + perform test.pathman_equal((plan->0->'Plan'->'Node Type')::text, + '"Nested Loop"', + 'wrong plan type'); + + /* Limit -> Custom Scan */ + perform test.pathman_equal((plan->0->'Plan'->'Plans'->1->0->'Node Type')::text, + '"Custom Scan"', + 'wrong plan type'); + + perform test.pathman_equal((plan->0->'Plan'->'Plans'->1->0->'Custom Plan Provider')::text, + '"RuntimeMergeAppend"', + 'wrong plan provider'); + + select count(*) from jsonb_array_elements_text(plan->0->'Plan'->'Plans'->1->'Plans'->0->'Plans') into num; + perform test.pathman_equal(num::text, '4', 'expected 4 child plans for custom scan'); + + for i in 0..3 loop + perform test.pathman_equal((plan->0->'Plan'->'Plans'->1->'Plans'->0->'Plans'->i->'Relation Name')::text, + format('"runtime_test_2_%s"', pathman.get_hash_part_idx(hashint4(i + 1), 6)), + 'wrong partition'); + + num = plan->0->'Plan'->'Plans'->1->'Plans'->0->'Plans'->i->'Actual Loops'; + perform test.pathman_assert(num = 1, 'expected no more than 1 loops'); + end loop; + + return 'ok'; +end; +$$ language plpgsql +set enable_mergejoin = off +set enable_hashjoin = off; +create or replace function test.pathman_test_5() returns text as $$ +declare + res record; +begin + select + from test.runtime_test_3 + where id = (select * from test.vals order by val limit 1) + limit 1 + into res; /* test empty tlist */ + + + select id * 2, id, 17 + from test.runtime_test_3 + where id = (select * from test.vals order by val limit 1) + limit 1 + into res; /* test computations */ + + + select test.vals.* from test.vals, lateral (select from test.runtime_test_3 + where id = test.vals.val) as q + into res; /* test lateral */ + + + select id, generate_series(1, 2) gen, val + from test.runtime_test_3 + where id = (select * from test.vals order by val limit 1) + order by id, gen, val + offset 1 limit 1 + into res; /* without IndexOnlyScan */ + + perform test.pathman_equal(res.id::text, '1', 'id is incorrect (t2)'); + perform test.pathman_equal(res.gen::text, '2', 'gen is incorrect (t2)'); + perform test.pathman_equal(res.val::text, 'k = 1', 'val is incorrect (t2)'); + + + select id + from test.runtime_test_3 + where id = any (select * from test.vals order by val limit 5) + order by id + offset 3 limit 1 + into res; /* with IndexOnlyScan */ + + perform test.pathman_equal(res.id::text, '4', 'id is incorrect (t3)'); + + + select v.val v1, generate_series(2, 2) gen, t.val v2 + from test.runtime_test_3 t join test.vals v on id = v.val + order by v1, gen, v2 + limit 1 + into res; + + perform test.pathman_equal(res.v1::text, '1', 'v1 is incorrect (t4)'); + perform test.pathman_equal(res.gen::text, '2', 'gen is incorrect (t4)'); + perform test.pathman_equal(res.v2::text, 'k = 1', 'v2 is incorrect (t4)'); + + return 'ok'; +end; +$$ language plpgsql +set enable_hashjoin = off +set enable_mergejoin = off; +create table test.run_values as select generate_series(1, 10000) val; +create table test.runtime_test_1(id serial primary key, val real); +insert into test.runtime_test_1 select generate_series(1, 10000), random(); +select pathman.create_hash_partitions('test.runtime_test_1', 'id', 6); + create_hash_partitions +------------------------ + 6 +(1 row) + +create table test.category as (select id, 'cat' || id::text as name from generate_series(1, 4) id); +create table test.runtime_test_2 (id serial, category_id int not null, name text, rating real); +insert into test.runtime_test_2 (select id, (id % 6) + 1 as category_id, 'good' || id::text as name, random() as rating from generate_series(1, 100000) id); +create index on test.runtime_test_2 (category_id, rating); +select pathman.create_hash_partitions('test.runtime_test_2', 'category_id', 6); + create_hash_partitions +------------------------ + 6 +(1 row) + +create table test.vals as (select generate_series(1, 10000) as val); +create table test.runtime_test_3(val text, id serial not null); +insert into test.runtime_test_3(id, val) select * from generate_series(1, 10000) k, format('k = %s', k); +select pathman.create_hash_partitions('test.runtime_test_3', 'id', 4); + create_hash_partitions +------------------------ + 4 +(1 row) + +create index on test.runtime_test_3 (id); +create index on test.runtime_test_3_0 (id); +create table test.runtime_test_4(val text, id int not null); +insert into test.runtime_test_4(id, val) select * from generate_series(1, 10000) k, md5(k::text); +select pathman.create_range_partitions('test.runtime_test_4', 'id', 1, 2000); + create_range_partitions +------------------------- + 5 +(1 row) + +VACUUM ANALYZE; +set pg_pathman.enable_runtimeappend = on; +set pg_pathman.enable_runtimemergeappend = on; +select test.pathman_test_1(); /* RuntimeAppend (select ... where id = (subquery)) */ + pathman_test_1 +---------------- + ok +(1 row) + +select test.pathman_test_2(); /* RuntimeAppend (select ... where id = any(subquery)) */ + pathman_test_2 +---------------- + ok +(1 row) + +select test.pathman_test_3(); /* RuntimeAppend (a join b on a.id = b.val) */ + pathman_test_3 +---------------- + ok +(1 row) + +select test.pathman_test_4(); /* RuntimeMergeAppend (lateral) */ + pathman_test_4 +---------------- + ok +(1 row) + +select test.pathman_test_5(); /* projection tests for RuntimeXXX nodes */ + pathman_test_5 +---------------- + ok +(1 row) + +/* RuntimeAppend (join, enabled parent) */ +select pathman.set_enable_parent('test.runtime_test_1', true); + set_enable_parent +------------------- + +(1 row) + +explain (costs off) +select from test.runtime_test_1 as t1 +join (select * from test.run_values limit 4) as t2 on t1.id = t2.val; + QUERY PLAN +-------------------------------------------------------------------------------- + Nested Loop + -> Limit + -> Seq Scan on run_values + -> Custom Scan (RuntimeAppend) + Prune by: (t1.id = run_values.val) + -> Seq Scan on runtime_test_1 t1 + Filter: (id = run_values.val) + -> Index Only Scan using runtime_test_1_0_pkey on runtime_test_1_0 t1 + Index Cond: (id = run_values.val) + -> Index Only Scan using runtime_test_1_1_pkey on runtime_test_1_1 t1 + Index Cond: (id = run_values.val) + -> Index Only Scan using runtime_test_1_2_pkey on runtime_test_1_2 t1 + Index Cond: (id = run_values.val) + -> Index Only Scan using runtime_test_1_3_pkey on runtime_test_1_3 t1 + Index Cond: (id = run_values.val) + -> Index Only Scan using runtime_test_1_4_pkey on runtime_test_1_4 t1 + Index Cond: (id = run_values.val) + -> Index Only Scan using runtime_test_1_5_pkey on runtime_test_1_5 t1 + Index Cond: (id = run_values.val) +(19 rows) + +select from test.runtime_test_1 as t1 +join (select * from test.run_values limit 4) as t2 on t1.id = t2.val; +-- +(4 rows) + +/* RuntimeAppend (join, disabled parent) */ +select pathman.set_enable_parent('test.runtime_test_1', false); + set_enable_parent +------------------- + +(1 row) + +explain (costs off) +select from test.runtime_test_1 as t1 +join (select * from test.run_values limit 4) as t2 on t1.id = t2.val; + QUERY PLAN +-------------------------------------------------------------------------------- + Nested Loop + -> Limit + -> Seq Scan on run_values + -> Custom Scan (RuntimeAppend) + Prune by: (t1.id = run_values.val) + -> Index Only Scan using runtime_test_1_0_pkey on runtime_test_1_0 t1 + Index Cond: (id = run_values.val) + -> Index Only Scan using runtime_test_1_1_pkey on runtime_test_1_1 t1 + Index Cond: (id = run_values.val) + -> Index Only Scan using runtime_test_1_2_pkey on runtime_test_1_2 t1 + Index Cond: (id = run_values.val) + -> Index Only Scan using runtime_test_1_3_pkey on runtime_test_1_3 t1 + Index Cond: (id = run_values.val) + -> Index Only Scan using runtime_test_1_4_pkey on runtime_test_1_4 t1 + Index Cond: (id = run_values.val) + -> Index Only Scan using runtime_test_1_5_pkey on runtime_test_1_5 t1 + Index Cond: (id = run_values.val) +(17 rows) + +select from test.runtime_test_1 as t1 +join (select * from test.run_values limit 4) as t2 on t1.id = t2.val; +-- +(4 rows) + +/* RuntimeAppend (join, additional projections) */ +select generate_series(1, 2) from test.runtime_test_1 as t1 +join (select * from test.run_values limit 4) as t2 on t1.id = t2.val; + generate_series +----------------- + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 +(8 rows) + +/* RuntimeAppend (select ... where id = ANY (subquery), missing partitions) */ +select count(*) = 0 from pathman.pathman_partition_list +where parent = 'test.runtime_test_4'::regclass and coalesce(range_min::int, 1) < 0; + ?column? +---------- + t +(1 row) + +/* RuntimeAppend (check that dropped columns don't break tlists) */ +create table test.dropped_cols(val int4 not null); +select pathman.create_hash_partitions('test.dropped_cols', 'val', 4); + create_hash_partitions +------------------------ + 4 +(1 row) + +insert into test.dropped_cols select generate_series(1, 100); +alter table test.dropped_cols add column new_col text; /* add column */ +alter table test.dropped_cols drop column new_col; /* drop column! */ +explain (costs off) select * from generate_series(1, 10) f(id), lateral (select count(1) FILTER (WHERE true) from test.dropped_cols where val = f.id) c; + QUERY PLAN +----------------------------------------------------------- + Nested Loop + -> Function Scan on generate_series f + -> Aggregate + -> Custom Scan (RuntimeAppend) + Prune by: (dropped_cols.val = f.id) + -> Seq Scan on dropped_cols_0 dropped_cols + Filter: (val = f.id) + -> Seq Scan on dropped_cols_1 dropped_cols + Filter: (val = f.id) + -> Seq Scan on dropped_cols_2 dropped_cols + Filter: (val = f.id) + -> Seq Scan on dropped_cols_3 dropped_cols + Filter: (val = f.id) +(13 rows) + +drop table test.dropped_cols cascade; +NOTICE: drop cascades to 4 other objects +set enable_hashjoin = off; +set enable_mergejoin = off; +select from test.runtime_test_4 +where id = any (select generate_series(-10, -1)); /* should be empty */ +-- +(0 rows) + +set enable_hashjoin = on; +set enable_mergejoin = on; +DROP TABLE test.vals CASCADE; +DROP TABLE test.category CASCADE; +DROP TABLE test.run_values CASCADE; +DROP TABLE test.runtime_test_1 CASCADE; +NOTICE: drop cascades to 6 other objects +DROP TABLE test.runtime_test_2 CASCADE; +NOTICE: drop cascades to 6 other objects +DROP TABLE test.runtime_test_3 CASCADE; +NOTICE: drop cascades to 4 other objects +DROP TABLE test.runtime_test_4 CASCADE; +NOTICE: drop cascades to 6 other objects +DROP FUNCTION test.pathman_assert(bool, text); +DROP FUNCTION test.pathman_equal(text, text, text); +DROP FUNCTION test.pathman_test(text); +DROP FUNCTION test.pathman_test_1(); +DROP FUNCTION test.pathman_test_2(); +DROP FUNCTION test.pathman_test_3(); +DROP FUNCTION test.pathman_test_4(); +DROP FUNCTION test.pathman_test_5(); +DROP SCHEMA test; +-- +-- +-- PGPRO-7928 +-- Variable pg_pathman.enable must be called before any query. +-- +CREATE TABLE part_test (val int NOT NULL); +SELECT pathman.create_hash_partitions('part_test', 'val', 2, partition_names := array['part_test_1','pg_pathman']); + create_hash_partitions +------------------------ + 2 +(1 row) + +CREATE OR REPLACE FUNCTION part_test_trigger() RETURNS TRIGGER AS $$ +BEGIN + RAISE NOTICE '%', format('%s %s %s (%s)', TG_WHEN, TG_OP, TG_LEVEL, TG_TABLE_NAME); + IF TG_OP::text = 'DELETE'::text then + SET pg_pathman.enable = f; + RETURN new; + END IF; +END; +$$ LANGUAGE PLPGSQL; +SET pg_pathman.enable_partitionrouter = t; +CREATE TRIGGER ad AFTER DELETE ON part_test_1 FOR EACH ROW EXECUTE PROCEDURE part_test_trigger (); +INSERT INTO part_test VALUES (1); +UPDATE part_test SET val = val + 1 RETURNING *, tableoid::regclass; + val | tableoid +-----+------------- + 2 | part_test_1 +(1 row) + +UPDATE part_test SET val = val + 1 RETURNING *, tableoid::regclass; +NOTICE: AFTER DELETE ROW (part_test_1) +WARNING: "pg_pathman.enable" must be called before any query, ignored + val | tableoid +-----+------------ + 3 | pg_pathman +(1 row) + +RESET pg_pathman.enable_partitionrouter; +DROP TABLE part_test CASCADE; +NOTICE: drop cascades to 2 other objects +DROP FUNCTION part_test_trigger(); +DROP EXTENSION pg_pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_subpartitions.out b/expected/pathman_subpartitions.out index c13b4ee8..3a6a19eb 100644 --- a/expected/pathman_subpartitions.out +++ b/expected/pathman_subpartitions.out @@ -417,7 +417,8 @@ SELECT tableoid::regclass, * FROM subpartitions.abc ORDER BY id1, id2, val; (4 rows) SET pg_pathman.enable_partitionrouter = ON; -UPDATE subpartitions.abc SET id1 = -1, id2 = -1 RETURNING tableoid::regclass, *; +WITH updated AS (UPDATE subpartitions.abc SET id1 = -1, id2 = -1 RETURNING tableoid::regclass, *) +SELECT * FROM updated ORDER BY val ASC; tableoid | id1 | id2 | val -----------------------+-----+-----+----- subpartitions.abc_3_4 | -1 | -1 | 1 @@ -461,6 +462,6 @@ SELECT a2.* FROM subpartitions.a1 JOIN subpartitions.a2 ON a2.n1=a1.n1 FOR UPDAT DROP TABLE subpartitions.a2 CASCADE; NOTICE: drop cascades to 4 other objects DROP TABLE subpartitions.a1; -DROP SCHEMA subpartitions CASCADE; -NOTICE: drop cascades to function subpartitions.partitions_tree(regclass,text) +DROP FUNCTION subpartitions.partitions_tree(regclass, text); +DROP SCHEMA subpartitions; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_subpartitions_1.out b/expected/pathman_subpartitions_1.out index f190f798..d620cde9 100644 --- a/expected/pathman_subpartitions_1.out +++ b/expected/pathman_subpartitions_1.out @@ -411,7 +411,8 @@ SELECT tableoid::regclass, * FROM subpartitions.abc ORDER BY id1, id2, val; (4 rows) SET pg_pathman.enable_partitionrouter = ON; -UPDATE subpartitions.abc SET id1 = -1, id2 = -1 RETURNING tableoid::regclass, *; +WITH updated AS (UPDATE subpartitions.abc SET id1 = -1, id2 = -1 RETURNING tableoid::regclass, *) +SELECT * FROM updated ORDER BY val ASC; tableoid | id1 | id2 | val -----------------------+-----+-----+----- subpartitions.abc_3_4 | -1 | -1 | 1 @@ -455,6 +456,6 @@ SELECT a2.* FROM subpartitions.a1 JOIN subpartitions.a2 ON a2.n1=a1.n1 FOR UPDAT DROP TABLE subpartitions.a2 CASCADE; NOTICE: drop cascades to 4 other objects DROP TABLE subpartitions.a1; -DROP SCHEMA subpartitions CASCADE; -NOTICE: drop cascades to function subpartitions.partitions_tree(regclass,text) +DROP FUNCTION subpartitions.partitions_tree(regclass, text); +DROP SCHEMA subpartitions; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_subpartitions_2.out b/expected/pathman_subpartitions_2.out new file mode 100644 index 00000000..26eae913 --- /dev/null +++ b/expected/pathman_subpartitions_2.out @@ -0,0 +1,461 @@ +/* + * Since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated, + * causing different output; pathman_subpartitions_1.out is the updated version. + */ +\set VERBOSITY terse +CREATE EXTENSION pg_pathman; +CREATE SCHEMA subpartitions; +/* Create two level partitioning structure */ +CREATE TABLE subpartitions.abc(a INTEGER NOT NULL, b INTEGER NOT NULL); +INSERT INTO subpartitions.abc SELECT i, i FROM generate_series(1, 200, 20) as i; +SELECT create_range_partitions('subpartitions.abc', 'a', 0, 100, 2); + create_range_partitions +------------------------- + 2 +(1 row) + +SELECT create_hash_partitions('subpartitions.abc_1', 'a', 3); + create_hash_partitions +------------------------ + 3 +(1 row) + +SELECT create_hash_partitions('subpartitions.abc_2', 'b', 2); + create_hash_partitions +------------------------ + 2 +(1 row) + +SELECT * FROM pathman_partition_list; + parent | partition | parttype | expr | range_min | range_max +---------------------+-----------------------+----------+------+-----------+----------- + subpartitions.abc | subpartitions.abc_1 | 2 | a | 0 | 100 + subpartitions.abc | subpartitions.abc_2 | 2 | a | 100 | 200 + subpartitions.abc_1 | subpartitions.abc_1_0 | 1 | a | | + subpartitions.abc_1 | subpartitions.abc_1_1 | 1 | a | | + subpartitions.abc_1 | subpartitions.abc_1_2 | 1 | a | | + subpartitions.abc_2 | subpartitions.abc_2_0 | 1 | b | | + subpartitions.abc_2 | subpartitions.abc_2_1 | 1 | b | | +(7 rows) + +SELECT tableoid::regclass, * FROM subpartitions.abc ORDER BY a, b; + tableoid | a | b +-----------------------+-----+----- + subpartitions.abc_1_2 | 1 | 1 + subpartitions.abc_1_0 | 21 | 21 + subpartitions.abc_1_1 | 41 | 41 + subpartitions.abc_1_0 | 61 | 61 + subpartitions.abc_1_2 | 81 | 81 + subpartitions.abc_2_0 | 101 | 101 + subpartitions.abc_2_1 | 121 | 121 + subpartitions.abc_2_0 | 141 | 141 + subpartitions.abc_2_1 | 161 | 161 + subpartitions.abc_2_1 | 181 | 181 +(10 rows) + +/* Insert should result in creation of new subpartition */ +SELECT append_range_partition('subpartitions.abc', 'subpartitions.abc_3'); + append_range_partition +------------------------ + subpartitions.abc_3 +(1 row) + +SELECT create_range_partitions('subpartitions.abc_3', 'b', 200, 10, 2); + create_range_partitions +------------------------- + 2 +(1 row) + +SELECT * FROM pathman_partition_list WHERE parent = 'subpartitions.abc_3'::regclass; + parent | partition | parttype | expr | range_min | range_max +---------------------+-----------------------+----------+------+-----------+----------- + subpartitions.abc_3 | subpartitions.abc_3_1 | 2 | b | 200 | 210 + subpartitions.abc_3 | subpartitions.abc_3_2 | 2 | b | 210 | 220 +(2 rows) + +INSERT INTO subpartitions.abc VALUES (215, 215); +SELECT * FROM pathman_partition_list WHERE parent = 'subpartitions.abc_3'::regclass; + parent | partition | parttype | expr | range_min | range_max +---------------------+-----------------------+----------+------+-----------+----------- + subpartitions.abc_3 | subpartitions.abc_3_1 | 2 | b | 200 | 210 + subpartitions.abc_3 | subpartitions.abc_3_2 | 2 | b | 210 | 220 +(2 rows) + +SELECT tableoid::regclass, * FROM subpartitions.abc WHERE a = 215 AND b = 215 ORDER BY a, b; + tableoid | a | b +-----------------------+-----+----- + subpartitions.abc_3_2 | 215 | 215 +(1 row) + +/* Pruning tests */ +EXPLAIN (COSTS OFF) SELECT * FROM subpartitions.abc WHERE a < 150; + QUERY PLAN +--------------------------------------- + Append + -> Append + -> Seq Scan on abc_1_0 abc_2 + -> Seq Scan on abc_1_1 abc_3 + -> Seq Scan on abc_1_2 abc_4 + -> Append + -> Seq Scan on abc_2_0 abc_6 + Filter: (a < 150) + -> Seq Scan on abc_2_1 abc_7 + Filter: (a < 150) +(10 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM subpartitions.abc WHERE b = 215; + QUERY PLAN +--------------------------------------- + Append + -> Append + -> Seq Scan on abc_1_0 abc_2 + Filter: (b = 215) + -> Seq Scan on abc_1_1 abc_3 + Filter: (b = 215) + -> Seq Scan on abc_1_2 abc_4 + Filter: (b = 215) + -> Seq Scan on abc_2_1 abc_5 + Filter: (b = 215) + -> Seq Scan on abc_3_2 abc_6 + Filter: (b = 215) +(12 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM subpartitions.abc WHERE a = 215 AND b = 215; + QUERY PLAN +------------------------------------- + Seq Scan on abc_3_2 abc + Filter: ((a = 215) AND (b = 215)) +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM subpartitions.abc WHERE a >= 210 AND b >= 210; + QUERY PLAN +------------------------- + Seq Scan on abc_3_2 abc + Filter: (a >= 210) +(2 rows) + +CREATE OR REPLACE FUNCTION check_multilevel_queries() +RETURNS VOID AS +$$ +BEGIN + IF NOT EXISTS(SELECT * FROM (SELECT tableoid::regclass, * + FROM subpartitions.abc + WHERE a = 215 AND b = 215 + ORDER BY a, b) t1) + THEN + RAISE EXCEPTION 'should be at least one record in result'; + END IF; +END +$$ LANGUAGE plpgsql; +SELECT check_multilevel_queries(); + check_multilevel_queries +-------------------------- + +(1 row) + +DROP FUNCTION check_multilevel_queries(); +/* Multilevel partitioning with updates */ +CREATE OR REPLACE FUNCTION subpartitions.partitions_tree( + rel REGCLASS, + level TEXT DEFAULT ' ' +) +RETURNS SETOF TEXT AS +$$ +DECLARE + partition REGCLASS; + subpartition TEXT; +BEGIN + IF rel IS NULL THEN + RETURN; + END IF; + + RETURN NEXT rel::TEXT; + + FOR partition IN (SELECT l.partition FROM pathman_partition_list l WHERE parent = rel) + LOOP + FOR subpartition IN (SELECT subpartitions.partitions_tree(partition, level || ' ')) + LOOP + RETURN NEXT level || subpartition::TEXT; + END LOOP; + END LOOP; +END +$$ LANGUAGE plpgsql; +SELECT append_range_partition('subpartitions.abc', 'subpartitions.abc_4'); + append_range_partition +------------------------ + subpartitions.abc_4 +(1 row) + +SELECT create_hash_partitions('subpartitions.abc_4', 'b', 2); + create_hash_partitions +------------------------ + 2 +(1 row) + +SELECT subpartitions.partitions_tree('subpartitions.abc'); + partitions_tree +-------------------------- + subpartitions.abc + subpartitions.abc_1 + subpartitions.abc_1_0 + subpartitions.abc_1_1 + subpartitions.abc_1_2 + subpartitions.abc_2 + subpartitions.abc_2_0 + subpartitions.abc_2_1 + subpartitions.abc_3 + subpartitions.abc_3_1 + subpartitions.abc_3_2 + subpartitions.abc_4 + subpartitions.abc_4_0 + subpartitions.abc_4_1 +(14 rows) + +DROP TABLE subpartitions.abc CASCADE; +NOTICE: drop cascades to 15 other objects +/* Test that update works correctly */ +SET pg_pathman.enable_partitionrouter = ON; +CREATE TABLE subpartitions.abc(a INTEGER NOT NULL, b INTEGER NOT NULL); +SELECT create_range_partitions('subpartitions.abc', 'a', 0, 100, 2); + create_range_partitions +------------------------- + 2 +(1 row) + +SELECT create_range_partitions('subpartitions.abc_1', 'b', 0, 50, 2); /* 0 - 100 */ + create_range_partitions +------------------------- + 2 +(1 row) + +SELECT create_range_partitions('subpartitions.abc_2', 'b', 0, 50, 2); /* 100 - 200 */ + create_range_partitions +------------------------- + 2 +(1 row) + +INSERT INTO subpartitions.abc SELECT 25, 25 FROM generate_series(1, 10); +SELECT tableoid::regclass, * FROM subpartitions.abc; /* subpartitions.abc_1_1 */ + tableoid | a | b +-----------------------+----+---- + subpartitions.abc_1_1 | 25 | 25 + subpartitions.abc_1_1 | 25 | 25 + subpartitions.abc_1_1 | 25 | 25 + subpartitions.abc_1_1 | 25 | 25 + subpartitions.abc_1_1 | 25 | 25 + subpartitions.abc_1_1 | 25 | 25 + subpartitions.abc_1_1 | 25 | 25 + subpartitions.abc_1_1 | 25 | 25 + subpartitions.abc_1_1 | 25 | 25 + subpartitions.abc_1_1 | 25 | 25 +(10 rows) + +UPDATE subpartitions.abc SET a = 125 WHERE a = 25 and b = 25; +SELECT tableoid::regclass, * FROM subpartitions.abc; /* subpartitions.abc_2_1 */ + tableoid | a | b +-----------------------+-----+---- + subpartitions.abc_2_1 | 125 | 25 + subpartitions.abc_2_1 | 125 | 25 + subpartitions.abc_2_1 | 125 | 25 + subpartitions.abc_2_1 | 125 | 25 + subpartitions.abc_2_1 | 125 | 25 + subpartitions.abc_2_1 | 125 | 25 + subpartitions.abc_2_1 | 125 | 25 + subpartitions.abc_2_1 | 125 | 25 + subpartitions.abc_2_1 | 125 | 25 + subpartitions.abc_2_1 | 125 | 25 +(10 rows) + +UPDATE subpartitions.abc SET b = 75 WHERE a = 125 and b = 25; +SELECT tableoid::regclass, * FROM subpartitions.abc; /* subpartitions.abc_2_2 */ + tableoid | a | b +-----------------------+-----+---- + subpartitions.abc_2_2 | 125 | 75 + subpartitions.abc_2_2 | 125 | 75 + subpartitions.abc_2_2 | 125 | 75 + subpartitions.abc_2_2 | 125 | 75 + subpartitions.abc_2_2 | 125 | 75 + subpartitions.abc_2_2 | 125 | 75 + subpartitions.abc_2_2 | 125 | 75 + subpartitions.abc_2_2 | 125 | 75 + subpartitions.abc_2_2 | 125 | 75 + subpartitions.abc_2_2 | 125 | 75 +(10 rows) + +UPDATE subpartitions.abc SET b = 125 WHERE a = 125 and b = 75; +SELECT tableoid::regclass, * FROM subpartitions.abc; /* subpartitions.abc_2_3 */ + tableoid | a | b +-----------------------+-----+----- + subpartitions.abc_2_3 | 125 | 125 + subpartitions.abc_2_3 | 125 | 125 + subpartitions.abc_2_3 | 125 | 125 + subpartitions.abc_2_3 | 125 | 125 + subpartitions.abc_2_3 | 125 | 125 + subpartitions.abc_2_3 | 125 | 125 + subpartitions.abc_2_3 | 125 | 125 + subpartitions.abc_2_3 | 125 | 125 + subpartitions.abc_2_3 | 125 | 125 + subpartitions.abc_2_3 | 125 | 125 +(10 rows) + +/* split_range_partition */ +SELECT split_range_partition('subpartitions.abc_2', 150); /* FAIL */ +ERROR: cannot split partition that has children +SELECT split_range_partition('subpartitions.abc_2_2', 75); /* OK */ + split_range_partition +----------------------- + subpartitions.abc_2_4 +(1 row) + +SELECT subpartitions.partitions_tree('subpartitions.abc'); + partitions_tree +-------------------------- + subpartitions.abc + subpartitions.abc_1 + subpartitions.abc_1_1 + subpartitions.abc_1_2 + subpartitions.abc_2 + subpartitions.abc_2_1 + subpartitions.abc_2_2 + subpartitions.abc_2_4 + subpartitions.abc_2_3 +(9 rows) + +/* merge_range_partitions */ +TRUNCATE subpartitions.abc; +INSERT INTO subpartitions.abc VALUES (150, 0); +SELECT append_range_partition('subpartitions.abc', 'subpartitions.abc_3'); /* 200 - 300 */ + append_range_partition +------------------------ + subpartitions.abc_3 +(1 row) + +INSERT INTO subpartitions.abc VALUES (250, 50); +SELECT merge_range_partitions('subpartitions.abc_2', 'subpartitions.abc_3'); /* OK */ + merge_range_partitions +------------------------ + subpartitions.abc_2 +(1 row) + +SELECT tableoid::regclass, * FROM subpartitions.abc ORDER BY a, b; + tableoid | a | b +-----------------------+-----+---- + subpartitions.abc_2_1 | 150 | 0 + subpartitions.abc_2_2 | 250 | 50 +(2 rows) + +SELECT merge_range_partitions('subpartitions.abc_2_1', 'subpartitions.abc_2_2'); /* OK */ + merge_range_partitions +------------------------ + subpartitions.abc_2_1 +(1 row) + +SELECT tableoid::regclass, * FROM subpartitions.abc ORDER BY a, b; + tableoid | a | b +-----------------------+-----+---- + subpartitions.abc_2_1 | 150 | 0 + subpartitions.abc_2_1 | 250 | 50 +(2 rows) + +DROP TABLE subpartitions.abc CASCADE; +NOTICE: drop cascades to 10 other objects +/* Check insert & update with dropped columns */ +CREATE TABLE subpartitions.abc(a int, b int, c int, id1 int not null, id2 int not null, val serial); +SELECT create_range_partitions('subpartitions.abc', 'id1', 0, 100, 2); + create_range_partitions +------------------------- + 2 +(1 row) + +ALTER TABLE subpartitions.abc DROP COLUMN c; +SELECT prepend_range_partition('subpartitions.abc'); + prepend_range_partition +------------------------- + subpartitions.abc_3 +(1 row) + +ALTER TABLE subpartitions.abc DROP COLUMN b; +SELECT create_range_partitions('subpartitions.abc_3', 'id2', 0, 10, 3); + create_range_partitions +------------------------- + 3 +(1 row) + +ALTER TABLE subpartitions.abc DROP COLUMN a; +SELECT prepend_range_partition('subpartitions.abc_3'); + prepend_range_partition +------------------------- + subpartitions.abc_3_4 +(1 row) + +SELECT * FROM pathman_partition_list ORDER BY parent, partition; + parent | partition | parttype | expr | range_min | range_max +---------------------+-----------------------+----------+------+-----------+----------- + subpartitions.abc | subpartitions.abc_1 | 2 | id1 | 0 | 100 + subpartitions.abc | subpartitions.abc_2 | 2 | id1 | 100 | 200 + subpartitions.abc | subpartitions.abc_3 | 2 | id1 | -100 | 0 + subpartitions.abc_3 | subpartitions.abc_3_1 | 2 | id2 | 0 | 10 + subpartitions.abc_3 | subpartitions.abc_3_2 | 2 | id2 | 10 | 20 + subpartitions.abc_3 | subpartitions.abc_3_3 | 2 | id2 | 20 | 30 + subpartitions.abc_3 | subpartitions.abc_3_4 | 2 | id2 | -10 | 0 +(7 rows) + +INSERT INTO subpartitions.abc VALUES (10, 0), (110, 0), (-1, 0), (-1, -1); +SELECT tableoid::regclass, * FROM subpartitions.abc ORDER BY id1, id2, val; + tableoid | id1 | id2 | val +-----------------------+-----+-----+----- + subpartitions.abc_3_4 | -1 | -1 | 4 + subpartitions.abc_3_1 | -1 | 0 | 3 + subpartitions.abc_1 | 10 | 0 | 1 + subpartitions.abc_2 | 110 | 0 | 2 +(4 rows) + +SET pg_pathman.enable_partitionrouter = ON; +WITH updated AS (UPDATE subpartitions.abc SET id1 = -1, id2 = -1 RETURNING tableoid::regclass, *) +SELECT * FROM updated ORDER BY val ASC; + tableoid | id1 | id2 | val +-----------------------+-----+-----+----- + subpartitions.abc_3_4 | -1 | -1 | 1 + subpartitions.abc_3_4 | -1 | -1 | 2 + subpartitions.abc_3_4 | -1 | -1 | 3 + subpartitions.abc_3_4 | -1 | -1 | 4 +(4 rows) + +DROP TABLE subpartitions.abc CASCADE; +NOTICE: drop cascades to 9 other objects +--- basic check how rowmark plays along with subparts; PGPRO-2755 +CREATE TABLE subpartitions.a1(n1 integer); +CREATE TABLE subpartitions.a2(n1 integer not null, n2 integer not null); +SELECT create_range_partitions('subpartitions.a2', 'n1', 1, 2, 0); + create_range_partitions +------------------------- + 0 +(1 row) + +SELECT add_range_partition('subpartitions.a2', 10, 20, 'subpartitions.a2_1020'); + add_range_partition +----------------------- + subpartitions.a2_1020 +(1 row) + +SELECT create_range_partitions('subpartitions.a2_1020'::regclass, 'n2'::text, array[30,40], array['subpartitions.a2_1020_3040']); + create_range_partitions +------------------------- + 1 +(1 row) + +INSERT INTO subpartitions.a2 VALUES (10, 30), (11, 31), (12, 32), (19, 39); +INSERT INTO subpartitions.a1 VALUES (12), (19), (20); +SELECT a2.* FROM subpartitions.a1 JOIN subpartitions.a2 ON a2.n1=a1.n1 FOR UPDATE; + n1 | n2 +----+---- + 12 | 32 + 19 | 39 +(2 rows) + +DROP TABLE subpartitions.a2 CASCADE; +NOTICE: drop cascades to 4 other objects +DROP TABLE subpartitions.a1; +DROP FUNCTION subpartitions.partitions_tree(regclass, text); +DROP SCHEMA subpartitions; +DROP EXTENSION pg_pathman; diff --git a/expected/pathman_upd_del.out b/expected/pathman_upd_del.out index 2cc19239..752cff27 100644 --- a/expected/pathman_upd_del.out +++ b/expected/pathman_upd_del.out @@ -9,6 +9,9 @@ * plans here. There is an option to forcibly make them MATERIALIZED, but we * also need to run tests on older versions, so put updated plans in * pathman_upd_del_2.out instead. + * + * In Postgres Pro Standard/Enterprise 15+ the EXPLAIN output has changed so put + * the updated plan in pathman_upd_del_4.out. */ \set VERBOSITY terse SET search_path = 'public'; @@ -460,7 +463,11 @@ WITH q AS (SELECT id FROM test.tmp2 WHERE id < 3) DELETE FROM test.tmp t WHERE t.id in (SELECT id FROM q); ROLLBACK; -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 27 other objects +DROP TABLE test.tmp CASCADE; +DROP TABLE test.tmp2 CASCADE; +NOTICE: drop cascades to 11 other objects +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 13 other objects +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_upd_del_1.out b/expected/pathman_upd_del_1.out index 5cd5ac9f..6e0f312d 100644 --- a/expected/pathman_upd_del_1.out +++ b/expected/pathman_upd_del_1.out @@ -9,6 +9,9 @@ * plans here. There is an option to forcibly make them MATERIALIZED, but we * also need to run tests on older versions, so put updated plans in * pathman_upd_del_2.out instead. + * + * In Postgres Pro Standard/Enterprise 15+ the EXPLAIN output has changed so put + * the updated plan in pathman_upd_del_4.out. */ \set VERBOSITY terse SET search_path = 'public'; @@ -460,7 +463,11 @@ WITH q AS (SELECT id FROM test.tmp2 WHERE id < 3) DELETE FROM test.tmp t WHERE t.id in (SELECT id FROM q); ROLLBACK; -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 27 other objects +DROP TABLE test.tmp CASCADE; +DROP TABLE test.tmp2 CASCADE; +NOTICE: drop cascades to 11 other objects +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 13 other objects +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_upd_del_2.out b/expected/pathman_upd_del_2.out index 2aeb6702..0826594c 100644 --- a/expected/pathman_upd_del_2.out +++ b/expected/pathman_upd_del_2.out @@ -9,6 +9,9 @@ * plans here. There is an option to forcibly make them MATERIALIZED, but we * also need to run tests on older versions, so put updated plans in * pathman_upd_del_2.out instead. + * + * In Postgres Pro Standard/Enterprise 15+ the EXPLAIN output has changed so put + * the updated plan in pathman_upd_del_4.out. */ \set VERBOSITY terse SET search_path = 'public'; @@ -452,7 +455,11 @@ WITH q AS (SELECT id FROM test.tmp2 WHERE id < 3) DELETE FROM test.tmp t WHERE t.id in (SELECT id FROM q); ROLLBACK; -DROP SCHEMA test CASCADE; -NOTICE: drop cascades to 27 other objects +DROP TABLE test.tmp CASCADE; +DROP TABLE test.tmp2 CASCADE; +NOTICE: drop cascades to 11 other objects +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 13 other objects +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_upd_del_3.out b/expected/pathman_upd_del_3.out new file mode 100644 index 00000000..d11eb6f8 --- /dev/null +++ b/expected/pathman_upd_del_3.out @@ -0,0 +1,465 @@ +/* + * ------------------------------------------- + * NOTE: This test behaves differenly on 9.5 + * ------------------------------------------- + * + * Also since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated, + * causing different output. Moreover, again since 12 (608b167f9f), CTEs which are + * scanned once are no longer an optimization fence, changing a good deal of + * plans here. There is an option to forcibly make them MATERIALIZED, but we + * also need to run tests on older versions, so put updated plans in + * pathman_upd_del_2.out instead. + * + * In Postgres Pro Standard/Enterprise 15+ the EXPLAIN output has changed so put + * the updated plan in pathman_upd_del_4.out. + */ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE SCHEMA pathman; +CREATE EXTENSION pg_pathman SCHEMA pathman; +CREATE SCHEMA test; +SET enable_indexscan = ON; +SET enable_seqscan = OFF; +/* Temporary tables for JOINs */ +CREATE TABLE test.tmp (id INTEGER NOT NULL, value INTEGER NOT NULL); +INSERT INTO test.tmp VALUES (1, 1), (2, 2); +CREATE TABLE test.tmp2 (id INTEGER NOT NULL, value INTEGER NOT NULL); +INSERT INTO test.tmp2 SELECT i % 10 + 1, i FROM generate_series(1, 100) i; +SELECT pathman.create_range_partitions('test.tmp2', 'id', 1, 1, 10); + create_range_partitions +------------------------- + 10 +(1 row) + +/* Partition table by RANGE */ +CREATE TABLE test.range_rel ( + id SERIAL PRIMARY KEY, + dt TIMESTAMP NOT NULL, + value INTEGER); +INSERT INTO test.range_rel (dt, value) SELECT g, extract(day from g) +FROM generate_series('2010-01-01'::date, '2010-12-31'::date, '1 day') AS g; +SELECT pathman.create_range_partitions('test.range_rel', 'dt', + '2010-01-01'::date, '1 month'::interval, + 12); + create_range_partitions +------------------------- + 12 +(1 row) + +VACUUM ANALYZE; +/* + * Test UPDATE and DELETE + */ +/* have partitions for this 'dt' */ +EXPLAIN (COSTS OFF) UPDATE test.range_rel SET value = 111 WHERE dt = '2010-06-15'; + QUERY PLAN +-------------------------------------------------------------------------------- + Update on range_rel_6 + -> Seq Scan on range_rel_6 + Filter: (dt = 'Tue Jun 15 00:00:00 2010'::timestamp without time zone) +(3 rows) + +BEGIN; +UPDATE test.range_rel SET value = 111 WHERE dt = '2010-06-15'; +SELECT * FROM test.range_rel WHERE dt = '2010-06-15'; + id | dt | value +-----+--------------------------+------- + 166 | Tue Jun 15 00:00:00 2010 | 111 +(1 row) + +ROLLBACK; +/* have partitions for this 'dt' */ +EXPLAIN (COSTS OFF) DELETE FROM test.range_rel WHERE dt = '2010-06-15'; + QUERY PLAN +-------------------------------------------------------------------------------- + Delete on range_rel_6 + -> Seq Scan on range_rel_6 + Filter: (dt = 'Tue Jun 15 00:00:00 2010'::timestamp without time zone) +(3 rows) + +BEGIN; +DELETE FROM test.range_rel WHERE dt = '2010-06-15'; +SELECT * FROM test.range_rel WHERE dt = '2010-06-15'; + id | dt | value +----+----+------- +(0 rows) + +ROLLBACK; +/* no partitions for this 'dt' */ +EXPLAIN (COSTS OFF) UPDATE test.range_rel SET value = 222 WHERE dt = '1990-01-01'; + QUERY PLAN +-------------------------------------------------------------------------------- + Update on range_rel + -> Seq Scan on range_rel + Filter: (dt = 'Mon Jan 01 00:00:00 1990'::timestamp without time zone) +(3 rows) + +BEGIN; +UPDATE test.range_rel SET value = 111 WHERE dt = '1990-01-01'; +SELECT * FROM test.range_rel WHERE dt = '1990-01-01'; + id | dt | value +----+----+------- +(0 rows) + +ROLLBACK; +/* no partitions for this 'dt' */ +EXPLAIN (COSTS OFF) DELETE FROM test.range_rel WHERE dt < '1990-01-01'; + QUERY PLAN +-------------------------------------------------------------------------------- + Delete on range_rel + -> Seq Scan on range_rel + Filter: (dt < 'Mon Jan 01 00:00:00 1990'::timestamp without time zone) +(3 rows) + +BEGIN; +DELETE FROM test.range_rel WHERE dt < '1990-01-01'; +SELECT * FROM test.range_rel WHERE dt < '1990-01-01'; + id | dt | value +----+----+------- +(0 rows) + +ROLLBACK; +/* UPDATE + FROM, partitioned table */ +EXPLAIN (COSTS OFF) +UPDATE test.range_rel r SET value = t.value +FROM test.tmp t WHERE r.dt = '2010-01-01' AND r.id = t.id; + QUERY PLAN +-------------------------------------------------------------------------------------- + Update on range_rel_1 r + -> Nested Loop + Join Filter: (r.id = t.id) + -> Index Scan using range_rel_1_pkey on range_rel_1 r + Filter: (dt = 'Fri Jan 01 00:00:00 2010'::timestamp without time zone) + -> Seq Scan on tmp t +(6 rows) + +BEGIN; +UPDATE test.range_rel r SET value = t.value +FROM test.tmp t WHERE r.dt = '2010-01-01' AND r.id = t.id; +ROLLBACK; +/* UPDATE + FROM, single table */ +EXPLAIN (COSTS OFF) +UPDATE test.tmp t SET value = r.value +FROM test.range_rel r WHERE r.dt = '2010-01-01' AND r.id = t.id; + QUERY PLAN +-------------------------------------------------------------------------------------- + Update on tmp t + -> Nested Loop + -> Seq Scan on tmp t + -> Index Scan using range_rel_1_pkey on range_rel_1 r + Index Cond: (id = t.id) + Filter: (dt = 'Fri Jan 01 00:00:00 2010'::timestamp without time zone) +(6 rows) + +BEGIN; +UPDATE test.tmp t SET value = r.value +FROM test.range_rel r WHERE r.dt = '2010-01-01' AND r.id = t.id; +ROLLBACK; +/* DELETE + USING, partitioned table */ +EXPLAIN (COSTS OFF) +DELETE FROM test.range_rel r USING test.tmp t +WHERE r.dt = '2010-01-02' AND r.id = t.id; + QUERY PLAN +-------------------------------------------------------------------------------------- + Delete on range_rel_1 r + -> Nested Loop + Join Filter: (r.id = t.id) + -> Index Scan using range_rel_1_pkey on range_rel_1 r + Filter: (dt = 'Sat Jan 02 00:00:00 2010'::timestamp without time zone) + -> Seq Scan on tmp t +(6 rows) + +BEGIN; +DELETE FROM test.range_rel r USING test.tmp t +WHERE r.dt = '2010-01-02' AND r.id = t.id; +ROLLBACK; +/* DELETE + USING, single table */ +EXPLAIN (COSTS OFF) +DELETE FROM test.tmp t USING test.range_rel r +WHERE r.dt = '2010-01-02' AND r.id = t.id; + QUERY PLAN +-------------------------------------------------------------------------------------- + Delete on tmp t + -> Nested Loop + -> Seq Scan on tmp t + -> Index Scan using range_rel_1_pkey on range_rel_1 r + Index Cond: (id = t.id) + Filter: (dt = 'Sat Jan 02 00:00:00 2010'::timestamp without time zone) +(6 rows) + +BEGIN; +DELETE FROM test.tmp t USING test.range_rel r +WHERE r.dt = '2010-01-02' AND r.id = t.id; +ROLLBACK; +/* DELETE + USING, two partitioned tables */ +EXPLAIN (COSTS OFF) +DELETE FROM test.range_rel r USING test.tmp2 t +WHERE t.id = r.id; +ERROR: DELETE and UPDATE queries with a join of partitioned tables are not supported +BEGIN; +DELETE FROM test.range_rel r USING test.tmp2 t +WHERE t.id = r.id; +ERROR: DELETE and UPDATE queries with a join of partitioned tables are not supported +ROLLBACK; +/* DELETE + USING, partitioned table + two partitioned tables in subselect */ +EXPLAIN (COSTS OFF) +DELETE FROM test.range_rel r +USING (SELECT * + FROM test.tmp2 a1 + JOIN test.tmp2 a2 + USING(id)) t +WHERE t.id = r.id; +ERROR: DELETE and UPDATE queries with a join of partitioned tables are not supported +BEGIN; +DELETE FROM test.range_rel r +USING (SELECT * + FROM test.tmp2 a1 + JOIN test.tmp2 a2 + USING(id)) t +WHERE t.id = r.id; +ERROR: DELETE and UPDATE queries with a join of partitioned tables are not supported +ROLLBACK; +/* DELETE + USING, single table + two partitioned tables in subselect */ +EXPLAIN (COSTS OFF) +DELETE FROM test.tmp r +USING (SELECT * + FROM test.tmp2 a1 + JOIN test.tmp2 a2 + USING(id)) t +WHERE t.id = r.id; + QUERY PLAN +------------------------------------------------ + Delete on tmp r + -> Nested Loop + -> Nested Loop + -> Seq Scan on tmp r + -> Custom Scan (RuntimeAppend) + Prune by: (r.id = a1.id) + -> Seq Scan on tmp2_1 a1 + Filter: (r.id = id) + -> Seq Scan on tmp2_2 a1 + Filter: (r.id = id) + -> Seq Scan on tmp2_3 a1 + Filter: (r.id = id) + -> Seq Scan on tmp2_4 a1 + Filter: (r.id = id) + -> Seq Scan on tmp2_5 a1 + Filter: (r.id = id) + -> Seq Scan on tmp2_6 a1 + Filter: (r.id = id) + -> Seq Scan on tmp2_7 a1 + Filter: (r.id = id) + -> Seq Scan on tmp2_8 a1 + Filter: (r.id = id) + -> Seq Scan on tmp2_9 a1 + Filter: (r.id = id) + -> Seq Scan on tmp2_10 a1 + Filter: (r.id = id) + -> Custom Scan (RuntimeAppend) + Prune by: (a1.id = a2.id) + -> Seq Scan on tmp2_1 a2 + Filter: (a1.id = id) + -> Seq Scan on tmp2_2 a2 + Filter: (a1.id = id) + -> Seq Scan on tmp2_3 a2 + Filter: (a1.id = id) + -> Seq Scan on tmp2_4 a2 + Filter: (a1.id = id) + -> Seq Scan on tmp2_5 a2 + Filter: (a1.id = id) + -> Seq Scan on tmp2_6 a2 + Filter: (a1.id = id) + -> Seq Scan on tmp2_7 a2 + Filter: (a1.id = id) + -> Seq Scan on tmp2_8 a2 + Filter: (a1.id = id) + -> Seq Scan on tmp2_9 a2 + Filter: (a1.id = id) + -> Seq Scan on tmp2_10 a2 + Filter: (a1.id = id) +(48 rows) + +BEGIN; +DELETE FROM test.tmp r +USING (SELECT * + FROM test.tmp2 a1 + JOIN test.tmp2 a2 + USING(id)) t +WHERE t.id = r.id; +ROLLBACK; +/* UPDATE + FROM, two partitioned tables */ +EXPLAIN (COSTS OFF) +UPDATE test.range_rel r SET value = 1 FROM test.tmp2 t +WHERE t.id = r.id; +ERROR: DELETE and UPDATE queries with a join of partitioned tables are not supported +BEGIN; +UPDATE test.range_rel r SET value = 1 FROM test.tmp2 t +WHERE t.id = r.id; +ERROR: DELETE and UPDATE queries with a join of partitioned tables are not supported +ROLLBACK; +/* + * UPDATE + subquery with partitioned table (PG 9.5). + * See pathman_rel_pathlist_hook() + RELOPT_OTHER_MEMBER_REL. + */ +EXPLAIN (COSTS OFF) +UPDATE test.tmp t SET value = 2 +WHERE t.id IN (SELECT id + FROM test.tmp2 t2 + WHERE id = t.id); + QUERY PLAN +-------------------------------------------- + Update on tmp t + -> Seq Scan on tmp t + Filter: (SubPlan 1) + SubPlan 1 + -> Custom Scan (RuntimeAppend) + Prune by: (t2.id = t.id) + -> Seq Scan on tmp2_1 t2 + Filter: (id = t.id) + -> Seq Scan on tmp2_2 t2 + Filter: (id = t.id) + -> Seq Scan on tmp2_3 t2 + Filter: (id = t.id) + -> Seq Scan on tmp2_4 t2 + Filter: (id = t.id) + -> Seq Scan on tmp2_5 t2 + Filter: (id = t.id) + -> Seq Scan on tmp2_6 t2 + Filter: (id = t.id) + -> Seq Scan on tmp2_7 t2 + Filter: (id = t.id) + -> Seq Scan on tmp2_8 t2 + Filter: (id = t.id) + -> Seq Scan on tmp2_9 t2 + Filter: (id = t.id) + -> Seq Scan on tmp2_10 t2 + Filter: (id = t.id) +(26 rows) + +/* Test special rule for CTE; SELECT (PostgreSQL 9.5) */ +EXPLAIN (COSTS OFF) +WITH q AS (SELECT * FROM test.range_rel r + WHERE r.dt = '2010-01-02') +DELETE FROM test.tmp USING q; + QUERY PLAN +-------------------------------------------------------------------------------------------- + Delete on tmp + -> Nested Loop + -> Seq Scan on tmp + -> Materialize + -> Seq Scan on range_rel_1 r + Filter: (dt = 'Sat Jan 02 00:00:00 2010'::timestamp without time zone) +(6 rows) + +BEGIN; +WITH q AS (SELECT * FROM test.range_rel r + WHERE r.dt = '2010-01-02') +DELETE FROM test.tmp USING q; +ROLLBACK; +/* Test special rule for CTE; DELETE (PostgreSQL 9.5) */ +EXPLAIN (COSTS OFF) +WITH q AS (DELETE FROM test.range_rel r + WHERE r.dt = '2010-01-02' + RETURNING *) +DELETE FROM test.tmp USING q; + QUERY PLAN +---------------------------------------------------------------------------------------- + Delete on tmp + CTE q + -> Delete on range_rel_1 r + -> Seq Scan on range_rel_1 r + Filter: (dt = 'Sat Jan 02 00:00:00 2010'::timestamp without time zone) + -> Nested Loop + -> Seq Scan on tmp + -> CTE Scan on q +(8 rows) + +BEGIN; +WITH q AS (DELETE FROM test.range_rel r + WHERE r.dt = '2010-01-02' + RETURNING *) +DELETE FROM test.tmp USING q; +ROLLBACK; +/* Test special rule for CTE; DELETE + USING (PostgreSQL 9.5) */ +EXPLAIN (COSTS OFF) +WITH q AS (DELETE FROM test.tmp t + USING test.range_rel r + WHERE r.dt = '2010-01-02' AND r.id = t.id + RETURNING *) +DELETE FROM test.tmp USING q; + QUERY PLAN +---------------------------------------------------------------------------------------------- + Delete on tmp + CTE q + -> Delete on tmp t + -> Nested Loop + -> Seq Scan on tmp t + -> Index Scan using range_rel_1_pkey on range_rel_1 r + Index Cond: (id = t.id) + Filter: (dt = 'Sat Jan 02 00:00:00 2010'::timestamp without time zone) + -> Nested Loop + -> Seq Scan on tmp + -> CTE Scan on q +(11 rows) + +BEGIN; +WITH q AS (DELETE FROM test.tmp t + USING test.range_rel r + WHERE r.dt = '2010-01-02' AND r.id = t.id + RETURNING *) +DELETE FROM test.tmp USING q; +ROLLBACK; +/* Test special rule for CTE; Nested CTEs (PostgreSQL 9.5) */ +EXPLAIN (COSTS OFF) +WITH q AS (WITH n AS (SELECT id FROM test.tmp2 WHERE id = 2) + DELETE FROM test.tmp t + USING n + WHERE t.id = n.id + RETURNING *) +DELETE FROM test.tmp USING q; + QUERY PLAN +--------------------------------------------- + Delete on tmp + CTE q + -> Delete on tmp t + -> Nested Loop + -> Seq Scan on tmp t + Filter: (id = 2) + -> Seq Scan on tmp2_2 tmp2 + Filter: (id = 2) + -> Nested Loop + -> Seq Scan on tmp + -> CTE Scan on q +(11 rows) + +/* Test special rule for CTE; CTE in quals (PostgreSQL 9.5) */ +EXPLAIN (COSTS OFF) +WITH q AS (SELECT id FROM test.tmp2 + WHERE id < 3) +DELETE FROM test.tmp t WHERE t.id in (SELECT id FROM q); + QUERY PLAN +-------------------------------------------------------------- + Delete on tmp t + -> Nested Loop Semi Join + -> Seq Scan on tmp t + -> Custom Scan (RuntimeAppend) + Prune by: ((tmp2.id < 3) AND (t.id = tmp2.id)) + -> Seq Scan on tmp2_1 tmp2 + Filter: (t.id = id) + -> Seq Scan on tmp2_2 tmp2 + Filter: (t.id = id) +(9 rows) + +BEGIN; +WITH q AS (SELECT id FROM test.tmp2 + WHERE id < 3) +DELETE FROM test.tmp t WHERE t.id in (SELECT id FROM q); +ROLLBACK; +DROP TABLE test.tmp CASCADE; +DROP TABLE test.tmp2 CASCADE; +NOTICE: drop cascades to 11 other objects +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 13 other objects +DROP SCHEMA test; +DROP EXTENSION pg_pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_upd_del_4.out b/expected/pathman_upd_del_4.out new file mode 100644 index 00000000..54330190 --- /dev/null +++ b/expected/pathman_upd_del_4.out @@ -0,0 +1,464 @@ +/* + * ------------------------------------------- + * NOTE: This test behaves differenly on 9.5 + * ------------------------------------------- + * + * Also since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated, + * causing different output. Moreover, again since 12 (608b167f9f), CTEs which are + * scanned once are no longer an optimization fence, changing a good deal of + * plans here. There is an option to forcibly make them MATERIALIZED, but we + * also need to run tests on older versions, so put updated plans in + * pathman_upd_del_2.out instead. + * + * In Postgres Pro Standard/Enterprise 15+ the EXPLAIN output has changed so put + * the updated plan in pathman_upd_del_4.out. + */ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE SCHEMA pathman; +CREATE EXTENSION pg_pathman SCHEMA pathman; +CREATE SCHEMA test; +SET enable_indexscan = ON; +SET enable_seqscan = OFF; +/* Temporary tables for JOINs */ +CREATE TABLE test.tmp (id INTEGER NOT NULL, value INTEGER NOT NULL); +INSERT INTO test.tmp VALUES (1, 1), (2, 2); +CREATE TABLE test.tmp2 (id INTEGER NOT NULL, value INTEGER NOT NULL); +INSERT INTO test.tmp2 SELECT i % 10 + 1, i FROM generate_series(1, 100) i; +SELECT pathman.create_range_partitions('test.tmp2', 'id', 1, 1, 10); + create_range_partitions +------------------------- + 10 +(1 row) + +/* Partition table by RANGE */ +CREATE TABLE test.range_rel ( + id SERIAL PRIMARY KEY, + dt TIMESTAMP NOT NULL, + value INTEGER); +INSERT INTO test.range_rel (dt, value) SELECT g, extract(day from g) +FROM generate_series('2010-01-01'::date, '2010-12-31'::date, '1 day') AS g; +SELECT pathman.create_range_partitions('test.range_rel', 'dt', + '2010-01-01'::date, '1 month'::interval, + 12); + create_range_partitions +------------------------- + 12 +(1 row) + +VACUUM ANALYZE; +/* + * Test UPDATE and DELETE + */ +/* have partitions for this 'dt' */ +EXPLAIN (COSTS OFF) UPDATE test.range_rel SET value = 111 WHERE dt = '2010-06-15'; + QUERY PLAN +-------------------------------------------------------------------------------- + Update on range_rel_6 + -> Seq Scan on range_rel_6 + Filter: (dt = 'Tue Jun 15 00:00:00 2010'::timestamp without time zone) +(3 rows) + +BEGIN; +UPDATE test.range_rel SET value = 111 WHERE dt = '2010-06-15'; +SELECT * FROM test.range_rel WHERE dt = '2010-06-15'; + id | dt | value +-----+--------------------------+------- + 166 | Tue Jun 15 00:00:00 2010 | 111 +(1 row) + +ROLLBACK; +/* have partitions for this 'dt' */ +EXPLAIN (COSTS OFF) DELETE FROM test.range_rel WHERE dt = '2010-06-15'; + QUERY PLAN +-------------------------------------------------------------------------------- + Delete on range_rel_6 + -> Seq Scan on range_rel_6 + Filter: (dt = 'Tue Jun 15 00:00:00 2010'::timestamp without time zone) +(3 rows) + +BEGIN; +DELETE FROM test.range_rel WHERE dt = '2010-06-15'; +SELECT * FROM test.range_rel WHERE dt = '2010-06-15'; + id | dt | value +----+----+------- +(0 rows) + +ROLLBACK; +/* no partitions for this 'dt' */ +EXPLAIN (COSTS OFF) UPDATE test.range_rel SET value = 222 WHERE dt = '1990-01-01'; + QUERY PLAN +-------------------------------------------------------------------------------- + Update on range_rel + -> Seq Scan on range_rel + Filter: (dt = 'Mon Jan 01 00:00:00 1990'::timestamp without time zone) +(3 rows) + +BEGIN; +UPDATE test.range_rel SET value = 111 WHERE dt = '1990-01-01'; +SELECT * FROM test.range_rel WHERE dt = '1990-01-01'; + id | dt | value +----+----+------- +(0 rows) + +ROLLBACK; +/* no partitions for this 'dt' */ +EXPLAIN (COSTS OFF) DELETE FROM test.range_rel WHERE dt < '1990-01-01'; + QUERY PLAN +-------------------------------------------------------------------------------- + Delete on range_rel + -> Seq Scan on range_rel + Filter: (dt < 'Mon Jan 01 00:00:00 1990'::timestamp without time zone) +(3 rows) + +BEGIN; +DELETE FROM test.range_rel WHERE dt < '1990-01-01'; +SELECT * FROM test.range_rel WHERE dt < '1990-01-01'; + id | dt | value +----+----+------- +(0 rows) + +ROLLBACK; +/* UPDATE + FROM, partitioned table */ +EXPLAIN (COSTS OFF) +UPDATE test.range_rel r SET value = t.value +FROM test.tmp t WHERE r.dt = '2010-01-01' AND r.id = t.id; + QUERY PLAN +-------------------------------------------------------------------------------------- + Update on range_rel_1 r + -> Nested Loop + Join Filter: (r.id = t.id) + -> Index Scan using range_rel_1_pkey on range_rel_1 r + Filter: (dt = 'Fri Jan 01 00:00:00 2010'::timestamp without time zone) + -> Seq Scan on tmp t +(6 rows) + +BEGIN; +UPDATE test.range_rel r SET value = t.value +FROM test.tmp t WHERE r.dt = '2010-01-01' AND r.id = t.id; +ROLLBACK; +/* UPDATE + FROM, single table */ +EXPLAIN (COSTS OFF) +UPDATE test.tmp t SET value = r.value +FROM test.range_rel r WHERE r.dt = '2010-01-01' AND r.id = t.id; + QUERY PLAN +-------------------------------------------------------------------------------------- + Update on tmp t + -> Nested Loop + -> Seq Scan on tmp t + -> Index Scan using range_rel_1_pkey on range_rel_1 r + Index Cond: (id = t.id) + Filter: (dt = 'Fri Jan 01 00:00:00 2010'::timestamp without time zone) +(6 rows) + +BEGIN; +UPDATE test.tmp t SET value = r.value +FROM test.range_rel r WHERE r.dt = '2010-01-01' AND r.id = t.id; +ROLLBACK; +/* DELETE + USING, partitioned table */ +EXPLAIN (COSTS OFF) +DELETE FROM test.range_rel r USING test.tmp t +WHERE r.dt = '2010-01-02' AND r.id = t.id; + QUERY PLAN +-------------------------------------------------------------------------------------- + Delete on range_rel_1 r + -> Nested Loop + Join Filter: (r.id = t.id) + -> Index Scan using range_rel_1_pkey on range_rel_1 r + Filter: (dt = 'Sat Jan 02 00:00:00 2010'::timestamp without time zone) + -> Seq Scan on tmp t +(6 rows) + +BEGIN; +DELETE FROM test.range_rel r USING test.tmp t +WHERE r.dt = '2010-01-02' AND r.id = t.id; +ROLLBACK; +/* DELETE + USING, single table */ +EXPLAIN (COSTS OFF) +DELETE FROM test.tmp t USING test.range_rel r +WHERE r.dt = '2010-01-02' AND r.id = t.id; + QUERY PLAN +-------------------------------------------------------------------------------------- + Delete on tmp t + -> Nested Loop + -> Seq Scan on tmp t + -> Index Scan using range_rel_1_pkey on range_rel_1 r + Index Cond: (id = t.id) + Filter: (dt = 'Sat Jan 02 00:00:00 2010'::timestamp without time zone) +(6 rows) + +BEGIN; +DELETE FROM test.tmp t USING test.range_rel r +WHERE r.dt = '2010-01-02' AND r.id = t.id; +ROLLBACK; +/* DELETE + USING, two partitioned tables */ +EXPLAIN (COSTS OFF) +DELETE FROM test.range_rel r USING test.tmp2 t +WHERE t.id = r.id; +ERROR: DELETE and UPDATE queries with a join of partitioned tables are not supported +BEGIN; +DELETE FROM test.range_rel r USING test.tmp2 t +WHERE t.id = r.id; +ERROR: DELETE and UPDATE queries with a join of partitioned tables are not supported +ROLLBACK; +/* DELETE + USING, partitioned table + two partitioned tables in subselect */ +EXPLAIN (COSTS OFF) +DELETE FROM test.range_rel r +USING (SELECT * + FROM test.tmp2 a1 + JOIN test.tmp2 a2 + USING(id)) t +WHERE t.id = r.id; +ERROR: DELETE and UPDATE queries with a join of partitioned tables are not supported +BEGIN; +DELETE FROM test.range_rel r +USING (SELECT * + FROM test.tmp2 a1 + JOIN test.tmp2 a2 + USING(id)) t +WHERE t.id = r.id; +ERROR: DELETE and UPDATE queries with a join of partitioned tables are not supported +ROLLBACK; +/* DELETE + USING, single table + two partitioned tables in subselect */ +EXPLAIN (COSTS OFF) +DELETE FROM test.tmp r +USING (SELECT * + FROM test.tmp2 a1 + JOIN test.tmp2 a2 + USING(id)) t +WHERE t.id = r.id; + QUERY PLAN +------------------------------------------------ + Delete on tmp r + -> Nested Loop + -> Nested Loop + -> Seq Scan on tmp r + -> Custom Scan (RuntimeAppend) + Prune by: (r.id = a1.id) + -> Seq Scan on tmp2_1 a1 + Filter: (r.id = id) + -> Seq Scan on tmp2_2 a1 + Filter: (r.id = id) + -> Seq Scan on tmp2_3 a1 + Filter: (r.id = id) + -> Seq Scan on tmp2_4 a1 + Filter: (r.id = id) + -> Seq Scan on tmp2_5 a1 + Filter: (r.id = id) + -> Seq Scan on tmp2_6 a1 + Filter: (r.id = id) + -> Seq Scan on tmp2_7 a1 + Filter: (r.id = id) + -> Seq Scan on tmp2_8 a1 + Filter: (r.id = id) + -> Seq Scan on tmp2_9 a1 + Filter: (r.id = id) + -> Seq Scan on tmp2_10 a1 + Filter: (r.id = id) + -> Custom Scan (RuntimeAppend) + Prune by: (a1.id = a2.id) + -> Seq Scan on tmp2_1 a2 + Filter: (a1.id = id) + -> Seq Scan on tmp2_2 a2 + Filter: (a1.id = id) + -> Seq Scan on tmp2_3 a2 + Filter: (a1.id = id) + -> Seq Scan on tmp2_4 a2 + Filter: (a1.id = id) + -> Seq Scan on tmp2_5 a2 + Filter: (a1.id = id) + -> Seq Scan on tmp2_6 a2 + Filter: (a1.id = id) + -> Seq Scan on tmp2_7 a2 + Filter: (a1.id = id) + -> Seq Scan on tmp2_8 a2 + Filter: (a1.id = id) + -> Seq Scan on tmp2_9 a2 + Filter: (a1.id = id) + -> Seq Scan on tmp2_10 a2 + Filter: (a1.id = id) +(48 rows) + +BEGIN; +DELETE FROM test.tmp r +USING (SELECT * + FROM test.tmp2 a1 + JOIN test.tmp2 a2 + USING(id)) t +WHERE t.id = r.id; +ROLLBACK; +/* UPDATE + FROM, two partitioned tables */ +EXPLAIN (COSTS OFF) +UPDATE test.range_rel r SET value = 1 FROM test.tmp2 t +WHERE t.id = r.id; +ERROR: DELETE and UPDATE queries with a join of partitioned tables are not supported +BEGIN; +UPDATE test.range_rel r SET value = 1 FROM test.tmp2 t +WHERE t.id = r.id; +ERROR: DELETE and UPDATE queries with a join of partitioned tables are not supported +ROLLBACK; +/* + * UPDATE + subquery with partitioned table (PG 9.5). + * See pathman_rel_pathlist_hook() + RELOPT_OTHER_MEMBER_REL. + */ +EXPLAIN (COSTS OFF) +UPDATE test.tmp t SET value = 2 +WHERE t.id IN (SELECT id + FROM test.tmp2 t2 + WHERE id = t.id); + QUERY PLAN +------------------------------------------ + Update on tmp t + -> Nested Loop Semi Join + -> Seq Scan on tmp t + -> Custom Scan (RuntimeAppend) + Prune by: (t.id = t2.id) + -> Seq Scan on tmp2_1 t2 + Filter: (t.id = id) + -> Seq Scan on tmp2_2 t2 + Filter: (t.id = id) + -> Seq Scan on tmp2_3 t2 + Filter: (t.id = id) + -> Seq Scan on tmp2_4 t2 + Filter: (t.id = id) + -> Seq Scan on tmp2_5 t2 + Filter: (t.id = id) + -> Seq Scan on tmp2_6 t2 + Filter: (t.id = id) + -> Seq Scan on tmp2_7 t2 + Filter: (t.id = id) + -> Seq Scan on tmp2_8 t2 + Filter: (t.id = id) + -> Seq Scan on tmp2_9 t2 + Filter: (t.id = id) + -> Seq Scan on tmp2_10 t2 + Filter: (t.id = id) +(25 rows) + +/* Test special rule for CTE; SELECT (PostgreSQL 9.5) */ +EXPLAIN (COSTS OFF) +WITH q AS (SELECT * FROM test.range_rel r + WHERE r.dt = '2010-01-02') +DELETE FROM test.tmp USING q; + QUERY PLAN +-------------------------------------------------------------------------------------------- + Delete on tmp + -> Nested Loop + -> Seq Scan on tmp + -> Materialize + -> Seq Scan on range_rel_1 r + Filter: (dt = 'Sat Jan 02 00:00:00 2010'::timestamp without time zone) +(6 rows) + +BEGIN; +WITH q AS (SELECT * FROM test.range_rel r + WHERE r.dt = '2010-01-02') +DELETE FROM test.tmp USING q; +ROLLBACK; +/* Test special rule for CTE; DELETE (PostgreSQL 9.5) */ +EXPLAIN (COSTS OFF) +WITH q AS (DELETE FROM test.range_rel r + WHERE r.dt = '2010-01-02' + RETURNING *) +DELETE FROM test.tmp USING q; + QUERY PLAN +---------------------------------------------------------------------------------------- + Delete on tmp + CTE q + -> Delete on range_rel_1 r + -> Seq Scan on range_rel_1 r + Filter: (dt = 'Sat Jan 02 00:00:00 2010'::timestamp without time zone) + -> Nested Loop + -> Seq Scan on tmp + -> CTE Scan on q +(8 rows) + +BEGIN; +WITH q AS (DELETE FROM test.range_rel r + WHERE r.dt = '2010-01-02' + RETURNING *) +DELETE FROM test.tmp USING q; +ROLLBACK; +/* Test special rule for CTE; DELETE + USING (PostgreSQL 9.5) */ +EXPLAIN (COSTS OFF) +WITH q AS (DELETE FROM test.tmp t + USING test.range_rel r + WHERE r.dt = '2010-01-02' AND r.id = t.id + RETURNING *) +DELETE FROM test.tmp USING q; + QUERY PLAN +---------------------------------------------------------------------------------------------- + Delete on tmp + CTE q + -> Delete on tmp t + -> Nested Loop + -> Seq Scan on tmp t + -> Index Scan using range_rel_1_pkey on range_rel_1 r + Index Cond: (id = t.id) + Filter: (dt = 'Sat Jan 02 00:00:00 2010'::timestamp without time zone) + -> Nested Loop + -> Seq Scan on tmp + -> CTE Scan on q +(11 rows) + +BEGIN; +WITH q AS (DELETE FROM test.tmp t + USING test.range_rel r + WHERE r.dt = '2010-01-02' AND r.id = t.id + RETURNING *) +DELETE FROM test.tmp USING q; +ROLLBACK; +/* Test special rule for CTE; Nested CTEs (PostgreSQL 9.5) */ +EXPLAIN (COSTS OFF) +WITH q AS (WITH n AS (SELECT id FROM test.tmp2 WHERE id = 2) + DELETE FROM test.tmp t + USING n + WHERE t.id = n.id + RETURNING *) +DELETE FROM test.tmp USING q; + QUERY PLAN +--------------------------------------------- + Delete on tmp + CTE q + -> Delete on tmp t + -> Nested Loop + -> Seq Scan on tmp t + Filter: (id = 2) + -> Seq Scan on tmp2_2 tmp2 + Filter: (id = 2) + -> Nested Loop + -> Seq Scan on tmp + -> CTE Scan on q +(11 rows) + +/* Test special rule for CTE; CTE in quals (PostgreSQL 9.5) */ +EXPLAIN (COSTS OFF) +WITH q AS (SELECT id FROM test.tmp2 + WHERE id < 3) +DELETE FROM test.tmp t WHERE t.id in (SELECT id FROM q); + QUERY PLAN +-------------------------------------------------------------- + Delete on tmp t + -> Nested Loop Semi Join + -> Seq Scan on tmp t + -> Custom Scan (RuntimeAppend) + Prune by: ((tmp2.id < 3) AND (t.id = tmp2.id)) + -> Seq Scan on tmp2_1 tmp2 + Filter: (t.id = id) + -> Seq Scan on tmp2_2 tmp2 + Filter: (t.id = id) +(9 rows) + +BEGIN; +WITH q AS (SELECT id FROM test.tmp2 + WHERE id < 3) +DELETE FROM test.tmp t WHERE t.id in (SELECT id FROM q); +ROLLBACK; +DROP TABLE test.tmp CASCADE; +DROP TABLE test.tmp2 CASCADE; +NOTICE: drop cascades to 11 other objects +DROP TABLE test.range_rel CASCADE; +NOTICE: drop cascades to 13 other objects +DROP SCHEMA test; +DROP EXTENSION pg_pathman CASCADE; +DROP SCHEMA pathman; diff --git a/expected/pathman_update_node.out b/expected/pathman_update_node.out index 120b42c4..9fc1d07f 100644 --- a/expected/pathman_update_node.out +++ b/expected/pathman_update_node.out @@ -446,6 +446,9 @@ SELECT count(*) FROM test_update_node.test_hash; 10 (1 row) -DROP SCHEMA test_update_node CASCADE; -NOTICE: drop cascades to 17 other objects +DROP TABLE test_update_node.test_hash CASCADE; +NOTICE: drop cascades to 3 other objects +DROP TABLE test_update_node.test_range CASCADE; +NOTICE: drop cascades to 12 other objects +DROP SCHEMA test_update_node; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_update_triggers.out b/expected/pathman_update_triggers.out index d5c92b9f..40c6a19c 100644 --- a/expected/pathman_update_triggers.out +++ b/expected/pathman_update_triggers.out @@ -184,6 +184,8 @@ select count(distinct val) from test_update_triggers.test; 1 (1 row) -DROP SCHEMA test_update_triggers CASCADE; -NOTICE: drop cascades to 4 other objects +DROP TABLE test_update_triggers.test CASCADE; +NOTICE: drop cascades to 2 other objects +DROP FUNCTION test_update_triggers.test_trigger(); +DROP SCHEMA test_update_triggers; DROP EXTENSION pg_pathman CASCADE; diff --git a/expected/pathman_update_triggers_1.out b/expected/pathman_update_triggers_1.out new file mode 100644 index 00000000..5d26ac1e --- /dev/null +++ b/expected/pathman_update_triggers_1.out @@ -0,0 +1,198 @@ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE EXTENSION pg_pathman; +CREATE SCHEMA test_update_triggers; +create table test_update_triggers.test (val int not null); +select create_hash_partitions('test_update_triggers.test', 'val', 2, + partition_names := array[ + 'test_update_triggers.test_1', + 'test_update_triggers.test_2']); + create_hash_partitions +------------------------ + 2 +(1 row) + +create or replace function test_update_triggers.test_trigger() returns trigger as $$ +begin + raise notice '%', format('%s %s %s (%s)', TG_WHEN, TG_OP, TG_LEVEL, TG_TABLE_NAME); + + if TG_OP::text = 'DELETE'::text then + return old; + else + return new; + end if; end; +$$ language plpgsql; +/* Enable our precious custom node */ +set pg_pathman.enable_partitionrouter = t; +/* + * Statement level triggers + */ +create trigger bus before update ON test_update_triggers.test + execute procedure test_update_triggers.test_trigger (); +create trigger bds before delete ON test_update_triggers.test + execute procedure test_update_triggers.test_trigger (); +create trigger bis before insert ON test_update_triggers.test + execute procedure test_update_triggers.test_trigger (); +create trigger aus after update ON test_update_triggers.test + execute procedure test_update_triggers.test_trigger (); +create trigger ads after delete ON test_update_triggers.test + execute procedure test_update_triggers.test_trigger (); +create trigger ais after insert ON test_update_triggers.test + execute procedure test_update_triggers.test_trigger (); +create trigger bus before update ON test_update_triggers.test_1 + execute procedure test_update_triggers.test_trigger (); +create trigger bds before delete ON test_update_triggers.test_1 + execute procedure test_update_triggers.test_trigger (); +create trigger bis before insert ON test_update_triggers.test_1 + execute procedure test_update_triggers.test_trigger (); +create trigger aus after update ON test_update_triggers.test_1 + execute procedure test_update_triggers.test_trigger (); +create trigger ads after delete ON test_update_triggers.test_1 + execute procedure test_update_triggers.test_trigger (); +create trigger ais after insert ON test_update_triggers.test_1 + execute procedure test_update_triggers.test_trigger (); +create trigger bus before update ON test_update_triggers.test_2 + execute procedure test_update_triggers.test_trigger (); +create trigger bds before delete ON test_update_triggers.test_2 + execute procedure test_update_triggers.test_trigger (); +create trigger bis before insert ON test_update_triggers.test_2 + execute procedure test_update_triggers.test_trigger (); +create trigger aus after update ON test_update_triggers.test_2 + execute procedure test_update_triggers.test_trigger (); +create trigger ads after delete ON test_update_triggers.test_2 + execute procedure test_update_triggers.test_trigger (); +create trigger ais after insert ON test_update_triggers.test_2 + execute procedure test_update_triggers.test_trigger (); +/* multiple values */ +insert into test_update_triggers.test select generate_series(1, 200); +NOTICE: BEFORE INSERT STATEMENT (test) +NOTICE: AFTER INSERT STATEMENT (test) +update test_update_triggers.test set val = val + 1; +NOTICE: BEFORE UPDATE STATEMENT (test) +NOTICE: AFTER INSERT STATEMENT (test) +NOTICE: AFTER UPDATE STATEMENT (test) +update test_update_triggers.test set val = val + 1; +NOTICE: BEFORE UPDATE STATEMENT (test) +NOTICE: AFTER INSERT STATEMENT (test) +NOTICE: AFTER UPDATE STATEMENT (test) +update test_update_triggers.test set val = val + 1; +NOTICE: BEFORE UPDATE STATEMENT (test) +NOTICE: AFTER INSERT STATEMENT (test) +NOTICE: AFTER UPDATE STATEMENT (test) +update test_update_triggers.test set val = val + 1; +NOTICE: BEFORE UPDATE STATEMENT (test) +NOTICE: AFTER INSERT STATEMENT (test) +NOTICE: AFTER UPDATE STATEMENT (test) +update test_update_triggers.test set val = val + 1; +NOTICE: BEFORE UPDATE STATEMENT (test) +NOTICE: AFTER INSERT STATEMENT (test) +NOTICE: AFTER UPDATE STATEMENT (test) +select count(distinct val) from test_update_triggers.test; + count +------- + 200 +(1 row) + +truncate test_update_triggers.test; +/* + * Row level triggers + */ +create trigger bu before update ON test_update_triggers.test_1 + for each row execute procedure test_update_triggers.test_trigger (); +create trigger bd before delete ON test_update_triggers.test_1 + for each row execute procedure test_update_triggers.test_trigger (); +create trigger bi before insert ON test_update_triggers.test_1 + for each row execute procedure test_update_triggers.test_trigger (); +create trigger au after update ON test_update_triggers.test_1 + for each row execute procedure test_update_triggers.test_trigger (); +create trigger ad after delete ON test_update_triggers.test_1 + for each row execute procedure test_update_triggers.test_trigger (); +create trigger ai after insert ON test_update_triggers.test_1 + for each row execute procedure test_update_triggers.test_trigger (); +create trigger bu before update ON test_update_triggers.test_2 + for each row execute procedure test_update_triggers.test_trigger (); +create trigger bd before delete ON test_update_triggers.test_2 + for each row execute procedure test_update_triggers.test_trigger (); +create trigger bi before insert ON test_update_triggers.test_2 + for each row execute procedure test_update_triggers.test_trigger (); +create trigger au after update ON test_update_triggers.test_2 + for each row execute procedure test_update_triggers.test_trigger (); +create trigger ad after delete ON test_update_triggers.test_2 + for each row execute procedure test_update_triggers.test_trigger (); +create trigger ai after insert ON test_update_triggers.test_2 + for each row execute procedure test_update_triggers.test_trigger (); +/* single value */ +insert into test_update_triggers.test values (1); +NOTICE: BEFORE INSERT STATEMENT (test) +NOTICE: BEFORE INSERT ROW (test_1) +NOTICE: AFTER INSERT ROW (test_1) +NOTICE: AFTER INSERT STATEMENT (test) +update test_update_triggers.test set val = val + 1 returning *, tableoid::regclass; +NOTICE: BEFORE UPDATE STATEMENT (test) +NOTICE: BEFORE UPDATE ROW (test_1) +NOTICE: AFTER UPDATE ROW (test_1) +NOTICE: AFTER UPDATE STATEMENT (test) + val | tableoid +-----+----------------------------- + 2 | test_update_triggers.test_1 +(1 row) + +update test_update_triggers.test set val = val + 1 returning *, tableoid::regclass; +NOTICE: BEFORE UPDATE STATEMENT (test) +NOTICE: BEFORE UPDATE ROW (test_1) +NOTICE: BEFORE DELETE ROW (test_1) +NOTICE: BEFORE INSERT ROW (test_2) +NOTICE: AFTER DELETE ROW (test_1) +NOTICE: AFTER INSERT STATEMENT (test) +NOTICE: AFTER INSERT ROW (test_2) +NOTICE: AFTER UPDATE STATEMENT (test) + val | tableoid +-----+----------------------------- + 3 | test_update_triggers.test_2 +(1 row) + +update test_update_triggers.test set val = val + 1 returning *, tableoid::regclass; +NOTICE: BEFORE UPDATE STATEMENT (test) +NOTICE: BEFORE UPDATE ROW (test_2) +NOTICE: AFTER UPDATE ROW (test_2) +NOTICE: AFTER UPDATE STATEMENT (test) + val | tableoid +-----+----------------------------- + 4 | test_update_triggers.test_2 +(1 row) + +update test_update_triggers.test set val = val + 1 returning *, tableoid::regclass; +NOTICE: BEFORE UPDATE STATEMENT (test) +NOTICE: BEFORE UPDATE ROW (test_2) +NOTICE: BEFORE DELETE ROW (test_2) +NOTICE: BEFORE INSERT ROW (test_1) +NOTICE: AFTER DELETE ROW (test_2) +NOTICE: AFTER INSERT STATEMENT (test) +NOTICE: AFTER INSERT ROW (test_1) +NOTICE: AFTER UPDATE STATEMENT (test) + val | tableoid +-----+----------------------------- + 5 | test_update_triggers.test_1 +(1 row) + +update test_update_triggers.test set val = val + 1 returning *, tableoid::regclass; +NOTICE: BEFORE UPDATE STATEMENT (test) +NOTICE: BEFORE UPDATE ROW (test_1) +NOTICE: AFTER UPDATE ROW (test_1) +NOTICE: AFTER UPDATE STATEMENT (test) + val | tableoid +-----+----------------------------- + 6 | test_update_triggers.test_1 +(1 row) + +select count(distinct val) from test_update_triggers.test; + count +------- + 1 +(1 row) + +DROP TABLE test_update_triggers.test CASCADE; +NOTICE: drop cascades to 2 other objects +DROP FUNCTION test_update_triggers.test_trigger(); +DROP SCHEMA test_update_triggers; +DROP EXTENSION pg_pathman CASCADE; diff --git a/expected/pathman_utility_stmt.out b/expected/pathman_utility_stmt.out index 4cc4d493..1a8b969e 100644 --- a/expected/pathman_utility_stmt.out +++ b/expected/pathman_utility_stmt.out @@ -214,8 +214,11 @@ SELECT COUNT(*) FROM copy_stmt_hooking.test2; 1 (1 row) -DROP SCHEMA copy_stmt_hooking CASCADE; -NOTICE: drop cascades to 797 other objects +DROP TABLE copy_stmt_hooking.test CASCADE; +NOTICE: drop cascades to 5 other objects +DROP TABLE copy_stmt_hooking.test2 CASCADE; +NOTICE: drop cascades to 790 other objects +DROP SCHEMA copy_stmt_hooking; /* * Test auto check constraint renaming */ @@ -353,8 +356,15 @@ WHERE r.conrelid = 'rename.plain_test'::regclass AND r.contype = 'c'; pathman_plain_test_renamed_check | CHECK (a < 100) (1 row) -DROP SCHEMA rename CASCADE; -NOTICE: drop cascades to 11 other objects +DROP TABLE rename.plain_test CASCADE; +DROP TABLE rename.test_inh CASCADE; +NOTICE: drop cascades to table rename.test_inh_one +DROP TABLE rename.parent CASCADE; +NOTICE: drop cascades to 3 other objects +DROP TABLE rename.test CASCADE; +NOTICE: drop cascades to 3 other objects +DROP FUNCTION add_constraint(regclass); +DROP SCHEMA rename; /* * Test DROP INDEX CONCURRENTLY (test snapshots) */ @@ -368,6 +378,71 @@ SELECT create_hash_partitions('drop_index.test', 'val', 2); (1 row) DROP INDEX CONCURRENTLY drop_index.test_0_val_idx; -DROP SCHEMA drop_index CASCADE; -NOTICE: drop cascades to 3 other objects +DROP TABLE drop_index.test CASCADE; +NOTICE: drop cascades to 2 other objects +DROP SCHEMA drop_index; +/* + * Checking that ALTER TABLE IF EXISTS with loaded (and created) pg_pathman extension works the same as in vanilla + */ +CREATE SCHEMA test_nonexistance; +ALTER TABLE IF EXISTS test_nonexistance.nonexistent_table RENAME TO other_table_name; +NOTICE: relation "nonexistent_table" does not exist, skipping +/* renaming existent tables already tested earlier (see rename.plain_test) */ +ALTER TABLE IF EXISTS test_nonexistance.nonexistent_table ADD COLUMN j INT4; +NOTICE: relation "nonexistent_table" does not exist, skipping +CREATE TABLE test_nonexistance.existent_table(i INT4); +ALTER TABLE IF EXISTS test_nonexistance.existent_table ADD COLUMN j INT4; +SELECT attname FROM pg_attribute WHERE attnum > 0 AND attrelid = 'test_nonexistance.existent_table'::REGCLASS; + attname +--------- + i + j +(2 rows) + +DROP TABLE test_nonexistance.existent_table; +ALTER TABLE IF EXISTS test_nonexistance.nonexistent_table DROP COLUMN IF EXISTS i; +NOTICE: relation "nonexistent_table" does not exist, skipping +CREATE TABLE test_nonexistance.existent_table(i INT4); +ALTER TABLE IF EXISTS test_nonexistance.existent_table DROP COLUMN IF EXISTS i; +ALTER TABLE IF EXISTS test_nonexistance.existent_table DROP COLUMN IF EXISTS nonexistent_column; +NOTICE: column "nonexistent_column" of relation "existent_table" does not exist, skipping +SELECT attname FROM pg_attribute WHERE attnum > 0 AND attrelid = 'test_nonexistance.existent_table'::REGCLASS; + attname +------------------------------ + ........pg.dropped.1........ +(1 row) + +DROP TABLE test_nonexistance.existent_table; +ALTER TABLE IF EXISTS test_nonexistance.nonexistent_table RENAME COLUMN i TO j; +NOTICE: relation "nonexistent_table" does not exist, skipping +CREATE TABLE test_nonexistance.existent_table(i INT4); +ALTER TABLE IF EXISTS test_nonexistance.existent_table RENAME COLUMN i TO j; +SELECT attname FROM pg_attribute WHERE attnum > 0 AND attrelid = 'test_nonexistance.existent_table'::REGCLASS; + attname +--------- + j +(1 row) + +DROP TABLE test_nonexistance.existent_table; +ALTER TABLE IF EXISTS test_nonexistance.nonexistent_table RENAME CONSTRAINT baz TO bar; +NOTICE: relation "nonexistent_table" does not exist, skipping +CREATE TABLE test_nonexistance.existent_table(i INT4 CONSTRAINT existent_table_i_check CHECK (i < 100)); +ALTER TABLE IF EXISTS test_nonexistance.existent_table RENAME CONSTRAINT existent_table_i_check TO existent_table_i_other_check; +DROP TABLE test_nonexistance.existent_table; +ALTER TABLE IF EXISTS test_nonexistance.nonexistent_table SET SCHEMA nonexistent_schema; +NOTICE: relation "nonexistent_table" does not exist, skipping +CREATE TABLE test_nonexistance.existent_table(i INT4); +ALTER TABLE IF EXISTS test_nonexistance.existent_table SET SCHEMA nonexistent_schema; +ERROR: schema "nonexistent_schema" does not exist +CREATE SCHEMA test_nonexistance2; +ALTER TABLE IF EXISTS test_nonexistance.existent_table SET SCHEMA test_nonexistance2; +DROP TABLE test_nonexistance2.existent_table; +DROP SCHEMA test_nonexistance2; +ALTER TABLE IF EXISTS test_nonexistance.nonexistent_table SET TABLESPACE nonexistent_tablespace; +NOTICE: relation "nonexistent_table" does not exist, skipping +CREATE TABLE test_nonexistance.existent_table(i INT4); +ALTER TABLE IF EXISTS test_nonexistance.existent_table SET TABLESPACE nonexistent_tablespace; +ERROR: tablespace "nonexistent_tablespace" does not exist +DROP TABLE test_nonexistance.existent_table; +DROP SCHEMA test_nonexistance; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_views.out b/expected/pathman_views.out index 78589970..64b8425d 100644 --- a/expected/pathman_views.out +++ b/expected/pathman_views.out @@ -186,6 +186,9 @@ explain (costs off) select * from views.abc_union_all where id = 5; Filter: (id = 5) (5 rows) -DROP SCHEMA views CASCADE; -NOTICE: drop cascades to 16 other objects +DROP TABLE views._abc CASCADE; +NOTICE: drop cascades to 13 other objects +DROP TABLE views._abc_add CASCADE; +DROP FUNCTION views.disable_modification(); +DROP SCHEMA views; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_views_1.out b/expected/pathman_views_1.out index ea390d84..e6bb45f5 100644 --- a/expected/pathman_views_1.out +++ b/expected/pathman_views_1.out @@ -242,6 +242,9 @@ explain (costs off) select * from views.abc_union_all where id = 5; Filter: (id = 5) (5 rows) -DROP SCHEMA views CASCADE; -NOTICE: drop cascades to 16 other objects +DROP TABLE views._abc CASCADE; +NOTICE: drop cascades to 13 other objects +DROP TABLE views._abc_add CASCADE; +DROP FUNCTION views.disable_modification(); +DROP SCHEMA views; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_views_2.out b/expected/pathman_views_2.out index 15770ec0..45ea3eb4 100644 --- a/expected/pathman_views_2.out +++ b/expected/pathman_views_2.out @@ -183,6 +183,9 @@ explain (costs off) select * from views.abc_union_all where id = 5; Filter: (id = 5) (5 rows) -DROP SCHEMA views CASCADE; -NOTICE: drop cascades to 16 other objects +DROP TABLE views._abc CASCADE; +NOTICE: drop cascades to 13 other objects +DROP TABLE views._abc_add CASCADE; +DROP FUNCTION views.disable_modification(); +DROP SCHEMA views; DROP EXTENSION pg_pathman; diff --git a/expected/pathman_views_3.out b/expected/pathman_views_3.out new file mode 100644 index 00000000..ae50bcb3 --- /dev/null +++ b/expected/pathman_views_3.out @@ -0,0 +1,192 @@ +/* + * ------------------------------------------- + * NOTE: This test behaves differenly on 9.5 + * ------------------------------------------- + * + * Also since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated, + * causing different output; pathman_views_2.out is the updated version. + */ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE EXTENSION pg_pathman; +CREATE SCHEMA views; +/* create a partitioned table */ +create table views._abc(id int4 not null); +select create_hash_partitions('views._abc', 'id', 10); + create_hash_partitions +------------------------ + 10 +(1 row) + +insert into views._abc select generate_series(1, 100); +/* create a dummy table */ +create table views._abc_add (like views._abc); +vacuum analyze; +/* create a facade view */ +create view views.abc as select * from views._abc; +create or replace function views.disable_modification() +returns trigger as +$$ +BEGIN + RAISE EXCEPTION '%', TG_OP; + RETURN NULL; +END; +$$ +language 'plpgsql'; +create trigger abc_mod_tr +instead of insert or update or delete +on views.abc for each row +execute procedure views.disable_modification(); +/* Test SELECT */ +explain (costs off) select * from views.abc; + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on _abc_0 _abc_1 + -> Seq Scan on _abc_1 _abc_2 + -> Seq Scan on _abc_2 _abc_3 + -> Seq Scan on _abc_3 _abc_4 + -> Seq Scan on _abc_4 _abc_5 + -> Seq Scan on _abc_5 _abc_6 + -> Seq Scan on _abc_6 _abc_7 + -> Seq Scan on _abc_7 _abc_8 + -> Seq Scan on _abc_8 _abc_9 + -> Seq Scan on _abc_9 _abc_10 +(11 rows) + +explain (costs off) select * from views.abc where id = 1; + QUERY PLAN +------------------------- + Seq Scan on _abc_0 _abc + Filter: (id = 1) +(2 rows) + +explain (costs off) select * from views.abc where id = 1 for update; + QUERY PLAN +------------------------------- + LockRows + -> Seq Scan on _abc_0 _abc + Filter: (id = 1) +(3 rows) + +select * from views.abc where id = 1 for update; + id +---- + 1 +(1 row) + +select count (*) from views.abc; + count +------- + 100 +(1 row) + +/* Test INSERT */ +explain (costs off) insert into views.abc values (1); + QUERY PLAN +--------------- + Insert on abc + -> Result +(2 rows) + +insert into views.abc values (1); +ERROR: INSERT +/* Test UPDATE */ +explain (costs off) update views.abc set id = 2 where id = 1 or id = 2; + QUERY PLAN +--------------------------------------------- + Update on abc + -> Result + -> Append + -> Seq Scan on _abc_0 _abc_1 + Filter: (id = 1) + -> Seq Scan on _abc_6 _abc_2 + Filter: (id = 2) +(7 rows) + +update views.abc set id = 2 where id = 1 or id = 2; +ERROR: UPDATE +/* Test DELETE */ +explain (costs off) delete from views.abc where id = 1 or id = 2; + QUERY PLAN +--------------------------------------------- + Delete on abc + -> Result + -> Append + -> Seq Scan on _abc_0 _abc_1 + Filter: (id = 1) + -> Seq Scan on _abc_6 _abc_2 + Filter: (id = 2) +(7 rows) + +delete from views.abc where id = 1 or id = 2; +ERROR: DELETE +/* Test SELECT with UNION */ +create view views.abc_union as table views._abc union table views._abc_add; +create view views.abc_union_all as table views._abc union all table views._abc_add; +explain (costs off) table views.abc_union; + QUERY PLAN +---------------------------------------------- + HashAggregate + Group Key: _abc.id + -> Append + -> Append + -> Seq Scan on _abc_0 _abc_1 + -> Seq Scan on _abc_1 _abc_2 + -> Seq Scan on _abc_2 _abc_3 + -> Seq Scan on _abc_3 _abc_4 + -> Seq Scan on _abc_4 _abc_5 + -> Seq Scan on _abc_5 _abc_6 + -> Seq Scan on _abc_6 _abc_7 + -> Seq Scan on _abc_7 _abc_8 + -> Seq Scan on _abc_8 _abc_9 + -> Seq Scan on _abc_9 _abc_10 + -> Seq Scan on _abc_add +(15 rows) + +explain (costs off) select * from views.abc_union where id = 5; + QUERY PLAN +------------------------------------------- + Unique + -> Sort + Sort Key: _abc.id + -> Append + -> Seq Scan on _abc_8 _abc + Filter: (id = 5) + -> Seq Scan on _abc_add + Filter: (id = 5) +(8 rows) + +explain (costs off) table views.abc_union_all; + QUERY PLAN +------------------------------- + Append + -> Seq Scan on _abc_0 _abc + -> Seq Scan on _abc_1 + -> Seq Scan on _abc_2 + -> Seq Scan on _abc_3 + -> Seq Scan on _abc_4 + -> Seq Scan on _abc_5 + -> Seq Scan on _abc_6 + -> Seq Scan on _abc_7 + -> Seq Scan on _abc_8 + -> Seq Scan on _abc_9 + -> Seq Scan on _abc_add +(12 rows) + +explain (costs off) select * from views.abc_union_all where id = 5; + QUERY PLAN +------------------------------- + Append + -> Seq Scan on _abc_8 _abc + Filter: (id = 5) + -> Seq Scan on _abc_add + Filter: (id = 5) +(5 rows) + +DROP TABLE views._abc CASCADE; +NOTICE: drop cascades to 13 other objects +DROP TABLE views._abc_add CASCADE; +DROP FUNCTION views.disable_modification(); +DROP SCHEMA views; +DROP EXTENSION pg_pathman; diff --git a/expected/pathman_views_4.out b/expected/pathman_views_4.out new file mode 100644 index 00000000..8fde5770 --- /dev/null +++ b/expected/pathman_views_4.out @@ -0,0 +1,191 @@ +/* + * ------------------------------------------- + * NOTE: This test behaves differenly on 9.5 + * ------------------------------------------- + * + * Also since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated, + * causing different output; pathman_views_2.out is the updated version. + */ +\set VERBOSITY terse +SET search_path = 'public'; +CREATE EXTENSION pg_pathman; +CREATE SCHEMA views; +/* create a partitioned table */ +create table views._abc(id int4 not null); +select create_hash_partitions('views._abc', 'id', 10); + create_hash_partitions +------------------------ + 10 +(1 row) + +insert into views._abc select generate_series(1, 100); +/* create a dummy table */ +create table views._abc_add (like views._abc); +vacuum analyze; +/* create a facade view */ +create view views.abc as select * from views._abc; +create or replace function views.disable_modification() +returns trigger as +$$ +BEGIN + RAISE EXCEPTION '%', TG_OP; + RETURN NULL; +END; +$$ +language 'plpgsql'; +create trigger abc_mod_tr +instead of insert or update or delete +on views.abc for each row +execute procedure views.disable_modification(); +/* Test SELECT */ +explain (costs off) select * from views.abc; + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on _abc_0 _abc_1 + -> Seq Scan on _abc_1 _abc_2 + -> Seq Scan on _abc_2 _abc_3 + -> Seq Scan on _abc_3 _abc_4 + -> Seq Scan on _abc_4 _abc_5 + -> Seq Scan on _abc_5 _abc_6 + -> Seq Scan on _abc_6 _abc_7 + -> Seq Scan on _abc_7 _abc_8 + -> Seq Scan on _abc_8 _abc_9 + -> Seq Scan on _abc_9 _abc_10 +(11 rows) + +explain (costs off) select * from views.abc where id = 1; + QUERY PLAN +------------------------- + Seq Scan on _abc_0 _abc + Filter: (id = 1) +(2 rows) + +explain (costs off) select * from views.abc where id = 1 for update; + QUERY PLAN +------------------------------- + LockRows + -> Seq Scan on _abc_0 _abc + Filter: (id = 1) +(3 rows) + +select * from views.abc where id = 1 for update; + id +---- + 1 +(1 row) + +select count (*) from views.abc; + count +------- + 100 +(1 row) + +/* Test INSERT */ +explain (costs off) insert into views.abc values (1); + QUERY PLAN +--------------- + Insert on abc + -> Result +(2 rows) + +insert into views.abc values (1); +ERROR: INSERT +/* Test UPDATE */ +explain (costs off) update views.abc set id = 2 where id = 1 or id = 2; + QUERY PLAN +--------------------------------------------- + Update on abc + -> Result + -> Append + -> Seq Scan on _abc_0 _abc_1 + Filter: (id = 1) + -> Seq Scan on _abc_6 _abc_2 + Filter: (id = 2) +(7 rows) + +update views.abc set id = 2 where id = 1 or id = 2; +ERROR: UPDATE +/* Test DELETE */ +explain (costs off) delete from views.abc where id = 1 or id = 2; + QUERY PLAN +--------------------------------------------- + Delete on abc + -> Result + -> Append + -> Seq Scan on _abc_0 _abc_1 + Filter: (id = 1) + -> Seq Scan on _abc_6 _abc_2 + Filter: (id = 2) +(7 rows) + +delete from views.abc where id = 1 or id = 2; +ERROR: DELETE +/* Test SELECT with UNION */ +create view views.abc_union as table views._abc union table views._abc_add; +create view views.abc_union_all as table views._abc union all table views._abc_add; +explain (costs off) table views.abc_union; + QUERY PLAN +---------------------------------------------- + HashAggregate + Group Key: _abc.id + -> Append + -> Append + -> Seq Scan on _abc_0 _abc_1 + -> Seq Scan on _abc_1 _abc_2 + -> Seq Scan on _abc_2 _abc_3 + -> Seq Scan on _abc_3 _abc_4 + -> Seq Scan on _abc_4 _abc_5 + -> Seq Scan on _abc_5 _abc_6 + -> Seq Scan on _abc_6 _abc_7 + -> Seq Scan on _abc_7 _abc_8 + -> Seq Scan on _abc_8 _abc_9 + -> Seq Scan on _abc_9 _abc_10 + -> Seq Scan on _abc_add +(15 rows) + +explain (costs off) select * from views.abc_union where id = 5; + QUERY PLAN +------------------------------------- + HashAggregate + Group Key: _abc.id + -> Append + -> Seq Scan on _abc_8 _abc + Filter: (id = 5) + -> Seq Scan on _abc_add + Filter: (id = 5) +(7 rows) + +explain (costs off) table views.abc_union_all; + QUERY PLAN +------------------------------- + Append + -> Seq Scan on _abc_0 _abc + -> Seq Scan on _abc_1 + -> Seq Scan on _abc_2 + -> Seq Scan on _abc_3 + -> Seq Scan on _abc_4 + -> Seq Scan on _abc_5 + -> Seq Scan on _abc_6 + -> Seq Scan on _abc_7 + -> Seq Scan on _abc_8 + -> Seq Scan on _abc_9 + -> Seq Scan on _abc_add +(12 rows) + +explain (costs off) select * from views.abc_union_all where id = 5; + QUERY PLAN +------------------------------- + Append + -> Seq Scan on _abc_8 _abc + Filter: (id = 5) + -> Seq Scan on _abc_add + Filter: (id = 5) +(5 rows) + +DROP TABLE views._abc CASCADE; +NOTICE: drop cascades to 13 other objects +DROP TABLE views._abc_add CASCADE; +DROP FUNCTION views.disable_modification(); +DROP SCHEMA views; +DROP EXTENSION pg_pathman; diff --git a/expected/rollback_on_create_partitions.out b/expected/rollback_on_create_partitions.out index 3531107d..ee0c7c0f 100644 --- a/expected/rollback_on_create_partitions.out +++ b/expected/rollback_on_create_partitions.out @@ -5,64 +5,72 @@ step begin: BEGIN; step insert_data: INSERT INTO range_rel SELECT generate_series(1, 10000); step create_partitions: SELECT create_range_partitions('range_rel', 'id', 1, 1000); create_range_partitions +----------------------- + 10 +(1 row) + +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent |partition +---------+------------ +range_rel|range_rel_1 +range_rel|range_rel_2 +range_rel|range_rel_3 +range_rel|range_rel_4 +range_rel|range_rel_5 +range_rel|range_rel_6 +range_rel|range_rel_7 +range_rel|range_rel_8 +range_rel|range_rel_9 +range_rel|range_rel_10 +(10 rows) -10 -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN - -Append - -> Seq Scan on range_rel_1 - -> Seq Scan on range_rel_2 - -> Seq Scan on range_rel_3 - -> Seq Scan on range_rel_4 - -> Seq Scan on range_rel_5 - -> Seq Scan on range_rel_6 - -> Seq Scan on range_rel_7 - -> Seq Scan on range_rel_8 - -> Seq Scan on range_rel_9 - -> Seq Scan on range_rel_10 step rollback: ROLLBACK; -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent|partition +------+--------- +(0 rows) -Seq Scan on range_rel starting permutation: begin insert_data create_partitions show_rel commit show_rel step begin: BEGIN; step insert_data: INSERT INTO range_rel SELECT generate_series(1, 10000); step create_partitions: SELECT create_range_partitions('range_rel', 'id', 1, 1000); create_range_partitions +----------------------- + 10 +(1 row) + +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent |partition +---------+------------ +range_rel|range_rel_1 +range_rel|range_rel_2 +range_rel|range_rel_3 +range_rel|range_rel_4 +range_rel|range_rel_5 +range_rel|range_rel_6 +range_rel|range_rel_7 +range_rel|range_rel_8 +range_rel|range_rel_9 +range_rel|range_rel_10 +(10 rows) -10 -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN - -Append - -> Seq Scan on range_rel_1 - -> Seq Scan on range_rel_2 - -> Seq Scan on range_rel_3 - -> Seq Scan on range_rel_4 - -> Seq Scan on range_rel_5 - -> Seq Scan on range_rel_6 - -> Seq Scan on range_rel_7 - -> Seq Scan on range_rel_8 - -> Seq Scan on range_rel_9 - -> Seq Scan on range_rel_10 step commit: COMMIT; -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN - -Append - -> Seq Scan on range_rel_1 - -> Seq Scan on range_rel_2 - -> Seq Scan on range_rel_3 - -> Seq Scan on range_rel_4 - -> Seq Scan on range_rel_5 - -> Seq Scan on range_rel_6 - -> Seq Scan on range_rel_7 - -> Seq Scan on range_rel_8 - -> Seq Scan on range_rel_9 - -> Seq Scan on range_rel_10 +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent |partition +---------+------------ +range_rel|range_rel_1 +range_rel|range_rel_2 +range_rel|range_rel_3 +range_rel|range_rel_4 +range_rel|range_rel_5 +range_rel|range_rel_6 +range_rel|range_rel_7 +range_rel|range_rel_8 +range_rel|range_rel_9 +range_rel|range_rel_10 +(10 rows) + starting permutation: begin insert_data savepoint_a create_partitions savepoint_b drop_partitions show_rel savepoint_c rollback show_rel step begin: BEGIN; @@ -70,23 +78,39 @@ step insert_data: INSERT INTO range_rel SELECT generate_series(1, 10000); step savepoint_a: SAVEPOINT a; step create_partitions: SELECT create_range_partitions('range_rel', 'id', 1, 1000); create_range_partitions +----------------------- + 10 +(1 row) -10 step savepoint_b: SAVEPOINT b; +s1: NOTICE: 1000 rows copied from range_rel_1 +s1: NOTICE: 1000 rows copied from range_rel_2 +s1: NOTICE: 1000 rows copied from range_rel_3 +s1: NOTICE: 1000 rows copied from range_rel_4 +s1: NOTICE: 1000 rows copied from range_rel_5 +s1: NOTICE: 1000 rows copied from range_rel_6 +s1: NOTICE: 1000 rows copied from range_rel_7 +s1: NOTICE: 1000 rows copied from range_rel_8 +s1: NOTICE: 1000 rows copied from range_rel_9 +s1: NOTICE: 1000 rows copied from range_rel_10 step drop_partitions: SELECT drop_partitions('range_rel'); drop_partitions +--------------- + 10 +(1 row) -10 -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent|partition +------+--------- +(0 rows) -Seq Scan on range_rel step savepoint_c: SAVEPOINT c; step rollback: ROLLBACK; -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent|partition +------+--------- +(0 rows) -Seq Scan on range_rel starting permutation: begin insert_data savepoint_a create_partitions savepoint_b drop_partitions show_rel savepoint_c commit show_rel step begin: BEGIN; @@ -94,23 +118,39 @@ step insert_data: INSERT INTO range_rel SELECT generate_series(1, 10000); step savepoint_a: SAVEPOINT a; step create_partitions: SELECT create_range_partitions('range_rel', 'id', 1, 1000); create_range_partitions +----------------------- + 10 +(1 row) -10 step savepoint_b: SAVEPOINT b; +s1: NOTICE: 1000 rows copied from range_rel_1 +s1: NOTICE: 1000 rows copied from range_rel_2 +s1: NOTICE: 1000 rows copied from range_rel_3 +s1: NOTICE: 1000 rows copied from range_rel_4 +s1: NOTICE: 1000 rows copied from range_rel_5 +s1: NOTICE: 1000 rows copied from range_rel_6 +s1: NOTICE: 1000 rows copied from range_rel_7 +s1: NOTICE: 1000 rows copied from range_rel_8 +s1: NOTICE: 1000 rows copied from range_rel_9 +s1: NOTICE: 1000 rows copied from range_rel_10 step drop_partitions: SELECT drop_partitions('range_rel'); drop_partitions +--------------- + 10 +(1 row) -10 -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent|partition +------+--------- +(0 rows) -Seq Scan on range_rel step savepoint_c: SAVEPOINT c; step commit: COMMIT; -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent|partition +------+--------- +(0 rows) -Seq Scan on range_rel starting permutation: begin insert_data savepoint_a create_partitions savepoint_b drop_partitions savepoint_c rollback_b show_rel rollback show_rel step begin: BEGIN; @@ -118,34 +158,50 @@ step insert_data: INSERT INTO range_rel SELECT generate_series(1, 10000); step savepoint_a: SAVEPOINT a; step create_partitions: SELECT create_range_partitions('range_rel', 'id', 1, 1000); create_range_partitions +----------------------- + 10 +(1 row) -10 step savepoint_b: SAVEPOINT b; +s1: NOTICE: 1000 rows copied from range_rel_1 +s1: NOTICE: 1000 rows copied from range_rel_2 +s1: NOTICE: 1000 rows copied from range_rel_3 +s1: NOTICE: 1000 rows copied from range_rel_4 +s1: NOTICE: 1000 rows copied from range_rel_5 +s1: NOTICE: 1000 rows copied from range_rel_6 +s1: NOTICE: 1000 rows copied from range_rel_7 +s1: NOTICE: 1000 rows copied from range_rel_8 +s1: NOTICE: 1000 rows copied from range_rel_9 +s1: NOTICE: 1000 rows copied from range_rel_10 step drop_partitions: SELECT drop_partitions('range_rel'); drop_partitions +--------------- + 10 +(1 row) -10 step savepoint_c: SAVEPOINT c; step rollback_b: ROLLBACK TO SAVEPOINT b; -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN - -Append - -> Seq Scan on range_rel_1 - -> Seq Scan on range_rel_2 - -> Seq Scan on range_rel_3 - -> Seq Scan on range_rel_4 - -> Seq Scan on range_rel_5 - -> Seq Scan on range_rel_6 - -> Seq Scan on range_rel_7 - -> Seq Scan on range_rel_8 - -> Seq Scan on range_rel_9 - -> Seq Scan on range_rel_10 +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent |partition +---------+------------ +range_rel|range_rel_1 +range_rel|range_rel_2 +range_rel|range_rel_3 +range_rel|range_rel_4 +range_rel|range_rel_5 +range_rel|range_rel_6 +range_rel|range_rel_7 +range_rel|range_rel_8 +range_rel|range_rel_9 +range_rel|range_rel_10 +(10 rows) + step rollback: ROLLBACK; -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent|partition +------+--------- +(0 rows) -Seq Scan on range_rel starting permutation: begin insert_data savepoint_a create_partitions savepoint_b drop_partitions savepoint_c rollback_b show_rel commit show_rel step begin: BEGIN; @@ -153,44 +209,60 @@ step insert_data: INSERT INTO range_rel SELECT generate_series(1, 10000); step savepoint_a: SAVEPOINT a; step create_partitions: SELECT create_range_partitions('range_rel', 'id', 1, 1000); create_range_partitions +----------------------- + 10 +(1 row) -10 step savepoint_b: SAVEPOINT b; +s1: NOTICE: 1000 rows copied from range_rel_1 +s1: NOTICE: 1000 rows copied from range_rel_2 +s1: NOTICE: 1000 rows copied from range_rel_3 +s1: NOTICE: 1000 rows copied from range_rel_4 +s1: NOTICE: 1000 rows copied from range_rel_5 +s1: NOTICE: 1000 rows copied from range_rel_6 +s1: NOTICE: 1000 rows copied from range_rel_7 +s1: NOTICE: 1000 rows copied from range_rel_8 +s1: NOTICE: 1000 rows copied from range_rel_9 +s1: NOTICE: 1000 rows copied from range_rel_10 step drop_partitions: SELECT drop_partitions('range_rel'); drop_partitions +--------------- + 10 +(1 row) -10 step savepoint_c: SAVEPOINT c; step rollback_b: ROLLBACK TO SAVEPOINT b; -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN - -Append - -> Seq Scan on range_rel_1 - -> Seq Scan on range_rel_2 - -> Seq Scan on range_rel_3 - -> Seq Scan on range_rel_4 - -> Seq Scan on range_rel_5 - -> Seq Scan on range_rel_6 - -> Seq Scan on range_rel_7 - -> Seq Scan on range_rel_8 - -> Seq Scan on range_rel_9 - -> Seq Scan on range_rel_10 +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent |partition +---------+------------ +range_rel|range_rel_1 +range_rel|range_rel_2 +range_rel|range_rel_3 +range_rel|range_rel_4 +range_rel|range_rel_5 +range_rel|range_rel_6 +range_rel|range_rel_7 +range_rel|range_rel_8 +range_rel|range_rel_9 +range_rel|range_rel_10 +(10 rows) + step commit: COMMIT; -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN - -Append - -> Seq Scan on range_rel_1 - -> Seq Scan on range_rel_2 - -> Seq Scan on range_rel_3 - -> Seq Scan on range_rel_4 - -> Seq Scan on range_rel_5 - -> Seq Scan on range_rel_6 - -> Seq Scan on range_rel_7 - -> Seq Scan on range_rel_8 - -> Seq Scan on range_rel_9 - -> Seq Scan on range_rel_10 +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent |partition +---------+------------ +range_rel|range_rel_1 +range_rel|range_rel_2 +range_rel|range_rel_3 +range_rel|range_rel_4 +range_rel|range_rel_5 +range_rel|range_rel_6 +range_rel|range_rel_7 +range_rel|range_rel_8 +range_rel|range_rel_9 +range_rel|range_rel_10 +(10 rows) + starting permutation: begin insert_data savepoint_a create_partitions savepoint_b drop_partitions show_rel savepoint_c rollback_a show_rel rollback show_rel step begin: BEGIN; @@ -198,28 +270,45 @@ step insert_data: INSERT INTO range_rel SELECT generate_series(1, 10000); step savepoint_a: SAVEPOINT a; step create_partitions: SELECT create_range_partitions('range_rel', 'id', 1, 1000); create_range_partitions +----------------------- + 10 +(1 row) -10 step savepoint_b: SAVEPOINT b; +s1: NOTICE: 1000 rows copied from range_rel_1 +s1: NOTICE: 1000 rows copied from range_rel_2 +s1: NOTICE: 1000 rows copied from range_rel_3 +s1: NOTICE: 1000 rows copied from range_rel_4 +s1: NOTICE: 1000 rows copied from range_rel_5 +s1: NOTICE: 1000 rows copied from range_rel_6 +s1: NOTICE: 1000 rows copied from range_rel_7 +s1: NOTICE: 1000 rows copied from range_rel_8 +s1: NOTICE: 1000 rows copied from range_rel_9 +s1: NOTICE: 1000 rows copied from range_rel_10 step drop_partitions: SELECT drop_partitions('range_rel'); drop_partitions +--------------- + 10 +(1 row) -10 -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent|partition +------+--------- +(0 rows) -Seq Scan on range_rel step savepoint_c: SAVEPOINT c; step rollback_a: ROLLBACK TO SAVEPOINT a; -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent|partition +------+--------- +(0 rows) -Seq Scan on range_rel step rollback: ROLLBACK; -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent|partition +------+--------- +(0 rows) -Seq Scan on range_rel starting permutation: begin insert_data savepoint_a create_partitions savepoint_b drop_partitions show_rel savepoint_c rollback_a show_rel commit show_rel step begin: BEGIN; @@ -227,28 +316,45 @@ step insert_data: INSERT INTO range_rel SELECT generate_series(1, 10000); step savepoint_a: SAVEPOINT a; step create_partitions: SELECT create_range_partitions('range_rel', 'id', 1, 1000); create_range_partitions +----------------------- + 10 +(1 row) -10 step savepoint_b: SAVEPOINT b; +s1: NOTICE: 1000 rows copied from range_rel_1 +s1: NOTICE: 1000 rows copied from range_rel_2 +s1: NOTICE: 1000 rows copied from range_rel_3 +s1: NOTICE: 1000 rows copied from range_rel_4 +s1: NOTICE: 1000 rows copied from range_rel_5 +s1: NOTICE: 1000 rows copied from range_rel_6 +s1: NOTICE: 1000 rows copied from range_rel_7 +s1: NOTICE: 1000 rows copied from range_rel_8 +s1: NOTICE: 1000 rows copied from range_rel_9 +s1: NOTICE: 1000 rows copied from range_rel_10 step drop_partitions: SELECT drop_partitions('range_rel'); drop_partitions +--------------- + 10 +(1 row) -10 -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent|partition +------+--------- +(0 rows) -Seq Scan on range_rel step savepoint_c: SAVEPOINT c; step rollback_a: ROLLBACK TO SAVEPOINT a; -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent|partition +------+--------- +(0 rows) -Seq Scan on range_rel step commit: COMMIT; -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent|partition +------+--------- +(0 rows) -Seq Scan on range_rel starting permutation: begin insert_data savepoint_a create_partitions savepoint_b drop_partitions show_rel savepoint_c rollback_b drop_partitions show_rel rollback show_rel step begin: BEGIN; @@ -256,32 +362,61 @@ step insert_data: INSERT INTO range_rel SELECT generate_series(1, 10000); step savepoint_a: SAVEPOINT a; step create_partitions: SELECT create_range_partitions('range_rel', 'id', 1, 1000); create_range_partitions +----------------------- + 10 +(1 row) -10 step savepoint_b: SAVEPOINT b; +s1: NOTICE: 1000 rows copied from range_rel_1 +s1: NOTICE: 1000 rows copied from range_rel_2 +s1: NOTICE: 1000 rows copied from range_rel_3 +s1: NOTICE: 1000 rows copied from range_rel_4 +s1: NOTICE: 1000 rows copied from range_rel_5 +s1: NOTICE: 1000 rows copied from range_rel_6 +s1: NOTICE: 1000 rows copied from range_rel_7 +s1: NOTICE: 1000 rows copied from range_rel_8 +s1: NOTICE: 1000 rows copied from range_rel_9 +s1: NOTICE: 1000 rows copied from range_rel_10 step drop_partitions: SELECT drop_partitions('range_rel'); drop_partitions +--------------- + 10 +(1 row) -10 -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent|partition +------+--------- +(0 rows) -Seq Scan on range_rel step savepoint_c: SAVEPOINT c; step rollback_b: ROLLBACK TO SAVEPOINT b; +s1: NOTICE: 1000 rows copied from range_rel_1 +s1: NOTICE: 1000 rows copied from range_rel_2 +s1: NOTICE: 1000 rows copied from range_rel_3 +s1: NOTICE: 1000 rows copied from range_rel_4 +s1: NOTICE: 1000 rows copied from range_rel_5 +s1: NOTICE: 1000 rows copied from range_rel_6 +s1: NOTICE: 1000 rows copied from range_rel_7 +s1: NOTICE: 1000 rows copied from range_rel_8 +s1: NOTICE: 1000 rows copied from range_rel_9 +s1: NOTICE: 1000 rows copied from range_rel_10 step drop_partitions: SELECT drop_partitions('range_rel'); drop_partitions +--------------- + 10 +(1 row) -10 -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent|partition +------+--------- +(0 rows) -Seq Scan on range_rel step rollback: ROLLBACK; -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent|partition +------+--------- +(0 rows) -Seq Scan on range_rel starting permutation: begin insert_data savepoint_a create_partitions savepoint_b drop_partitions show_rel savepoint_c rollback_b drop_partitions show_rel commit show_rel step begin: BEGIN; @@ -289,32 +424,61 @@ step insert_data: INSERT INTO range_rel SELECT generate_series(1, 10000); step savepoint_a: SAVEPOINT a; step create_partitions: SELECT create_range_partitions('range_rel', 'id', 1, 1000); create_range_partitions +----------------------- + 10 +(1 row) -10 step savepoint_b: SAVEPOINT b; +s1: NOTICE: 1000 rows copied from range_rel_1 +s1: NOTICE: 1000 rows copied from range_rel_2 +s1: NOTICE: 1000 rows copied from range_rel_3 +s1: NOTICE: 1000 rows copied from range_rel_4 +s1: NOTICE: 1000 rows copied from range_rel_5 +s1: NOTICE: 1000 rows copied from range_rel_6 +s1: NOTICE: 1000 rows copied from range_rel_7 +s1: NOTICE: 1000 rows copied from range_rel_8 +s1: NOTICE: 1000 rows copied from range_rel_9 +s1: NOTICE: 1000 rows copied from range_rel_10 step drop_partitions: SELECT drop_partitions('range_rel'); drop_partitions +--------------- + 10 +(1 row) -10 -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent|partition +------+--------- +(0 rows) -Seq Scan on range_rel step savepoint_c: SAVEPOINT c; step rollback_b: ROLLBACK TO SAVEPOINT b; +s1: NOTICE: 1000 rows copied from range_rel_1 +s1: NOTICE: 1000 rows copied from range_rel_2 +s1: NOTICE: 1000 rows copied from range_rel_3 +s1: NOTICE: 1000 rows copied from range_rel_4 +s1: NOTICE: 1000 rows copied from range_rel_5 +s1: NOTICE: 1000 rows copied from range_rel_6 +s1: NOTICE: 1000 rows copied from range_rel_7 +s1: NOTICE: 1000 rows copied from range_rel_8 +s1: NOTICE: 1000 rows copied from range_rel_9 +s1: NOTICE: 1000 rows copied from range_rel_10 step drop_partitions: SELECT drop_partitions('range_rel'); drop_partitions +--------------- + 10 +(1 row) -10 -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent|partition +------+--------- +(0 rows) -Seq Scan on range_rel step commit: COMMIT; -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent|partition +------+--------- +(0 rows) -Seq Scan on range_rel starting permutation: begin insert_data savepoint_a create_partitions savepoint_b drop_partitions rollback_a create_partitions show_rel rollback show_rel step begin: BEGIN; @@ -322,37 +486,55 @@ step insert_data: INSERT INTO range_rel SELECT generate_series(1, 10000); step savepoint_a: SAVEPOINT a; step create_partitions: SELECT create_range_partitions('range_rel', 'id', 1, 1000); create_range_partitions +----------------------- + 10 +(1 row) -10 step savepoint_b: SAVEPOINT b; +s1: NOTICE: 1000 rows copied from range_rel_1 +s1: NOTICE: 1000 rows copied from range_rel_2 +s1: NOTICE: 1000 rows copied from range_rel_3 +s1: NOTICE: 1000 rows copied from range_rel_4 +s1: NOTICE: 1000 rows copied from range_rel_5 +s1: NOTICE: 1000 rows copied from range_rel_6 +s1: NOTICE: 1000 rows copied from range_rel_7 +s1: NOTICE: 1000 rows copied from range_rel_8 +s1: NOTICE: 1000 rows copied from range_rel_9 +s1: NOTICE: 1000 rows copied from range_rel_10 step drop_partitions: SELECT drop_partitions('range_rel'); drop_partitions +--------------- + 10 +(1 row) -10 step rollback_a: ROLLBACK TO SAVEPOINT a; step create_partitions: SELECT create_range_partitions('range_rel', 'id', 1, 1000); create_range_partitions +----------------------- + 10 +(1 row) + +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent |partition +---------+------------ +range_rel|range_rel_1 +range_rel|range_rel_2 +range_rel|range_rel_3 +range_rel|range_rel_4 +range_rel|range_rel_5 +range_rel|range_rel_6 +range_rel|range_rel_7 +range_rel|range_rel_8 +range_rel|range_rel_9 +range_rel|range_rel_10 +(10 rows) -10 -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN - -Append - -> Seq Scan on range_rel_1 - -> Seq Scan on range_rel_2 - -> Seq Scan on range_rel_3 - -> Seq Scan on range_rel_4 - -> Seq Scan on range_rel_5 - -> Seq Scan on range_rel_6 - -> Seq Scan on range_rel_7 - -> Seq Scan on range_rel_8 - -> Seq Scan on range_rel_9 - -> Seq Scan on range_rel_10 step rollback: ROLLBACK; -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent|partition +------+--------- +(0 rows) -Seq Scan on range_rel starting permutation: begin insert_data savepoint_a create_partitions savepoint_b drop_partitions rollback_a create_partitions show_rel commit show_rel step begin: BEGIN; @@ -360,44 +542,62 @@ step insert_data: INSERT INTO range_rel SELECT generate_series(1, 10000); step savepoint_a: SAVEPOINT a; step create_partitions: SELECT create_range_partitions('range_rel', 'id', 1, 1000); create_range_partitions +----------------------- + 10 +(1 row) -10 step savepoint_b: SAVEPOINT b; +s1: NOTICE: 1000 rows copied from range_rel_1 +s1: NOTICE: 1000 rows copied from range_rel_2 +s1: NOTICE: 1000 rows copied from range_rel_3 +s1: NOTICE: 1000 rows copied from range_rel_4 +s1: NOTICE: 1000 rows copied from range_rel_5 +s1: NOTICE: 1000 rows copied from range_rel_6 +s1: NOTICE: 1000 rows copied from range_rel_7 +s1: NOTICE: 1000 rows copied from range_rel_8 +s1: NOTICE: 1000 rows copied from range_rel_9 +s1: NOTICE: 1000 rows copied from range_rel_10 step drop_partitions: SELECT drop_partitions('range_rel'); drop_partitions +--------------- + 10 +(1 row) -10 step rollback_a: ROLLBACK TO SAVEPOINT a; step create_partitions: SELECT create_range_partitions('range_rel', 'id', 1, 1000); create_range_partitions +----------------------- + 10 +(1 row) + +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent |partition +---------+------------ +range_rel|range_rel_1 +range_rel|range_rel_2 +range_rel|range_rel_3 +range_rel|range_rel_4 +range_rel|range_rel_5 +range_rel|range_rel_6 +range_rel|range_rel_7 +range_rel|range_rel_8 +range_rel|range_rel_9 +range_rel|range_rel_10 +(10 rows) -10 -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN - -Append - -> Seq Scan on range_rel_1 - -> Seq Scan on range_rel_2 - -> Seq Scan on range_rel_3 - -> Seq Scan on range_rel_4 - -> Seq Scan on range_rel_5 - -> Seq Scan on range_rel_6 - -> Seq Scan on range_rel_7 - -> Seq Scan on range_rel_8 - -> Seq Scan on range_rel_9 - -> Seq Scan on range_rel_10 step commit: COMMIT; -step show_rel: EXPLAIN (COSTS OFF) SELECT * FROM range_rel; -QUERY PLAN - -Append - -> Seq Scan on range_rel_1 - -> Seq Scan on range_rel_2 - -> Seq Scan on range_rel_3 - -> Seq Scan on range_rel_4 - -> Seq Scan on range_rel_5 - -> Seq Scan on range_rel_6 - -> Seq Scan on range_rel_7 - -> Seq Scan on range_rel_8 - -> Seq Scan on range_rel_9 - -> Seq Scan on range_rel_10 +step show_rel: SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; +parent |partition +---------+------------ +range_rel|range_rel_1 +range_rel|range_rel_2 +range_rel|range_rel_3 +range_rel|range_rel_4 +range_rel|range_rel_5 +range_rel|range_rel_6 +range_rel|range_rel_7 +range_rel|range_rel_8 +range_rel|range_rel_9 +range_rel|range_rel_10 +(10 rows) + diff --git a/patches/REL_11_STABLE-pg_pathman-core.diff b/patches/REL_11_STABLE-pg_pathman-core.diff new file mode 100644 index 00000000..b3b08e0a --- /dev/null +++ b/patches/REL_11_STABLE-pg_pathman-core.diff @@ -0,0 +1,53 @@ +diff --git a/src/backend/jit/llvm/llvmjit_deform.c b/src/backend/jit/llvm/llvmjit_deform.c +index 6384ac940d8..8b4f731e7a8 100644 +--- a/src/backend/jit/llvm/llvmjit_deform.c ++++ b/src/backend/jit/llvm/llvmjit_deform.c +@@ -104,6 +104,10 @@ slot_compile_deform(LLVMJitContext *context, TupleDesc desc, int natts) + + int attnum; + ++ /* don't generate code for tuples without user attributes */ ++ if (desc->natts == 0) ++ return NULL; ++ + mod = llvm_mutable_module(context); + + funcname = llvm_expand_funcname(context, "deform"); +diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c +index 12138e49577..8638ebc4ba1 100644 +--- a/src/backend/jit/llvm/llvmjit_expr.c ++++ b/src/backend/jit/llvm/llvmjit_expr.c +@@ -274,6 +274,7 @@ llvm_compile_expr(ExprState *state) + LLVMValueRef v_slot; + LLVMBasicBlockRef b_fetch; + LLVMValueRef v_nvalid; ++ LLVMValueRef l_jit_deform = NULL; + + b_fetch = l_bb_before_v(opblocks[i + 1], + "op.%d.fetch", i); +@@ -336,17 +337,20 @@ llvm_compile_expr(ExprState *state) + */ + if (desc && (context->base.flags & PGJIT_DEFORM)) + { +- LLVMValueRef params[1]; +- LLVMValueRef l_jit_deform; +- + l_jit_deform = +- slot_compile_deform(context, desc, ++ slot_compile_deform(context, ++ desc, + op->d.fetch.last_var); ++ } ++ ++ if (l_jit_deform) ++ { ++ LLVMValueRef params[1]; ++ + params[0] = v_slot; + + LLVMBuildCall(b, l_jit_deform, + params, lengthof(params), ""); +- + } + else + { diff --git a/patches/REL_14_STABLE-pg_pathman-core.diff b/patches/REL_14_STABLE-pg_pathman-core.diff new file mode 100644 index 00000000..a6ac1afa --- /dev/null +++ b/patches/REL_14_STABLE-pg_pathman-core.diff @@ -0,0 +1,513 @@ +diff --git a/contrib/Makefile b/contrib/Makefile +index f27e458482..ea47c341c1 100644 +--- a/contrib/Makefile ++++ b/contrib/Makefile +@@ -32,6 +32,7 @@ SUBDIRS = \ + passwordcheck \ + pg_buffercache \ + pg_freespacemap \ ++ pg_pathman \ + pg_prewarm \ + pg_stat_statements \ + pg_surgery \ +diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c +index bf551b0395..10d2044ae6 100644 +--- a/src/backend/access/transam/xact.c ++++ b/src/backend/access/transam/xact.c +@@ -76,7 +76,7 @@ int DefaultXactIsoLevel = XACT_READ_COMMITTED; + int XactIsoLevel; + + bool DefaultXactReadOnly = false; +-bool XactReadOnly; ++bool XactReadOnly = false; + + bool DefaultXactDeferrable = false; + bool XactDeferrable; +diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c +index bdf59a10fc..972453d9a5 100644 +--- a/src/backend/executor/execExprInterp.c ++++ b/src/backend/executor/execExprInterp.c +@@ -1799,6 +1799,16 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) + } + + out: ++ ++ /* ++ * pg_pathman: pass 'tts_tableOid' to result tuple to determine from ++ * which partition the tuple was read ++ */ ++ if (resultslot) ++ { ++ resultslot->tts_tableOid = scanslot ? scanslot->tts_tableOid : ++ (innerslot ? innerslot->tts_tableOid : (outerslot ? outerslot->tts_tableOid : InvalidOid)); ++ } + *isnull = state->resnull; + return state->resvalue; + } +diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c +index b3ce4bae53..8f2bb12542 100644 +--- a/src/backend/executor/execMain.c ++++ b/src/backend/executor/execMain.c +@@ -824,6 +824,13 @@ InitPlan(QueryDesc *queryDesc, int eflags) + + estate->es_plannedstmt = plannedstmt; + ++ /* ++ * Fields "es_result_relation_info", "es_original_tuple" are used for ++ * pg_pathman only: ++ */ ++ estate->es_result_relation_info = NULL; ++ estate->es_original_tuple = NULL; ++ + /* + * Next, build the ExecRowMark array from the PlanRowMark(s), if any. + */ +@@ -2713,6 +2720,13 @@ EvalPlanQualStart(EPQState *epqstate, Plan *planTree) + rcestate->es_junkFilter = parentestate->es_junkFilter; + rcestate->es_output_cid = parentestate->es_output_cid; + ++ /* ++ * Fields "es_result_relation_info", "es_original_tuple" are used for ++ * pg_pathman only: ++ */ ++ rcestate->es_result_relation_info = NULL; ++ rcestate->es_original_tuple = NULL; ++ + /* + * ResultRelInfos needed by subplans are initialized from scratch when the + * subplans themselves are initialized. +diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c +index 55c430c9ec..21d9e6304a 100644 +--- a/src/backend/executor/nodeModifyTable.c ++++ b/src/backend/executor/nodeModifyTable.c +@@ -510,7 +510,7 @@ ExecInitInsertProjection(ModifyTableState *mtstate, + * This is also a convenient place to verify that the output of an UPDATE + * matches the target table (ExecBuildUpdateProjection does that). + */ +-static void ++void + ExecInitUpdateProjection(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo) + { +@@ -2486,6 +2486,7 @@ ExecModifyTable(PlanState *pstate) + ItemPointerData tuple_ctid; + HeapTupleData oldtupdata; + HeapTuple oldtuple; ++ ResultRelInfo *saved_resultRelInfo; + + CHECK_FOR_INTERRUPTS(); + +@@ -2523,12 +2524,23 @@ ExecModifyTable(PlanState *pstate) + resultRelInfo = node->resultRelInfo + node->mt_lastResultIndex; + subplanstate = outerPlanState(node); + ++ saved_resultRelInfo = estate->es_result_relation_info; ++ estate->es_result_relation_info = NULL; ++ + /* + * Fetch rows from subplan, and execute the required table modification + * for each row. + */ + for (;;) + { ++ /* ++ * "es_original_tuple" should contains original modified tuple (new ++ * values of the changed columns plus row identity information such as ++ * CTID) in case tuple planSlot is replaced in pg_pathman to new value ++ * in call "ExecProcNode(subplanstate)". ++ */ ++ estate->es_original_tuple = NULL; ++ + /* + * Reset the per-output-tuple exprcontext. This is needed because + * triggers expect to use that context as workspace. It's a bit ugly +@@ -2562,7 +2574,9 @@ ExecModifyTable(PlanState *pstate) + bool isNull; + Oid resultoid; + +- datum = ExecGetJunkAttribute(planSlot, node->mt_resultOidAttno, ++ datum = ExecGetJunkAttribute(estate->es_original_tuple ? ++ estate->es_original_tuple : planSlot, ++ node->mt_resultOidAttno, + &isNull); + if (isNull) + elog(ERROR, "tableoid is NULL"); +@@ -2581,6 +2595,8 @@ ExecModifyTable(PlanState *pstate) + if (resultRelInfo->ri_usesFdwDirectModify) + { + Assert(resultRelInfo->ri_projectReturning); ++ /* PartitionRouter does not support foreign data wrappers: */ ++ Assert(estate->es_original_tuple == NULL); + + /* + * A scan slot containing the data that was actually inserted, +@@ -2590,6 +2606,7 @@ ExecModifyTable(PlanState *pstate) + */ + slot = ExecProcessReturning(resultRelInfo, NULL, planSlot); + ++ estate->es_result_relation_info = saved_resultRelInfo; + return slot; + } + +@@ -2619,7 +2636,8 @@ ExecModifyTable(PlanState *pstate) + { + /* ri_RowIdAttNo refers to a ctid attribute */ + Assert(AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo)); +- datum = ExecGetJunkAttribute(slot, ++ datum = ExecGetJunkAttribute(estate->es_original_tuple ++ ? estate->es_original_tuple : slot, + resultRelInfo->ri_RowIdAttNo, + &isNull); + /* shouldn't ever get a null result... */ +@@ -2649,7 +2667,8 @@ ExecModifyTable(PlanState *pstate) + */ + else if (AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo)) + { +- datum = ExecGetJunkAttribute(slot, ++ datum = ExecGetJunkAttribute(estate->es_original_tuple ++ ? estate->es_original_tuple : slot, + resultRelInfo->ri_RowIdAttNo, + &isNull); + /* shouldn't ever get a null result... */ +@@ -2680,8 +2699,12 @@ ExecModifyTable(PlanState *pstate) + /* Initialize projection info if first time for this table */ + if (unlikely(!resultRelInfo->ri_projectNewInfoValid)) + ExecInitInsertProjection(node, resultRelInfo); +- slot = ExecGetInsertNewTuple(resultRelInfo, planSlot); +- slot = ExecInsert(node, resultRelInfo, slot, planSlot, ++ /* Do nothing in case tuple was modified in pg_pathman: */ ++ if (!estate->es_original_tuple) ++ slot = ExecGetInsertNewTuple(resultRelInfo, planSlot); ++ slot = ExecInsert(node, estate->es_result_relation_info ? ++ estate->es_result_relation_info : resultRelInfo, ++ slot, planSlot, + estate, node->canSetTag); + break; + case CMD_UPDATE: +@@ -2689,6 +2712,13 @@ ExecModifyTable(PlanState *pstate) + if (unlikely(!resultRelInfo->ri_projectNewInfoValid)) + ExecInitUpdateProjection(node, resultRelInfo); + ++ /* ++ * Do not change the indentation for PostgreSQL code to make it ++ * easier to merge new PostgreSQL changes. ++ */ ++ /* Do nothing in case tuple was modified in pg_pathman: */ ++ if (!estate->es_original_tuple) ++ { + /* + * Make the new tuple by combining plan's output tuple with + * the old tuple being updated. +@@ -2712,14 +2742,19 @@ ExecModifyTable(PlanState *pstate) + } + slot = ExecGetUpdateNewTuple(resultRelInfo, planSlot, + oldSlot); ++ } + + /* Now apply the update. */ +- slot = ExecUpdate(node, resultRelInfo, tupleid, oldtuple, slot, ++ slot = ExecUpdate(node, estate->es_result_relation_info ? ++ estate->es_result_relation_info : resultRelInfo, ++ tupleid, oldtuple, slot, + planSlot, &node->mt_epqstate, estate, + node->canSetTag); + break; + case CMD_DELETE: +- slot = ExecDelete(node, resultRelInfo, tupleid, oldtuple, ++ slot = ExecDelete(node, estate->es_result_relation_info ? ++ estate->es_result_relation_info : resultRelInfo, ++ tupleid, oldtuple, + planSlot, &node->mt_epqstate, estate, + true, /* processReturning */ + node->canSetTag, +@@ -2736,7 +2771,10 @@ ExecModifyTable(PlanState *pstate) + * the work on next call. + */ + if (slot) ++ { ++ estate->es_result_relation_info = saved_resultRelInfo; + return slot; ++ } + } + + /* +@@ -2752,6 +2790,7 @@ ExecModifyTable(PlanState *pstate) + + node->mt_done = true; + ++ estate->es_result_relation_info = saved_resultRelInfo; + return NULL; + } + +@@ -2826,6 +2865,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) + ListCell *l; + int i; + Relation rel; ++ ResultRelInfo *saved_resultRelInfo; + + /* check for unsupported flags */ + Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK))); +@@ -2922,6 +2962,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) + i++; + } + ++ /* ++ * pg_pathman: set "estate->es_result_relation_info" value for take it in ++ * functions partition_filter_begin(), partition_router_begin() ++ */ ++ saved_resultRelInfo = estate->es_result_relation_info; ++ estate->es_result_relation_info = mtstate->resultRelInfo; ++ + /* + * Now we may initialize the subplan. + */ +@@ -3002,6 +3049,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) + ExecInitStoredGenerated(resultRelInfo, estate, operation); + } + ++ estate->es_result_relation_info = saved_resultRelInfo; ++ + /* + * If this is an inherited update/delete, there will be a junk attribute + * named "tableoid" present in the subplan's targetlist. It will be used +diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c +index 381d9e548d..0a4657d291 100644 +--- a/src/backend/utils/init/globals.c ++++ b/src/backend/utils/init/globals.c +@@ -25,7 +25,7 @@ + #include "storage/backendid.h" + + +-ProtocolVersion FrontendProtocol; ++ProtocolVersion FrontendProtocol = (ProtocolVersion) 0; + + volatile sig_atomic_t InterruptPending = false; + volatile sig_atomic_t QueryCancelPending = false; +diff --git a/src/include/access/xact.h b/src/include/access/xact.h +index 5af78bd0dc..0c13bc9d83 100644 +--- a/src/include/access/xact.h ++++ b/src/include/access/xact.h +@@ -53,7 +53,9 @@ extern PGDLLIMPORT int XactIsoLevel; + + /* Xact read-only state */ + extern bool DefaultXactReadOnly; +-extern bool XactReadOnly; ++ ++#define PGPRO_PATHMAN_AWARE_COPY ++extern PGDLLIMPORT bool XactReadOnly; + + /* flag for logging statements in this transaction */ + extern bool xact_is_sampled; +diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h +index 2b4e104bb9..80d1274efe 100644 +--- a/src/include/catalog/objectaddress.h ++++ b/src/include/catalog/objectaddress.h +@@ -28,7 +28,7 @@ typedef struct ObjectAddress + int32 objectSubId; /* Subitem within object (eg column), or 0 */ + } ObjectAddress; + +-extern const ObjectAddress InvalidObjectAddress; ++extern PGDLLIMPORT const ObjectAddress InvalidObjectAddress; + + #define ObjectAddressSubSet(addr, class_id, object_id, object_sub_id) \ + do { \ +diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h +index 3dc03c913e..1002d97499 100644 +--- a/src/include/executor/executor.h ++++ b/src/include/executor/executor.h +@@ -657,5 +657,7 @@ extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *node, + Oid resultoid, + bool missing_ok, + bool update_cache); ++extern void ExecInitUpdateProjection(ModifyTableState *mtstate, ++ ResultRelInfo *resultRelInfo); + + #endif /* EXECUTOR_H */ +diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h +index 4acb1cda6e..fd8d38347d 100644 +--- a/src/include/libpq/libpq-be.h ++++ b/src/include/libpq/libpq-be.h +@@ -327,7 +327,7 @@ extern ssize_t be_gssapi_read(Port *port, void *ptr, size_t len); + extern ssize_t be_gssapi_write(Port *port, void *ptr, size_t len); + #endif /* ENABLE_GSS */ + +-extern ProtocolVersion FrontendProtocol; ++extern PGDLLIMPORT ProtocolVersion FrontendProtocol; + + /* TCP keepalives configuration. These are no-ops on an AF_UNIX socket. */ + +diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h +index ee5ad3c058..dc474819d7 100644 +--- a/src/include/nodes/execnodes.h ++++ b/src/include/nodes/execnodes.h +@@ -592,6 +592,12 @@ typedef struct EState + * es_result_relations in no + * specific order */ + ++ /* These fields was added for compatibility pg_pathman with 14: */ ++ ResultRelInfo *es_result_relation_info; /* currently active array elt */ ++ TupleTableSlot *es_original_tuple; /* original modified tuple (new values ++ * of the changed columns plus row ++ * identity information such as CTID) */ ++ + PartitionDirectory es_partition_directory; /* for PartitionDesc lookup */ + + /* +diff --git a/src/include/utils/snapmgr.h b/src/include/utils/snapmgr.h +index 33e6c14e81..abd9bba23e 100644 +--- a/src/include/utils/snapmgr.h ++++ b/src/include/utils/snapmgr.h +@@ -53,7 +53,7 @@ extern TimestampTz GetSnapshotCurrentTimestamp(void); + extern TimestampTz GetOldSnapshotThresholdTimestamp(void); + extern void SnapshotTooOldMagicForTest(void); + +-extern bool FirstSnapshotSet; ++extern PGDLLIMPORT bool FirstSnapshotSet; + + extern PGDLLIMPORT TransactionId TransactionXmin; + extern PGDLLIMPORT TransactionId RecentXmin; +diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm +index de22c9ba2c..c8be5323b8 100644 +--- a/src/tools/msvc/Install.pm ++++ b/src/tools/msvc/Install.pm +@@ -30,6 +30,18 @@ my @client_program_files = ( + 'pg_receivewal', 'pg_recvlogical', 'pg_restore', 'psql', + 'reindexdb', 'vacuumdb', @client_contribs); + ++sub SubstituteMakefileVariables { ++ local $_ = shift; # Line to substitue ++ my $mf = shift; # Makefile text ++ while (/\$\((\w+)\)/) { ++ my $varname = $1; ++ if ($mf =~ /^$varname\s*=\s*(.*)$/mg) { ++ my $varvalue=$1; ++ s/\$\($varname\)/$varvalue/g; ++ } ++ } ++ return $_; ++} + sub lcopy + { + my $src = shift; +@@ -608,7 +620,7 @@ sub ParseAndCleanRule + substr($flist, 0, index($flist, '$(addsuffix ')) + . substr($flist, $i + 1); + } +- return $flist; ++ return SubstituteMakefileVariables($flist, $mf); + } + + sub CopyIncludeFiles +diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm +index 9b6539fb15..f8a67c6701 100644 +--- a/src/tools/msvc/Mkvcbuild.pm ++++ b/src/tools/msvc/Mkvcbuild.pm +@@ -41,7 +41,10 @@ my @contrib_uselibpq = + my @contrib_uselibpgport = ('libpq_pipeline', 'oid2name', 'vacuumlo'); + my @contrib_uselibpgcommon = ('libpq_pipeline', 'oid2name', 'vacuumlo'); + my $contrib_extralibs = { 'libpq_pipeline' => ['ws2_32.lib'] }; +-my $contrib_extraincludes = { 'dblink' => ['src/backend'] }; ++my $contrib_extraincludes = { ++ 'dblink' => ['src/backend'], ++ 'pg_pathman' => ['contrib/pg_pathman/src/include'] ++}; + my $contrib_extrasource = { + 'cube' => [ 'contrib/cube/cubescan.l', 'contrib/cube/cubeparse.y' ], + 'seg' => [ 'contrib/seg/segscan.l', 'contrib/seg/segparse.y' ], +@@ -973,6 +976,7 @@ sub AddContrib + my $dn = $1; + my $proj = $solution->AddProject($dn, 'dll', 'contrib', "$subdir/$n"); + $proj->AddReference($postgres); ++ $proj->RemoveFile("$subdir/$n/src/declarative.c") if $n eq 'pg_pathman'; + AdjustContribProj($proj); + } + elsif ($mf =~ /^MODULES\s*=\s*(.*)$/mg) +@@ -1002,6 +1006,19 @@ sub AddContrib + return; + } + ++sub SubstituteMakefileVariables { ++ local $_ = shift; # Line to substitue ++ my $mf = shift; # Makefile text ++ while (/\$\((\w+)\)/) { ++ my $varname = $1; ++ if ($mf =~ /^$varname\s*=\s*(.*)$/mg) { ++ my $varvalue=$1; ++ s/\$\($varname\)/$varvalue/g; ++ } ++ } ++ return $_; ++} ++ + sub GenerateContribSqlFiles + { + my $n = shift; +@@ -1026,23 +1043,53 @@ sub GenerateContribSqlFiles + substr($l, 0, index($l, '$(addsuffix ')) . substr($l, $i + 1); + } + ++ $l = SubstituteMakefileVariables($l,$mf); + foreach my $d (split /\s+/, $l) + { +- my $in = "$d.in"; +- my $out = "$d"; +- +- if (Solution::IsNewer("contrib/$n/$out", "contrib/$n/$in")) +- { +- print "Building $out from $in (contrib/$n)...\n"; +- my $cont = Project::read_file("contrib/$n/$in"); +- my $dn = $out; +- $dn =~ s/\.sql$//; +- $cont =~ s/MODULE_PATHNAME/\$libdir\/$dn/g; +- my $o; +- open($o, '>', "contrib/$n/$out") +- || croak "Could not write to contrib/$n/$d"; +- print $o $cont; +- close($o); ++ if ( -f "contrib/$n/$d.in" ) { ++ my $in = "$d.in"; ++ my $out = "$d"; ++ if (Solution::IsNewer("contrib/$n/$out", "contrib/$n/$in")) ++ { ++ print "Building $out from $in (contrib/$n)...\n"; ++ my $cont = Project::read_file("contrib/$n/$in"); ++ my $dn = $out; ++ $dn =~ s/\.sql$//; ++ $cont =~ s/MODULE_PATHNAME/\$libdir\/$dn/g; ++ my $o; ++ open($o, '>', "contrib/$n/$out") ++ || croak "Could not write to contrib/$n/$d"; ++ print $o $cont; ++ close($o); ++ } ++ } else { ++ # Search for makefile rule. ++ # For now we do not process rule command and assume ++ # that we should just concatenate all prerequisites ++ # ++ my @prereq = (); ++ my $target; ++ my @rules = $mf =~ /^(\S+)\s*:\s*([^=].*)$/mg; ++ RULE: ++ while (@rules) { ++ $target = SubstituteMakefileVariables(shift @rules,$mf); ++ @prereq = split(/\s+/,SubstituteMakefileVariables(shift @rules,$mf)); ++ last RULE if ($target eq $d); ++ @prereq = (); ++ } ++ croak "Don't know how to build contrib/$n/$d" unless @prereq; ++ if (grep(Solution::IsNewer("contrib/$n/$d","contrib/$n/$_"), ++ @prereq)) { ++ print STDERR "building $d from @prereq by concatentation\n"; ++ my $o; ++ open $o, ">contrib/$n/$d" ++ or croak("Couldn't write to contrib/$n/$d:$!"); ++ for my $in (@prereq) { ++ my $data = Project::read_file("contrib/$n/$in"); ++ print $o $data; ++ } ++ close $o; ++ } + } + } + } diff --git a/patches/REL_15_STABLE-pg_pathman-core.diff b/patches/REL_15_STABLE-pg_pathman-core.diff new file mode 100644 index 00000000..b8db29fd --- /dev/null +++ b/patches/REL_15_STABLE-pg_pathman-core.diff @@ -0,0 +1,487 @@ +diff --git a/contrib/Makefile b/contrib/Makefile +index bbf220407b..9a82a2db04 100644 +--- a/contrib/Makefile ++++ b/contrib/Makefile +@@ -34,6 +34,7 @@ SUBDIRS = \ + passwordcheck \ + pg_buffercache \ + pg_freespacemap \ ++ pg_pathman \ + pg_prewarm \ + pg_stat_statements \ + pg_surgery \ +diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c +index 7a3d9b4b01..0c3d2dec6c 100644 +--- a/src/backend/access/transam/xact.c ++++ b/src/backend/access/transam/xact.c +@@ -78,7 +78,7 @@ int DefaultXactIsoLevel = XACT_READ_COMMITTED; + int XactIsoLevel; + + bool DefaultXactReadOnly = false; +-bool XactReadOnly; ++bool XactReadOnly = false; + + bool DefaultXactDeferrable = false; + bool XactDeferrable; +diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c +index 87c7603f2b..9cc0bc0da8 100644 +--- a/src/backend/executor/execExprInterp.c ++++ b/src/backend/executor/execExprInterp.c +@@ -1801,6 +1801,16 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) + } + + out: ++ ++ /* ++ * pg_pathman: pass 'tts_tableOid' to result tuple to determine from ++ * which partition the tuple was read ++ */ ++ if (resultslot) ++ { ++ resultslot->tts_tableOid = scanslot ? scanslot->tts_tableOid : ++ (innerslot ? innerslot->tts_tableOid : (outerslot ? outerslot->tts_tableOid : InvalidOid)); ++ } + *isnull = state->resnull; + return state->resvalue; + } +diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c +index 0ba61fd547..29d93998b2 100644 +--- a/src/backend/executor/execMain.c ++++ b/src/backend/executor/execMain.c +@@ -826,6 +826,13 @@ InitPlan(QueryDesc *queryDesc, int eflags) + + estate->es_plannedstmt = plannedstmt; + ++ /* ++ * Fields "es_result_relation_info", "es_original_tuple" are used for ++ * pg_pathman only: ++ */ ++ estate->es_result_relation_info = NULL; ++ estate->es_original_tuple = NULL; ++ + /* + * Next, build the ExecRowMark array from the PlanRowMark(s), if any. + */ +@@ -2849,6 +2856,13 @@ EvalPlanQualStart(EPQState *epqstate, Plan *planTree) + rcestate->es_junkFilter = parentestate->es_junkFilter; + rcestate->es_output_cid = parentestate->es_output_cid; + ++ /* ++ * Fields "es_result_relation_info", "es_original_tuple" are used for ++ * pg_pathman only: ++ */ ++ rcestate->es_result_relation_info = NULL; ++ rcestate->es_original_tuple = NULL; ++ + /* + * ResultRelInfos needed by subplans are initialized from scratch when the + * subplans themselves are initialized. +diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c +index 1ad5dcb406..047508e0da 100644 +--- a/src/backend/executor/nodeModifyTable.c ++++ b/src/backend/executor/nodeModifyTable.c +@@ -641,6 +641,13 @@ ExecInitUpdateProjection(ModifyTableState *mtstate, + resultRelInfo->ri_projectNewInfoValid = true; + } + ++void ++PgproExecInitUpdateProjection(ModifyTableState *mtstate, ++ ResultRelInfo *resultRelInfo) ++{ ++ ExecInitUpdateProjection(mtstate, resultRelInfo); ++} ++ + /* + * ExecGetInsertNewTuple + * This prepares a "new" tuple ready to be inserted into given result +@@ -3581,6 +3588,7 @@ ExecModifyTable(PlanState *pstate) + HeapTupleData oldtupdata; + HeapTuple oldtuple; + ItemPointer tupleid; ++ ResultRelInfo *saved_resultRelInfo; + + CHECK_FOR_INTERRUPTS(); + +@@ -3622,6 +3630,8 @@ ExecModifyTable(PlanState *pstate) + context.mtstate = node; + context.epqstate = &node->mt_epqstate; + context.estate = estate; ++ saved_resultRelInfo = estate->es_result_relation_info; ++ estate->es_result_relation_info = NULL; + + /* + * Fetch rows from subplan, and execute the required table modification +@@ -3629,6 +3639,14 @@ ExecModifyTable(PlanState *pstate) + */ + for (;;) + { ++ /* ++ * "es_original_tuple" should contain original modified tuple (new ++ * values of the changed columns plus row identity information such as ++ * CTID) in case tuple planSlot is replaced in pg_pathman to new value ++ * in call "ExecProcNode(subplanstate)". ++ */ ++ estate->es_original_tuple = NULL; ++ + /* + * Reset the per-output-tuple exprcontext. This is needed because + * triggers expect to use that context as workspace. It's a bit ugly +@@ -3662,7 +3680,9 @@ ExecModifyTable(PlanState *pstate) + bool isNull; + Oid resultoid; + +- datum = ExecGetJunkAttribute(context.planSlot, node->mt_resultOidAttno, ++ datum = ExecGetJunkAttribute(estate->es_original_tuple ? ++ estate->es_original_tuple : context.planSlot, ++ node->mt_resultOidAttno, + &isNull); + if (isNull) + { +@@ -3699,6 +3719,8 @@ ExecModifyTable(PlanState *pstate) + if (resultRelInfo->ri_usesFdwDirectModify) + { + Assert(resultRelInfo->ri_projectReturning); ++ /* PartitionRouter does not support foreign data wrappers: */ ++ Assert(estate->es_original_tuple == NULL); + + /* + * A scan slot containing the data that was actually inserted, +@@ -3708,6 +3730,7 @@ ExecModifyTable(PlanState *pstate) + */ + slot = ExecProcessReturning(resultRelInfo, NULL, context.planSlot); + ++ estate->es_result_relation_info = saved_resultRelInfo; + return slot; + } + +@@ -3738,7 +3761,8 @@ ExecModifyTable(PlanState *pstate) + { + /* ri_RowIdAttNo refers to a ctid attribute */ + Assert(AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo)); +- datum = ExecGetJunkAttribute(slot, ++ datum = ExecGetJunkAttribute(estate->es_original_tuple ++ ? estate->es_original_tuple : slot, + resultRelInfo->ri_RowIdAttNo, + &isNull); + +@@ -3786,7 +3810,8 @@ ExecModifyTable(PlanState *pstate) + */ + else if (AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo)) + { +- datum = ExecGetJunkAttribute(slot, ++ datum = ExecGetJunkAttribute(estate->es_original_tuple ++ ? estate->es_original_tuple : slot, + resultRelInfo->ri_RowIdAttNo, + &isNull); + /* shouldn't ever get a null result... */ +@@ -3817,9 +3842,12 @@ ExecModifyTable(PlanState *pstate) + /* Initialize projection info if first time for this table */ + if (unlikely(!resultRelInfo->ri_projectNewInfoValid)) + ExecInitInsertProjection(node, resultRelInfo); +- slot = ExecGetInsertNewTuple(resultRelInfo, context.planSlot); +- slot = ExecInsert(&context, resultRelInfo, slot, +- node->canSetTag, NULL, NULL); ++ /* Do nothing in case tuple was modified in pg_pathman: */ ++ if (!estate->es_original_tuple) ++ slot = ExecGetInsertNewTuple(resultRelInfo, context.planSlot); ++ slot = ExecInsert(&context, estate->es_result_relation_info ? ++ estate->es_result_relation_info : resultRelInfo, ++ slot, node->canSetTag, NULL, NULL); + break; + + case CMD_UPDATE: +@@ -3827,6 +3855,13 @@ ExecModifyTable(PlanState *pstate) + if (unlikely(!resultRelInfo->ri_projectNewInfoValid)) + ExecInitUpdateProjection(node, resultRelInfo); + ++ /* ++ * Do not change the indentation for PostgreSQL code to make it ++ * easier to merge new PostgreSQL changes. ++ */ ++ /* Do nothing in case tuple was modified in pg_pathman: */ ++ if (!estate->es_original_tuple) ++ { + /* + * Make the new tuple by combining plan's output tuple with + * the old tuple being updated. +@@ -3850,14 +3885,19 @@ ExecModifyTable(PlanState *pstate) + slot = ExecGetUpdateNewTuple(resultRelInfo, context.planSlot, + oldSlot); + context.relaction = NULL; ++ } + + /* Now apply the update. */ +- slot = ExecUpdate(&context, resultRelInfo, tupleid, oldtuple, ++ slot = ExecUpdate(&context, estate->es_result_relation_info ? ++ estate->es_result_relation_info : resultRelInfo, ++ tupleid, oldtuple, + slot, node->canSetTag); + break; + + case CMD_DELETE: +- slot = ExecDelete(&context, resultRelInfo, tupleid, oldtuple, ++ slot = ExecDelete(&context, estate->es_result_relation_info ? ++ estate->es_result_relation_info : resultRelInfo, ++ tupleid, oldtuple, + true, false, node->canSetTag, NULL, NULL, NULL); + break; + +@@ -3875,7 +3915,10 @@ ExecModifyTable(PlanState *pstate) + * the work on next call. + */ + if (slot) ++ { ++ estate->es_result_relation_info = saved_resultRelInfo; + return slot; ++ } + } + + /* +@@ -3891,6 +3934,7 @@ ExecModifyTable(PlanState *pstate) + + node->mt_done = true; + ++ estate->es_result_relation_info = saved_resultRelInfo; + return NULL; + } + +@@ -3965,6 +4009,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) + ListCell *l; + int i; + Relation rel; ++ ResultRelInfo *saved_resultRelInfo; + + /* check for unsupported flags */ + Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK))); +@@ -4067,6 +4112,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) + i++; + } + ++ /* ++ * pg_pathman: set "estate->es_result_relation_info" value for take it in ++ * functions partition_filter_begin(), partition_router_begin() ++ */ ++ saved_resultRelInfo = estate->es_result_relation_info; ++ estate->es_result_relation_info = mtstate->resultRelInfo; ++ + /* + * Now we may initialize the subplan. + */ +@@ -4161,6 +4213,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) + ExecInitStoredGenerated(resultRelInfo, estate, operation); + } + ++ estate->es_result_relation_info = saved_resultRelInfo; ++ + /* + * If this is an inherited update/delete/merge, there will be a junk + * attribute named "tableoid" present in the subplan's targetlist. It +diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c +index 1a5d29ac9b..aadca8ea47 100644 +--- a/src/backend/utils/init/globals.c ++++ b/src/backend/utils/init/globals.c +@@ -25,7 +25,7 @@ + #include "storage/backendid.h" + + +-ProtocolVersion FrontendProtocol; ++ProtocolVersion FrontendProtocol = (ProtocolVersion) 0; + + volatile sig_atomic_t InterruptPending = false; + volatile sig_atomic_t QueryCancelPending = false; +diff --git a/src/include/access/xact.h b/src/include/access/xact.h +index 8d46a781bb..150d70cb64 100644 +--- a/src/include/access/xact.h ++++ b/src/include/access/xact.h +@@ -53,6 +53,8 @@ extern PGDLLIMPORT int XactIsoLevel; + + /* Xact read-only state */ + extern PGDLLIMPORT bool DefaultXactReadOnly; ++ ++#define PGPRO_PATHMAN_AWARE_COPY + extern PGDLLIMPORT bool XactReadOnly; + + /* flag for logging statements in this transaction */ +diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h +index 7cd9b2f2bf..b31a7934a4 100644 +--- a/src/include/executor/executor.h ++++ b/src/include/executor/executor.h +@@ -662,5 +662,17 @@ extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *node, + Oid resultoid, + bool missing_ok, + bool update_cache); ++#define PG_HAVE_PGPRO_EXEC_INIT_UPDATE_PROJECTION ++/* ++ * This function is static in vanilla, but pg_pathman wants it exported. ++ * We cannot make it extern with the same name to avoid compilation errors ++ * in timescaledb, which ships it's own static copy of the same function. ++ * So, export ExecInitUpdateProjection with Pgpro prefix. ++ * ++ * The define above helps pg_pathman to expect proper exported symbol ++ * from various versions of pgpro. ++ */ ++extern void PgproExecInitUpdateProjection(ModifyTableState *mtstate, ++ ResultRelInfo *resultRelInfo); + + #endif /* EXECUTOR_H */ +diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h +index 9f176b0e37..a65799dcce 100644 +--- a/src/include/nodes/execnodes.h ++++ b/src/include/nodes/execnodes.h +@@ -624,6 +624,12 @@ typedef struct EState + * es_result_relations in no + * specific order */ + ++ /* These fields was added for compatibility pg_pathman with 14: */ ++ ResultRelInfo *es_result_relation_info; /* currently active array elt */ ++ TupleTableSlot *es_original_tuple; /* original modified tuple (new values ++ * of the changed columns plus row ++ * identity information such as CTID) */ ++ + PartitionDirectory es_partition_directory; /* for PartitionDesc lookup */ + + /* +diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm +index 8de79c618c..c9226ba5ad 100644 +--- a/src/tools/msvc/Install.pm ++++ b/src/tools/msvc/Install.pm +@@ -30,6 +30,18 @@ my @client_program_files = ( + 'pg_receivewal', 'pg_recvlogical', 'pg_restore', 'psql', + 'reindexdb', 'vacuumdb', @client_contribs); + ++sub SubstituteMakefileVariables { ++ local $_ = shift; # Line to substitue ++ my $mf = shift; # Makefile text ++ while (/\$\((\w+)\)/) { ++ my $varname = $1; ++ if ($mf =~ /^$varname\s*=\s*(.*)$/mg) { ++ my $varvalue=$1; ++ s/\$\($varname\)/$varvalue/g; ++ } ++ } ++ return $_; ++} + sub lcopy + { + my $src = shift; +@@ -609,7 +621,7 @@ sub ParseAndCleanRule + substr($flist, 0, index($flist, '$(addsuffix ')) + . substr($flist, $i + 1); + } +- return $flist; ++ return SubstituteMakefileVariables($flist, $mf); + } + + sub CopyIncludeFiles +diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm +index 990c223a9b..cd5048f8d5 100644 +--- a/src/tools/msvc/Mkvcbuild.pm ++++ b/src/tools/msvc/Mkvcbuild.pm +@@ -39,8 +39,8 @@ my $contrib_defines = {}; + my @contrib_uselibpq = (); + my @contrib_uselibpgport = (); + my @contrib_uselibpgcommon = (); +-my $contrib_extralibs = { 'libpq_pipeline' => ['ws2_32.lib'] }; +-my $contrib_extraincludes = {}; ++my $contrib_extralibs = { 'libpq_pipeline' => ['ws2_32.lib'] }; ++my $contrib_extraincludes = { 'pg_pathman' => ['contrib/pg_pathman/src/include'] }; + my $contrib_extrasource = {}; + my @contrib_excludes = ( + 'bool_plperl', 'commit_ts', +@@ -967,6 +967,7 @@ sub AddContrib + my $dn = $1; + my $proj = $solution->AddProject($dn, 'dll', 'contrib', "$subdir/$n"); + $proj->AddReference($postgres); ++ $proj->RemoveFile("$subdir/$n/src/declarative.c") if $n eq 'pg_pathman'; + AdjustContribProj($proj); + push @projects, $proj; + } +@@ -1070,6 +1071,19 @@ sub AddContrib + return; + } + ++sub SubstituteMakefileVariables { ++ local $_ = shift; # Line to substitue ++ my $mf = shift; # Makefile text ++ while (/\$\((\w+)\)/) { ++ my $varname = $1; ++ if ($mf =~ /^$varname\s*=\s*(.*)$/mg) { ++ my $varvalue=$1; ++ s/\$\($varname\)/$varvalue/g; ++ } ++ } ++ return $_; ++} ++ + sub GenerateContribSqlFiles + { + my $n = shift; +@@ -1094,23 +1108,53 @@ sub GenerateContribSqlFiles + substr($l, 0, index($l, '$(addsuffix ')) . substr($l, $i + 1); + } + ++ $l = SubstituteMakefileVariables($l,$mf); + foreach my $d (split /\s+/, $l) + { +- my $in = "$d.in"; +- my $out = "$d"; +- +- if (Solution::IsNewer("contrib/$n/$out", "contrib/$n/$in")) +- { +- print "Building $out from $in (contrib/$n)...\n"; +- my $cont = Project::read_file("contrib/$n/$in"); +- my $dn = $out; +- $dn =~ s/\.sql$//; +- $cont =~ s/MODULE_PATHNAME/\$libdir\/$dn/g; +- my $o; +- open($o, '>', "contrib/$n/$out") +- || croak "Could not write to contrib/$n/$d"; +- print $o $cont; +- close($o); ++ if ( -f "contrib/$n/$d.in" ) { ++ my $in = "$d.in"; ++ my $out = "$d"; ++ if (Solution::IsNewer("contrib/$n/$out", "contrib/$n/$in")) ++ { ++ print "Building $out from $in (contrib/$n)...\n"; ++ my $cont = Project::read_file("contrib/$n/$in"); ++ my $dn = $out; ++ $dn =~ s/\.sql$//; ++ $cont =~ s/MODULE_PATHNAME/\$libdir\/$dn/g; ++ my $o; ++ open($o, '>', "contrib/$n/$out") ++ || croak "Could not write to contrib/$n/$d"; ++ print $o $cont; ++ close($o); ++ } ++ } else { ++ # Search for makefile rule. ++ # For now we do not process rule command and assume ++ # that we should just concatenate all prerequisites ++ # ++ my @prereq = (); ++ my $target; ++ my @rules = $mf =~ /^(\S+)\s*:\s*([^=].*)$/mg; ++ RULE: ++ while (@rules) { ++ $target = SubstituteMakefileVariables(shift @rules,$mf); ++ @prereq = split(/\s+/,SubstituteMakefileVariables(shift @rules,$mf)); ++ last RULE if ($target eq $d); ++ @prereq = (); ++ } ++ croak "Don't know how to build contrib/$n/$d" unless @prereq; ++ if (grep(Solution::IsNewer("contrib/$n/$d","contrib/$n/$_"), ++ @prereq)) { ++ print STDERR "building $d from @prereq by concatentation\n"; ++ my $o; ++ open $o, ">contrib/$n/$d" ++ or croak("Couldn't write to contrib/$n/$d:$!"); ++ for my $in (@prereq) { ++ my $data = Project::read_file("contrib/$n/$in"); ++ print $o $data; ++ } ++ close $o; ++ } + } + } + } diff --git a/patches/REL_16_STABLE-pg_pathman-core.diff b/patches/REL_16_STABLE-pg_pathman-core.diff new file mode 100644 index 00000000..50dad389 --- /dev/null +++ b/patches/REL_16_STABLE-pg_pathman-core.diff @@ -0,0 +1,547 @@ +diff --git a/contrib/Makefile b/contrib/Makefile +index bbf220407b..9a82a2db04 100644 +--- a/contrib/Makefile ++++ b/contrib/Makefile +@@ -34,6 +34,7 @@ SUBDIRS = \ + passwordcheck \ + pg_buffercache \ + pg_freespacemap \ ++ pg_pathman \ + pg_prewarm \ + pg_stat_statements \ + pg_surgery \ +diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c +index 4a2ea4adba..7cadde5499 100644 +--- a/src/backend/access/transam/xact.c ++++ b/src/backend/access/transam/xact.c +@@ -79,7 +79,7 @@ int DefaultXactIsoLevel = XACT_READ_COMMITTED; + int XactIsoLevel = XACT_READ_COMMITTED; + + bool DefaultXactReadOnly = false; +-bool XactReadOnly; ++bool XactReadOnly = false; + + bool DefaultXactDeferrable = false; + bool XactDeferrable; +diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c +index 6b7997465d..5e9e878d3b 100644 +--- a/src/backend/executor/execExprInterp.c ++++ b/src/backend/executor/execExprInterp.c +@@ -1845,6 +1845,16 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) + } + + out: ++ ++ /* ++ * pg_pathman: pass 'tts_tableOid' to result tuple to determine from ++ * which partition the tuple was read ++ */ ++ if (resultslot) ++ { ++ resultslot->tts_tableOid = scanslot ? scanslot->tts_tableOid : ++ (innerslot ? innerslot->tts_tableOid : (outerslot ? outerslot->tts_tableOid : InvalidOid)); ++ } + *isnull = state->resnull; + return state->resvalue; + } +diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c +index 4c5a7bbf62..7d638aa22d 100644 +--- a/src/backend/executor/execMain.c ++++ b/src/backend/executor/execMain.c +@@ -561,6 +561,39 @@ ExecutorRewind(QueryDesc *queryDesc) + } + + ++/* ++ * ExecCheckOneRtePermissions ++ * Check access permissions for one RTE ++ * ++ * Returns true if permissions are adequate. Otherwise, throws an appropriate ++ * error if ereport_on_violation is true, or simply returns false otherwise. ++ * ++ * This function uses pg_pathman due to commit f75cec4fff, see PGPRO-7792 ++ */ ++bool ++ExecCheckOneRtePermissions(RangeTblEntry *rte, RTEPermissionInfo *perminfo, ++ bool ereport_on_violation) ++{ ++ bool result = true; ++ ++ Assert(OidIsValid(perminfo->relid)); ++ Assert(rte->relid == perminfo->relid); ++ ++ result = ExecCheckOneRelPerms(perminfo); ++ ++ if (!result) ++ { ++ if (ereport_on_violation) ++ aclcheck_error(ACLCHECK_NO_PRIV, ++ get_relkind_objtype(get_rel_relkind(perminfo->relid)), ++ get_rel_name(perminfo->relid)); ++ return false; ++ } ++ ++ return result; ++} ++ ++ + /* + * ExecCheckPermissions + * Check access permissions of relations mentioned in a query +@@ -856,6 +889,13 @@ InitPlan(QueryDesc *queryDesc, int eflags) + + estate->es_plannedstmt = plannedstmt; + ++ /* ++ * Fields "es_result_relation_info", "es_original_tuple" are used for ++ * pg_pathman only: ++ */ ++ estate->es_result_relation_info = NULL; ++ estate->es_original_tuple = NULL; ++ + /* + * Next, build the ExecRowMark array from the PlanRowMark(s), if any. + */ +@@ -2873,6 +2913,13 @@ EvalPlanQualStart(EPQState *epqstate, Plan *planTree) + rcestate->es_output_cid = parentestate->es_output_cid; + rcestate->es_queryEnv = parentestate->es_queryEnv; + ++ /* ++ * Fields "es_result_relation_info", "es_original_tuple" are used for ++ * pg_pathman only: ++ */ ++ rcestate->es_result_relation_info = NULL; ++ rcestate->es_original_tuple = NULL; ++ + /* + * ResultRelInfos needed by subplans are initialized from scratch when the + * subplans themselves are initialized. +diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c +index c84caeeaee..2a355607e9 100644 +--- a/src/backend/executor/nodeModifyTable.c ++++ b/src/backend/executor/nodeModifyTable.c +@@ -660,6 +660,13 @@ ExecInitUpdateProjection(ModifyTableState *mtstate, + resultRelInfo->ri_projectNewInfoValid = true; + } + ++void ++PgproExecInitUpdateProjection(ModifyTableState *mtstate, ++ ResultRelInfo *resultRelInfo) ++{ ++ ExecInitUpdateProjection(mtstate, resultRelInfo); ++} ++ + /* + * ExecGetInsertNewTuple + * This prepares a "new" tuple ready to be inserted into given result +@@ -3570,6 +3577,7 @@ ExecModifyTable(PlanState *pstate) + HeapTupleData oldtupdata; + HeapTuple oldtuple; + ItemPointer tupleid; ++ ResultRelInfo *saved_resultRelInfo; + + CHECK_FOR_INTERRUPTS(); + +@@ -3611,6 +3619,8 @@ ExecModifyTable(PlanState *pstate) + context.mtstate = node; + context.epqstate = &node->mt_epqstate; + context.estate = estate; ++ saved_resultRelInfo = estate->es_result_relation_info; ++ estate->es_result_relation_info = NULL; + + /* + * Fetch rows from subplan, and execute the required table modification +@@ -3618,6 +3628,14 @@ ExecModifyTable(PlanState *pstate) + */ + for (;;) + { ++ /* ++ * "es_original_tuple" should contain original modified tuple (new ++ * values of the changed columns plus row identity information such as ++ * CTID) in case tuple planSlot is replaced in pg_pathman to new value ++ * in call "ExecProcNode(subplanstate)". ++ */ ++ estate->es_original_tuple = NULL; ++ + /* + * Reset the per-output-tuple exprcontext. This is needed because + * triggers expect to use that context as workspace. It's a bit ugly +@@ -3651,7 +3669,9 @@ ExecModifyTable(PlanState *pstate) + bool isNull; + Oid resultoid; + +- datum = ExecGetJunkAttribute(context.planSlot, node->mt_resultOidAttno, ++ datum = ExecGetJunkAttribute(estate->es_original_tuple ? ++ estate->es_original_tuple : context.planSlot, ++ node->mt_resultOidAttno, + &isNull); + if (isNull) + { +@@ -3688,6 +3708,8 @@ ExecModifyTable(PlanState *pstate) + if (resultRelInfo->ri_usesFdwDirectModify) + { + Assert(resultRelInfo->ri_projectReturning); ++ /* PartitionRouter does not support foreign data wrappers: */ ++ Assert(estate->es_original_tuple == NULL); + + /* + * A scan slot containing the data that was actually inserted, +@@ -3697,6 +3719,7 @@ ExecModifyTable(PlanState *pstate) + */ + slot = ExecProcessReturning(resultRelInfo, NULL, context.planSlot); + ++ estate->es_result_relation_info = saved_resultRelInfo; + return slot; + } + +@@ -3727,7 +3750,8 @@ ExecModifyTable(PlanState *pstate) + { + /* ri_RowIdAttNo refers to a ctid attribute */ + Assert(AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo)); +- datum = ExecGetJunkAttribute(slot, ++ datum = ExecGetJunkAttribute(estate->es_original_tuple ++ ? estate->es_original_tuple : slot, + resultRelInfo->ri_RowIdAttNo, + &isNull); + +@@ -3775,7 +3799,8 @@ ExecModifyTable(PlanState *pstate) + */ + else if (AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo)) + { +- datum = ExecGetJunkAttribute(slot, ++ datum = ExecGetJunkAttribute(estate->es_original_tuple ++ ? estate->es_original_tuple : slot, + resultRelInfo->ri_RowIdAttNo, + &isNull); + /* shouldn't ever get a null result... */ +@@ -3806,9 +3831,12 @@ ExecModifyTable(PlanState *pstate) + /* Initialize projection info if first time for this table */ + if (unlikely(!resultRelInfo->ri_projectNewInfoValid)) + ExecInitInsertProjection(node, resultRelInfo); +- slot = ExecGetInsertNewTuple(resultRelInfo, context.planSlot); +- slot = ExecInsert(&context, resultRelInfo, slot, +- node->canSetTag, NULL, NULL); ++ /* Do nothing in case tuple was modified in pg_pathman: */ ++ if (!estate->es_original_tuple) ++ slot = ExecGetInsertNewTuple(resultRelInfo, context.planSlot); ++ slot = ExecInsert(&context, estate->es_result_relation_info ? ++ estate->es_result_relation_info : resultRelInfo, ++ slot, node->canSetTag, NULL, NULL); + break; + + case CMD_UPDATE: +@@ -3816,6 +3844,13 @@ ExecModifyTable(PlanState *pstate) + if (unlikely(!resultRelInfo->ri_projectNewInfoValid)) + ExecInitUpdateProjection(node, resultRelInfo); + ++ /* ++ * Do not change the indentation for PostgreSQL code to make it ++ * easier to merge new PostgreSQL changes. ++ */ ++ /* Do nothing in case tuple was modified in pg_pathman: */ ++ if (!estate->es_original_tuple) ++ { + /* + * Make the new tuple by combining plan's output tuple with + * the old tuple being updated. +@@ -3839,14 +3874,19 @@ ExecModifyTable(PlanState *pstate) + slot = ExecGetUpdateNewTuple(resultRelInfo, context.planSlot, + oldSlot); + context.relaction = NULL; ++ } + + /* Now apply the update. */ +- slot = ExecUpdate(&context, resultRelInfo, tupleid, oldtuple, ++ slot = ExecUpdate(&context, estate->es_result_relation_info ? ++ estate->es_result_relation_info : resultRelInfo, ++ tupleid, oldtuple, + slot, node->canSetTag); + break; + + case CMD_DELETE: +- slot = ExecDelete(&context, resultRelInfo, tupleid, oldtuple, ++ slot = ExecDelete(&context, estate->es_result_relation_info ? ++ estate->es_result_relation_info : resultRelInfo, ++ tupleid, oldtuple, + true, false, node->canSetTag, NULL, NULL, NULL); + break; + +@@ -3864,7 +3904,10 @@ ExecModifyTable(PlanState *pstate) + * the work on next call. + */ + if (slot) ++ { ++ estate->es_result_relation_info = saved_resultRelInfo; + return slot; ++ } + } + + /* +@@ -3880,6 +3923,7 @@ ExecModifyTable(PlanState *pstate) + + node->mt_done = true; + ++ estate->es_result_relation_info = saved_resultRelInfo; + return NULL; + } + +@@ -3954,6 +3998,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) + ListCell *l; + int i; + Relation rel; ++ ResultRelInfo *saved_resultRelInfo; + + /* check for unsupported flags */ + Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK))); +@@ -4056,6 +4101,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) + i++; + } + ++ /* ++ * pg_pathman: set "estate->es_result_relation_info" value for take it in ++ * functions partition_filter_begin(), partition_router_begin() ++ */ ++ saved_resultRelInfo = estate->es_result_relation_info; ++ estate->es_result_relation_info = mtstate->resultRelInfo; ++ + /* + * Now we may initialize the subplan. + */ +@@ -4138,6 +4190,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) + } + } + ++ estate->es_result_relation_info = saved_resultRelInfo; ++ + /* + * If this is an inherited update/delete/merge, there will be a junk + * attribute named "tableoid" present in the subplan's targetlist. It +diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c +index 011ec18015..7b4fcb2807 100644 +--- a/src/backend/utils/init/globals.c ++++ b/src/backend/utils/init/globals.c +@@ -25,7 +25,7 @@ + #include "storage/backendid.h" + + +-ProtocolVersion FrontendProtocol; ++ProtocolVersion FrontendProtocol = (ProtocolVersion) 0; + + volatile sig_atomic_t InterruptPending = false; + volatile sig_atomic_t QueryCancelPending = false; +diff --git a/src/include/access/xact.h b/src/include/access/xact.h +index 7d3b9446e6..20030111f4 100644 +--- a/src/include/access/xact.h ++++ b/src/include/access/xact.h +@@ -53,6 +53,8 @@ extern PGDLLIMPORT int XactIsoLevel; + + /* Xact read-only state */ + extern PGDLLIMPORT bool DefaultXactReadOnly; ++ ++#define PGPRO_PATHMAN_AWARE_COPY + extern PGDLLIMPORT bool XactReadOnly; + + /* flag for logging statements in this transaction */ +diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h +index ac02247947..c39ae13a8e 100644 +--- a/src/include/executor/executor.h ++++ b/src/include/executor/executor.h +@@ -208,6 +208,9 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc); + extern void ExecutorEnd(QueryDesc *queryDesc); + extern void standard_ExecutorEnd(QueryDesc *queryDesc); + extern void ExecutorRewind(QueryDesc *queryDesc); ++extern bool ExecCheckOneRtePermissions(RangeTblEntry *rte, ++ RTEPermissionInfo *perminfo, ++ bool ereport_on_violation); + extern bool ExecCheckPermissions(List *rangeTable, + List *rteperminfos, bool ereport_on_violation); + extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation); +@@ -676,5 +679,17 @@ extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *node, + Oid resultoid, + bool missing_ok, + bool update_cache); ++#define PG_HAVE_PGPRO_EXEC_INIT_UPDATE_PROJECTION ++/* ++ * This function is static in vanilla, but pg_pathman wants it exported. ++ * We cannot make it extern with the same name to avoid compilation errors ++ * in timescaledb, which ships it's own static copy of the same function. ++ * So, export ExecInitUpdateProjection with Pgpro prefix. ++ * ++ * The define above helps pg_pathman to expect proper exported symbol ++ * from various versions of pgpro. ++ */ ++extern void PgproExecInitUpdateProjection(ModifyTableState *mtstate, ++ ResultRelInfo *resultRelInfo); + + #endif /* EXECUTOR_H */ +diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h +index 869465d6f8..6bdde351d7 100644 +--- a/src/include/nodes/execnodes.h ++++ b/src/include/nodes/execnodes.h +@@ -638,6 +638,12 @@ typedef struct EState + * es_result_relations in no + * specific order */ + ++ /* These fields was added for compatibility pg_pathman with 14: */ ++ ResultRelInfo *es_result_relation_info; /* currently active array elt */ ++ TupleTableSlot *es_original_tuple; /* original modified tuple (new values ++ * of the changed columns plus row ++ * identity information such as CTID) */ ++ + PartitionDirectory es_partition_directory; /* for PartitionDesc lookup */ + + /* +diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm +index 05548d7c0a..37754370e0 100644 +--- a/src/tools/msvc/Install.pm ++++ b/src/tools/msvc/Install.pm +@@ -30,6 +30,22 @@ my @client_program_files = ( + 'pg_receivewal', 'pg_recvlogical', 'pg_restore', 'psql', + 'reindexdb', 'vacuumdb', @client_contribs); + ++sub SubstituteMakefileVariables ++{ ++ local $_ = shift; # Line to substitue ++ my $mf = shift; # Makefile text ++ while (/\$\((\w+)\)/) ++ { ++ my $varname = $1; ++ if ($mf =~ /^$varname\s*=\s*(.*)$/mg) ++ { ++ my $varvalue=$1; ++ s/\$\($varname\)/$varvalue/g; ++ } ++ } ++ return $_; ++} ++ + sub lcopy + { + my $src = shift; +@@ -580,7 +596,7 @@ sub ParseAndCleanRule + substr($flist, 0, index($flist, '$(addsuffix ')) + . substr($flist, $i + 1); + } +- return $flist; ++ return SubstituteMakefileVariables($flist, $mf); + } + + sub CopyIncludeFiles +diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm +index 6a79a0e037..93696f53ae 100644 +--- a/src/tools/msvc/Mkvcbuild.pm ++++ b/src/tools/msvc/Mkvcbuild.pm +@@ -40,7 +40,7 @@ my @contrib_uselibpq = (); + my @contrib_uselibpgport = (); + my @contrib_uselibpgcommon = (); + my $contrib_extralibs = { 'libpq_pipeline' => ['ws2_32.lib'] }; +-my $contrib_extraincludes = {}; ++my $contrib_extraincludes = { 'pg_pathman' => ['contrib/pg_pathman/src/include'] }; + my $contrib_extrasource = {}; + my @contrib_excludes = ( + 'bool_plperl', 'commit_ts', +@@ -980,6 +980,7 @@ sub AddContrib + my $dn = $1; + my $proj = $solution->AddProject($dn, 'dll', 'contrib', "$subdir/$n"); + $proj->AddReference($postgres); ++ $proj->RemoveFile("$subdir/$n/src/declarative.c") if $n eq 'pg_pathman'; + AdjustContribProj($proj); + push @projects, $proj; + } +@@ -1083,6 +1084,22 @@ sub AddContrib + return; + } + ++sub SubstituteMakefileVariables ++{ ++ local $_ = shift; # Line to substitue ++ my $mf = shift; # Makefile text ++ while (/\$\((\w+)\)/) ++ { ++ my $varname = $1; ++ if ($mf =~ /^$varname\s*=\s*(.*)$/mg) ++ { ++ my $varvalue=$1; ++ s/\$\($varname\)/$varvalue/g; ++ } ++ } ++ return $_; ++} ++ + sub GenerateContribSqlFiles + { + my $n = shift; +@@ -1107,23 +1124,59 @@ sub GenerateContribSqlFiles + substr($l, 0, index($l, '$(addsuffix ')) . substr($l, $i + 1); + } + ++ $l = SubstituteMakefileVariables($l,$mf); + foreach my $d (split /\s+/, $l) + { +- my $in = "$d.in"; +- my $out = "$d"; +- +- if (Solution::IsNewer("contrib/$n/$out", "contrib/$n/$in")) ++ if ( -f "contrib/$n/$d.in" ) ++ { ++ my $in = "$d.in"; ++ my $out = "$d"; ++ if (Solution::IsNewer("contrib/$n/$out", "contrib/$n/$in")) ++ { ++ print "Building $out from $in (contrib/$n)...\n"; ++ my $cont = Project::read_file("contrib/$n/$in"); ++ my $dn = $out; ++ $dn =~ s/\.sql$//; ++ $cont =~ s/MODULE_PATHNAME/\$libdir\/$dn/g; ++ my $o; ++ open($o, '>', "contrib/$n/$out") ++ || croak "Could not write to contrib/$n/$d"; ++ print $o $cont; ++ close($o); ++ } ++ } ++ else + { +- print "Building $out from $in (contrib/$n)...\n"; +- my $cont = Project::read_file("contrib/$n/$in"); +- my $dn = $out; +- $dn =~ s/\.sql$//; +- $cont =~ s/MODULE_PATHNAME/\$libdir\/$dn/g; +- my $o; +- open($o, '>', "contrib/$n/$out") +- || croak "Could not write to contrib/$n/$d"; +- print $o $cont; +- close($o); ++ # Search for makefile rule. ++ # For now we do not process rule command and assume ++ # that we should just concatenate all prerequisites ++ # ++ my @prereq = (); ++ my $target; ++ my @rules = $mf =~ /^(\S+)\s*:\s*([^=].*)$/mg; ++ RULE: ++ while (@rules) ++ { ++ $target = SubstituteMakefileVariables(shift @rules,$mf); ++ @prereq = split(/\s+/,SubstituteMakefileVariables(shift @rules,$mf)); ++ last RULE if ($target eq $d); ++ @prereq = (); ++ } ++ croak "Don't know how to build contrib/$n/$d" unless @prereq; ++ if (grep(Solution::IsNewer("contrib/$n/$d","contrib/$n/$_"), ++ @prereq)) ++ { ++ print STDERR "building $d from @prereq by concatentation\n"; ++ my $o; ++ open $o, ">contrib/$n/$d" ++ or croak("Couldn't write to contrib/$n/$d:$!"); ++ for my $in (@prereq) ++ { ++ my $data = Project::read_file("contrib/$n/$in"); ++ print $o $data; ++ } ++ close $o; ++ } + } + } + } diff --git a/run_tests.sh b/run_tests.sh index 8f06d39c..2e2edc6f 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -17,9 +17,26 @@ status=0 export PGPORT=55435 export VIRTUAL_ENV_DISABLE_PROMPT=1 -# rebuild PostgreSQL with cassert + valgrind support +PATHMAN_DIR=$PWD + +# indicator of using cassert + valgrind support +USE_ASSERT_VALGRIND=false if [ "$LEVEL" = "hardcore" ] || \ [ "$LEVEL" = "nightmare" ]; then + USE_ASSERT_VALGRIND=true +fi + +# indicator of using special patch for vanilla +if [ "$(printf '%s\n' "14" "$PG_VERSION" | sort -V | head -n1)" = "$PG_VERSION" ]; then + USE_PATH=false +else + #patch version 14 and newer + USE_PATH=true +fi + +# rebuild PostgreSQL with cassert + valgrind support +if [ "$USE_ASSERT_VALGRIND" = true ] || \ + [ "$USE_PATH" = true ]; then set -e @@ -40,15 +57,28 @@ if [ "$LEVEL" = "hardcore" ] || \ cd $CUSTOM_PG_SRC - # enable Valgrind support - sed -i.bak "s/\/* #define USE_VALGRIND *\//#define USE_VALGRIND/g" src/include/pg_config_manual.h - - # enable additional options - ./configure \ - CFLAGS='-Og -ggdb3 -fno-omit-frame-pointer' \ - --enable-cassert \ - --prefix=$CUSTOM_PG_BIN \ - --quiet + if [ "$USE_PATH" = true ]; then + # apply the patch + patch -p1 < $PATHMAN_DIR/patches/REL_${PG_VERSION%.*}_STABLE-pg_pathman-core.diff + fi + + if [ "$USE_ASSERT_VALGRIND" = true ]; then + # enable Valgrind support + sed -i.bak "s/\/* #define USE_VALGRIND *\//#define USE_VALGRIND/g" src/include/pg_config_manual.h + + # enable additional options + ./configure \ + CFLAGS='-Og -ggdb3 -fno-omit-frame-pointer' \ + --enable-cassert \ + --prefix=$CUSTOM_PG_BIN \ + --quiet + else + # without additional options + ./configure \ + --enable-cassert \ + --prefix=$CUSTOM_PG_BIN \ + --quiet + fi # build & install PG time make -s -j$(nproc) && make -s install diff --git a/specs/for_update.spec b/specs/for_update.spec index f7a8f758..c18cd4f8 100644 --- a/specs/for_update.spec +++ b/specs/for_update.spec @@ -19,8 +19,6 @@ step "s1_r" { rollback; } step "s1_update" { update test_tbl set id = 2 where id = 1; } session "s2" -step "s2_b" { begin; } -step "s2_c" { commit; } step "s2_select_locked" { select * from test_tbl where id = 1 for share; } step "s2_select" { select * from test_tbl where id = 1; } diff --git a/specs/insert_nodes.spec b/specs/insert_nodes.spec index 3bb67746..a5d0c7f9 100644 --- a/specs/insert_nodes.spec +++ b/specs/insert_nodes.spec @@ -17,20 +17,19 @@ session "s1" step "s1b" { BEGIN; } step "s1_insert_150" { INSERT INTO range_rel SELECT generate_series(1, 150); } step "s1_insert_300" { INSERT INTO range_rel SELECT generate_series(151, 300); } -step "s1_show_partitions" { SELECT c.consrc FROM pg_inherits i LEFT JOIN pg_constraint c +step "s1_show_partitions" { SELECT pg_get_constraintdef(c.oid) FROM pg_inherits i LEFT JOIN pg_constraint c ON c.conrelid = i.inhrelid - WHERE i.inhparent = 'range_rel'::regclass + WHERE i.inhparent = 'range_rel'::regclass AND c.contype = 'c' ORDER BY c.oid; } step "s1r" { ROLLBACK; } -step "s1c" { COMMIT; } session "s2" step "s2b" { BEGIN; } step "s2_insert_150" { INSERT INTO range_rel SELECT generate_series(1, 150); } step "s2_insert_300" { INSERT INTO range_rel SELECT generate_series(151, 300); } -step "s2_show_partitions" { SELECT c.consrc FROM pg_inherits i LEFT JOIN pg_constraint c +step "s2_show_partitions" { SELECT pg_get_constraintdef(c.oid) FROM pg_inherits i LEFT JOIN pg_constraint c ON c.conrelid = i.inhrelid - WHERE i.inhparent = 'range_rel'::regclass + WHERE i.inhparent = 'range_rel'::regclass AND c.contype = 'c' ORDER BY c.oid; } step "s2r" { ROLLBACK; } step "s2c" { COMMIT; } diff --git a/specs/rollback_on_create_partitions.spec b/specs/rollback_on_create_partitions.spec index a24c2897..806e6072 100644 --- a/specs/rollback_on_create_partitions.spec +++ b/specs/rollback_on_create_partitions.spec @@ -22,7 +22,7 @@ step "rollback_a" { ROLLBACK TO SAVEPOINT a; } step "savepoint_b" { SAVEPOINT b; } step "rollback_b" { ROLLBACK TO SAVEPOINT b; } step "savepoint_c" { SAVEPOINT c; } -step "show_rel" { EXPLAIN (COSTS OFF) SELECT * FROM range_rel; } +step "show_rel" { SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; } permutation "begin" "insert_data" "create_partitions" "show_rel" "rollback" "show_rel" diff --git a/sql/pathman_CVE-2020-14350.sql b/sql/pathman_CVE-2020-14350.sql index 877f3280..07daa617 100644 --- a/sql/pathman_CVE-2020-14350.sql +++ b/sql/pathman_CVE-2020-14350.sql @@ -8,23 +8,24 @@ DROP FUNCTION IF EXISTS _partition_data_concurrent(oid,integer); DROP FUNCTION IF EXISTS create_single_range_partition(TEXT,ANYELEMENT,ANYELEMENT,TEXT); DROP TABLE IF EXISTS test1 CASCADE; DROP TABLE IF EXISTS test2 CASCADE; -DROP ROLE IF EXISTS regress_hacker; +DROP ROLE IF EXISTS pathman_regress_hacker; SET client_min_messages = 'notice'; +GRANT CREATE ON SCHEMA public TO PUBLIC; CREATE EXTENSION pg_pathman; -CREATE ROLE regress_hacker LOGIN; +CREATE ROLE pathman_regress_hacker LOGIN; -- Test 1 RESET ROLE; -ALTER ROLE regress_hacker NOSUPERUSER; +ALTER ROLE pathman_regress_hacker NOSUPERUSER; -SET ROLE regress_hacker; +SET ROLE pathman_regress_hacker; SHOW is_superuser; CREATE FUNCTION _partition_data_concurrent(relation oid, p_limit INT, OUT p_total BIGINT) RETURNS bigint AS $$ BEGIN - ALTER ROLE regress_hacker SUPERUSER; + ALTER ROLE pathman_regress_hacker SUPERUSER; SELECT _partition_data_concurrent(relation, NULL::text, NULL::text, p_limit) INTO p_total; END $$ LANGUAGE plpgsql; @@ -38,20 +39,20 @@ SELECT partition_table_concurrently('test1', 10, 1); SELECT pg_sleep(1); -- Test result (must be 'off') -SET ROLE regress_hacker; +SET ROLE pathman_regress_hacker; SHOW is_superuser; -- Test 2 RESET ROLE; -ALTER ROLE regress_hacker NOSUPERUSER; +ALTER ROLE pathman_regress_hacker NOSUPERUSER; -SET ROLE regress_hacker; +SET ROLE pathman_regress_hacker; SHOW is_superuser; CREATE FUNCTION create_single_range_partition(parent_relid TEXT, start_value ANYELEMENT, end_value ANYELEMENT, partition_name TEXT) RETURNS REGCLASS AS $$ BEGIN - ALTER ROLE regress_hacker SUPERUSER; + ALTER ROLE pathman_regress_hacker SUPERUSER; RETURN create_single_range_partition(parent_relid, start_value, end_value, partition_name, NULL::text); END $$ LANGUAGE plpgsql; @@ -63,7 +64,7 @@ SELECT create_range_partitions('test2', 'i', 0, 1); INSERT INTO test2 values(1); -- Test result (must be 'off') -SET ROLE regress_hacker; +SET ROLE pathman_regress_hacker; SHOW is_superuser; -- Cleanup @@ -72,6 +73,6 @@ DROP FUNCTION _partition_data_concurrent(oid,integer); DROP FUNCTION create_single_range_partition(TEXT,ANYELEMENT,ANYELEMENT,TEXT); DROP TABLE test1 CASCADE; DROP TABLE test2 CASCADE; -DROP ROLE regress_hacker; +DROP ROLE pathman_regress_hacker; DROP EXTENSION pg_pathman; diff --git a/sql/pathman_array_qual.sql b/sql/pathman_array_qual.sql index 84327359..9f1b0c1e 100644 --- a/sql/pathman_array_qual.sql +++ b/sql/pathman_array_qual.sql @@ -427,5 +427,6 @@ DEALLOCATE q; -DROP SCHEMA array_qual CASCADE; +DROP TABLE array_qual.test CASCADE; +DROP SCHEMA array_qual; DROP EXTENSION pg_pathman; diff --git a/sql/pathman_basic.sql b/sql/pathman_basic.sql index 403424f5..478935c5 100644 --- a/sql/pathman_basic.sql +++ b/sql/pathman_basic.sql @@ -563,6 +563,17 @@ INSERT INTO test.mixinh_child1 VALUES (1); SELECT * FROM test.mixinh_child1; SELECT * FROM test.mixinh_parent; -DROP SCHEMA test CASCADE; +DROP TABLE test.hash_rel CASCADE; +DROP TABLE test.index_on_childs CASCADE; +DROP TABLE test.mixinh_child1 CASCADE; +DROP TABLE test.mixinh_parent CASCADE; +DROP TABLE test.num_range_rel CASCADE; +DROP TABLE test.hash_rel_wrong CASCADE; +DROP TABLE test.range_rel CASCADE; +DROP TABLE test.range_rel_archive CASCADE; +DROP TABLE test.special_case_1_ind_o_s CASCADE; +DROP TABLE test.range_rel_test1 CASCADE; +DROP TABLE test.range_rel_test2 CASCADE; +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/sql/pathman_bgw.sql b/sql/pathman_bgw.sql index 28f922e6..74239e99 100644 --- a/sql/pathman_bgw.sql +++ b/sql/pathman_bgw.sql @@ -145,5 +145,5 @@ DROP TABLE test_bgw.conc_part CASCADE; -DROP SCHEMA test_bgw CASCADE; +DROP SCHEMA test_bgw; DROP EXTENSION pg_pathman; diff --git a/sql/pathman_cache_pranks.sql b/sql/pathman_cache_pranks.sql index 782ef7f0..e3fe00d9 100644 --- a/sql/pathman_cache_pranks.sql +++ b/sql/pathman_cache_pranks.sql @@ -48,6 +48,75 @@ SELECT create_hash_partitions('part_test', 'val', 2, partition_names := ARRAY[]: DROP TABLE part_test CASCADE; -- +-- +-- PGPRO-7870 +-- Added error for case executing prepared query after DROP/CREATE EXTENSION. +-- +-- DROP/CREATE extension +CREATE TABLE part_test(a INT4 NOT NULL, b INT4); +PREPARE q(int4) AS SELECT * FROM part_test WHERE a > ALL (array[$1, 898]); +SELECT create_range_partitions('part_test', 'a', 1, 100, 10); + +EXECUTE q(1); +EXECUTE q(1); +EXECUTE q(1); +EXECUTE q(1); +EXECUTE q(1); +EXECUTE q(1); + +DROP EXTENSION pg_pathman; +CREATE EXTENSION pg_pathman; + +EXECUTE q(1); + +DEALLOCATE q; +DROP TABLE part_test CASCADE; + +-- DROP/CREATE disabled extension +CREATE TABLE part_test(a INT4 NOT NULL, b INT4); +PREPARE q(int4) AS SELECT * FROM part_test WHERE a > ALL (array[$1, 898]); +SELECT create_range_partitions('part_test', 'a', 1, 100, 10); + +EXECUTE q(1); +EXECUTE q(1); +EXECUTE q(1); +EXECUTE q(1); +EXECUTE q(1); +EXECUTE q(1); + +SET pg_pathman.enable = f; +DROP EXTENSION pg_pathman; +CREATE EXTENSION pg_pathman; +SET pg_pathman.enable = t; + +EXECUTE q(1); + +DEALLOCATE q; +DROP TABLE part_test CASCADE; + +-- DROP/CREATE extension in autonomous transaction +CREATE TABLE part_test(a INT4 NOT NULL, b INT4); +PREPARE q(int4) AS SELECT * FROM part_test WHERE a > ALL (array[$1, 198]); +SELECT create_range_partitions('part_test', 'a', 1, 100, 2); + +EXECUTE q(1); +EXECUTE q(1); +EXECUTE q(1); +EXECUTE q(1); +EXECUTE q(1); +EXECUTE q(1); + +BEGIN; + BEGIN AUTONOMOUS; + DROP EXTENSION pg_pathman; + CREATE EXTENSION pg_pathman; + COMMIT; +COMMIT; + +EXECUTE q(1); + +DEALLOCATE q; +DROP TABLE part_test CASCADE; -- finalize DROP EXTENSION pg_pathman; diff --git a/sql/pathman_calamity.sql b/sql/pathman_calamity.sql index b49d061c..ecc2c30f 100644 --- a/sql/pathman_calamity.sql +++ b/sql/pathman_calamity.sql @@ -345,7 +345,12 @@ SELECT merge_range_partitions('calamity.merge_test_a_1', DROP TABLE calamity.merge_test_a,calamity.merge_test_b CASCADE; -DROP SCHEMA calamity CASCADE; +DROP DOMAIN calamity.test_domain; +DROP TABLE calamity.part_test CASCADE; +DROP TABLE calamity.part_ok CASCADE; +DROP TABLE calamity.hash_two_times CASCADE; +DROP TABLE calamity.to_be_disabled CASCADE; +DROP SCHEMA calamity; DROP EXTENSION pg_pathman; @@ -383,9 +388,11 @@ CREATE EXTENSION pg_pathman; /* check that cache loading is lazy */ CREATE TABLE calamity.test_pathman_cache_stats(val NUMERIC NOT NULL); SELECT create_range_partitions('calamity.test_pathman_cache_stats', 'val', 1, 10, 10); -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ DROP TABLE calamity.test_pathman_cache_stats CASCADE; -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ /* Change this setting for code coverage */ SET pg_pathman.enable_bounds_cache = false; @@ -394,9 +401,11 @@ SET pg_pathman.enable_bounds_cache = false; CREATE TABLE calamity.test_pathman_cache_stats(val NUMERIC NOT NULL); SELECT create_range_partitions('calamity.test_pathman_cache_stats', 'val', 1, 10, 10); EXPLAIN (COSTS OFF) SELECT * FROM calamity.test_pathman_cache_stats; -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ DROP TABLE calamity.test_pathman_cache_stats CASCADE; -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ /* Restore this GUC */ SET pg_pathman.enable_bounds_cache = true; @@ -405,21 +414,26 @@ SET pg_pathman.enable_bounds_cache = true; CREATE TABLE calamity.test_pathman_cache_stats(val NUMERIC NOT NULL); SELECT create_range_partitions('calamity.test_pathman_cache_stats', 'val', 1, 10, 10); EXPLAIN (COSTS OFF) SELECT * FROM calamity.test_pathman_cache_stats; -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ DROP TABLE calamity.test_pathman_cache_stats CASCADE; -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ /* check that parents cache has been flushed after partition was dropped */ CREATE TABLE calamity.test_pathman_cache_stats(val NUMERIC NOT NULL); SELECT create_range_partitions('calamity.test_pathman_cache_stats', 'val', 1, 10, 10); EXPLAIN (COSTS OFF) SELECT * FROM calamity.test_pathman_cache_stats; -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ SELECT drop_range_partition('calamity.test_pathman_cache_stats_1'); -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ DROP TABLE calamity.test_pathman_cache_stats CASCADE; -SELECT context, entries FROM pathman_cache_stats ORDER BY context; /* OK */ +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* OK */ -DROP SCHEMA calamity CASCADE; +DROP SCHEMA calamity; DROP EXTENSION pg_pathman; @@ -458,5 +472,5 @@ EXPLAIN (COSTS OFF) SELECT * FROM calamity.survivor; /* OK */ DROP TABLE calamity.survivor CASCADE; -DROP SCHEMA calamity CASCADE; +DROP SCHEMA calamity; DROP EXTENSION pg_pathman; diff --git a/sql/pathman_callbacks.sql b/sql/pathman_callbacks.sql index 65b729d9..096a55ad 100644 --- a/sql/pathman_callbacks.sql +++ b/sql/pathman_callbacks.sql @@ -144,5 +144,8 @@ ORDER BY range_min::INT4; DROP TABLE callbacks.abc CASCADE; -DROP SCHEMA callbacks CASCADE; +DROP FUNCTION callbacks.abc_on_part_created_callback(jsonb); +DROP FUNCTION public.dummy_cb(jsonb); +DROP FUNCTION callbacks.rotation_callback(jsonb); +DROP SCHEMA callbacks; DROP EXTENSION pg_pathman CASCADE; diff --git a/sql/pathman_column_type.sql b/sql/pathman_column_type.sql index 685643fd..d3f16107 100644 --- a/sql/pathman_column_type.sql +++ b/sql/pathman_column_type.sql @@ -20,7 +20,8 @@ SELECT create_range_partitions('test_column_type.test', 'val', 1, 10, 10); /* make sure that bounds and dispatch info has been cached */ SELECT * FROM test_column_type.test; -SELECT context, entries FROM pathman_cache_stats ORDER BY context; +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* * Get parsed and analyzed expression. @@ -45,7 +46,8 @@ DROP FUNCTION get_cached_partition_cooked_key(REGCLASS); /* make sure that everything works properly */ SELECT * FROM test_column_type.test; -SELECT context, entries FROM pathman_cache_stats ORDER BY context; +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* check insert dispatching */ INSERT INTO test_column_type.test VALUES (1); @@ -65,21 +67,24 @@ SELECT create_hash_partitions('test_column_type.test', 'id', 5); /* make sure that bounds and dispatch info has been cached */ SELECT * FROM test_column_type.test; -SELECT context, entries FROM pathman_cache_stats ORDER BY context; +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* change column's type (should NOT work) */ ALTER TABLE test_column_type.test ALTER id TYPE NUMERIC; /* make sure that everything works properly */ SELECT * FROM test_column_type.test; -SELECT context, entries FROM pathman_cache_stats ORDER BY context; +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* change column's type (should flush caches) */ ALTER TABLE test_column_type.test ALTER val TYPE NUMERIC; /* make sure that everything works properly */ SELECT * FROM test_column_type.test; -SELECT context, entries FROM pathman_cache_stats ORDER BY context; +SELECT context, entries FROM pathman_cache_stats + WHERE context != 'partition status cache' ORDER BY context; /* check insert dispatching */ INSERT INTO test_column_type.test VALUES (1); @@ -89,5 +94,5 @@ SELECT drop_partitions('test_column_type.test'); DROP TABLE test_column_type.test CASCADE; -DROP SCHEMA test_column_type CASCADE; +DROP SCHEMA test_column_type; DROP EXTENSION pg_pathman; diff --git a/sql/pathman_cte.sql b/sql/pathman_cte.sql index 5a695cbb..594c6db7 100644 --- a/sql/pathman_cte.sql +++ b/sql/pathman_cte.sql @@ -157,5 +157,6 @@ SELECT * FROM test; -DROP SCHEMA test_cte CASCADE; +DROP TABLE test_cte.recursive_cte_test_tbl CASCADE; +DROP SCHEMA test_cte; DROP EXTENSION pg_pathman; diff --git a/sql/pathman_declarative.sql b/sql/pathman_declarative.sql index 864e3af8..eb12c295 100644 --- a/sql/pathman_declarative.sql +++ b/sql/pathman_declarative.sql @@ -39,6 +39,12 @@ CREATE TABLE test.r4 PARTITION OF test.range_rel FOR VALUES FROM ('2015-06-01') TO ('2016-01-01'); \d+ test.r4; -DROP SCHEMA test CASCADE; +/* Note: PG-10 doesn't support ATTACH PARTITION ... DEFAULT */ +ALTER TABLE IF EXISTS test.nonexistent_table ATTACH PARTITION baz FOR VALUES IN (42); +ALTER TABLE IF EXISTS test.nonexistent_table DETACH PARTITION baz; + +DROP TABLE test.r2 CASCADE; +DROP TABLE test.range_rel CASCADE; +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/sql/pathman_domains.sql b/sql/pathman_domains.sql index 4793c6f8..105b2399 100644 --- a/sql/pathman_domains.sql +++ b/sql/pathman_domains.sql @@ -41,5 +41,7 @@ SELECT * FROM pathman_partition_list ORDER BY "partition"::TEXT; -DROP SCHEMA domains CASCADE; +DROP TABLE domains.dom_table CASCADE; +DROP DOMAIN domains.dom_test CASCADE; +DROP SCHEMA domains; DROP EXTENSION pg_pathman CASCADE; diff --git a/sql/pathman_dropped_cols.sql b/sql/pathman_dropped_cols.sql index 0ae16c8a..2a128df2 100644 --- a/sql/pathman_dropped_cols.sql +++ b/sql/pathman_dropped_cols.sql @@ -96,9 +96,9 @@ EXECUTE getbyroot(2); -- errors usually start here EXECUTE getbyroot(2); EXECUTE getbyroot(2); -EXPLAIN EXECUTE getbyroot(2); +EXPLAIN (COSTS OFF) EXECUTE getbyroot(2); DEALLOCATE getbyroot; DROP TABLE root_dict CASCADE; -DROP SCHEMA dropped_cols CASCADE; +DROP SCHEMA dropped_cols; DROP EXTENSION pg_pathman; diff --git a/sql/pathman_expressions.sql b/sql/pathman_expressions.sql index ed05be79..bf29f896 100644 --- a/sql/pathman_expressions.sql +++ b/sql/pathman_expressions.sql @@ -178,5 +178,9 @@ INSERT INTO test_exprs.range_rel_6 (dt, txt) VALUES ('2020-01-01'::DATE, md5('as SELECT COUNT(*) FROM test_exprs.range_rel_6; EXPLAIN (COSTS OFF) SELECT * FROM test_exprs.range_rel WHERE (AGE(dt, '2000-01-01'::DATE)) = '18 years'::interval; -DROP SCHEMA test_exprs CASCADE; +DROP TABLE test_exprs.canary CASCADE; +DROP TABLE test_exprs.canary_copy CASCADE; +DROP TABLE test_exprs.range_rel CASCADE; +DROP TABLE test_exprs.hash_rel CASCADE; +DROP SCHEMA test_exprs; DROP EXTENSION pg_pathman; diff --git a/sql/pathman_foreign_keys.sql b/sql/pathman_foreign_keys.sql index 1ec1b766..74dee25f 100644 --- a/sql/pathman_foreign_keys.sql +++ b/sql/pathman_foreign_keys.sql @@ -52,5 +52,7 @@ DROP TABLE fkeys.messages, fkeys.replies CASCADE; -DROP SCHEMA fkeys CASCADE; +DROP TABLE fkeys.test_fkey CASCADE; +DROP TABLE fkeys.test_ref CASCADE; +DROP SCHEMA fkeys; DROP EXTENSION pg_pathman CASCADE; diff --git a/sql/pathman_gaps.sql b/sql/pathman_gaps.sql index 55c9a16d..129b210c 100644 --- a/sql/pathman_gaps.sql +++ b/sql/pathman_gaps.sql @@ -137,5 +137,9 @@ EXPLAIN (COSTS OFF) SELECT * FROM gaps.test_4 WHERE val >= 51; -DROP SCHEMA gaps CASCADE; +DROP TABLE gaps.test_1 CASCADE; +DROP TABLE gaps.test_2 CASCADE; +DROP TABLE gaps.test_3 CASCADE; +DROP TABLE gaps.test_4 CASCADE; +DROP SCHEMA gaps; DROP EXTENSION pg_pathman; diff --git a/sql/pathman_hashjoin.sql b/sql/pathman_hashjoin.sql index 2c3654d4..620dee5f 100644 --- a/sql/pathman_hashjoin.sql +++ b/sql/pathman_hashjoin.sql @@ -49,6 +49,8 @@ JOIN test.range_rel j2 on j2.id = j1.id JOIN test.num_range_rel j3 on j3.id = j1.id WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; -DROP SCHEMA test CASCADE; +DROP TABLE test.num_range_rel CASCADE; +DROP TABLE test.range_rel CASCADE; +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/sql/pathman_inserts.sql b/sql/pathman_inserts.sql index c8c6439d..aa5b6c1c 100644 --- a/sql/pathman_inserts.sql +++ b/sql/pathman_inserts.sql @@ -223,5 +223,9 @@ DROP TABLE test_inserts.special_2; DROP TABLE test_inserts.test_special_only CASCADE; -DROP SCHEMA test_inserts CASCADE; +DROP TABLE test_inserts.storage CASCADE; +DROP FUNCTION test_inserts.set_triggers(jsonb); +DROP FUNCTION test_inserts.print_cols_before_change(); +DROP FUNCTION test_inserts.print_cols_after_change(); +DROP SCHEMA test_inserts; DROP EXTENSION pg_pathman CASCADE; diff --git a/sql/pathman_interval.sql b/sql/pathman_interval.sql index f2933ab0..3a457e7a 100644 --- a/sql/pathman_interval.sql +++ b/sql/pathman_interval.sql @@ -168,5 +168,5 @@ DROP TABLE test_interval.abc CASCADE; -DROP SCHEMA test_interval CASCADE; +DROP SCHEMA test_interval; DROP EXTENSION pg_pathman; diff --git a/sql/pathman_join_clause.sql b/sql/pathman_join_clause.sql index 3a0a655f..aa30b0b8 100644 --- a/sql/pathman_join_clause.sql +++ b/sql/pathman_join_clause.sql @@ -106,6 +106,11 @@ WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); -DROP SCHEMA test CASCADE; +DROP TABLE test.child CASCADE; +DROP TABLE test.child_nopart CASCADE; +DROP TABLE test.mytbl CASCADE; +DROP TABLE test.fk CASCADE; +DROP TABLE test.parent CASCADE; +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/sql/pathman_lateral.sql b/sql/pathman_lateral.sql index d287c051..d5def38c 100644 --- a/sql/pathman_lateral.sql +++ b/sql/pathman_lateral.sql @@ -45,5 +45,6 @@ set enable_mergejoin = on; -DROP SCHEMA test_lateral CASCADE; +DROP TABLE test_lateral.data CASCADE; +DROP SCHEMA test_lateral; DROP EXTENSION pg_pathman; diff --git a/sql/pathman_mergejoin.sql b/sql/pathman_mergejoin.sql index 05de4ba2..d1084375 100644 --- a/sql/pathman_mergejoin.sql +++ b/sql/pathman_mergejoin.sql @@ -48,6 +48,9 @@ SET enable_hashjoin = OFF; SET enable_nestloop = OFF; SET enable_mergejoin = ON; +SET enable_indexscan = ON; +SET enable_seqscan = OFF; + EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel j1 JOIN test.range_rel j2 on j2.id = j1.id @@ -56,7 +59,10 @@ WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; SET enable_hashjoin = ON; SET enable_nestloop = ON; +SET enable_seqscan = ON; -DROP SCHEMA test CASCADE; +DROP TABLE test.num_range_rel CASCADE; +DROP TABLE test.range_rel CASCADE; +DROP SCHEMA test; DROP EXTENSION pg_pathman; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/sql/pathman_only.sql b/sql/pathman_only.sql index 53ef6a9a..68dc4ca1 100644 --- a/sql/pathman_only.sql +++ b/sql/pathman_only.sql @@ -3,13 +3,31 @@ * NOTE: This test behaves differenly on PgPro * --------------------------------------------- * - * Since 12 (608b167f9f), CTEs which are scanned once are no longer an - * optimization fence, which changes practically all plans here. There is + * -------------------- + * pathman_only_1.sql + * -------------------- + * Since 608b167f9f in PostgreSQL 12, CTEs which are scanned once are no longer + * an optimization fence, which changes practically all plans here. There is * an option to forcibly make them MATERIALIZED, but we also need to run tests * on older versions, so create pathman_only_1.out instead. * - * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, - * now it includes aliases for inherited tables. + * -------------------- + * pathman_only_2.sql + * -------------------- + * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13, output of EXPLAIN was + * changed, now it includes aliases for inherited tables. + * + * -------------------- + * pathman_only_3.sql + * -------------------- + * Since a5fc46414de in PostgreSQL 16, the order of the operands was changed, + * which affected the output of the "Prune by" in EXPLAIN. + * + * -------------------- + * pathman_only_4.sql + * -------------------- + * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was + * changed, now it displays SubPlan nodes and output parameters. */ \set VERBOSITY terse @@ -74,5 +92,6 @@ WHERE val = (SELECT val FROM ONLY test_only.from_only_test -DROP SCHEMA test_only CASCADE; +DROP TABLE test_only.from_only_test CASCADE; +DROP SCHEMA test_only; DROP EXTENSION pg_pathman; diff --git a/sql/pathman_param_upd_del.sql b/sql/pathman_param_upd_del.sql index f4e42a41..0f3030e7 100644 --- a/sql/pathman_param_upd_del.sql +++ b/sql/pathman_param_upd_del.sql @@ -45,5 +45,6 @@ EXPLAIN (COSTS OFF) EXECUTE del(11); DEALLOCATE del; -DROP SCHEMA param_upd_del CASCADE; +DROP TABLE param_upd_del.test CASCADE; +DROP SCHEMA param_upd_del; DROP EXTENSION pg_pathman; diff --git a/sql/pathman_permissions.sql b/sql/pathman_permissions.sql index 3a234676..3e2cf92a 100644 --- a/sql/pathman_permissions.sql +++ b/sql/pathman_permissions.sql @@ -4,137 +4,137 @@ SET search_path = 'public'; CREATE EXTENSION pg_pathman; CREATE SCHEMA permissions; -CREATE ROLE user1 LOGIN; -CREATE ROLE user2 LOGIN; +CREATE ROLE pathman_user1 LOGIN; +CREATE ROLE pathman_user2 LOGIN; -GRANT USAGE, CREATE ON SCHEMA permissions TO user1; -GRANT USAGE, CREATE ON SCHEMA permissions TO user2; +GRANT USAGE, CREATE ON SCHEMA permissions TO pathman_user1; +GRANT USAGE, CREATE ON SCHEMA permissions TO pathman_user2; /* Switch to #1 */ -SET ROLE user1; -CREATE TABLE permissions.user1_table(id serial, a int); -INSERT INTO permissions.user1_table SELECT g, g FROM generate_series(1, 20) as g; +SET ROLE pathman_user1; +CREATE TABLE permissions.pathman_user1_table(id serial, a int); +INSERT INTO permissions.pathman_user1_table SELECT g, g FROM generate_series(1, 20) as g; /* Should fail (can't SELECT) */ -SET ROLE user2; +SET ROLE pathman_user2; DO $$ BEGIN - SELECT create_range_partitions('permissions.user1_table', 'id', 1, 10, 2); + SELECT create_range_partitions('permissions.pathman_user1_table', 'id', 1, 10, 2); EXCEPTION WHEN insufficient_privilege THEN RAISE NOTICE 'Insufficient priviliges'; END$$; -/* Grant SELECT to user2 */ -SET ROLE user1; -GRANT SELECT ON permissions.user1_table TO user2; +/* Grant SELECT to pathman_user2 */ +SET ROLE pathman_user1; +GRANT SELECT ON permissions.pathman_user1_table TO pathman_user2; /* Should fail (don't own parent) */ -SET ROLE user2; +SET ROLE pathman_user2; DO $$ BEGIN - SELECT create_range_partitions('permissions.user1_table', 'id', 1, 10, 2); + SELECT create_range_partitions('permissions.pathman_user1_table', 'id', 1, 10, 2); EXCEPTION WHEN insufficient_privilege THEN RAISE NOTICE 'Insufficient priviliges'; END$$; /* Should be ok */ -SET ROLE user1; -SELECT create_range_partitions('permissions.user1_table', 'id', 1, 10, 2); +SET ROLE pathman_user1; +SELECT create_range_partitions('permissions.pathman_user1_table', 'id', 1, 10, 2); /* Should be able to see */ -SET ROLE user2; +SET ROLE pathman_user2; SELECT * FROM pathman_config; SELECT * FROM pathman_config_params; /* Should fail */ -SET ROLE user2; -SELECT set_enable_parent('permissions.user1_table', true); -SELECT set_auto('permissions.user1_table', false); +SET ROLE pathman_user2; +SELECT set_enable_parent('permissions.pathman_user1_table', true); +SELECT set_auto('permissions.pathman_user1_table', false); /* Should fail */ -SET ROLE user2; +SET ROLE pathman_user2; DELETE FROM pathman_config -WHERE partrel = 'permissions.user1_table'::regclass; +WHERE partrel = 'permissions.pathman_user1_table'::regclass; /* No rights to insert, should fail */ -SET ROLE user2; +SET ROLE pathman_user2; DO $$ BEGIN - INSERT INTO permissions.user1_table (id, a) VALUES (35, 0); + INSERT INTO permissions.pathman_user1_table (id, a) VALUES (35, 0); EXCEPTION WHEN insufficient_privilege THEN RAISE NOTICE 'Insufficient priviliges'; END$$; /* No rights to create partitions (need INSERT privilege) */ -SET ROLE user2; -SELECT prepend_range_partition('permissions.user1_table'); +SET ROLE pathman_user2; +SELECT prepend_range_partition('permissions.pathman_user1_table'); -/* Allow user2 to create partitions */ -SET ROLE user1; -GRANT INSERT ON permissions.user1_table TO user2; -GRANT UPDATE(a) ON permissions.user1_table TO user2; /* per-column ACL */ +/* Allow pathman_user2 to create partitions */ +SET ROLE pathman_user1; +GRANT INSERT ON permissions.pathman_user1_table TO pathman_user2; +GRANT UPDATE(a) ON permissions.pathman_user1_table TO pathman_user2; /* per-column ACL */ /* Should be able to prepend a partition */ -SET ROLE user2; -SELECT prepend_range_partition('permissions.user1_table'); +SET ROLE pathman_user2; +SELECT prepend_range_partition('permissions.pathman_user1_table'); SELECT attname, attacl FROM pg_attribute WHERE attrelid = (SELECT "partition" FROM pathman_partition_list - WHERE parent = 'permissions.user1_table'::REGCLASS + WHERE parent = 'permissions.pathman_user1_table'::REGCLASS ORDER BY range_min::int ASC /* prepend */ LIMIT 1) ORDER BY attname; /* check ACL for each column */ /* Have rights, should be ok (parent's ACL is shared by new children) */ -SET ROLE user2; -INSERT INTO permissions.user1_table (id, a) VALUES (35, 0) RETURNING *; +SET ROLE pathman_user2; +INSERT INTO permissions.pathman_user1_table (id, a) VALUES (35, 0) RETURNING *; SELECT relname, relacl FROM pg_class WHERE oid = ANY (SELECT "partition" FROM pathman_partition_list - WHERE parent = 'permissions.user1_table'::REGCLASS + WHERE parent = 'permissions.pathman_user1_table'::REGCLASS ORDER BY range_max::int DESC /* append */ LIMIT 3) -ORDER BY relname; /* we also check ACL for "user1_table_2" */ +ORDER BY relname; /* we also check ACL for "pathman_user1_table_2" */ /* Try to drop partition, should fail */ DO $$ BEGIN - SELECT drop_range_partition('permissions.user1_table_4'); + SELECT drop_range_partition('permissions.pathman_user1_table_4'); EXCEPTION WHEN insufficient_privilege THEN RAISE NOTICE 'Insufficient priviliges'; END$$; /* Disable automatic partition creation */ -SET ROLE user1; -SELECT set_auto('permissions.user1_table', false); +SET ROLE pathman_user1; +SELECT set_auto('permissions.pathman_user1_table', false); /* Partition creation, should fail */ -SET ROLE user2; -INSERT INTO permissions.user1_table (id, a) VALUES (55, 0) RETURNING *; +SET ROLE pathman_user2; +INSERT INTO permissions.pathman_user1_table (id, a) VALUES (55, 0) RETURNING *; /* Finally drop partitions */ -SET ROLE user1; -SELECT drop_partitions('permissions.user1_table'); +SET ROLE pathman_user1; +SELECT drop_partitions('permissions.pathman_user1_table'); /* Switch to #2 */ -SET ROLE user2; +SET ROLE pathman_user2; /* Test ddl event trigger */ -CREATE TABLE permissions.user2_table(id serial); -SELECT create_hash_partitions('permissions.user2_table', 'id', 3); -INSERT INTO permissions.user2_table SELECT generate_series(1, 30); -SELECT drop_partitions('permissions.user2_table'); +CREATE TABLE permissions.pathman_user2_table(id serial); +SELECT create_hash_partitions('permissions.pathman_user2_table', 'id', 3); +INSERT INTO permissions.pathman_user2_table SELECT generate_series(1, 30); +SELECT drop_partitions('permissions.pathman_user2_table'); /* Switch to #1 */ -SET ROLE user1; +SET ROLE pathman_user1; CREATE TABLE permissions.dropped_column(a int, val int not null, b int, c int); INSERT INTO permissions.dropped_column SELECT i,i,i,i FROM generate_series(1, 30) i; -GRANT SELECT(val), INSERT(val) ON permissions.dropped_column TO user2; +GRANT SELECT(val), INSERT(val) ON permissions.dropped_column TO pathman_user2; SELECT create_range_partitions('permissions.dropped_column', 'val', 1, 10); @@ -168,11 +168,11 @@ DROP TABLE permissions.dropped_column CASCADE; /* Finally reset user */ RESET ROLE; -DROP OWNED BY user1; -DROP OWNED BY user2; -DROP USER user1; -DROP USER user2; +DROP OWNED BY pathman_user1; +DROP OWNED BY pathman_user2; +DROP USER pathman_user1; +DROP USER pathman_user2; -DROP SCHEMA permissions CASCADE; +DROP SCHEMA permissions; DROP EXTENSION pg_pathman; diff --git a/sql/pathman_rebuild_deletes.sql b/sql/pathman_rebuild_deletes.sql index 28a09916..1af6b61a 100644 --- a/sql/pathman_rebuild_deletes.sql +++ b/sql/pathman_rebuild_deletes.sql @@ -60,5 +60,6 @@ DROP TABLE test_deletes.test_dummy; -DROP SCHEMA test_deletes CASCADE; +DROP TABLE test_deletes.test CASCADE; +DROP SCHEMA test_deletes; DROP EXTENSION pg_pathman; diff --git a/sql/pathman_rebuild_updates.sql b/sql/pathman_rebuild_updates.sql index f4229d09..fbbbcbba 100644 --- a/sql/pathman_rebuild_updates.sql +++ b/sql/pathman_rebuild_updates.sql @@ -79,6 +79,26 @@ UPDATE test_updates.test SET val = 95 WHERE val = 115 RETURNING *, tableoid::RE UPDATE test_updates.test SET val = -1 WHERE val = 95 RETURNING *, tableoid::REGCLASS; - -DROP SCHEMA test_updates CASCADE; +/* basic check for 'ALTER TABLE ... ADD COLUMN'; PGPRO-5113 */ +create table test_updates.test_5113(val int4 not null); +insert into test_updates.test_5113 values (1); +select create_range_partitions('test_updates.test_5113', 'val', 1, 10); +update test_updates.test_5113 set val = 11 where val = 1; +alter table test_updates.test_5113 add column x varchar; +/* no error here: */ +select * from test_updates.test_5113 where val = 11; +drop table test_updates.test_5113 cascade; + +create table test_updates.test_5113(val int4 not null); +insert into test_updates.test_5113 values (1); +select create_range_partitions('test_updates.test_5113', 'val', 1, 10); +update test_updates.test_5113 set val = 11 where val = 1; +alter table test_updates.test_5113 add column x int8; +/* no extra data in column 'x' here: */ +select * from test_updates.test_5113 where val = 11; +drop table test_updates.test_5113 cascade; + + +DROP TABLE test_updates.test CASCADE; +DROP SCHEMA test_updates; DROP EXTENSION pg_pathman; diff --git a/sql/pathman_rowmarks.sql b/sql/pathman_rowmarks.sql index ab7f24ac..8847b80c 100644 --- a/sql/pathman_rowmarks.sql +++ b/sql/pathman_rowmarks.sql @@ -1,13 +1,30 @@ /* * ------------------------------------------- - * NOTE: This test behaves differenly on 9.5 + * NOTE: This test behaves differenly on PgPro * ------------------------------------------- * - * Also since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated, - * causing different output; pathman_rowmarks_2.out is the updated version. + * ------------------------ + * pathman_rowmarks_1.sql + * ------------------------ + * Since PostgreSQL 9.5, output of EXPLAIN was changed. * - * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, - * now it includes aliases for inherited tables. + * ------------------------ + * pathman_rowmarks_2.sql + * ------------------------ + * Since 8edd0e794 in PostgreSQL 12, append nodes with single subplan are + * eliminated, causing different output. + * + * ------------------------ + * pathman_rowmarks_3.sql + * ------------------------ + * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13 output of EXPLAIN was + * changed, now it includes aliases for inherited tables. + * + * ------------------------ + * pathman_rowmarks_3.sql + * ------------------------ + * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was + * changed, now it displays SubPlan nodes and output parameters. */ SET search_path = 'public'; CREATE EXTENSION pg_pathman; @@ -135,5 +152,7 @@ SET enable_mergejoin = t; -DROP SCHEMA rowmarks CASCADE; +DROP TABLE rowmarks.first CASCADE; +DROP TABLE rowmarks.second CASCADE; +DROP SCHEMA rowmarks; DROP EXTENSION pg_pathman; diff --git a/sql/pathman_runtime_nodes.sql b/sql/pathman_runtime_nodes.sql index e0b50e9b..bf917d88 100644 --- a/sql/pathman_runtime_nodes.sql +++ b/sql/pathman_runtime_nodes.sql @@ -63,7 +63,6 @@ begin return 'ok'; end; $$ language plpgsql -set pg_pathman.enable = true set enable_mergejoin = off set enable_hashjoin = off; @@ -106,7 +105,6 @@ begin return 'ok'; end; $$ language plpgsql -set pg_pathman.enable = true set enable_mergejoin = off set enable_hashjoin = off; @@ -140,7 +138,6 @@ begin return 'ok'; end; $$ language plpgsql -set pg_pathman.enable = true set enable_mergejoin = off set enable_hashjoin = off; @@ -180,7 +177,6 @@ begin return 'ok'; end; $$ language plpgsql -set pg_pathman.enable = true set enable_mergejoin = off set enable_hashjoin = off; @@ -242,7 +238,6 @@ begin return 'ok'; end; $$ language plpgsql -set pg_pathman.enable = true set enable_hashjoin = off set enable_mergejoin = off; @@ -331,7 +326,47 @@ set enable_hashjoin = on; set enable_mergejoin = on; -DROP SCHEMA test CASCADE; -DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP TABLE test.vals CASCADE; +DROP TABLE test.category CASCADE; +DROP TABLE test.run_values CASCADE; +DROP TABLE test.runtime_test_1 CASCADE; +DROP TABLE test.runtime_test_2 CASCADE; +DROP TABLE test.runtime_test_3 CASCADE; +DROP TABLE test.runtime_test_4 CASCADE; +DROP FUNCTION test.pathman_assert(bool, text); +DROP FUNCTION test.pathman_equal(text, text, text); +DROP FUNCTION test.pathman_test(text); +DROP FUNCTION test.pathman_test_1(); +DROP FUNCTION test.pathman_test_2(); +DROP FUNCTION test.pathman_test_3(); +DROP FUNCTION test.pathman_test_4(); +DROP FUNCTION test.pathman_test_5(); +DROP SCHEMA test; +-- +-- +-- PGPRO-7928 +-- Variable pg_pathman.enable must be called before any query. +-- +CREATE TABLE part_test (val int NOT NULL); +SELECT pathman.create_hash_partitions('part_test', 'val', 2, partition_names := array['part_test_1','pg_pathman']); +CREATE OR REPLACE FUNCTION part_test_trigger() RETURNS TRIGGER AS $$ +BEGIN + RAISE NOTICE '%', format('%s %s %s (%s)', TG_WHEN, TG_OP, TG_LEVEL, TG_TABLE_NAME); + IF TG_OP::text = 'DELETE'::text then + SET pg_pathman.enable = f; + RETURN new; + END IF; +END; +$$ LANGUAGE PLPGSQL; +SET pg_pathman.enable_partitionrouter = t; +CREATE TRIGGER ad AFTER DELETE ON part_test_1 FOR EACH ROW EXECUTE PROCEDURE part_test_trigger (); +INSERT INTO part_test VALUES (1); +UPDATE part_test SET val = val + 1 RETURNING *, tableoid::regclass; +UPDATE part_test SET val = val + 1 RETURNING *, tableoid::regclass; + +RESET pg_pathman.enable_partitionrouter; +DROP TABLE part_test CASCADE; +DROP FUNCTION part_test_trigger(); +DROP EXTENSION pg_pathman CASCADE; +DROP SCHEMA pathman; diff --git a/sql/pathman_subpartitions.sql b/sql/pathman_subpartitions.sql index 5aaea49a..5515874c 100644 --- a/sql/pathman_subpartitions.sql +++ b/sql/pathman_subpartitions.sql @@ -142,7 +142,8 @@ INSERT INTO subpartitions.abc VALUES (10, 0), (110, 0), (-1, 0), (-1, -1); SELECT tableoid::regclass, * FROM subpartitions.abc ORDER BY id1, id2, val; SET pg_pathman.enable_partitionrouter = ON; -UPDATE subpartitions.abc SET id1 = -1, id2 = -1 RETURNING tableoid::regclass, *; +WITH updated AS (UPDATE subpartitions.abc SET id1 = -1, id2 = -1 RETURNING tableoid::regclass, *) +SELECT * FROM updated ORDER BY val ASC; DROP TABLE subpartitions.abc CASCADE; @@ -163,5 +164,6 @@ DROP TABLE subpartitions.a2 CASCADE; DROP TABLE subpartitions.a1; -DROP SCHEMA subpartitions CASCADE; +DROP FUNCTION subpartitions.partitions_tree(regclass, text); +DROP SCHEMA subpartitions; DROP EXTENSION pg_pathman; diff --git a/sql/pathman_upd_del.sql b/sql/pathman_upd_del.sql index a6cab581..c99b9666 100644 --- a/sql/pathman_upd_del.sql +++ b/sql/pathman_upd_del.sql @@ -9,6 +9,9 @@ * plans here. There is an option to forcibly make them MATERIALIZED, but we * also need to run tests on older versions, so put updated plans in * pathman_upd_del_2.out instead. + * + * In Postgres Pro Standard/Enterprise 15+ the EXPLAIN output has changed so put + * the updated plan in pathman_upd_del_4.out. */ \set VERBOSITY terse @@ -274,6 +277,9 @@ ROLLBACK; -DROP SCHEMA test CASCADE; +DROP TABLE test.tmp CASCADE; +DROP TABLE test.tmp2 CASCADE; +DROP TABLE test.range_rel CASCADE; +DROP SCHEMA test; DROP EXTENSION pg_pathman CASCADE; -DROP SCHEMA pathman CASCADE; +DROP SCHEMA pathman; diff --git a/sql/pathman_update_node.sql b/sql/pathman_update_node.sql index 2c7e97f7..e70f60f4 100644 --- a/sql/pathman_update_node.sql +++ b/sql/pathman_update_node.sql @@ -214,5 +214,7 @@ SELECT count(*) FROM test_update_node.test_hash; -DROP SCHEMA test_update_node CASCADE; +DROP TABLE test_update_node.test_hash CASCADE; +DROP TABLE test_update_node.test_range CASCADE; +DROP SCHEMA test_update_node; DROP EXTENSION pg_pathman; diff --git a/sql/pathman_update_triggers.sql b/sql/pathman_update_triggers.sql index e8405acb..646afe65 100644 --- a/sql/pathman_update_triggers.sql +++ b/sql/pathman_update_triggers.sql @@ -140,5 +140,7 @@ update test_update_triggers.test set val = val + 1 returning *, tableoid::regcla select count(distinct val) from test_update_triggers.test; -DROP SCHEMA test_update_triggers CASCADE; +DROP TABLE test_update_triggers.test CASCADE; +DROP FUNCTION test_update_triggers.test_trigger(); +DROP SCHEMA test_update_triggers; DROP EXTENSION pg_pathman CASCADE; diff --git a/sql/pathman_utility_stmt.sql b/sql/pathman_utility_stmt.sql index 31232ce1..08992835 100644 --- a/sql/pathman_utility_stmt.sql +++ b/sql/pathman_utility_stmt.sql @@ -154,7 +154,9 @@ COPY copy_stmt_hooking.test2(t) FROM stdin; \. SELECT COUNT(*) FROM copy_stmt_hooking.test2; -DROP SCHEMA copy_stmt_hooking CASCADE; +DROP TABLE copy_stmt_hooking.test CASCADE; +DROP TABLE copy_stmt_hooking.test2 CASCADE; +DROP SCHEMA copy_stmt_hooking; @@ -234,7 +236,12 @@ FROM pg_constraint r WHERE r.conrelid = 'rename.plain_test'::regclass AND r.contype = 'c'; -DROP SCHEMA rename CASCADE; +DROP TABLE rename.plain_test CASCADE; +DROP TABLE rename.test_inh CASCADE; +DROP TABLE rename.parent CASCADE; +DROP TABLE rename.test CASCADE; +DROP FUNCTION add_constraint(regclass); +DROP SCHEMA rename; @@ -248,8 +255,55 @@ CREATE INDEX ON drop_index.test (val); SELECT create_hash_partitions('drop_index.test', 'val', 2); DROP INDEX CONCURRENTLY drop_index.test_0_val_idx; -DROP SCHEMA drop_index CASCADE; +DROP TABLE drop_index.test CASCADE; +DROP SCHEMA drop_index; +/* + * Checking that ALTER TABLE IF EXISTS with loaded (and created) pg_pathman extension works the same as in vanilla + */ +CREATE SCHEMA test_nonexistance; + +ALTER TABLE IF EXISTS test_nonexistance.nonexistent_table RENAME TO other_table_name; +/* renaming existent tables already tested earlier (see rename.plain_test) */ + +ALTER TABLE IF EXISTS test_nonexistance.nonexistent_table ADD COLUMN j INT4; +CREATE TABLE test_nonexistance.existent_table(i INT4); +ALTER TABLE IF EXISTS test_nonexistance.existent_table ADD COLUMN j INT4; +SELECT attname FROM pg_attribute WHERE attnum > 0 AND attrelid = 'test_nonexistance.existent_table'::REGCLASS; +DROP TABLE test_nonexistance.existent_table; + +ALTER TABLE IF EXISTS test_nonexistance.nonexistent_table DROP COLUMN IF EXISTS i; +CREATE TABLE test_nonexistance.existent_table(i INT4); +ALTER TABLE IF EXISTS test_nonexistance.existent_table DROP COLUMN IF EXISTS i; +ALTER TABLE IF EXISTS test_nonexistance.existent_table DROP COLUMN IF EXISTS nonexistent_column; +SELECT attname FROM pg_attribute WHERE attnum > 0 AND attrelid = 'test_nonexistance.existent_table'::REGCLASS; +DROP TABLE test_nonexistance.existent_table; + +ALTER TABLE IF EXISTS test_nonexistance.nonexistent_table RENAME COLUMN i TO j; +CREATE TABLE test_nonexistance.existent_table(i INT4); +ALTER TABLE IF EXISTS test_nonexistance.existent_table RENAME COLUMN i TO j; +SELECT attname FROM pg_attribute WHERE attnum > 0 AND attrelid = 'test_nonexistance.existent_table'::REGCLASS; +DROP TABLE test_nonexistance.existent_table; + +ALTER TABLE IF EXISTS test_nonexistance.nonexistent_table RENAME CONSTRAINT baz TO bar; +CREATE TABLE test_nonexistance.existent_table(i INT4 CONSTRAINT existent_table_i_check CHECK (i < 100)); +ALTER TABLE IF EXISTS test_nonexistance.existent_table RENAME CONSTRAINT existent_table_i_check TO existent_table_i_other_check; +DROP TABLE test_nonexistance.existent_table; + +ALTER TABLE IF EXISTS test_nonexistance.nonexistent_table SET SCHEMA nonexistent_schema; +CREATE TABLE test_nonexistance.existent_table(i INT4); +ALTER TABLE IF EXISTS test_nonexistance.existent_table SET SCHEMA nonexistent_schema; +CREATE SCHEMA test_nonexistance2; +ALTER TABLE IF EXISTS test_nonexistance.existent_table SET SCHEMA test_nonexistance2; +DROP TABLE test_nonexistance2.existent_table; +DROP SCHEMA test_nonexistance2; + +ALTER TABLE IF EXISTS test_nonexistance.nonexistent_table SET TABLESPACE nonexistent_tablespace; +CREATE TABLE test_nonexistance.existent_table(i INT4); +ALTER TABLE IF EXISTS test_nonexistance.existent_table SET TABLESPACE nonexistent_tablespace; +DROP TABLE test_nonexistance.existent_table; + +DROP SCHEMA test_nonexistance; DROP EXTENSION pg_pathman; diff --git a/sql/pathman_views.sql b/sql/pathman_views.sql index 65e64149..36baa5c5 100644 --- a/sql/pathman_views.sql +++ b/sql/pathman_views.sql @@ -79,5 +79,8 @@ explain (costs off) select * from views.abc_union_all where id = 5; -DROP SCHEMA views CASCADE; +DROP TABLE views._abc CASCADE; +DROP TABLE views._abc_add CASCADE; +DROP FUNCTION views.disable_modification(); +DROP SCHEMA views; DROP EXTENSION pg_pathman; diff --git a/src/compat/pg_compat.c b/src/compat/pg_compat.c index abf71f9d..216fd382 100644 --- a/src/compat/pg_compat.c +++ b/src/compat/pg_compat.c @@ -181,7 +181,9 @@ make_restrictinfos_from_actual_clauses(PlannerInfo *root, root->hasPseudoConstantQuals = true; } - rinfo = make_restrictinfo(clause, + rinfo = make_restrictinfo_compat( + root, + clause, true, false, pseudoconstant, @@ -232,10 +234,12 @@ McxtStatsInternal(MemoryContext context, int level, MemoryContextCounters local_totals; MemoryContext child; - AssertArg(MemoryContextIsValid(context)); + Assert(MemoryContextIsValid(context)); /* Examine the context itself */ -#if PG_VERSION_NUM >= 110000 +#if PG_VERSION_NUM >= 140000 + (*context->methods->stats) (context, NULL, NULL, totals, true); +#elif PG_VERSION_NUM >= 110000 (*context->methods->stats) (context, NULL, NULL, totals); #else (*context->methods->stats) (context, level, false, totals); diff --git a/src/compat/rowmarks_fix.c b/src/compat/rowmarks_fix.c index 4dd1c20a..35eea44b 100644 --- a/src/compat/rowmarks_fix.c +++ b/src/compat/rowmarks_fix.c @@ -47,7 +47,7 @@ append_tle_for_rowmark(PlannerInfo *root, PlanRowMark *rc) root->processed_tlist = lappend(root->processed_tlist, tle); - add_vars_to_targetlist(root, list_make1(var), bms_make_singleton(0), true); + add_vars_to_targetlist_compat(root, list_make1(var), bms_make_singleton(0)); } diff --git a/src/declarative.c b/src/declarative.c index ca4fe165..42e9ffac 100644 --- a/src/declarative.c +++ b/src/declarative.c @@ -75,7 +75,11 @@ is_pathman_related_partitioning_cmd(Node *parsetree, Oid *parent_relid) AlterTableStmt *stmt = (AlterTableStmt *) parsetree; int cnt = 0; - *parent_relid = RangeVarGetRelid(stmt->relation, NoLock, false); + *parent_relid = RangeVarGetRelid(stmt->relation, NoLock, stmt->missing_ok); + + if (stmt->missing_ok && *parent_relid == InvalidOid) + return false; + if ((prel = get_pathman_relation_info(*parent_relid)) == NULL) return false; @@ -233,6 +237,8 @@ handle_attach_partition(Oid parent_relid, AlterTableCmd *cmd) /* Fetch pg_pathman's schema */ pathman_schema = get_namespace_name(get_pathman_schema()); + if (pathman_schema == NULL) + elog(ERROR, "pg_pathman schema not initialized"); /* Build function's name */ proc_name = list_make2(makeString(pathman_schema), @@ -292,6 +298,8 @@ handle_detach_partition(AlterTableCmd *cmd) /* Fetch pg_pathman's schema */ pathman_schema = get_namespace_name(get_pathman_schema()); + if (pathman_schema == NULL) + elog(ERROR, "pg_pathman schema not initialized"); /* Build function's name */ proc_name = list_make2(makeString(pathman_schema), diff --git a/src/hooks.c b/src/hooks.c index e9ff1ed7..2ff2667c 100644 --- a/src/hooks.c +++ b/src/hooks.c @@ -21,6 +21,7 @@ #include "hooks.h" #include "init.h" #include "partition_filter.h" +#include "partition_overseer.h" #include "partition_router.h" #include "pathman_workers.h" #include "planner_tree_modification.h" @@ -38,8 +39,9 @@ #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" #include "rewrite/rewriteManip.h" -#include "utils/typcache.h" #include "utils/lsyscache.h" +#include "utils/typcache.h" +#include "utils/snapmgr.h" #ifdef USE_ASSERT_CHECKING @@ -74,6 +76,7 @@ planner_hook_type pathman_planner_hook_next = NULL; post_parse_analyze_hook_type pathman_post_parse_analyze_hook_next = NULL; shmem_startup_hook_type pathman_shmem_startup_hook_next = NULL; ProcessUtility_hook_type pathman_process_utility_hook_next = NULL; +ExecutorStart_hook_type pathman_executor_start_hook_prev = NULL; /* Take care of joins */ @@ -293,7 +296,11 @@ pathman_join_pathlist_hook(PlannerInfo *root, * Currently we use get_parameterized_joinrel_size() since * it works just fine, but this might change some day. */ +#if PG_VERSION_NUM >= 150000 /* for commit 18fea737b5e4 */ + nest_path->jpath.path.rows = +#else nest_path->path.rows = +#endif get_parameterized_joinrel_size_compat(root, joinrel, outer, inner, extra->sjinfo, @@ -442,12 +449,12 @@ pathman_rel_pathlist_hook(PlannerInfo *root, tce = lookup_type_cache(prel->ev_type, TYPECACHE_LT_OPR | TYPECACHE_GT_OPR); /* Make pathkeys */ - pathkeys = build_expression_pathkey(root, (Expr *) part_expr, NULL, - tce->lt_opr, NULL, false); + pathkeys = build_expression_pathkey_compat(root, (Expr *) part_expr, NULL, + tce->lt_opr, NULL, false); if (pathkeys) pathkeyAsc = (PathKey *) linitial(pathkeys); - pathkeys = build_expression_pathkey(root, (Expr *) part_expr, NULL, - tce->gt_opr, NULL, false); + pathkeys = build_expression_pathkey_compat(root, (Expr *) part_expr, NULL, + tce->gt_opr, NULL, false); if (pathkeys) pathkeyDesc = (PathKey *) linitial(pathkeys); } @@ -608,6 +615,39 @@ pathman_rel_pathlist_hook(PlannerInfo *root, close_pathman_relation_info(prel); } +/* + * 'pg_pathman.enable' GUC check. + */ +bool +pathman_enable_check_hook(bool *newval, void **extra, GucSource source) +{ + /* The top level statement requires immediate commit: accept GUC change */ + if (MyXactFlags & XACT_FLAGS_NEEDIMMEDIATECOMMIT) + return true; + + /* Ignore the case of re-setting the same value */ + if (*newval == pathman_init_state.pg_pathman_enable) + return true; + + /* Command must be at top level of a fresh transaction. */ + if (FirstSnapshotSet || + GetTopTransactionIdIfAny() != InvalidTransactionId || +#ifdef PGPRO_EE + getNestLevelATX() > 0 || +#endif + IsSubTransaction()) + { + ereport(WARNING, + (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), + errmsg("\"pg_pathman.enable\" must be called before any query, ignored"))); + + /* Keep the old value. */ + *newval = pathman_init_state.pg_pathman_enable; + } + + return true; +} + /* * Intercept 'pg_pathman.enable' GUC assignments. */ @@ -669,6 +709,23 @@ execute_for_plantree(PlannedStmt *planned_stmt, planned_stmt->subplans = subplans; } +/* + * Truncated version of set_plan_refs. + * Pathman can add nodes to already completed and post-processed plan tree. + * reset_plan_node_ids fixes some presentation values for updated plan tree + * to avoid problems in further processing. + */ +static Plan * +reset_plan_node_ids(Plan *plan, void *lastPlanNodeId) +{ + if (plan == NULL) + return NULL; + + plan->plan_node_id = (*(int *) lastPlanNodeId)++; + + return plan; +} + /* * Planner hook. It disables inheritance for tables that have been partitioned * by pathman to prevent standart PostgreSQL partitioning mechanism from @@ -684,7 +741,7 @@ pathman_planner_hook(Query *parse, int cursorOptions, ParamListInfo boundParams) #endif { PlannedStmt *result; - uint32 query_id = parse->queryId; + uint64 query_id = parse->queryId; /* Save the result in case it changes */ bool pathman_ready = IsPathmanReady(); @@ -716,6 +773,9 @@ pathman_planner_hook(Query *parse, int cursorOptions, ParamListInfo boundParams) if (pathman_ready) { + int lastPlanNodeId = 0; + ListCell *l; + /* Add PartitionFilter node for INSERT queries */ execute_for_plantree(result, add_partition_filters); @@ -725,6 +785,13 @@ pathman_planner_hook(Query *parse, int cursorOptions, ParamListInfo boundParams) /* Decrement planner() calls count */ decr_planner_calls_count(); + /* remake parsed tree presentation fixes due to possible adding nodes */ + result->planTree = plan_tree_visitor(result->planTree, reset_plan_node_ids, &lastPlanNodeId); + foreach(l, result->subplans) + { + lfirst(l) = plan_tree_visitor((Plan *) lfirst(l), reset_plan_node_ids, &lastPlanNodeId); + } + /* HACK: restore queryId set by pg_stat_statements */ result->queryId = query_id; } @@ -751,12 +818,25 @@ pathman_planner_hook(Query *parse, int cursorOptions, ParamListInfo boundParams) * Post parse analysis hook. It makes sure the config is loaded before executing * any statement, including utility commands. */ +#if PG_VERSION_NUM >= 140000 +/* + * pathman_post_parse_analyze_hook(), pathman_post_parse_analyze_hook_next(): + * in 14 new argument was added (5fd9dfa5f50) + */ +void +pathman_post_parse_analyze_hook(ParseState *pstate, Query *query, JumbleState *jstate) +{ + /* Invoke original hook if needed */ + if (pathman_post_parse_analyze_hook_next) + pathman_post_parse_analyze_hook_next(pstate, query, jstate); +#else void pathman_post_parse_analyze_hook(ParseState *pstate, Query *query) { /* Invoke original hook if needed */ if (pathman_post_parse_analyze_hook_next) pathman_post_parse_analyze_hook_next(pstate, query); +#endif /* See cook_partitioning_expression() */ if (!pathman_hooks_enabled) @@ -944,7 +1024,23 @@ pathman_relcache_hook(Datum arg, Oid relid) * In PG 13 (2f9661311b8) command completion tags was reworked (added QueryCompletion struct) */ void -#if PG_VERSION_NUM >= 130000 +#if PG_VERSION_NUM >= 140000 +/* + * pathman_post_parse_analyze_hook(), pathman_post_parse_analyze_hook_next(): + * in 14 new argument was added (5fd9dfa5f50) + */ +pathman_process_utility_hook(PlannedStmt *first_arg, + const char *queryString, + bool readOnlyTree, + ProcessUtilityContext context, + ParamListInfo params, + QueryEnvironment *queryEnv, + DestReceiver *dest, QueryCompletion *queryCompletion) +{ + Node *parsetree = first_arg->utilityStmt; + int stmt_location = first_arg->stmt_location, + stmt_len = first_arg->stmt_len; +#elif PG_VERSION_NUM >= 130000 pathman_process_utility_hook(PlannedStmt *first_arg, const char *queryString, ProcessUtilityContext context, @@ -1068,7 +1164,15 @@ pathman_process_utility_hook(Node *first_arg, } /* Finally call process_utility_hook_next or standard_ProcessUtility */ -#if PG_VERSION_NUM >= 130000 +#if PG_VERSION_NUM >= 140000 + call_process_utility_compat((pathman_process_utility_hook_next ? + pathman_process_utility_hook_next : + standard_ProcessUtility), + first_arg, queryString, + readOnlyTree, + context, params, queryEnv, + dest, queryCompletion); +#elif PG_VERSION_NUM >= 130000 call_process_utility_compat((pathman_process_utility_hook_next ? pathman_process_utility_hook_next : standard_ProcessUtility), @@ -1084,3 +1188,53 @@ pathman_process_utility_hook(Node *first_arg, dest, completionTag); #endif } + +/* + * Planstate tree nodes could have been copied. + * It breaks references on correspoding + * ModifyTable node from PartitionRouter nodes. + */ +static void +fix_mt_refs(PlanState *state, void *context) +{ + ModifyTableState *mt_state = (ModifyTableState *) state; + PartitionRouterState *pr_state; +#if PG_VERSION_NUM < 140000 + int i; +#endif + + if (!IsA(state, ModifyTableState)) + return; +#if PG_VERSION_NUM >= 140000 + { + CustomScanState *pf_state = (CustomScanState *) outerPlanState(mt_state); +#else + for (i = 0; i < mt_state->mt_nplans; i++) + { + CustomScanState *pf_state = (CustomScanState *) mt_state->mt_plans[i]; +#endif + if (IsPartitionFilterState(pf_state)) + { + pr_state = linitial(pf_state->custom_ps); + if (IsPartitionRouterState(pr_state)) + { + pr_state->mt_state = mt_state; + } + } + } +} + +void +pathman_executor_start_hook(QueryDesc *queryDesc, int eflags) +{ + if (pathman_executor_start_hook_prev) + pathman_executor_start_hook_prev(queryDesc, eflags); + else + standard_ExecutorStart(queryDesc, eflags); + + /* + * HACK for compatibility with pgpro_stats. + * Fix possibly broken planstate tree. + */ + state_tree_visitor(queryDesc->planstate, fix_mt_refs, NULL); +} diff --git a/src/include/compat/pg_compat.h b/src/include/compat/pg_compat.h index 24a36fea..f6330627 100644 --- a/src/include/compat/pg_compat.h +++ b/src/include/compat/pg_compat.h @@ -65,11 +65,11 @@ */ #if PG_VERSION_NUM >= 110000 #define calc_nestloop_required_outer_compat(outer, inner) \ - calc_nestloop_required_outer((outer)->parent->relids, PATH_REQ_OUTER(outer), \ + calc_nestloop_required_outer((outer)->parent->relids, PATH_REQ_OUTER(outer), \ (inner)->parent->relids, PATH_REQ_OUTER(inner)) #else #define calc_nestloop_required_outer_compat(outer, inner) \ - calc_nestloop_required_outer((outer), (inner)) + calc_nestloop_required_outer((outer), (inner)) #endif @@ -119,18 +119,26 @@ /* * CheckValidResultRel() */ -#if PG_VERSION_NUM >= 100000 -#define CheckValidResultRelCompat(rri, cmd) \ - CheckValidResultRel((rri), (cmd)) +#if PG_VERSION_NUM >= 170000 +#define CheckValidResultRelCompat(rri, cmd) \ + CheckValidResultRel((rri), (cmd), NIL) +#elif PG_VERSION_NUM >= 100000 +#define CheckValidResultRelCompat(rri, cmd) \ + CheckValidResultRel((rri), (cmd)) #elif PG_VERSION_NUM >= 90500 -#define CheckValidResultRelCompat(rri, cmd) \ - CheckValidResultRel((rri)->ri_RelationDesc, (cmd)) +#define CheckValidResultRelCompat(rri, cmd) \ + CheckValidResultRel((rri)->ri_RelationDesc, (cmd)) #endif /* * BeginCopyFrom() */ -#if PG_VERSION_NUM >= 100000 +#if PG_VERSION_NUM >= 140000 +#define BeginCopyFromCompat(pstate, rel, filename, is_program, data_source_cb, \ + attnamelist, options) \ + BeginCopyFrom((pstate), (rel), NULL, (filename), (is_program), \ + (data_source_cb), (attnamelist), (options)) +#elif PG_VERSION_NUM >= 100000 #define BeginCopyFromCompat(pstate, rel, filename, is_program, data_source_cb, \ attnamelist, options) \ BeginCopyFrom((pstate), (rel), (filename), (is_program), \ @@ -174,7 +182,14 @@ * - in pg 10 PlannedStmt object * - in pg 9.6 and lower Node parsetree */ -#if PG_VERSION_NUM >= 100000 +#if PG_VERSION_NUM >= 140000 +#define call_process_utility_compat(process_utility, first_arg, query_string, \ + readOnlyTree, context, params, query_env, \ + dest, completion_tag) \ + (process_utility)((first_arg), (query_string), readOnlyTree, \ + (context), (params), \ + (query_env), (dest), (completion_tag)) +#elif PG_VERSION_NUM >= 100000 #define call_process_utility_compat(process_utility, first_arg, query_string, \ context, params, query_env, dest, \ completion_tag) \ @@ -225,22 +240,14 @@ #endif -/* - * CheckValidResultRel() - */ -#if PG_VERSION_NUM >= 100000 -#define CheckValidResultRelCompat(rri, cmd) \ - CheckValidResultRel((rri), (cmd)) -#elif PG_VERSION_NUM >= 90500 -#define CheckValidResultRelCompat(rri, cmd) \ - CheckValidResultRel((rri)->ri_RelationDesc, (cmd)) -#endif - - /* * create_append_path() */ -#if PG_VERSION_NUM >= 130000 +#if PG_VERSION_NUM >= 140000 +#define create_append_path_compat(rel, subpaths, required_outer, parallel_workers) \ + create_append_path(NULL, (rel), (subpaths), NIL, NIL, (required_outer), \ + (parallel_workers), false, -1) +#elif PG_VERSION_NUM >= 130000 /* * PGPRO-3938 made create_append_path compatible with vanilla again */ @@ -258,7 +265,7 @@ #define create_append_path_compat(rel, subpaths, required_outer, parallel_workers) \ create_append_path(NULL, (rel), (subpaths), NIL, NIL, (required_outer), \ (parallel_workers), false, NIL, -1, false) -#endif /* PGPRO_VERSION */ +#endif /* PGPRO_VERSION */ #elif PG_VERSION_NUM >= 110000 @@ -270,7 +277,7 @@ #define create_append_path_compat(rel, subpaths, required_outer, parallel_workers) \ create_append_path(NULL, (rel), (subpaths), NIL, (required_outer), \ (parallel_workers), false, NIL, -1, false, NIL) -#endif /* PGPRO_VERSION */ +#endif /* PGPRO_VERSION */ #elif PG_VERSION_NUM >= 100000 @@ -281,7 +288,7 @@ #define create_append_path_compat(rel, subpaths, required_outer, parallel_workers) \ create_append_path((rel), (subpaths), (required_outer), (parallel_workers), NIL, \ false, NIL) -#endif /* PGPRO_VERSION */ +#endif /* PGPRO_VERSION */ #elif PG_VERSION_NUM >= 90600 @@ -292,18 +299,23 @@ #define create_append_path_compat(rel, subpaths, required_outer, parallel_workers) \ create_append_path((rel), (subpaths), (required_outer), \ false, NIL, (parallel_workers)) -#endif /* PGPRO_VERSION */ +#endif /* PGPRO_VERSION */ #elif PG_VERSION_NUM >= 90500 #define create_append_path_compat(rel, subpaths, required_outer, parallel_workers) \ create_append_path((rel), (subpaths), (required_outer)) -#endif /* PG_VERSION_NUM */ +#endif /* PG_VERSION_NUM */ /* * create_merge_append_path() */ -#if PG_VERSION_NUM >= 100000 +#if PG_VERSION_NUM >= 140000 +#define create_merge_append_path_compat(root, rel, subpaths, pathkeys, \ + required_outer) \ + create_merge_append_path((root), (rel), (subpaths), (pathkeys), \ + (required_outer)) +#elif PG_VERSION_NUM >= 100000 #define create_merge_append_path_compat(root, rel, subpaths, pathkeys, \ required_outer) \ create_merge_append_path((root), (rel), (subpaths), (pathkeys), \ @@ -402,8 +414,8 @@ extern void create_plain_partial_paths(PlannerInfo *root, static inline Datum ExecEvalExprCompat(ExprState *expr, ExprContext *econtext, bool *isnull) { - ExprDoneCond isdone; - Datum result = ExecEvalExpr(expr, econtext, isnull, &isdone); + ExprDoneCond isdone; + Datum result = ExecEvalExpr(expr, econtext, isnull, &isdone); if (isdone != ExprSingleResult) elog(ERROR, "expression should return single value"); @@ -420,9 +432,9 @@ ExecEvalExprCompat(ExprState *expr, ExprContext *econtext, bool *isnull) static inline bool ExecCheck(ExprState *state, ExprContext *econtext) { - Datum ret; - bool isnull; - MemoryContext old_mcxt; + Datum ret; + bool isnull; + MemoryContext old_mcxt; /* short-circuit (here and in ExecInitCheck) for empty restriction list */ if (state == NULL) @@ -518,7 +530,7 @@ extern List *get_all_actual_clauses(List *restrictinfo_list); * get_rel_persistence() */ #if PG_VERSION_NUM >= 90500 && PG_VERSION_NUM < 90600 -char get_rel_persistence(Oid relid); +char get_rel_persistence(Oid relid); #endif @@ -571,8 +583,8 @@ char get_rel_persistence(Oid relid); * make_restrictinfo() */ #if PG_VERSION_NUM >= 100000 -extern List * make_restrictinfos_from_actual_clauses(PlannerInfo *root, - List *clause_list); +extern List *make_restrictinfos_from_actual_clauses(PlannerInfo *root, + List *clause_list); #endif @@ -595,9 +607,9 @@ extern Result *make_result(List *tlist, * McxtStatsInternal() */ #if PG_VERSION_NUM >= 90600 -void McxtStatsInternal(MemoryContext context, int level, - bool examine_children, - MemoryContextCounters *totals); +void McxtStatsInternal(MemoryContext context, int level, + bool examine_children, + MemoryContextCounters *totals); #endif @@ -605,7 +617,7 @@ void McxtStatsInternal(MemoryContext context, int level, * oid_cmp() */ #if PG_VERSION_NUM >=90500 && PG_VERSION_NUM < 100000 -extern int oid_cmp(const void *p1, const void *p2); +extern int oid_cmp(const void *p1, const void *p2); #endif @@ -614,7 +626,12 @@ extern int oid_cmp(const void *p1, const void *p2); * * for v10 cast first arg to RawStmt type */ -#if PG_VERSION_NUM >= 100000 +#if PG_VERSION_NUM >= 150000 /* for commit 791b1b71da35 */ +#define parse_analyze_compat(parse_tree, query_string, param_types, nparams, \ + query_env) \ + parse_analyze_fixedparams((RawStmt *) (parse_tree), (query_string), (param_types), \ + (nparams), (query_env)) +#elif PG_VERSION_NUM >= 100000 #define parse_analyze_compat(parse_tree, query_string, param_types, nparams, \ query_env) \ parse_analyze((RawStmt *) (parse_tree), (query_string), (param_types), \ @@ -632,7 +649,12 @@ extern int oid_cmp(const void *p1, const void *p2); * * for v10 cast first arg to RawStmt type */ -#if PG_VERSION_NUM >= 100000 +#if PG_VERSION_NUM >= 150000 /* for commit 791b1b71da35 */ +#define pg_analyze_and_rewrite_compat(parsetree, query_string, param_types, \ + nparams, query_env) \ + pg_analyze_and_rewrite_fixedparams((RawStmt *) (parsetree), (query_string), \ + (param_types), (nparams), (query_env)) +#elif PG_VERSION_NUM >= 100000 #define pg_analyze_and_rewrite_compat(parsetree, query_string, param_types, \ nparams, query_env) \ pg_analyze_and_rewrite((RawStmt *) (parsetree), (query_string), \ @@ -650,7 +672,20 @@ extern int oid_cmp(const void *p1, const void *p2); * * for v10 set NULL into 'queryEnv' argument */ -#if PG_VERSION_NUM >= 100000 +#if PG_VERSION_NUM >= 140000 +#define ProcessUtilityCompat(parsetree, queryString, context, params, dest, \ + completionTag) \ + do { \ + PlannedStmt *stmt = makeNode(PlannedStmt); \ + stmt->commandType = CMD_UTILITY; \ + stmt->canSetTag = true; \ + stmt->utilityStmt = (parsetree); \ + stmt->stmt_location = -1; \ + stmt->stmt_len = 0; \ + ProcessUtility(stmt, (queryString), false, (context), (params), NULL, \ + (dest), (completionTag)); \ + } while (0) +#elif PG_VERSION_NUM >= 100000 #define ProcessUtilityCompat(parsetree, queryString, context, params, dest, \ completionTag) \ do { \ @@ -687,7 +722,7 @@ extern int oid_cmp(const void *p1, const void *p2); * set_dummy_rel_pathlist() */ #if PG_VERSION_NUM >= 90500 && PG_VERSION_NUM < 90600 -void set_dummy_rel_pathlist(RelOptInfo *rel); +void set_dummy_rel_pathlist(RelOptInfo *rel); #endif @@ -709,6 +744,10 @@ extern void set_rel_consider_parallel(PlannerInfo *root, * in compat version the type of first argument is (Expr *) */ #if PG_VERSION_NUM >= 100000 +#if PG_VERSION_NUM >= 140000 /* function removed in + * 375398244168add84a884347625d14581a421e71 */ +extern TargetEntry *tlist_member_ignore_relabel(Expr *node, List *targetlist); +#endif #define tlist_member_ignore_relabel_compat(expr, targetlist) \ tlist_member_ignore_relabel((expr), (targetlist)) #elif PG_VERSION_NUM >= 90500 @@ -729,6 +768,25 @@ extern AttrNumber *convert_tuples_by_name_map(TupleDesc indesc, #include "access/tupconvert.h" #endif +/* + * ExecBRUpdateTriggers() + */ +#if PG_VERSION_NUM >= 160000 +#define ExecBRUpdateTriggersCompat(estate, epqstate, relinfo, \ + tupleid, fdw_trigtuple, newslot) \ + ExecBRUpdateTriggers((estate), (epqstate), (relinfo), (tupleid), \ + (fdw_trigtuple), (newslot), NULL, NULL) +#elif PG_VERSION_NUM >= 150000 /* for commit 7103ebb7aae8 */ +#define ExecBRUpdateTriggersCompat(estate, epqstate, relinfo, \ + tupleid, fdw_trigtuple, newslot) \ + ExecBRUpdateTriggers((estate), (epqstate), (relinfo), (tupleid), \ + (fdw_trigtuple), (newslot), NULL) +#else +#define ExecBRUpdateTriggersCompat(estate, epqstate, relinfo, \ + tupleid, fdw_trigtuple, newslot) \ + ExecBRUpdateTriggers((estate), (epqstate), (relinfo), (tupleid), \ + (fdw_trigtuple), (newslot)) +#endif /* * ExecARInsertTriggers() @@ -748,7 +806,12 @@ extern AttrNumber *convert_tuples_by_name_map(TupleDesc indesc, /* * ExecBRDeleteTriggers() */ -#if PG_VERSION_NUM >= 110000 +#if PG_VERSION_NUM >= 160000 +#define ExecBRDeleteTriggersCompat(estate, epqstate, relinfo, tupleid, \ + fdw_trigtuple, epqslot) \ + ExecBRDeleteTriggers((estate), (epqstate), (relinfo), (tupleid), \ + (fdw_trigtuple), (epqslot), NULL, NULL) +#elif PG_VERSION_NUM >= 110000 #define ExecBRDeleteTriggersCompat(estate, epqstate, relinfo, tupleid, \ fdw_trigtuple, epqslot) \ ExecBRDeleteTriggers((estate), (epqstate), (relinfo), (tupleid), \ @@ -764,7 +827,12 @@ extern AttrNumber *convert_tuples_by_name_map(TupleDesc indesc, /* * ExecARDeleteTriggers() */ -#if PG_VERSION_NUM >= 100000 +#if PG_VERSION_NUM >= 150000 /* for commit ba9a7e392171 */ +#define ExecARDeleteTriggersCompat(estate, relinfo, tupleid, \ + fdw_trigtuple, transition_capture) \ + ExecARDeleteTriggers((estate), (relinfo), (tupleid), \ + (fdw_trigtuple), (transition_capture), false) +#elif PG_VERSION_NUM >= 100000 #define ExecARDeleteTriggersCompat(estate, relinfo, tupleid, \ fdw_trigtuple, transition_capture) \ ExecARDeleteTriggers((estate), (relinfo), (tupleid), \ @@ -903,9 +971,9 @@ extern AttrNumber *convert_tuples_by_name_map(TupleDesc indesc, * we need access to entire tuple, not just its header. */ #ifdef XID_IS_64BIT -# define HeapTupleGetXminCompat(htup) HeapTupleGetXmin(htup) +#define HeapTupleGetXminCompat(htup) HeapTupleGetXmin(htup) #else -# define HeapTupleGetXminCompat(htup) HeapTupleHeaderGetXmin((htup)->t_data) +#define HeapTupleGetXminCompat(htup) HeapTupleHeaderGetXmin((htup)->t_data) #endif /* @@ -961,12 +1029,20 @@ extern AttrNumber *convert_tuples_by_name_map(TupleDesc indesc, /* * ExecInsertIndexTuples. Since 12 slot contains tupleid. - */ -#if PG_VERSION_NUM >= 120000 -#define ExecInsertIndexTuplesCompat(slot, tupleid, estate, noDupError, specConflict, arbiterIndexes) \ + * Since 14: new fields "resultRelInfo", "update". + * Since 16: new bool field "onlySummarizing". + */ +#if PG_VERSION_NUM >= 160000 +#define ExecInsertIndexTuplesCompat(resultRelInfo, slot, tupleid, estate, update, noDupError, specConflict, arbiterIndexes, onlySummarizing) \ + ExecInsertIndexTuples((resultRelInfo), (slot), (estate), (update), (noDupError), (specConflict), (arbiterIndexes), (onlySummarizing)) +#elif PG_VERSION_NUM >= 140000 +#define ExecInsertIndexTuplesCompat(resultRelInfo, slot, tupleid, estate, update, noDupError, specConflict, arbiterIndexes, onlySummarizing) \ + ExecInsertIndexTuples((resultRelInfo), (slot), (estate), (update), (noDupError), (specConflict), (arbiterIndexes)) +#elif PG_VERSION_NUM >= 120000 +#define ExecInsertIndexTuplesCompat(resultRelInfo, slot, tupleid, estate, update, noDupError, specConflict, arbiterIndexes, onlySummarizing) \ ExecInsertIndexTuples((slot), (estate), (noDupError), (specConflict), (arbiterIndexes)) #else -#define ExecInsertIndexTuplesCompat(slot, tupleid, estate, noDupError, specConflict, arbiterIndexes) \ +#define ExecInsertIndexTuplesCompat(resultRelInfo, slot, tupleid, estate, update, noDupError, specConflict, arbiterIndexes, onlySummarizing) \ ExecInsertIndexTuples((slot), (tupleid), (estate), (noDupError), (specConflict), (arbiterIndexes)) #endif @@ -1006,7 +1082,7 @@ extern AttrNumber *convert_tuples_by_name_map(TupleDesc indesc, * macro (and never will be, for old versions), so distinguish via macro added * by the commit. */ -#ifdef QTW_DONT_COPY_DEFAULT +#if defined(QTW_DONT_COPY_DEFAULT) && (PG_VERSION_NUM < 140000) #define expression_tree_mutator_compat(node, mutator, context) \ expression_tree_mutator((node), (mutator), (context), 0) #else @@ -1014,6 +1090,17 @@ extern AttrNumber *convert_tuples_by_name_map(TupleDesc indesc, expression_tree_mutator((node), (mutator), (context)) #endif +/* + * stringToQualifiedNameList + */ +#if PG_VERSION_NUM >= 160000 +#define stringToQualifiedNameListCompat(string) \ + stringToQualifiedNameList((string), NULL) +#else +#define stringToQualifiedNameListCompat(string) \ + stringToQualifiedNameList((string)) +#endif + /* * ------------- * Common code @@ -1029,9 +1116,10 @@ static inline TupleTableSlot * ExecInitExtraTupleSlotCompatHorse(EState *s, TupleDesc t) { #if PG_VERSION_NUM >= 110000 - return ExecInitExtraTupleSlot(s,t); + return ExecInitExtraTupleSlot(s, t); #else - TupleTableSlot *res = ExecInitExtraTupleSlot(s); + TupleTableSlot *res = ExecInitExtraTupleSlot(s); + if (t) ExecSetSlotDescriptor(res, t); @@ -1063,7 +1151,7 @@ CustomEvalParamExternCompat(Param *param, return prm; } -void set_append_rel_size_compat(PlannerInfo *root, RelOptInfo *rel, Index rti); +void set_append_rel_size_compat(PlannerInfo *root, RelOptInfo *rel, Index rti); /* * lnext() @@ -1101,4 +1189,60 @@ void set_append_rel_size_compat(PlannerInfo *root, RelOptInfo *rel, Index rti); #define convert_tuples_by_name_compat(i, o, m) convert_tuples_by_name((i), (o), (m)) #endif -#endif /* PG_COMPAT_H */ +/* + * raw_parser() + * In 14 new argument was added (844fe9f159a) + */ +#if PG_VERSION_NUM >= 140000 +#define raw_parser_compat(s) raw_parser((s), RAW_PARSE_DEFAULT) +#else +#define raw_parser_compat(s) raw_parser(s) +#endif + +/* + * make_restrictinfo() + * In >=16 4th, 5th and 9th arguments were added (991a3df227e) + * In >=16 3th and 9th arguments were removed (b448f1c8d83) + * In >=14 new argument was added (55dc86eca70) + */ +#if PG_VERSION_NUM >= 160000 +#define make_restrictinfo_compat(r, c, ipd, od, p, sl, rr, or, nr) make_restrictinfo((r), (c), (ipd), false, false, (p), (sl), (rr), NULL, (or)) +#else +#if PG_VERSION_NUM >= 140000 +#define make_restrictinfo_compat(r, c, ipd, od, p, sl, rr, or, nr) make_restrictinfo((r), (c), (ipd), (od), (p), (sl), (rr), (or), (nr)) +#else +#define make_restrictinfo_compat(r, c, ipd, od, p, sl, rr, or, nr) make_restrictinfo((c), (ipd), (od), (p), (sl), (rr), (or), (nr)) +#endif /* #if PG_VERSION_NUM >= 140000 */ +#endif /* #if PG_VERSION_NUM >= 160000 */ + +/* + * pull_varnos() + * In >=14 new argument was added (55dc86eca70) + */ +#if PG_VERSION_NUM >= 140000 +#define pull_varnos_compat(r, n) pull_varnos((r), (n)) +#else +#define pull_varnos_compat(r, n) pull_varnos(n) +#endif + +/* + * build_expression_pathkey() + * In >=16 argument was removed (b448f1c8d83) + */ +#if PG_VERSION_NUM >= 160000 +#define build_expression_pathkey_compat(root, expr, nullable_relids, opno, rel, create_it) build_expression_pathkey(root, expr, opno, rel, create_it) +#else +#define build_expression_pathkey_compat(root, expr, nullable_relids, opno, rel, create_it) build_expression_pathkey(root, expr, nullable_relids, opno, rel, create_it) +#endif + +/* + * EvalPlanQualInit() + * In >=16 argument was added (70b42f27902) + */ +#if PG_VERSION_NUM >= 160000 +#define EvalPlanQualInit_compat(epqstate, parentestate, subplan, auxrowmarks, epqParam) EvalPlanQualInit(epqstate, parentestate, subplan, auxrowmarks, epqParam, NIL) +#else +#define EvalPlanQualInit_compat(epqstate, parentestate, subplan, auxrowmarks, epqParam) EvalPlanQualInit(epqstate, parentestate, subplan, auxrowmarks, epqParam) +#endif + +#endif /* PG_COMPAT_H */ diff --git a/src/include/compat/rowmarks_fix.h b/src/include/compat/rowmarks_fix.h index 09e5fbef..c94504c3 100644 --- a/src/include/compat/rowmarks_fix.h +++ b/src/include/compat/rowmarks_fix.h @@ -45,5 +45,17 @@ void append_tle_for_rowmark(PlannerInfo *root, PlanRowMark *rc); #endif +/* + * add_vars_to_targetlist() + * In >=16 last argument was removed (b3ff6c742f6c) + */ +#if PG_VERSION_NUM >= 160000 +#define add_vars_to_targetlist_compat(root, vars, where_needed) \ + add_vars_to_targetlist((root), (vars), (where_needed)); +#else +#define add_vars_to_targetlist_compat(root, vars, where_needed) \ + add_vars_to_targetlist((root), (vars), (where_needed), true); +#endif + #endif /* ROWMARKS_FIX_H */ diff --git a/src/include/hooks.h b/src/include/hooks.h index 49d7e8f1..4d426f5a 100644 --- a/src/include/hooks.h +++ b/src/include/hooks.h @@ -28,6 +28,7 @@ extern post_parse_analyze_hook_type pathman_post_parse_analyze_hook_next; extern shmem_startup_hook_type pathman_shmem_startup_hook_next; extern ProcessUtility_hook_type pathman_process_utility_hook_next; extern ExecutorRun_hook_type pathman_executor_run_hook_next; +extern ExecutorStart_hook_type pathman_executor_start_hook_prev; void pathman_join_pathlist_hook(PlannerInfo *root, @@ -43,6 +44,7 @@ void pathman_rel_pathlist_hook(PlannerInfo *root, RangeTblEntry *rte); void pathman_enable_assign_hook(bool newval, void *extra); +bool pathman_enable_check_hook(bool *newval, void **extra, GucSource source); PlannedStmt * pathman_planner_hook(Query *parse, #if PG_VERSION_NUM >= 130000 @@ -51,14 +53,29 @@ PlannedStmt * pathman_planner_hook(Query *parse, int cursorOptions, ParamListInfo boundParams); +#if PG_VERSION_NUM >= 140000 +void pathman_post_parse_analyze_hook(ParseState *pstate, + Query *query, + JumbleState *jstate); +#else void pathman_post_parse_analyze_hook(ParseState *pstate, Query *query); +#endif void pathman_shmem_startup_hook(void); void pathman_relcache_hook(Datum arg, Oid relid); -#if PG_VERSION_NUM >= 130000 +#if PG_VERSION_NUM >= 140000 +void pathman_process_utility_hook(PlannedStmt *pstmt, + const char *queryString, + bool readOnlyTree, + ProcessUtilityContext context, + ParamListInfo params, + QueryEnvironment *queryEnv, + DestReceiver *dest, + QueryCompletion *qc); +#elif PG_VERSION_NUM >= 130000 void pathman_process_utility_hook(PlannedStmt *pstmt, const char *queryString, ProcessUtilityContext context, @@ -100,4 +117,6 @@ void pathman_executor_hook(QueryDesc *queryDesc, ExecutorRun_CountArgType count); #endif +void pathman_executor_start_hook(QueryDesc *queryDescc, + int eflags); #endif /* PATHMAN_HOOKS_H */ diff --git a/src/include/init.h b/src/include/init.h index f2234c8f..58335c46 100644 --- a/src/include/init.h +++ b/src/include/init.h @@ -171,8 +171,8 @@ void *pathman_cache_search_relid(HTAB *cache_table, /* * Save and restore PathmanInitState. */ -void save_pathman_init_state(PathmanInitState *temp_init_state); -void restore_pathman_init_state(const PathmanInitState *temp_init_state); +void save_pathman_init_state(volatile PathmanInitState *temp_init_state); +void restore_pathman_init_state(const volatile PathmanInitState *temp_init_state); /* * Create main GUC variables. diff --git a/src/include/partition_filter.h b/src/include/partition_filter.h index 233054b7..4aae0bbb 100644 --- a/src/include/partition_filter.h +++ b/src/include/partition_filter.h @@ -48,6 +48,7 @@ typedef struct Oid partid; /* partition's relid */ ResultRelInfo *result_rel_info; /* cached ResultRelInfo */ TupleConversionMap *tuple_map; /* tuple mapping (parent => child) */ + TupleConversionMap *tuple_map_child; /* tuple mapping (child => child), for exclude 'ctid' */ PartRelationInfo *prel; /* this child might be a parent... */ ExprState *prel_expr_state; /* and have its own part. expression */ @@ -100,6 +101,9 @@ struct ResultPartsStorage PartRelationInfo *prel; ExprState *prel_expr_state; ExprContext *prel_econtext; +#if PG_VERSION_NUM >= 160000 /* for commit a61b1f74823c */ + ResultRelInfo *init_rri; /* first initialized ResultRelInfo */ +#endif }; typedef struct @@ -115,6 +119,11 @@ typedef struct CmdType command_type; TupleTableSlot *tup_convert_slot; /* slot for rebuilt tuples */ + +#if PG_VERSION_NUM >= 160000 /* for commit 178ee1d858 */ + Index parent_rti; /* Parent RT index for use of EXPLAIN, + see "ModifyTable::nominalRelation" */ +#endif } PartitionFilterState; @@ -166,22 +175,26 @@ void init_result_parts_storage(ResultPartsStorage *parts_storage, void fini_result_parts_storage(ResultPartsStorage *parts_storage); /* Find ResultRelInfo holder in storage */ -ResultRelInfoHolder * scan_result_parts_storage(ResultPartsStorage *storage, Oid partid); +ResultRelInfoHolder * scan_result_parts_storage(EState *estate, ResultPartsStorage *storage, Oid partid); /* Refresh PartRelationInfo in storage */ PartRelationInfo * refresh_result_parts_storage(ResultPartsStorage *parts_storage, Oid partid); TupleConversionMap * build_part_tuple_map(Relation parent_rel, Relation child_rel); -List * pfilter_build_tlist(Plan *subplan); +TupleConversionMap * build_part_tuple_map_child(Relation child_rel); + +void destroy_tuple_map(TupleConversionMap *tuple_map); +List * pfilter_build_tlist(Plan *subplan, Index varno); /* Find suitable partition using 'value' */ Oid * find_partitions_for_value(Datum value, Oid value_type, const PartRelationInfo *prel, int *nparts); -ResultRelInfoHolder *select_partition_for_insert(ResultPartsStorage *parts_storage, +ResultRelInfoHolder *select_partition_for_insert(EState *estate, + ResultPartsStorage *parts_storage, TupleTableSlot *slot); Plan * make_partition_filter(Plan *subplan, diff --git a/src/include/partition_router.h b/src/include/partition_router.h index 8240d13b..d5684eba 100644 --- a/src/include/partition_router.h +++ b/src/include/partition_router.h @@ -32,7 +32,9 @@ typedef struct PartitionRouterState Plan *subplan; /* proxy variable to store subplan */ ExprState *constraint; /* should tuple remain in partition? */ +#if PG_VERSION_NUM < 140000 /* field removed in 86dc90056dfd */ JunkFilter *junkfilter; /* 'ctid' extraction facility */ +#endif ResultRelInfo *current_rri; /* Machinery required for EvalPlanQual */ @@ -42,6 +44,9 @@ typedef struct PartitionRouterState /* Preserved slot from last call */ bool yielded; TupleTableSlot *yielded_slot; +#if PG_VERSION_NUM >= 140000 + TupleTableSlot *yielded_original_slot; +#endif /* Need these for a GREAT deal of hackery */ ModifyTableState *mt_state; @@ -66,8 +71,6 @@ extern CustomExecMethods partition_router_exec_methods; #define MTHackField(mt_state, field) ( (mt_state)->field ) void init_partition_router_static_data(void); -void prepare_modify_table_for_partition_router(PlanState *state, - void *context); void partition_router_begin(CustomScanState *node, EState *estate, int eflags); void partition_router_end(CustomScanState *node); void partition_router_rescan(CustomScanState *node); @@ -75,7 +78,7 @@ void partition_router_explain(CustomScanState *node, List *ancestors, ExplainState *es); -Plan *make_partition_router(Plan *subplan, int epq_param); +Plan *make_partition_router(Plan *subplan, int epq_param, Index parent_rti); Node *partition_router_create_scan_state(CustomScan *node); TupleTableSlot *partition_router_exec(CustomScanState *node); diff --git a/src/include/pathman.h b/src/include/pathman.h index b9acfe59..28f6ef30 100644 --- a/src/include/pathman.h +++ b/src/include/pathman.h @@ -118,7 +118,8 @@ Index append_child_relation(PlannerInfo *root, * Copied from PostgreSQL (prepunion.c) */ void make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, List **translated_vars); + Index newvarno, List **translated_vars, + AppendRelInfo *appinfo); Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); diff --git a/src/include/utils.h b/src/include/utils.h index 1e0b87a4..566c04db 100644 --- a/src/include/utils.h +++ b/src/include/utils.h @@ -84,5 +84,6 @@ Datum extract_binary_interval_from_text(Datum interval_text, Oid *interval_type); char **deconstruct_text_array(Datum array, int *array_size); RangeVar **qualified_relnames_to_rangevars(char **relnames, size_t nrelnames); +void check_relation_oid(Oid relid); #endif /* PATHMAN_UTILS_H */ diff --git a/src/init.c b/src/init.c index 86e96ebe..1907d9dc 100644 --- a/src/init.c +++ b/src/init.c @@ -134,13 +134,13 @@ pathman_cache_search_relid(HTAB *cache_table, */ void -save_pathman_init_state(PathmanInitState *temp_init_state) +save_pathman_init_state(volatile PathmanInitState *temp_init_state) { *temp_init_state = pathman_init_state; } void -restore_pathman_init_state(const PathmanInitState *temp_init_state) +restore_pathman_init_state(const volatile PathmanInitState *temp_init_state) { /* * initialization_needed is not restored: it is not just a setting but @@ -166,7 +166,7 @@ init_main_pathman_toggles(void) DEFAULT_PATHMAN_ENABLE, PGC_SUSET, 0, - NULL, + pathman_enable_check_hook, pathman_enable_assign_hook, NULL); @@ -273,7 +273,8 @@ static bool init_pathman_relation_oids(void) { Oid schema = get_pathman_schema(); - Assert(schema != InvalidOid); + if (schema == InvalidOid) + return false; /* extension can be dropped by another backend */ /* Cache PATHMAN_CONFIG relation's Oid */ pathman_config_relid = get_relname_relid(PATHMAN_CONFIG, schema); @@ -569,7 +570,7 @@ find_inheritance_children_array(Oid parent_relid, char * build_check_constraint_name_relid_internal(Oid relid) { - AssertArg(OidIsValid(relid)); + Assert(OidIsValid(relid)); return build_check_constraint_name_relname_internal(get_rel_name(relid)); } @@ -580,7 +581,7 @@ build_check_constraint_name_relid_internal(Oid relid) char * build_check_constraint_name_relname_internal(const char *relname) { - AssertArg(relname != NULL); + Assert(relname != NULL); return psprintf("pathman_%s_check", relname); } @@ -591,7 +592,7 @@ build_check_constraint_name_relname_internal(const char *relname) char * build_sequence_name_relid_internal(Oid relid) { - AssertArg(OidIsValid(relid)); + Assert(OidIsValid(relid)); return build_sequence_name_relname_internal(get_rel_name(relid)); } @@ -602,7 +603,7 @@ build_sequence_name_relid_internal(Oid relid) char * build_sequence_name_relname_internal(const char *relname) { - AssertArg(relname != NULL); + Assert(relname != NULL); return psprintf("%s_seq", relname); } @@ -613,7 +614,7 @@ build_sequence_name_relname_internal(const char *relname) char * build_update_trigger_name_internal(Oid relid) { - AssertArg(OidIsValid(relid)); + Assert(OidIsValid(relid)); return psprintf("%s_upd_trig", get_rel_name(relid)); } @@ -624,7 +625,7 @@ build_update_trigger_name_internal(Oid relid) char * build_update_trigger_func_name_internal(Oid relid) { - AssertArg(OidIsValid(relid)); + Assert(OidIsValid(relid)); return psprintf("%s_upd_trig_func", get_rel_name(relid)); } @@ -930,7 +931,7 @@ read_opexpr_const(const OpExpr *opexpr, /* Update RIGHT */ right = (Node *) constant; } - /* FALL THROUGH (no break) */ + /* FALLTHROUGH */ case T_Const: { diff --git a/src/nodes_common.c b/src/nodes_common.c index c2a02649..f4ebc6b1 100644 --- a/src/nodes_common.c +++ b/src/nodes_common.c @@ -59,7 +59,7 @@ transform_plans_into_states(RuntimeAppendState *scan_state, ChildScanCommon child; PlanState *ps; - AssertArg(selected_plans); + Assert(selected_plans); child = selected_plans[i]; /* Create new node since this plan hasn't been used yet */ @@ -184,6 +184,42 @@ build_parent_tlist(List *tlist, AppendRelInfo *appinfo) return temp_tlist; } +#if PG_VERSION_NUM >= 140000 +/* + * Function "tlist_member_ignore_relabel" was removed in vanilla (375398244168) + * Function moved to pg_pathman. + */ +/* + * tlist_member_ignore_relabel + * Finds the (first) member of the given tlist whose expression is + * equal() to the given expression. Result is NULL if no such member. + * We ignore top-level RelabelType nodes + * while checking for a match. This is needed for some scenarios + * involving binary-compatible sort operations. + */ +TargetEntry * +tlist_member_ignore_relabel(Expr *node, List *targetlist) +{ + ListCell *temp; + + while (node && IsA(node, RelabelType)) + node = ((RelabelType *) node)->arg; + + foreach(temp, targetlist) + { + TargetEntry *tlentry = (TargetEntry *) lfirst(temp); + Expr *tlexpr = tlentry->expr; + + while (tlexpr && IsA(tlexpr, RelabelType)) + tlexpr = ((RelabelType *) tlexpr)->arg; + + if (equal(node, tlexpr)) + return tlentry; + } + return NULL; +} +#endif + /* Is tlist 'a' subset of tlist 'b'? (in terms of Vars) */ static bool tlist_is_var_subset(List *a, List *b) @@ -565,7 +601,10 @@ create_append_plan_common(PlannerInfo *root, RelOptInfo *rel, CustomScan *cscan; prel = get_pathman_relation_info(rpath->relid); - Assert(prel); + if (!prel) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("table \"%s\" is not partitioned", + get_rel_name_or_relid(rpath->relid)))); cscan = makeNode(CustomScan); cscan->custom_scan_tlist = NIL; /* initial value (empty list) */ @@ -673,7 +712,15 @@ begin_append_common(CustomScanState *node, EState *estate, int eflags) #endif scan_state->prel = get_pathman_relation_info(scan_state->relid); - Assert(scan_state->prel); + /* + * scan_state->prel can be NULL in case execution of prepared query that + * was prepared before DROP/CREATE EXTENSION pg_pathman or after + * pathman_config table truncation etc. + */ + if (!scan_state->prel) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("table \"%s\" is not partitioned", + get_rel_name_or_relid(scan_state->relid)))); /* Prepare expression according to set_set_customscan_references() */ scan_state->prel_expr = PrelExpressionForRelid(scan_state->prel, INDEX_VAR); diff --git a/src/partition_creation.c b/src/partition_creation.c index 65335c65..d6080c85 100644 --- a/src/partition_creation.c +++ b/src/partition_creation.c @@ -92,8 +92,13 @@ static void postprocess_child_table_and_atts(Oid parent_relid, Oid partition_rel static Oid text_to_regprocedure(text *proname_args); static Constraint *make_constraint_common(char *name, Node *raw_expr); -static Value make_string_value_struct(char *str); +#if PG_VERSION_NUM >= 150000 /* for commit 639a86e36aae */ +static String make_string_value_struct(char *str); +static Integer make_int_value_struct(int int_val); +#else +static Value make_string_value_struct(char* str); static Value make_int_value_struct(int int_val); +#endif static Node *build_partitioning_expression(Oid parent_relid, Oid *expr_type, @@ -580,6 +585,7 @@ spawn_partitions_val(Oid parent_relid, /* parent's Oid */ Oid parent_nsp = get_rel_namespace(parent_relid); char *parent_nsp_name = get_namespace_name(parent_nsp); char *partition_name = choose_range_partition_name(parent_relid, parent_nsp); + char *pathman_schema; /* Assign the 'following' boundary to current 'leading' value */ cur_following_bound = cur_leading_bound; @@ -601,14 +607,19 @@ spawn_partitions_val(Oid parent_relid, /* parent's Oid */ /* Get typname of range_bound_type to perform cast */ typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(range_bound_type)); - Assert(HeapTupleIsValid(typeTuple)); + if (!HeapTupleIsValid(typeTuple)) + elog(ERROR, "cache lookup failed for type %u", range_bound_type); typname = pstrdup(NameStr(((Form_pg_type) GETSTRUCT(typeTuple))->typname)); ReleaseSysCache(typeTuple); + pathman_schema = get_namespace_name(get_pathman_schema()); + if (pathman_schema == NULL) + elog(ERROR, "pg_pathman schema not initialized"); + /* Construct call to create_single_range_partition() */ create_sql = psprintf( "select %s.create_single_range_partition('%s.%s'::regclass, '%s'::%s, '%s'::%s, '%s.%s', NULL::text)", - quote_identifier(get_namespace_name(get_pathman_schema())), + quote_identifier(pathman_schema), quote_identifier(parent_nsp_name), quote_identifier(get_rel_name(parent_relid)), IsInfinite(&bounds[0]) ? "NULL" : datum_to_cstring(bounds[0].value, range_bound_type), @@ -990,7 +1001,7 @@ postprocess_child_table_and_atts(Oid parent_relid, Oid partition_relid) parent_rel = heap_open_compat(parent_relid, NoLock); partition_rel = heap_open_compat(partition_relid, NoLock); - make_inh_translation_list(parent_rel, partition_rel, 0, &translated_vars); + make_inh_translation_list(parent_rel, partition_rel, 0, &translated_vars, NULL); heap_close_compat(parent_rel, NoLock); heap_close_compat(partition_rel, NoLock); @@ -1189,6 +1200,8 @@ copy_foreign_keys(Oid parent_relid, Oid partition_oid) /* Fetch pg_pathman's schema */ pathman_schema = get_namespace_name(get_pathman_schema()); + if (pathman_schema == NULL) + elog(ERROR, "pg_pathman schema not initialized"); /* Build function's name */ copy_fkeys_proc_name = list_make2(makeString(pathman_schema), @@ -1356,12 +1369,21 @@ build_raw_range_check_tree(Node *raw_expression, const Bound *end_value, Oid value_type) { +#if PG_VERSION_NUM >= 150000 /* for commit 639a86e36aae */ +#define BuildConstExpr(node, value, value_type) \ + do { \ + (node)->val.sval = make_string_value_struct( \ + datum_to_cstring((value), (value_type))); \ + (node)->location = -1; \ + } while (0) +#else #define BuildConstExpr(node, value, value_type) \ do { \ (node)->val = make_string_value_struct( \ datum_to_cstring((value), (value_type))); \ (node)->location = -1; \ } while (0) +#endif #define BuildCmpExpr(node, opname, expr, c) \ do { \ @@ -1549,16 +1571,25 @@ build_raw_hash_check_tree(Node *raw_expression, Oid hash_proc; TypeCacheEntry *tce; + char *pathman_schema; tce = lookup_type_cache(value_type, TYPECACHE_HASH_PROC); hash_proc = tce->hash_proc; /* Total amount of partitions */ +#if PG_VERSION_NUM >= 150000 /* for commit 639a86e36aae */ + part_count_c->val.ival = make_int_value_struct(part_count); +#else part_count_c->val = make_int_value_struct(part_count); +#endif part_count_c->location = -1; /* Index of this partition (hash % total amount) */ +#if PG_VERSION_NUM >= 150000 /* for commit 639a86e36aae */ + part_idx_c->val.ival = make_int_value_struct(part_idx); +#else part_idx_c->val = make_int_value_struct(part_idx); +#endif part_idx_c->location = -1; /* Call hash_proc() */ @@ -1573,9 +1604,13 @@ build_raw_hash_check_tree(Node *raw_expression, hash_call->over = NULL; hash_call->location = -1; + pathman_schema = get_namespace_name(get_pathman_schema()); + if (pathman_schema == NULL) + elog(ERROR, "pg_pathman schema not initialized"); + /* Build schema-qualified name of function get_hash_part_idx() */ get_hash_part_idx_proc = - list_make2(makeString(get_namespace_name(get_pathman_schema())), + list_make2(makeString(pathman_schema), makeString("get_hash_part_idx")); /* Call get_hash_part_idx() */ @@ -1649,6 +1684,29 @@ make_constraint_common(char *name, Node *raw_expr) return constraint; } +#if PG_VERSION_NUM >= 150000 /* for commits 639a86e36aae, c4cc2850f4d1 */ +static String +make_string_value_struct(char* str) +{ + String val; + + val.type = T_String; + val.sval = str; + + return val; +} + +static Integer +make_int_value_struct(int int_val) +{ + Integer val; + + val.type = T_Integer; + val.ival = int_val; + + return val; +} +#else static Value make_string_value_struct(char *str) { @@ -1670,7 +1728,7 @@ make_int_value_struct(int int_val) return val; } - +#endif /* PG_VERSION_NUM >= 150000 */ /* * --------------------- @@ -1982,17 +2040,15 @@ build_partitioning_expression(Oid parent_relid, /* We need expression type for hash functions */ if (expr_type) { - Node *expr; - expr = cook_partitioning_expression(parent_relid, expr_cstr, NULL); - /* Finally return expression type */ - *expr_type = exprType(expr); + *expr_type = exprType( + cook_partitioning_expression(parent_relid, expr_cstr, NULL)); } if (columns) { /* Column list should be empty */ - AssertArg(*columns == NIL); + Assert(*columns == NIL); extract_column_names(expr, columns); } diff --git a/src/partition_filter.c b/src/partition_filter.c index 3808dc26..3d5e4bd3 100644 --- a/src/partition_filter.c +++ b/src/partition_filter.c @@ -14,6 +14,7 @@ #include "pathman.h" #include "partition_creation.h" #include "partition_filter.h" +#include "partition_router.h" #include "utils.h" #include "access/htup_details.h" @@ -26,6 +27,9 @@ #include "foreign/fdwapi.h" #include "foreign/foreign.h" #include "nodes/nodeFuncs.h" +#if PG_VERSION_NUM >= 160000 /* for commit a61b1f74823c */ +#include "parser/parse_relation.h" +#endif #include "rewrite/rewriteManip.h" #include "utils/guc.h" #include "utils/memutils.h" @@ -238,13 +242,9 @@ fini_result_parts_storage(ResultPartsStorage *parts_storage) } /* Free conversion-related stuff */ - if (rri_holder->tuple_map) - { - FreeTupleDesc(rri_holder->tuple_map->indesc); - FreeTupleDesc(rri_holder->tuple_map->outdesc); + destroy_tuple_map(rri_holder->tuple_map); - free_conversion_map(rri_holder->tuple_map); - } + destroy_tuple_map(rri_holder->tuple_map_child); /* Don't forget to close 'prel'! */ if (rri_holder->prel) @@ -260,7 +260,8 @@ fini_result_parts_storage(ResultPartsStorage *parts_storage) /* Find a ResultRelInfo for the partition using ResultPartsStorage */ ResultRelInfoHolder * -scan_result_parts_storage(ResultPartsStorage *parts_storage, Oid partid) +scan_result_parts_storage(EState *estate, ResultPartsStorage *parts_storage, + Oid partid) { #define CopyToResultRelInfo(field_name) \ ( child_result_rel_info->field_name = parts_storage->base_rri->field_name ) @@ -283,6 +284,12 @@ scan_result_parts_storage(ResultPartsStorage *parts_storage, Oid partid) ResultRelInfo *child_result_rel_info; List *translated_vars; MemoryContext old_mcxt; +#if PG_VERSION_NUM >= 160000 /* for commit a61b1f74823c */ + RTEPermissionInfo *parent_perminfo, + *child_perminfo; + /* ResultRelInfo of partitioned table. */ + RangeTblEntry *init_rte; +#endif /* Lock partition and check if it exists */ LockRelationOid(partid, parts_storage->head_open_lock_mode); @@ -309,15 +316,41 @@ scan_result_parts_storage(ResultPartsStorage *parts_storage, Oid partid) /* Open child relation and check if it is a valid target */ child_rel = heap_open_compat(partid, NoLock); - /* Build Var translation list for 'inserted_cols' */ - make_inh_translation_list(base_rel, child_rel, 0, &translated_vars); - /* Create RangeTblEntry for partition */ child_rte = makeNode(RangeTblEntry); child_rte->rtekind = RTE_RELATION; child_rte->relid = partid; child_rte->relkind = child_rel->rd_rel->relkind; child_rte->eref = parent_rte->eref; +#if PG_VERSION_NUM >= 160000 /* for commit a61b1f74823c */ + /* Build Var translation list for 'inserted_cols' */ + make_inh_translation_list(parts_storage->init_rri->ri_RelationDesc, + child_rel, 0, &translated_vars, NULL); + + /* + * Need to use ResultRelInfo of partitioned table 'init_rri' because + * 'base_rri' can be ResultRelInfo of partition without any + * ResultRelInfo, see expand_single_inheritance_child(). + */ + init_rte = rt_fetch(parts_storage->init_rri->ri_RangeTableIndex, + parts_storage->estate->es_range_table); + parent_perminfo = getRTEPermissionInfo(estate->es_rteperminfos, init_rte); + + child_rte->perminfoindex = 0; /* expected by addRTEPermissionInfo() */ + child_perminfo = addRTEPermissionInfo(&estate->es_rteperminfos, child_rte); + child_perminfo->requiredPerms = parent_perminfo->requiredPerms; + child_perminfo->checkAsUser = parent_perminfo->checkAsUser; + child_perminfo->insertedCols = translate_col_privs(parent_perminfo->insertedCols, + translated_vars); + child_perminfo->updatedCols = translate_col_privs(parent_perminfo->updatedCols, + translated_vars); + + /* Check permissions for one partition */ + ExecCheckOneRtePermissions(child_rte, child_perminfo, true); +#else + /* Build Var translation list for 'inserted_cols' */ + make_inh_translation_list(base_rel, child_rel, 0, &translated_vars, NULL); + child_rte->requiredPerms = parent_rte->requiredPerms; child_rte->checkAsUser = parent_rte->checkAsUser; child_rte->insertedCols = translate_col_privs(parent_rte->insertedCols, @@ -327,6 +360,7 @@ scan_result_parts_storage(ResultPartsStorage *parts_storage, Oid partid) /* Check permissions for partition */ ExecCheckRTPerms(list_make1(child_rte), true); +#endif /* Append RangeTblEntry to estate->es_range_table */ child_rte_idx = append_rte_to_estate(parts_storage->estate, child_rte, child_rel); @@ -353,10 +387,13 @@ scan_result_parts_storage(ResultPartsStorage *parts_storage, Oid partid) CopyToResultRelInfo(ri_onConflictSetWhere); #endif +#if PG_VERSION_NUM < 140000 + /* field "ri_junkFilter" removed in 86dc90056dfd */ if (parts_storage->command_type != CMD_UPDATE) CopyToResultRelInfo(ri_junkFilter); else child_result_rel_info->ri_junkFilter = NULL; +#endif /* ri_ConstraintExprs will be initialized by ExecRelCheck() */ child_result_rel_info->ri_ConstraintExprs = NULL; @@ -377,6 +414,13 @@ scan_result_parts_storage(ResultPartsStorage *parts_storage, Oid partid) */ rri_holder->tuple_map = build_part_tuple_map(base_rel, child_rel); + /* + * Field for child->child tuple transformation map. We need to + * convert tuples because child TupleDesc might have extra + * columns ('ctid' etc.) and need remove them. + */ + rri_holder->tuple_map_child = NULL; + /* Default values */ rri_holder->prel = NULL; rri_holder->prel_expr_state = NULL; @@ -464,6 +508,75 @@ build_part_tuple_map(Relation base_rel, Relation child_rel) return tuple_map; } +/* + * Build tuple conversion map (e.g. partition tuple has extra column(s)). + * We create a special tuple map (tuple_map_child), which, when applied to the + * tuple of partition, translates the tuple attributes into the tuple + * attributes of the same partition, discarding service attributes like "ctid" + * (i.e. working like junkFilter). + */ +TupleConversionMap * +build_part_tuple_map_child(Relation child_rel) +{ + TupleConversionMap *tuple_map; + TupleDesc child_tupdesc1; + TupleDesc child_tupdesc2; + int n; +#if PG_VERSION_NUM >= 130000 + AttrMap *attrMap; +#else + AttrNumber *attrMap; +#endif + + child_tupdesc1 = CreateTupleDescCopy(RelationGetDescr(child_rel)); + child_tupdesc1->tdtypeid = InvalidOid; + + child_tupdesc2 = CreateTupleDescCopy(RelationGetDescr(child_rel)); + child_tupdesc2->tdtypeid = InvalidOid; + + /* Generate tuple transformation map */ +#if PG_VERSION_NUM >= 160000 /* for commit ad86d159b6ab */ + attrMap = build_attrmap_by_name(child_tupdesc1, child_tupdesc2, false); +#elif PG_VERSION_NUM >= 130000 + attrMap = build_attrmap_by_name(child_tupdesc1, child_tupdesc2); +#else + attrMap = convert_tuples_by_name_map(child_tupdesc1, child_tupdesc2, + ERR_PART_DESC_CONVERT); +#endif + + /* Prepare the map structure */ + tuple_map = (TupleConversionMap *) palloc(sizeof(TupleConversionMap)); + tuple_map->indesc = child_tupdesc1; + tuple_map->outdesc = child_tupdesc2; + tuple_map->attrMap = attrMap; + + /* preallocate workspace for Datum arrays */ + n = child_tupdesc1->natts; + tuple_map->outvalues = (Datum *) palloc(n * sizeof(Datum)); + tuple_map->outisnull = (bool *) palloc(n * sizeof(bool)); + + n = child_tupdesc1->natts + 1; /* +1 for NULL */ + tuple_map->invalues = (Datum *) palloc(n * sizeof(Datum)); + tuple_map->inisnull = (bool *) palloc(n * sizeof(bool)); + + tuple_map->invalues[0] = (Datum) 0; /* set up the NULL entry */ + tuple_map->inisnull[0] = true; + + return tuple_map; +} + +/* Destroy tuple conversion map */ +void +destroy_tuple_map(TupleConversionMap *tuple_map) +{ + if (tuple_map) + { + FreeTupleDesc(tuple_map->indesc); + FreeTupleDesc(tuple_map->outdesc); + + free_conversion_map(tuple_map); + } +} /* * ----------------------------------- @@ -512,7 +625,8 @@ find_partitions_for_value(Datum value, Oid value_type, * Smart wrapper for scan_result_parts_storage(). */ ResultRelInfoHolder * -select_partition_for_insert(ResultPartsStorage *parts_storage, +select_partition_for_insert(EState *estate, + ResultPartsStorage *parts_storage, TupleTableSlot *slot) { PartRelationInfo *prel = parts_storage->prel; @@ -563,7 +677,7 @@ select_partition_for_insert(ResultPartsStorage *parts_storage, else partition_relid = parts[0]; /* Get ResultRelationInfo holder for the selected partition */ - result = scan_result_parts_storage(parts_storage, partition_relid); + result = scan_result_parts_storage(estate, parts_storage, partition_relid); /* Somebody has dropped or created partitions */ if ((nparts == 0 || result == NULL) && !PrelIsFresh(prel)) @@ -698,17 +812,21 @@ make_partition_filter(Plan *subplan, cscan->scan.scanrelid = 0; /* Build an appropriate target list */ - cscan->scan.plan.targetlist = pfilter_build_tlist(subplan); - - /* Prepare 'custom_scan_tlist' for EXPLAIN (VERBOSE) */ - cscan->custom_scan_tlist = copyObject(cscan->scan.plan.targetlist); - ChangeVarNodes((Node *) cscan->custom_scan_tlist, INDEX_VAR, parent_rti, 0); + cscan->scan.plan.targetlist = pfilter_build_tlist(subplan, parent_rti); /* Pack partitioned table's Oid and conflict_action */ +#if PG_VERSION_NUM >= 160000 /* for commit 178ee1d858 */ + cscan->custom_private = list_make5(makeInteger(parent_relid), + makeInteger(conflict_action), + returning_list, + makeInteger(command_type), + makeInteger(parent_rti)); +#else cscan->custom_private = list_make4(makeInteger(parent_relid), makeInteger(conflict_action), returning_list, makeInteger(command_type)); +#endif return &cscan->scan.plan; } @@ -731,6 +849,9 @@ partition_filter_create_scan_state(CustomScan *node) state->on_conflict_action = intVal(lsecond(node->custom_private)); state->returning_list = (List *) lthird(node->custom_private); state->command_type = (CmdType) intVal(lfourth(node->custom_private)); +#if PG_VERSION_NUM >= 160000 /* for commit 178ee1d858 */ + state->parent_rti = (Index) intVal(lfirst(list_nth_cell(node->custom_private, 4))); +#endif /* Check boundaries */ Assert(state->on_conflict_action >= ONCONFLICT_NONE || @@ -763,8 +884,55 @@ partition_filter_begin(CustomScanState *node, EState *estate, int eflags) state->on_conflict_action != ONCONFLICT_NONE, RPS_RRI_CB(prepare_rri_for_insert, state), RPS_RRI_CB(NULL, NULL)); +#if PG_VERSION_NUM >= 160000 /* for commit a61b1f74823c */ + /* ResultRelInfo of partitioned table. */ + { + RangeTblEntry *rte = rt_fetch(current_rri->ri_RangeTableIndex, estate->es_range_table); + + if (rte->perminfoindex > 0) + state->result_parts.init_rri = current_rri; + else + { + /* + * Additional changes for 178ee1d858d: we cannot use current_rri + * because RTE for this ResultRelInfo has perminfoindex = 0. Need + * to use parent_rti (modify_table->nominalRelation) instead. + */ + Assert(state->parent_rti > 0); + state->result_parts.init_rri = estate->es_result_relations[state->parent_rti - 1]; + if (!state->result_parts.init_rri) + elog(ERROR, "cannot determine result info for partitioned table"); + } + } +#endif } +#if PG_VERSION_NUM >= 140000 +/* + * Re-initialization of PartitionFilterState for using new partition with new + * "current_rri" + */ +static void +reint_partition_filter_state(PartitionFilterState *state, ResultRelInfo *current_rri) +{ + Oid parent_relid = state->partitioned_table; + EState *estate = state->result_parts.estate; + + fini_result_parts_storage(&state->result_parts); + + state->returning_list = current_rri->ri_returningList; + + /* Init ResultRelInfo cache */ + init_result_parts_storage(&state->result_parts, + parent_relid, current_rri, + estate, state->command_type, + RPS_SKIP_RELATIONS, + state->on_conflict_action != ONCONFLICT_NONE, + RPS_RRI_CB(prepare_rri_for_insert, state), + RPS_RRI_CB(NULL, NULL)); +} +#endif + TupleTableSlot * partition_filter_exec(CustomScanState *node) { @@ -782,12 +950,31 @@ partition_filter_exec(CustomScanState *node) MemoryContext old_mcxt; ResultRelInfoHolder *rri_holder; ResultRelInfo *rri; + JunkFilter *junkfilter = NULL; +#if PG_VERSION_NUM >= 140000 + PartitionRouterState *pr_state = linitial(node->custom_ps); + + /* + * For 14: in case UPDATE command, we can scanning several partitions + * in one plan. Need to switch context each time partition is switched. + */ + if (IsPartitionRouterState(pr_state) && + state->result_parts.base_rri != pr_state->current_rri) + { /* + * Slot switched to new partition: need to + * reinitialize some PartitionFilterState variables + */ + reint_partition_filter_state(state, pr_state->current_rri); + } +#else + junkfilter = estate->es_result_relation_info->ri_junkFilter; +#endif /* Switch to per-tuple context */ old_mcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); /* Search for a matching partition */ - rri_holder = select_partition_for_insert(&state->result_parts, slot); + rri_holder = select_partition_for_insert(estate, &state->result_parts, slot); /* Switch back and clean up per-tuple context */ MemoryContextSwitchTo(old_mcxt); @@ -798,18 +985,58 @@ partition_filter_exec(CustomScanState *node) /* Magic: replace parent's ResultRelInfo with ours */ estate->es_result_relation_info = rri; + /* + * Besides 'transform map' we should process two cases: + * 1) CMD_UPDATE, row moved to other partition, junkfilter == NULL + * (filled in router_set_slot() for SELECT + INSERT); + * we should clear attribute 'ctid' (do not insert it into database); + * 2) CMD_INSERT/CMD_UPDATE operations for partitions with deleted column(s), + * junkfilter == NULL. + */ /* If there's a transform map, rebuild the tuple */ - if (rri_holder->tuple_map) + if (rri_holder->tuple_map || + (!junkfilter && + (state->command_type == CMD_INSERT || state->command_type == CMD_UPDATE) && + (slot->tts_tupleDescriptor->natts > rri->ri_RelationDesc->rd_att->natts /* extra fields */ +#if PG_VERSION_NUM < 120000 + /* + * If we have a regular physical tuple 'slot->tts_tuple' and + * it's locally palloc'd => we will use this tuple in + * ExecMaterializeSlot() instead of materialize the slot, so + * need to check number of attributes for this tuple: + */ + || (slot->tts_tuple && slot->tts_shouldFree && + HeapTupleHeaderGetNatts(slot->tts_tuple->t_data) > + rri->ri_RelationDesc->rd_att->natts /* extra fields */) +#endif + ))) { - Relation child_rel = rri->ri_RelationDesc; - - /* xxx why old code decided to materialize it? */ #if PG_VERSION_NUM < 120000 HeapTuple htup_old, htup_new; +#endif + Relation child_rel = rri->ri_RelationDesc; + TupleConversionMap *tuple_map; + + if (rri_holder->tuple_map) + tuple_map = rri_holder->tuple_map; + else + { + if (!rri_holder->tuple_map_child) + { /* + * Generate child->child tuple transformation map. We need to + * convert tuples because child TupleDesc has extra + * columns ('ctid' etc.) and need remove them. + */ + rri_holder->tuple_map_child = build_part_tuple_map_child(child_rel); + } + tuple_map = rri_holder->tuple_map_child; + } + /* xxx why old code decided to materialize it? */ +#if PG_VERSION_NUM < 120000 htup_old = ExecMaterializeSlot(slot); - htup_new = do_convert_tuple(htup_old, rri_holder->tuple_map); + htup_new = do_convert_tuple(htup_old, tuple_map); ExecClearTuple(slot); #endif @@ -826,7 +1053,7 @@ partition_filter_exec(CustomScanState *node) /* TODO: why should we *always* set a new slot descriptor? */ ExecSetSlotDescriptor(state->tup_convert_slot, RelationGetDescr(child_rel)); #if PG_VERSION_NUM >= 120000 - slot = execute_attr_map_slot(rri_holder->tuple_map->attrMap, slot, state->tup_convert_slot); + slot = execute_attr_map_slot(tuple_map->attrMap, slot, state->tup_convert_slot); #else slot = ExecStoreTuple(htup_new, state->tup_convert_slot, InvalidBuffer, true); #endif @@ -872,7 +1099,7 @@ partition_filter_explain(CustomScanState *node, List *ancestors, ExplainState *e * Build partition filter's target list pointing to subplan tuple's elements. */ List * -pfilter_build_tlist(Plan *subplan) +pfilter_build_tlist(Plan *subplan, Index varno) { List *result_tlist = NIL; ListCell *lc; @@ -892,7 +1119,7 @@ pfilter_build_tlist(Plan *subplan) } else { - Var *var = makeVar(INDEX_VAR, /* point to subplan's elements */ + Var *var = makeVar(varno, /* point to subplan's elements */ tle->resno, exprType((Node *) tle->expr), exprTypmod((Node *) tle->expr), @@ -1016,6 +1243,7 @@ prepare_rri_fdw_for_insert(ResultRelInfoHolder *rri_holder, elog(ERROR, "FDWs other than postgres_fdw are restricted"); + break; case PF_FDW_INSERT_ANY_FDW: elog(WARNING, "unrestricted FDW mode may lead to crashes"); @@ -1063,6 +1291,14 @@ prepare_rri_fdw_for_insert(ResultRelInfoHolder *rri_holder, query.targetList = NIL; query.returningList = NIL; +#if PG_VERSION_NUM >= 160000 /* for commit a61b1f74823c */ + /* + * Copy the RTEPermissionInfos into query as well, so that + * add_rte_to_flat_rtable() will work correctly. + */ + query.rteperminfos = estate->es_rteperminfos; +#endif + /* Generate 'query.targetList' using 'tupdesc' */ target_attr = 1; for (i = 0; i < tupdesc->natts; i++) @@ -1111,9 +1347,18 @@ prepare_rri_fdw_for_insert(ResultRelInfoHolder *rri_holder, NodeSetTag(&mtstate, T_ModifyTableState); mtstate.ps.state = estate; mtstate.operation = CMD_INSERT; +#if PG_VERSION_NUM >= 140000 + /* + * Some fields ("mt_plans", "mt_nplans", "mt_whichplan") removed + * in 86dc90056dfd + */ + outerPlanState(&mtstate.ps) = pstate_ptr; + mtstate.mt_nrels = 1; +#else mtstate.mt_plans = &pstate_ptr; mtstate.mt_nplans = 1; mtstate.mt_whichplan = 0; +#endif mtstate.resultRelInfo = rri; #if PG_VERSION_NUM < 110000 mtstate.mt_onconflict = ONCONFLICT_NONE; @@ -1254,9 +1499,40 @@ append_rte_to_estate(EState *estate, RangeTblEntry *rte, Relation child_rel) static int append_rri_to_estate(EState *estate, ResultRelInfo *rri) { - estate_mod_data *emd_struct = fetch_estate_mod_data(estate); - int result_rels_allocated = emd_struct->estate_alloc_result_rels; + estate_mod_data *emd_struct = fetch_estate_mod_data(estate); + int result_rels_allocated = emd_struct->estate_alloc_result_rels; +#if PG_VERSION_NUM >= 140000 /* reworked in commit a04daa97a433 */ + ResultRelInfo **rri_array = estate->es_result_relations; + + /* + * We already increased variable "estate->es_range_table_size" in previous + * call append_rte_to_estate(): see + * "estate->es_range_table_size = list_length(estate->es_range_table)" + * after "lappend(estate->es_range_table, rte)". So we should append + * new value in "estate->es_result_relations" only. + */ + /* Reallocate estate->es_result_relations if needed */ + if (result_rels_allocated < estate->es_range_table_size) + { + result_rels_allocated = result_rels_allocated * ALLOC_EXP + 1; + estate->es_result_relations = palloc(result_rels_allocated * + sizeof(ResultRelInfo *)); + memcpy(estate->es_result_relations, + rri_array, + (estate->es_range_table_size - 1) * sizeof(ResultRelInfo *)); + } + + estate->es_result_relations[estate->es_range_table_size - 1] = rri; + + estate->es_opened_result_relations = lappend(estate->es_opened_result_relations, rri); + + /* Update estate_mod_data */ + emd_struct->estate_alloc_result_rels = result_rels_allocated; + emd_struct->estate_not_modified = false; + + return estate->es_range_table_size; +#else /* Reallocate estate->es_result_relations if needed */ if (result_rels_allocated <= estate->es_num_result_relations) { @@ -1283,6 +1559,7 @@ append_rri_to_estate(EState *estate, ResultRelInfo *rri) emd_struct->estate_not_modified = false; return estate->es_num_result_relations++; +#endif } @@ -1317,7 +1594,15 @@ fetch_estate_mod_data(EState *estate) /* Have to create a new one */ emd_struct = MemoryContextAlloc(estate_mcxt, sizeof(estate_mod_data)); emd_struct->estate_not_modified = true; +#if PG_VERSION_NUM >= 140000 + /* + * Reworked in commit a04daa97a433: field "es_num_result_relations" + * removed + */ + emd_struct->estate_alloc_result_rels = estate->es_range_table_size; +#else emd_struct->estate_alloc_result_rels = estate->es_num_result_relations; +#endif cb = MemoryContextAlloc(estate_mcxt, sizeof(MemoryContextCallback)); cb->func = pf_memcxt_callback; diff --git a/src/partition_overseer.c b/src/partition_overseer.c index 41590425..d858374a 100644 --- a/src/partition_overseer.c +++ b/src/partition_overseer.c @@ -46,7 +46,7 @@ make_partition_overseer(Plan *subplan) cscan->scan.scanrelid = 0; /* Build an appropriate target list */ - cscan->scan.plan.targetlist = pfilter_build_tlist(subplan); + cscan->scan.plan.targetlist = pfilter_build_tlist(subplan, INDEX_VAR); cscan->custom_scan_tlist = subplan->targetlist; return &cscan->scan.plan; @@ -68,23 +68,32 @@ partition_overseer_create_scan_state(CustomScan *node) static void set_mt_state_for_router(PlanState *state, void *context) { +#if PG_VERSION_NUM < 140000 int i; - ModifyTableState *mt_state = (ModifyTableState *) state; +#endif + ModifyTableState *mt_state = (ModifyTableState *) state; - if (!IsA(state, ModifyTableState)) + if (!IsA(state, ModifyTableState)) return; +#if PG_VERSION_NUM >= 140000 + /* Fields "mt_plans", "mt_nplans" removed in 86dc90056dfd */ + { + CustomScanState *pf_state = (CustomScanState *) outerPlanState(mt_state); +#else for (i = 0; i < mt_state->mt_nplans; i++) { - CustomScanState *pf_state = (CustomScanState *) mt_state->mt_plans[i]; - PartitionRouterState *pr_state; - + CustomScanState *pf_state = (CustomScanState *) mt_state->mt_plans[i]; +#endif /* Check if this is a PartitionFilter + PartitionRouter combo */ - if (IsPartitionFilterState(pf_state) && - IsPartitionRouterState(pr_state = linitial(pf_state->custom_ps))) + if (IsPartitionFilterState(pf_state)) { - /* HACK: point to ModifyTable in PartitionRouter */ - pr_state->mt_state = mt_state; + PartitionRouterState *pr_state = linitial(pf_state->custom_ps); + if (IsPartitionRouterState(pr_state)) + { + /* HACK: point to ModifyTable in PartitionRouter */ + pr_state->mt_state = mt_state; + } } } } @@ -116,25 +125,40 @@ partition_overseer_exec(CustomScanState *node) mt_plans_new; /* Get initial signal */ +#if PG_VERSION_NUM >= 140000 /* field "mt_nplans" removed in 86dc90056dfd */ + mt_plans_old = mt_state->mt_nrels; +#else mt_plans_old = mt_state->mt_nplans; +#endif restart: /* Run ModifyTable */ slot = ExecProcNode((PlanState *) mt_state); /* Get current signal */ +#if PG_VERSION_NUM >= 140000 /* field "mt_nplans" removed in 86dc90056dfd */ + mt_plans_new = MTHackField(mt_state, mt_nrels); +#else mt_plans_new = MTHackField(mt_state, mt_nplans); +#endif /* Did PartitionRouter ask us to restart? */ if (mt_plans_new != mt_plans_old) { /* Signal points to current plan */ +#if PG_VERSION_NUM < 140000 int state_idx = -mt_plans_new; +#endif /* HACK: partially restore ModifyTable's state */ MTHackField(mt_state, mt_done) = false; +#if PG_VERSION_NUM >= 140000 + /* Fields "mt_nplans", "mt_whichplan" removed in 86dc90056dfd */ + MTHackField(mt_state, mt_nrels) = mt_plans_old; +#else MTHackField(mt_state, mt_nplans) = mt_plans_old; MTHackField(mt_state, mt_whichplan) = state_idx; +#endif /* Rerun ModifyTable */ goto restart; diff --git a/src/partition_router.c b/src/partition_router.c index b602347b..5f00e9b1 100644 --- a/src/partition_router.c +++ b/src/partition_router.c @@ -63,7 +63,7 @@ -bool pg_pathman_enable_partition_router = true; +bool pg_pathman_enable_partition_router = false; CustomScanMethods partition_router_plan_methods; CustomExecMethods partition_router_exec_methods; @@ -72,9 +72,10 @@ static TupleTableSlot *router_set_slot(PartitionRouterState *state, TupleTableSlot *slot, CmdType operation); static TupleTableSlot *router_get_slot(PartitionRouterState *state, + EState *estate, bool *should_process); -static void router_lazy_init_constraint(PartitionRouterState *state); +static void router_lazy_init_constraint(PartitionRouterState *state, bool recreate); static ItemPointerData router_extract_ctid(PartitionRouterState *state, TupleTableSlot *slot); @@ -114,7 +115,7 @@ init_partition_router_static_data(void) } Plan * -make_partition_router(Plan *subplan, int epq_param) +make_partition_router(Plan *subplan, int epq_param, Index parent_rti) { CustomScan *cscan = makeNode(CustomScan); @@ -133,10 +134,7 @@ make_partition_router(Plan *subplan, int epq_param) cscan->scan.scanrelid = 0; /* Build an appropriate target list */ - cscan->scan.plan.targetlist = pfilter_build_tlist(subplan); - - /* FIXME: should we use the same tlist? */ - cscan->custom_scan_tlist = subplan->targetlist; + cscan->scan.plan.targetlist = pfilter_build_tlist(subplan, parent_rti); return &cscan->scan.plan; } @@ -167,9 +165,9 @@ partition_router_begin(CustomScanState *node, EState *estate, int eflags) /* Remember current relation we're going to delete from */ state->current_rri = estate->es_result_relation_info; - EvalPlanQualInit(&state->epqstate, estate, - state->subplan, NIL, - state->epqparam); + EvalPlanQualInit_compat(&state->epqstate, estate, + state->subplan, NIL, + state->epqparam); /* It's convenient to store PlanState in 'custom_ps' */ node->custom_ps = list_make1(ExecInitNode(state->subplan, estate, eflags)); @@ -185,43 +183,103 @@ partition_router_exec(CustomScanState *node) take_next_tuple: /* Get next tuple for processing */ - slot = router_get_slot(state, &should_process); + slot = router_get_slot(state, estate, &should_process); if (should_process) { CmdType new_cmd; bool deleted; ItemPointerData ctid; + /* Variables for prepare a full "new" tuple, after 86dc90056dfd */ +#if PG_VERSION_NUM >= 140000 + TupleTableSlot *old_slot; + ResultRelInfo *rri; +#endif + TupleTableSlot *full_slot; + bool partition_changed = false; ItemPointerSetInvalid(&ctid); +#if PG_VERSION_NUM < 140000 + full_slot = slot; + /* Build new junkfilter if needed */ if (state->junkfilter == NULL) state->junkfilter = state->current_rri->ri_junkFilter; +#else + if (slot->tts_tableOid == InvalidOid) + elog(ERROR, "invalid table OID in returned tuple"); + + /* + * For 14: in case UPDATE command we can scanning several partitions + * in one plan. Need to switch context each time partition is switched. + */ + if (RelationGetRelid(state->current_rri->ri_RelationDesc) != slot->tts_tableOid) + { + /* + * Function router_get_slot() switched to new partition: need to + * reinitialize some PartitionRouterState variables + */ + state->current_rri = ExecLookupResultRelByOid(state->mt_state, + slot->tts_tableOid, false, false); + partition_changed = true; + } +#endif - /* Build recheck constraint state lazily */ - router_lazy_init_constraint(state); + /* Build recheck constraint state lazily (and re-create constraint + * in case we start scan another relation) */ + router_lazy_init_constraint(state, partition_changed); /* Extract item pointer from current tuple */ ctid = router_extract_ctid(state, slot); + Assert(ItemPointerIsValid(&ctid)); /* Magic: replace parent's ResultRelInfo with ours */ estate->es_result_relation_info = state->current_rri; +#if PG_VERSION_NUM >= 140000 /* after 86dc90056dfd */ + /* Store original slot */ + estate->es_original_tuple = slot; + /* + * "slot" contains new values of the changed columns plus row + * identity information such as CTID. + * Need to prepare a "newSlot" with full tuple for triggers in + * router_lock_or_delete_tuple(). But we should return old slot + * with CTID because this CTID is used in ExecModifyTable(). + */ + rri = state->current_rri; + + /* Initialize projection info if first time for this table. */ + if (unlikely(!rri->ri_projectNewInfoValid)) +#ifdef PG_HAVE_PGPRO_EXEC_INIT_UPDATE_PROJECTION + PgproExecInitUpdateProjection(state->mt_state, rri); +#else + ExecInitUpdateProjection(state->mt_state, rri); +#endif /* !PG_HAVE_PGPRO_EXEC_INIT_UPDATE_PROJECTION */ + + old_slot = rri->ri_oldTupleSlot; + /* Fetch the most recent version of old tuple. */ + if (!table_tuple_fetch_row_version(rri->ri_RelationDesc, + &ctid, SnapshotAny, old_slot)) + elog(ERROR, "failed to fetch partition tuple being updated"); + + /* Build full tuple (using "old_slot" + changed from "slot"): */ + full_slot = ExecGetUpdateNewTuple(rri, slot, old_slot); +#endif /* PG_VERSION_NUM >= 140000 */ + /* Lock or delete tuple from old partition */ - Assert(ItemPointerIsValid(&ctid)); - slot = router_lock_or_delete_tuple(state, slot, - &ctid, &deleted); + full_slot = router_lock_or_delete_tuple(state, full_slot, + &ctid, &deleted); /* We require a tuple (previous one has vanished) */ - if (TupIsNull(slot)) + if (TupIsNull(full_slot)) goto take_next_tuple; /* Should we use UPDATE or DELETE + INSERT? */ new_cmd = deleted ? CMD_INSERT : CMD_UPDATE; /* Alter ModifyTable's state and return */ - return router_set_slot(state, slot, new_cmd); + return router_set_slot(state, full_slot, new_cmd); } return slot; @@ -265,7 +323,12 @@ router_set_slot(PartitionRouterState *state, return slot; /* HACK: alter ModifyTable's state */ +#if PG_VERSION_NUM >= 140000 + /* Fields "mt_nplans", "mt_whichplan" removed in 86dc90056dfd */ + MTHackField(mt_state, mt_nrels) = -mt_state->mt_nrels; +#else MTHackField(mt_state, mt_nplans) = -mt_state->mt_whichplan; +#endif MTHackField(mt_state, operation) = operation; /* HACK: disable AFTER STATEMENT triggers */ @@ -273,6 +336,9 @@ router_set_slot(PartitionRouterState *state, if (!TupIsNull(slot)) { + EState *estate = mt_state->ps.state; + +#if PG_VERSION_NUM < 140000 /* field "ri_junkFilter" removed in 86dc90056dfd */ /* We should've cached junk filter already */ Assert(state->junkfilter); @@ -280,12 +346,20 @@ router_set_slot(PartitionRouterState *state, state->current_rri->ri_junkFilter = (operation == CMD_UPDATE) ? state->junkfilter : NULL; +#endif /* Don't forget to set saved_slot! */ - state->yielded_slot = ExecInitExtraTupleSlotCompat(mt_state->ps.state, + state->yielded_slot = ExecInitExtraTupleSlotCompat(estate, slot->tts_tupleDescriptor, &TTSOpsHeapTuple); ExecCopySlot(state->yielded_slot, slot); +#if PG_VERSION_NUM >= 140000 + Assert(estate->es_original_tuple != NULL); + state->yielded_original_slot = ExecInitExtraTupleSlotCompat(estate, + estate->es_original_tuple->tts_tupleDescriptor, + &TTSOpsHeapTuple); + ExecCopySlot(state->yielded_original_slot, estate->es_original_tuple); +#endif } /* Yield */ @@ -296,6 +370,7 @@ router_set_slot(PartitionRouterState *state, /* Fetch next tuple (either fresh or yielded) */ static TupleTableSlot * router_get_slot(PartitionRouterState *state, + EState *estate, bool *should_process) { TupleTableSlot *slot; @@ -309,6 +384,10 @@ router_get_slot(PartitionRouterState *state, /* Reset saved slot */ slot = state->yielded_slot; state->yielded_slot = NULL; +#if PG_VERSION_NUM >= 140000 + estate->es_original_tuple = state->yielded_original_slot; + state->yielded_original_slot = NULL; +#endif state->yielded = false; /* We shouldn't process preserved slot... */ @@ -331,9 +410,9 @@ router_get_slot(PartitionRouterState *state, } static void -router_lazy_init_constraint(PartitionRouterState *state) +router_lazy_init_constraint(PartitionRouterState *state, bool reinit) { - if (state->constraint == NULL) + if (state->constraint == NULL || reinit) { Relation rel = state->current_rri->ri_RelationDesc; Oid relid = RelationGetRelid(rel); @@ -376,7 +455,11 @@ router_extract_ctid(PartitionRouterState *state, TupleTableSlot *slot) bool ctid_isnull; ctid_datum = ExecGetJunkAttribute(slot, +#if PG_VERSION_NUM >= 140000 /* field "junkfilter" removed in 86dc90056dfd */ + state->current_rri->ri_RowIdAttNo, +#else state->junkfilter->jf_junkAttNo, +#endif &ctid_isnull); /* shouldn't ever get a null result... */ @@ -443,7 +526,7 @@ router_lock_or_delete_tuple(PartitionRouterState *state, rri->ri_TrigDesc->trig_update_before_row) { #if PG_VERSION_NUM >= 120000 - if (!ExecBRUpdateTriggers(estate, epqstate, rri, tupleid, NULL, slot)) + if (!ExecBRUpdateTriggersCompat(estate, epqstate, rri, tupleid, NULL, slot)) return NULL; #else slot = ExecBRUpdateTriggers(estate, epqstate, rri, tupleid, NULL, slot); diff --git a/src/pathman_workers.c b/src/pathman_workers.c index a75e912b..bf23bd94 100644 --- a/src/pathman_workers.c +++ b/src/pathman_workers.c @@ -57,8 +57,8 @@ extern PGDLLEXPORT void bgw_main_concurrent_part(Datum main_arg); static void handle_sigterm(SIGNAL_ARGS); static void bg_worker_load_config(const char *bgw_name); -static bool start_bgworker(const char bgworker_name[BGW_MAXLEN], - const char bgworker_proc[BGW_MAXLEN], +static bool start_bgworker(const char *bgworker_name, + const char *bgworker_proc, Datum bgw_arg, bool wait_for_shutdown); @@ -166,8 +166,8 @@ bg_worker_load_config(const char *bgw_name) * Common function to start background worker. */ static bool -start_bgworker(const char bgworker_name[BGW_MAXLEN], - const char bgworker_proc[BGW_MAXLEN], +start_bgworker(const char *bgworker_name, + const char *bgworker_proc, Datum bgw_arg, bool wait_for_shutdown) { #define HandleError(condition, new_state) \ @@ -195,6 +195,9 @@ start_bgworker(const char bgworker_name[BGW_MAXLEN], snprintf(worker.bgw_library_name, BGW_MAXLEN, "pg_pathman"); worker.bgw_flags = BGWORKER_SHMEM_ACCESS | +#if defined(PGPRO_EE) && PG_VERSION_NUM >= 130000 && PG_VERSION_NUM < 140000 /* FIXME: need to remove last condition in future */ + BGWORKER_CLASS_PERSISTENT | +#endif BGWORKER_BACKEND_DATABASE_CONNECTION; worker.bgw_start_time = BgWorkerStart_RecoveryFinished; worker.bgw_restart_time = BGW_NEVER_RESTART; @@ -455,8 +458,8 @@ bgw_main_concurrent_part(Datum main_arg) ConcurrentPartSlot *part_slot; char *sql = NULL; int64 rows; - bool failed; - int failures_count = 0; + volatile bool failed; + volatile int failures_count = 0; LOCKMODE lockmode = RowExclusiveLock; /* Update concurrent part slot */ @@ -494,7 +497,7 @@ bgw_main_concurrent_part(Datum main_arg) Oid types[2] = { OIDOID, INT4OID }; Datum vals[2] = { part_slot->relid, part_slot->batch_size }; - bool rel_locked = false; + volatile bool rel_locked = false; /* Reset loop variables */ failed = false; @@ -517,6 +520,11 @@ bgw_main_concurrent_part(Datum main_arg) if (sql == NULL) { MemoryContext current_mcxt; + char *pathman_schema; + + pathman_schema = get_namespace_name(get_pathman_schema()); + if (pathman_schema == NULL) + elog(ERROR, "pg_pathman schema not initialized"); /* * Allocate SQL query in TopPathmanContext because current @@ -524,7 +532,7 @@ bgw_main_concurrent_part(Datum main_arg) */ current_mcxt = MemoryContextSwitchTo(TopPathmanContext); sql = psprintf("SELECT %s._partition_data_concurrent($1::regclass, NULL::text, NULL::text, p_limit:=$2)", - get_namespace_name(get_pathman_schema())); + pathman_schema); MemoryContextSwitchTo(current_mcxt); } @@ -542,14 +550,12 @@ bgw_main_concurrent_part(Datum main_arg) /* Great, now relation is locked */ rel_locked = true; - (void) rel_locked; /* mute clang analyzer */ /* Make sure that relation exists */ if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(part_slot->relid))) { /* Exit after we raise ERROR */ failures_count = PART_WORKER_MAX_ATTEMPTS; - (void) failures_count; /* mute clang analyzer */ elog(ERROR, "relation %u does not exist", part_slot->relid); } @@ -559,7 +565,6 @@ bgw_main_concurrent_part(Datum main_arg) { /* Exit after we raise ERROR */ failures_count = PART_WORKER_MAX_ATTEMPTS; - (void) failures_count; /* mute clang analyzer */ elog(ERROR, "relation \"%s\" is not partitioned", get_rel_name(part_slot->relid)); @@ -700,6 +705,7 @@ partition_table_concurrently(PG_FUNCTION_ARGS) i; TransactionId rel_xmin; LOCKMODE lockmode = ShareUpdateExclusiveLock; + char *pathman_schema; /* Check batch_size */ if (batch_size < 1 || batch_size > 10000) @@ -712,6 +718,8 @@ partition_table_concurrently(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("'sleep_time' should not be less than 0.5"))); + check_relation_oid(relid); + /* Prevent concurrent function calls */ LockRelationOid(relid, lockmode); @@ -798,11 +806,15 @@ partition_table_concurrently(PG_FUNCTION_ARGS) start_bgworker_errmsg(concurrent_part_bgw); } + pathman_schema = get_namespace_name(get_pathman_schema()); + if (pathman_schema == NULL) + elog(ERROR, "pg_pathman schema not initialized"); + /* Tell user everything's fine */ elog(NOTICE, "worker started, you can stop it " "with the following command: select %s.%s('%s');", - get_namespace_name(get_pathman_schema()), + pathman_schema, CppAsString(stop_concurrent_part_task), get_rel_name(relid)); diff --git a/src/pg_pathman.c b/src/pg_pathman.c index e3a46abd..6e835a1f 100644 --- a/src/pg_pathman.c +++ b/src/pg_pathman.c @@ -4,7 +4,7 @@ * This module sets planner hooks, handles SELECT queries and produces * paths for partitioned tables * - * Copyright (c) 2015-2016, Postgres Professional + * Copyright (c) 2015-2021, Postgres Professional * * ------------------------------------------------------------------------ */ @@ -281,6 +281,32 @@ estimate_paramsel_using_prel(const PartRelationInfo *prel, int strategy) else return 1.0; } +#if defined(PGPRO_EE) && PG_VERSION_NUM >= 100000 +/* + * Reset cache at start and at finish ATX transaction + */ +static void +pathman_xact_cb(XactEvent event, void *arg) +{ + if (getNestLevelATX() > 0) + { + /* + * For each ATX transaction start/finish: need to reset pg_pathman + * cache because we shouldn't see uncommitted data in autonomous + * transaction and data of autonomous transaction in main transaction + */ + if ((event == XACT_EVENT_START /* start */) || + (event == XACT_EVENT_ABORT || + event == XACT_EVENT_PARALLEL_ABORT || + event == XACT_EVENT_COMMIT || + event == XACT_EVENT_PARALLEL_COMMIT || + event == XACT_EVENT_PREPARE /* finish */)) + { + pathman_relcache_hook(PointerGetDatum(NULL), InvalidOid); + } + } +} +#endif /* * ------------------- @@ -288,6 +314,11 @@ estimate_paramsel_using_prel(const PartRelationInfo *prel, int strategy) * ------------------- */ +#if PG_VERSION_NUM >= 150000 /* for commit 4f2400cb3f10 */ +static shmem_request_hook_type prev_shmem_request_hook = NULL; +static void pg_pathman_shmem_request(void); +#endif + /* Set initial values for all Postmaster's forks */ void _PG_init(void) @@ -300,7 +331,12 @@ _PG_init(void) } /* Request additional shared resources */ +#if PG_VERSION_NUM >= 150000 /* for commit 4f2400cb3f10 */ + prev_shmem_request_hook = shmem_request_hook; + shmem_request_hook = pg_pathman_shmem_request; +#else RequestAddinShmemSpace(estimate_pathman_shmem_size()); +#endif /* Assign pg_pathman's initial state */ pathman_init_state.pg_pathman_enable = DEFAULT_PATHMAN_ENABLE; @@ -321,6 +357,8 @@ _PG_init(void) planner_hook = pathman_planner_hook; pathman_process_utility_hook_next = ProcessUtility_hook; ProcessUtility_hook = pathman_process_utility_hook; + pathman_executor_start_hook_prev = ExecutorStart_hook; + ExecutorStart_hook = pathman_executor_start_hook; /* Initialize static data for all subsystems */ init_main_pathman_toggles(); @@ -330,7 +368,23 @@ _PG_init(void) init_partition_filter_static_data(); init_partition_router_static_data(); init_partition_overseer_static_data(); + +#ifdef PGPRO_EE + /* Callbacks for reload relcache for ATX transactions */ + PgproRegisterXactCallback(pathman_xact_cb, NULL, XACT_EVENT_KIND_VANILLA | XACT_EVENT_KIND_ATX); +#endif +} + +#if PG_VERSION_NUM >= 150000 /* for commit 4f2400cb3f10 */ +static void +pg_pathman_shmem_request(void) +{ + if (prev_shmem_request_hook) + prev_shmem_request_hook(); + + RequestAddinShmemSpace(estimate_pathman_shmem_size()); } +#endif /* Get cached PATHMAN_CONFIG relation Oid */ Oid @@ -455,6 +509,11 @@ append_child_relation(PlannerInfo *root, ListCell *lc1, *lc2; LOCKMODE lockmode; +#if PG_VERSION_NUM >= 130000 /* see commit 55a1954d */ + TupleDesc child_tupdesc; + List *parent_colnames; + List *child_colnames; +#endif /* Choose a correct lock mode */ if (parent_rti == root->parse->resultRelation) @@ -486,10 +545,20 @@ append_child_relation(PlannerInfo *root, child_relation = heap_open_compat(child_oid, NoLock); /* Create RangeTblEntry for child relation */ +#if PG_VERSION_NUM >= 130000 /* see commit 55a1954d */ + child_rte = makeNode(RangeTblEntry); + memcpy(child_rte, parent_rte, sizeof(RangeTblEntry)); +#else child_rte = copyObject(parent_rte); +#endif child_rte->relid = child_oid; child_rte->relkind = child_relation->rd_rel->relkind; +#if PG_VERSION_NUM >= 160000 /* for commit a61b1f74823c */ + /* No permission checking for the child RTE */ + child_rte->perminfoindex = 0; +#else child_rte->requiredPerms = 0; /* perform all checks on parent */ +#endif child_rte->inh = false; /* Add 'child_rte' to rtable and 'root->simple_rte_array' */ @@ -508,7 +577,56 @@ append_child_relation(PlannerInfo *root, appinfo->child_reltype = RelationGetDescr(child_relation)->tdtypeid; make_inh_translation_list(parent_relation, child_relation, child_rti, - &appinfo->translated_vars); + &appinfo->translated_vars, appinfo); + +#if PG_VERSION_NUM >= 130000 /* see commit 55a1954d */ + /* tablesample is probably null, but copy it */ + child_rte->tablesample = copyObject(parent_rte->tablesample); + + /* + * Construct an alias clause for the child, which we can also use as eref. + * This is important so that EXPLAIN will print the right column aliases + * for child-table columns. (Since ruleutils.c doesn't have any easy way + * to reassociate parent and child columns, we must get the child column + * aliases right to start with. Note that setting childrte->alias forces + * ruleutils.c to use these column names, which it otherwise would not.) + */ + child_tupdesc = RelationGetDescr(child_relation); + parent_colnames = parent_rte->eref->colnames; + child_colnames = NIL; + for (int cattno = 0; cattno < child_tupdesc->natts; cattno++) + { + Form_pg_attribute att = TupleDescAttr(child_tupdesc, cattno); + const char *attname; + + if (att->attisdropped) + { + /* Always insert an empty string for a dropped column */ + attname = ""; + } + else if (appinfo->parent_colnos[cattno] > 0 && + appinfo->parent_colnos[cattno] <= list_length(parent_colnames)) + { + /* Duplicate the query-assigned name for the parent column */ + attname = strVal(list_nth(parent_colnames, + appinfo->parent_colnos[cattno] - 1)); + } + else + { + /* New column, just use its real name */ + attname = NameStr(att->attname); + } + child_colnames = lappend(child_colnames, makeString(pstrdup(attname))); + } + + /* + * We just duplicate the parent's table alias name for each child. If the + * plan gets printed, ruleutils.c has to sort out unique table aliases to + * use, which it can handle. + */ + child_rte->alias = child_rte->eref = makeAlias(parent_rte->eref->aliasname, + child_colnames); +#endif /* Now append 'appinfo' to 'root->append_rel_list' */ root->append_rel_list = lappend(root->append_rel_list, appinfo); @@ -565,6 +683,7 @@ append_child_relation(PlannerInfo *root, } +#if PG_VERSION_NUM < 160000 /* for commit a61b1f74823c */ /* Translate column privileges for this child */ if (parent_rte->relid != child_oid) { @@ -575,9 +694,18 @@ append_child_relation(PlannerInfo *root, child_rte->updatedCols = translate_col_privs(parent_rte->updatedCols, appinfo->translated_vars); } +#if PG_VERSION_NUM >= 130000 /* see commit 55a1954d */ + else + { + child_rte->selectedCols = bms_copy(parent_rte->selectedCols); + child_rte->insertedCols = bms_copy(parent_rte->insertedCols); + child_rte->updatedCols = bms_copy(parent_rte->updatedCols); + } +#endif +#endif /* PG_VERSION_NUM < 160000 */ /* Here and below we assume that parent RelOptInfo exists */ - AssertState(parent_rel); + Assert(parent_rel); /* Adjust join quals for this child */ child_rel->joininfo = (List *) adjust_appendrel_attrs_compat(root, @@ -1159,8 +1287,14 @@ handle_array(ArrayType *array, bool elem_byval; char elem_align; - /* Check if we can work with this strategy */ - if (strategy == 0) + /* + * Check if we can work with this strategy + * We can work only with BTLessStrategyNumber, BTLessEqualStrategyNumber, + * BTEqualStrategyNumber, BTGreaterEqualStrategyNumber and BTGreaterStrategyNumber. + * If new search strategies appear in the future, then access optimizations from + * this function will not work, and the default behavior (handle_array_return:) will work. + */ + if (strategy == InvalidStrategy || strategy > BTGreaterStrategyNumber) goto handle_array_return; /* Get element's properties */ @@ -1177,8 +1311,12 @@ handle_array(ArrayType *array, List *ranges; int i; - /* This is only for paranoia's sake */ - Assert(BTMaxStrategyNumber == 5 && BTEqualStrategyNumber == 3); + /* This is only for paranoia's sake (checking correctness of following take_min calculation) */ + Assert(BTEqualStrategyNumber == 3 + && BTLessStrategyNumber < BTEqualStrategyNumber + && BTLessEqualStrategyNumber < BTEqualStrategyNumber + && BTGreaterEqualStrategyNumber > BTEqualStrategyNumber + && BTGreaterStrategyNumber > BTEqualStrategyNumber); /* Optimizations for <, <=, >=, > */ if (strategy != BTEqualStrategyNumber) @@ -1883,7 +2021,8 @@ translate_col_privs(const Bitmapset *parent_privs, */ void make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, List **translated_vars) + Index newvarno, List **translated_vars, + AppendRelInfo *appinfo) { List *vars = NIL; TupleDesc old_tupdesc = RelationGetDescr(oldrelation); @@ -1891,6 +2030,17 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; +#if PG_VERSION_NUM >= 130000 /* see commit ce76c0ba */ + AttrNumber *pcolnos = NULL; + + if (appinfo) + { + /* Initialize reverse-translation array with all entries zero */ + appinfo->num_child_cols = newnatts; + appinfo->parent_colnos = pcolnos = + (AttrNumber *) palloc0(newnatts * sizeof(AttrNumber)); + } +#endif for (old_attno = 0; old_attno < oldnatts; old_attno++) { @@ -1925,6 +2075,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, atttypmod, attcollation, 0)); +#if PG_VERSION_NUM >= 130000 + if (pcolnos) + pcolnos[old_attno] = old_attno + 1; +#endif continue; } @@ -1982,6 +2136,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, atttypmod, attcollation, 0)); +#if PG_VERSION_NUM >= 130000 + if (pcolnos) + pcolnos[new_attno] = old_attno + 1; +#endif } *translated_vars = vars; diff --git a/src/pl_funcs.c b/src/pl_funcs.c index 76ecbe3d..75c1c12a 100644 --- a/src/pl_funcs.c +++ b/src/pl_funcs.c @@ -174,12 +174,17 @@ get_partition_cooked_key_pl(PG_FUNCTION_ARGS) expr_cstr = TextDatumGetCString(values[Anum_pathman_config_expr - 1]); expr = cook_partitioning_expression(relid, expr_cstr, NULL); + +#if PG_VERSION_NUM >= 170000 /* for commit d20d8fbd3e4d */ + cooked_cstr = nodeToStringWithLocations(expr); +#else cooked_cstr = nodeToString(expr); +#endif pfree(expr_cstr); pfree(expr); - PG_RETURN_TEXT_P(CStringGetTextDatum(cooked_cstr)); + PG_RETURN_DATUM(CStringGetTextDatum(cooked_cstr)); } /* @@ -196,10 +201,16 @@ get_cached_partition_cooked_key_pl(PG_FUNCTION_ARGS) prel = get_pathman_relation_info(relid); shout_if_prel_is_invalid(relid, prel, PT_ANY); + +#if PG_VERSION_NUM >= 170000 /* for commit d20d8fbd3e4d */ + res = CStringGetTextDatum(nodeToStringWithLocations(prel->expr)); +#else res = CStringGetTextDatum(nodeToString(prel->expr)); +#endif + close_pathman_relation_info(prel); - PG_RETURN_TEXT_P(res); + PG_RETURN_DATUM(res); } /* @@ -673,6 +684,7 @@ validate_expression(PG_FUNCTION_ARGS) if (!PG_ARGISNULL(0)) { relid = PG_GETARG_OID(0); + check_relation_oid(relid); } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("'relid' should not be NULL"))); @@ -688,7 +700,7 @@ validate_expression(PG_FUNCTION_ARGS) if (!PG_ARGISNULL(1)) { - expression = TextDatumGetCString(PG_GETARG_TEXT_P(1)); + expression = TextDatumGetCString(PG_GETARG_DATUM(1)); } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("'expression' should not be NULL"))); @@ -725,7 +737,10 @@ is_tuple_convertible(PG_FUNCTION_ARGS) rel2 = heap_open_compat(PG_GETARG_OID(1), AccessShareLock); /* Try to build a conversion map */ -#if PG_VERSION_NUM >= 130000 +#if PG_VERSION_NUM >= 160000 /* for commit ad86d159b6ab */ + map = build_attrmap_by_name(RelationGetDescr(rel1), + RelationGetDescr(rel2), false); +#elif PG_VERSION_NUM >= 130000 map = build_attrmap_by_name(RelationGetDescr(rel1), RelationGetDescr(rel2)); #else @@ -796,7 +811,7 @@ add_to_pathman_config(PG_FUNCTION_ARGS) Oid expr_type; - PathmanInitState init_state; + volatile PathmanInitState init_state; if (!IsPathmanReady()) elog(ERROR, "pg_pathman is disabled"); @@ -804,6 +819,7 @@ add_to_pathman_config(PG_FUNCTION_ARGS) if (!PG_ARGISNULL(0)) { relid = PG_GETARG_OID(0); + check_relation_oid(relid); } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("'parent_relid' should not be NULL"))); @@ -818,7 +834,7 @@ add_to_pathman_config(PG_FUNCTION_ARGS) if (!PG_ARGISNULL(1)) { - expression = TextDatumGetCString(PG_GETARG_TEXT_P(1)); + expression = TextDatumGetCString(PG_GETARG_DATUM(1)); } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("'expression' should not be NULL"))); @@ -1034,6 +1050,8 @@ prevent_part_modification(PG_FUNCTION_ARGS) { Oid relid = PG_GETARG_OID(0); + check_relation_oid(relid); + /* Lock partitioned relation till transaction's end */ LockRelationOid(relid, ShareUpdateExclusiveLock); @@ -1048,6 +1066,8 @@ prevent_data_modification(PG_FUNCTION_ARGS) { Oid relid = PG_GETARG_OID(0); + check_relation_oid(relid); + /* * Check that isolation level is READ COMMITTED. * Else we won't be able to see new rows @@ -1203,7 +1223,7 @@ is_operator_supported(PG_FUNCTION_ARGS) { Oid opid, typid = PG_GETARG_OID(0); - char *opname = TextDatumGetCString(PG_GETARG_TEXT_P(1)); + char *opname = TextDatumGetCString(PG_GETARG_DATUM(1)); opid = compatible_oper_opid(list_make1(makeString(opname)), typid, typid, true); diff --git a/src/pl_hash_funcs.c b/src/pl_hash_funcs.c index f4a44b71..4b08c324 100644 --- a/src/pl_hash_funcs.c +++ b/src/pl_hash_funcs.c @@ -119,9 +119,10 @@ Datum build_hash_condition(PG_FUNCTION_ARGS) { Oid expr_type = PG_GETARG_OID(0); - char *expr_cstr = TextDatumGetCString(PG_GETARG_TEXT_P(1)); + char *expr_cstr = TextDatumGetCString(PG_GETARG_DATUM(1)); uint32 part_count = PG_GETARG_UINT32(2), part_idx = PG_GETARG_UINT32(3); + char *pathman_schema; TypeCacheEntry *tce; @@ -141,9 +142,13 @@ build_hash_condition(PG_FUNCTION_ARGS) errmsg("no hash function for type %s", format_type_be(expr_type)))); + pathman_schema = get_namespace_name(get_pathman_schema()); + if (pathman_schema == NULL) + elog(ERROR, "pg_pathman schema not initialized"); + /* Create hash condition CSTRING */ result = psprintf("%s.get_hash_part_idx(%s(%s), %u) = %u", - get_namespace_name(get_pathman_schema()), + pathman_schema, get_func_name(tce->hash_proc), expr_cstr, part_count, diff --git a/src/pl_range_funcs.c b/src/pl_range_funcs.c index 12c247ab..19292a0a 100644 --- a/src/pl_range_funcs.c +++ b/src/pl_range_funcs.c @@ -156,7 +156,7 @@ create_single_range_partition_pl(PG_FUNCTION_ARGS) /* Fetch 'tablespace' */ if (!PG_ARGISNULL(4)) { - tablespace = TextDatumGetCString(PG_GETARG_TEXT_P(4)); + tablespace = TextDatumGetCString(PG_GETARG_DATUM(4)); } else tablespace = NULL; /* default */ @@ -429,7 +429,7 @@ validate_interval_value(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("'expression' should not be NULL"))); } - else expr_cstr = TextDatumGetCString(PG_GETARG_TEXT_P(ARG_EXPRESSION)); + else expr_cstr = TextDatumGetCString(PG_GETARG_DATUM(ARG_EXPRESSION)); if (PG_ARGISNULL(ARG_PARTTYPE)) { @@ -499,6 +499,7 @@ split_range_partition(PG_FUNCTION_ARGS) if (!PG_ARGISNULL(0)) { partition1 = PG_GETARG_OID(0); + check_relation_oid(partition1); } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("'partition1' should not be NULL"))); @@ -683,9 +684,6 @@ merge_range_partitions(PG_FUNCTION_ARGS) /* Extract partition Oids from array */ parts[i] = DatumGetObjectId(datums[i]); - /* Prevent modification of partitions */ - LockRelationOid(parts[i], AccessExclusiveLock); - /* Check if all partitions are from the same parent */ cur_parent = get_parent_of_partition(parts[i]); @@ -708,6 +706,10 @@ merge_range_partitions(PG_FUNCTION_ARGS) /* Prevent changes in partitioning scheme */ LockRelationOid(parent, ShareUpdateExclusiveLock); + /* Prevent modification of partitions */ + for (i = 0; i < nparts; i++) + LockRelationOid(parts[i], AccessExclusiveLock); + /* Emit an error if it is not partitioned by RANGE */ prel = get_pathman_relation_info(parent); shout_if_prel_is_invalid(parent, prel, PT_RANGE); @@ -834,6 +836,8 @@ drop_range_partition_expand_next(PG_FUNCTION_ARGS) RangeEntry *ranges; int i; + check_relation_oid(partition); + /* Lock the partition we're going to drop */ LockRelationOid(partition, AccessExclusiveLock); @@ -1085,7 +1089,7 @@ build_range_condition(PG_FUNCTION_ARGS) if (!PG_ARGISNULL(1)) { - expression = TextDatumGetCString(PG_GETARG_TEXT_P(1)); + expression = TextDatumGetCString(PG_GETARG_DATUM(1)); } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("'expression' should not be NULL")));; diff --git a/src/planner_tree_modification.c b/src/planner_tree_modification.c index 77a55bd3..5b6a7982 100644 --- a/src/planner_tree_modification.c +++ b/src/planner_tree_modification.c @@ -27,6 +27,9 @@ #include "foreign/fdwapi.h" #include "miscadmin.h" #include "optimizer/clauses.h" +#if PG_VERSION_NUM >= 160000 /* for commit a61b1f74823c */ +#include "parser/parse_relation.h" +#endif #include "storage/lmgr.h" #include "utils/syscache.h" @@ -119,8 +122,8 @@ static void handle_modification_query(Query *parse, transform_query_cxt *context static Plan *partition_filter_visitor(Plan *plan, void *context); static Plan *partition_router_visitor(Plan *plan, void *context); -static void state_visit_subplans(List *plans, void (*visitor) (), void *context); -static void state_visit_members(PlanState **planstates, int nplans, void (*visitor) (), void *context); +static void state_visit_subplans(List *plans, void (*visitor) (PlanState *plan, void *context), void *context); +static void state_visit_members(PlanState **planstates, int nplans, void (*visitor) (PlanState *plan, void *context), void *context); static Oid find_deepest_partition(Oid relid, Index rti, Expr *quals); static Node *eval_extern_params_mutator(Node *node, ParamListInfo params); @@ -134,13 +137,13 @@ static bool modifytable_contains_fdw(List *rtable, ModifyTable *node); * id in order to recognize them properly. */ #define QUERY_ID_INITIAL 0 -static uint32 latest_query_id = QUERY_ID_INITIAL; +static uint64 latest_query_id = QUERY_ID_INITIAL; void assign_query_id(Query *query) { - uint32 prev_id = latest_query_id++; + uint64 prev_id = latest_query_id++; if (prev_id > latest_query_id) elog(WARNING, "assign_query_id(): queryId overflow"); @@ -184,10 +187,12 @@ plan_tree_visitor(Plan *plan, plan_tree_visitor((Plan *) lfirst(l), visitor, context); break; +#if PG_VERSION_NUM < 140000 /* reworked in commit 86dc90056dfd */ case T_ModifyTable: foreach (l, ((ModifyTable *) plan)->plans) plan_tree_visitor((Plan *) lfirst(l), visitor, context); break; +#endif case T_Append: foreach (l, ((Append *) plan)->appendplans) @@ -247,11 +252,13 @@ state_tree_visitor(PlanState *state, state_tree_visitor((PlanState *) lfirst(lc), visitor, context); break; +#if PG_VERSION_NUM < 140000 /* reworked in commit 86dc90056dfd */ case T_ModifyTable: state_visit_members(((ModifyTableState *) state)->mt_plans, ((ModifyTableState *) state)->mt_nplans, visitor, context); break; +#endif case T_Append: state_visit_members(((AppendState *) state)->appendplans, @@ -296,7 +303,7 @@ state_tree_visitor(PlanState *state, */ static void state_visit_subplans(List *plans, - void (*visitor) (), + void (*visitor) (PlanState *plan, void *context), void *context) { ListCell *lc; @@ -304,7 +311,7 @@ state_visit_subplans(List *plans, foreach (lc, plans) { SubPlanState *sps = lfirst_node(SubPlanState, lc); - visitor(sps->planstate, context); + state_tree_visitor(sps->planstate, visitor, context); } } @@ -314,12 +321,12 @@ state_visit_subplans(List *plans, */ static void state_visit_members(PlanState **planstates, int nplans, - void (*visitor) (), void *context) + void (*visitor) (PlanState *plan, void *context), void *context) { int i; for (i = 0; i < nplans; i++) - visitor(planstates[i], context); + state_tree_visitor(planstates[i], visitor, context); } @@ -487,7 +494,17 @@ disable_standard_inheritance(Query *parse, transform_query_cxt *context) if (rte->rtekind != RTE_RELATION || rte->relkind != RELKIND_RELATION || parse->resultRelation == current_rti) /* is it a result relation? */ + { +#if PG_VERSION_NUM >= 150000 /* for commit 7103ebb7aae8 */ + if (parse->commandType == CMD_MERGE && + (rte->rtekind == RTE_RELATION || + rte->relkind == RELKIND_RELATION) && + rte->inh && has_pathman_relation_info(rte->relid)) + elog(ERROR, "pg_pathman doesn't support MERGE command yet"); +#endif + continue; + } /* Table may be partitioned */ if (rte->inh) @@ -560,6 +577,10 @@ handle_modification_query(Query *parse, transform_query_cxt *context) List *translated_vars; adjust_appendrel_varnos_cxt aav_cxt; +#if PG_VERSION_NUM >= 160000 /* for commit a61b1f74823c */ + RTEPermissionInfo *parent_perminfo, + *child_perminfo; +#endif /* Lock 'child' table */ LockRelationOid(child, lockmode); @@ -580,10 +601,24 @@ handle_modification_query(Query *parse, transform_query_cxt *context) return; /* nothing to do here */ } +#if PG_VERSION_NUM >= 160000 /* for commit a61b1f74823c */ + parent_perminfo = getRTEPermissionInfo(parse->rteperminfos, rte); +#endif /* Update RTE's relid and relkind (for FDW) */ rte->relid = child; rte->relkind = child_relkind; +#if PG_VERSION_NUM >= 160000 /* for commit a61b1f74823c */ + /* Copy parent RTEPermissionInfo. */ + rte->perminfoindex = 0; /* expected by addRTEPermissionInfo() */ + child_perminfo = addRTEPermissionInfo(&parse->rteperminfos, rte); + memcpy(child_perminfo, parent_perminfo, sizeof(RTEPermissionInfo)); + + /* Correct RTEPermissionInfo for child. */ + child_perminfo->relid = child; + child_perminfo->inh = false; +#endif + /* HACK: unset the 'inh' flag (no children) */ rte->inh = false; @@ -591,7 +626,7 @@ handle_modification_query(Query *parse, transform_query_cxt *context) child_rel = heap_open_compat(child, NoLock); parent_rel = heap_open_compat(parent, NoLock); - make_inh_translation_list(parent_rel, child_rel, 0, &translated_vars); + make_inh_translation_list(parent_rel, child_rel, 0, &translated_vars, NULL); /* Perform some additional adjustments */ if (!inh_translation_list_is_trivial(translated_vars)) @@ -604,10 +639,16 @@ handle_modification_query(Query *parse, transform_query_cxt *context) aav_cxt.translated_vars = translated_vars; adjust_appendrel_varnos((Node *) parse, &aav_cxt); +#if PG_VERSION_NUM >= 160000 /* for commit a61b1f74823c */ + child_perminfo->selectedCols = translate_col_privs(parent_perminfo->selectedCols, translated_vars); + child_perminfo->insertedCols = translate_col_privs(parent_perminfo->insertedCols, translated_vars); + child_perminfo->updatedCols = translate_col_privs(parent_perminfo->updatedCols, translated_vars); +#else /* Translate column privileges for this child */ rte->selectedCols = translate_col_privs(rte->selectedCols, translated_vars); rte->insertedCols = translate_col_privs(rte->insertedCols, translated_vars); rte->updatedCols = translate_col_privs(rte->updatedCols, translated_vars); +#endif } /* Close relations (should remain locked, though) */ @@ -757,9 +798,19 @@ partition_filter_visitor(Plan *plan, void *context) { List *rtable = (List *) context; ModifyTable *modify_table = (ModifyTable *) plan; +#if PG_VERSION_NUM >= 140000 /* for changes 86dc90056dfd */ + /* + * We have only one subplan for 14: need to modify it without + * using any cycle + */ + Plan *subplan = outerPlan(modify_table); + ListCell *lc2, + *lc3; +#else ListCell *lc1, *lc2, *lc3; +#endif /* Skip if not ModifyTable with 'INSERT' command */ if (!IsA(modify_table, ModifyTable) || modify_table->operation != CMD_INSERT) @@ -768,8 +819,12 @@ partition_filter_visitor(Plan *plan, void *context) Assert(rtable && IsA(rtable, List)); lc3 = list_head(modify_table->returningLists); +#if PG_VERSION_NUM >= 140000 /* for changes 86dc90056dfd */ + lc2 = list_head(modify_table->resultRelations); +#else forboth (lc1, modify_table->plans, lc2, modify_table->resultRelations) +#endif { Index rindex = lfirst_int(lc2); Oid relid = getrelid(rindex, rtable); @@ -783,14 +838,24 @@ partition_filter_visitor(Plan *plan, void *context) if (lc3) { returning_list = lfirst(lc3); +#if PG_VERSION_NUM < 140000 lc3 = lnext_compat(modify_table->returningLists, lc3); +#endif } +#if PG_VERSION_NUM >= 140000 /* for changes 86dc90056dfd */ + outerPlan(modify_table) = make_partition_filter(subplan, relid, + modify_table->nominalRelation, + modify_table->onConflictAction, + modify_table->operation, + returning_list); +#else lfirst(lc1) = make_partition_filter((Plan *) lfirst(lc1), relid, modify_table->nominalRelation, modify_table->onConflictAction, modify_table->operation, returning_list); +#endif } } @@ -807,9 +872,19 @@ partition_router_visitor(Plan *plan, void *context) { List *rtable = (List *) context; ModifyTable *modify_table = (ModifyTable *) plan; +#if PG_VERSION_NUM >= 140000 /* for changes 86dc90056dfd */ + /* + * We have only one subplan for 14: need to modify it without + * using any cycle + */ + Plan *subplan = outerPlan(modify_table); + ListCell *lc2, + *lc3; +#else ListCell *lc1, *lc2, *lc3; +#endif bool changed = false; /* Skip if not ModifyTable with 'UPDATE' command */ @@ -827,8 +902,12 @@ partition_router_visitor(Plan *plan, void *context) } lc3 = list_head(modify_table->returningLists); +#if PG_VERSION_NUM >= 140000 /* for changes 86dc90056dfd */ + lc2 = list_head(modify_table->resultRelations); +#else forboth (lc1, modify_table->plans, lc2, modify_table->resultRelations) +#endif { Index rindex = lfirst_int(lc2); Oid relid = getrelid(rindex, rtable), @@ -849,11 +928,20 @@ partition_router_visitor(Plan *plan, void *context) if (lc3) { returning_list = lfirst(lc3); +#if PG_VERSION_NUM < 140000 lc3 = lnext_compat(modify_table->returningLists, lc3); +#endif } +#if PG_VERSION_NUM >= 140000 /* for changes 86dc90056dfd */ + prouter = make_partition_router(subplan, + modify_table->epqParam, + modify_table->nominalRelation); +#else prouter = make_partition_router((Plan *) lfirst(lc1), - modify_table->epqParam); + modify_table->epqParam, + modify_table->nominalRelation); +#endif pfilter = make_partition_filter((Plan *) prouter, relid, modify_table->nominalRelation, @@ -861,7 +949,11 @@ partition_router_visitor(Plan *plan, void *context) CMD_UPDATE, returning_list); +#if PG_VERSION_NUM >= 140000 /* for changes in 86dc90056dfd */ + outerPlan(modify_table) = pfilter; +#else lfirst(lc1) = pfilter; +#endif changed = true; } } diff --git a/src/relation_info.c b/src/relation_info.c index df60dde3..2794a183 100644 --- a/src/relation_info.c +++ b/src/relation_info.c @@ -71,34 +71,34 @@ int prel_resowner_line = 0; #define LeakTrackerAdd(prel) \ do { \ - MemoryContext old_mcxt = MemoryContextSwitchTo((prel)->mcxt); \ + MemoryContext leak_tracker_add_old_mcxt = MemoryContextSwitchTo((prel)->mcxt); \ (prel)->owners = \ list_append_unique( \ (prel)->owners, \ list_make2(makeString((char *) prel_resowner_function), \ makeInteger(prel_resowner_line))); \ - MemoryContextSwitchTo(old_mcxt); \ + MemoryContextSwitchTo(leak_tracker_add_old_mcxt); \ \ (prel)->access_total++; \ } while (0) #define LeakTrackerPrint(prel) \ do { \ - ListCell *lc; \ - foreach (lc, (prel)->owners) \ + ListCell *leak_tracker_print_lc; \ + foreach (leak_tracker_print_lc, (prel)->owners) \ { \ - char *fun = strVal(linitial(lfirst(lc))); \ - int line = intVal(lsecond(lfirst(lc))); \ + char *fun = strVal(linitial(lfirst(leak_tracker_print_lc))); \ + int line = intVal(lsecond(lfirst(leak_tracker_print_lc))); \ elog(WARNING, "PartRelationInfo referenced in %s:%d", fun, line); \ } \ } while (0) #define LeakTrackerFree(prel) \ do { \ - ListCell *lc; \ - foreach (lc, (prel)->owners) \ + ListCell *leak_tracker_free_lc; \ + foreach (leak_tracker_free_lc, (prel)->owners) \ { \ - list_free_deep(lfirst(lc)); \ + list_free_deep(lfirst(leak_tracker_free_lc)); \ } \ list_free((prel)->owners); \ (prel)->owners = NIL; \ @@ -304,7 +304,7 @@ invalidate_psin_entry(PartStatusInfo *psin) void close_pathman_relation_info(PartRelationInfo *prel) { - AssertArg(prel); + Assert(prel); (void) resowner_prel_del(prel); } @@ -1167,7 +1167,7 @@ invalidate_bounds_cache(void) /* * Get constraint expression tree of a partition. * - * build_check_constraint_name_internal() is used to build conname. + * build_check_constraint_name_relid_internal() is used to build conname. */ Expr * get_partition_constraint_expr(Oid partition, bool raise_error) @@ -1193,6 +1193,16 @@ get_partition_constraint_expr(Oid partition, bool raise_error) } con_tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(conid)); + if (!HeapTupleIsValid(con_tuple)) + { + if (!raise_error) + return NULL; + + ereport(ERROR, + (errmsg("cache lookup failed for constraint \"%s\" of partition \"%s\"", + conname, get_rel_name_or_relid(partition)))); + } + conbin_datum = SysCacheGetAttr(CONSTROID, con_tuple, Anum_pg_constraint_conbin, &conbin_isnull); @@ -1204,9 +1214,6 @@ get_partition_constraint_expr(Oid partition, bool raise_error) ereport(ERROR, (errmsg("constraint \"%s\" of partition \"%s\" has NULL conbin", conname, get_rel_name_or_relid(partition)))); - pfree(conname); - - return NULL; /* could not parse */ } pfree(conname); @@ -1444,7 +1451,7 @@ parse_partitioning_expression(const Oid relid, PG_TRY(); { - parsetree_list = raw_parser(query_string); + parsetree_list = raw_parser_compat(query_string); } PG_CATCH(); { @@ -1484,7 +1491,8 @@ parse_partitioning_expression(const Oid relid, return ((ResTarget *) linitial(select_stmt->targetList))->val; } -/* Parse partitioning expression and return its type and nodeToString() as TEXT */ +/* Parse partitioning expression and return its type and nodeToString() + * (or nodeToStringWithLocations() in version 17 and higher) as TEXT */ Node * cook_partitioning_expression(const Oid relid, const char *expr_cstr, @@ -1555,7 +1563,7 @@ cook_partitioning_expression(const Oid relid, " must be marked IMMUTABLE"))); /* Sanity check #5 */ - expr_varnos = pull_varnos(expr); + expr_varnos = pull_varnos_compat(NULL, expr); if (bms_num_members(expr_varnos) != 1 || relid != ((RangeTblEntry *) linitial(query->rtable))->relid) { diff --git a/src/runtime_merge_append.c b/src/runtime_merge_append.c index 601c663f..5edd803c 100644 --- a/src/runtime_merge_append.c +++ b/src/runtime_merge_append.c @@ -374,7 +374,8 @@ fetch_next_tuple(CustomScanState *node) for (i = 0; i < scan_state->rstate.ncur_plans; i++) { ChildScanCommon child = scan_state->rstate.cur_plans[i]; - PlanState *ps = child->content.plan_state; + + ps = child->content.plan_state; Assert(child->content_type == CHILD_PLAN_STATE); @@ -721,10 +722,11 @@ prepare_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, foreach(j, ec->ec_members) { - EquivalenceMember *em = (EquivalenceMember *) lfirst(j); List *exprvars; ListCell *k; + em = (EquivalenceMember *) lfirst(j); + /* * We shouldn't be trying to sort by an equivalence class that * contains a constant, so no need to consider such cases any diff --git a/src/utility_stmt_hooking.c b/src/utility_stmt_hooking.c index c9ffbf14..83bfa680 100644 --- a/src/utility_stmt_hooking.c +++ b/src/utility_stmt_hooking.c @@ -26,12 +26,18 @@ #include "access/xact.h" #include "catalog/namespace.h" #include "commands/copy.h" +#if PG_VERSION_NUM >= 160000 /* for commit a61b1f74823c */ +#include "commands/copyfrom_internal.h" +#endif #include "commands/defrem.h" #include "commands/trigger.h" #include "commands/tablecmds.h" #include "foreign/fdwapi.h" #include "miscadmin.h" #include "nodes/makefuncs.h" +#if PG_VERSION_NUM >= 160000 /* for commit a61b1f74823c */ +#include "parser/parse_relation.h" +#endif #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -67,7 +73,12 @@ ProtocolVersion FrontendProtocol = (ProtocolVersion) 0; #define PATHMAN_COPY_WRITE_LOCK RowExclusiveLock -static uint64 PathmanCopyFrom(CopyState cstate, +static uint64 PathmanCopyFrom( +#if PG_VERSION_NUM >= 140000 /* Structure changed in c532d15dddff */ + CopyFromState cstate, +#else + CopyState cstate, +#endif Relation parent_rel, List *range_table, bool old_protocol); @@ -109,7 +120,11 @@ is_pathman_related_copy(Node *parsetree) (copy_stmt->is_from ? PATHMAN_COPY_WRITE_LOCK : PATHMAN_COPY_READ_LOCK), - false); + true); + + /* Skip relation if it does not exist (for Citus compatibility) */ + if (!OidIsValid(parent_relid)) + return false; /* Check that relation is partitioned */ if (has_pathman_relation_info(parent_relid)) @@ -175,7 +190,11 @@ is_pathman_related_table_rename(Node *parsetree, /* Fetch Oid of this relation */ relation_oid = RangeVarGetRelid(rename_stmt->relation, AccessShareLock, - false); + rename_stmt->missing_ok); + + /* Check ALTER TABLE ... IF EXISTS of nonexistent table */ + if (rename_stmt->missing_ok && relation_oid == InvalidOid) + return false; /* Assume it's a parent */ if (has_pathman_relation_info(relation_oid)) @@ -226,13 +245,21 @@ is_pathman_related_alter_column_type(Node *parsetree, return false; /* Are we going to modify some table? */ +#if PG_VERSION_NUM >= 140000 + if (alter_table_stmt->objtype != OBJECT_TABLE) +#else if (alter_table_stmt->relkind != OBJECT_TABLE) +#endif return false; /* Assume it's a parent, fetch its Oid */ parent_relid = RangeVarGetRelid(alter_table_stmt->relation, AccessShareLock, - false); + alter_table_stmt->missing_ok); + + /* Check ALTER TABLE ... IF EXISTS of nonexistent table */ + if (alter_table_stmt->missing_ok && parent_relid == InvalidOid) + return false; /* Is parent partitioned? */ if ((prel = get_pathman_relation_info(parent_relid)) != NULL) @@ -276,7 +303,7 @@ is_pathman_related_alter_column_type(Node *parsetree, } /* - * CopyGetAttnums - build an integer list of attnums to be copied + * PathmanCopyGetAttnums - build an integer list of attnums to be copied * * The input attnamelist is either the user-specified column list, * or NIL if there was none (in which case we want all the non-dropped @@ -285,7 +312,7 @@ is_pathman_related_alter_column_type(Node *parsetree, * rel can be NULL ... it's only used for error reports. */ static List * -CopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist) +PathmanCopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist) { List *attnums = NIL; @@ -364,7 +391,11 @@ PathmanDoCopy(const CopyStmt *stmt, int stmt_len, uint64 *processed) { +#if PG_VERSION_NUM >= 140000 /* Structure changed in c532d15dddff */ + CopyFromState cstate; +#else CopyState cstate; +#endif ParseState *pstate; Relation rel; List *range_table = NIL; @@ -389,6 +420,9 @@ PathmanDoCopy(const CopyStmt *stmt, "psql's \\copy command also works for anyone."))); } + pstate = make_parsestate(NULL); + pstate->p_sourcetext = queryString; + /* Check that we have a relation */ if (stmt->relation) { @@ -397,6 +431,9 @@ PathmanDoCopy(const CopyStmt *stmt, List *attnums; ListCell *cur; RangeTblEntry *rte; +#if PG_VERSION_NUM >= 160000 /* for commit a61b1f74823c */ + RTEPermissionInfo *perminfo; +#endif Assert(!stmt->query); @@ -407,11 +444,30 @@ PathmanDoCopy(const CopyStmt *stmt, rte->rtekind = RTE_RELATION; rte->relid = RelationGetRelid(rel); rte->relkind = rel->rd_rel->relkind; +#if PG_VERSION_NUM >= 160000 /* for commit a61b1f74823c */ + pstate->p_rtable = lappend(pstate->p_rtable, rte); + perminfo = addRTEPermissionInfo(&pstate->p_rteperminfos, rte); + perminfo->requiredPerms = required_access; +#else rte->requiredPerms = required_access; +#endif range_table = list_make1(rte); tupDesc = RelationGetDescr(rel); - attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist); + attnums = PathmanCopyGetAttnums(tupDesc, rel, stmt->attlist); +#if PG_VERSION_NUM >= 160000 /* for commit a61b1f74823c */ + foreach(cur, attnums) + { + int attno; + Bitmapset **bms; + + attno = lfirst_int(cur) - FirstLowInvalidHeapAttributeNumber; + bms = is_from ? &perminfo->insertedCols : &perminfo->selectedCols; + + *bms = bms_add_member(*bms, attno); + } + ExecCheckPermissions(pstate->p_rtable, list_make1(perminfo), true); +#else foreach(cur, attnums) { int attnum = lfirst_int(cur) - FirstLowInvalidHeapAttributeNumber; @@ -422,6 +478,7 @@ PathmanDoCopy(const CopyStmt *stmt, rte->selectedCols = bms_add_member(rte->selectedCols, attnum); } ExecCheckRTPerms(range_table, true); +#endif /* Disable COPY FROM if table has RLS */ if (is_from && check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED) @@ -445,9 +502,6 @@ PathmanDoCopy(const CopyStmt *stmt, /* This should never happen (see is_pathman_related_copy()) */ else elog(ERROR, "error in function " CppAsString(PathmanDoCopy)); - pstate = make_parsestate(NULL); - pstate->p_sourcetext = queryString; - if (is_from) { /* check read-only transaction and parallel mode */ @@ -463,6 +517,14 @@ PathmanDoCopy(const CopyStmt *stmt, } else { +#if PG_VERSION_NUM >= 160000 /* for commit f75cec4fff87 */ + /* + * Forget current RangeTblEntries and RTEPermissionInfos. + * Standard DoCopy will create new ones. + */ + pstate->p_rtable = NULL; + pstate->p_rteperminfos = NULL; +#endif /* Call standard DoCopy using a new CopyStmt */ DoCopyCompat(pstate, stmt, stmt_location, stmt_len, processed); } @@ -475,7 +537,13 @@ PathmanDoCopy(const CopyStmt *stmt, * Copy FROM file to relation. */ static uint64 -PathmanCopyFrom(CopyState cstate, Relation parent_rel, +PathmanCopyFrom( +#if PG_VERSION_NUM >= 140000 /* Structure changed in c532d15dddff */ + CopyFromState cstate, +#else + CopyState cstate, +#endif + Relation parent_rel, List *range_table, bool old_protocol) { HeapTuple tuple; @@ -502,6 +570,27 @@ PathmanCopyFrom(CopyState cstate, Relation parent_rel, 0); ExecOpenIndices(parent_rri, false); +#if PG_VERSION_NUM >= 140000 /* reworked in 1375422c7826 */ + /* + * Call ExecInitRangeTable() should be first because in 14+ it initializes + * field "estate->es_result_relations": + */ +#if PG_VERSION_NUM >= 160000 + ExecInitRangeTable(estate, range_table, cstate->rteperminfos); +#else + ExecInitRangeTable(estate, range_table); +#endif + estate->es_result_relations = + (ResultRelInfo **) palloc0(list_length(range_table) * sizeof(ResultRelInfo *)); + estate->es_result_relations[0] = parent_rri; + /* + * Saving in the list allows to avoid needlessly traversing the whole + * array when only a few of its entries are possibly non-NULL. + */ + estate->es_opened_result_relations = + lappend(estate->es_opened_result_relations, parent_rri); + estate->es_result_relation_info = parent_rri; +#else estate->es_result_relations = parent_rri; estate->es_num_result_relations = 1; estate->es_result_relation_info = parent_rri; @@ -510,7 +599,7 @@ PathmanCopyFrom(CopyState cstate, Relation parent_rel, #else estate->es_range_table = range_table; #endif - +#endif /* Initialize ResultPartsStorage */ init_result_parts_storage(&parts_storage, parent_relid, parent_rri, @@ -519,6 +608,16 @@ PathmanCopyFrom(CopyState cstate, Relation parent_rel, RPS_DEFAULT_SPECULATIVE, RPS_RRI_CB(prepare_rri_for_copy, cstate), RPS_RRI_CB(finish_rri_for_copy, NULL)); +#if PG_VERSION_NUM >= 160000 /* for commit a61b1f74823c */ + /* ResultRelInfo of partitioned table. */ + parts_storage.init_rri = parent_rri; + + /* + * Copy the RTEPermissionInfos into estate as well, so that + * scan_result_parts_storage() et al will work correctly. + */ + estate->es_rteperminfos = cstate->rteperminfos; +#endif /* Set up a tuple slot too */ myslot = ExecInitExtraTupleSlotCompat(estate, NULL, &TTSOpsHeapTuple); @@ -581,7 +680,7 @@ PathmanCopyFrom(CopyState cstate, Relation parent_rel, #endif /* Search for a matching partition */ - rri_holder = select_partition_for_insert(&parts_storage, slot); + rri_holder = select_partition_for_insert(estate, &parts_storage, slot); child_rri = rri_holder->result_rel_info; /* Magic: replace parent's ResultRelInfo with ours */ @@ -661,8 +760,8 @@ PathmanCopyFrom(CopyState cstate, Relation parent_rel, /* ... and create index entries for it */ if (child_rri->ri_NumIndices > 0) - recheckIndexes = ExecInsertIndexTuplesCompat(slot, &(tuple->t_self), - estate, false, NULL, NIL); + recheckIndexes = ExecInsertIndexTuplesCompat(estate->es_result_relation_info, + slot, &(tuple->t_self), estate, false, false, NULL, NIL, false); } #ifdef PG_SHARDMAN /* Handle foreign tables */ diff --git a/src/utils.c b/src/utils.c index ddf10bae..9402d618 100644 --- a/src/utils.c +++ b/src/utils.c @@ -515,10 +515,10 @@ qualified_relnames_to_rangevars(char **relnames, size_t nrelnames) /* Convert partition names into RangeVars */ if (relnames) { - rangevars = palloc(sizeof(RangeVar) * nrelnames); + rangevars = palloc(sizeof(RangeVar *) * nrelnames); for (i = 0; i < nrelnames; i++) { - List *nl = stringToQualifiedNameList(relnames[i]); + List *nl = stringToQualifiedNameListCompat(relnames[i]); rangevars[i] = makeRangeVarFromNameList(nl); } @@ -527,3 +527,14 @@ qualified_relnames_to_rangevars(char **relnames, size_t nrelnames) return rangevars; } +/* + * Checks that Oid is valid (it need to do before relation locking: locking of + * invalid Oid causes an error on replica). + */ +void +check_relation_oid(Oid relid) +{ + if (relid < FirstNormalObjectId) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("identifier \"%u\" must be normal Oid", relid))); +} diff --git a/tests/cmocka/missing_basic.c b/tests/cmocka/missing_basic.c index 36d76160..d20eb87f 100644 --- a/tests/cmocka/missing_basic.c +++ b/tests/cmocka/missing_basic.c @@ -24,20 +24,29 @@ pfree(void *pointer) void ExceptionalCondition(const char *conditionName, +#if PG_VERSION_NUM < 160000 const char *errorType, +#endif const char *fileName, int lineNumber) { - if (!PointerIsValid(conditionName) || - !PointerIsValid(fileName) || - !PointerIsValid(errorType)) + if (!PointerIsValid(conditionName) || !PointerIsValid(fileName) +#if PG_VERSION_NUM < 160000 + || !PointerIsValid(errorType) +#endif + ) { printf("TRAP: ExceptionalCondition: bad arguments\n"); } else { printf("TRAP: %s(\"%s\", File: \"%s\", Line: %d)\n", - errorType, conditionName, +#if PG_VERSION_NUM < 160000 + errorType, +#else + "", +#endif + conditionName, fileName, lineNumber); } diff --git a/tests/cmocka/missing_stringinfo.c b/tests/cmocka/missing_stringinfo.c index edf4d8a4..80710a4e 100644 --- a/tests/cmocka/missing_stringinfo.c +++ b/tests/cmocka/missing_stringinfo.c @@ -206,7 +206,13 @@ appendStringInfoSpaces(StringInfo str, int count) * if necessary. */ void -appendBinaryStringInfo(StringInfo str, const char *data, int datalen) +appendBinaryStringInfo(StringInfo str, +#if PG_VERSION_NUM < 160000 + const char *data, +#else + const void *data, +#endif + int datalen) { Assert(str != NULL); diff --git a/tests/python/partitioning_test.py b/tests/python/partitioning_test.py index ad555455..ba4b205f 100644 --- a/tests/python/partitioning_test.py +++ b/tests/python/partitioning_test.py @@ -20,7 +20,7 @@ import time import unittest -from distutils.version import LooseVersion +from packaging.version import Version from testgres import get_new_node, get_pg_version, configure_testgres # set setup base logging config, it can be turned on by `use_python_logging` @@ -58,7 +58,7 @@ } logging.config.dictConfig(LOG_CONFIG) -version = LooseVersion(get_pg_version()) +version = Version(get_pg_version()) # Helper function for json equality @@ -448,7 +448,7 @@ def test_parallel_nodes(self): # Check version of postgres server # If version < 9.6 skip all tests for parallel queries - if version < LooseVersion('9.6.0'): + if version < Version('9.6.0'): return # Prepare test database @@ -485,7 +485,7 @@ def test_parallel_nodes(self): # Test parallel select with node.connect() as con: con.execute('set max_parallel_workers_per_gather = 2') - if version >= LooseVersion('10'): + if version >= Version('10'): con.execute('set min_parallel_table_scan_size = 0') else: con.execute('set min_parallel_relation_size = 0') @@ -549,7 +549,7 @@ def test_parallel_nodes(self): } ] """) - self.assertEqual(ordered(plan, skip_keys=['Subplans Removed']), ordered(expected)) + self.assertEqual(ordered(plan, skip_keys=['Subplans Removed', 'Async Capable']), ordered(expected)) # Check count of returned tuples count = con.execute( @@ -602,7 +602,7 @@ def test_parallel_nodes(self): } ] """) - self.assertEqual(ordered(plan, skip_keys=['Subplans Removed']), ordered(expected)) + self.assertEqual(ordered(plan, skip_keys=['Subplans Removed', 'Async Capable']), ordered(expected)) # Check tuples returned by query above res_tuples = con.execute( @@ -625,7 +625,7 @@ def test_parallel_nodes(self): } ] """) - self.assertEqual(ordered(plan), ordered(expected)) + self.assertEqual(ordered(plan, skip_keys=['Async Capable']), ordered(expected)) # Remove all objects for testing node.psql('drop table range_partitioned cascade') @@ -665,13 +665,13 @@ def con2_thread(): res = con2.execute(""" explain (analyze, costs off, timing off) select * from drop_test - where val = any (select generate_series(1, 40, 34)) - """) # query selects from drop_test_1 and drop_test_4 + where val = any (select generate_series(22, 40, 13)) + """) # query selects from drop_test_3 and drop_test_4 con2.commit() has_runtime_append = False - has_drop_test_1 = False + has_drop_test_3 = False has_drop_test_4 = False for row in res: @@ -679,8 +679,8 @@ def con2_thread(): has_runtime_append = True continue - if row[0].find('drop_test_1') >= 0: - has_drop_test_1 = True + if row[0].find('drop_test_3') >= 0: + has_drop_test_3 = True continue if row[0].find('drop_test_4') >= 0: @@ -688,7 +688,7 @@ def con2_thread(): continue # return all values in tuple - queue.put((has_runtime_append, has_drop_test_1, has_drop_test_4)) + queue.put((has_runtime_append, has_drop_test_3, has_drop_test_4)) # Step 1: cache partitioned table in con1 con1.begin() @@ -702,7 +702,7 @@ def con2_thread(): # Step 3: drop first partition of 'drop_test' con1.begin() - con1.execute('drop table drop_test_1') + con1.execute('drop table drop_test_3') # Step 4: try executing select (RuntimeAppend) t = threading.Thread(target=con2_thread) @@ -734,9 +734,9 @@ def con2_thread(): self.assertEqual(len(rows), 99) # check RuntimeAppend + selected partitions - (has_runtime_append, has_drop_test_1, has_drop_test_4) = queue.get() + (has_runtime_append, has_drop_test_3, has_drop_test_4) = queue.get() self.assertTrue(has_runtime_append) - self.assertFalse(has_drop_test_1) + self.assertFalse(has_drop_test_3) self.assertTrue(has_drop_test_4) def test_conc_part_creation_insert(self): @@ -1044,34 +1044,36 @@ def test_update_node_plan1(self): self.assertEqual(plan["Relation Name"], "test_range") self.assertEqual(len(plan["Target Tables"]), 11) - expected_format = ''' - { - "Plans": [ - { - "Plans": [ - { - "Filter": "(comment = '15'::text)", - "Node Type": "Seq Scan", - "Relation Name": "test_range%s", - "Parent Relationship": "child" - } - ], - "Node Type": "Custom Scan", - "Parent Relationship": "child", - "Custom Plan Provider": "PartitionRouter" - } - ], - "Node Type": "Custom Scan", - "Parent Relationship": "Member", - "Custom Plan Provider": "PartitionFilter" - } - ''' - - for i, f in enumerate([''] + list(map(str, range(1, 10)))): - num = '_' + f if f else '' - expected = json.loads(expected_format % num) - p = ordered(plan["Plans"][i], skip_keys=['Parallel Aware', 'Alias']) - self.assertEqual(p, ordered(expected)) + # Plan was seriously changed in vanilla since v14 + if version < Version('14'): + expected_format = ''' + { + "Plans": [ + { + "Plans": [ + { + "Filter": "(comment = '15'::text)", + "Node Type": "Seq Scan", + "Relation Name": "test_range%s", + "Parent Relationship": "child" + } + ], + "Node Type": "Custom Scan", + "Parent Relationship": "child", + "Custom Plan Provider": "PartitionRouter" + } + ], + "Node Type": "Custom Scan", + "Parent Relationship": "Member", + "Custom Plan Provider": "PartitionFilter" + } + ''' + + for i, f in enumerate([''] + list(map(str, range(1, 10)))): + num = '_' + f if f else '' + expected = json.loads(expected_format % num) + p = ordered(plan["Plans"][i], skip_keys=['Parallel Aware', 'Alias']) + self.assertEqual(p, ordered(expected)) node.psql('postgres', 'DROP SCHEMA test_update_node CASCADE;') node.psql('postgres', 'DROP EXTENSION pg_pathman CASCADE;')