├── .dockerignore ├── .editorconfig ├── .gitattributes ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── Dockerfile.tmpl ├── LICENSE ├── META.json ├── Makefile ├── README.md ├── conf.add ├── docker-compose.yml ├── expected ├── for_update.out ├── insert_nodes.out ├── pathman_CVE-2020-14350.out ├── pathman_array_qual.out ├── pathman_array_qual_1.out ├── pathman_array_qual_2.out ├── pathman_basic.out ├── pathman_basic_1.out ├── pathman_basic_2.out ├── pathman_bgw.out ├── pathman_cache_pranks.out ├── pathman_cache_pranks_1.out ├── pathman_calamity.out ├── pathman_calamity_1.out ├── pathman_calamity_2.out ├── pathman_calamity_3.out ├── pathman_callbacks.out ├── pathman_check.out ├── pathman_column_type.out ├── pathman_column_type_1.out ├── pathman_column_type_2.out ├── pathman_cte.out ├── pathman_cte_1.out ├── pathman_cte_2.out ├── pathman_cte_3.out ├── pathman_declarative.out ├── pathman_declarative_1.out ├── pathman_domains.out ├── pathman_domains_1.out ├── pathman_dropped_cols.out ├── pathman_expressions.out ├── pathman_expressions_1.out ├── pathman_expressions_2.out ├── pathman_expressions_3.out ├── pathman_foreign_keys.out ├── pathman_gaps.out ├── pathman_gaps_1.out ├── pathman_gaps_2.out ├── pathman_hashjoin.out ├── pathman_hashjoin_1.out ├── pathman_hashjoin_2.out ├── pathman_hashjoin_3.out ├── pathman_hashjoin_4.out ├── pathman_hashjoin_5.out ├── pathman_hashjoin_6.out ├── pathman_inserts.out ├── pathman_inserts_1.out ├── pathman_inserts_2.out ├── pathman_interval.out ├── pathman_join_clause.out ├── pathman_join_clause_1.out ├── pathman_join_clause_2.out ├── pathman_join_clause_3.out ├── pathman_join_clause_4.out ├── pathman_join_clause_5.out ├── pathman_lateral.out ├── pathman_lateral_1.out ├── pathman_lateral_2.out ├── pathman_lateral_3.out ├── pathman_lateral_4.out ├── pathman_mergejoin.out ├── pathman_mergejoin_1.out ├── pathman_mergejoin_2.out ├── pathman_mergejoin_3.out ├── pathman_mergejoin_4.out ├── pathman_mergejoin_5.out ├── pathman_mergejoin_6.out ├── pathman_only.out ├── pathman_only_1.out ├── pathman_only_2.out ├── pathman_only_3.out ├── pathman_only_4.out ├── pathman_param_upd_del.out ├── pathman_permissions.out ├── pathman_permissions_1.out ├── pathman_rebuild_deletes.out ├── pathman_rebuild_deletes_1.out ├── pathman_rebuild_updates.out ├── pathman_rebuild_updates_1.out ├── pathman_rowmarks.out ├── pathman_rowmarks_1.out ├── pathman_rowmarks_2.out ├── pathman_rowmarks_3.out ├── pathman_rowmarks_4.out ├── pathman_runtime_nodes.out ├── pathman_runtime_nodes_1.out ├── pathman_subpartitions.out ├── pathman_subpartitions_1.out ├── pathman_subpartitions_2.out ├── pathman_upd_del.out ├── pathman_upd_del_1.out ├── pathman_upd_del_2.out ├── pathman_upd_del_3.out ├── pathman_upd_del_4.out ├── pathman_update_node.out ├── pathman_update_triggers.out ├── pathman_update_triggers_1.out ├── pathman_utility_stmt.out ├── pathman_views.out ├── pathman_views_1.out ├── pathman_views_2.out ├── pathman_views_3.out ├── pathman_views_4.out ├── rollback_on_create_partitions.out └── test_variants.sh ├── hash.sql ├── init.sql ├── mk_dockerfile.sh ├── patches ├── REL_11_STABLE-pg_pathman-core.diff ├── REL_14_STABLE-pg_pathman-core.diff ├── REL_15_STABLE-pg_pathman-core.diff └── REL_16_STABLE-pg_pathman-core.diff ├── pg_compat_available.sh ├── pg_pathman--1.0--1.1.sql ├── pg_pathman--1.1--1.2.sql ├── pg_pathman--1.2--1.3.sql ├── pg_pathman--1.3--1.4.sql ├── pg_pathman--1.4--1.5.sql ├── pg_pathman.control ├── range.sql ├── run_tests.sh ├── specs ├── for_update.spec ├── insert_nodes.spec └── rollback_on_create_partitions.spec ├── sql ├── pathman_CVE-2020-14350.sql ├── pathman_array_qual.sql ├── pathman_basic.sql ├── pathman_bgw.sql ├── pathman_cache_pranks.sql ├── pathman_calamity.sql ├── pathman_callbacks.sql ├── pathman_column_type.sql ├── pathman_cte.sql ├── pathman_declarative.sql ├── pathman_domains.sql ├── pathman_dropped_cols.sql ├── pathman_expressions.sql ├── pathman_foreign_keys.sql ├── pathman_gaps.sql ├── pathman_hashjoin.sql ├── pathman_inserts.sql ├── pathman_interval.sql ├── pathman_join_clause.sql ├── pathman_lateral.sql ├── pathman_mergejoin.sql ├── pathman_only.sql ├── pathman_param_upd_del.sql ├── pathman_permissions.sql ├── pathman_rebuild_deletes.sql ├── pathman_rebuild_updates.sql ├── pathman_rowmarks.sql ├── pathman_runtime_nodes.sql ├── pathman_subpartitions.sql ├── pathman_upd_del.sql ├── pathman_update_node.sql ├── pathman_update_triggers.sql ├── pathman_utility_stmt.sql └── pathman_views.sql ├── src ├── compat │ ├── pg_compat.c │ └── rowmarks_fix.c ├── debug_print.c ├── declarative.c ├── hooks.c ├── include │ ├── compat │ │ ├── debug_compat_features.h │ │ ├── pg_compat.h │ │ └── rowmarks_fix.h │ ├── declarative.h │ ├── hooks.h │ ├── init.h │ ├── nodes_common.h │ ├── partition_creation.h │ ├── partition_filter.h │ ├── partition_overseer.h │ ├── partition_router.h │ ├── pathman.h │ ├── pathman_workers.h │ ├── planner_tree_modification.h │ ├── rangeset.h │ ├── relation_info.h │ ├── runtime_append.h │ ├── runtime_merge_append.h │ ├── utility_stmt_hooking.h │ ├── utils.h │ └── xact_handling.h ├── init.c ├── nodes_common.c ├── partition_creation.c ├── partition_filter.c ├── partition_overseer.c ├── partition_router.c ├── pathman_workers.c ├── pg_pathman.c ├── pl_funcs.c ├── pl_hash_funcs.c ├── pl_range_funcs.c ├── planner_tree_modification.c ├── rangeset.c ├── relation_info.c ├── runtime_append.c ├── runtime_merge_append.c ├── utility_stmt_hooking.c ├── utils.c └── xact_handling.c └── tests ├── cmocka ├── .gitignore ├── Makefile ├── missing_basic.c ├── missing_bitmapset.c ├── missing_list.c ├── missing_stringinfo.c ├── rangeset_tests.c └── undef_printf.h ├── python ├── .flake8 ├── .gitignore ├── .style.yapf ├── Makefile ├── README.md ├── __init__.py ├── partitioning_test.py └── pgbench_scripts │ ├── detachs_in_timeout.pgbench │ └── insert_current_timestamp.pgbench └── update ├── README.md ├── check_update.py ├── dump_pathman_objects.sql └── get_sql_diff /.dockerignore: -------------------------------------------------------------------------------- 1 | *.gcno 2 | *.gcda 3 | *.gcov 4 | *.so 5 | *.o 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = tab 3 | indent_size = 4 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | pg_pathman*.sql linguist-vendored=true 2 | *.h linguist-language=C 3 | *.c linguist-language=C 4 | *.spec linguist-vendored=true 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | ### Problem description 10 | 11 | Explain your problem here (it's always better to provide reproduction steps) ... 12 | 13 | 14 | 15 | ### Environment 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .deps 2 | results/* 3 | regression.diffs 4 | regression.out 5 | *.o 6 | *.so 7 | *.pyc 8 | *.gcda 9 | *.gcno 10 | *.gcov 11 | *.log 12 | pg_pathman--*.sql 13 | tags 14 | cscope* 15 | Dockerfile 16 | testgres 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: linux 2 | 3 | dist: focal 4 | 5 | language: c 6 | 7 | services: 8 | - docker 9 | 10 | install: 11 | - ./mk_dockerfile.sh 12 | - docker-compose build 13 | 14 | script: 15 | - docker-compose run $(bash <(curl -s https://codecov.io/env)) tests 16 | 17 | notifications: 18 | email: 19 | on_success: change 20 | on_failure: always 21 | 22 | env: 23 | - PG_VERSION=16 LEVEL=hardcore 24 | - PG_VERSION=16 25 | - PG_VERSION=15 LEVEL=hardcore 26 | - PG_VERSION=15 27 | - PG_VERSION=14 LEVEL=hardcore 28 | - PG_VERSION=14 29 | - PG_VERSION=13 LEVEL=hardcore 30 | - PG_VERSION=13 31 | - PG_VERSION=12 LEVEL=hardcore 32 | - PG_VERSION=12 33 | - PG_VERSION=11 LEVEL=hardcore 34 | - PG_VERSION=11 35 | -------------------------------------------------------------------------------- /Dockerfile.tmpl: -------------------------------------------------------------------------------- 1 | FROM postgres:${PG_VERSION}-alpine 2 | 3 | # Install dependencies 4 | RUN apk add --no-cache \ 5 | openssl curl git patch \ 6 | cmocka-dev \ 7 | perl perl-ipc-run \ 8 | python3 python3-dev py3-virtualenv \ 9 | coreutils linux-headers \ 10 | make musl-dev gcc bison flex \ 11 | zlib-dev libedit-dev \ 12 | pkgconf icu-dev clang clang15 clang-analyzer; 13 | 14 | # Install fresh valgrind 15 | RUN apk add valgrind \ 16 | --update-cache \ 17 | --repository http://dl-3.alpinelinux.org/alpine/edge/main; 18 | 19 | # Environment 20 | ENV LANG=C.UTF-8 PGDATA=/pg/data 21 | 22 | # Make directories 23 | RUN mkdir -p ${PGDATA} && \ 24 | mkdir -p /pg/testdir 25 | 26 | # Add data to test dir 27 | ADD . /pg/testdir 28 | 29 | # Grant privileges 30 | RUN chown -R postgres:postgres ${PGDATA} && \ 31 | chown -R postgres:postgres /pg/testdir && \ 32 | chmod a+rwx /usr/local/share/postgresql/extension && \ 33 | find /usr/local/lib/postgresql -type d -print0 | xargs -0 chmod a+rwx 34 | 35 | COPY run_tests.sh /run.sh 36 | RUN chmod 755 /run.sh 37 | 38 | USER postgres 39 | WORKDIR /pg/testdir 40 | ENTRYPOINT LEVEL=${LEVEL} /run.sh 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | pg_pathman is released under the PostgreSQL License, a liberal Open Source license, similar to the BSD or MIT licenses. 2 | 3 | Copyright (c) 2015-2017, Postgres Professional 4 | Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group 5 | Portions Copyright (c) 1994, The Regents of the University of California 6 | 7 | Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. 8 | 9 | IN NO EVENT SHALL POSTGRES PROFESSIONAL BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF POSTGRES PROFESSIONAL HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | 11 | POSTGRES PROFESSIONAL SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND POSTGRES PROFESSIONAL HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 12 | -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pg_pathman", 3 | "abstract": "Fast partitioning tool for PostgreSQL", 4 | "description": "pg_pathman provides optimized partitioning mechanism and functions to manage partitions.", 5 | "version": "1.5.12", 6 | "maintainer": [ 7 | "Arseny Sher " 8 | ], 9 | "license": "postgresql", 10 | "resources": { 11 | "bugtracker": { 12 | "web": "https://github.com/postgrespro/pg_pathman/issues" 13 | }, 14 | "repository": { 15 | "url": "git://github.com:postgrespro/pg_pathman.git", 16 | "web": "https://github.com/postgrespro/pg_pathman", 17 | "type": "git" 18 | } 19 | }, 20 | "generated_by": "pgpro", 21 | "provides": { 22 | "pg_pathman": { 23 | "file": "pg_pathman--1.5.sql", 24 | "docfile": "README.md", 25 | "version": "1.5.12", 26 | "abstract": "Effective partitioning tool for PostgreSQL 9.5 and higher" 27 | } 28 | }, 29 | "meta-spec": { 30 | "version": "1.0.0", 31 | "url": "http://pgxn.org/meta/spec.txt" 32 | }, 33 | "tags": [ 34 | "partitioning", 35 | "partition", 36 | "optimization", 37 | "table", 38 | "tables", 39 | "custom node", 40 | "runtime append", 41 | "background worker", 42 | "fdw", 43 | "range", 44 | "hash" 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # contrib/pg_pathman/Makefile 2 | 3 | MODULE_big = pg_pathman 4 | 5 | OBJS = src/init.o src/relation_info.o src/utils.o src/partition_filter.o \ 6 | src/runtime_append.o src/runtime_merge_append.o src/pg_pathman.o src/rangeset.o \ 7 | src/pl_funcs.o src/pl_range_funcs.o src/pl_hash_funcs.o src/pathman_workers.o \ 8 | src/hooks.o src/nodes_common.o src/xact_handling.o src/utility_stmt_hooking.o \ 9 | src/planner_tree_modification.o src/debug_print.o src/partition_creation.o \ 10 | src/compat/pg_compat.o src/compat/rowmarks_fix.o src/partition_router.o \ 11 | src/partition_overseer.o $(WIN32RES) 12 | 13 | ifdef USE_PGXS 14 | override PG_CPPFLAGS += -I$(CURDIR)/src/include 15 | else 16 | override PG_CPPFLAGS += -I$(top_srcdir)/$(subdir)/src/include 17 | endif 18 | 19 | EXTENSION = pg_pathman 20 | 21 | EXTVERSION = 1.5 22 | 23 | DATA_built = pg_pathman--$(EXTVERSION).sql 24 | 25 | DATA = pg_pathman--1.0--1.1.sql \ 26 | pg_pathman--1.1--1.2.sql \ 27 | pg_pathman--1.2--1.3.sql \ 28 | pg_pathman--1.3--1.4.sql \ 29 | pg_pathman--1.4--1.5.sql 30 | 31 | PGFILEDESC = "pg_pathman - partitioning tool for PostgreSQL" 32 | 33 | ifneq (pg_pathman,$(filter pg_pathman,$(PG_TEST_SKIP))) 34 | REGRESS = pathman_array_qual \ 35 | pathman_basic \ 36 | pathman_bgw \ 37 | pathman_cache_pranks \ 38 | pathman_calamity \ 39 | pathman_callbacks \ 40 | pathman_column_type \ 41 | pathman_cte \ 42 | pathman_domains \ 43 | pathman_dropped_cols \ 44 | pathman_expressions \ 45 | pathman_foreign_keys \ 46 | pathman_gaps \ 47 | pathman_inserts \ 48 | pathman_interval \ 49 | pathman_join_clause \ 50 | pathman_lateral \ 51 | pathman_hashjoin \ 52 | pathman_mergejoin \ 53 | pathman_only \ 54 | pathman_param_upd_del \ 55 | pathman_permissions \ 56 | pathman_rebuild_deletes \ 57 | pathman_rebuild_updates \ 58 | pathman_rowmarks \ 59 | pathman_runtime_nodes \ 60 | pathman_subpartitions \ 61 | pathman_update_node \ 62 | pathman_update_triggers \ 63 | pathman_upd_del \ 64 | pathman_utility_stmt \ 65 | pathman_views \ 66 | pathman_CVE-2020-14350 67 | endif 68 | 69 | ISOLATION = insert_nodes for_update rollback_on_create_partitions 70 | 71 | REGRESS_OPTS = --temp-config $(top_srcdir)/$(subdir)/conf.add 72 | ISOLATION_OPTS = --temp-config $(top_srcdir)/$(subdir)/conf.add 73 | 74 | CMOCKA_EXTRA_CLEAN = missing_basic.o missing_list.o missing_stringinfo.o missing_bitmapset.o rangeset_tests.o rangeset_tests 75 | EXTRA_CLEAN = $(patsubst %,tests/cmocka/%, $(CMOCKA_EXTRA_CLEAN)) 76 | 77 | ifdef USE_PGXS 78 | PG_CONFIG=pg_config 79 | PGXS := $(shell $(PG_CONFIG) --pgxs) 80 | VNUM := $(shell $(PG_CONFIG) --version | awk '{print $$2}') 81 | 82 | # check for declarative syntax 83 | # this feature will not be ported to >=12 84 | ifeq ($(VNUM),$(filter 10% 11%,$(VNUM))) 85 | REGRESS += pathman_declarative 86 | OBJS += src/declarative.o 87 | override PG_CPPFLAGS += -DENABLE_DECLARATIVE 88 | endif 89 | 90 | # We cannot run isolation test for versions 12,13 in PGXS case 91 | # because 'pg_isolation_regress' is not copied to install 92 | # directory, see src/test/isolation/Makefile 93 | ifeq ($(VNUM),$(filter 12% 13%,$(VNUM))) 94 | undefine ISOLATION 95 | undefine ISOLATION_OPTS 96 | endif 97 | 98 | include $(PGXS) 99 | else 100 | subdir = contrib/pg_pathman 101 | top_builddir = ../.. 102 | include $(top_builddir)/src/Makefile.global 103 | include $(top_srcdir)/contrib/contrib-global.mk 104 | endif 105 | 106 | $(EXTENSION)--$(EXTVERSION).sql: init.sql hash.sql range.sql 107 | cat $^ > $@ 108 | 109 | python_tests: 110 | $(MAKE) -C tests/python partitioning_tests CASE=$(CASE) 111 | 112 | cmocka_tests: 113 | $(MAKE) -C tests/cmocka check 114 | 115 | clean_gcov: 116 | find . \ 117 | -name "*.gcda" -delete -o \ 118 | -name "*.gcno" -delete -o \ 119 | -name "*.gcov" -delete 120 | -------------------------------------------------------------------------------- /conf.add: -------------------------------------------------------------------------------- 1 | shared_preload_libraries='pg_pathman' 2 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | tests: 3 | build: . 4 | -------------------------------------------------------------------------------- /expected/for_update.out: -------------------------------------------------------------------------------- 1 | Parsed test spec with 2 sessions 2 | 3 | starting permutation: s1_b s1_update s2_select s1_r 4 | create_range_partitions 5 | ----------------------- 6 | 10 7 | (1 row) 8 | 9 | step s1_b: begin; 10 | step s1_update: update test_tbl set id = 2 where id = 1; 11 | step s2_select: select * from test_tbl where id = 1; 12 | id|val 13 | --+--- 14 | 1| 1 15 | (1 row) 16 | 17 | step s1_r: rollback; 18 | 19 | starting permutation: s1_b s1_update s2_select_locked s1_r 20 | create_range_partitions 21 | ----------------------- 22 | 10 23 | (1 row) 24 | 25 | step s1_b: begin; 26 | step s1_update: update test_tbl set id = 2 where id = 1; 27 | step s2_select_locked: select * from test_tbl where id = 1 for share; 28 | step s1_r: rollback; 29 | step s2_select_locked: <... completed> 30 | id|val 31 | --+--- 32 | 1| 1 33 | (1 row) 34 | 35 | 36 | starting permutation: s1_b s1_update s2_select_locked s1_c 37 | create_range_partitions 38 | ----------------------- 39 | 10 40 | (1 row) 41 | 42 | step s1_b: begin; 43 | step s1_update: update test_tbl set id = 2 where id = 1; 44 | step s2_select_locked: select * from test_tbl where id = 1 for share; 45 | step s1_c: commit; 46 | step s2_select_locked: <... completed> 47 | id|val 48 | --+--- 49 | (0 rows) 50 | 51 | -------------------------------------------------------------------------------- /expected/insert_nodes.out: -------------------------------------------------------------------------------- 1 | Parsed test spec with 2 sessions 2 | 3 | starting permutation: s1b s1_insert_150 s1r s1_show_partitions s2b s2_insert_150 s2c s2_show_partitions 4 | set_spawn_using_bgw 5 | ------------------- 6 | 7 | (1 row) 8 | 9 | step s1b: BEGIN; 10 | step s1_insert_150: INSERT INTO range_rel SELECT generate_series(1, 150); 11 | step s1r: ROLLBACK; 12 | step s1_show_partitions: SELECT pg_get_constraintdef(c.oid) FROM pg_inherits i LEFT JOIN pg_constraint c 13 | ON c.conrelid = i.inhrelid 14 | WHERE i.inhparent = 'range_rel'::regclass AND c.contype = 'c' 15 | ORDER BY c.oid; 16 | pg_get_constraintdef 17 | ------------------------------------ 18 | CHECK (((id >= 1) AND (id < 101))) 19 | CHECK (((id >= 101) AND (id < 201))) 20 | (2 rows) 21 | 22 | step s2b: BEGIN; 23 | step s2_insert_150: INSERT INTO range_rel SELECT generate_series(1, 150); 24 | step s2c: COMMIT; 25 | step s2_show_partitions: SELECT pg_get_constraintdef(c.oid) FROM pg_inherits i LEFT JOIN pg_constraint c 26 | ON c.conrelid = i.inhrelid 27 | WHERE i.inhparent = 'range_rel'::regclass AND c.contype = 'c' 28 | ORDER BY c.oid; 29 | pg_get_constraintdef 30 | ------------------------------------ 31 | CHECK (((id >= 1) AND (id < 101))) 32 | CHECK (((id >= 101) AND (id < 201))) 33 | (2 rows) 34 | 35 | 36 | starting permutation: s1b s1_insert_150 s1r s1_show_partitions s2b s2_insert_300 s2c s2_show_partitions 37 | set_spawn_using_bgw 38 | ------------------- 39 | 40 | (1 row) 41 | 42 | step s1b: BEGIN; 43 | step s1_insert_150: INSERT INTO range_rel SELECT generate_series(1, 150); 44 | step s1r: ROLLBACK; 45 | step s1_show_partitions: SELECT pg_get_constraintdef(c.oid) FROM pg_inherits i LEFT JOIN pg_constraint c 46 | ON c.conrelid = i.inhrelid 47 | WHERE i.inhparent = 'range_rel'::regclass AND c.contype = 'c' 48 | ORDER BY c.oid; 49 | pg_get_constraintdef 50 | ------------------------------------ 51 | CHECK (((id >= 1) AND (id < 101))) 52 | CHECK (((id >= 101) AND (id < 201))) 53 | (2 rows) 54 | 55 | step s2b: BEGIN; 56 | step s2_insert_300: INSERT INTO range_rel SELECT generate_series(151, 300); 57 | step s2c: COMMIT; 58 | step s2_show_partitions: SELECT pg_get_constraintdef(c.oid) FROM pg_inherits i LEFT JOIN pg_constraint c 59 | ON c.conrelid = i.inhrelid 60 | WHERE i.inhparent = 'range_rel'::regclass AND c.contype = 'c' 61 | ORDER BY c.oid; 62 | pg_get_constraintdef 63 | ------------------------------------ 64 | CHECK (((id >= 1) AND (id < 101))) 65 | CHECK (((id >= 101) AND (id < 201))) 66 | CHECK (((id >= 201) AND (id < 301))) 67 | (3 rows) 68 | 69 | 70 | starting permutation: s1b s1_insert_300 s1r s1_show_partitions s2b s2_insert_150 s2c s2_show_partitions 71 | set_spawn_using_bgw 72 | ------------------- 73 | 74 | (1 row) 75 | 76 | step s1b: BEGIN; 77 | step s1_insert_300: INSERT INTO range_rel SELECT generate_series(151, 300); 78 | step s1r: ROLLBACK; 79 | step s1_show_partitions: SELECT pg_get_constraintdef(c.oid) FROM pg_inherits i LEFT JOIN pg_constraint c 80 | ON c.conrelid = i.inhrelid 81 | WHERE i.inhparent = 'range_rel'::regclass AND c.contype = 'c' 82 | ORDER BY c.oid; 83 | pg_get_constraintdef 84 | ------------------------------------ 85 | CHECK (((id >= 1) AND (id < 101))) 86 | CHECK (((id >= 101) AND (id < 201))) 87 | CHECK (((id >= 201) AND (id < 301))) 88 | (3 rows) 89 | 90 | step s2b: BEGIN; 91 | step s2_insert_150: INSERT INTO range_rel SELECT generate_series(1, 150); 92 | step s2c: COMMIT; 93 | step s2_show_partitions: SELECT pg_get_constraintdef(c.oid) FROM pg_inherits i LEFT JOIN pg_constraint c 94 | ON c.conrelid = i.inhrelid 95 | WHERE i.inhparent = 'range_rel'::regclass AND c.contype = 'c' 96 | ORDER BY c.oid; 97 | pg_get_constraintdef 98 | ------------------------------------ 99 | CHECK (((id >= 1) AND (id < 101))) 100 | CHECK (((id >= 101) AND (id < 201))) 101 | CHECK (((id >= 201) AND (id < 301))) 102 | (3 rows) 103 | 104 | 105 | starting permutation: s1b s1_insert_150 s2b s2_insert_300 s1r s2r s2_show_partitions 106 | set_spawn_using_bgw 107 | ------------------- 108 | 109 | (1 row) 110 | 111 | step s1b: BEGIN; 112 | step s1_insert_150: INSERT INTO range_rel SELECT generate_series(1, 150); 113 | step s2b: BEGIN; 114 | step s2_insert_300: INSERT INTO range_rel SELECT generate_series(151, 300); 115 | step s1r: ROLLBACK; 116 | step s2r: ROLLBACK; 117 | step s2_show_partitions: SELECT pg_get_constraintdef(c.oid) FROM pg_inherits i LEFT JOIN pg_constraint c 118 | ON c.conrelid = i.inhrelid 119 | WHERE i.inhparent = 'range_rel'::regclass AND c.contype = 'c' 120 | ORDER BY c.oid; 121 | pg_get_constraintdef 122 | ------------------------------------ 123 | CHECK (((id >= 1) AND (id < 101))) 124 | CHECK (((id >= 101) AND (id < 201))) 125 | CHECK (((id >= 201) AND (id < 301))) 126 | (3 rows) 127 | 128 | -------------------------------------------------------------------------------- /expected/pathman_CVE-2020-14350.out: -------------------------------------------------------------------------------- 1 | /* 2 | * Check fix for CVE-2020-14350. 3 | * See also 7eeb1d986 postgresql commit. 4 | */ 5 | SET client_min_messages = 'warning'; 6 | DROP FUNCTION IF EXISTS _partition_data_concurrent(oid,integer); 7 | DROP FUNCTION IF EXISTS create_single_range_partition(TEXT,ANYELEMENT,ANYELEMENT,TEXT); 8 | DROP TABLE IF EXISTS test1 CASCADE; 9 | DROP TABLE IF EXISTS test2 CASCADE; 10 | DROP ROLE IF EXISTS pathman_regress_hacker; 11 | SET client_min_messages = 'notice'; 12 | GRANT CREATE ON SCHEMA public TO PUBLIC; 13 | CREATE EXTENSION pg_pathman; 14 | CREATE ROLE pathman_regress_hacker LOGIN; 15 | -- Test 1 16 | RESET ROLE; 17 | ALTER ROLE pathman_regress_hacker NOSUPERUSER; 18 | SET ROLE pathman_regress_hacker; 19 | SHOW is_superuser; 20 | is_superuser 21 | -------------- 22 | off 23 | (1 row) 24 | 25 | CREATE FUNCTION _partition_data_concurrent(relation oid, p_limit INT, OUT p_total BIGINT) 26 | RETURNS bigint 27 | AS $$ 28 | BEGIN 29 | ALTER ROLE pathman_regress_hacker SUPERUSER; 30 | SELECT _partition_data_concurrent(relation, NULL::text, NULL::text, p_limit) INTO p_total; 31 | END 32 | $$ LANGUAGE plpgsql; 33 | CREATE TABLE test1(i INT4 NOT NULL); 34 | INSERT INTO test1 SELECT generate_series(1, 500); 35 | SELECT create_hash_partitions('test1', 'i', 5, false); 36 | create_hash_partitions 37 | ------------------------ 38 | 5 39 | (1 row) 40 | 41 | RESET ROLE; 42 | SELECT partition_table_concurrently('test1', 10, 1); 43 | NOTICE: worker started, you can stop it with the following command: select public.stop_concurrent_part_task('test1'); 44 | partition_table_concurrently 45 | ------------------------------ 46 | 47 | (1 row) 48 | 49 | SELECT pg_sleep(1); 50 | pg_sleep 51 | ---------- 52 | 53 | (1 row) 54 | 55 | -- Test result (must be 'off') 56 | SET ROLE pathman_regress_hacker; 57 | SHOW is_superuser; 58 | is_superuser 59 | -------------- 60 | off 61 | (1 row) 62 | 63 | -- Test 2 64 | RESET ROLE; 65 | ALTER ROLE pathman_regress_hacker NOSUPERUSER; 66 | SET ROLE pathman_regress_hacker; 67 | SHOW is_superuser; 68 | is_superuser 69 | -------------- 70 | off 71 | (1 row) 72 | 73 | CREATE FUNCTION create_single_range_partition(parent_relid TEXT, start_value ANYELEMENT, end_value ANYELEMENT, partition_name TEXT) 74 | RETURNS REGCLASS 75 | AS $$ 76 | BEGIN 77 | ALTER ROLE pathman_regress_hacker SUPERUSER; 78 | RETURN create_single_range_partition(parent_relid, start_value, end_value, partition_name, NULL::text); 79 | END 80 | $$ LANGUAGE plpgsql; 81 | RESET ROLE; 82 | CREATE TABLE test2(i INT4 NOT NULL); 83 | INSERT INTO test2 VALUES(0); 84 | SELECT create_range_partitions('test2', 'i', 0, 1); 85 | create_range_partitions 86 | ------------------------- 87 | 1 88 | (1 row) 89 | 90 | INSERT INTO test2 values(1); 91 | -- Test result (must be 'off') 92 | SET ROLE pathman_regress_hacker; 93 | SHOW is_superuser; 94 | is_superuser 95 | -------------- 96 | off 97 | (1 row) 98 | 99 | -- Cleanup 100 | RESET ROLE; 101 | DROP FUNCTION _partition_data_concurrent(oid,integer); 102 | DROP FUNCTION create_single_range_partition(TEXT,ANYELEMENT,ANYELEMENT,TEXT); 103 | DROP TABLE test1 CASCADE; 104 | NOTICE: drop cascades to 5 other objects 105 | DETAIL: drop cascades to table test1_0 106 | drop cascades to table test1_1 107 | drop cascades to table test1_2 108 | drop cascades to table test1_3 109 | drop cascades to table test1_4 110 | DROP TABLE test2 CASCADE; 111 | NOTICE: drop cascades to 3 other objects 112 | DETAIL: drop cascades to sequence test2_seq 113 | drop cascades to table test2_1 114 | drop cascades to table test2_2 115 | DROP ROLE pathman_regress_hacker; 116 | DROP EXTENSION pg_pathman; 117 | -------------------------------------------------------------------------------- /expected/pathman_check.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postgrespro/pg_pathman/73aafcfc7c088f891890b6896b7b4d62e36f6931/expected/pathman_check.out -------------------------------------------------------------------------------- /expected/pathman_foreign_keys.out: -------------------------------------------------------------------------------- 1 | \set VERBOSITY terse 2 | SET search_path = 'public'; 3 | CREATE EXTENSION pg_pathman; 4 | CREATE SCHEMA fkeys; 5 | /* Check primary keys generation */ 6 | CREATE TABLE fkeys.test_ref(comment TEXT UNIQUE); 7 | INSERT INTO fkeys.test_ref VALUES('test'); 8 | CREATE TABLE fkeys.test_fkey( 9 | id INT NOT NULL, 10 | comment TEXT, 11 | FOREIGN KEY (comment) REFERENCES fkeys.test_ref(comment)); 12 | INSERT INTO fkeys.test_fkey SELECT generate_series(1, 1000), 'test'; 13 | SELECT create_range_partitions('fkeys.test_fkey', 'id', 1, 100); 14 | create_range_partitions 15 | ------------------------- 16 | 10 17 | (1 row) 18 | 19 | INSERT INTO fkeys.test_fkey VALUES(1, 'wrong'); 20 | ERROR: insert or update on table "test_fkey_1" violates foreign key constraint "test_fkey_1_comment_fkey" 21 | INSERT INTO fkeys.test_fkey VALUES(1, 'test'); 22 | SELECT drop_partitions('fkeys.test_fkey'); 23 | NOTICE: 101 rows copied from fkeys.test_fkey_1 24 | NOTICE: 100 rows copied from fkeys.test_fkey_2 25 | NOTICE: 100 rows copied from fkeys.test_fkey_3 26 | NOTICE: 100 rows copied from fkeys.test_fkey_4 27 | NOTICE: 100 rows copied from fkeys.test_fkey_5 28 | NOTICE: 100 rows copied from fkeys.test_fkey_6 29 | NOTICE: 100 rows copied from fkeys.test_fkey_7 30 | NOTICE: 100 rows copied from fkeys.test_fkey_8 31 | NOTICE: 100 rows copied from fkeys.test_fkey_9 32 | NOTICE: 100 rows copied from fkeys.test_fkey_10 33 | drop_partitions 34 | ----------------- 35 | 10 36 | (1 row) 37 | 38 | SELECT create_hash_partitions('fkeys.test_fkey', 'id', 10); 39 | create_hash_partitions 40 | ------------------------ 41 | 10 42 | (1 row) 43 | 44 | INSERT INTO fkeys.test_fkey VALUES(1, 'wrong'); 45 | ERROR: insert or update on table "test_fkey_0" violates foreign key constraint "test_fkey_0_comment_fkey" 46 | INSERT INTO fkeys.test_fkey VALUES(1, 'test'); 47 | SELECT drop_partitions('fkeys.test_fkey'); 48 | NOTICE: 100 rows copied from fkeys.test_fkey_0 49 | NOTICE: 90 rows copied from fkeys.test_fkey_1 50 | NOTICE: 90 rows copied from fkeys.test_fkey_2 51 | NOTICE: 116 rows copied from fkeys.test_fkey_3 52 | NOTICE: 101 rows copied from fkeys.test_fkey_4 53 | NOTICE: 90 rows copied from fkeys.test_fkey_5 54 | NOTICE: 95 rows copied from fkeys.test_fkey_6 55 | NOTICE: 118 rows copied from fkeys.test_fkey_7 56 | NOTICE: 108 rows copied from fkeys.test_fkey_8 57 | NOTICE: 94 rows copied from fkeys.test_fkey_9 58 | drop_partitions 59 | ----------------- 60 | 10 61 | (1 row) 62 | 63 | /* Try to partition table that's being referenced */ 64 | CREATE TABLE fkeys.messages( 65 | id SERIAL PRIMARY KEY, 66 | msg TEXT); 67 | CREATE TABLE fkeys.replies( 68 | id SERIAL PRIMARY KEY, 69 | message_id INTEGER REFERENCES fkeys.messages(id), 70 | msg TEXT); 71 | INSERT INTO fkeys.messages SELECT g, md5(g::text) FROM generate_series(1, 10) as g; 72 | INSERT INTO fkeys.replies SELECT g, g, md5(g::text) FROM generate_series(1, 10) as g; 73 | SELECT create_range_partitions('fkeys.messages', 'id', 1, 100, 2); /* not ok */ 74 | WARNING: foreign key "replies_message_id_fkey" references table "fkeys.messages" 75 | ERROR: table "fkeys.messages" is referenced from other tables 76 | ALTER TABLE fkeys.replies DROP CONSTRAINT replies_message_id_fkey; 77 | SELECT create_range_partitions('fkeys.messages', 'id', 1, 100, 2); /* ok */ 78 | create_range_partitions 79 | ------------------------- 80 | 2 81 | (1 row) 82 | 83 | EXPLAIN (COSTS OFF) SELECT * FROM fkeys.messages; 84 | QUERY PLAN 85 | ------------------------------ 86 | Append 87 | -> Seq Scan on messages_1 88 | -> Seq Scan on messages_2 89 | (3 rows) 90 | 91 | DROP TABLE fkeys.messages, fkeys.replies CASCADE; 92 | NOTICE: drop cascades to 3 other objects 93 | DROP TABLE fkeys.test_fkey CASCADE; 94 | DROP TABLE fkeys.test_ref CASCADE; 95 | DROP SCHEMA fkeys; 96 | DROP EXTENSION pg_pathman CASCADE; 97 | -------------------------------------------------------------------------------- /expected/pathman_hashjoin.out: -------------------------------------------------------------------------------- 1 | /* 2 | * pathman_hashjoin_1.out and pathman_hashjoin_2.out seem to deal with pgpro's 3 | * different behaviour. 8edd0e794 (>= 12) Append nodes with single subplan 4 | * are eliminated, hence pathman_hashjoin_3.out 5 | * 6 | * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, 7 | * now it includes aliases for inherited tables. 8 | */ 9 | \set VERBOSITY terse 10 | SET search_path = 'public'; 11 | CREATE SCHEMA pathman; 12 | CREATE EXTENSION pg_pathman SCHEMA pathman; 13 | CREATE SCHEMA test; 14 | CREATE TABLE test.range_rel ( 15 | id SERIAL PRIMARY KEY, 16 | dt TIMESTAMP NOT NULL, 17 | txt TEXT); 18 | CREATE INDEX ON test.range_rel (dt); 19 | INSERT INTO test.range_rel (dt, txt) 20 | SELECT g, md5(g::TEXT) FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) as g; 21 | SELECT pathman.create_range_partitions('test.range_rel', 'DT', '2015-01-01'::DATE, '1 month'::INTERVAL); 22 | create_range_partitions 23 | ------------------------- 24 | 4 25 | (1 row) 26 | 27 | CREATE TABLE test.num_range_rel ( 28 | id SERIAL PRIMARY KEY, 29 | txt TEXT); 30 | SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); 31 | create_range_partitions 32 | ------------------------- 33 | 4 34 | (1 row) 35 | 36 | INSERT INTO test.num_range_rel 37 | SELECT g, md5(g::TEXT) FROM generate_series(1, 3000) as g; 38 | SET pg_pathman.enable_runtimeappend = OFF; 39 | SET pg_pathman.enable_runtimemergeappend = OFF; 40 | VACUUM; 41 | /* 42 | * Hash join 43 | */ 44 | SET enable_indexscan = ON; 45 | SET enable_seqscan = OFF; 46 | SET enable_nestloop = OFF; 47 | SET enable_hashjoin = ON; 48 | SET enable_mergejoin = OFF; 49 | EXPLAIN (COSTS OFF) 50 | SELECT * FROM test.range_rel j1 51 | JOIN test.range_rel j2 on j2.id = j1.id 52 | JOIN test.num_range_rel j3 on j3.id = j1.id 53 | WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; 54 | QUERY PLAN 55 | --------------------------------------------------------------------------------------- 56 | Sort 57 | Sort Key: j2.dt 58 | -> Hash Join 59 | Hash Cond: (j1.id = j2.id) 60 | -> Hash Join 61 | Hash Cond: (j3.id = j1.id) 62 | -> Append 63 | -> Index Scan using num_range_rel_1_pkey on num_range_rel_1 j3 64 | -> Index Scan using num_range_rel_2_pkey on num_range_rel_2 j3_1 65 | -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 j3_2 66 | -> Index Scan using num_range_rel_4_pkey on num_range_rel_4 j3_3 67 | -> Hash 68 | -> Append 69 | -> Index Scan using range_rel_1_pkey on range_rel_1 j1 70 | -> Index Scan using range_rel_2_pkey on range_rel_2 j1_1 71 | -> Hash 72 | -> Append 73 | -> Index Scan using range_rel_2_dt_idx on range_rel_2 j2 74 | -> Index Scan using range_rel_3_dt_idx on range_rel_3 j2_1 75 | -> Index Scan using range_rel_4_dt_idx on range_rel_4 j2_2 76 | (20 rows) 77 | 78 | DROP TABLE test.num_range_rel CASCADE; 79 | NOTICE: drop cascades to 5 other objects 80 | DROP TABLE test.range_rel CASCADE; 81 | NOTICE: drop cascades to 5 other objects 82 | DROP SCHEMA test; 83 | DROP EXTENSION pg_pathman CASCADE; 84 | DROP SCHEMA pathman; 85 | -------------------------------------------------------------------------------- /expected/pathman_hashjoin_1.out: -------------------------------------------------------------------------------- 1 | /* 2 | * pathman_hashjoin_1.out and pathman_hashjoin_2.out seem to deal with pgpro's 3 | * different behaviour. 8edd0e794 (>= 12) Append nodes with single subplan 4 | * are eliminated, hence pathman_hashjoin_3.out 5 | * 6 | * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, 7 | * now it includes aliases for inherited tables. 8 | */ 9 | \set VERBOSITY terse 10 | SET search_path = 'public'; 11 | CREATE SCHEMA pathman; 12 | CREATE EXTENSION pg_pathman SCHEMA pathman; 13 | CREATE SCHEMA test; 14 | CREATE TABLE test.range_rel ( 15 | id SERIAL PRIMARY KEY, 16 | dt TIMESTAMP NOT NULL, 17 | txt TEXT); 18 | CREATE INDEX ON test.range_rel (dt); 19 | INSERT INTO test.range_rel (dt, txt) 20 | SELECT g, md5(g::TEXT) FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) as g; 21 | SELECT pathman.create_range_partitions('test.range_rel', 'DT', '2015-01-01'::DATE, '1 month'::INTERVAL); 22 | create_range_partitions 23 | ------------------------- 24 | 4 25 | (1 row) 26 | 27 | CREATE TABLE test.num_range_rel ( 28 | id SERIAL PRIMARY KEY, 29 | txt TEXT); 30 | SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); 31 | create_range_partitions 32 | ------------------------- 33 | 4 34 | (1 row) 35 | 36 | INSERT INTO test.num_range_rel 37 | SELECT g, md5(g::TEXT) FROM generate_series(1, 3000) as g; 38 | SET pg_pathman.enable_runtimeappend = OFF; 39 | SET pg_pathman.enable_runtimemergeappend = OFF; 40 | VACUUM; 41 | /* 42 | * Hash join 43 | */ 44 | SET enable_indexscan = ON; 45 | SET enable_seqscan = OFF; 46 | SET enable_nestloop = OFF; 47 | SET enable_hashjoin = ON; 48 | SET enable_mergejoin = OFF; 49 | EXPLAIN (COSTS OFF) 50 | SELECT * FROM test.range_rel j1 51 | JOIN test.range_rel j2 on j2.id = j1.id 52 | JOIN test.num_range_rel j3 on j3.id = j1.id 53 | WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; 54 | QUERY PLAN 55 | ------------------------------------------------------------------------------------------- 56 | Sort 57 | Sort Key: j2.dt 58 | -> Hash Join 59 | Hash Cond: (j3.id = j2.id) 60 | -> Append 61 | -> Index Scan using num_range_rel_1_pkey on num_range_rel_1 j3 62 | -> Index Scan using num_range_rel_2_pkey on num_range_rel_2 j3_1 63 | -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 j3_2 64 | -> Index Scan using num_range_rel_4_pkey on num_range_rel_4 j3_3 65 | -> Hash 66 | -> Hash Join 67 | Hash Cond: (j2.id = j1.id) 68 | -> Append 69 | -> Index Scan using range_rel_2_dt_idx on range_rel_2 j2 70 | -> Index Scan using range_rel_3_dt_idx on range_rel_3 j2_1 71 | -> Index Scan using range_rel_4_dt_idx on range_rel_4 j2_2 72 | -> Hash 73 | -> Append 74 | -> Index Scan using range_rel_1_pkey on range_rel_1 j1 75 | -> Index Scan using range_rel_2_pkey on range_rel_2 j1_1 76 | (20 rows) 77 | 78 | DROP TABLE test.num_range_rel CASCADE; 79 | NOTICE: drop cascades to 5 other objects 80 | DROP TABLE test.range_rel CASCADE; 81 | NOTICE: drop cascades to 5 other objects 82 | DROP SCHEMA test; 83 | DROP EXTENSION pg_pathman CASCADE; 84 | DROP SCHEMA pathman; 85 | -------------------------------------------------------------------------------- /expected/pathman_hashjoin_2.out: -------------------------------------------------------------------------------- 1 | /* 2 | * pathman_hashjoin_1.out and pathman_hashjoin_2.out seem to deal with pgpro's 3 | * different behaviour. 8edd0e794 (>= 12) Append nodes with single subplan 4 | * are eliminated, hence pathman_hashjoin_3.out 5 | * 6 | * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, 7 | * now it includes aliases for inherited tables. 8 | */ 9 | \set VERBOSITY terse 10 | SET search_path = 'public'; 11 | CREATE SCHEMA pathman; 12 | CREATE EXTENSION pg_pathman SCHEMA pathman; 13 | CREATE SCHEMA test; 14 | CREATE TABLE test.range_rel ( 15 | id SERIAL PRIMARY KEY, 16 | dt TIMESTAMP NOT NULL, 17 | txt TEXT); 18 | CREATE INDEX ON test.range_rel (dt); 19 | INSERT INTO test.range_rel (dt, txt) 20 | SELECT g, md5(g::TEXT) FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) as g; 21 | SELECT pathman.create_range_partitions('test.range_rel', 'DT', '2015-01-01'::DATE, '1 month'::INTERVAL); 22 | create_range_partitions 23 | ------------------------- 24 | 4 25 | (1 row) 26 | 27 | CREATE TABLE test.num_range_rel ( 28 | id SERIAL PRIMARY KEY, 29 | txt TEXT); 30 | SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); 31 | create_range_partitions 32 | ------------------------- 33 | 4 34 | (1 row) 35 | 36 | INSERT INTO test.num_range_rel 37 | SELECT g, md5(g::TEXT) FROM generate_series(1, 3000) as g; 38 | SET pg_pathman.enable_runtimeappend = OFF; 39 | SET pg_pathman.enable_runtimemergeappend = OFF; 40 | VACUUM; 41 | /* 42 | * Hash join 43 | */ 44 | SET enable_indexscan = ON; 45 | SET enable_seqscan = OFF; 46 | SET enable_nestloop = OFF; 47 | SET enable_hashjoin = ON; 48 | SET enable_mergejoin = OFF; 49 | EXPLAIN (COSTS OFF) 50 | SELECT * FROM test.range_rel j1 51 | JOIN test.range_rel j2 on j2.id = j1.id 52 | JOIN test.num_range_rel j3 on j3.id = j1.id 53 | WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; 54 | QUERY PLAN 55 | --------------------------------------------------------------------------------- 56 | Sort 57 | Sort Key: j2.dt 58 | -> Hash Join 59 | Hash Cond: (j3.id = j2.id) 60 | -> Append 61 | -> Index Scan using num_range_rel_1_pkey on num_range_rel_1 j3 62 | -> Index Scan using num_range_rel_2_pkey on num_range_rel_2 j3_1 63 | -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 j3_2 64 | -> Index Scan using num_range_rel_4_pkey on num_range_rel_4 j3_3 65 | -> Hash 66 | -> Append 67 | -> Index Scan using range_rel_2_dt_idx on range_rel_2 j2 68 | Filter: (id IS NOT NULL) 69 | (13 rows) 70 | 71 | DROP TABLE test.num_range_rel CASCADE; 72 | NOTICE: drop cascades to 5 other objects 73 | DROP TABLE test.range_rel CASCADE; 74 | NOTICE: drop cascades to 5 other objects 75 | DROP SCHEMA test; 76 | DROP EXTENSION pg_pathman CASCADE; 77 | DROP SCHEMA pathman; 78 | -------------------------------------------------------------------------------- /expected/pathman_hashjoin_3.out: -------------------------------------------------------------------------------- 1 | /* 2 | * pathman_hashjoin_1.out and pathman_hashjoin_2.out seem to deal with pgpro's 3 | * different behaviour. 8edd0e794 (>= 12) Append nodes with single subplan 4 | * are eliminated, hence pathman_hashjoin_3.out 5 | * 6 | * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, 7 | * now it includes aliases for inherited tables. 8 | */ 9 | \set VERBOSITY terse 10 | SET search_path = 'public'; 11 | CREATE SCHEMA pathman; 12 | CREATE EXTENSION pg_pathman SCHEMA pathman; 13 | CREATE SCHEMA test; 14 | CREATE TABLE test.range_rel ( 15 | id SERIAL PRIMARY KEY, 16 | dt TIMESTAMP NOT NULL, 17 | txt TEXT); 18 | CREATE INDEX ON test.range_rel (dt); 19 | INSERT INTO test.range_rel (dt, txt) 20 | SELECT g, md5(g::TEXT) FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) as g; 21 | SELECT pathman.create_range_partitions('test.range_rel', 'DT', '2015-01-01'::DATE, '1 month'::INTERVAL); 22 | create_range_partitions 23 | ------------------------- 24 | 4 25 | (1 row) 26 | 27 | CREATE TABLE test.num_range_rel ( 28 | id SERIAL PRIMARY KEY, 29 | txt TEXT); 30 | SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); 31 | create_range_partitions 32 | ------------------------- 33 | 4 34 | (1 row) 35 | 36 | INSERT INTO test.num_range_rel 37 | SELECT g, md5(g::TEXT) FROM generate_series(1, 3000) as g; 38 | SET pg_pathman.enable_runtimeappend = OFF; 39 | SET pg_pathman.enable_runtimemergeappend = OFF; 40 | VACUUM; 41 | /* 42 | * Hash join 43 | */ 44 | SET enable_indexscan = ON; 45 | SET enable_seqscan = OFF; 46 | SET enable_nestloop = OFF; 47 | SET enable_hashjoin = ON; 48 | SET enable_mergejoin = OFF; 49 | EXPLAIN (COSTS OFF) 50 | SELECT * FROM test.range_rel j1 51 | JOIN test.range_rel j2 on j2.id = j1.id 52 | JOIN test.num_range_rel j3 on j3.id = j1.id 53 | WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; 54 | QUERY PLAN 55 | --------------------------------------------------------------------------------- 56 | Sort 57 | Sort Key: j2.dt 58 | -> Hash Join 59 | Hash Cond: (j3.id = j2.id) 60 | -> Append 61 | -> Index Scan using num_range_rel_1_pkey on num_range_rel_1 j3 62 | -> Index Scan using num_range_rel_2_pkey on num_range_rel_2 j3_1 63 | -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 j3_2 64 | -> Index Scan using num_range_rel_4_pkey on num_range_rel_4 j3_3 65 | -> Hash 66 | -> Index Scan using range_rel_2_dt_idx on range_rel_2 j2 67 | Filter: (id IS NOT NULL) 68 | (12 rows) 69 | 70 | DROP TABLE test.num_range_rel CASCADE; 71 | NOTICE: drop cascades to 5 other objects 72 | DROP TABLE test.range_rel CASCADE; 73 | NOTICE: drop cascades to 5 other objects 74 | DROP SCHEMA test; 75 | DROP EXTENSION pg_pathman CASCADE; 76 | DROP SCHEMA pathman; 77 | -------------------------------------------------------------------------------- /expected/pathman_hashjoin_4.out: -------------------------------------------------------------------------------- 1 | /* 2 | * pathman_hashjoin_1.out and pathman_hashjoin_2.out seem to deal with pgpro's 3 | * different behaviour. 8edd0e794 (>= 12) Append nodes with single subplan 4 | * are eliminated, hence pathman_hashjoin_3.out 5 | * 6 | * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, 7 | * now it includes aliases for inherited tables. 8 | */ 9 | \set VERBOSITY terse 10 | SET search_path = 'public'; 11 | CREATE SCHEMA pathman; 12 | CREATE EXTENSION pg_pathman SCHEMA pathman; 13 | CREATE SCHEMA test; 14 | CREATE TABLE test.range_rel ( 15 | id SERIAL PRIMARY KEY, 16 | dt TIMESTAMP NOT NULL, 17 | txt TEXT); 18 | CREATE INDEX ON test.range_rel (dt); 19 | INSERT INTO test.range_rel (dt, txt) 20 | SELECT g, md5(g::TEXT) FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) as g; 21 | SELECT pathman.create_range_partitions('test.range_rel', 'DT', '2015-01-01'::DATE, '1 month'::INTERVAL); 22 | create_range_partitions 23 | ------------------------- 24 | 4 25 | (1 row) 26 | 27 | CREATE TABLE test.num_range_rel ( 28 | id SERIAL PRIMARY KEY, 29 | txt TEXT); 30 | SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); 31 | create_range_partitions 32 | ------------------------- 33 | 4 34 | (1 row) 35 | 36 | INSERT INTO test.num_range_rel 37 | SELECT g, md5(g::TEXT) FROM generate_series(1, 3000) as g; 38 | SET pg_pathman.enable_runtimeappend = OFF; 39 | SET pg_pathman.enable_runtimemergeappend = OFF; 40 | VACUUM; 41 | /* 42 | * Hash join 43 | */ 44 | SET enable_indexscan = ON; 45 | SET enable_seqscan = OFF; 46 | SET enable_nestloop = OFF; 47 | SET enable_hashjoin = ON; 48 | SET enable_mergejoin = OFF; 49 | EXPLAIN (COSTS OFF) 50 | SELECT * FROM test.range_rel j1 51 | JOIN test.range_rel j2 on j2.id = j1.id 52 | JOIN test.num_range_rel j3 on j3.id = j1.id 53 | WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; 54 | QUERY PLAN 55 | --------------------------------------------------------------------------------------- 56 | Sort 57 | Sort Key: j2.dt 58 | -> Hash Join 59 | Hash Cond: (j1.id = j2.id) 60 | -> Hash Join 61 | Hash Cond: (j3.id = j1.id) 62 | -> Append 63 | -> Index Scan using num_range_rel_1_pkey on num_range_rel_1 j3_1 64 | -> Index Scan using num_range_rel_2_pkey on num_range_rel_2 j3_2 65 | -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 j3_3 66 | -> Index Scan using num_range_rel_4_pkey on num_range_rel_4 j3_4 67 | -> Hash 68 | -> Append 69 | -> Index Scan using range_rel_1_pkey on range_rel_1 j1_1 70 | -> Index Scan using range_rel_2_pkey on range_rel_2 j1_2 71 | -> Hash 72 | -> Append 73 | -> Index Scan using range_rel_2_dt_idx on range_rel_2 j2_1 74 | -> Index Scan using range_rel_3_dt_idx on range_rel_3 j2_2 75 | -> Index Scan using range_rel_4_dt_idx on range_rel_4 j2_3 76 | (20 rows) 77 | 78 | DROP TABLE test.num_range_rel CASCADE; 79 | NOTICE: drop cascades to 5 other objects 80 | DROP TABLE test.range_rel CASCADE; 81 | NOTICE: drop cascades to 5 other objects 82 | DROP SCHEMA test; 83 | DROP EXTENSION pg_pathman CASCADE; 84 | DROP SCHEMA pathman; 85 | -------------------------------------------------------------------------------- /expected/pathman_hashjoin_5.out: -------------------------------------------------------------------------------- 1 | /* 2 | * pathman_hashjoin_1.out and pathman_hashjoin_2.out seem to deal with pgpro's 3 | * different behaviour. 8edd0e794 (>= 12) Append nodes with single subplan 4 | * are eliminated, hence pathman_hashjoin_3.out 5 | * 6 | * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, 7 | * now it includes aliases for inherited tables. 8 | */ 9 | \set VERBOSITY terse 10 | SET search_path = 'public'; 11 | CREATE SCHEMA pathman; 12 | CREATE EXTENSION pg_pathman SCHEMA pathman; 13 | CREATE SCHEMA test; 14 | CREATE TABLE test.range_rel ( 15 | id SERIAL PRIMARY KEY, 16 | dt TIMESTAMP NOT NULL, 17 | txt TEXT); 18 | CREATE INDEX ON test.range_rel (dt); 19 | INSERT INTO test.range_rel (dt, txt) 20 | SELECT g, md5(g::TEXT) FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) as g; 21 | SELECT pathman.create_range_partitions('test.range_rel', 'DT', '2015-01-01'::DATE, '1 month'::INTERVAL); 22 | create_range_partitions 23 | ------------------------- 24 | 4 25 | (1 row) 26 | 27 | CREATE TABLE test.num_range_rel ( 28 | id SERIAL PRIMARY KEY, 29 | txt TEXT); 30 | SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); 31 | create_range_partitions 32 | ------------------------- 33 | 4 34 | (1 row) 35 | 36 | INSERT INTO test.num_range_rel 37 | SELECT g, md5(g::TEXT) FROM generate_series(1, 3000) as g; 38 | SET pg_pathman.enable_runtimeappend = OFF; 39 | SET pg_pathman.enable_runtimemergeappend = OFF; 40 | VACUUM; 41 | /* 42 | * Hash join 43 | */ 44 | SET enable_indexscan = ON; 45 | SET enable_seqscan = OFF; 46 | SET enable_nestloop = OFF; 47 | SET enable_hashjoin = ON; 48 | SET enable_mergejoin = OFF; 49 | EXPLAIN (COSTS OFF) 50 | SELECT * FROM test.range_rel j1 51 | JOIN test.range_rel j2 on j2.id = j1.id 52 | JOIN test.num_range_rel j3 on j3.id = j1.id 53 | WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; 54 | QUERY PLAN 55 | --------------------------------------------------------------------------------- 56 | Sort 57 | Sort Key: j2.dt 58 | -> Hash Join 59 | Hash Cond: (j3.id = j2.id) 60 | -> Append 61 | -> Index Scan using num_range_rel_1_pkey on num_range_rel_1 j3_1 62 | -> Index Scan using num_range_rel_2_pkey on num_range_rel_2 j3_2 63 | -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 j3_3 64 | -> Index Scan using num_range_rel_4_pkey on num_range_rel_4 j3_4 65 | -> Hash 66 | -> Index Scan using range_rel_2_dt_idx on range_rel_2 j2 67 | Filter: (id IS NOT NULL) 68 | (12 rows) 69 | 70 | DROP TABLE test.num_range_rel CASCADE; 71 | NOTICE: drop cascades to 5 other objects 72 | DROP TABLE test.range_rel CASCADE; 73 | NOTICE: drop cascades to 5 other objects 74 | DROP SCHEMA test; 75 | DROP EXTENSION pg_pathman CASCADE; 76 | DROP SCHEMA pathman; 77 | -------------------------------------------------------------------------------- /expected/pathman_hashjoin_6.out: -------------------------------------------------------------------------------- 1 | /* 2 | * pathman_hashjoin_1.out and pathman_hashjoin_2.out seem to deal with pgpro's 3 | * different behaviour. 8edd0e794 (>= 12) Append nodes with single subplan 4 | * are eliminated, hence pathman_hashjoin_3.out 5 | * 6 | * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, 7 | * now it includes aliases for inherited tables. 8 | */ 9 | \set VERBOSITY terse 10 | SET search_path = 'public'; 11 | CREATE SCHEMA pathman; 12 | CREATE EXTENSION pg_pathman SCHEMA pathman; 13 | CREATE SCHEMA test; 14 | CREATE TABLE test.range_rel ( 15 | id SERIAL PRIMARY KEY, 16 | dt TIMESTAMP NOT NULL, 17 | txt TEXT); 18 | CREATE INDEX ON test.range_rel (dt); 19 | INSERT INTO test.range_rel (dt, txt) 20 | SELECT g, md5(g::TEXT) FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) as g; 21 | SELECT pathman.create_range_partitions('test.range_rel', 'DT', '2015-01-01'::DATE, '1 month'::INTERVAL); 22 | create_range_partitions 23 | ------------------------- 24 | 4 25 | (1 row) 26 | 27 | CREATE TABLE test.num_range_rel ( 28 | id SERIAL PRIMARY KEY, 29 | txt TEXT); 30 | SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); 31 | create_range_partitions 32 | ------------------------- 33 | 4 34 | (1 row) 35 | 36 | INSERT INTO test.num_range_rel 37 | SELECT g, md5(g::TEXT) FROM generate_series(1, 3000) as g; 38 | SET pg_pathman.enable_runtimeappend = OFF; 39 | SET pg_pathman.enable_runtimemergeappend = OFF; 40 | VACUUM; 41 | /* 42 | * Hash join 43 | */ 44 | SET enable_indexscan = ON; 45 | SET enable_seqscan = OFF; 46 | SET enable_nestloop = OFF; 47 | SET enable_hashjoin = ON; 48 | SET enable_mergejoin = OFF; 49 | EXPLAIN (COSTS OFF) 50 | SELECT * FROM test.range_rel j1 51 | JOIN test.range_rel j2 on j2.id = j1.id 52 | JOIN test.num_range_rel j3 on j3.id = j1.id 53 | WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; 54 | QUERY PLAN 55 | --------------------------------------------------------------------------------- 56 | Sort 57 | Sort Key: j2.dt 58 | -> Hash Join 59 | Hash Cond: (j3.id = j2.id) 60 | -> Append 61 | -> Index Scan using num_range_rel_1_pkey on num_range_rel_1 j3_1 62 | -> Index Scan using num_range_rel_2_pkey on num_range_rel_2 j3_2 63 | -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 j3_3 64 | -> Index Scan using num_range_rel_4_pkey on num_range_rel_4 j3_4 65 | -> Hash 66 | -> Index Scan using range_rel_2_dt_idx on range_rel_2 j2 67 | (11 rows) 68 | 69 | DROP TABLE test.num_range_rel CASCADE; 70 | NOTICE: drop cascades to 5 other objects 71 | DROP TABLE test.range_rel CASCADE; 72 | NOTICE: drop cascades to 5 other objects 73 | DROP SCHEMA test; 74 | DROP EXTENSION pg_pathman CASCADE; 75 | DROP SCHEMA pathman; 76 | -------------------------------------------------------------------------------- /expected/pathman_mergejoin.out: -------------------------------------------------------------------------------- 1 | /* 2 | * pathman_mergejoin_1.out and pathman_mergejoin_2.out seem to deal with pgpro's 3 | * different behaviour. 8edd0e794 (>= 12) Append nodes with single subplan 4 | * are eliminated, hence pathman_mergejoin_3.out 5 | * 6 | * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, 7 | * now it includes aliases for inherited tables. 8 | * 9 | * --------------------------------------------- 10 | * NOTE: This test behaves differenly on PgPro 11 | * --------------------------------------------- 12 | */ 13 | \set VERBOSITY terse 14 | SET search_path = 'public'; 15 | CREATE SCHEMA pathman; 16 | CREATE EXTENSION pg_pathman SCHEMA pathman; 17 | CREATE SCHEMA test; 18 | CREATE TABLE test.range_rel ( 19 | id SERIAL PRIMARY KEY, 20 | dt TIMESTAMP NOT NULL, 21 | txt TEXT); 22 | CREATE INDEX ON test.range_rel (dt); 23 | INSERT INTO test.range_rel (dt, txt) 24 | SELECT g, md5(g::TEXT) FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) as g; 25 | SELECT pathman.create_range_partitions('test.range_rel', 'DT', '2015-01-01'::DATE, '1 month'::INTERVAL); 26 | create_range_partitions 27 | ------------------------- 28 | 4 29 | (1 row) 30 | 31 | CREATE TABLE test.num_range_rel ( 32 | id SERIAL PRIMARY KEY, 33 | txt TEXT); 34 | INSERT INTO test.num_range_rel SELECT g, md5(g::TEXT) FROM generate_series(1, 3000) as g; 35 | SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); 36 | create_range_partitions 37 | ------------------------- 38 | 4 39 | (1 row) 40 | 41 | /* 42 | * Merge join between 3 partitioned tables 43 | * 44 | * test case for the fix of sorting, merge append and index scan issues 45 | * details in commit 54dd0486fc55b2d25cf7d095f83dee6ff4adee06 46 | */ 47 | SET enable_hashjoin = OFF; 48 | SET enable_nestloop = OFF; 49 | SET enable_mergejoin = ON; 50 | SET enable_indexscan = ON; 51 | SET enable_seqscan = OFF; 52 | EXPLAIN (COSTS OFF) 53 | SELECT * FROM test.range_rel j1 54 | JOIN test.range_rel j2 on j2.id = j1.id 55 | JOIN test.num_range_rel j3 on j3.id = j1.id 56 | WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; 57 | QUERY PLAN 58 | ------------------------------------------------------------------------------------------- 59 | Sort 60 | Sort Key: j2.dt 61 | -> Merge Join 62 | Merge Cond: (j3.id = j2.id) 63 | -> Append 64 | -> Index Scan using num_range_rel_1_pkey on num_range_rel_1 j3 65 | -> Index Scan using num_range_rel_2_pkey on num_range_rel_2 j3_1 66 | -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 j3_2 67 | -> Index Scan using num_range_rel_4_pkey on num_range_rel_4 j3_3 68 | -> Materialize 69 | -> Merge Join 70 | Merge Cond: (j2.id = j1.id) 71 | -> Merge Append 72 | Sort Key: j2.id 73 | -> Index Scan using range_rel_2_pkey on range_rel_2 j2 74 | -> Index Scan using range_rel_3_pkey on range_rel_3 j2_1 75 | -> Index Scan using range_rel_4_pkey on range_rel_4 j2_2 76 | -> Materialize 77 | -> Merge Append 78 | Sort Key: j1.id 79 | -> Index Scan using range_rel_1_pkey on range_rel_1 j1 80 | -> Index Scan using range_rel_2_pkey on range_rel_2 j1_1 81 | (22 rows) 82 | 83 | SET enable_hashjoin = ON; 84 | SET enable_nestloop = ON; 85 | SET enable_seqscan = ON; 86 | DROP TABLE test.num_range_rel CASCADE; 87 | NOTICE: drop cascades to 5 other objects 88 | DROP TABLE test.range_rel CASCADE; 89 | NOTICE: drop cascades to 5 other objects 90 | DROP SCHEMA test; 91 | DROP EXTENSION pg_pathman; 92 | DROP SCHEMA pathman; 93 | -------------------------------------------------------------------------------- /expected/pathman_mergejoin_1.out: -------------------------------------------------------------------------------- 1 | /* 2 | * pathman_mergejoin_1.out and pathman_mergejoin_2.out seem to deal with pgpro's 3 | * different behaviour. 8edd0e794 (>= 12) Append nodes with single subplan 4 | * are eliminated, hence pathman_mergejoin_3.out 5 | * 6 | * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, 7 | * now it includes aliases for inherited tables. 8 | * 9 | * --------------------------------------------- 10 | * NOTE: This test behaves differenly on PgPro 11 | * --------------------------------------------- 12 | */ 13 | \set VERBOSITY terse 14 | SET search_path = 'public'; 15 | CREATE SCHEMA pathman; 16 | CREATE EXTENSION pg_pathman SCHEMA pathman; 17 | CREATE SCHEMA test; 18 | CREATE TABLE test.range_rel ( 19 | id SERIAL PRIMARY KEY, 20 | dt TIMESTAMP NOT NULL, 21 | txt TEXT); 22 | CREATE INDEX ON test.range_rel (dt); 23 | INSERT INTO test.range_rel (dt, txt) 24 | SELECT g, md5(g::TEXT) FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) as g; 25 | SELECT pathman.create_range_partitions('test.range_rel', 'DT', '2015-01-01'::DATE, '1 month'::INTERVAL); 26 | create_range_partitions 27 | ------------------------- 28 | 4 29 | (1 row) 30 | 31 | CREATE TABLE test.num_range_rel ( 32 | id SERIAL PRIMARY KEY, 33 | txt TEXT); 34 | INSERT INTO test.num_range_rel SELECT g, md5(g::TEXT) FROM generate_series(1, 3000) as g; 35 | SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); 36 | create_range_partitions 37 | ------------------------- 38 | 4 39 | (1 row) 40 | 41 | /* 42 | * Merge join between 3 partitioned tables 43 | * 44 | * test case for the fix of sorting, merge append and index scan issues 45 | * details in commit 54dd0486fc55b2d25cf7d095f83dee6ff4adee06 46 | */ 47 | SET enable_hashjoin = OFF; 48 | SET enable_nestloop = OFF; 49 | SET enable_mergejoin = ON; 50 | SET enable_indexscan = ON; 51 | SET enable_seqscan = OFF; 52 | EXPLAIN (COSTS OFF) 53 | SELECT * FROM test.range_rel j1 54 | JOIN test.range_rel j2 on j2.id = j1.id 55 | JOIN test.num_range_rel j3 on j3.id = j1.id 56 | WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; 57 | QUERY PLAN 58 | --------------------------------------------------------------------------------- 59 | Sort 60 | Sort Key: j2.dt 61 | -> Merge Join 62 | Merge Cond: (j2.id = j3.id) 63 | -> Merge Join 64 | Merge Cond: (j1.id = j2.id) 65 | -> Merge Append 66 | Sort Key: j1.id 67 | -> Index Scan using range_rel_1_pkey on range_rel_1 j1 68 | -> Index Scan using range_rel_2_pkey on range_rel_2 j1_1 69 | -> Merge Append 70 | Sort Key: j2.id 71 | -> Index Scan using range_rel_2_pkey on range_rel_2 j2 72 | -> Index Scan using range_rel_3_pkey on range_rel_3 j2_1 73 | -> Index Scan using range_rel_4_pkey on range_rel_4 j2_2 74 | -> Append 75 | -> Index Scan using num_range_rel_1_pkey on num_range_rel_1 j3 76 | -> Index Scan using num_range_rel_2_pkey on num_range_rel_2 j3_1 77 | -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 j3_2 78 | -> Index Scan using num_range_rel_4_pkey on num_range_rel_4 j3_3 79 | (20 rows) 80 | 81 | SET enable_hashjoin = ON; 82 | SET enable_nestloop = ON; 83 | SET enable_seqscan = ON; 84 | DROP TABLE test.num_range_rel CASCADE; 85 | NOTICE: drop cascades to 5 other objects 86 | DROP TABLE test.range_rel CASCADE; 87 | NOTICE: drop cascades to 5 other objects 88 | DROP SCHEMA test; 89 | DROP EXTENSION pg_pathman; 90 | DROP SCHEMA pathman; 91 | -------------------------------------------------------------------------------- /expected/pathman_mergejoin_2.out: -------------------------------------------------------------------------------- 1 | /* 2 | * pathman_mergejoin_1.out and pathman_mergejoin_2.out seem to deal with pgpro's 3 | * different behaviour. 8edd0e794 (>= 12) Append nodes with single subplan 4 | * are eliminated, hence pathman_mergejoin_3.out 5 | * 6 | * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, 7 | * now it includes aliases for inherited tables. 8 | * 9 | * --------------------------------------------- 10 | * NOTE: This test behaves differenly on PgPro 11 | * --------------------------------------------- 12 | */ 13 | \set VERBOSITY terse 14 | SET search_path = 'public'; 15 | CREATE SCHEMA pathman; 16 | CREATE EXTENSION pg_pathman SCHEMA pathman; 17 | CREATE SCHEMA test; 18 | CREATE TABLE test.range_rel ( 19 | id SERIAL PRIMARY KEY, 20 | dt TIMESTAMP NOT NULL, 21 | txt TEXT); 22 | CREATE INDEX ON test.range_rel (dt); 23 | INSERT INTO test.range_rel (dt, txt) 24 | SELECT g, md5(g::TEXT) FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) as g; 25 | SELECT pathman.create_range_partitions('test.range_rel', 'DT', '2015-01-01'::DATE, '1 month'::INTERVAL); 26 | create_range_partitions 27 | ------------------------- 28 | 4 29 | (1 row) 30 | 31 | CREATE TABLE test.num_range_rel ( 32 | id SERIAL PRIMARY KEY, 33 | txt TEXT); 34 | INSERT INTO test.num_range_rel SELECT g, md5(g::TEXT) FROM generate_series(1, 3000) as g; 35 | SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); 36 | create_range_partitions 37 | ------------------------- 38 | 4 39 | (1 row) 40 | 41 | /* 42 | * Merge join between 3 partitioned tables 43 | * 44 | * test case for the fix of sorting, merge append and index scan issues 45 | * details in commit 54dd0486fc55b2d25cf7d095f83dee6ff4adee06 46 | */ 47 | SET enable_hashjoin = OFF; 48 | SET enable_nestloop = OFF; 49 | SET enable_mergejoin = ON; 50 | SET enable_indexscan = ON; 51 | SET enable_seqscan = OFF; 52 | EXPLAIN (COSTS OFF) 53 | SELECT * FROM test.range_rel j1 54 | JOIN test.range_rel j2 on j2.id = j1.id 55 | JOIN test.num_range_rel j3 on j3.id = j1.id 56 | WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; 57 | QUERY PLAN 58 | --------------------------------------------------------------------------------- 59 | Sort 60 | Sort Key: j2.dt 61 | -> Merge Join 62 | Merge Cond: (j2.id = j3.id) 63 | -> Merge Append 64 | Sort Key: j2.id 65 | -> Index Scan using range_rel_2_pkey on range_rel_2 j2 66 | Index Cond: (id IS NOT NULL) 67 | -> Append 68 | -> Index Scan using num_range_rel_1_pkey on num_range_rel_1 j3 69 | -> Index Scan using num_range_rel_2_pkey on num_range_rel_2 j3_1 70 | -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 j3_2 71 | -> Index Scan using num_range_rel_4_pkey on num_range_rel_4 j3_3 72 | (13 rows) 73 | 74 | SET enable_hashjoin = ON; 75 | SET enable_nestloop = ON; 76 | SET enable_seqscan = ON; 77 | DROP TABLE test.num_range_rel CASCADE; 78 | NOTICE: drop cascades to 5 other objects 79 | DROP TABLE test.range_rel CASCADE; 80 | NOTICE: drop cascades to 5 other objects 81 | DROP SCHEMA test; 82 | DROP EXTENSION pg_pathman; 83 | DROP SCHEMA pathman; 84 | -------------------------------------------------------------------------------- /expected/pathman_mergejoin_3.out: -------------------------------------------------------------------------------- 1 | /* 2 | * pathman_mergejoin_1.out and pathman_mergejoin_2.out seem to deal with pgpro's 3 | * different behaviour. 8edd0e794 (>= 12) Append nodes with single subplan 4 | * are eliminated, hence pathman_mergejoin_3.out 5 | * 6 | * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, 7 | * now it includes aliases for inherited tables. 8 | * 9 | * --------------------------------------------- 10 | * NOTE: This test behaves differenly on PgPro 11 | * --------------------------------------------- 12 | */ 13 | \set VERBOSITY terse 14 | SET search_path = 'public'; 15 | CREATE SCHEMA pathman; 16 | CREATE EXTENSION pg_pathman SCHEMA pathman; 17 | CREATE SCHEMA test; 18 | CREATE TABLE test.range_rel ( 19 | id SERIAL PRIMARY KEY, 20 | dt TIMESTAMP NOT NULL, 21 | txt TEXT); 22 | CREATE INDEX ON test.range_rel (dt); 23 | INSERT INTO test.range_rel (dt, txt) 24 | SELECT g, md5(g::TEXT) FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) as g; 25 | SELECT pathman.create_range_partitions('test.range_rel', 'DT', '2015-01-01'::DATE, '1 month'::INTERVAL); 26 | create_range_partitions 27 | ------------------------- 28 | 4 29 | (1 row) 30 | 31 | CREATE TABLE test.num_range_rel ( 32 | id SERIAL PRIMARY KEY, 33 | txt TEXT); 34 | INSERT INTO test.num_range_rel SELECT g, md5(g::TEXT) FROM generate_series(1, 3000) as g; 35 | SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); 36 | create_range_partitions 37 | ------------------------- 38 | 4 39 | (1 row) 40 | 41 | /* 42 | * Merge join between 3 partitioned tables 43 | * 44 | * test case for the fix of sorting, merge append and index scan issues 45 | * details in commit 54dd0486fc55b2d25cf7d095f83dee6ff4adee06 46 | */ 47 | SET enable_hashjoin = OFF; 48 | SET enable_nestloop = OFF; 49 | SET enable_mergejoin = ON; 50 | SET enable_indexscan = ON; 51 | SET enable_seqscan = OFF; 52 | EXPLAIN (COSTS OFF) 53 | SELECT * FROM test.range_rel j1 54 | JOIN test.range_rel j2 on j2.id = j1.id 55 | JOIN test.num_range_rel j3 on j3.id = j1.id 56 | WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; 57 | QUERY PLAN 58 | --------------------------------------------------------------------------------- 59 | Sort 60 | Sort Key: j2.dt 61 | -> Merge Join 62 | Merge Cond: (j2.id = j3.id) 63 | -> Index Scan using range_rel_2_pkey on range_rel_2 j2 64 | Index Cond: (id IS NOT NULL) 65 | -> Append 66 | -> Index Scan using num_range_rel_1_pkey on num_range_rel_1 j3 67 | -> Index Scan using num_range_rel_2_pkey on num_range_rel_2 j3_1 68 | -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 j3_2 69 | -> Index Scan using num_range_rel_4_pkey on num_range_rel_4 j3_3 70 | (11 rows) 71 | 72 | SET enable_hashjoin = ON; 73 | SET enable_nestloop = ON; 74 | SET enable_seqscan = ON; 75 | DROP TABLE test.num_range_rel CASCADE; 76 | NOTICE: drop cascades to 5 other objects 77 | DROP TABLE test.range_rel CASCADE; 78 | NOTICE: drop cascades to 5 other objects 79 | DROP SCHEMA test; 80 | DROP EXTENSION pg_pathman; 81 | DROP SCHEMA pathman; 82 | -------------------------------------------------------------------------------- /expected/pathman_mergejoin_4.out: -------------------------------------------------------------------------------- 1 | /* 2 | * pathman_mergejoin_1.out and pathman_mergejoin_2.out seem to deal with pgpro's 3 | * different behaviour. 8edd0e794 (>= 12) Append nodes with single subplan 4 | * are eliminated, hence pathman_mergejoin_3.out 5 | * 6 | * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, 7 | * now it includes aliases for inherited tables. 8 | * 9 | * --------------------------------------------- 10 | * NOTE: This test behaves differenly on PgPro 11 | * --------------------------------------------- 12 | */ 13 | \set VERBOSITY terse 14 | SET search_path = 'public'; 15 | CREATE SCHEMA pathman; 16 | CREATE EXTENSION pg_pathman SCHEMA pathman; 17 | CREATE SCHEMA test; 18 | CREATE TABLE test.range_rel ( 19 | id SERIAL PRIMARY KEY, 20 | dt TIMESTAMP NOT NULL, 21 | txt TEXT); 22 | CREATE INDEX ON test.range_rel (dt); 23 | INSERT INTO test.range_rel (dt, txt) 24 | SELECT g, md5(g::TEXT) FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) as g; 25 | SELECT pathman.create_range_partitions('test.range_rel', 'DT', '2015-01-01'::DATE, '1 month'::INTERVAL); 26 | create_range_partitions 27 | ------------------------- 28 | 4 29 | (1 row) 30 | 31 | CREATE TABLE test.num_range_rel ( 32 | id SERIAL PRIMARY KEY, 33 | txt TEXT); 34 | INSERT INTO test.num_range_rel SELECT g, md5(g::TEXT) FROM generate_series(1, 3000) as g; 35 | SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); 36 | create_range_partitions 37 | ------------------------- 38 | 4 39 | (1 row) 40 | 41 | /* 42 | * Merge join between 3 partitioned tables 43 | * 44 | * test case for the fix of sorting, merge append and index scan issues 45 | * details in commit 54dd0486fc55b2d25cf7d095f83dee6ff4adee06 46 | */ 47 | SET enable_hashjoin = OFF; 48 | SET enable_nestloop = OFF; 49 | SET enable_mergejoin = ON; 50 | SET enable_indexscan = ON; 51 | SET enable_seqscan = OFF; 52 | EXPLAIN (COSTS OFF) 53 | SELECT * FROM test.range_rel j1 54 | JOIN test.range_rel j2 on j2.id = j1.id 55 | JOIN test.num_range_rel j3 on j3.id = j1.id 56 | WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; 57 | QUERY PLAN 58 | --------------------------------------------------------------------------------- 59 | Sort 60 | Sort Key: j2.dt 61 | -> Merge Join 62 | Merge Cond: (j2.id = j3.id) 63 | -> Merge Join 64 | Merge Cond: (j1.id = j2.id) 65 | -> Merge Append 66 | Sort Key: j1.id 67 | -> Index Scan using range_rel_1_pkey on range_rel_1 j1_1 68 | -> Index Scan using range_rel_2_pkey on range_rel_2 j1_2 69 | -> Merge Append 70 | Sort Key: j2.id 71 | -> Index Scan using range_rel_2_pkey on range_rel_2 j2_1 72 | -> Index Scan using range_rel_3_pkey on range_rel_3 j2_2 73 | -> Index Scan using range_rel_4_pkey on range_rel_4 j2_3 74 | -> Append 75 | -> Index Scan using num_range_rel_1_pkey on num_range_rel_1 j3_1 76 | -> Index Scan using num_range_rel_2_pkey on num_range_rel_2 j3_2 77 | -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 j3_3 78 | -> Index Scan using num_range_rel_4_pkey on num_range_rel_4 j3_4 79 | (20 rows) 80 | 81 | SET enable_hashjoin = ON; 82 | SET enable_nestloop = ON; 83 | SET enable_seqscan = ON; 84 | DROP TABLE test.num_range_rel CASCADE; 85 | NOTICE: drop cascades to 5 other objects 86 | DROP TABLE test.range_rel CASCADE; 87 | NOTICE: drop cascades to 5 other objects 88 | DROP SCHEMA test; 89 | DROP EXTENSION pg_pathman; 90 | DROP SCHEMA pathman; 91 | -------------------------------------------------------------------------------- /expected/pathman_mergejoin_5.out: -------------------------------------------------------------------------------- 1 | /* 2 | * pathman_mergejoin_1.out and pathman_mergejoin_2.out seem to deal with pgpro's 3 | * different behaviour. 8edd0e794 (>= 12) Append nodes with single subplan 4 | * are eliminated, hence pathman_mergejoin_3.out 5 | * 6 | * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, 7 | * now it includes aliases for inherited tables. 8 | * 9 | * --------------------------------------------- 10 | * NOTE: This test behaves differenly on PgPro 11 | * --------------------------------------------- 12 | */ 13 | \set VERBOSITY terse 14 | SET search_path = 'public'; 15 | CREATE SCHEMA pathman; 16 | CREATE EXTENSION pg_pathman SCHEMA pathman; 17 | CREATE SCHEMA test; 18 | CREATE TABLE test.range_rel ( 19 | id SERIAL PRIMARY KEY, 20 | dt TIMESTAMP NOT NULL, 21 | txt TEXT); 22 | CREATE INDEX ON test.range_rel (dt); 23 | INSERT INTO test.range_rel (dt, txt) 24 | SELECT g, md5(g::TEXT) FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) as g; 25 | SELECT pathman.create_range_partitions('test.range_rel', 'DT', '2015-01-01'::DATE, '1 month'::INTERVAL); 26 | create_range_partitions 27 | ------------------------- 28 | 4 29 | (1 row) 30 | 31 | CREATE TABLE test.num_range_rel ( 32 | id SERIAL PRIMARY KEY, 33 | txt TEXT); 34 | INSERT INTO test.num_range_rel SELECT g, md5(g::TEXT) FROM generate_series(1, 3000) as g; 35 | SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); 36 | create_range_partitions 37 | ------------------------- 38 | 4 39 | (1 row) 40 | 41 | /* 42 | * Merge join between 3 partitioned tables 43 | * 44 | * test case for the fix of sorting, merge append and index scan issues 45 | * details in commit 54dd0486fc55b2d25cf7d095f83dee6ff4adee06 46 | */ 47 | SET enable_hashjoin = OFF; 48 | SET enable_nestloop = OFF; 49 | SET enable_mergejoin = ON; 50 | SET enable_indexscan = ON; 51 | SET enable_seqscan = OFF; 52 | EXPLAIN (COSTS OFF) 53 | SELECT * FROM test.range_rel j1 54 | JOIN test.range_rel j2 on j2.id = j1.id 55 | JOIN test.num_range_rel j3 on j3.id = j1.id 56 | WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; 57 | QUERY PLAN 58 | --------------------------------------------------------------------------------- 59 | Sort 60 | Sort Key: j2.dt 61 | -> Merge Join 62 | Merge Cond: (j2.id = j3.id) 63 | -> Index Scan using range_rel_2_pkey on range_rel_2 j2 64 | Index Cond: (id IS NOT NULL) 65 | -> Append 66 | -> Index Scan using num_range_rel_1_pkey on num_range_rel_1 j3_1 67 | -> Index Scan using num_range_rel_2_pkey on num_range_rel_2 j3_2 68 | -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 j3_3 69 | -> Index Scan using num_range_rel_4_pkey on num_range_rel_4 j3_4 70 | (11 rows) 71 | 72 | SET enable_hashjoin = ON; 73 | SET enable_nestloop = ON; 74 | SET enable_seqscan = ON; 75 | DROP TABLE test.num_range_rel CASCADE; 76 | NOTICE: drop cascades to 5 other objects 77 | DROP TABLE test.range_rel CASCADE; 78 | NOTICE: drop cascades to 5 other objects 79 | DROP SCHEMA test; 80 | DROP EXTENSION pg_pathman; 81 | DROP SCHEMA pathman; 82 | -------------------------------------------------------------------------------- /expected/pathman_mergejoin_6.out: -------------------------------------------------------------------------------- 1 | /* 2 | * pathman_mergejoin_1.out and pathman_mergejoin_2.out seem to deal with pgpro's 3 | * different behaviour. 8edd0e794 (>= 12) Append nodes with single subplan 4 | * are eliminated, hence pathman_mergejoin_3.out 5 | * 6 | * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, 7 | * now it includes aliases for inherited tables. 8 | * 9 | * --------------------------------------------- 10 | * NOTE: This test behaves differenly on PgPro 11 | * --------------------------------------------- 12 | */ 13 | \set VERBOSITY terse 14 | SET search_path = 'public'; 15 | CREATE SCHEMA pathman; 16 | CREATE EXTENSION pg_pathman SCHEMA pathman; 17 | CREATE SCHEMA test; 18 | CREATE TABLE test.range_rel ( 19 | id SERIAL PRIMARY KEY, 20 | dt TIMESTAMP NOT NULL, 21 | txt TEXT); 22 | CREATE INDEX ON test.range_rel (dt); 23 | INSERT INTO test.range_rel (dt, txt) 24 | SELECT g, md5(g::TEXT) FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) as g; 25 | SELECT pathman.create_range_partitions('test.range_rel', 'DT', '2015-01-01'::DATE, '1 month'::INTERVAL); 26 | create_range_partitions 27 | ------------------------- 28 | 4 29 | (1 row) 30 | 31 | CREATE TABLE test.num_range_rel ( 32 | id SERIAL PRIMARY KEY, 33 | txt TEXT); 34 | INSERT INTO test.num_range_rel SELECT g, md5(g::TEXT) FROM generate_series(1, 3000) as g; 35 | SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); 36 | create_range_partitions 37 | ------------------------- 38 | 4 39 | (1 row) 40 | 41 | /* 42 | * Merge join between 3 partitioned tables 43 | * 44 | * test case for the fix of sorting, merge append and index scan issues 45 | * details in commit 54dd0486fc55b2d25cf7d095f83dee6ff4adee06 46 | */ 47 | SET enable_hashjoin = OFF; 48 | SET enable_nestloop = OFF; 49 | SET enable_mergejoin = ON; 50 | SET enable_indexscan = ON; 51 | SET enable_seqscan = OFF; 52 | EXPLAIN (COSTS OFF) 53 | SELECT * FROM test.range_rel j1 54 | JOIN test.range_rel j2 on j2.id = j1.id 55 | JOIN test.num_range_rel j3 on j3.id = j1.id 56 | WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; 57 | QUERY PLAN 58 | --------------------------------------------------------------------------------- 59 | Sort 60 | Sort Key: j2.dt 61 | -> Merge Join 62 | Merge Cond: (j2.id = j3.id) 63 | -> Index Scan using range_rel_2_pkey on range_rel_2 j2 64 | -> Append 65 | -> Index Scan using num_range_rel_1_pkey on num_range_rel_1 j3_1 66 | -> Index Scan using num_range_rel_2_pkey on num_range_rel_2 j3_2 67 | -> Index Scan using num_range_rel_3_pkey on num_range_rel_3 j3_3 68 | -> Index Scan using num_range_rel_4_pkey on num_range_rel_4 j3_4 69 | (10 rows) 70 | 71 | SET enable_hashjoin = ON; 72 | SET enable_nestloop = ON; 73 | SET enable_seqscan = ON; 74 | DROP TABLE test.num_range_rel CASCADE; 75 | NOTICE: drop cascades to 5 other objects 76 | DROP TABLE test.range_rel CASCADE; 77 | NOTICE: drop cascades to 5 other objects 78 | DROP SCHEMA test; 79 | DROP EXTENSION pg_pathman; 80 | DROP SCHEMA pathman; 81 | -------------------------------------------------------------------------------- /expected/pathman_param_upd_del.out: -------------------------------------------------------------------------------- 1 | \set VERBOSITY terse 2 | SET search_path = 'public'; 3 | CREATE EXTENSION pg_pathman; 4 | CREATE SCHEMA param_upd_del; 5 | CREATE TABLE param_upd_del.test(key INT4 NOT NULL, val INT4); 6 | SELECT create_hash_partitions('param_upd_del.test', 'key', 10); 7 | create_hash_partitions 8 | ------------------------ 9 | 10 10 | (1 row) 11 | 12 | INSERT INTO param_upd_del.test SELECT i, i FROM generate_series(1, 1000) i; 13 | ANALYZE; 14 | PREPARE upd(INT4) AS UPDATE param_upd_del.test SET val = val + 1 WHERE key = $1; 15 | EXPLAIN (COSTS OFF) EXECUTE upd(10); 16 | QUERY PLAN 17 | ---------------------------- 18 | Update on test_3 19 | -> Seq Scan on test_3 20 | Filter: (key = 10) 21 | (3 rows) 22 | 23 | EXPLAIN (COSTS OFF) EXECUTE upd(10); 24 | QUERY PLAN 25 | ---------------------------- 26 | Update on test_3 27 | -> Seq Scan on test_3 28 | Filter: (key = 10) 29 | (3 rows) 30 | 31 | EXPLAIN (COSTS OFF) EXECUTE upd(10); 32 | QUERY PLAN 33 | ---------------------------- 34 | Update on test_3 35 | -> Seq Scan on test_3 36 | Filter: (key = 10) 37 | (3 rows) 38 | 39 | EXPLAIN (COSTS OFF) EXECUTE upd(10); 40 | QUERY PLAN 41 | ---------------------------- 42 | Update on test_3 43 | -> Seq Scan on test_3 44 | Filter: (key = 10) 45 | (3 rows) 46 | 47 | EXPLAIN (COSTS OFF) EXECUTE upd(10); 48 | QUERY PLAN 49 | ---------------------------- 50 | Update on test_3 51 | -> Seq Scan on test_3 52 | Filter: (key = 10) 53 | (3 rows) 54 | 55 | EXPLAIN (COSTS OFF) EXECUTE upd(10); 56 | QUERY PLAN 57 | ---------------------------- 58 | Update on test_3 59 | -> Seq Scan on test_3 60 | Filter: (key = 10) 61 | (3 rows) 62 | 63 | EXPLAIN (COSTS OFF) EXECUTE upd(11); 64 | QUERY PLAN 65 | ---------------------------- 66 | Update on test_9 67 | -> Seq Scan on test_9 68 | Filter: (key = 11) 69 | (3 rows) 70 | 71 | DEALLOCATE upd; 72 | PREPARE upd(INT4) AS UPDATE param_upd_del.test SET val = val + 1 WHERE key = ($1 + 3) * 2; 73 | EXPLAIN (COSTS OFF) EXECUTE upd(5); 74 | QUERY PLAN 75 | ---------------------------- 76 | Update on test_7 77 | -> Seq Scan on test_7 78 | Filter: (key = 16) 79 | (3 rows) 80 | 81 | EXPLAIN (COSTS OFF) EXECUTE upd(5); 82 | QUERY PLAN 83 | ---------------------------- 84 | Update on test_7 85 | -> Seq Scan on test_7 86 | Filter: (key = 16) 87 | (3 rows) 88 | 89 | EXPLAIN (COSTS OFF) EXECUTE upd(5); 90 | QUERY PLAN 91 | ---------------------------- 92 | Update on test_7 93 | -> Seq Scan on test_7 94 | Filter: (key = 16) 95 | (3 rows) 96 | 97 | EXPLAIN (COSTS OFF) EXECUTE upd(5); 98 | QUERY PLAN 99 | ---------------------------- 100 | Update on test_7 101 | -> Seq Scan on test_7 102 | Filter: (key = 16) 103 | (3 rows) 104 | 105 | EXPLAIN (COSTS OFF) EXECUTE upd(5); 106 | QUERY PLAN 107 | ---------------------------- 108 | Update on test_7 109 | -> Seq Scan on test_7 110 | Filter: (key = 16) 111 | (3 rows) 112 | 113 | EXPLAIN (COSTS OFF) EXECUTE upd(5); 114 | QUERY PLAN 115 | ---------------------------- 116 | Update on test_7 117 | -> Seq Scan on test_7 118 | Filter: (key = 16) 119 | (3 rows) 120 | 121 | EXPLAIN (COSTS OFF) EXECUTE upd(6); 122 | QUERY PLAN 123 | ---------------------------- 124 | Update on test_3 125 | -> Seq Scan on test_3 126 | Filter: (key = 18) 127 | (3 rows) 128 | 129 | DEALLOCATE upd; 130 | PREPARE del(INT4) AS DELETE FROM param_upd_del.test WHERE key = $1; 131 | EXPLAIN (COSTS OFF) EXECUTE del(10); 132 | QUERY PLAN 133 | ---------------------------- 134 | Delete on test_3 135 | -> Seq Scan on test_3 136 | Filter: (key = 10) 137 | (3 rows) 138 | 139 | EXPLAIN (COSTS OFF) EXECUTE del(10); 140 | QUERY PLAN 141 | ---------------------------- 142 | Delete on test_3 143 | -> Seq Scan on test_3 144 | Filter: (key = 10) 145 | (3 rows) 146 | 147 | EXPLAIN (COSTS OFF) EXECUTE del(10); 148 | QUERY PLAN 149 | ---------------------------- 150 | Delete on test_3 151 | -> Seq Scan on test_3 152 | Filter: (key = 10) 153 | (3 rows) 154 | 155 | EXPLAIN (COSTS OFF) EXECUTE del(10); 156 | QUERY PLAN 157 | ---------------------------- 158 | Delete on test_3 159 | -> Seq Scan on test_3 160 | Filter: (key = 10) 161 | (3 rows) 162 | 163 | EXPLAIN (COSTS OFF) EXECUTE del(10); 164 | QUERY PLAN 165 | ---------------------------- 166 | Delete on test_3 167 | -> Seq Scan on test_3 168 | Filter: (key = 10) 169 | (3 rows) 170 | 171 | EXPLAIN (COSTS OFF) EXECUTE del(10); 172 | QUERY PLAN 173 | ---------------------------- 174 | Delete on test_3 175 | -> Seq Scan on test_3 176 | Filter: (key = 10) 177 | (3 rows) 178 | 179 | EXPLAIN (COSTS OFF) EXECUTE del(11); 180 | QUERY PLAN 181 | ---------------------------- 182 | Delete on test_9 183 | -> Seq Scan on test_9 184 | Filter: (key = 11) 185 | (3 rows) 186 | 187 | DEALLOCATE del; 188 | DROP TABLE param_upd_del.test CASCADE; 189 | NOTICE: drop cascades to 10 other objects 190 | DROP SCHEMA param_upd_del; 191 | DROP EXTENSION pg_pathman; 192 | -------------------------------------------------------------------------------- /expected/pathman_rebuild_deletes.out: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------- 3 | * NOTE: This test behaves differenly on < 11 because planner now turns 4 | * Row(Const, Const) into just Const of record type, apparently since 3decd150 5 | * ------------------------------------------- 6 | */ 7 | \set VERBOSITY terse 8 | SET search_path = 'public'; 9 | CREATE EXTENSION pg_pathman; 10 | CREATE SCHEMA test_deletes; 11 | /* 12 | * Test DELETEs on a partition with different TupleDescriptor. 13 | */ 14 | /* create partitioned table */ 15 | CREATE TABLE test_deletes.test(a FLOAT4, val INT4 NOT NULL, b FLOAT8); 16 | INSERT INTO test_deletes.test SELECT i, i, i FROM generate_series(1, 100) AS i; 17 | SELECT create_range_partitions('test_deletes.test', 'val', 1, 10); 18 | create_range_partitions 19 | ------------------------- 20 | 10 21 | (1 row) 22 | 23 | /* drop column 'a' */ 24 | ALTER TABLE test_deletes.test DROP COLUMN a; 25 | /* append new partition */ 26 | SELECT append_range_partition('test_deletes.test'); 27 | append_range_partition 28 | ------------------------ 29 | test_deletes.test_11 30 | (1 row) 31 | 32 | INSERT INTO test_deletes.test_11 (val, b) VALUES (101, 10); 33 | VACUUM ANALYZE; 34 | /* tuple descs are the same */ 35 | EXPLAIN (COSTS OFF) DELETE FROM test_deletes.test WHERE val = 1; 36 | QUERY PLAN 37 | --------------------------- 38 | Delete on test_1 39 | -> Seq Scan on test_1 40 | Filter: (val = 1) 41 | (3 rows) 42 | 43 | DELETE FROM test_deletes.test WHERE val = 1 RETURNING *, tableoid::REGCLASS; 44 | val | b | tableoid 45 | -----+---+--------------------- 46 | 1 | 1 | test_deletes.test_1 47 | (1 row) 48 | 49 | /* tuple descs are different */ 50 | EXPLAIN (COSTS OFF) DELETE FROM test_deletes.test WHERE val = 101; 51 | QUERY PLAN 52 | ----------------------------- 53 | Delete on test_11 54 | -> Seq Scan on test_11 55 | Filter: (val = 101) 56 | (3 rows) 57 | 58 | DELETE FROM test_deletes.test WHERE val = 101 RETURNING *, tableoid::REGCLASS; 59 | val | b | tableoid 60 | -----+----+---------------------- 61 | 101 | 10 | test_deletes.test_11 62 | (1 row) 63 | 64 | CREATE TABLE test_deletes.test_dummy (val INT4); 65 | EXPLAIN (COSTS OFF) DELETE FROM test_deletes.test 66 | WHERE val = 101 AND val = ANY (TABLE test_deletes.test_dummy) 67 | RETURNING *, tableoid::REGCLASS; 68 | QUERY PLAN 69 | ------------------------------------ 70 | Delete on test_11 71 | -> Nested Loop Semi Join 72 | -> Seq Scan on test_11 73 | Filter: (val = 101) 74 | -> Seq Scan on test_dummy 75 | Filter: (val = 101) 76 | (6 rows) 77 | 78 | EXPLAIN (COSTS OFF) DELETE FROM test_deletes.test t1 79 | USING test_deletes.test_dummy t2 80 | WHERE t1.val = 101 AND t1.val = t2.val 81 | RETURNING t1.*, t1.tableoid::REGCLASS; 82 | QUERY PLAN 83 | --------------------------------------- 84 | Delete on test_11 t1 85 | -> Nested Loop 86 | -> Seq Scan on test_11 t1 87 | Filter: (val = 101) 88 | -> Seq Scan on test_dummy t2 89 | Filter: (val = 101) 90 | (6 rows) 91 | 92 | EXPLAIN (COSTS OFF) DELETE FROM test_deletes.test 93 | WHERE val = 101 AND test >= (100, 8) 94 | RETURNING *, tableoid::REGCLASS; 95 | QUERY PLAN 96 | ----------------------------------------------------------------------------------------- 97 | Delete on test_11 98 | -> Seq Scan on test_11 99 | Filter: (((test_11.*)::test_deletes.test >= '(100,8)'::record) AND (val = 101)) 100 | (3 rows) 101 | 102 | DROP TABLE test_deletes.test_dummy; 103 | DROP TABLE test_deletes.test CASCADE; 104 | NOTICE: drop cascades to 12 other objects 105 | DROP SCHEMA test_deletes; 106 | DROP EXTENSION pg_pathman; 107 | -------------------------------------------------------------------------------- /expected/pathman_rebuild_deletes_1.out: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------- 3 | * NOTE: This test behaves differenly on < 11 because planner now turns 4 | * Row(Const, Const) into just Const of record type, apparently since 3decd150 5 | * ------------------------------------------- 6 | */ 7 | \set VERBOSITY terse 8 | SET search_path = 'public'; 9 | CREATE EXTENSION pg_pathman; 10 | CREATE SCHEMA test_deletes; 11 | /* 12 | * Test DELETEs on a partition with different TupleDescriptor. 13 | */ 14 | /* create partitioned table */ 15 | CREATE TABLE test_deletes.test(a FLOAT4, val INT4 NOT NULL, b FLOAT8); 16 | INSERT INTO test_deletes.test SELECT i, i, i FROM generate_series(1, 100) AS i; 17 | SELECT create_range_partitions('test_deletes.test', 'val', 1, 10); 18 | create_range_partitions 19 | ------------------------- 20 | 10 21 | (1 row) 22 | 23 | /* drop column 'a' */ 24 | ALTER TABLE test_deletes.test DROP COLUMN a; 25 | /* append new partition */ 26 | SELECT append_range_partition('test_deletes.test'); 27 | append_range_partition 28 | ------------------------ 29 | test_deletes.test_11 30 | (1 row) 31 | 32 | INSERT INTO test_deletes.test_11 (val, b) VALUES (101, 10); 33 | VACUUM ANALYZE; 34 | /* tuple descs are the same */ 35 | EXPLAIN (COSTS OFF) DELETE FROM test_deletes.test WHERE val = 1; 36 | QUERY PLAN 37 | --------------------------- 38 | Delete on test_1 39 | -> Seq Scan on test_1 40 | Filter: (val = 1) 41 | (3 rows) 42 | 43 | DELETE FROM test_deletes.test WHERE val = 1 RETURNING *, tableoid::REGCLASS; 44 | val | b | tableoid 45 | -----+---+--------------------- 46 | 1 | 1 | test_deletes.test_1 47 | (1 row) 48 | 49 | /* tuple descs are different */ 50 | EXPLAIN (COSTS OFF) DELETE FROM test_deletes.test WHERE val = 101; 51 | QUERY PLAN 52 | ----------------------------- 53 | Delete on test_11 54 | -> Seq Scan on test_11 55 | Filter: (val = 101) 56 | (3 rows) 57 | 58 | DELETE FROM test_deletes.test WHERE val = 101 RETURNING *, tableoid::REGCLASS; 59 | val | b | tableoid 60 | -----+----+---------------------- 61 | 101 | 10 | test_deletes.test_11 62 | (1 row) 63 | 64 | CREATE TABLE test_deletes.test_dummy (val INT4); 65 | EXPLAIN (COSTS OFF) DELETE FROM test_deletes.test 66 | WHERE val = 101 AND val = ANY (TABLE test_deletes.test_dummy) 67 | RETURNING *, tableoid::REGCLASS; 68 | QUERY PLAN 69 | ------------------------------------ 70 | Delete on test_11 71 | -> Nested Loop Semi Join 72 | -> Seq Scan on test_11 73 | Filter: (val = 101) 74 | -> Seq Scan on test_dummy 75 | Filter: (val = 101) 76 | (6 rows) 77 | 78 | EXPLAIN (COSTS OFF) DELETE FROM test_deletes.test t1 79 | USING test_deletes.test_dummy t2 80 | WHERE t1.val = 101 AND t1.val = t2.val 81 | RETURNING t1.*, t1.tableoid::REGCLASS; 82 | QUERY PLAN 83 | --------------------------------------- 84 | Delete on test_11 t1 85 | -> Nested Loop 86 | -> Seq Scan on test_11 t1 87 | Filter: (val = 101) 88 | -> Seq Scan on test_dummy t2 89 | Filter: (val = 101) 90 | (6 rows) 91 | 92 | EXPLAIN (COSTS OFF) DELETE FROM test_deletes.test 93 | WHERE val = 101 AND test >= (100, 8) 94 | RETURNING *, tableoid::REGCLASS; 95 | QUERY PLAN 96 | ----------------------------------------------------------------------------------- 97 | Delete on test_11 98 | -> Seq Scan on test_11 99 | Filter: (((test_11.*)::test_deletes.test >= ROW(100, 8)) AND (val = 101)) 100 | (3 rows) 101 | 102 | DROP TABLE test_deletes.test_dummy; 103 | DROP TABLE test_deletes.test CASCADE; 104 | NOTICE: drop cascades to 12 other objects 105 | DROP SCHEMA test_deletes; 106 | DROP EXTENSION pg_pathman; 107 | -------------------------------------------------------------------------------- /expected/test_variants.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | ret=0 4 | 5 | red="\033[0;31m" 6 | reset='\033[0m' 7 | 8 | shopt -s extglob 9 | 10 | for result in ./*_+([0-9]).out; do 11 | f1="$result" 12 | f2="${f1//_+([0-9])/}" 13 | 14 | printf "examine $(basename $f1) \n" 15 | 16 | file_diff=$(diff $f1 $f2 | wc -l) 17 | 18 | if [ $file_diff -eq 0 ]; then 19 | printf $red 20 | printf "WARNING: $(basename $f1) is redundant \n" >&2 21 | printf $reset 22 | 23 | ret=1 # change exit code 24 | fi 25 | done 26 | 27 | exit $ret 28 | -------------------------------------------------------------------------------- /mk_dockerfile.sh: -------------------------------------------------------------------------------- 1 | if [ -z ${PG_VERSION+x} ]; then 2 | echo PG_VERSION is not set! 3 | exit 1 4 | fi 5 | 6 | if [ -z ${LEVEL+x} ]; then 7 | LEVEL=scan-build 8 | fi 9 | 10 | echo PG_VERSION=${PG_VERSION} 11 | echo LEVEL=${LEVEL} 12 | 13 | sed \ 14 | -e 's/${PG_VERSION}/'${PG_VERSION}/g \ 15 | -e 's/${LEVEL}/'${LEVEL}/g \ 16 | Dockerfile.tmpl > Dockerfile 17 | -------------------------------------------------------------------------------- /patches/REL_11_STABLE-pg_pathman-core.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/backend/jit/llvm/llvmjit_deform.c b/src/backend/jit/llvm/llvmjit_deform.c 2 | index 6384ac940d8..8b4f731e7a8 100644 3 | --- a/src/backend/jit/llvm/llvmjit_deform.c 4 | +++ b/src/backend/jit/llvm/llvmjit_deform.c 5 | @@ -104,6 +104,10 @@ slot_compile_deform(LLVMJitContext *context, TupleDesc desc, int natts) 6 | 7 | int attnum; 8 | 9 | + /* don't generate code for tuples without user attributes */ 10 | + if (desc->natts == 0) 11 | + return NULL; 12 | + 13 | mod = llvm_mutable_module(context); 14 | 15 | funcname = llvm_expand_funcname(context, "deform"); 16 | diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c 17 | index 12138e49577..8638ebc4ba1 100644 18 | --- a/src/backend/jit/llvm/llvmjit_expr.c 19 | +++ b/src/backend/jit/llvm/llvmjit_expr.c 20 | @@ -274,6 +274,7 @@ llvm_compile_expr(ExprState *state) 21 | LLVMValueRef v_slot; 22 | LLVMBasicBlockRef b_fetch; 23 | LLVMValueRef v_nvalid; 24 | + LLVMValueRef l_jit_deform = NULL; 25 | 26 | b_fetch = l_bb_before_v(opblocks[i + 1], 27 | "op.%d.fetch", i); 28 | @@ -336,17 +337,20 @@ llvm_compile_expr(ExprState *state) 29 | */ 30 | if (desc && (context->base.flags & PGJIT_DEFORM)) 31 | { 32 | - LLVMValueRef params[1]; 33 | - LLVMValueRef l_jit_deform; 34 | - 35 | l_jit_deform = 36 | - slot_compile_deform(context, desc, 37 | + slot_compile_deform(context, 38 | + desc, 39 | op->d.fetch.last_var); 40 | + } 41 | + 42 | + if (l_jit_deform) 43 | + { 44 | + LLVMValueRef params[1]; 45 | + 46 | params[0] = v_slot; 47 | 48 | LLVMBuildCall(b, l_jit_deform, 49 | params, lengthof(params), ""); 50 | - 51 | } 52 | else 53 | { 54 | -------------------------------------------------------------------------------- /pg_compat_available.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | dir=$(dirname $0) 4 | func="$1" 5 | 6 | grep -n -r --include=pg_compat.c --include=pg_compat.h $func $dir | head -n1 7 | -------------------------------------------------------------------------------- /pg_pathman.control: -------------------------------------------------------------------------------- 1 | # pg_pathman extension 2 | comment = 'Partitioning tool for PostgreSQL' 3 | default_version = '1.5' 4 | module_pathname = '$libdir/pg_pathman' 5 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # Copyright (c) 2018, Postgres Professional 5 | # 6 | # supported levels: 7 | # * standard 8 | # * scan-build 9 | # * hardcore 10 | # * nightmare 11 | # 12 | 13 | set -ux 14 | status=0 15 | 16 | # global exports 17 | export PGPORT=55435 18 | export VIRTUAL_ENV_DISABLE_PROMPT=1 19 | 20 | PATHMAN_DIR=$PWD 21 | 22 | # indicator of using cassert + valgrind support 23 | USE_ASSERT_VALGRIND=false 24 | if [ "$LEVEL" = "hardcore" ] || \ 25 | [ "$LEVEL" = "nightmare" ]; then 26 | USE_ASSERT_VALGRIND=true 27 | fi 28 | 29 | # indicator of using special patch for vanilla 30 | if [ "$(printf '%s\n' "14" "$PG_VERSION" | sort -V | head -n1)" = "$PG_VERSION" ]; then 31 | USE_PATH=false 32 | else 33 | #patch version 14 and newer 34 | USE_PATH=true 35 | fi 36 | 37 | # rebuild PostgreSQL with cassert + valgrind support 38 | if [ "$USE_ASSERT_VALGRIND" = true ] || \ 39 | [ "$USE_PATH" = true ]; then 40 | 41 | set -e 42 | 43 | CUSTOM_PG_BIN=$PWD/pg_bin 44 | CUSTOM_PG_SRC=$PWD/postgresql 45 | 46 | # here PG_VERSION is provided by postgres:X-alpine docker image 47 | curl "https://ftp.postgresql.org/pub/source/v$PG_VERSION/postgresql-$PG_VERSION.tar.bz2" -o postgresql.tar.bz2 48 | echo "$PG_SHA256 *postgresql.tar.bz2" | sha256sum -c - 49 | 50 | mkdir $CUSTOM_PG_SRC 51 | 52 | tar \ 53 | --extract \ 54 | --file postgresql.tar.bz2 \ 55 | --directory $CUSTOM_PG_SRC \ 56 | --strip-components 1 57 | 58 | cd $CUSTOM_PG_SRC 59 | 60 | if [ "$USE_PATH" = true ]; then 61 | # apply the patch 62 | patch -p1 < $PATHMAN_DIR/patches/REL_${PG_VERSION%.*}_STABLE-pg_pathman-core.diff 63 | fi 64 | 65 | if [ "$USE_ASSERT_VALGRIND" = true ]; then 66 | # enable Valgrind support 67 | sed -i.bak "s/\/* #define USE_VALGRIND *\//#define USE_VALGRIND/g" src/include/pg_config_manual.h 68 | 69 | # enable additional options 70 | ./configure \ 71 | CFLAGS='-Og -ggdb3 -fno-omit-frame-pointer' \ 72 | --enable-cassert \ 73 | --prefix=$CUSTOM_PG_BIN \ 74 | --quiet 75 | else 76 | # without additional options 77 | ./configure \ 78 | --enable-cassert \ 79 | --prefix=$CUSTOM_PG_BIN \ 80 | --quiet 81 | fi 82 | 83 | # build & install PG 84 | time make -s -j$(nproc) && make -s install 85 | 86 | # build & install FDW 87 | time make -s -C contrib/postgres_fdw -j$(nproc) && \ 88 | make -s -C contrib/postgres_fdw install 89 | 90 | # override default PostgreSQL instance 91 | export PATH=$CUSTOM_PG_BIN/bin:$PATH 92 | export LD_LIBRARY_PATH=$CUSTOM_PG_BIN/lib 93 | 94 | # show pg_config path (just in case) 95 | which pg_config 96 | 97 | cd - 98 | 99 | set +e 100 | fi 101 | 102 | # show pg_config just in case 103 | pg_config 104 | 105 | # perform code checks if asked to 106 | if [ "$LEVEL" = "scan-build" ] || \ 107 | [ "$LEVEL" = "hardcore" ] || \ 108 | [ "$LEVEL" = "nightmare" ]; then 109 | 110 | # perform static analyzis 111 | scan-build --status-bugs make USE_PGXS=1 || status=$? 112 | 113 | # something's wrong, exit now! 114 | if [ $status -ne 0 ]; then exit 1; fi 115 | 116 | # don't forget to "make clean" 117 | make USE_PGXS=1 clean 118 | fi 119 | 120 | 121 | # build and install extension (using PG_CPPFLAGS and SHLIB_LINK for gcov) 122 | make USE_PGXS=1 PG_CPPFLAGS="-coverage" SHLIB_LINK="-coverage" 123 | make USE_PGXS=1 install 124 | 125 | # initialize database 126 | initdb -D $PGDATA 127 | 128 | # change PG's config 129 | echo "port = $PGPORT" >> $PGDATA/postgresql.conf 130 | cat conf.add >> $PGDATA/postgresql.conf 131 | 132 | # restart cluster 'test' 133 | if [ "$LEVEL" = "nightmare" ]; then 134 | ls $CUSTOM_PG_BIN/bin 135 | 136 | valgrind \ 137 | --tool=memcheck \ 138 | --leak-check=no \ 139 | --time-stamp=yes \ 140 | --track-origins=yes \ 141 | --trace-children=yes \ 142 | --gen-suppressions=all \ 143 | --suppressions=$CUSTOM_PG_SRC/src/tools/valgrind.supp \ 144 | --log-file=/tmp/valgrind-%p.log \ 145 | pg_ctl start -l /tmp/postgres.log -w || status=$? 146 | else 147 | pg_ctl start -l /tmp/postgres.log -w || status=$? 148 | fi 149 | 150 | # something's wrong, exit now! 151 | if [ $status -ne 0 ]; then cat /tmp/postgres.log; exit 1; fi 152 | 153 | # run regression tests 154 | export PG_REGRESS_DIFF_OPTS="-w -U3" # for alpine's diff (BusyBox) 155 | make USE_PGXS=1 installcheck || status=$? 156 | 157 | # show diff if it exists 158 | if [ -f regression.diffs ]; then cat regression.diffs; fi 159 | 160 | # run python tests 161 | set +x 162 | virtualenv /tmp/env && source /tmp/env/bin/activate && pip install testgres 163 | make USE_PGXS=1 python_tests || status=$? 164 | deactivate 165 | set -x 166 | 167 | if [ $status -ne 0 ]; then tail -n 2000 tests/python/tests.log; fi 168 | 169 | # show Valgrind logs if necessary 170 | if [ "$LEVEL" = "nightmare" ]; then 171 | for f in $(find /tmp -name valgrind-*.log); do 172 | if grep -q 'Command: [^ ]*/postgres' $f && grep -q 'ERROR SUMMARY: [1-9]' $f; then 173 | echo "========= Contents of $f" 174 | cat $f 175 | status=1 176 | fi 177 | done 178 | fi 179 | 180 | # run cmocka tests (using CFLAGS_SL for gcov) 181 | make USE_PGXS=1 PG_CPPFLAGS="-coverage" cmocka_tests || status=$? 182 | 183 | # something's wrong, exit now! 184 | if [ $status -ne 0 ]; then exit 1; fi 185 | 186 | # generate *.gcov files 187 | gcov *.c *.h 188 | 189 | 190 | set +ux 191 | 192 | 193 | # send coverage stats to Codecov 194 | bash <(curl -s https://codecov.io/bash) 195 | -------------------------------------------------------------------------------- /specs/for_update.spec: -------------------------------------------------------------------------------- 1 | setup 2 | { 3 | create extension pg_pathman; 4 | create table test_tbl(id int not null, val real); 5 | insert into test_tbl select i, i from generate_series(1, 1000) as i; 6 | select create_range_partitions('test_tbl', 'id', 1, 100, 10); 7 | } 8 | 9 | teardown 10 | { 11 | drop table test_tbl cascade; 12 | drop extension pg_pathman; 13 | } 14 | 15 | session "s1" 16 | step "s1_b" { begin; } 17 | step "s1_c" { commit; } 18 | step "s1_r" { rollback; } 19 | step "s1_update" { update test_tbl set id = 2 where id = 1; } 20 | 21 | session "s2" 22 | step "s2_select_locked" { select * from test_tbl where id = 1 for share; } 23 | step "s2_select" { select * from test_tbl where id = 1; } 24 | 25 | 26 | permutation "s1_b" "s1_update" "s2_select" "s1_r" 27 | 28 | permutation "s1_b" "s1_update" "s2_select_locked" "s1_r" 29 | 30 | permutation "s1_b" "s1_update" "s2_select_locked" "s1_c" 31 | -------------------------------------------------------------------------------- /specs/insert_nodes.spec: -------------------------------------------------------------------------------- 1 | setup 2 | { 3 | CREATE EXTENSION pg_pathman; 4 | CREATE TABLE range_rel(id serial primary key); 5 | SELECT create_range_partitions('range_rel', 'id', 1, 100, 1); 6 | SELECT set_spawn_using_bgw('range_rel', true); 7 | } 8 | 9 | teardown 10 | { 11 | SELECT drop_partitions('range_rel'); 12 | DROP TABLE range_rel CASCADE; 13 | DROP EXTENSION pg_pathman; 14 | } 15 | 16 | session "s1" 17 | step "s1b" { BEGIN; } 18 | step "s1_insert_150" { INSERT INTO range_rel SELECT generate_series(1, 150); } 19 | step "s1_insert_300" { INSERT INTO range_rel SELECT generate_series(151, 300); } 20 | step "s1_show_partitions" { SELECT pg_get_constraintdef(c.oid) FROM pg_inherits i LEFT JOIN pg_constraint c 21 | ON c.conrelid = i.inhrelid 22 | WHERE i.inhparent = 'range_rel'::regclass AND c.contype = 'c' 23 | ORDER BY c.oid; } 24 | step "s1r" { ROLLBACK; } 25 | 26 | session "s2" 27 | step "s2b" { BEGIN; } 28 | step "s2_insert_150" { INSERT INTO range_rel SELECT generate_series(1, 150); } 29 | step "s2_insert_300" { INSERT INTO range_rel SELECT generate_series(151, 300); } 30 | step "s2_show_partitions" { SELECT pg_get_constraintdef(c.oid) FROM pg_inherits i LEFT JOIN pg_constraint c 31 | ON c.conrelid = i.inhrelid 32 | WHERE i.inhparent = 'range_rel'::regclass AND c.contype = 'c' 33 | ORDER BY c.oid; } 34 | step "s2r" { ROLLBACK; } 35 | step "s2c" { COMMIT; } 36 | 37 | # Rollback first transactions 38 | permutation "s1b" "s1_insert_150" "s1r" "s1_show_partitions" "s2b" "s2_insert_150" "s2c" "s2_show_partitions" 39 | 40 | permutation "s1b" "s1_insert_150" "s1r" "s1_show_partitions" "s2b" "s2_insert_300" "s2c" "s2_show_partitions" 41 | 42 | permutation "s1b" "s1_insert_300" "s1r" "s1_show_partitions" "s2b" "s2_insert_150" "s2c" "s2_show_partitions" 43 | 44 | # Rollback both transactions 45 | permutation "s1b" "s1_insert_150" "s2b" "s2_insert_300" "s1r" "s2r" "s2_show_partitions" 46 | -------------------------------------------------------------------------------- /specs/rollback_on_create_partitions.spec: -------------------------------------------------------------------------------- 1 | setup 2 | { 3 | CREATE EXTENSION pg_pathman; 4 | CREATE TABLE range_rel(id serial primary key); 5 | } 6 | 7 | teardown 8 | { 9 | DROP TABLE range_rel CASCADE; 10 | DROP EXTENSION pg_pathman; 11 | } 12 | 13 | session "s1" 14 | step "begin" { BEGIN; } 15 | step "rollback" { ROLLBACK; } 16 | step "commit" { COMMIT; } 17 | step "insert_data" { INSERT INTO range_rel SELECT generate_series(1, 10000); } 18 | step "create_partitions" { SELECT create_range_partitions('range_rel', 'id', 1, 1000); } 19 | step "drop_partitions" { SELECT drop_partitions('range_rel'); } 20 | step "savepoint_a" { SAVEPOINT a; } 21 | step "rollback_a" { ROLLBACK TO SAVEPOINT a; } 22 | step "savepoint_b" { SAVEPOINT b; } 23 | step "rollback_b" { ROLLBACK TO SAVEPOINT b; } 24 | step "savepoint_c" { SAVEPOINT c; } 25 | step "show_rel" { SELECT l.parent, l.partition FROM pathman_partition_list l WHERE l.parent = 'range_rel'::regclass; } 26 | 27 | permutation "begin" "insert_data" "create_partitions" "show_rel" "rollback" "show_rel" 28 | 29 | permutation "begin" "insert_data" "create_partitions" "show_rel" "commit" "show_rel" 30 | 31 | permutation "begin" "insert_data" "savepoint_a" "create_partitions" "savepoint_b" "drop_partitions" "show_rel" "savepoint_c" "rollback" "show_rel" 32 | permutation "begin" "insert_data" "savepoint_a" "create_partitions" "savepoint_b" "drop_partitions" "show_rel" "savepoint_c" "commit" "show_rel" 33 | 34 | # rollback to 'b' after dropping partitions 35 | permutation "begin" "insert_data" "savepoint_a" "create_partitions" "savepoint_b" "drop_partitions" "savepoint_c" "rollback_b" "show_rel" "rollback" "show_rel" 36 | permutation "begin" "insert_data" "savepoint_a" "create_partitions" "savepoint_b" "drop_partitions" "savepoint_c" "rollback_b" "show_rel" "commit" "show_rel" 37 | 38 | # rollback to 'a' after dropping partitions 39 | permutation "begin" "insert_data" "savepoint_a" "create_partitions" "savepoint_b" "drop_partitions" "show_rel" "savepoint_c" "rollback_a" "show_rel" "rollback" "show_rel" 40 | permutation "begin" "insert_data" "savepoint_a" "create_partitions" "savepoint_b" "drop_partitions" "show_rel" "savepoint_c" "rollback_a" "show_rel" "commit" "show_rel" 41 | 42 | # drop partitions twice in a single transaction 43 | 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" 44 | 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" 45 | 46 | # create partitions twice in a single transaction 47 | permutation "begin" "insert_data" "savepoint_a" "create_partitions" "savepoint_b" "drop_partitions" "rollback_a" "create_partitions" "show_rel" "rollback" "show_rel" 48 | permutation "begin" "insert_data" "savepoint_a" "create_partitions" "savepoint_b" "drop_partitions" "rollback_a" "create_partitions" "show_rel" "commit" "show_rel" 49 | -------------------------------------------------------------------------------- /sql/pathman_CVE-2020-14350.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Check fix for CVE-2020-14350. 3 | * See also 7eeb1d986 postgresql commit. 4 | */ 5 | 6 | SET client_min_messages = 'warning'; 7 | DROP FUNCTION IF EXISTS _partition_data_concurrent(oid,integer); 8 | DROP FUNCTION IF EXISTS create_single_range_partition(TEXT,ANYELEMENT,ANYELEMENT,TEXT); 9 | DROP TABLE IF EXISTS test1 CASCADE; 10 | DROP TABLE IF EXISTS test2 CASCADE; 11 | DROP ROLE IF EXISTS pathman_regress_hacker; 12 | SET client_min_messages = 'notice'; 13 | GRANT CREATE ON SCHEMA public TO PUBLIC; 14 | 15 | CREATE EXTENSION pg_pathman; 16 | CREATE ROLE pathman_regress_hacker LOGIN; 17 | 18 | -- Test 1 19 | RESET ROLE; 20 | ALTER ROLE pathman_regress_hacker NOSUPERUSER; 21 | 22 | SET ROLE pathman_regress_hacker; 23 | SHOW is_superuser; 24 | CREATE FUNCTION _partition_data_concurrent(relation oid, p_limit INT, OUT p_total BIGINT) 25 | RETURNS bigint 26 | AS $$ 27 | BEGIN 28 | ALTER ROLE pathman_regress_hacker SUPERUSER; 29 | SELECT _partition_data_concurrent(relation, NULL::text, NULL::text, p_limit) INTO p_total; 30 | END 31 | $$ LANGUAGE plpgsql; 32 | 33 | CREATE TABLE test1(i INT4 NOT NULL); 34 | INSERT INTO test1 SELECT generate_series(1, 500); 35 | SELECT create_hash_partitions('test1', 'i', 5, false); 36 | 37 | RESET ROLE; 38 | SELECT partition_table_concurrently('test1', 10, 1); 39 | SELECT pg_sleep(1); 40 | 41 | -- Test result (must be 'off') 42 | SET ROLE pathman_regress_hacker; 43 | SHOW is_superuser; 44 | 45 | -- Test 2 46 | RESET ROLE; 47 | ALTER ROLE pathman_regress_hacker NOSUPERUSER; 48 | 49 | SET ROLE pathman_regress_hacker; 50 | SHOW is_superuser; 51 | CREATE FUNCTION create_single_range_partition(parent_relid TEXT, start_value ANYELEMENT, end_value ANYELEMENT, partition_name TEXT) 52 | RETURNS REGCLASS 53 | AS $$ 54 | BEGIN 55 | ALTER ROLE pathman_regress_hacker SUPERUSER; 56 | RETURN create_single_range_partition(parent_relid, start_value, end_value, partition_name, NULL::text); 57 | END 58 | $$ LANGUAGE plpgsql; 59 | 60 | RESET ROLE; 61 | CREATE TABLE test2(i INT4 NOT NULL); 62 | INSERT INTO test2 VALUES(0); 63 | SELECT create_range_partitions('test2', 'i', 0, 1); 64 | INSERT INTO test2 values(1); 65 | 66 | -- Test result (must be 'off') 67 | SET ROLE pathman_regress_hacker; 68 | SHOW is_superuser; 69 | 70 | -- Cleanup 71 | RESET ROLE; 72 | DROP FUNCTION _partition_data_concurrent(oid,integer); 73 | DROP FUNCTION create_single_range_partition(TEXT,ANYELEMENT,ANYELEMENT,TEXT); 74 | DROP TABLE test1 CASCADE; 75 | DROP TABLE test2 CASCADE; 76 | DROP ROLE pathman_regress_hacker; 77 | DROP EXTENSION pg_pathman; 78 | 79 | -------------------------------------------------------------------------------- /sql/pathman_bgw.sql: -------------------------------------------------------------------------------- 1 | \set VERBOSITY terse 2 | 3 | SET search_path = 'public'; 4 | CREATE EXTENSION pg_pathman; 5 | CREATE SCHEMA test_bgw; 6 | 7 | 8 | 9 | /* 10 | * Tests for SpawnPartitionsWorker 11 | */ 12 | 13 | /* int4, size of Datum == 4 */ 14 | CREATE TABLE test_bgw.test_1(val INT4 NOT NULL); 15 | SELECT create_range_partitions('test_bgw.test_1', 'val', 1, 5, 2); 16 | 17 | SELECT set_spawn_using_bgw('test_bgw.test_1', true); 18 | INSERT INTO test_bgw.test_1 VALUES (11); 19 | SELECT * FROM pathman_partition_list ORDER BY partition; /* should contain 3 partitions */ 20 | 21 | DROP TABLE test_bgw.test_1 CASCADE; 22 | 23 | 24 | /* int8, size of Datum == 8 */ 25 | CREATE TABLE test_bgw.test_2(val INT8 NOT NULL); 26 | SELECT create_range_partitions('test_bgw.test_2', 'val', 1, 5, 2); 27 | 28 | SELECT set_spawn_using_bgw('test_bgw.test_2', true); 29 | INSERT INTO test_bgw.test_2 VALUES (11); 30 | SELECT * FROM pathman_partition_list ORDER BY partition; /* should contain 3 partitions */ 31 | 32 | DROP TABLE test_bgw.test_2 CASCADE; 33 | 34 | 35 | /* numeric, size of Datum == var */ 36 | CREATE TABLE test_bgw.test_3(val NUMERIC NOT NULL); 37 | SELECT create_range_partitions('test_bgw.test_3', 'val', 1, 5, 2); 38 | 39 | SELECT set_spawn_using_bgw('test_bgw.test_3', true); 40 | INSERT INTO test_bgw.test_3 VALUES (11); 41 | SELECT * FROM pathman_partition_list ORDER BY partition; /* should contain 3 partitions */ 42 | 43 | DROP TABLE test_bgw.test_3 CASCADE; 44 | 45 | 46 | /* date, size of Datum == var */ 47 | CREATE TABLE test_bgw.test_4(val DATE NOT NULL); 48 | SELECT create_range_partitions('test_bgw.test_4', 'val', '20170213'::date, '1 day'::interval, 2); 49 | 50 | SELECT set_spawn_using_bgw('test_bgw.test_4', true); 51 | INSERT INTO test_bgw.test_4 VALUES ('20170215'); 52 | SELECT * FROM pathman_partition_list ORDER BY partition; /* should contain 3 partitions */ 53 | 54 | DROP TABLE test_bgw.test_4 CASCADE; 55 | 56 | 57 | /* test error handling in BGW */ 58 | CREATE TABLE test_bgw.test_5(val INT4 NOT NULL); 59 | SELECT create_range_partitions('test_bgw.test_5', 'val', 1, 10, 2); 60 | 61 | CREATE OR REPLACE FUNCTION test_bgw.abort_xact(args JSONB) 62 | RETURNS VOID AS $$ 63 | BEGIN 64 | RAISE EXCEPTION 'aborting xact!'; 65 | END 66 | $$ language plpgsql; 67 | 68 | SELECT set_spawn_using_bgw('test_bgw.test_5', true); 69 | SELECT set_init_callback('test_bgw.test_5', 'test_bgw.abort_xact(jsonb)'); 70 | INSERT INTO test_bgw.test_5 VALUES (-100); 71 | SELECT * FROM pathman_partition_list ORDER BY partition; /* should contain 3 partitions */ 72 | 73 | DROP FUNCTION test_bgw.abort_xact(args JSONB); 74 | DROP TABLE test_bgw.test_5 CASCADE; 75 | 76 | 77 | 78 | /* 79 | * Tests for ConcurrentPartWorker 80 | */ 81 | 82 | CREATE TABLE test_bgw.conc_part(id INT4 NOT NULL); 83 | INSERT INTO test_bgw.conc_part SELECT generate_series(1, 500); 84 | SELECT create_hash_partitions('test_bgw.conc_part', 'id', 5, false); 85 | 86 | BEGIN; 87 | /* Also test FOR SHARE/UPDATE conflicts in BGW */ 88 | SELECT * FROM test_bgw.conc_part ORDER BY id LIMIT 1 FOR SHARE; 89 | /* Run partitioning bgworker */ 90 | SELECT partition_table_concurrently('test_bgw.conc_part', 10, 1); 91 | /* Wait until bgworker starts */ 92 | SELECT pg_sleep(1); 93 | ROLLBACK; 94 | 95 | /* Wait until it finises */ 96 | DO $$ 97 | DECLARE 98 | ops int8; 99 | rows int8; 100 | rows_old int8 := 0; 101 | i int4 := 0; -- protect from endless loop 102 | BEGIN 103 | LOOP 104 | -- get total number of processed rows 105 | SELECT processed 106 | FROM pathman_concurrent_part_tasks 107 | WHERE relid = 'test_bgw.conc_part'::regclass 108 | INTO rows; 109 | 110 | -- get number of partitioning tasks 111 | GET DIAGNOSTICS ops = ROW_COUNT; 112 | 113 | IF ops > 0 THEN 114 | PERFORM pg_sleep(0.2); 115 | 116 | ASSERT rows IS NOT NULL; 117 | 118 | IF rows_old = rows THEN 119 | i = i + 1; 120 | ELSIF rows < rows_old THEN 121 | RAISE EXCEPTION 'rows is decreasing: new %, old %', rows, rows_old; 122 | ELSIF rows > 500 THEN 123 | RAISE EXCEPTION 'processed % rows', rows; 124 | END IF; 125 | ELSE 126 | EXIT; -- exit loop 127 | END IF; 128 | 129 | IF i > 500 THEN 130 | RAISE WARNING 'looks like partitioning bgw is stuck!'; 131 | EXIT; -- exit loop 132 | END IF; 133 | 134 | rows_old = rows; 135 | END LOOP; 136 | END 137 | $$ LANGUAGE plpgsql; 138 | 139 | /* Check amount of tasks and rows in parent and partitions */ 140 | SELECT count(*) FROM pathman_concurrent_part_tasks; 141 | SELECT count(*) FROM ONLY test_bgw.conc_part; 142 | SELECT count(*) FROM test_bgw.conc_part; 143 | 144 | DROP TABLE test_bgw.conc_part CASCADE; 145 | 146 | 147 | 148 | DROP SCHEMA test_bgw; 149 | DROP EXTENSION pg_pathman; 150 | -------------------------------------------------------------------------------- /sql/pathman_cache_pranks.sql: -------------------------------------------------------------------------------- 1 | \set VERBOSITY terse 2 | -- is pathman (caches, in particular) strong enough to carry out this? 3 | 4 | SET search_path = 'public'; 5 | 6 | -- make sure nothing breaks on disable/enable when nothing was initialized yet 7 | SET pg_pathman.enable = false; 8 | SET pg_pathman.enable = true; 9 | 10 | -- wobble with create-drop ext: tests cached relids sanity 11 | CREATE EXTENSION pg_pathman; 12 | SET pg_pathman.enable = f; 13 | DROP EXTENSION pg_pathman; 14 | CREATE EXTENSION pg_pathman; 15 | SET pg_pathman.enable = true; 16 | DROP EXTENSION pg_pathman; 17 | CREATE EXTENSION pg_pathman; 18 | DROP EXTENSION pg_pathman; 19 | 20 | -- create it for further tests 21 | CREATE EXTENSION pg_pathman; 22 | 23 | -- 079797e0d5 24 | CREATE TABLE part_test(val serial); 25 | INSERT INTO part_test SELECT generate_series(1, 30); 26 | SELECT create_range_partitions('part_test', 'val', 1, 10); 27 | SELECT set_interval('part_test', 100); 28 | DELETE FROM pathman_config WHERE partrel = 'part_test'::REGCLASS; 29 | SELECT drop_partitions('part_test'); 30 | SELECT disable_pathman_for('part_test'); 31 | 32 | CREATE TABLE wrong_partition (LIKE part_test) INHERITS (part_test); 33 | SELECT add_to_pathman_config('part_test', 'val', '10'); 34 | SELECT add_to_pathman_config('part_test', 'val'); 35 | 36 | DROP TABLE part_test CASCADE; 37 | -- 38 | 39 | -- 85fc5ccf121 40 | CREATE TABLE part_test(val serial); 41 | INSERT INTO part_test SELECT generate_series(1, 3000); 42 | SELECT create_range_partitions('part_test', 'val', 1, 10); 43 | SELECT append_range_partition('part_test'); 44 | DELETE FROM part_test; 45 | SELECT create_single_range_partition('part_test', NULL::INT4, NULL); /* not ok */ 46 | DELETE FROM pathman_config WHERE partrel = 'part_test'::REGCLASS; 47 | SELECT create_hash_partitions('part_test', 'val', 2, partition_names := ARRAY[]::TEXT[]); /* not ok */ 48 | 49 | DROP TABLE part_test CASCADE; 50 | -- 51 | -- 52 | -- PGPRO-7870 53 | -- Added error for case executing prepared query after DROP/CREATE EXTENSION. 54 | -- 55 | -- DROP/CREATE extension 56 | CREATE TABLE part_test(a INT4 NOT NULL, b INT4); 57 | PREPARE q(int4) AS SELECT * FROM part_test WHERE a > ALL (array[$1, 898]); 58 | SELECT create_range_partitions('part_test', 'a', 1, 100, 10); 59 | 60 | EXECUTE q(1); 61 | EXECUTE q(1); 62 | EXECUTE q(1); 63 | EXECUTE q(1); 64 | EXECUTE q(1); 65 | EXECUTE q(1); 66 | 67 | DROP EXTENSION pg_pathman; 68 | CREATE EXTENSION pg_pathman; 69 | 70 | EXECUTE q(1); 71 | 72 | DEALLOCATE q; 73 | DROP TABLE part_test CASCADE; 74 | 75 | -- DROP/CREATE disabled extension 76 | CREATE TABLE part_test(a INT4 NOT NULL, b INT4); 77 | PREPARE q(int4) AS SELECT * FROM part_test WHERE a > ALL (array[$1, 898]); 78 | SELECT create_range_partitions('part_test', 'a', 1, 100, 10); 79 | 80 | EXECUTE q(1); 81 | EXECUTE q(1); 82 | EXECUTE q(1); 83 | EXECUTE q(1); 84 | EXECUTE q(1); 85 | EXECUTE q(1); 86 | 87 | SET pg_pathman.enable = f; 88 | DROP EXTENSION pg_pathman; 89 | CREATE EXTENSION pg_pathman; 90 | SET pg_pathman.enable = t; 91 | 92 | EXECUTE q(1); 93 | 94 | DEALLOCATE q; 95 | DROP TABLE part_test CASCADE; 96 | 97 | -- DROP/CREATE extension in autonomous transaction 98 | CREATE TABLE part_test(a INT4 NOT NULL, b INT4); 99 | PREPARE q(int4) AS SELECT * FROM part_test WHERE a > ALL (array[$1, 198]); 100 | SELECT create_range_partitions('part_test', 'a', 1, 100, 2); 101 | 102 | EXECUTE q(1); 103 | EXECUTE q(1); 104 | EXECUTE q(1); 105 | EXECUTE q(1); 106 | EXECUTE q(1); 107 | EXECUTE q(1); 108 | 109 | BEGIN; 110 | BEGIN AUTONOMOUS; 111 | DROP EXTENSION pg_pathman; 112 | CREATE EXTENSION pg_pathman; 113 | COMMIT; 114 | COMMIT; 115 | 116 | EXECUTE q(1); 117 | 118 | DEALLOCATE q; 119 | DROP TABLE part_test CASCADE; 120 | 121 | -- finalize 122 | DROP EXTENSION pg_pathman; 123 | -------------------------------------------------------------------------------- /sql/pathman_callbacks.sql: -------------------------------------------------------------------------------- 1 | \set VERBOSITY terse 2 | SET search_path = 'public'; 3 | CREATE EXTENSION pg_pathman; 4 | CREATE SCHEMA callbacks; 5 | 6 | 7 | 8 | /* callback #1 */ 9 | CREATE OR REPLACE FUNCTION callbacks.abc_on_part_created_callback(args JSONB) 10 | RETURNS VOID AS $$ 11 | BEGIN 12 | RAISE WARNING 'callback arg: %', args::TEXT; 13 | END 14 | $$ language plpgsql; 15 | 16 | /* callback #2 */ 17 | CREATE OR REPLACE FUNCTION public.dummy_cb(args JSONB) 18 | RETURNS VOID AS $$ 19 | BEGIN 20 | END 21 | $$ language plpgsql; 22 | 23 | 24 | 25 | CREATE TABLE callbacks.abc(a serial, b int); 26 | SELECT create_range_partitions('callbacks.abc', 'a', 1, 100, 2); 27 | 28 | SELECT set_init_callback('callbacks.abc', 'public.dummy_cb(jsonb)'); 29 | 30 | /* check that callback is schema-qualified */ 31 | SELECT init_callback FROM pathman_config_params 32 | WHERE partrel = 'callbacks.abc'::REGCLASS; 33 | 34 | /* reset callback */ 35 | SELECT set_init_callback('callbacks.abc'); 36 | 37 | /* should return NULL */ 38 | SELECT init_callback FROM pathman_config_params 39 | WHERE partrel = 'callbacks.abc'::REGCLASS; 40 | 41 | SELECT set_init_callback('callbacks.abc', 42 | 'callbacks.abc_on_part_created_callback(jsonb)'); 43 | 44 | /* check that callback is schema-qualified */ 45 | SELECT init_callback FROM pathman_config_params 46 | WHERE partrel = 'callbacks.abc'::REGCLASS; 47 | 48 | DROP TABLE callbacks.abc CASCADE; 49 | 50 | 51 | /* set callback to be called on RANGE partitions */ 52 | CREATE TABLE callbacks.abc(a serial, b int); 53 | SELECT create_range_partitions('callbacks.abc', 'a', 1, 100, 2); 54 | 55 | SELECT set_init_callback('callbacks.abc', 56 | 'callbacks.abc_on_part_created_callback(jsonb)'); 57 | 58 | INSERT INTO callbacks.abc VALUES (123, 1); 59 | INSERT INTO callbacks.abc VALUES (223, 1); /* show warning */ 60 | 61 | SELECT set_spawn_using_bgw('callbacks.abc', true); 62 | SELECT get_number_of_partitions('callbacks.abc'); 63 | INSERT INTO callbacks.abc VALUES (323, 1); 64 | SELECT get_number_of_partitions('callbacks.abc'); /* +1 partition (created by BGW) */ 65 | SELECT set_spawn_using_bgw('callbacks.abc', false); 66 | 67 | 68 | SELECT append_range_partition('callbacks.abc'); 69 | SELECT prepend_range_partition('callbacks.abc'); 70 | SELECT add_range_partition('callbacks.abc', 501, 602); 71 | 72 | SELECT drop_partitions('callbacks.abc'); 73 | 74 | 75 | /* set callback to be called on HASH partitions */ 76 | SELECT set_init_callback('callbacks.abc', 77 | 'callbacks.abc_on_part_created_callback(jsonb)'); 78 | SELECT create_hash_partitions('callbacks.abc', 'a', 5); 79 | 80 | DROP TABLE callbacks.abc CASCADE; 81 | 82 | 83 | /* test the temprary deletion of callback function */ 84 | CREATE TABLE callbacks.abc(a serial, b int); 85 | SELECT set_init_callback('callbacks.abc', 86 | 'callbacks.abc_on_part_created_callback(jsonb)'); 87 | SELECT create_range_partitions('callbacks.abc', 'a', 1, 100, 2); 88 | 89 | INSERT INTO callbacks.abc VALUES (201, 0); /* +1 new partition */ 90 | 91 | BEGIN; 92 | DROP FUNCTION callbacks.abc_on_part_created_callback(jsonb); 93 | INSERT INTO callbacks.abc VALUES (301, 0); /* +0 new partitions (ERROR) */ 94 | ROLLBACK; 95 | 96 | INSERT INTO callbacks.abc VALUES (301, 0); /* +1 new partition */ 97 | 98 | DROP TABLE callbacks.abc CASCADE; 99 | 100 | 101 | /* more complex test using rotation of tables */ 102 | CREATE TABLE callbacks.abc(a INT4 NOT NULL); 103 | INSERT INTO callbacks.abc 104 | SELECT a FROM generate_series(1, 100) a; 105 | SELECT create_range_partitions('callbacks.abc', 'a', 1, 10, 10); 106 | 107 | CREATE OR REPLACE FUNCTION callbacks.rotation_callback(params jsonb) 108 | RETURNS VOID AS 109 | $$ 110 | DECLARE 111 | relation regclass; 112 | parent_rel regclass; 113 | BEGIN 114 | parent_rel := concat(params->>'partition_schema', '.', params->>'parent')::regclass; 115 | 116 | -- drop "old" partitions 117 | FOR relation IN (SELECT partition FROM 118 | (SELECT partition, range_min::INT4 FROM pathman_partition_list 119 | WHERE parent = parent_rel 120 | ORDER BY range_min::INT4 DESC 121 | OFFSET 4) t -- remain 4 last partitions 122 | ORDER BY range_min) 123 | LOOP 124 | RAISE NOTICE 'dropping partition %', relation; 125 | PERFORM drop_range_partition(relation); 126 | END LOOP; 127 | END 128 | $$ LANGUAGE plpgsql; 129 | 130 | SELECT * FROM pathman_partition_list 131 | WHERE parent = 'callbacks.abc'::REGCLASS 132 | ORDER BY range_min::INT4; 133 | 134 | SELECT set_init_callback('callbacks.abc', 135 | 'callbacks.rotation_callback(jsonb)'); 136 | 137 | INSERT INTO callbacks.abc VALUES (1000); 138 | INSERT INTO callbacks.abc VALUES (1500); 139 | 140 | SELECT * FROM pathman_partition_list 141 | WHERE parent = 'callbacks.abc'::REGCLASS 142 | ORDER BY range_min::INT4; 143 | 144 | 145 | 146 | DROP TABLE callbacks.abc CASCADE; 147 | DROP FUNCTION callbacks.abc_on_part_created_callback(jsonb); 148 | DROP FUNCTION public.dummy_cb(jsonb); 149 | DROP FUNCTION callbacks.rotation_callback(jsonb); 150 | DROP SCHEMA callbacks; 151 | DROP EXTENSION pg_pathman CASCADE; 152 | -------------------------------------------------------------------------------- /sql/pathman_column_type.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * In 9ce77d75c5a (>= 13) struct Var was changed, which caused the output 3 | * of get_partition_cooked_key to change. 4 | */ 5 | 6 | \set VERBOSITY terse 7 | 8 | SET search_path = 'public'; 9 | CREATE EXTENSION pg_pathman; 10 | CREATE SCHEMA test_column_type; 11 | 12 | 13 | /* 14 | * RANGE partitioning. 15 | */ 16 | 17 | /* create new table (val int) */ 18 | CREATE TABLE test_column_type.test(val INT4 NOT NULL); 19 | SELECT create_range_partitions('test_column_type.test', 'val', 1, 10, 10); 20 | 21 | /* make sure that bounds and dispatch info has been cached */ 22 | SELECT * FROM test_column_type.test; 23 | SELECT context, entries FROM pathman_cache_stats 24 | WHERE context != 'partition status cache' ORDER BY context; 25 | 26 | /* 27 | * Get parsed and analyzed expression. 28 | */ 29 | CREATE FUNCTION get_cached_partition_cooked_key(REGCLASS) 30 | RETURNS TEXT AS 'pg_pathman', 'get_cached_partition_cooked_key_pl' 31 | LANGUAGE C STRICT; 32 | 33 | SELECT get_partition_cooked_key('test_column_type.test'::REGCLASS); 34 | SELECT get_cached_partition_cooked_key('test_column_type.test'::REGCLASS); 35 | SELECT get_partition_key_type('test_column_type.test'::REGCLASS); 36 | 37 | /* change column's type (should also flush caches) */ 38 | ALTER TABLE test_column_type.test ALTER val TYPE NUMERIC; 39 | 40 | /* check that correct expression has been built */ 41 | SELECT get_partition_key_type('test_column_type.test'::REGCLASS); 42 | SELECT get_partition_cooked_key('test_column_type.test'::REGCLASS); 43 | SELECT get_cached_partition_cooked_key('test_column_type.test'::REGCLASS); 44 | DROP FUNCTION get_cached_partition_cooked_key(REGCLASS); 45 | 46 | /* make sure that everything works properly */ 47 | SELECT * FROM test_column_type.test; 48 | 49 | SELECT context, entries FROM pathman_cache_stats 50 | WHERE context != 'partition status cache' ORDER BY context; 51 | 52 | /* check insert dispatching */ 53 | INSERT INTO test_column_type.test VALUES (1); 54 | SELECT tableoid::regclass, * FROM test_column_type.test; 55 | 56 | SELECT drop_partitions('test_column_type.test'); 57 | DROP TABLE test_column_type.test CASCADE; 58 | 59 | 60 | /* 61 | * HASH partitioning. 62 | */ 63 | 64 | /* create new table (id int, val int) */ 65 | CREATE TABLE test_column_type.test(id INT4 NOT NULL, val INT4); 66 | SELECT create_hash_partitions('test_column_type.test', 'id', 5); 67 | 68 | /* make sure that bounds and dispatch info has been cached */ 69 | SELECT * FROM test_column_type.test; 70 | SELECT context, entries FROM pathman_cache_stats 71 | WHERE context != 'partition status cache' ORDER BY context; 72 | 73 | /* change column's type (should NOT work) */ 74 | ALTER TABLE test_column_type.test ALTER id TYPE NUMERIC; 75 | 76 | /* make sure that everything works properly */ 77 | SELECT * FROM test_column_type.test; 78 | SELECT context, entries FROM pathman_cache_stats 79 | WHERE context != 'partition status cache' ORDER BY context; 80 | 81 | /* change column's type (should flush caches) */ 82 | ALTER TABLE test_column_type.test ALTER val TYPE NUMERIC; 83 | 84 | /* make sure that everything works properly */ 85 | SELECT * FROM test_column_type.test; 86 | SELECT context, entries FROM pathman_cache_stats 87 | WHERE context != 'partition status cache' ORDER BY context; 88 | 89 | /* check insert dispatching */ 90 | INSERT INTO test_column_type.test VALUES (1); 91 | SELECT tableoid::regclass, * FROM test_column_type.test; 92 | 93 | SELECT drop_partitions('test_column_type.test'); 94 | DROP TABLE test_column_type.test CASCADE; 95 | 96 | 97 | DROP SCHEMA test_column_type; 98 | DROP EXTENSION pg_pathman; 99 | -------------------------------------------------------------------------------- /sql/pathman_declarative.sql: -------------------------------------------------------------------------------- 1 | \set VERBOSITY terse 2 | 3 | SET search_path = 'public'; 4 | CREATE SCHEMA pathman; 5 | CREATE EXTENSION pg_pathman SCHEMA pathman; 6 | CREATE SCHEMA test; 7 | 8 | CREATE TABLE test.range_rel ( 9 | id SERIAL PRIMARY KEY, 10 | dt DATE NOT NULL 11 | ); 12 | CREATE TABLE test.r2 (LIKE test.range_rel); 13 | ALTER TABLE test.range_rel ATTACH PARTITION test.r2 14 | FOR VALUES FROM ('2015-05-01') TO ('2015-06-01'); 15 | 16 | INSERT INTO test.range_rel (dt) 17 | SELECT g FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) AS g; 18 | SELECT pathman.create_range_partitions('test.range_rel', 'dt', 19 | '2015-01-01'::DATE, '1 month'::INTERVAL); 20 | 21 | SELECT * FROM pathman.pathman_partition_list; 22 | ALTER TABLE test.range_rel ATTACH PARTITION test.r2 23 | FOR VALUES IN ('2015-05-01', '2015-06-01'); 24 | ALTER TABLE test.range_rel ATTACH PARTITION test.r2 25 | FOR VALUES FROM ('2014-05-01') TO ('2015-06-01'); 26 | ALTER TABLE test.range_rel ATTACH PARTITION test.r2 27 | FOR VALUES FROM ('2015-05-01') TO ('2015-06-01'); 28 | SELECT * FROM pathman.pathman_partition_list; 29 | \d+ test.r2; 30 | ALTER TABLE test.range_rel DETACH PARTITION test.r2; 31 | SELECT * FROM pathman.pathman_partition_list; 32 | \d+ test.r2; 33 | 34 | CREATE TABLE test.r4 PARTITION OF test.range_rel 35 | FOR VALUES IN ('2015-05-01', '2015-06-01'); 36 | CREATE TABLE test.r4 PARTITION OF test.range_rel 37 | FOR VALUES FROM ('2014-05-01') TO ('2015-06-01'); 38 | CREATE TABLE test.r4 PARTITION OF test.range_rel 39 | FOR VALUES FROM ('2015-06-01') TO ('2016-01-01'); 40 | \d+ test.r4; 41 | 42 | /* Note: PG-10 doesn't support ATTACH PARTITION ... DEFAULT */ 43 | ALTER TABLE IF EXISTS test.nonexistent_table ATTACH PARTITION baz FOR VALUES IN (42); 44 | ALTER TABLE IF EXISTS test.nonexistent_table DETACH PARTITION baz; 45 | 46 | DROP TABLE test.r2 CASCADE; 47 | DROP TABLE test.range_rel CASCADE; 48 | DROP SCHEMA test; 49 | DROP EXTENSION pg_pathman CASCADE; 50 | DROP SCHEMA pathman; 51 | -------------------------------------------------------------------------------- /sql/pathman_domains.sql: -------------------------------------------------------------------------------- 1 | \set VERBOSITY terse 2 | 3 | SET search_path = 'public'; 4 | CREATE EXTENSION pg_pathman; 5 | CREATE SCHEMA domains; 6 | 7 | CREATE DOMAIN domains.dom_test AS numeric CHECK (value < 1200); 8 | 9 | CREATE TABLE domains.dom_table(val domains.dom_test NOT NULL); 10 | INSERT INTO domains.dom_table SELECT generate_series(1, 999); 11 | 12 | SELECT create_range_partitions('domains.dom_table', 'val', 1, 100); 13 | 14 | EXPLAIN (COSTS OFF) 15 | SELECT * FROM domains.dom_table 16 | WHERE val < 250; 17 | 18 | INSERT INTO domains.dom_table VALUES(1500); 19 | INSERT INTO domains.dom_table VALUES(-10); 20 | 21 | SELECT append_range_partition('domains.dom_table'); 22 | SELECT prepend_range_partition('domains.dom_table'); 23 | SELECT merge_range_partitions('domains.dom_table_1', 'domains.dom_table_2'); 24 | SELECT split_range_partition('domains.dom_table_1', 50); 25 | 26 | INSERT INTO domains.dom_table VALUES(1101); 27 | 28 | EXPLAIN (COSTS OFF) 29 | SELECT * FROM domains.dom_table 30 | WHERE val < 450; 31 | 32 | 33 | SELECT * FROM pathman_partition_list 34 | ORDER BY range_min::INT, range_max::INT; 35 | 36 | 37 | SELECT drop_partitions('domains.dom_table'); 38 | SELECT create_hash_partitions('domains.dom_table', 'val', 5); 39 | 40 | SELECT * FROM pathman_partition_list 41 | ORDER BY "partition"::TEXT; 42 | 43 | 44 | DROP TABLE domains.dom_table CASCADE; 45 | DROP DOMAIN domains.dom_test CASCADE; 46 | DROP SCHEMA domains; 47 | DROP EXTENSION pg_pathman CASCADE; 48 | -------------------------------------------------------------------------------- /sql/pathman_dropped_cols.sql: -------------------------------------------------------------------------------- 1 | \set VERBOSITY terse 2 | 3 | SET search_path = 'public'; 4 | CREATE EXTENSION pg_pathman; 5 | CREATE SCHEMA dropped_cols; 6 | 7 | 8 | /* 9 | * we should be able to manage tables with dropped columns 10 | */ 11 | 12 | create table test_range(a int, b int, key int not null); 13 | 14 | alter table test_range drop column a; 15 | select create_range_partitions('test_range', 'key', 1, 10, 2); 16 | 17 | alter table test_range drop column b; 18 | select prepend_range_partition('test_range'); 19 | 20 | select * from pathman_partition_list order by parent, partition; 21 | select pg_get_constraintdef(oid, true) from pg_constraint where conname = 'pathman_test_range_1_check'; 22 | select pg_get_constraintdef(oid, true) from pg_constraint where conname = 'pathman_test_range_3_check'; 23 | 24 | drop table test_range cascade; 25 | 26 | 27 | create table test_hash(a int, b int, key int not null); 28 | 29 | alter table test_hash drop column a; 30 | select create_hash_partitions('test_hash', 'key', 3); 31 | 32 | alter table test_hash drop column b; 33 | create table test_dummy (like test_hash); 34 | select replace_hash_partition('test_hash_2', 'test_dummy', true); 35 | 36 | select * from pathman_partition_list order by parent, partition; 37 | select pg_get_constraintdef(oid, true) from pg_constraint where conname = 'pathman_test_hash_1_check'; 38 | select pg_get_constraintdef(oid, true) from pg_constraint where conname = 'pathman_test_dummy_check'; 39 | drop table test_hash cascade; 40 | 41 | -- Yury Smirnov case 42 | CREATE TABLE root_dict ( 43 | id BIGSERIAL PRIMARY KEY NOT NULL, 44 | root_id BIGINT NOT NULL, 45 | start_date DATE, 46 | num TEXT, 47 | main TEXT, 48 | dict_code TEXT, 49 | dict_name TEXT, 50 | edit_num TEXT, 51 | edit_date DATE, 52 | sign CHAR(4) 53 | ); 54 | 55 | CREATE INDEX "root_dict_root_id_idx" ON "root_dict" ("root_id"); 56 | 57 | DO 58 | $$ 59 | DECLARE 60 | r RECORD; 61 | BEGIN 62 | FOR r IN SELECT * FROM generate_series(1, 3) r 63 | LOOP 64 | FOR d IN 1..2 LOOP 65 | INSERT INTO root_dict (root_id, start_date, num, main, dict_code, dict_name, edit_num, edit_date, sign) VALUES 66 | (r.r, '2010-10-10'::date, 'num_' || d, (d % 2) + 1, 'code_' || d, 'name_' || d, NULL, NULL, '2014'); 67 | END LOOP; 68 | END LOOP; 69 | END 70 | $$; 71 | 72 | ALTER TABLE root_dict ADD COLUMN dict_id BIGINT DEFAULT 3; 73 | ALTER TABLE root_dict DROP COLUMN dict_code, 74 | DROP COLUMN dict_name, 75 | DROP COLUMN sign; 76 | 77 | SELECT create_hash_partitions('root_dict' :: REGCLASS, 78 | 'root_id', 79 | 3, 80 | true); 81 | VACUUM FULL ANALYZE "root_dict"; 82 | SELECT set_enable_parent('root_dict' :: REGCLASS, FALSE); 83 | 84 | PREPARE getbyroot AS 85 | SELECT 86 | id, root_id, start_date, num, main, edit_num, edit_date, dict_id 87 | FROM root_dict 88 | WHERE root_id = $1; 89 | 90 | EXECUTE getbyroot(2); 91 | EXECUTE getbyroot(2); 92 | EXECUTE getbyroot(2); 93 | EXECUTE getbyroot(2); 94 | EXECUTE getbyroot(2); 95 | 96 | -- errors usually start here 97 | EXECUTE getbyroot(2); 98 | EXECUTE getbyroot(2); 99 | EXPLAIN (COSTS OFF) EXECUTE getbyroot(2); 100 | 101 | DEALLOCATE getbyroot; 102 | DROP TABLE root_dict CASCADE; 103 | DROP SCHEMA dropped_cols; 104 | DROP EXTENSION pg_pathman; 105 | -------------------------------------------------------------------------------- /sql/pathman_foreign_keys.sql: -------------------------------------------------------------------------------- 1 | \set VERBOSITY terse 2 | 3 | SET search_path = 'public'; 4 | CREATE EXTENSION pg_pathman; 5 | CREATE SCHEMA fkeys; 6 | 7 | 8 | 9 | /* Check primary keys generation */ 10 | CREATE TABLE fkeys.test_ref(comment TEXT UNIQUE); 11 | INSERT INTO fkeys.test_ref VALUES('test'); 12 | 13 | CREATE TABLE fkeys.test_fkey( 14 | id INT NOT NULL, 15 | comment TEXT, 16 | FOREIGN KEY (comment) REFERENCES fkeys.test_ref(comment)); 17 | 18 | INSERT INTO fkeys.test_fkey SELECT generate_series(1, 1000), 'test'; 19 | 20 | SELECT create_range_partitions('fkeys.test_fkey', 'id', 1, 100); 21 | INSERT INTO fkeys.test_fkey VALUES(1, 'wrong'); 22 | INSERT INTO fkeys.test_fkey VALUES(1, 'test'); 23 | SELECT drop_partitions('fkeys.test_fkey'); 24 | 25 | SELECT create_hash_partitions('fkeys.test_fkey', 'id', 10); 26 | INSERT INTO fkeys.test_fkey VALUES(1, 'wrong'); 27 | INSERT INTO fkeys.test_fkey VALUES(1, 'test'); 28 | SELECT drop_partitions('fkeys.test_fkey'); 29 | 30 | 31 | /* Try to partition table that's being referenced */ 32 | CREATE TABLE fkeys.messages( 33 | id SERIAL PRIMARY KEY, 34 | msg TEXT); 35 | 36 | CREATE TABLE fkeys.replies( 37 | id SERIAL PRIMARY KEY, 38 | message_id INTEGER REFERENCES fkeys.messages(id), 39 | msg TEXT); 40 | 41 | INSERT INTO fkeys.messages SELECT g, md5(g::text) FROM generate_series(1, 10) as g; 42 | INSERT INTO fkeys.replies SELECT g, g, md5(g::text) FROM generate_series(1, 10) as g; 43 | 44 | SELECT create_range_partitions('fkeys.messages', 'id', 1, 100, 2); /* not ok */ 45 | 46 | ALTER TABLE fkeys.replies DROP CONSTRAINT replies_message_id_fkey; 47 | 48 | SELECT create_range_partitions('fkeys.messages', 'id', 1, 100, 2); /* ok */ 49 | EXPLAIN (COSTS OFF) SELECT * FROM fkeys.messages; 50 | 51 | DROP TABLE fkeys.messages, fkeys.replies CASCADE; 52 | 53 | 54 | 55 | DROP TABLE fkeys.test_fkey CASCADE; 56 | DROP TABLE fkeys.test_ref CASCADE; 57 | DROP SCHEMA fkeys; 58 | DROP EXTENSION pg_pathman CASCADE; 59 | -------------------------------------------------------------------------------- /sql/pathman_hashjoin.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * pathman_hashjoin_1.out and pathman_hashjoin_2.out seem to deal with pgpro's 3 | * different behaviour. 8edd0e794 (>= 12) Append nodes with single subplan 4 | * are eliminated, hence pathman_hashjoin_3.out 5 | * 6 | * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, 7 | * now it includes aliases for inherited tables. 8 | */ 9 | 10 | \set VERBOSITY terse 11 | 12 | SET search_path = 'public'; 13 | CREATE SCHEMA pathman; 14 | CREATE EXTENSION pg_pathman SCHEMA pathman; 15 | CREATE SCHEMA test; 16 | 17 | CREATE TABLE test.range_rel ( 18 | id SERIAL PRIMARY KEY, 19 | dt TIMESTAMP NOT NULL, 20 | txt TEXT); 21 | CREATE INDEX ON test.range_rel (dt); 22 | INSERT INTO test.range_rel (dt, txt) 23 | SELECT g, md5(g::TEXT) FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) as g; 24 | SELECT pathman.create_range_partitions('test.range_rel', 'DT', '2015-01-01'::DATE, '1 month'::INTERVAL); 25 | 26 | CREATE TABLE test.num_range_rel ( 27 | id SERIAL PRIMARY KEY, 28 | txt TEXT); 29 | SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); 30 | INSERT INTO test.num_range_rel 31 | SELECT g, md5(g::TEXT) FROM generate_series(1, 3000) as g; 32 | 33 | SET pg_pathman.enable_runtimeappend = OFF; 34 | SET pg_pathman.enable_runtimemergeappend = OFF; 35 | VACUUM; 36 | 37 | /* 38 | * Hash join 39 | */ 40 | SET enable_indexscan = ON; 41 | SET enable_seqscan = OFF; 42 | SET enable_nestloop = OFF; 43 | SET enable_hashjoin = ON; 44 | SET enable_mergejoin = OFF; 45 | 46 | EXPLAIN (COSTS OFF) 47 | SELECT * FROM test.range_rel j1 48 | JOIN test.range_rel j2 on j2.id = j1.id 49 | JOIN test.num_range_rel j3 on j3.id = j1.id 50 | WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; 51 | 52 | DROP TABLE test.num_range_rel CASCADE; 53 | DROP TABLE test.range_rel CASCADE; 54 | DROP SCHEMA test; 55 | DROP EXTENSION pg_pathman CASCADE; 56 | DROP SCHEMA pathman; 57 | -------------------------------------------------------------------------------- /sql/pathman_join_clause.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated, 3 | * causing different output; pathman_gaps_1.out is the updated version. 4 | */ 5 | \set VERBOSITY terse 6 | SET search_path = 'public'; 7 | CREATE SCHEMA pathman; 8 | CREATE EXTENSION pg_pathman SCHEMA pathman; 9 | CREATE SCHEMA test; 10 | 11 | 12 | 13 | /* 14 | * Test push down a join clause into child nodes of append 15 | */ 16 | 17 | /* create test tables */ 18 | CREATE TABLE test.fk ( 19 | id1 INT NOT NULL, 20 | id2 INT NOT NULL, 21 | start_key INT, 22 | end_key INT, 23 | PRIMARY KEY (id1, id2)); 24 | 25 | CREATE TABLE test.mytbl ( 26 | id1 INT NOT NULL, 27 | id2 INT NOT NULL, 28 | key INT NOT NULL, 29 | CONSTRAINT fk_fk FOREIGN KEY (id1, id2) REFERENCES test.fk(id1, id2), 30 | PRIMARY KEY (id1, key)); 31 | 32 | SELECT pathman.create_hash_partitions('test.mytbl', 'id1', 8); 33 | 34 | /* ...fill out with test data */ 35 | INSERT INTO test.fk VALUES (1, 1); 36 | INSERT INTO test.mytbl VALUES (1, 1, 5), (1, 1, 6); 37 | 38 | /* gather statistics on test tables to have deterministic plans */ 39 | ANALYZE; 40 | 41 | 42 | /* run test queries */ 43 | EXPLAIN (COSTS OFF) /* test plan */ 44 | SELECT m.tableoid::regclass, id1, id2, key, start_key, end_key 45 | FROM test.mytbl m JOIN test.fk USING(id1, id2) 46 | WHERE NOT key <@ int4range(6, end_key); 47 | 48 | /* test joint data */ 49 | SELECT m.tableoid::regclass, id1, id2, key, start_key, end_key 50 | FROM test.mytbl m JOIN test.fk USING(id1, id2) 51 | WHERE NOT key <@ int4range(6, end_key); 52 | 53 | 54 | 55 | /* 56 | * Test case by @dimarick 57 | */ 58 | 59 | CREATE TABLE test.parent ( 60 | id SERIAL NOT NULL, 61 | owner_id INTEGER NOT NULL 62 | ); 63 | 64 | CREATE TABLE test.child ( 65 | parent_id INTEGER NOT NULL, 66 | owner_id INTEGER NOT NULL 67 | ); 68 | 69 | CREATE TABLE test.child_nopart ( 70 | parent_id INTEGER NOT NULL, 71 | owner_id INTEGER NOT NULL 72 | ); 73 | 74 | INSERT INTO test.parent (owner_id) VALUES (1), (2), (3), (3); 75 | INSERT INTO test.child (parent_id, owner_id) VALUES (1, 1), (2, 2), (3, 3), (5, 3); 76 | INSERT INTO test.child_nopart (parent_id, owner_id) VALUES (1, 1), (2, 2), (3, 3), (5, 3); 77 | 78 | SELECT pathman.create_hash_partitions('test.child', 'owner_id', 2); 79 | 80 | /* gather statistics on test tables to have deterministic plans */ 81 | ANALYZE; 82 | 83 | 84 | /* Query #1 */ 85 | EXPLAIN (COSTS OFF) SELECT * FROM test.parent 86 | LEFT JOIN test.child ON test.child.parent_id = test.parent.id AND 87 | test.child.owner_id = test.parent.owner_id 88 | WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); 89 | 90 | SELECT * FROM test.parent 91 | LEFT JOIN test.child ON test.child.parent_id = test.parent.id AND 92 | test.child.owner_id = test.parent.owner_id 93 | WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); 94 | 95 | 96 | /* Query #2 */ 97 | EXPLAIN (COSTS OFF) SELECT * FROM test.parent 98 | LEFT JOIN test.child ON test.child.parent_id = test.parent.id AND 99 | test.child.owner_id = 3 100 | WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); 101 | 102 | SELECT * FROM test.parent 103 | LEFT JOIN test.child ON test.child.parent_id = test.parent.id AND 104 | test.child.owner_id = 3 105 | WHERE test.parent.owner_id = 3 and test.parent.id IN (3, 4); 106 | 107 | 108 | 109 | DROP TABLE test.child CASCADE; 110 | DROP TABLE test.child_nopart CASCADE; 111 | DROP TABLE test.mytbl CASCADE; 112 | DROP TABLE test.fk CASCADE; 113 | DROP TABLE test.parent CASCADE; 114 | DROP SCHEMA test; 115 | DROP EXTENSION pg_pathman CASCADE; 116 | DROP SCHEMA pathman; 117 | -------------------------------------------------------------------------------- /sql/pathman_lateral.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Sometimes join selectivity improvements patches in pgpro force nested loop 3 | * members swap -- in pathman_lateral_1.out and pathman_lateral_3.out 4 | * 5 | * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, 6 | * now it includes aliases for inherited tables. 7 | */ 8 | 9 | 10 | \set VERBOSITY terse 11 | 12 | SET search_path = 'public'; 13 | CREATE EXTENSION pg_pathman; 14 | CREATE SCHEMA test_lateral; 15 | 16 | 17 | /* create table partitioned by HASH */ 18 | create table test_lateral.data(id int8 not null); 19 | select create_hash_partitions('test_lateral.data', 'id', 10); 20 | insert into test_lateral.data select generate_series(1, 10000); 21 | 22 | 23 | VACUUM ANALYZE; 24 | 25 | 26 | set enable_hashjoin = off; 27 | set enable_mergejoin = off; 28 | 29 | 30 | /* all credits go to Ivan Frolkov */ 31 | explain (costs off) 32 | select * from 33 | test_lateral.data as t1, 34 | lateral(select * from test_lateral.data as t2 where t2.id > t1.id) t2, 35 | lateral(select * from test_lateral.data as t3 where t3.id = t2.id + t1.id) t3 36 | where t1.id between 1 and 100 and 37 | t2.id between 2 and 299 and 38 | t1.id > t2.id and 39 | exists(select * from test_lateral.data t 40 | where t1.id = t2.id and t.id = t3.id); 41 | 42 | 43 | set enable_hashjoin = on; 44 | set enable_mergejoin = on; 45 | 46 | 47 | 48 | DROP TABLE test_lateral.data CASCADE; 49 | DROP SCHEMA test_lateral; 50 | DROP EXTENSION pg_pathman; 51 | -------------------------------------------------------------------------------- /sql/pathman_mergejoin.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * pathman_mergejoin_1.out and pathman_mergejoin_2.out seem to deal with pgpro's 3 | * different behaviour. 8edd0e794 (>= 12) Append nodes with single subplan 4 | * are eliminated, hence pathman_mergejoin_3.out 5 | * 6 | * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed, 7 | * now it includes aliases for inherited tables. 8 | * 9 | * --------------------------------------------- 10 | * NOTE: This test behaves differenly on PgPro 11 | * --------------------------------------------- 12 | */ 13 | 14 | \set VERBOSITY terse 15 | 16 | SET search_path = 'public'; 17 | CREATE SCHEMA pathman; 18 | CREATE EXTENSION pg_pathman SCHEMA pathman; 19 | CREATE SCHEMA test; 20 | 21 | 22 | CREATE TABLE test.range_rel ( 23 | id SERIAL PRIMARY KEY, 24 | dt TIMESTAMP NOT NULL, 25 | txt TEXT); 26 | CREATE INDEX ON test.range_rel (dt); 27 | 28 | INSERT INTO test.range_rel (dt, txt) 29 | SELECT g, md5(g::TEXT) FROM generate_series('2015-01-01', '2015-04-30', '1 day'::interval) as g; 30 | 31 | SELECT pathman.create_range_partitions('test.range_rel', 'DT', '2015-01-01'::DATE, '1 month'::INTERVAL); 32 | 33 | CREATE TABLE test.num_range_rel ( 34 | id SERIAL PRIMARY KEY, 35 | txt TEXT); 36 | 37 | INSERT INTO test.num_range_rel SELECT g, md5(g::TEXT) FROM generate_series(1, 3000) as g; 38 | 39 | SELECT pathman.create_range_partitions('test.num_range_rel', 'id', 0, 1000, 4); 40 | 41 | /* 42 | * Merge join between 3 partitioned tables 43 | * 44 | * test case for the fix of sorting, merge append and index scan issues 45 | * details in commit 54dd0486fc55b2d25cf7d095f83dee6ff4adee06 46 | */ 47 | SET enable_hashjoin = OFF; 48 | SET enable_nestloop = OFF; 49 | SET enable_mergejoin = ON; 50 | 51 | SET enable_indexscan = ON; 52 | SET enable_seqscan = OFF; 53 | 54 | EXPLAIN (COSTS OFF) 55 | SELECT * FROM test.range_rel j1 56 | JOIN test.range_rel j2 on j2.id = j1.id 57 | JOIN test.num_range_rel j3 on j3.id = j1.id 58 | WHERE j1.dt < '2015-03-01' AND j2.dt >= '2015-02-01' ORDER BY j2.dt; 59 | 60 | SET enable_hashjoin = ON; 61 | SET enable_nestloop = ON; 62 | SET enable_seqscan = ON; 63 | 64 | DROP TABLE test.num_range_rel CASCADE; 65 | DROP TABLE test.range_rel CASCADE; 66 | DROP SCHEMA test; 67 | DROP EXTENSION pg_pathman; 68 | DROP SCHEMA pathman; 69 | -------------------------------------------------------------------------------- /sql/pathman_only.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * --------------------------------------------- 3 | * NOTE: This test behaves differenly on PgPro 4 | * --------------------------------------------- 5 | * 6 | * -------------------- 7 | * pathman_only_1.sql 8 | * -------------------- 9 | * Since 608b167f9f in PostgreSQL 12, CTEs which are scanned once are no longer 10 | * an optimization fence, which changes practically all plans here. There is 11 | * an option to forcibly make them MATERIALIZED, but we also need to run tests 12 | * on older versions, so create pathman_only_1.out instead. 13 | * 14 | * -------------------- 15 | * pathman_only_2.sql 16 | * -------------------- 17 | * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13, output of EXPLAIN was 18 | * changed, now it includes aliases for inherited tables. 19 | * 20 | * -------------------- 21 | * pathman_only_3.sql 22 | * -------------------- 23 | * Since a5fc46414de in PostgreSQL 16, the order of the operands was changed, 24 | * which affected the output of the "Prune by" in EXPLAIN. 25 | * 26 | * -------------------- 27 | * pathman_only_4.sql 28 | * -------------------- 29 | * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was 30 | * changed, now it displays SubPlan nodes and output parameters. 31 | */ 32 | 33 | \set VERBOSITY terse 34 | 35 | SET search_path = 'public'; 36 | CREATE EXTENSION pg_pathman; 37 | CREATE SCHEMA test_only; 38 | 39 | 40 | 41 | /* Test special case: ONLY statement with not-ONLY for partitioned table */ 42 | CREATE TABLE test_only.from_only_test(val INT NOT NULL); 43 | INSERT INTO test_only.from_only_test SELECT generate_series(1, 20); 44 | SELECT create_range_partitions('test_only.from_only_test', 'val', 1, 2); 45 | 46 | VACUUM ANALYZE; 47 | 48 | /* should be OK */ 49 | EXPLAIN (COSTS OFF) 50 | SELECT * FROM ONLY test_only.from_only_test 51 | UNION SELECT * FROM test_only.from_only_test; 52 | 53 | /* should be OK */ 54 | EXPLAIN (COSTS OFF) 55 | SELECT * FROM test_only.from_only_test 56 | UNION SELECT * FROM ONLY test_only.from_only_test; 57 | 58 | /* should be OK */ 59 | EXPLAIN (COSTS OFF) 60 | SELECT * FROM test_only.from_only_test 61 | UNION SELECT * FROM test_only.from_only_test 62 | UNION SELECT * FROM ONLY test_only.from_only_test; 63 | 64 | /* should be OK */ 65 | EXPLAIN (COSTS OFF) 66 | SELECT * FROM ONLY test_only.from_only_test 67 | UNION SELECT * FROM test_only.from_only_test 68 | UNION SELECT * FROM test_only.from_only_test; 69 | 70 | /* not ok, ONLY|non-ONLY in one query (this is not the case for PgPro) */ 71 | EXPLAIN (COSTS OFF) 72 | SELECT * FROM test_only.from_only_test a 73 | JOIN ONLY test_only.from_only_test b USING(val); 74 | 75 | /* should be OK */ 76 | EXPLAIN (COSTS OFF) 77 | WITH q1 AS (SELECT * FROM test_only.from_only_test), 78 | q2 AS (SELECT * FROM ONLY test_only.from_only_test) 79 | SELECT * FROM q1 JOIN q2 USING(val); 80 | 81 | /* should be OK */ 82 | EXPLAIN (COSTS OFF) 83 | WITH q1 AS (SELECT * FROM ONLY test_only.from_only_test) 84 | SELECT * FROM test_only.from_only_test JOIN q1 USING(val); 85 | 86 | /* should be OK */ 87 | EXPLAIN (COSTS OFF) 88 | SELECT * FROM test_only.from_only_test 89 | WHERE val = (SELECT val FROM ONLY test_only.from_only_test 90 | ORDER BY val ASC 91 | LIMIT 1); 92 | 93 | 94 | 95 | DROP TABLE test_only.from_only_test CASCADE; 96 | DROP SCHEMA test_only; 97 | DROP EXTENSION pg_pathman; 98 | -------------------------------------------------------------------------------- /sql/pathman_param_upd_del.sql: -------------------------------------------------------------------------------- 1 | \set VERBOSITY terse 2 | 3 | SET search_path = 'public'; 4 | CREATE EXTENSION pg_pathman; 5 | CREATE SCHEMA param_upd_del; 6 | 7 | 8 | CREATE TABLE param_upd_del.test(key INT4 NOT NULL, val INT4); 9 | SELECT create_hash_partitions('param_upd_del.test', 'key', 10); 10 | INSERT INTO param_upd_del.test SELECT i, i FROM generate_series(1, 1000) i; 11 | 12 | ANALYZE; 13 | 14 | 15 | PREPARE upd(INT4) AS UPDATE param_upd_del.test SET val = val + 1 WHERE key = $1; 16 | EXPLAIN (COSTS OFF) EXECUTE upd(10); 17 | EXPLAIN (COSTS OFF) EXECUTE upd(10); 18 | EXPLAIN (COSTS OFF) EXECUTE upd(10); 19 | EXPLAIN (COSTS OFF) EXECUTE upd(10); 20 | EXPLAIN (COSTS OFF) EXECUTE upd(10); 21 | EXPLAIN (COSTS OFF) EXECUTE upd(10); 22 | EXPLAIN (COSTS OFF) EXECUTE upd(11); 23 | DEALLOCATE upd; 24 | 25 | 26 | PREPARE upd(INT4) AS UPDATE param_upd_del.test SET val = val + 1 WHERE key = ($1 + 3) * 2; 27 | EXPLAIN (COSTS OFF) EXECUTE upd(5); 28 | EXPLAIN (COSTS OFF) EXECUTE upd(5); 29 | EXPLAIN (COSTS OFF) EXECUTE upd(5); 30 | EXPLAIN (COSTS OFF) EXECUTE upd(5); 31 | EXPLAIN (COSTS OFF) EXECUTE upd(5); 32 | EXPLAIN (COSTS OFF) EXECUTE upd(5); 33 | EXPLAIN (COSTS OFF) EXECUTE upd(6); 34 | DEALLOCATE upd; 35 | 36 | 37 | PREPARE del(INT4) AS DELETE FROM param_upd_del.test WHERE key = $1; 38 | EXPLAIN (COSTS OFF) EXECUTE del(10); 39 | EXPLAIN (COSTS OFF) EXECUTE del(10); 40 | EXPLAIN (COSTS OFF) EXECUTE del(10); 41 | EXPLAIN (COSTS OFF) EXECUTE del(10); 42 | EXPLAIN (COSTS OFF) EXECUTE del(10); 43 | EXPLAIN (COSTS OFF) EXECUTE del(10); 44 | EXPLAIN (COSTS OFF) EXECUTE del(11); 45 | DEALLOCATE del; 46 | 47 | 48 | DROP TABLE param_upd_del.test CASCADE; 49 | DROP SCHEMA param_upd_del; 50 | DROP EXTENSION pg_pathman; 51 | -------------------------------------------------------------------------------- /sql/pathman_rebuild_deletes.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------- 3 | * NOTE: This test behaves differenly on < 11 because planner now turns 4 | * Row(Const, Const) into just Const of record type, apparently since 3decd150 5 | * ------------------------------------------- 6 | */ 7 | 8 | \set VERBOSITY terse 9 | 10 | SET search_path = 'public'; 11 | CREATE EXTENSION pg_pathman; 12 | CREATE SCHEMA test_deletes; 13 | 14 | 15 | /* 16 | * Test DELETEs on a partition with different TupleDescriptor. 17 | */ 18 | 19 | /* create partitioned table */ 20 | CREATE TABLE test_deletes.test(a FLOAT4, val INT4 NOT NULL, b FLOAT8); 21 | INSERT INTO test_deletes.test SELECT i, i, i FROM generate_series(1, 100) AS i; 22 | SELECT create_range_partitions('test_deletes.test', 'val', 1, 10); 23 | 24 | /* drop column 'a' */ 25 | ALTER TABLE test_deletes.test DROP COLUMN a; 26 | 27 | /* append new partition */ 28 | SELECT append_range_partition('test_deletes.test'); 29 | INSERT INTO test_deletes.test_11 (val, b) VALUES (101, 10); 30 | 31 | 32 | VACUUM ANALYZE; 33 | 34 | 35 | /* tuple descs are the same */ 36 | EXPLAIN (COSTS OFF) DELETE FROM test_deletes.test WHERE val = 1; 37 | DELETE FROM test_deletes.test WHERE val = 1 RETURNING *, tableoid::REGCLASS; 38 | 39 | 40 | /* tuple descs are different */ 41 | EXPLAIN (COSTS OFF) DELETE FROM test_deletes.test WHERE val = 101; 42 | DELETE FROM test_deletes.test WHERE val = 101 RETURNING *, tableoid::REGCLASS; 43 | 44 | CREATE TABLE test_deletes.test_dummy (val INT4); 45 | 46 | EXPLAIN (COSTS OFF) DELETE FROM test_deletes.test 47 | WHERE val = 101 AND val = ANY (TABLE test_deletes.test_dummy) 48 | RETURNING *, tableoid::REGCLASS; 49 | 50 | EXPLAIN (COSTS OFF) DELETE FROM test_deletes.test t1 51 | USING test_deletes.test_dummy t2 52 | WHERE t1.val = 101 AND t1.val = t2.val 53 | RETURNING t1.*, t1.tableoid::REGCLASS; 54 | 55 | EXPLAIN (COSTS OFF) DELETE FROM test_deletes.test 56 | WHERE val = 101 AND test >= (100, 8) 57 | RETURNING *, tableoid::REGCLASS; 58 | 59 | DROP TABLE test_deletes.test_dummy; 60 | 61 | 62 | 63 | DROP TABLE test_deletes.test CASCADE; 64 | DROP SCHEMA test_deletes; 65 | DROP EXTENSION pg_pathman; 66 | -------------------------------------------------------------------------------- /sql/pathman_rebuild_updates.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------- 3 | * NOTE: This test behaves differenly on < 11 because planner now turns 4 | * Row(Const, Const) into just Const of record type, apparently since 3decd150 5 | * ------------------------------------------- 6 | */ 7 | 8 | \set VERBOSITY terse 9 | 10 | SET search_path = 'public'; 11 | CREATE EXTENSION pg_pathman; 12 | CREATE SCHEMA test_updates; 13 | 14 | 15 | /* 16 | * Test UPDATEs on a partition with different TupleDescriptor. 17 | */ 18 | 19 | /* create partitioned table */ 20 | CREATE TABLE test_updates.test(a FLOAT4, val INT4 NOT NULL, b FLOAT8); 21 | INSERT INTO test_updates.test SELECT i, i, i FROM generate_series(1, 100) AS i; 22 | SELECT create_range_partitions('test_updates.test', 'val', 1, 10); 23 | 24 | /* drop column 'a' */ 25 | ALTER TABLE test_updates.test DROP COLUMN a; 26 | 27 | /* append new partition */ 28 | SELECT append_range_partition('test_updates.test'); 29 | INSERT INTO test_updates.test_11 (val, b) VALUES (101, 10); 30 | 31 | 32 | VACUUM ANALYZE; 33 | 34 | 35 | /* tuple descs are the same */ 36 | EXPLAIN (COSTS OFF) UPDATE test_updates.test SET b = 0 WHERE val = 1; 37 | UPDATE test_updates.test SET b = 0 WHERE val = 1 RETURNING *, tableoid::REGCLASS; 38 | 39 | 40 | /* tuple descs are different */ 41 | EXPLAIN (COSTS OFF) UPDATE test_updates.test SET b = 0 WHERE val = 101; 42 | UPDATE test_updates.test SET b = 0 WHERE val = 101 RETURNING *, tableoid::REGCLASS; 43 | 44 | CREATE TABLE test_updates.test_dummy (val INT4); 45 | 46 | EXPLAIN (COSTS OFF) UPDATE test_updates.test SET val = val + 1 47 | WHERE val = 101 AND val = ANY (TABLE test_updates.test_dummy) 48 | RETURNING *, tableoid::REGCLASS; 49 | 50 | EXPLAIN (COSTS OFF) UPDATE test_updates.test t1 SET b = 0 51 | FROM test_updates.test_dummy t2 52 | WHERE t1.val = 101 AND t1.val = t2.val 53 | RETURNING t1.*, t1.tableoid::REGCLASS; 54 | 55 | EXPLAIN (COSTS OFF) UPDATE test_updates.test SET b = 0 56 | WHERE val = 101 AND test >= (100, 8) 57 | RETURNING *, tableoid::REGCLASS; 58 | 59 | /* execute this one */ 60 | UPDATE test_updates.test SET b = 0 61 | WHERE val = 101 AND test >= (100, -1) 62 | RETURNING test; 63 | 64 | DROP TABLE test_updates.test_dummy; 65 | 66 | 67 | /* cross-partition updates (& different tuple descs) */ 68 | TRUNCATE test_updates.test; 69 | SET pg_pathman.enable_partitionrouter = ON; 70 | 71 | SELECT *, (select count(*) from pg_attribute where attrelid = partition) as columns 72 | FROM pathman_partition_list 73 | ORDER BY range_min::int, range_max::int; 74 | 75 | INSERT INTO test_updates.test VALUES (105, 105); 76 | UPDATE test_updates.test SET val = 106 WHERE val = 105 RETURNING *, tableoid::REGCLASS; 77 | UPDATE test_updates.test SET val = 115 WHERE val = 106 RETURNING *, tableoid::REGCLASS; 78 | UPDATE test_updates.test SET val = 95 WHERE val = 115 RETURNING *, tableoid::REGCLASS; 79 | UPDATE test_updates.test SET val = -1 WHERE val = 95 RETURNING *, tableoid::REGCLASS; 80 | 81 | 82 | /* basic check for 'ALTER TABLE ... ADD COLUMN'; PGPRO-5113 */ 83 | create table test_updates.test_5113(val int4 not null); 84 | insert into test_updates.test_5113 values (1); 85 | select create_range_partitions('test_updates.test_5113', 'val', 1, 10); 86 | update test_updates.test_5113 set val = 11 where val = 1; 87 | alter table test_updates.test_5113 add column x varchar; 88 | /* no error here: */ 89 | select * from test_updates.test_5113 where val = 11; 90 | drop table test_updates.test_5113 cascade; 91 | 92 | create table test_updates.test_5113(val int4 not null); 93 | insert into test_updates.test_5113 values (1); 94 | select create_range_partitions('test_updates.test_5113', 'val', 1, 10); 95 | update test_updates.test_5113 set val = 11 where val = 1; 96 | alter table test_updates.test_5113 add column x int8; 97 | /* no extra data in column 'x' here: */ 98 | select * from test_updates.test_5113 where val = 11; 99 | drop table test_updates.test_5113 cascade; 100 | 101 | 102 | DROP TABLE test_updates.test CASCADE; 103 | DROP SCHEMA test_updates; 104 | DROP EXTENSION pg_pathman; 105 | -------------------------------------------------------------------------------- /sql/pathman_rowmarks.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------- 3 | * NOTE: This test behaves differenly on PgPro 4 | * ------------------------------------------- 5 | * 6 | * ------------------------ 7 | * pathman_rowmarks_1.sql 8 | * ------------------------ 9 | * Since PostgreSQL 9.5, output of EXPLAIN was changed. 10 | * 11 | * ------------------------ 12 | * pathman_rowmarks_2.sql 13 | * ------------------------ 14 | * Since 8edd0e794 in PostgreSQL 12, append nodes with single subplan are 15 | * eliminated, causing different output. 16 | * 17 | * ------------------------ 18 | * pathman_rowmarks_3.sql 19 | * ------------------------ 20 | * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13 output of EXPLAIN was 21 | * changed, now it includes aliases for inherited tables. 22 | * 23 | * ------------------------ 24 | * pathman_rowmarks_3.sql 25 | * ------------------------ 26 | * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was 27 | * changed, now it displays SubPlan nodes and output parameters. 28 | */ 29 | SET search_path = 'public'; 30 | CREATE EXTENSION pg_pathman; 31 | CREATE SCHEMA rowmarks; 32 | 33 | 34 | 35 | CREATE TABLE rowmarks.first(id int NOT NULL); 36 | CREATE TABLE rowmarks.second(id int NOT NULL); 37 | 38 | INSERT INTO rowmarks.first SELECT generate_series(1, 10); 39 | INSERT INTO rowmarks.second SELECT generate_series(1, 10); 40 | 41 | 42 | SELECT create_hash_partitions('rowmarks.first', 'id', 5); 43 | 44 | 45 | VACUUM ANALYZE; 46 | 47 | 48 | /* Not partitioned */ 49 | SELECT * FROM rowmarks.second ORDER BY id FOR UPDATE; 50 | 51 | /* Simple case (plan) */ 52 | EXPLAIN (COSTS OFF) 53 | SELECT * FROM rowmarks.first ORDER BY id FOR UPDATE; 54 | 55 | /* Simple case (execution) */ 56 | SELECT * FROM rowmarks.first ORDER BY id FOR UPDATE; 57 | SELECT FROM rowmarks.first ORDER BY id FOR UPDATE; 58 | SELECT tableoid > 0 FROM rowmarks.first ORDER BY id FOR UPDATE; 59 | 60 | /* A little harder (plan) */ 61 | EXPLAIN (COSTS OFF) 62 | SELECT * FROM rowmarks.first 63 | WHERE id = (SELECT id FROM rowmarks.first 64 | ORDER BY id 65 | OFFSET 10 LIMIT 1 66 | FOR UPDATE) 67 | FOR SHARE; 68 | 69 | /* A little harder (execution) */ 70 | SELECT * FROM rowmarks.first 71 | WHERE id = (SELECT id FROM rowmarks.first 72 | ORDER BY id 73 | OFFSET 5 LIMIT 1 74 | FOR UPDATE) 75 | FOR SHARE; 76 | 77 | /* Two tables (plan) */ 78 | EXPLAIN (COSTS OFF) 79 | SELECT * FROM rowmarks.first 80 | WHERE id = (SELECT id FROM rowmarks.second 81 | ORDER BY id 82 | OFFSET 5 LIMIT 1 83 | FOR UPDATE) 84 | FOR SHARE; 85 | 86 | /* Two tables (execution) */ 87 | SELECT * FROM rowmarks.first 88 | WHERE id = (SELECT id FROM rowmarks.second 89 | ORDER BY id 90 | OFFSET 5 LIMIT 1 91 | FOR UPDATE) 92 | FOR SHARE; 93 | 94 | /* JOIN (plan) */ 95 | EXPLAIN (COSTS OFF) 96 | SELECT * FROM rowmarks.first 97 | JOIN rowmarks.second USING(id) 98 | ORDER BY id 99 | FOR UPDATE; 100 | 101 | /* JOIN (execution) */ 102 | SELECT * FROM rowmarks.first 103 | JOIN rowmarks.second USING(id) 104 | ORDER BY id 105 | FOR UPDATE; 106 | 107 | /* ONLY (plan) */ 108 | EXPLAIN (COSTS OFF) 109 | SELECT * FROM ONLY rowmarks.first FOR SHARE; 110 | 111 | /* ONLY (execution) */ 112 | SELECT * FROM ONLY rowmarks.first FOR SHARE; 113 | 114 | /* Check updates (plan) */ 115 | SET enable_hashjoin = f; /* Hash Semi Join on 10 vs Hash Join on 9.6 */ 116 | SET enable_mergejoin = f; /* Merge Semi Join on 10 vs Merge Join on 9.6 */ 117 | EXPLAIN (COSTS OFF) 118 | UPDATE rowmarks.second SET id = 2 119 | WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1); 120 | EXPLAIN (COSTS OFF) 121 | UPDATE rowmarks.second SET id = 2 122 | WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id < 1); 123 | EXPLAIN (COSTS OFF) 124 | UPDATE rowmarks.second SET id = 2 125 | WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1 OR id = 2); 126 | EXPLAIN (COSTS OFF) 127 | UPDATE rowmarks.second SET id = 2 128 | WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1) 129 | RETURNING *, tableoid::regclass; 130 | SET enable_hashjoin = t; 131 | SET enable_mergejoin = t; 132 | 133 | /* Check updates (execution) */ 134 | UPDATE rowmarks.second SET id = 1 135 | WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1 OR id = 2) 136 | RETURNING *, tableoid::regclass; 137 | 138 | /* Check deletes (plan) */ 139 | SET enable_hashjoin = f; /* Hash Semi Join on 10 vs Hash Join on 9.6 */ 140 | SET enable_mergejoin = f; /* Merge Semi Join on 10 vs Merge Join on 9.6 */ 141 | EXPLAIN (COSTS OFF) 142 | DELETE FROM rowmarks.second 143 | WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1); 144 | EXPLAIN (COSTS OFF) 145 | DELETE FROM rowmarks.second 146 | WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id < 1); 147 | EXPLAIN (COSTS OFF) 148 | DELETE FROM rowmarks.second 149 | WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1 OR id = 2); 150 | SET enable_hashjoin = t; 151 | SET enable_mergejoin = t; 152 | 153 | 154 | 155 | DROP TABLE rowmarks.first CASCADE; 156 | DROP TABLE rowmarks.second CASCADE; 157 | DROP SCHEMA rowmarks; 158 | DROP EXTENSION pg_pathman; 159 | -------------------------------------------------------------------------------- /sql/pathman_views.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------- 3 | * NOTE: This test behaves differenly on 9.5 4 | * ------------------------------------------- 5 | * 6 | * Also since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated, 7 | * causing different output; pathman_views_2.out is the updated version. 8 | */ 9 | 10 | \set VERBOSITY terse 11 | 12 | SET search_path = 'public'; 13 | CREATE EXTENSION pg_pathman; 14 | CREATE SCHEMA views; 15 | 16 | 17 | 18 | /* create a partitioned table */ 19 | create table views._abc(id int4 not null); 20 | select create_hash_partitions('views._abc', 'id', 10); 21 | insert into views._abc select generate_series(1, 100); 22 | 23 | /* create a dummy table */ 24 | create table views._abc_add (like views._abc); 25 | 26 | 27 | vacuum analyze; 28 | 29 | 30 | /* create a facade view */ 31 | create view views.abc as select * from views._abc; 32 | 33 | create or replace function views.disable_modification() 34 | returns trigger as 35 | $$ 36 | BEGIN 37 | RAISE EXCEPTION '%', TG_OP; 38 | RETURN NULL; 39 | END; 40 | $$ 41 | language 'plpgsql'; 42 | 43 | create trigger abc_mod_tr 44 | instead of insert or update or delete 45 | on views.abc for each row 46 | execute procedure views.disable_modification(); 47 | 48 | 49 | /* Test SELECT */ 50 | explain (costs off) select * from views.abc; 51 | explain (costs off) select * from views.abc where id = 1; 52 | explain (costs off) select * from views.abc where id = 1 for update; 53 | select * from views.abc where id = 1 for update; 54 | select count (*) from views.abc; 55 | 56 | 57 | /* Test INSERT */ 58 | explain (costs off) insert into views.abc values (1); 59 | insert into views.abc values (1); 60 | 61 | 62 | /* Test UPDATE */ 63 | explain (costs off) update views.abc set id = 2 where id = 1 or id = 2; 64 | update views.abc set id = 2 where id = 1 or id = 2; 65 | 66 | 67 | /* Test DELETE */ 68 | explain (costs off) delete from views.abc where id = 1 or id = 2; 69 | delete from views.abc where id = 1 or id = 2; 70 | 71 | 72 | /* Test SELECT with UNION */ 73 | create view views.abc_union as table views._abc union table views._abc_add; 74 | create view views.abc_union_all as table views._abc union all table views._abc_add; 75 | explain (costs off) table views.abc_union; 76 | explain (costs off) select * from views.abc_union where id = 5; 77 | explain (costs off) table views.abc_union_all; 78 | explain (costs off) select * from views.abc_union_all where id = 5; 79 | 80 | 81 | 82 | DROP TABLE views._abc CASCADE; 83 | DROP TABLE views._abc_add CASCADE; 84 | DROP FUNCTION views.disable_modification(); 85 | DROP SCHEMA views; 86 | DROP EXTENSION pg_pathman; 87 | -------------------------------------------------------------------------------- /src/compat/rowmarks_fix.c: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------------ 2 | * 3 | * rowmarks_fix.h 4 | * Hack incorrect RowMark generation due to unset 'RTE->inh' flag 5 | * NOTE: this code is only useful for vanilla 6 | * 7 | * Copyright (c) 2017, Postgres Professional 8 | * 9 | * ------------------------------------------------------------------------ 10 | */ 11 | 12 | #include "compat/rowmarks_fix.h" 13 | #include "planner_tree_modification.h" 14 | 15 | #include "access/sysattr.h" 16 | #include "catalog/pg_type.h" 17 | #include "nodes/nodeFuncs.h" 18 | #include "optimizer/planmain.h" 19 | #include "utils/builtins.h" 20 | #include "utils/rel.h" 21 | 22 | 23 | #if PG_VERSION_NUM >= 90600 24 | 25 | 26 | /* Add missing "tableoid" column for partitioned table */ 27 | void 28 | append_tle_for_rowmark(PlannerInfo *root, PlanRowMark *rc) 29 | { 30 | Var *var; 31 | char resname[32]; 32 | TargetEntry *tle; 33 | 34 | var = makeVar(rc->rti, 35 | TableOidAttributeNumber, 36 | OIDOID, 37 | -1, 38 | InvalidOid, 39 | 0); 40 | 41 | snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); 42 | 43 | tle = makeTargetEntry((Expr *) var, 44 | list_length(root->processed_tlist) + 1, 45 | pstrdup(resname), 46 | true); 47 | 48 | root->processed_tlist = lappend(root->processed_tlist, tle); 49 | 50 | add_vars_to_targetlist_compat(root, list_make1(var), bms_make_singleton(0)); 51 | } 52 | 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /src/debug_print.c: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------------ 2 | * 3 | * debug_print.c 4 | * Print sophisticated structs as CSTRING 5 | * 6 | * Copyright (c) 2016, Postgres Professional 7 | * 8 | * ------------------------------------------------------------------------ 9 | */ 10 | 11 | #include 12 | #include "rangeset.h" 13 | 14 | #include "postgres.h" 15 | #include "fmgr.h" 16 | #include "executor/tuptable.h" 17 | #include "nodes/bitmapset.h" 18 | #include "nodes/parsenodes.h" 19 | #include "nodes/pg_list.h" 20 | #include "lib/stringinfo.h" 21 | #include "utils/lsyscache.h" 22 | 23 | 24 | /* 25 | * Print Bitmapset as cstring. 26 | */ 27 | #ifdef __GNUC__ 28 | __attribute__((unused)) 29 | #endif 30 | static char * 31 | bms_print(Bitmapset *bms) 32 | { 33 | StringInfoData str; 34 | int x; 35 | 36 | initStringInfo(&str); 37 | x = -1; 38 | while ((x = bms_next_member(bms, x)) >= 0) 39 | appendStringInfo(&str, " %d", x); 40 | 41 | return str.data; 42 | } 43 | 44 | /* 45 | * Print list of IndexRanges as cstring. 46 | */ 47 | #ifdef __GNUC__ 48 | __attribute__((unused)) 49 | #endif 50 | static char * 51 | rangeset_print(List *rangeset) 52 | { 53 | StringInfoData str; 54 | ListCell *lc; 55 | bool first_irange = true; 56 | char lossy = 'L', /* Lossy IndexRange */ 57 | complete = 'C'; /* Complete IndexRange */ 58 | 59 | initStringInfo(&str); 60 | 61 | foreach (lc, rangeset) 62 | { 63 | IndexRange irange = lfirst_irange(lc); 64 | 65 | /* Append comma if needed */ 66 | if (!first_irange) 67 | appendStringInfo(&str, ", "); 68 | 69 | if (!is_irange_valid(irange)) 70 | appendStringInfo(&str, "X"); 71 | else if (irange_lower(irange) == irange_upper(irange)) 72 | appendStringInfo(&str, "%u%c", 73 | irange_lower(irange), 74 | (is_irange_lossy(irange) ? lossy : complete)); 75 | else 76 | appendStringInfo(&str, "[%u-%u]%c", 77 | irange_lower(irange), irange_upper(irange), 78 | (is_irange_lossy(irange) ? lossy : complete)); 79 | 80 | first_irange = false; 81 | } 82 | 83 | return str.data; 84 | } 85 | 86 | /* 87 | * Print IndexRange struct as cstring. 88 | */ 89 | #ifdef __GNUC__ 90 | __attribute__((unused)) 91 | #endif 92 | static char * 93 | irange_print(IndexRange irange) 94 | { 95 | StringInfoData str; 96 | 97 | initStringInfo(&str); 98 | 99 | appendStringInfo(&str, "{ valid: %s, lossy: %s, lower: %u, upper: %u }", 100 | (is_irange_valid(irange) ? "true" : "false"), 101 | (is_irange_lossy(irange) ? "true" : "false"), 102 | irange_lower(irange), 103 | irange_upper(irange)); 104 | 105 | return str.data; 106 | } 107 | -------------------------------------------------------------------------------- /src/include/compat/debug_compat_features.h: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------------ 2 | * 3 | * debug_custom_features.h 4 | * Macros to control PgPro-related features etc 5 | * 6 | * Copyright (c) 2017, Postgres Professional 7 | * 8 | * ------------------------------------------------------------------------ 9 | */ 10 | 11 | /* Main toggle */ 12 | #define ENABLE_PGPRO_PATCHES 13 | 14 | /* PgPro exclusive features */ 15 | #define ENABLE_PATHMAN_AWARE_COPY_WIN32 16 | -------------------------------------------------------------------------------- /src/include/compat/rowmarks_fix.h: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------------ 2 | * 3 | * rowmarks_fix.h 4 | * Hack incorrect RowMark generation due to unset 'RTE->inh' flag 5 | * NOTE: this code is only useful for vanilla 6 | * 7 | * Copyright (c) 2017, Postgres Professional 8 | * 9 | * ------------------------------------------------------------------------ 10 | */ 11 | 12 | #ifndef ROWMARKS_FIX_H 13 | #define ROWMARKS_FIX_H 14 | 15 | #include "compat/debug_compat_features.h" 16 | 17 | #include "postgres.h" 18 | #include "nodes/parsenodes.h" 19 | #include "nodes/plannodes.h" 20 | #if PG_VERSION_NUM < 120000 21 | #include "nodes/relation.h" 22 | #else 23 | #include "optimizer/optimizer.h" 24 | #endif 25 | 26 | 27 | #if PG_VERSION_NUM >= 90600 28 | 29 | void append_tle_for_rowmark(PlannerInfo *root, PlanRowMark *rc); 30 | 31 | #else 32 | 33 | /* 34 | * Starting from 9.6, it's possible to append junk 35 | * tableoid columns using the PlannerInfo->processed_tlist. 36 | * This is absolutely crucial for UPDATE and DELETE queries, 37 | * so we had to add some special fixes for 9.5: 38 | * 39 | * 1) disable dangerous UPDATE & DELETE optimizations. 40 | * 2) disable optimizations for SELECT .. FOR UPDATE etc. 41 | */ 42 | #define LEGACY_ROWMARKS_95 43 | 44 | #define append_tle_for_rowmark(root, rc) ( (void) true ) 45 | 46 | #endif 47 | 48 | /* 49 | * add_vars_to_targetlist() 50 | * In >=16 last argument was removed (b3ff6c742f6c) 51 | */ 52 | #if PG_VERSION_NUM >= 160000 53 | #define add_vars_to_targetlist_compat(root, vars, where_needed) \ 54 | add_vars_to_targetlist((root), (vars), (where_needed)); 55 | #else 56 | #define add_vars_to_targetlist_compat(root, vars, where_needed) \ 57 | add_vars_to_targetlist((root), (vars), (where_needed), true); 58 | #endif 59 | 60 | 61 | #endif /* ROWMARKS_FIX_H */ 62 | -------------------------------------------------------------------------------- /src/include/declarative.h: -------------------------------------------------------------------------------- 1 | #ifndef DECLARATIVE_H 2 | #define DECLARATIVE_H 3 | 4 | #include "postgres.h" 5 | #include "nodes/nodes.h" 6 | #include "nodes/parsenodes.h" 7 | 8 | void modify_declarative_partitioning_query(Query *query); 9 | bool is_pathman_related_partitioning_cmd(Node *parsetree, Oid *parent_relid); 10 | 11 | /* actual actions */ 12 | void handle_attach_partition(Oid parent_relid, AlterTableCmd *cmd); 13 | void handle_detach_partition(AlterTableCmd *cmd); 14 | void handle_create_partition_of(Oid parent_relid, CreateStmt *stmt); 15 | 16 | #endif /* DECLARATIVE_H */ 17 | -------------------------------------------------------------------------------- /src/include/hooks.h: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------------ 2 | * 3 | * hooks.h 4 | * prototypes of rel_pathlist and join_pathlist hooks 5 | * 6 | * Copyright (c) 2016-2020, Postgres Professional 7 | * 8 | * ------------------------------------------------------------------------ 9 | */ 10 | 11 | #ifndef PATHMAN_HOOKS_H 12 | #define PATHMAN_HOOKS_H 13 | 14 | 15 | #include "postgres.h" 16 | #include "executor/executor.h" 17 | #include "optimizer/planner.h" 18 | #include "optimizer/paths.h" 19 | #include "parser/analyze.h" 20 | #include "storage/ipc.h" 21 | #include "tcop/utility.h" 22 | 23 | 24 | extern set_join_pathlist_hook_type pathman_set_join_pathlist_next; 25 | extern set_rel_pathlist_hook_type pathman_set_rel_pathlist_hook_next; 26 | extern planner_hook_type pathman_planner_hook_next; 27 | extern post_parse_analyze_hook_type pathman_post_parse_analyze_hook_next; 28 | extern shmem_startup_hook_type pathman_shmem_startup_hook_next; 29 | extern ProcessUtility_hook_type pathman_process_utility_hook_next; 30 | extern ExecutorRun_hook_type pathman_executor_run_hook_next; 31 | extern ExecutorStart_hook_type pathman_executor_start_hook_prev; 32 | 33 | 34 | void pathman_join_pathlist_hook(PlannerInfo *root, 35 | RelOptInfo *joinrel, 36 | RelOptInfo *outerrel, 37 | RelOptInfo *innerrel, 38 | JoinType jointype, 39 | JoinPathExtraData *extra); 40 | 41 | void pathman_rel_pathlist_hook(PlannerInfo *root, 42 | RelOptInfo *rel, 43 | Index rti, 44 | RangeTblEntry *rte); 45 | 46 | void pathman_enable_assign_hook(bool newval, void *extra); 47 | bool pathman_enable_check_hook(bool *newval, void **extra, GucSource source); 48 | 49 | PlannedStmt * pathman_planner_hook(Query *parse, 50 | #if PG_VERSION_NUM >= 130000 51 | const char *query_string, 52 | #endif 53 | int cursorOptions, 54 | ParamListInfo boundParams); 55 | 56 | #if PG_VERSION_NUM >= 140000 57 | void pathman_post_parse_analyze_hook(ParseState *pstate, 58 | Query *query, 59 | JumbleState *jstate); 60 | #else 61 | void pathman_post_parse_analyze_hook(ParseState *pstate, 62 | Query *query); 63 | #endif 64 | 65 | void pathman_shmem_startup_hook(void); 66 | 67 | void pathman_relcache_hook(Datum arg, Oid relid); 68 | 69 | #if PG_VERSION_NUM >= 140000 70 | void pathman_process_utility_hook(PlannedStmt *pstmt, 71 | const char *queryString, 72 | bool readOnlyTree, 73 | ProcessUtilityContext context, 74 | ParamListInfo params, 75 | QueryEnvironment *queryEnv, 76 | DestReceiver *dest, 77 | QueryCompletion *qc); 78 | #elif PG_VERSION_NUM >= 130000 79 | void pathman_process_utility_hook(PlannedStmt *pstmt, 80 | const char *queryString, 81 | ProcessUtilityContext context, 82 | ParamListInfo params, 83 | QueryEnvironment *queryEnv, 84 | DestReceiver *dest, 85 | QueryCompletion *qc); 86 | #elif PG_VERSION_NUM >= 100000 87 | void pathman_process_utility_hook(PlannedStmt *pstmt, 88 | const char *queryString, 89 | ProcessUtilityContext context, 90 | ParamListInfo params, 91 | QueryEnvironment *queryEnv, 92 | DestReceiver *dest, 93 | char *completionTag); 94 | #else 95 | void pathman_process_utility_hook(Node *parsetree, 96 | const char *queryString, 97 | ProcessUtilityContext context, 98 | ParamListInfo params, 99 | DestReceiver *dest, 100 | char *completionTag); 101 | #endif 102 | 103 | #if PG_VERSION_NUM >= 90600 104 | typedef uint64 ExecutorRun_CountArgType; 105 | #else 106 | typedef long ExecutorRun_CountArgType; 107 | #endif 108 | 109 | #if PG_VERSION_NUM >= 100000 110 | void pathman_executor_hook(QueryDesc *queryDesc, 111 | ScanDirection direction, 112 | ExecutorRun_CountArgType count, 113 | bool execute_once); 114 | #else 115 | void pathman_executor_hook(QueryDesc *queryDesc, 116 | ScanDirection direction, 117 | ExecutorRun_CountArgType count); 118 | #endif 119 | 120 | void pathman_executor_start_hook(QueryDesc *queryDescc, 121 | int eflags); 122 | #endif /* PATHMAN_HOOKS_H */ 123 | -------------------------------------------------------------------------------- /src/include/nodes_common.h: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------------ 2 | * 3 | * nodes_common.h 4 | * Common function prototypes and structs for custom nodes 5 | * 6 | * Copyright (c) 2016, Postgres Professional 7 | * 8 | * ------------------------------------------------------------------------ 9 | */ 10 | 11 | #ifndef NODES_COMMON_H 12 | #define NODES_COMMON_H 13 | 14 | 15 | #include "relation_info.h" 16 | 17 | #include "postgres.h" 18 | #include "commands/explain.h" 19 | #include "optimizer/planner.h" 20 | 21 | #if PG_VERSION_NUM >= 90600 22 | #include "nodes/extensible.h" 23 | #endif 24 | 25 | 26 | /* 27 | * Common structure for storing selected 28 | * Paths/Plans/PlanStates in a hash table 29 | * or its slice. 30 | */ 31 | typedef struct 32 | { 33 | Oid relid; /* partition relid */ 34 | 35 | enum 36 | { 37 | CHILD_PATH = 0, 38 | CHILD_PLAN, 39 | CHILD_PLAN_STATE 40 | } content_type; 41 | 42 | union 43 | { 44 | Path *path; 45 | Plan *plan; 46 | PlanState *plan_state; 47 | } content; 48 | 49 | int original_order; /* for sorting in EXPLAIN */ 50 | } ChildScanCommonData; 51 | 52 | typedef ChildScanCommonData *ChildScanCommon; 53 | 54 | /* 55 | * Destroy exhausted plan states 56 | */ 57 | static inline void 58 | clear_plan_states(CustomScanState *scan_state) 59 | { 60 | ListCell *state_cell; 61 | 62 | foreach (state_cell, scan_state->custom_ps) 63 | { 64 | ExecEndNode((PlanState *) lfirst(state_cell)); 65 | } 66 | } 67 | 68 | List * get_partitioning_clauses(List *restrictinfo_list, 69 | const PartRelationInfo *prel, 70 | Index partitioned_rel); 71 | 72 | Oid * get_partition_oids(List *ranges, int *n, const PartRelationInfo *prel, 73 | bool include_parent); 74 | 75 | Path * create_append_path_common(PlannerInfo *root, 76 | AppendPath *inner_append, 77 | ParamPathInfo *param_info, 78 | CustomPathMethods *path_methods, 79 | uint32 size, 80 | double sel); 81 | 82 | Plan * create_append_plan_common(PlannerInfo *root, RelOptInfo *rel, 83 | CustomPath *best_path, List *tlist, 84 | List *clauses, List *custom_plans, 85 | CustomScanMethods *scan_methods); 86 | 87 | Node * create_append_scan_state_common(CustomScan *node, 88 | CustomExecMethods *exec_methods, 89 | uint32 size); 90 | 91 | void begin_append_common(CustomScanState *node, EState *estate, int eflags); 92 | 93 | TupleTableSlot * exec_append_common(CustomScanState *node, 94 | void (*fetch_next_tuple) (CustomScanState *node)); 95 | 96 | void end_append_common(CustomScanState *node); 97 | 98 | void rescan_append_common(CustomScanState *node); 99 | 100 | void explain_append_common(CustomScanState *node, 101 | List *ancestors, 102 | ExplainState *es, 103 | HTAB *children_table, 104 | List *custom_exprs); 105 | 106 | 107 | #endif /* NODES_COMMON_H */ 108 | -------------------------------------------------------------------------------- /src/include/partition_creation.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * partition_creation.h 4 | * Various functions for partition creation. 5 | * 6 | * Copyright (c) 2016, Postgres Professional 7 | * 8 | *------------------------------------------------------------------------- 9 | */ 10 | 11 | #ifndef PARTITION_CREATION_H 12 | #define PARTITION_CREATION_H 13 | 14 | 15 | #include "relation_info.h" 16 | 17 | #include "postgres.h" 18 | #include "nodes/parsenodes.h" 19 | 20 | 21 | /* ACL privilege for partition creation */ 22 | #define ACL_SPAWN_PARTITIONS ACL_INSERT 23 | 24 | 25 | /* Create RANGE partitions to store some value */ 26 | Oid create_partitions_for_value(Oid relid, Datum value, Oid value_type); 27 | Oid create_partitions_for_value_internal(Oid relid, Datum value, Oid value_type); 28 | 29 | 30 | /* Create one RANGE partition */ 31 | Oid create_single_range_partition_internal(Oid parent_relid, 32 | const Bound *start_value, 33 | const Bound *end_value, 34 | Oid value_type, 35 | RangeVar *partition_rv, 36 | char *tablespace); 37 | 38 | /* Create one HASH partition */ 39 | Oid create_single_hash_partition_internal(Oid parent_relid, 40 | uint32 part_idx, 41 | uint32 part_count, 42 | RangeVar *partition_rv, 43 | char *tablespace); 44 | 45 | 46 | /* RANGE constraints */ 47 | Constraint * build_range_check_constraint(Oid child_relid, 48 | Node *raw_expression, 49 | const Bound *start_value, 50 | const Bound *end_value, 51 | Oid expr_type); 52 | 53 | Node * build_raw_range_check_tree(Node *raw_expression, 54 | const Bound *start_value, 55 | const Bound *end_value, 56 | Oid value_type); 57 | 58 | bool check_range_available(Oid parent_relid, 59 | const Bound *start_value, 60 | const Bound *end_value, 61 | Oid value_type, 62 | bool raise_error); 63 | 64 | 65 | /* HASH constraints */ 66 | Constraint * build_hash_check_constraint(Oid child_relid, 67 | Node *raw_expression, 68 | uint32 part_idx, 69 | uint32 part_count, 70 | Oid value_type); 71 | 72 | Node * build_raw_hash_check_tree(Node *raw_expression, 73 | uint32 part_idx, 74 | uint32 part_count, 75 | Oid relid, 76 | Oid value_type); 77 | 78 | /* Add & drop pg_pathman's check constraint */ 79 | void drop_pathman_check_constraint(Oid relid); 80 | void add_pathman_check_constraint(Oid relid, Constraint *constraint); 81 | 82 | 83 | /* Partitioning callback type */ 84 | typedef enum 85 | { 86 | PT_INIT_CALLBACK = 0 87 | } part_callback_type; 88 | 89 | /* Args for partitioning 'init_callback' */ 90 | typedef struct 91 | { 92 | part_callback_type cb_type; 93 | Oid callback; 94 | bool callback_is_cached; 95 | 96 | PartType parttype; 97 | 98 | Oid parent_relid; 99 | Oid partition_relid; 100 | 101 | union 102 | { 103 | struct 104 | { 105 | void *none; /* nothing (struct should have at least 1 element) */ 106 | } hash_params; 107 | 108 | struct 109 | { 110 | Bound start_value, 111 | end_value; 112 | Oid value_type; 113 | } range_params; 114 | 115 | } params; 116 | } init_callback_params; 117 | 118 | 119 | #define MakeInitCallbackRangeParams(params_p, cb, parent, child, start, end, type) \ 120 | do \ 121 | { \ 122 | memset((void *) (params_p), 0, sizeof(init_callback_params)); \ 123 | (params_p)->cb_type = PT_INIT_CALLBACK; \ 124 | (params_p)->callback = (cb); \ 125 | (params_p)->callback_is_cached = false; \ 126 | (params_p)->parttype = PT_RANGE; \ 127 | (params_p)->parent_relid = (parent); \ 128 | (params_p)->partition_relid = (child); \ 129 | (params_p)->params.range_params.start_value = (start); \ 130 | (params_p)->params.range_params.end_value = (end); \ 131 | (params_p)->params.range_params.value_type = (type); \ 132 | } while (0) 133 | 134 | #define MakeInitCallbackHashParams(params_p, cb, parent, child) \ 135 | do \ 136 | { \ 137 | memset((void *) (params_p), 0, sizeof(init_callback_params)); \ 138 | (params_p)->cb_type = PT_INIT_CALLBACK; \ 139 | (params_p)->callback = (cb); \ 140 | (params_p)->callback_is_cached = false; \ 141 | (params_p)->parttype = PT_HASH; \ 142 | (params_p)->parent_relid = (parent); \ 143 | (params_p)->partition_relid = (child); \ 144 | } while (0) 145 | 146 | 147 | void invoke_part_callback(init_callback_params *cb_params); 148 | bool validate_part_callback(Oid procid, bool emit_error); 149 | 150 | #endif /* PARTITION_CREATION_H */ 151 | -------------------------------------------------------------------------------- /src/include/partition_overseer.h: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------------ 2 | * 3 | * partition_overseer.h 4 | * Restart ModifyTable for unobvious reasons 5 | * 6 | * Copyright (c) 2018, Postgres Professional 7 | * 8 | * ------------------------------------------------------------------------ 9 | */ 10 | 11 | #ifndef PARTITION_OVERSEER_H 12 | #define PARTITION_OVERSEER_H 13 | 14 | #include "relation_info.h" 15 | #include "utils.h" 16 | 17 | #include "postgres.h" 18 | #include "access/tupconvert.h" 19 | #include "commands/explain.h" 20 | #include "optimizer/planner.h" 21 | 22 | #if PG_VERSION_NUM >= 90600 23 | #include "nodes/extensible.h" 24 | #endif 25 | 26 | 27 | #define OVERSEER_NODE_NAME "PartitionOverseer" 28 | 29 | 30 | extern CustomScanMethods partition_overseer_plan_methods; 31 | extern CustomExecMethods partition_overseer_exec_methods; 32 | 33 | 34 | void init_partition_overseer_static_data(void); 35 | Plan *make_partition_overseer(Plan *subplan); 36 | 37 | Node *partition_overseer_create_scan_state(CustomScan *node); 38 | 39 | void partition_overseer_begin(CustomScanState *node, 40 | EState *estate, 41 | int eflags); 42 | 43 | TupleTableSlot *partition_overseer_exec(CustomScanState *node); 44 | 45 | void partition_overseer_end(CustomScanState *node); 46 | 47 | void partition_overseer_rescan(CustomScanState *node); 48 | 49 | void partition_overseer_explain(CustomScanState *node, 50 | List *ancestors, 51 | ExplainState *es); 52 | 53 | 54 | #endif /* PARTITION_OVERSEER_H */ 55 | -------------------------------------------------------------------------------- /src/include/partition_router.h: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------------ 2 | * 3 | * partition_update.h 4 | * Insert row to right partition in UPDATE operation 5 | * 6 | * Copyright (c) 2017, Postgres Professional 7 | * 8 | * ------------------------------------------------------------------------ 9 | */ 10 | 11 | #ifndef PARTITION_UPDATE_H 12 | #define PARTITION_UPDATE_H 13 | 14 | #include "relation_info.h" 15 | #include "utils.h" 16 | 17 | #include "postgres.h" 18 | #include "commands/explain.h" 19 | #include "optimizer/planner.h" 20 | 21 | #if PG_VERSION_NUM >= 90600 22 | #include "nodes/extensible.h" 23 | #endif 24 | 25 | 26 | #define UPDATE_NODE_NAME "PartitionRouter" 27 | 28 | 29 | typedef struct PartitionRouterState 30 | { 31 | CustomScanState css; 32 | 33 | Plan *subplan; /* proxy variable to store subplan */ 34 | ExprState *constraint; /* should tuple remain in partition? */ 35 | #if PG_VERSION_NUM < 140000 /* field removed in 86dc90056dfd */ 36 | JunkFilter *junkfilter; /* 'ctid' extraction facility */ 37 | #endif 38 | ResultRelInfo *current_rri; 39 | 40 | /* Machinery required for EvalPlanQual */ 41 | EPQState epqstate; 42 | int epqparam; 43 | 44 | /* Preserved slot from last call */ 45 | bool yielded; 46 | TupleTableSlot *yielded_slot; 47 | #if PG_VERSION_NUM >= 140000 48 | TupleTableSlot *yielded_original_slot; 49 | #endif 50 | 51 | /* Need these for a GREAT deal of hackery */ 52 | ModifyTableState *mt_state; 53 | bool update_stmt_triggers, 54 | insert_stmt_triggers; 55 | } PartitionRouterState; 56 | 57 | 58 | extern bool pg_pathman_enable_partition_router; 59 | 60 | extern CustomScanMethods partition_router_plan_methods; 61 | extern CustomExecMethods partition_router_exec_methods; 62 | 63 | 64 | #define IsPartitionRouterState(node) \ 65 | ( \ 66 | IsA((node), CustomScanState) && \ 67 | (((CustomScanState *) (node))->methods == &partition_router_exec_methods) \ 68 | ) 69 | 70 | /* Highlight hacks with ModifyTable's fields */ 71 | #define MTHackField(mt_state, field) ( (mt_state)->field ) 72 | 73 | void init_partition_router_static_data(void); 74 | void partition_router_begin(CustomScanState *node, EState *estate, int eflags); 75 | void partition_router_end(CustomScanState *node); 76 | void partition_router_rescan(CustomScanState *node); 77 | void partition_router_explain(CustomScanState *node, 78 | List *ancestors, 79 | ExplainState *es); 80 | 81 | Plan *make_partition_router(Plan *subplan, int epq_param, Index parent_rti); 82 | Node *partition_router_create_scan_state(CustomScan *node); 83 | TupleTableSlot *partition_router_exec(CustomScanState *node); 84 | 85 | #endif /* PARTITION_UPDATE_H */ 86 | -------------------------------------------------------------------------------- /src/include/planner_tree_modification.h: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------------ 2 | * 3 | * planner_tree_modification.h 4 | * Functions for query- and plan- tree modification 5 | * 6 | * Copyright (c) 2016, Postgres Professional 7 | * 8 | * ------------------------------------------------------------------------ 9 | */ 10 | 11 | #ifndef PLANNER_TREE_MODIFICATION_H 12 | #define PLANNER_TREE_MODIFICATION_H 13 | 14 | 15 | #include "pathman.h" 16 | 17 | #include "postgres.h" 18 | #include "utils/rel.h" 19 | /* #include "nodes/relation.h" */ 20 | #include "nodes/nodeFuncs.h" 21 | 22 | 23 | /* Query ID generator */ 24 | void assign_query_id(Query *query); 25 | void reset_query_id_generator(void); 26 | 27 | /* Plan tree rewriting utility */ 28 | Plan * plan_tree_visitor(Plan *plan, 29 | Plan *(*visitor) (Plan *plan, void *context), 30 | void *context); 31 | 32 | /* PlanState tree rewriting utility */ 33 | void state_tree_visitor(PlanState *state, 34 | void (*visitor) (PlanState *state, void *context), 35 | void *context); 36 | 37 | /* Query tree rewriting utilities */ 38 | void pathman_transform_query(Query *parse, ParamListInfo params); 39 | void pathman_post_analyze_query(Query *parse); 40 | 41 | /* These functions scribble on Plan tree */ 42 | Plan *add_partition_filters(List *rtable, Plan *plan); 43 | Plan *add_partition_routers(List *rtable, Plan *plan); 44 | 45 | 46 | /* used by assign_rel_parenthood_status() etc */ 47 | typedef enum 48 | { 49 | PARENTHOOD_NOT_SET = 0, /* relation hasn't been tracked */ 50 | PARENTHOOD_DISALLOWED, /* children are disabled (e.g. ONLY) */ 51 | PARENTHOOD_ALLOWED /* children are enabled (default) */ 52 | } rel_parenthood_status; 53 | 54 | void assign_rel_parenthood_status(RangeTblEntry *rte, 55 | rel_parenthood_status new_status); 56 | 57 | rel_parenthood_status get_rel_parenthood_status(RangeTblEntry *rte); 58 | 59 | 60 | /* used to determine nested planner() calls */ 61 | void incr_planner_calls_count(void); 62 | void decr_planner_calls_count(void); 63 | int32 get_planner_calls_count(void); 64 | 65 | 66 | #endif /* PLANNER_TREE_MODIFICATION_H */ 67 | -------------------------------------------------------------------------------- /src/include/rangeset.h: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------------ 2 | * 3 | * rangeset.h 4 | * 5 | * Copyright (c) 2015-2016, Postgres Professional 6 | * 7 | * ------------------------------------------------------------------------ 8 | */ 9 | 10 | #ifndef PATHMAN_RANGESET_H 11 | #define PATHMAN_RANGESET_H 12 | 13 | 14 | #include "postgres.h" 15 | #include "nodes/pg_list.h" 16 | 17 | 18 | /* 19 | * IndexRange is essentially a segment [lower; upper]. This module provides 20 | * functions for efficient working (intersection, union) with Lists of 21 | * IndexRange's; this is used for quick selection of partitions. Numbers are 22 | * indexes of partitions in PartRelationInfo's children. 23 | */ 24 | typedef struct { 25 | /* lossy == should we use quals? */ 26 | /* valid == is this IndexRange valid? */ 27 | 28 | /* Don't swap these fields */ 29 | uint32 lower; /* valid + lower_bound */ 30 | uint32 upper; /* lossy + upper_bound */ 31 | } IndexRange; 32 | 33 | /* Convenience macros for make_irange(...) */ 34 | #define IR_LOSSY true 35 | #define IR_COMPLETE false 36 | 37 | #define IRANGE_SPECIAL_BIT ( (uint32) ( ((uint32) 1) << 31) ) 38 | #define IRANGE_BOUNDARY_MASK ( (uint32) (~IRANGE_SPECIAL_BIT) ) 39 | 40 | #define InvalidIndexRange { 0, 0 } 41 | 42 | #define is_irange_valid(irange) ( (irange.lower & IRANGE_SPECIAL_BIT) > 0 ) 43 | #define is_irange_lossy(irange) ( (irange.upper & IRANGE_SPECIAL_BIT) > 0 ) 44 | #define irange_lower(irange) ( (uint32) (irange.lower & IRANGE_BOUNDARY_MASK) ) 45 | #define irange_upper(irange) ( (uint32) (irange.upper & IRANGE_BOUNDARY_MASK) ) 46 | 47 | #define lfirst_irange(lc) ( *(IndexRange *) lfirst(lc) ) 48 | #define lappend_irange(list, irange) ( lappend((list), alloc_irange(irange)) ) 49 | #define lcons_irange(irange, list) ( lcons(alloc_irange(irange), (list)) ) 50 | #define list_make1_irange(irange) ( lcons_irange(irange, NIL) ) 51 | #define llast_irange(list) ( lfirst_irange(list_tail(list)) ) 52 | #define linitial_irange(list) ( lfirst_irange(list_head(list)) ) 53 | 54 | 55 | /* convenience macro (requires relation_info.h) */ 56 | #define list_make1_irange_full(prel, lossy) \ 57 | ( list_make1_irange(make_irange(0, PrelLastChild(prel), (lossy))) ) 58 | 59 | 60 | static inline IndexRange 61 | make_irange(uint32 lower, uint32 upper, bool lossy) 62 | { 63 | IndexRange result = { lower & IRANGE_BOUNDARY_MASK, 64 | upper & IRANGE_BOUNDARY_MASK }; 65 | 66 | /* Set VALID */ 67 | result.lower |= IRANGE_SPECIAL_BIT; 68 | 69 | /* Set LOSSY if needed */ 70 | if (lossy) result.upper |= IRANGE_SPECIAL_BIT; 71 | 72 | Assert(lower <= upper); 73 | 74 | return result; 75 | } 76 | 77 | static inline IndexRange * 78 | alloc_irange(IndexRange irange) 79 | { 80 | IndexRange *result = (IndexRange *) palloc(sizeof(IndexRange)); 81 | 82 | /* Copy all fields of IndexRange */ 83 | *result = irange; 84 | 85 | return result; 86 | } 87 | 88 | /* Return predecessor or 0 if boundary is 0 */ 89 | static inline uint32 90 | irb_pred(uint32 boundary) 91 | { 92 | if (boundary > 0) 93 | return (boundary - 1) & IRANGE_BOUNDARY_MASK; 94 | 95 | return 0; 96 | } 97 | 98 | /* Return successor or IRANGE_BONDARY_MASK */ 99 | static inline uint32 100 | irb_succ(uint32 boundary) 101 | { 102 | if (boundary >= IRANGE_BOUNDARY_MASK) 103 | return IRANGE_BOUNDARY_MASK; 104 | 105 | return boundary + 1; 106 | } 107 | 108 | 109 | /* Result of function irange_cmp_lossiness() */ 110 | typedef enum 111 | { 112 | IR_EQ_LOSSINESS = 0, /* IndexRanges share same lossiness */ 113 | IR_A_LOSSY, /* IndexRange 'a' is lossy ('b' is not) */ 114 | IR_B_LOSSY /* IndexRange 'b' is lossy ('a' is not) */ 115 | } ir_cmp_lossiness; 116 | 117 | /* Comapre lossiness factor of two IndexRanges */ 118 | static inline ir_cmp_lossiness 119 | irange_cmp_lossiness(IndexRange a, IndexRange b) 120 | { 121 | if (is_irange_lossy(a) == is_irange_lossy(b)) 122 | return IR_EQ_LOSSINESS; 123 | 124 | if (is_irange_lossy(a)) 125 | return IR_A_LOSSY; 126 | 127 | if (is_irange_lossy(b)) 128 | return IR_B_LOSSY; 129 | 130 | return IR_EQ_LOSSINESS; 131 | } 132 | 133 | 134 | /* Check if two ranges intersect */ 135 | static inline bool 136 | iranges_intersect(IndexRange a, IndexRange b) 137 | { 138 | return (irange_lower(a) <= irange_upper(b)) && 139 | (irange_lower(b) <= irange_upper(a)); 140 | } 141 | 142 | /* Check if two ranges adjoin */ 143 | static inline bool 144 | iranges_adjoin(IndexRange a, IndexRange b) 145 | { 146 | return (irange_upper(a) == irb_pred(irange_lower(b))) || 147 | (irange_upper(b) == irb_pred(irange_lower(a))); 148 | } 149 | 150 | /* Check if two ranges cover the same area */ 151 | static inline bool 152 | irange_eq_bounds(IndexRange a, IndexRange b) 153 | { 154 | return (irange_lower(a) == irange_lower(b)) && 155 | (irange_upper(a) == irange_upper(b)); 156 | } 157 | 158 | 159 | /* Basic operations on IndexRanges */ 160 | IndexRange irange_union_simple(IndexRange a, IndexRange b); 161 | IndexRange irange_intersection_simple(IndexRange a, IndexRange b); 162 | 163 | /* Operations on Lists of IndexRanges */ 164 | List *irange_list_union(List *a, List *b); 165 | List *irange_list_intersection(List *a, List *b); 166 | List *irange_list_set_lossiness(List *ranges, bool lossy); 167 | 168 | /* Utility functions */ 169 | int irange_list_length(List *rangeset); 170 | bool irange_list_find(List *rangeset, int index, bool *lossy); 171 | 172 | #endif /* PATHMAN_RANGESET_H */ 173 | -------------------------------------------------------------------------------- /src/include/runtime_append.h: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------------ 2 | * 3 | * runtimeappend.h 4 | * RuntimeAppend node's function prototypes and structures 5 | * 6 | * Copyright (c) 2016, Postgres Professional 7 | * 8 | * ------------------------------------------------------------------------ 9 | */ 10 | 11 | #ifndef RUNTIME_APPEND_H 12 | #define RUNTIME_APPEND_H 13 | 14 | 15 | #include "pathman.h" 16 | #include "nodes_common.h" 17 | 18 | #include "postgres.h" 19 | #include "optimizer/paths.h" 20 | #include "optimizer/pathnode.h" 21 | #include "commands/explain.h" 22 | 23 | 24 | #define RUNTIME_APPEND_NODE_NAME "RuntimeAppend" 25 | 26 | 27 | typedef struct 28 | { 29 | CustomPath cpath; 30 | Oid relid; /* relid of the partitioned table */ 31 | 32 | ChildScanCommon *children; /* all available plans */ 33 | int nchildren; 34 | } RuntimeAppendPath; 35 | 36 | typedef struct 37 | { 38 | CustomScanState css; 39 | Oid relid; /* relid of the partitioned table */ 40 | 41 | /* Restrictions to be checked during ReScan and Exec */ 42 | List *custom_exprs; 43 | 44 | /* Refined clauses for partition pruning */ 45 | List *canon_custom_exprs; 46 | 47 | /* Copy of partitioning expression and dispatch info */ 48 | Node *prel_expr; 49 | PartRelationInfo *prel; 50 | 51 | /* All available plans \ plan states */ 52 | HTAB *children_table; 53 | HASHCTL children_table_config; 54 | 55 | /* Currently selected plans \ plan states */ 56 | ChildScanCommon *cur_plans; 57 | int ncur_plans; 58 | 59 | /* Should we include parent table? Cached for prepared statements */ 60 | bool enable_parent; 61 | 62 | /* Index of the selected plan state */ 63 | int running_idx; 64 | 65 | /* Last saved tuple (for SRF projections) */ 66 | TupleTableSlot *slot; 67 | } RuntimeAppendState; 68 | 69 | 70 | extern bool pg_pathman_enable_runtimeappend; 71 | 72 | extern CustomPathMethods runtimeappend_path_methods; 73 | extern CustomScanMethods runtimeappend_plan_methods; 74 | extern CustomExecMethods runtimeappend_exec_methods; 75 | 76 | 77 | void init_runtime_append_static_data(void); 78 | 79 | Path * create_runtime_append_path(PlannerInfo *root, 80 | AppendPath *inner_append, 81 | ParamPathInfo *param_info, 82 | double sel); 83 | 84 | Plan * create_runtime_append_plan(PlannerInfo *root, RelOptInfo *rel, 85 | CustomPath *best_path, List *tlist, 86 | List *clauses, List *custom_plans); 87 | 88 | Node * runtime_append_create_scan_state(CustomScan *node); 89 | 90 | void runtime_append_begin(CustomScanState *node, 91 | EState *estate, 92 | int eflags); 93 | 94 | TupleTableSlot * runtime_append_exec(CustomScanState *node); 95 | 96 | void runtime_append_end(CustomScanState *node); 97 | 98 | void runtime_append_rescan(CustomScanState *node); 99 | 100 | void runtime_append_explain(CustomScanState *node, 101 | List *ancestors, 102 | ExplainState *es); 103 | 104 | 105 | #endif /* RUNTIME_APPEND_H */ 106 | -------------------------------------------------------------------------------- /src/include/runtime_merge_append.h: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------------ 2 | * 3 | * runtime_merge_append.h 4 | * RuntimeMergeAppend node's function prototypes and structures 5 | * 6 | * Copyright (c) 2016, Postgres Professional 7 | * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group 8 | * Portions Copyright (c) 1994, Regents of the University of California 9 | * 10 | * ------------------------------------------------------------------------ 11 | */ 12 | 13 | #ifndef RUNTIME_MERGE_APPEND_H 14 | #define RUNTIME_MERGE_APPEND_H 15 | 16 | 17 | #include "runtime_append.h" 18 | #include "pathman.h" 19 | 20 | #include "postgres.h" 21 | 22 | 23 | #define RUNTIME_MERGE_APPEND_NODE_NAME "RuntimeMergeAppend" 24 | 25 | 26 | typedef struct 27 | { 28 | RuntimeAppendPath rpath; 29 | 30 | double limit_tuples; 31 | } RuntimeMergeAppendPath; 32 | 33 | typedef struct 34 | { 35 | RuntimeAppendState rstate; 36 | 37 | int numCols; /* number of sort-key columns */ 38 | AttrNumber *sortColIdx; /* their indexes in the target list */ 39 | Oid *sortOperators; /* OIDs of operators to sort them by */ 40 | Oid *collations; /* OIDs of collations */ 41 | bool *nullsFirst; /* NULLS FIRST/LAST directions */ 42 | 43 | int ms_nkeys; 44 | SortSupport ms_sortkeys; 45 | TupleTableSlot **ms_slots; 46 | struct binaryheap *ms_heap; 47 | bool ms_initialized; 48 | } RuntimeMergeAppendState; 49 | 50 | 51 | extern bool pg_pathman_enable_runtime_merge_append; 52 | 53 | extern CustomPathMethods runtime_merge_append_path_methods; 54 | extern CustomScanMethods runtime_merge_append_plan_methods; 55 | extern CustomExecMethods runtime_merge_append_exec_methods; 56 | 57 | 58 | void init_runtime_merge_append_static_data(void); 59 | 60 | Path * create_runtime_merge_append_path(PlannerInfo *root, 61 | AppendPath *inner_append, 62 | ParamPathInfo *param_info, 63 | double sel); 64 | 65 | Plan * create_runtime_merge_append_plan(PlannerInfo *root, RelOptInfo *rel, 66 | CustomPath *best_path, List *tlist, 67 | List *clauses, List *custom_plans); 68 | 69 | Node * runtime_merge_append_create_scan_state(CustomScan *node); 70 | 71 | void runtime_merge_append_begin(CustomScanState *node, 72 | EState *estate, 73 | int eflags); 74 | 75 | TupleTableSlot * runtime_merge_append_exec(CustomScanState *node); 76 | 77 | void runtime_merge_append_end(CustomScanState *node); 78 | 79 | void runtime_merge_append_rescan(CustomScanState *node); 80 | 81 | void runtime_merge_append_explain(CustomScanState *node, 82 | List *ancestors, 83 | ExplainState *es); 84 | 85 | 86 | #endif /* RUNTIME_MERGE_APPEND_H */ 87 | -------------------------------------------------------------------------------- /src/include/utility_stmt_hooking.h: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------------ 2 | * 3 | * utility_stmt_hooking.h 4 | * Override COPY TO/FROM and ALTER TABLE ... RENAME statements 5 | * for partitioned tables 6 | * 7 | * Copyright (c) 2016, Postgres Professional 8 | * 9 | * ------------------------------------------------------------------------ 10 | */ 11 | 12 | #ifndef COPY_STMT_HOOKING_H 13 | #define COPY_STMT_HOOKING_H 14 | 15 | 16 | #include "relation_info.h" 17 | 18 | #include "postgres.h" 19 | #include "commands/copy.h" 20 | #include "nodes/nodes.h" 21 | 22 | 23 | /* Various traits */ 24 | bool is_pathman_related_copy(Node *parsetree); 25 | bool is_pathman_related_table_rename(Node *parsetree, 26 | Oid *relation_oid_out, 27 | bool *is_parent_out); 28 | bool is_pathman_related_alter_column_type(Node *parsetree, 29 | Oid *parent_relid_out, 30 | AttrNumber *attr_number, 31 | PartType *part_type_out); 32 | 33 | /* Statement handlers */ 34 | void PathmanDoCopy(const CopyStmt *stmt, const char *queryString, 35 | int stmt_location, int stmt_len, uint64 *processed); 36 | 37 | void PathmanRenameConstraint(Oid partition_relid, const RenameStmt *rename_stmt); 38 | void PathmanRenameSequence(Oid parent_relid, const RenameStmt *rename_stmt); 39 | 40 | 41 | #endif /* COPY_STMT_HOOKING_H */ 42 | -------------------------------------------------------------------------------- /src/include/utils.h: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------------ 2 | * 3 | * utils.h 4 | * prototypes of various support functions 5 | * 6 | * Copyright (c) 2016, Postgres Professional 7 | * 8 | * ------------------------------------------------------------------------ 9 | */ 10 | 11 | #ifndef PATHMAN_UTILS_H 12 | #define PATHMAN_UTILS_H 13 | 14 | 15 | #include "postgres.h" 16 | #include "parser/parse_oper.h" 17 | #include "fmgr.h" 18 | 19 | 20 | /* 21 | * Various traits. 22 | */ 23 | bool clause_contains_params(Node *clause); 24 | bool is_date_type_internal(Oid typid); 25 | bool check_security_policy_internal(Oid relid, Oid role); 26 | bool match_expr_to_operand(const Node *expr, const Node *operand); 27 | 28 | /* 29 | * Misc. 30 | */ 31 | List *list_reverse(List *l); 32 | 33 | /* 34 | * Dynamic arrays. 35 | */ 36 | 37 | #define ARRAY_EXP 2 38 | 39 | #define ArrayAlloc(array, alloced, used, size) \ 40 | do { \ 41 | (array) = palloc((size) * sizeof(*(array))); \ 42 | (alloced) = (size); \ 43 | (used) = 0; \ 44 | } while (0) 45 | 46 | #define ArrayPush(array, alloced, used, value) \ 47 | do { \ 48 | if ((alloced) <= (used)) \ 49 | { \ 50 | (alloced) = (alloced) * ARRAY_EXP + 1; \ 51 | (array) = repalloc((array), (alloced) * sizeof(*(array))); \ 52 | } \ 53 | \ 54 | (array)[(used)] = (value); \ 55 | \ 56 | (used)++; \ 57 | } while (0) 58 | 59 | /* 60 | * Useful functions for relations. 61 | */ 62 | Oid get_rel_owner(Oid relid); 63 | char *get_rel_name_or_relid(Oid relid); 64 | char *get_qualified_rel_name(Oid relid); 65 | RangeVar *makeRangeVarFromRelid(Oid relid); 66 | 67 | /* 68 | * Operator-related stuff. 69 | */ 70 | Operator get_binary_operator(char *opname, Oid arg1, Oid arg2); 71 | void fill_type_cmp_fmgr_info(FmgrInfo *finfo, Oid type1, Oid type2); 72 | void extract_op_func_and_ret_type(char *opname, 73 | Oid type1, Oid type2, 74 | Oid *op_func, 75 | Oid *op_ret_type); 76 | 77 | /* 78 | * Print values and cast types. 79 | */ 80 | char *datum_to_cstring(Datum datum, Oid typid); 81 | Datum perform_type_cast(Datum value, Oid in_type, Oid out_type, bool *success); 82 | Datum extract_binary_interval_from_text(Datum interval_text, 83 | Oid part_atttype, 84 | Oid *interval_type); 85 | char **deconstruct_text_array(Datum array, int *array_size); 86 | RangeVar **qualified_relnames_to_rangevars(char **relnames, size_t nrelnames); 87 | void check_relation_oid(Oid relid); 88 | 89 | #endif /* PATHMAN_UTILS_H */ 90 | -------------------------------------------------------------------------------- /src/include/xact_handling.h: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------------ 2 | * 3 | * xact_handling.h 4 | * Transaction-specific locks and other functions 5 | * 6 | * Copyright (c) 2016, Postgres Professional 7 | * 8 | * ------------------------------------------------------------------------ 9 | */ 10 | 11 | #ifndef XACT_HANDLING_H 12 | #define XACT_HANDLING_H 13 | 14 | 15 | #include "pathman.h" 16 | 17 | #include "postgres.h" 18 | 19 | 20 | /* 21 | * Transaction locks. 22 | */ 23 | LockAcquireResult xact_lock_rel(Oid relid, LOCKMODE lockmode, bool nowait); 24 | 25 | /* 26 | * Utility checks. 27 | */ 28 | bool xact_bgw_conflicting_lock_exists(Oid relid); 29 | bool xact_is_level_read_committed(void); 30 | bool xact_is_transaction_stmt(Node *stmt); 31 | bool xact_is_set_stmt(Node *stmt, const char *name); 32 | bool xact_is_alter_pathman_stmt(Node *stmt); 33 | bool xact_object_is_visible(TransactionId obj_xmin); 34 | 35 | 36 | #endif /* XACT_HANDLING_H */ 37 | -------------------------------------------------------------------------------- /src/pl_hash_funcs.c: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------------ 2 | * 3 | * pl_hash_funcs.c 4 | * Utility C functions for stored HASH procedures 5 | * 6 | * Copyright (c) 2016, Postgres Professional 7 | * 8 | * ------------------------------------------------------------------------ 9 | */ 10 | 11 | #include "pathman.h" 12 | #include "partition_creation.h" 13 | #include "relation_info.h" 14 | #include "utils.h" 15 | 16 | #include "utils/builtins.h" 17 | #include "utils/typcache.h" 18 | #include "utils/lsyscache.h" 19 | 20 | 21 | /* Function declarations */ 22 | 23 | PG_FUNCTION_INFO_V1( create_hash_partitions_internal ); 24 | 25 | PG_FUNCTION_INFO_V1( get_hash_part_idx ); 26 | PG_FUNCTION_INFO_V1( build_hash_condition ); 27 | 28 | 29 | /* 30 | * Create HASH partitions implementation (written in C). 31 | */ 32 | Datum 33 | create_hash_partitions_internal(PG_FUNCTION_ARGS) 34 | { 35 | /* Free allocated arrays */ 36 | #define DeepFreeArray(arr, arr_len) \ 37 | do { \ 38 | int arr_elem; \ 39 | if (!arr) break; \ 40 | for (arr_elem = 0; arr_elem < arr_len; arr_elem++) \ 41 | pfree(arr[arr_elem]); \ 42 | pfree(arr); \ 43 | } while (0) 44 | 45 | Oid parent_relid = PG_GETARG_OID(0); 46 | uint32 partitions_count = PG_GETARG_INT32(2), 47 | i; 48 | 49 | /* Partition names and tablespaces */ 50 | char **partition_names = NULL, 51 | **tablespaces = NULL; 52 | int partition_names_size = 0, 53 | tablespaces_size = 0; 54 | RangeVar **rangevars = NULL; 55 | 56 | /* Check that there's no partitions yet */ 57 | if (has_pathman_relation_info(parent_relid)) 58 | ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 59 | errmsg("cannot add new HASH partitions"))); 60 | 61 | /* Extract partition names */ 62 | if (!PG_ARGISNULL(3)) 63 | partition_names = deconstruct_text_array(PG_GETARG_DATUM(3), &partition_names_size); 64 | 65 | /* Extract partition tablespaces */ 66 | if (!PG_ARGISNULL(4)) 67 | tablespaces = deconstruct_text_array(PG_GETARG_DATUM(4), &tablespaces_size); 68 | 69 | /* Validate size of 'partition_names' */ 70 | if (partition_names && partition_names_size != partitions_count) 71 | ereport(ERROR, 72 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), 73 | errmsg("size of 'partition_names' must be equal to 'partitions_count'"))); 74 | 75 | /* Validate size of 'tablespaces' */ 76 | if (tablespaces && tablespaces_size != partitions_count) 77 | ereport(ERROR, 78 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), 79 | errmsg("size of 'tablespaces' must be equal to 'partitions_count'"))); 80 | 81 | /* Convert partition names into RangeVars */ 82 | rangevars = qualified_relnames_to_rangevars(partition_names, partitions_count); 83 | 84 | /* Finally create HASH partitions */ 85 | for (i = 0; i < partitions_count; i++) 86 | { 87 | RangeVar *partition_rv = rangevars ? rangevars[i] : NULL; 88 | char *tablespace = tablespaces ? tablespaces[i] : NULL; 89 | 90 | /* Create a partition (copy FKs, invoke callbacks etc) */ 91 | create_single_hash_partition_internal(parent_relid, i, partitions_count, 92 | partition_rv, tablespace); 93 | } 94 | 95 | /* Free arrays */ 96 | DeepFreeArray(partition_names, partition_names_size); 97 | DeepFreeArray(tablespaces, tablespaces_size); 98 | DeepFreeArray(rangevars, partition_names_size); 99 | 100 | PG_RETURN_VOID(); 101 | } 102 | 103 | /* 104 | * Wrapper for hash_to_part_index(). 105 | */ 106 | Datum 107 | get_hash_part_idx(PG_FUNCTION_ARGS) 108 | { 109 | uint32 value = PG_GETARG_UINT32(0), 110 | part_count = PG_GETARG_UINT32(1); 111 | 112 | PG_RETURN_UINT32(hash_to_part_index(value, part_count)); 113 | } 114 | 115 | /* 116 | * Build hash condition for a CHECK CONSTRAINT 117 | */ 118 | Datum 119 | build_hash_condition(PG_FUNCTION_ARGS) 120 | { 121 | Oid expr_type = PG_GETARG_OID(0); 122 | char *expr_cstr = TextDatumGetCString(PG_GETARG_DATUM(1)); 123 | uint32 part_count = PG_GETARG_UINT32(2), 124 | part_idx = PG_GETARG_UINT32(3); 125 | char *pathman_schema; 126 | 127 | TypeCacheEntry *tce; 128 | 129 | char *result; 130 | 131 | if (part_idx >= part_count) 132 | ereport(ERROR, 133 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), 134 | errmsg("'partition_index' must be lower than 'partitions_count'"))); 135 | 136 | tce = lookup_type_cache(expr_type, TYPECACHE_HASH_PROC); 137 | 138 | /* Check that HASH function exists */ 139 | if (!OidIsValid(tce->hash_proc)) 140 | ereport(ERROR, 141 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), 142 | errmsg("no hash function for type %s", 143 | format_type_be(expr_type)))); 144 | 145 | pathman_schema = get_namespace_name(get_pathman_schema()); 146 | if (pathman_schema == NULL) 147 | elog(ERROR, "pg_pathman schema not initialized"); 148 | 149 | /* Create hash condition CSTRING */ 150 | result = psprintf("%s.get_hash_part_idx(%s(%s), %u) = %u", 151 | pathman_schema, 152 | get_func_name(tce->hash_proc), 153 | expr_cstr, 154 | part_count, 155 | part_idx); 156 | 157 | PG_RETURN_TEXT_P(cstring_to_text(result)); 158 | } 159 | -------------------------------------------------------------------------------- /src/runtime_append.c: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------------ 2 | * 3 | * runtimeappend.c 4 | * RuntimeAppend node's function definitions and global variables 5 | * 6 | * Copyright (c) 2016, Postgres Professional 7 | * 8 | * ------------------------------------------------------------------------ 9 | */ 10 | 11 | #include "compat/pg_compat.h" 12 | 13 | #include "runtime_append.h" 14 | 15 | #include "utils/guc.h" 16 | 17 | 18 | bool pg_pathman_enable_runtimeappend = true; 19 | 20 | CustomPathMethods runtimeappend_path_methods; 21 | CustomScanMethods runtimeappend_plan_methods; 22 | CustomExecMethods runtimeappend_exec_methods; 23 | 24 | 25 | void 26 | init_runtime_append_static_data(void) 27 | { 28 | runtimeappend_path_methods.CustomName = RUNTIME_APPEND_NODE_NAME; 29 | runtimeappend_path_methods.PlanCustomPath = create_runtime_append_plan; 30 | 31 | runtimeappend_plan_methods.CustomName = RUNTIME_APPEND_NODE_NAME; 32 | runtimeappend_plan_methods.CreateCustomScanState = runtime_append_create_scan_state; 33 | 34 | runtimeappend_exec_methods.CustomName = RUNTIME_APPEND_NODE_NAME; 35 | runtimeappend_exec_methods.BeginCustomScan = runtime_append_begin; 36 | runtimeappend_exec_methods.ExecCustomScan = runtime_append_exec; 37 | runtimeappend_exec_methods.EndCustomScan = runtime_append_end; 38 | runtimeappend_exec_methods.ReScanCustomScan = runtime_append_rescan; 39 | runtimeappend_exec_methods.MarkPosCustomScan = NULL; 40 | runtimeappend_exec_methods.RestrPosCustomScan = NULL; 41 | runtimeappend_exec_methods.ExplainCustomScan = runtime_append_explain; 42 | 43 | DefineCustomBoolVariable("pg_pathman.enable_runtimeappend", 44 | "Enables the planner's use of " RUNTIME_APPEND_NODE_NAME " custom node.", 45 | NULL, 46 | &pg_pathman_enable_runtimeappend, 47 | true, 48 | PGC_USERSET, 49 | 0, 50 | NULL, 51 | NULL, 52 | NULL); 53 | 54 | RegisterCustomScanMethods(&runtimeappend_plan_methods); 55 | } 56 | 57 | Path * 58 | create_runtime_append_path(PlannerInfo *root, 59 | AppendPath *inner_append, 60 | ParamPathInfo *param_info, 61 | double sel) 62 | { 63 | return create_append_path_common(root, inner_append, 64 | param_info, 65 | &runtimeappend_path_methods, 66 | sizeof(RuntimeAppendPath), 67 | sel); 68 | } 69 | 70 | Plan * 71 | create_runtime_append_plan(PlannerInfo *root, RelOptInfo *rel, 72 | CustomPath *best_path, List *tlist, 73 | List *clauses, List *custom_plans) 74 | { 75 | return create_append_plan_common(root, rel, 76 | best_path, tlist, 77 | clauses, custom_plans, 78 | &runtimeappend_plan_methods); 79 | } 80 | 81 | Node * 82 | runtime_append_create_scan_state(CustomScan *node) 83 | { 84 | return create_append_scan_state_common(node, 85 | &runtimeappend_exec_methods, 86 | sizeof(RuntimeAppendState)); 87 | } 88 | 89 | void 90 | runtime_append_begin(CustomScanState *node, EState *estate, int eflags) 91 | { 92 | begin_append_common(node, estate, eflags); 93 | } 94 | 95 | static void 96 | fetch_next_tuple(CustomScanState *node) 97 | { 98 | RuntimeAppendState *scan_state = (RuntimeAppendState *) node; 99 | 100 | while (scan_state->running_idx < scan_state->ncur_plans) 101 | { 102 | ChildScanCommon child = scan_state->cur_plans[scan_state->running_idx]; 103 | PlanState *state = child->content.plan_state; 104 | 105 | for (;;) 106 | { 107 | TupleTableSlot *slot = ExecProcNode(state); 108 | 109 | if (TupIsNull(slot)) 110 | break; 111 | 112 | scan_state->slot = slot; 113 | return; 114 | } 115 | 116 | scan_state->running_idx++; 117 | } 118 | 119 | scan_state->slot = NULL; 120 | } 121 | 122 | TupleTableSlot * 123 | runtime_append_exec(CustomScanState *node) 124 | { 125 | return exec_append_common(node, fetch_next_tuple); 126 | } 127 | 128 | void 129 | runtime_append_end(CustomScanState *node) 130 | { 131 | end_append_common(node); 132 | } 133 | 134 | void 135 | runtime_append_rescan(CustomScanState *node) 136 | { 137 | rescan_append_common(node); 138 | } 139 | 140 | void 141 | runtime_append_explain(CustomScanState *node, List *ancestors, ExplainState *es) 142 | { 143 | RuntimeAppendState *scan_state = (RuntimeAppendState *) node; 144 | 145 | explain_append_common(node, ancestors, es, 146 | scan_state->children_table, 147 | scan_state->custom_exprs); 148 | } 149 | -------------------------------------------------------------------------------- /src/xact_handling.c: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------------ 2 | * 3 | * xact_handling.c 4 | * Transaction-specific locks and other functions 5 | * 6 | * Copyright (c) 2016, Postgres Professional 7 | * 8 | * ------------------------------------------------------------------------ 9 | */ 10 | 11 | #include "xact_handling.h" 12 | #include "utils.h" 13 | 14 | #include "postgres.h" 15 | #include "access/transam.h" 16 | #include "access/xact.h" 17 | #include "catalog/catalog.h" 18 | #include "miscadmin.h" 19 | #include "storage/lmgr.h" 20 | #include "utils/inval.h" 21 | 22 | 23 | static inline void SetLocktagRelationOid(LOCKTAG *tag, Oid relid); 24 | static inline bool do_we_hold_the_lock(Oid relid, LOCKMODE lockmode); 25 | 26 | 27 | 28 | static LockAcquireResult 29 | LockAcquireOid(Oid relid, LOCKMODE lockmode, bool sessionLock, bool dontWait) 30 | { 31 | LOCKTAG tag; 32 | LockAcquireResult res; 33 | 34 | /* Create a tag for lock */ 35 | SetLocktagRelationOid(&tag, relid); 36 | 37 | res = LockAcquire(&tag, lockmode, sessionLock, dontWait); 38 | 39 | /* 40 | * Now that we have the lock, check for invalidation messages; 41 | * see notes in LockRelationOid. 42 | */ 43 | if (res != LOCKACQUIRE_ALREADY_HELD) 44 | AcceptInvalidationMessages(); 45 | 46 | return res; 47 | } 48 | 49 | 50 | /* 51 | * Acquire lock and return LockAcquireResult. 52 | */ 53 | LockAcquireResult 54 | xact_lock_rel(Oid relid, LOCKMODE lockmode, bool nowait) 55 | { 56 | return LockAcquireOid(relid, lockmode, false, nowait); 57 | } 58 | 59 | /* 60 | * Check whether we already hold a lock that 61 | * might conflict with partition spawning BGW. 62 | */ 63 | bool 64 | xact_bgw_conflicting_lock_exists(Oid relid) 65 | { 66 | #if PG_VERSION_NUM >= 90600 67 | /* We use locking groups for 9.6+ */ 68 | return false; 69 | #else 70 | LOCKMODE lockmode; 71 | 72 | /* Try each lock >= ShareUpdateExclusiveLock */ 73 | for (lockmode = ShareUpdateExclusiveLock; 74 | lockmode <= AccessExclusiveLock; 75 | lockmode++) 76 | { 77 | if (do_we_hold_the_lock(relid, lockmode)) 78 | return true; 79 | } 80 | 81 | return false; 82 | #endif 83 | } 84 | 85 | 86 | /* 87 | * Check if current transaction's level is READ COMMITTED. 88 | */ 89 | bool 90 | xact_is_level_read_committed(void) 91 | { 92 | if (XactIsoLevel <= XACT_READ_COMMITTED) 93 | return true; 94 | 95 | return false; 96 | } 97 | 98 | /* 99 | * Check if 'stmt' is BEGIN/ROLLBACK/etc [TRANSACTION] statement. 100 | */ 101 | bool 102 | xact_is_transaction_stmt(Node *stmt) 103 | { 104 | if (!stmt) 105 | return false; 106 | 107 | if (IsA(stmt, TransactionStmt)) 108 | return true; 109 | 110 | return false; 111 | } 112 | 113 | /* 114 | * Check if 'stmt' is SET ('name' | [TRANSACTION]) statement. 115 | */ 116 | bool 117 | xact_is_set_stmt(Node *stmt, const char *name) 118 | { 119 | /* Check that SET TRANSACTION is implemented via VariableSetStmt */ 120 | Assert(VAR_SET_MULTI > 0); 121 | 122 | if (!stmt) 123 | return false; 124 | 125 | if (!IsA(stmt, VariableSetStmt)) 126 | return false; 127 | 128 | if (!name) 129 | return true; 130 | else 131 | { 132 | char *set_name = ((VariableSetStmt *) stmt)->name; 133 | 134 | if (set_name && pg_strcasecmp(name, set_name) == 0) 135 | return true; 136 | } 137 | 138 | return false; 139 | } 140 | 141 | /* 142 | * Check if 'stmt' is ALTER EXTENSION pg_pathman. 143 | */ 144 | bool 145 | xact_is_alter_pathman_stmt(Node *stmt) 146 | { 147 | if (!stmt) 148 | return false; 149 | 150 | if (!IsA(stmt, AlterExtensionStmt)) 151 | return false; 152 | 153 | if (pg_strcasecmp(((AlterExtensionStmt *) stmt)->extname, "pg_pathman") == 0) 154 | return true; 155 | 156 | return false; 157 | } 158 | 159 | /* 160 | * Check if object is visible to newer transactions. 161 | */ 162 | bool 163 | xact_object_is_visible(TransactionId obj_xmin) 164 | { 165 | return TransactionIdEquals(obj_xmin, FrozenTransactionId) || 166 | TransactionIdPrecedes(obj_xmin, GetCurrentTransactionId()); 167 | } 168 | 169 | /* 170 | * Do we hold the specified lock? 171 | */ 172 | #ifdef __GNUC__ 173 | __attribute__((unused)) 174 | #endif 175 | static inline bool 176 | do_we_hold_the_lock(Oid relid, LOCKMODE lockmode) 177 | { 178 | LOCKTAG tag; 179 | 180 | /* Create a tag for lock */ 181 | SetLocktagRelationOid(&tag, relid); 182 | 183 | /* If lock is alredy held, release it one time (decrement) */ 184 | switch (LockAcquire(&tag, lockmode, false, true)) 185 | { 186 | case LOCKACQUIRE_ALREADY_HELD: 187 | LockRelease(&tag, lockmode, false); 188 | return true; 189 | 190 | case LOCKACQUIRE_OK: 191 | LockRelease(&tag, lockmode, false); 192 | return false; 193 | 194 | default: 195 | return false; 196 | } 197 | } 198 | 199 | /* 200 | * SetLocktagRelationOid 201 | * Set up a locktag for a relation, given only relation OID 202 | */ 203 | static inline void 204 | SetLocktagRelationOid(LOCKTAG *tag, Oid relid) 205 | { 206 | Oid dbid; 207 | 208 | if (IsSharedRelation(relid)) 209 | dbid = InvalidOid; 210 | else 211 | dbid = MyDatabaseId; 212 | 213 | SET_LOCKTAG_RELATION(*tag, dbid, relid); 214 | } 215 | -------------------------------------------------------------------------------- /tests/cmocka/.gitignore: -------------------------------------------------------------------------------- 1 | rangeset_tests 2 | -------------------------------------------------------------------------------- /tests/cmocka/Makefile: -------------------------------------------------------------------------------- 1 | PG_CONFIG = pg_config 2 | TOP_SRC_DIR = ../../src 3 | 4 | CC = gcc 5 | CFLAGS += -I $(TOP_SRC_DIR) -I $(shell $(PG_CONFIG) --includedir-server) 6 | CFLAGS += -I$(CURDIR)/../../src/include -I. 7 | CFLAGS += $(shell $(PG_CONFIG) --cflags_sl) 8 | CFLAGS += $(shell $(PG_CONFIG) --cflags) 9 | CFLAGS += $(CFLAGS_SL) 10 | CFLAGS += $(PG_CPPFLAGS) 11 | CFLAGS += -D_GNU_SOURCE 12 | LDFLAGS += -lcmocka 13 | TEST_BIN = rangeset_tests 14 | 15 | OBJ = missing_basic.o missing_list.o missing_stringinfo.o \ 16 | missing_bitmapset.o rangeset_tests.o $(TOP_SRC_DIR)/rangeset.o 17 | 18 | 19 | all: build_extension $(TEST_BIN) 20 | 21 | $(TEST_BIN): $(OBJ) 22 | $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) 23 | 24 | %.o: %.c 25 | $(CC) -c -o $@ $< $(CFLAGS) 26 | 27 | build_extension: 28 | $(MAKE) -C $(TOP_SRC_DIR)/.. 29 | 30 | clean: 31 | rm -f $(OBJ) $(TEST_BIN) 32 | 33 | check: all 34 | ./$(TEST_BIN) 35 | -------------------------------------------------------------------------------- /tests/cmocka/missing_basic.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "postgres.h" 4 | #include "undef_printf.h" 5 | 6 | 7 | void * 8 | palloc(Size size) 9 | { 10 | return malloc(size); 11 | } 12 | 13 | void * 14 | repalloc(void *pointer, Size size) 15 | { 16 | return realloc(pointer, size); 17 | } 18 | 19 | void 20 | pfree(void *pointer) 21 | { 22 | free(pointer); 23 | } 24 | 25 | void 26 | ExceptionalCondition(const char *conditionName, 27 | #if PG_VERSION_NUM < 160000 28 | const char *errorType, 29 | #endif 30 | const char *fileName, 31 | int lineNumber) 32 | { 33 | if (!PointerIsValid(conditionName) || !PointerIsValid(fileName) 34 | #if PG_VERSION_NUM < 160000 35 | || !PointerIsValid(errorType) 36 | #endif 37 | ) 38 | { 39 | printf("TRAP: ExceptionalCondition: bad arguments\n"); 40 | } 41 | else 42 | { 43 | printf("TRAP: %s(\"%s\", File: \"%s\", Line: %d)\n", 44 | #if PG_VERSION_NUM < 160000 45 | errorType, 46 | #else 47 | "", 48 | #endif 49 | conditionName, 50 | fileName, lineNumber); 51 | 52 | } 53 | 54 | /* Usually this shouldn't be needed, but make sure the msg went out */ 55 | fflush(stderr); 56 | 57 | abort(); 58 | } 59 | -------------------------------------------------------------------------------- /tests/cmocka/missing_bitmapset.c: -------------------------------------------------------------------------------- 1 | #include "postgres.h" 2 | #include "undef_printf.h" 3 | #include "nodes/bitmapset.h" 4 | 5 | 6 | int 7 | bms_next_member(const Bitmapset *a, int prevbit); 8 | 9 | 10 | int 11 | bms_next_member(const Bitmapset *a, int prevbit) 12 | { 13 | printf("bms_next_member(): not implemented yet\n"); 14 | fflush(stdout); 15 | 16 | abort(); 17 | } 18 | -------------------------------------------------------------------------------- /tests/cmocka/undef_printf.h: -------------------------------------------------------------------------------- 1 | #ifdef vsnprintf 2 | #undef vsnprintf 3 | #endif 4 | #ifdef snprintf 5 | #undef snprintf 6 | #endif 7 | #ifdef vsprintf 8 | #undef vsprintf 9 | #endif 10 | #ifdef sprintf 11 | #undef sprintf 12 | #endif 13 | #ifdef vfprintf 14 | #undef vfprintf 15 | #endif 16 | #ifdef fprintf 17 | #undef fprintf 18 | #endif 19 | #ifdef vprintf 20 | #undef vprintf 21 | #endif 22 | #ifdef printf 23 | #undef printf 24 | #endif 25 | -------------------------------------------------------------------------------- /tests/python/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E241, E501 3 | -------------------------------------------------------------------------------- /tests/python/.gitignore: -------------------------------------------------------------------------------- 1 | tests.log 2 | -------------------------------------------------------------------------------- /tests/python/.style.yapf: -------------------------------------------------------------------------------- 1 | [style] 2 | based_on_style = pep8 3 | spaces_before_comment = 4 4 | split_before_logical_operator = false 5 | column_limit=100 6 | -------------------------------------------------------------------------------- /tests/python/Makefile: -------------------------------------------------------------------------------- 1 | partitioning_tests: 2 | ifneq ($(CASE),) 3 | python3 -u partitioning_test.py Tests.$(CASE) 4 | else 5 | python3 -u partitioning_test.py 6 | endif 7 | -------------------------------------------------------------------------------- /tests/python/README.md: -------------------------------------------------------------------------------- 1 | # Tests 2 | 3 | This directory contains script to tests some features which cannot be tested 4 | with only regression tests 5 | 6 | ## Running 7 | 8 | First of all you need to install `testgres` python module which contains useful 9 | functions to start postgres clusters and make queries: 10 | 11 | ``` 12 | pip3 install testgres 13 | ``` 14 | 15 | To run tests execute: 16 | 17 | ``` 18 | python3 -m unittest partitioning_test 19 | ``` 20 | 21 | from current directory. If you want to run a specific postgres build then 22 | you should specify the path to your pg_config executable by setting PG_CONFIG 23 | environment variable: 24 | 25 | ``` 26 | export PG_CONFIG=/path/to/pg_config 27 | ``` 28 | 29 | To test FDW features you need to install postgres_fdw contrib module first. 30 | If you want to skip FDW tests set the FDW_DISABLED environment variable: 31 | 32 | ``` 33 | export FDW_DISABLED=1 34 | ``` 35 | -------------------------------------------------------------------------------- /tests/python/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postgrespro/pg_pathman/73aafcfc7c088f891890b6896b7b4d62e36f6931/tests/python/__init__.py -------------------------------------------------------------------------------- /tests/python/pgbench_scripts/detachs_in_timeout.pgbench: -------------------------------------------------------------------------------- 1 | select detach_range_partition(partition) from (select partition from pathman_partition_list where parent='ts_range_partitioned'::regclass order by range_min limit 1) t; 2 | select pg_sleep(:timeout); 3 | -------------------------------------------------------------------------------- /tests/python/pgbench_scripts/insert_current_timestamp.pgbench: -------------------------------------------------------------------------------- 1 | insert into ts_range_partitioned values (current_timestamp); 2 | -------------------------------------------------------------------------------- /tests/update/README.md: -------------------------------------------------------------------------------- 1 | ## pg_pathman's update checker 2 | 3 | It's necessary to check that `ALTER EXTENSION pg_pathman UPDATE` produces an SQL frontend that is exactly the same as a fresh install. 4 | 5 | Usage: 6 | 7 | ```bash 8 | PG_CONFIG=... ./dump_pathman_objects %DBNAME% 9 | 10 | diff file_1 file_2 11 | ``` 12 | 13 | check_update.py script tries to verify that update is ok automatically. For 14 | instance, 15 | ```bash 16 | tests/update/check_update.py d34a77e worktree 17 | ``` 18 | -------------------------------------------------------------------------------- /tests/update/dump_pathman_objects.sql: -------------------------------------------------------------------------------- 1 | CREATE EXTENSION IF NOT EXISTS pg_pathman; 2 | 3 | SELECT pg_get_functiondef(objid) 4 | FROM pg_catalog.pg_depend JOIN pg_proc ON pg_proc.oid = pg_depend.objid 5 | WHERE refclassid = 'pg_catalog.pg_extension'::REGCLASS AND 6 | refobjid = (SELECT oid 7 | FROM pg_catalog.pg_extension 8 | WHERE extname = 'pg_pathman') AND 9 | deptype = 'e' 10 | ORDER BY objid::regprocedure::TEXT ASC; 11 | 12 | \d+ pathman_config 13 | \d+ pathman_config_params 14 | \d+ pathman_partition_list 15 | \d+ pathman_cache_stats 16 | \d+ pathman_concurrent_part_tasks 17 | -------------------------------------------------------------------------------- /tests/update/get_sql_diff: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PG_VER=$1 4 | WORK_DIR=/tmp/pg_pathman 5 | BRANCH_1=$2 6 | BRANCH_2=$3 7 | 8 | 9 | if [ -z "$PG_VER" ]; then 10 | PG_VER=10 11 | fi 12 | 13 | if [ -z "$BRANCH_1" ]; then 14 | BRANCH_1=master 15 | fi 16 | 17 | if [ -z "$BRANCH_1" ]; then 18 | BRANCH_2=$(git tag | sort -V | tail -1) 19 | fi 20 | 21 | 22 | printf "PG:\\t$PG_VER\\n" 23 | printf "BRANCH_1:\\t$BRANCH_1\\n" 24 | printf "BRANCH_2:\\t$BRANCH_2\\n" 25 | 26 | 27 | cp -R "$(dirname $0)" "$WORK_DIR" 28 | 29 | git checkout "$BRANCH_1" 30 | 31 | norsu pgxs "$PG_VER" -- clean install 32 | norsu run "$PG_VER" --pgxs --psql < "$WORK_DIR"/dump_pathman_objects.sql > "$WORK_DIR"/dump_1 33 | 34 | git checkout "$BRANCH_2" 35 | 36 | norsu pgxs "$PG_VER" -- clean install 37 | norsu run "$PG_VER" --pgxs --psql < "$WORK_DIR"/dump_pathman_objects.sql > "$WORK_DIR"/dump_2 38 | 39 | diff -u "$WORK_DIR"/dump_1 "$WORK_DIR"/dump_2 > "$WORK_DIR"/diff 40 | --------------------------------------------------------------------------------