diff --git a/.circleci/config.yml b/.circleci/config.yml index 225195d27..3b2b62cac 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,19 +6,16 @@ orbs: parameters: image_suffix: type: string - default: '-v3417e8d' - pg13_version: - type: string - default: '13.10' + default: '-vbab548a' pg14_version: type: string - default: '14.7' + default: '14.8' pg15_version: type: string - default: '15.2' + default: '15.3' upgrade_pg_versions: type: string - default: '13.10-14.7-15.2' + default: '14.8-15.3' style_checker_tools_version: type: string default: '0.8.18' @@ -421,6 +418,66 @@ jobs: - coverage: flags: 'test_<< parameters.pg_major >>,upgrade' + test-query-generator: + description: Expects that the generated queries that are run on distributed and local tables would have the same results + parameters: + pg_major: + description: 'postgres major version' + type: integer + image: + description: 'docker image to use as for the tests' + type: string + default: citus/failtester + image_tag: + description: 'docker image tag to use' + type: string + docker: + - image: '<< parameters.image >>:<< parameters.image_tag >><< pipeline.parameters.image_suffix >>' + working_directory: /home/circleci/project + steps: + - checkout + - attach_workspace: + at: . + - install_extension: + pg_major: << parameters.pg_major >> + - configure + - enable_core + - run: + name: 'Run Test' + command: | + gosu circleci make -C src/test/regress check-query-generator + no_output_timeout: 5m + - run: + name: 'Show regressions' + command: | + find src/test/regress/citus_tests/query_generator/out/ -name "local_dist.diffs" -exec cat {} + + lines=$(find src/test/regress/citus_tests/query_generator/out/ -name "local_dist.diffs" | wc -l) + if [ $lines -ne 0 ]; then + exit 1 + fi + when: on_fail + - run: + name: 'Copy logfiles' + command: | + mkdir src/test/regress/tmp_citus_test/logfiles + find src/test/regress/tmp_citus_test/ -name "logfile_*" -exec cp -t src/test/regress/tmp_citus_test/logfiles/ {} + + when: on_fail + - store_artifacts: + name: 'Save logfiles' + path: src/test/regress/tmp_citus_test/logfiles + - store_artifacts: + name: 'Save ddls' + path: src/test/regress/citus_tests/query_generator/out/ddls.sql + - store_artifacts: + name: 'Save dmls' + path: src/test/regress/citus_tests/query_generator/out/queries.sql + - store_artifacts: + name: 'Save diffs' + path: src/test/regress/citus_tests/query_generator/out/local_dist.diffs + - stack_trace + - coverage: + flags: 'test_<< parameters.pg_major >>,querygen' + test-citus: description: Runs the common tests of citus parameters: @@ -505,7 +562,7 @@ jobs: check-merge-to-enterprise: docker: - - image: citus/extbuilder:<< pipeline.parameters.pg13_version >> + - image: citus/extbuilder:<< pipeline.parameters.pg14_version >> working_directory: /home/circleci/project steps: - checkout @@ -657,10 +714,6 @@ workflows: when: not: << pipeline.parameters.flaky_test >> jobs: - - build: - name: build-13 - pg_major: 13 - image_tag: '<< pipeline.parameters.pg13_version >>' - build: name: build-14 pg_major: 14 @@ -673,79 +726,6 @@ workflows: - check-style - check-sql-snapshots - - test-citus: &test-citus-13 - name: 'test-13_check-multi' - make: check-multi - pg_major: 13 - image_tag: '<< pipeline.parameters.pg13_version >>' - requires: [build-13] - - test-citus: - <<: *test-citus-13 - name: 'test-13_check-multi-1' - make: check-multi-1 - - test-citus: - <<: *test-citus-13 - name: 'test-13_check-mx' - make: check-multi-mx - - test-citus: - <<: *test-citus-13 - name: 'test-13_check-vanilla' - make: check-vanilla - - test-citus: - <<: *test-citus-13 - name: 'test-13_check-isolation' - make: check-isolation - - test-citus: - <<: *test-citus-13 - name: 'test-13_check-operations' - make: check-operations - - test-citus: - <<: *test-citus-13 - name: 'test-13_check-follower-cluster' - make: check-follower-cluster - - test-citus: - <<: *test-citus-13 - name: 'test-13_check-columnar' - make: check-columnar - - test-citus: - <<: *test-citus-13 - name: 'test-13_check-columnar-isolation' - make: check-columnar-isolation - - test-citus: - <<: *test-citus-13 - name: 'test-13_check-failure' - image: citus/failtester - make: check-failure - - test-citus: - <<: *test-citus-13 - name: 'test-13_check-enterprise' - make: check-enterprise - - test-citus: - <<: *test-citus-13 - name: 'test-13_check-enterprise-isolation' - make: check-enterprise-isolation - - test-citus: - <<: *test-citus-13 - name: 'test-13_check-enterprise-isolation-logicalrep-1' - make: check-enterprise-isolation-logicalrep-1 - - test-citus: - <<: *test-citus-13 - name: 'test-13_check-enterprise-isolation-logicalrep-2' - make: check-enterprise-isolation-logicalrep-2 - - test-citus: - <<: *test-citus-13 - name: 'test-13_check-enterprise-isolation-logicalrep-3' - make: check-enterprise-isolation-logicalrep-3 - - test-citus: - <<: *test-citus-13 - name: 'test-13_check-enterprise-failure' - image: citus/failtester - make: check-enterprise-failure - - test-citus: - <<: *test-citus-13 - name: 'test-13_check-split' - make: check-split - - test-citus: &test-citus-14 name: 'test-14_check-split' make: check-split @@ -892,12 +872,6 @@ workflows: image: citus/failtester make: check-failure - - test-pytest: - name: 'test-13_pytest' - pg_major: 13 - image_tag: '<< pipeline.parameters.pg13_version >>' - requires: [build-13] - - test-pytest: name: 'test-14_pytest' pg_major: 14 @@ -917,12 +891,6 @@ workflows: image_tag: '<< pipeline.parameters.pg15_version >>' requires: [build-15] - - test-arbitrary-configs: - name: 'test-13_check-arbitrary-configs' - pg_major: 13 - image_tag: '<< pipeline.parameters.pg13_version >>' - requires: [build-13] - - test-arbitrary-configs: name: 'test-14_check-arbitrary-configs' pg_major: 14 @@ -935,12 +903,17 @@ workflows: image_tag: '<< pipeline.parameters.pg15_version >>' requires: [build-15] - - test-pg-upgrade: - name: 'test-13-14_check-pg-upgrade' - old_pg_major: 13 - new_pg_major: 14 - image_tag: '<< pipeline.parameters.upgrade_pg_versions >>' - requires: [build-13, build-14] + - test-query-generator: + name: 'test-14_check-query-generator' + pg_major: 14 + image_tag: '<< pipeline.parameters.pg14_version >>' + requires: [build-14] + + - test-query-generator: + name: 'test-15_check-query-generator' + pg_major: 15 + image_tag: '<< pipeline.parameters.pg15_version >>' + requires: [build-15] - test-pg-upgrade: name: 'test-14-15_check-pg-upgrade' @@ -950,31 +923,13 @@ workflows: requires: [build-14, build-15] - test-citus-upgrade: - name: test-13_check-citus-upgrade - pg_major: 13 - image_tag: '<< pipeline.parameters.pg13_version >>' - requires: [build-13] + name: test-14_check-citus-upgrade + pg_major: 14 + image_tag: '<< pipeline.parameters.pg14_version >>' + requires: [build-14] - upload-coverage: requires: - - test-13_check-multi - - test-13_check-multi-1 - - test-13_check-mx - - test-13_check-vanilla - - test-13_check-isolation - - test-13_check-operations - - test-13_check-follower-cluster - - test-13_check-columnar - - test-13_check-columnar-isolation - - test-13_check-failure - - test-13_check-enterprise - - test-13_check-enterprise-isolation - - test-13_check-enterprise-isolation-logicalrep-1 - - test-13_check-enterprise-isolation-logicalrep-2 - - test-13_check-enterprise-isolation-logicalrep-3 - - test-13_check-enterprise-failure - - test-13_check-split - - test-13_check-arbitrary-configs - test-14_check-multi - test-14_check-multi-1 - test-14_check-mx @@ -993,6 +948,7 @@ workflows: - test-14_check-enterprise-failure - test-14_check-split - test-14_check-arbitrary-configs + - test-14_check-query-generator - test-15_check-multi - test-15_check-multi-1 - test-15_check-mx @@ -1011,18 +967,18 @@ workflows: - test-15_check-enterprise-failure - test-15_check-split - test-15_check-arbitrary-configs - - test-13-14_check-pg-upgrade + - test-15_check-query-generator - test-14-15_check-pg-upgrade - - test-13_check-citus-upgrade + - test-14_check-citus-upgrade - ch_benchmark: - requires: [build-13] + requires: [build-14] filters: branches: only: - /ch_benchmark\/.*/ # match with ch_benchmark/ prefix - tpcc_benchmark: - requires: [build-13] + requires: [build-14] filters: branches: only: diff --git a/.flake8 b/.flake8 index 18feeb500..74457e31e 100644 --- a/.flake8 +++ b/.flake8 @@ -4,3 +4,4 @@ extend-ignore = E203 # black will truncate to 88 characters usually, but long string literals it # might keep. That's fine in most cases unless it gets really excessive. max-line-length = 150 +exclude = .git,__pycache__,vendor,tmp_* diff --git a/CHANGELOG.md b/CHANGELOG.md index 428598d1a..de9bfeeb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,200 @@ +### citus v11.3.0 (May 2, 2023) ### + +* Introduces CDC implementation for Citus using logical replication + (#6623, #6810, #6827) + +* Adds support for `MERGE` command on co-located distributed tables joined on + distribution column (#6696, #6733) + +* Adds the view `citus_stat_tenants` that monitor statistics on tenant usages + (#6725) + +* Adds the GUC `citus.max_background_task_executors_per_node` to control number + of background task executors involving a node (#6771) + +* Allows parallel shard moves in background rebalancer (#6756) + +* Introduces the GUC `citus.metadata_sync_mode` that introduces nontransactional + mode for metadata sync (#6728, #6889) + +* Propagates CREATE/ALTER/DROP PUBLICATION statements for distributed tables + (#6776) + +* Adds the GUC `citus.enable_non_colocated_router_query_pushdown` to ensure + generating a consistent distributed plan for the queries that reference + non-colocated distributed tables when set to "false" (#6793) + +* Checks if all moves are able to be done via logical replication for rebalancer + (#6754) + +* Correctly reports shard size in `citus_shards` view (#6748) + +* Fixes a bug in shard copy operations (#6721) + +* Fixes a bug that prevents enforcing identity column restrictions on worker + nodes (#6738) + +* Fixes a bug with `INSERT .. SELECT` queries with identity columns (#6802) + +* Fixes an issue that caused some queries with custom aggregates to fail (#6805) + +* Fixes an issue when `citus_set_coordinator_host` is called more than once + (#6837) + +* Fixes an uninitialized memory access in shard split API (#6845) + +* Fixes memory leak and max allocation block errors during metadata syncing + (#6728) + +* Fixes memory leak in `undistribute_table` (#6693) + +* Fixes memory leak in `alter_distributed_table` (#6726) + +* Fixes memory leak in `create_distributed_table` (#6722) + +* Fixes memory leak issue with query results that returns single row (#6724) + +* Improves rebalancer when shard groups have placement count less than worker + count (#6739) + +* Makes sure to stop maintenance daemon when dropping a database even without + Citus extension (#6688) + +* Prevents using `alter_distributed_table` and `undistribute_table` UDFs when a + table has identity columns (#6738) + +* Prevents using identity columns on data types other than `bigint` on + distributed tables (#6738) + +### citus v11.2.1 (April 20, 2023) ### + +* Correctly reports shard size in `citus_shards` view (#6748) + +* Fixes a bug in shard copy operations (#6721) + +* Fixes a bug with `INSERT .. SELECT` queries with identity columns (#6802) + +* Fixes an uninitialized memory access in shard split API (#6845) + +* Fixes compilation for PG13.10 and PG14.7 (#6711) + +* Fixes memory leak in `alter_distributed_table` (#6726) + +* Fixes memory leak issue with query results that returns single row (#6724) + +* Prevents using `alter_distributed_table` and `undistribute_table` UDFs when a + table has identity columns (#6738) + +* Prevents using identity columns on data types other than `bigint` on + distributed tables (#6738) + +### citus v11.1.6 (April 20, 2023) ### + +* Correctly reports shard size in `citus_shards` view (#6748) + +* Fixes a bug in shard copy operations (#6721) + +* Fixes a bug that breaks pg upgrades if the user has a columnar table (#6624) + +* Fixes a bug that causes background rebalancer to fail when a reference table + doesn't have a primary key (#6682) + +* Fixes a regression in allowed foreign keys on distributed tables (#6550) + +* Fixes a use-after-free bug in connection management (#6685) + +* Fixes an unexpected foreign table error by disallowing to drop the + `table_name` option (#6669) + +* Fixes an uninitialized memory access in shard split API (#6845) + +* Fixes compilation for PG13.10 and PG14.7 (#6711) + +* Fixes crash that happens when trying to replicate a reference table that is + actually dropped (#6595) + +* Fixes memory leak issue with query results that returns single row (#6724) + +* Fixes the modifiers for subscription and role creation (#6603) + +* Makes sure to quote all identifiers used for logical replication to prevent + potential issues (#6604) + +* Makes sure to skip foreign key validations at the end of shard moves (#6640) + +### citus v11.0.8 (April 20, 2023) ### + +* Correctly reports shard size in `citus_shards` view (#6748) + +* Fixes a bug that breaks pg upgrades if the user has a columnar table (#6624) + +* Fixes an unexpected foreign table error by disallowing to drop the + `table_name` option (#6669) + +* Fixes compilation warning on PG13 + OpenSSL 3.0 (#6038, #6502) + +* Fixes crash that happens when trying to replicate a reference table that is + actually dropped (#6595) + +* Fixes memory leak issue with query results that returns single row (#6724) + +* Fixes the modifiers for subscription and role creation (#6603) + +* Fixes two potential dangling pointer issues (#6504, #6507) + +* Makes sure to quote all identifiers used for logical replication to prevent + potential issues (#6604) + +### citus v10.2.9 (April 20, 2023) ### + +* Correctly reports shard size in `citus_shards` view (#6748) + +* Fixes a bug in `ALTER EXTENSION citus UPDATE` (#6383) + +* Fixes a bug that breaks pg upgrades if the user has a columnar table (#6624) + +* Fixes a bug that prevents retaining columnar table options after a + table-rewrite (#6337) + +* Fixes memory leak issue with query results that returns single row (#6724) + +* Raises memory limits in columnar from 256MB to 1GB for reads and writes + (#6419) + +### citus v10.1.6 (April 20, 2023) ### + +* Fixes a crash that occurs when the aggregate that cannot be pushed-down + returns empty result from a worker (#5679) + +* Fixes columnar freezing/wraparound bug (#5962) + +* Fixes memory leak issue with query results that returns single row (#6724) + +* Prevents alter table functions from dropping extensions (#5974) + +### citus v10.0.8 (April 20, 2023) ### + +* Fixes a bug that could break `DROP SCHEMA/EXTENSON` commands when there is a + columnar table (#5458) + +* Fixes a crash that occurs when the aggregate that cannot be pushed-down + returns empty result from a worker (#5679) + +* Fixes columnar freezing/wraparound bug (#5962) + +* Fixes memory leak issue with query results that returns single row (#6724) + +* Prevents alter table functions from dropping extensions (#5974) + +### citus v9.5.12 (April 20, 2023) ### + +* Fixes a crash that occurs when the aggregate that cannot be pushed-down + returns empty result from a worker (#5679) + +* Fixes memory leak issue with query results that returns single row (#6724) + +* Prevents alter table functions from dropping extensions (#5974) + ### citus v11.2.0 (January 30, 2023) ### * Adds support for outer joins with reference tables / complex subquery-CTEs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cff39bf51..66d026b8b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -219,6 +219,18 @@ style `#include` statements like this: Any other SQL you can put directly in the main sql file, e.g. `src/backend/distributed/sql/citus--8.3-1--9.0-1.sql`. +### Backporting a commit to a release branch + +1. Check out the release branch that you want to backport to `git checkout release-11.3` +2. Make sure you have the latest changes `git pull` +3. Create a new release branch with a unique name `git checkout -b release-11.3-` +4. Cherry-pick the commit that you want to backport `git cherry-pick -x ` (the `-x` is important) +5. Push the branch `git push` +6. Wait for tests to pass +7. If the cherry-pick required non-trivial merge conflicts, create a PR and ask + for a review. +8. After the tests pass on CI, fast-forward the release branch `git push origin release-11.3-:release-11.3` + ### Running tests See [`src/test/regress/README.md`](https://github.com/citusdata/citus/blob/master/src/test/regress/README.md) diff --git a/README.md b/README.md index 7a15cd6d8..ae67dadca 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -| **
The Citus database is 100% open source.

Learn what's new in the [Citus 11.2 release blog](https://www.citusdata.com/blog/2023/02/08/whats-new-in-citus-11-2-patroni-ha-support/) and the [Citus Updates page](https://www.citusdata.com/updates/).

**| +| **
The Citus database is 100% open source.

Learn what's new in the [Citus 11.3 release blog](https://www.citusdata.com/blog/2023/05/05/whats-new-in-citus-11-3-multi-tenant-saas/) and the [Citus Updates page](https://www.citusdata.com/updates/).

**| |---|
@@ -8,7 +8,7 @@ [![Latest Docs](https://img.shields.io/badge/docs-latest-brightgreen.svg)](https://docs.citusdata.com/) [![Stack Overflow](https://img.shields.io/badge/Stack%20Overflow-%20-545353?logo=Stack%20Overflow)](https://stackoverflow.com/questions/tagged/citus) -[![Slack Status](https://citus-slack.herokuapp.com/badge.svg)](https://citus-public.slack.com/) +[Slack](https://citus-public.slack.com/) [![Code Coverage](https://codecov.io/gh/citusdata/citus/branch/master/graph/badge.svg)](https://app.codecov.io/gh/citusdata/citus) [![Twitter](https://img.shields.io/twitter/follow/citusdata.svg?label=Follow%20@citusdata)](https://twitter.com/intent/follow?screen_name=citusdata) @@ -94,14 +94,14 @@ Install packages on Ubuntu / Debian: ```bash curl https://install.citusdata.com/community/deb.sh > add-citus-repo.sh sudo bash add-citus-repo.sh -sudo apt-get -y install postgresql-15-citus-11.2 +sudo apt-get -y install postgresql-15-citus-11.3 ``` Install packages on CentOS / Red Hat: ```bash curl https://install.citusdata.com/community/rpm.sh > add-citus-repo.sh sudo bash add-citus-repo.sh -sudo yum install -y citus112_15 +sudo yum install -y citus113_15 ``` To add Citus to your local PostgreSQL database, add the following to `postgresql.conf`: @@ -349,7 +349,7 @@ To learn more about columnar storage, check out the [columnar storage README](ht ## Setting up with High Availability -One of the most popular high availability solutions for PostgreSQL, [Patroni 3.0](https://github.com/zalando/patroni), has [first class support for Citus 10.0 and above](https://patroni.readthedocs.io/en/latest/citus.html#citus), additionally Citus 11.2 ships with improvements for smoother node switchover in Patroni. +One of the most popular high availability solutions for PostgreSQL, [Patroni 3.0](https://github.com/zalando/patroni), has [first class support for Citus 10.0 and above](https://patroni.readthedocs.io/en/latest/citus.html#citus), additionally since Citus 11.2 ships with improvements for smoother node switchover in Patroni. An example of patronictl list output for the Citus cluster: diff --git a/configure b/configure index 65e89799f..9150c1ab2 100755 --- a/configure +++ b/configure @@ -2588,7 +2588,7 @@ fi if test "$with_pg_version_check" = no; then { $as_echo "$as_me:${as_lineno-$LINENO}: building against PostgreSQL $version_num (skipped compatibility check)" >&5 $as_echo "$as_me: building against PostgreSQL $version_num (skipped compatibility check)" >&6;} -elif test "$version_num" != '13' -a "$version_num" != '14' -a "$version_num" != '15'; then +elif test "$version_num" != '14' -a "$version_num" != '15'; then as_fn_error $? "Citus is not compatible with the detected PostgreSQL version ${version_num}." "$LINENO" 5 else { $as_echo "$as_me:${as_lineno-$LINENO}: building against PostgreSQL $version_num" >&5 @@ -6160,3 +6160,4 @@ if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi + diff --git a/configure.ac b/configure.ac index f44e9f177..078e14c42 100644 --- a/configure.ac +++ b/configure.ac @@ -80,7 +80,7 @@ AC_SUBST(with_pg_version_check) if test "$with_pg_version_check" = no; then AC_MSG_NOTICE([building against PostgreSQL $version_num (skipped compatibility check)]) -elif test "$version_num" != '13' -a "$version_num" != '14' -a "$version_num" != '15'; then +elif test "$version_num" != '14' -a "$version_num" != '15'; then AC_MSG_ERROR([Citus is not compatible with the detected PostgreSQL version ${version_num}.]) else AC_MSG_NOTICE([building against PostgreSQL $version_num]) diff --git a/src/backend/columnar/citus_columnar.control b/src/backend/columnar/citus_columnar.control index d60a71bad..d8a54923c 100644 --- a/src/backend/columnar/citus_columnar.control +++ b/src/backend/columnar/citus_columnar.control @@ -1,6 +1,6 @@ # Columnar extension comment = 'Citus Columnar extension' -default_version = '11.2-1' +default_version = '11.3-1' module_pathname = '$libdir/citus_columnar' relocatable = false schema = pg_catalog diff --git a/src/backend/columnar/columnar_customscan.c b/src/backend/columnar/columnar_customscan.c index 74c50e4f6..bd01c4faa 100644 --- a/src/backend/columnar/columnar_customscan.c +++ b/src/backend/columnar/columnar_customscan.c @@ -198,7 +198,7 @@ columnar_customscan_init() &EnableColumnarCustomScan, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( "columnar.enable_qual_pushdown", @@ -208,7 +208,7 @@ columnar_customscan_init() &EnableColumnarQualPushdown, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomRealVariable( "columnar.qual_pushdown_correlation_threshold", @@ -222,7 +222,7 @@ columnar_customscan_init() 0.0, 1.0, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomIntVariable( "columnar.max_custom_scan_paths", @@ -234,7 +234,7 @@ columnar_customscan_init() 1, 1024, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomEnumVariable( "columnar.planner_debug_level", diff --git a/src/backend/columnar/columnar_debug.c b/src/backend/columnar/columnar_debug.c index e6b19f768..cbb0d554f 100644 --- a/src/backend/columnar/columnar_debug.c +++ b/src/backend/columnar/columnar_debug.c @@ -159,5 +159,5 @@ MemoryContextTotals(MemoryContext context, MemoryContextCounters *counters) MemoryContextTotals(child, counters); } - context->methods->stats_compat(context, NULL, NULL, counters, true); + context->methods->stats(context, NULL, NULL, counters, true); } diff --git a/src/backend/columnar/columnar_metadata.c b/src/backend/columnar/columnar_metadata.c index 015df65eb..7fbc96419 100644 --- a/src/backend/columnar/columnar_metadata.c +++ b/src/backend/columnar/columnar_metadata.c @@ -1623,12 +1623,8 @@ StartModifyRelation(Relation rel) { EState *estate = create_estate_for_relation(rel); -#if PG_VERSION_NUM >= PG_VERSION_14 ResultRelInfo *resultRelInfo = makeNode(ResultRelInfo); InitResultRelInfo(resultRelInfo, rel, 1, NULL, 0); -#else - ResultRelInfo *resultRelInfo = estate->es_result_relation_info; -#endif /* ExecSimpleRelationInsert, ... require caller to open indexes */ ExecOpenIndices(resultRelInfo, false); @@ -1658,7 +1654,7 @@ InsertTupleAndEnforceConstraints(ModifyState *state, Datum *values, bool *nulls) ExecStoreHeapTuple(tuple, slot, false); /* use ExecSimpleRelationInsert to enforce constraints */ - ExecSimpleRelationInsert_compat(state->resultRelInfo, state->estate, slot); + ExecSimpleRelationInsert(state->resultRelInfo, state->estate, slot); } @@ -1689,12 +1685,8 @@ FinishModifyRelation(ModifyState *state) ExecCloseIndices(state->resultRelInfo); AfterTriggerEndQuery(state->estate); -#if PG_VERSION_NUM >= PG_VERSION_14 ExecCloseResultRelations(state->estate); ExecCloseRangeTableRelations(state->estate); -#else - ExecCleanUpTriggerState(state->estate); -#endif ExecResetTupleTable(state->estate->es_tupleTable, false); FreeExecutorState(state->estate); @@ -1723,15 +1715,6 @@ create_estate_for_relation(Relation rel) rte->rellockmode = AccessShareLock; ExecInitRangeTable(estate, list_make1(rte)); -#if PG_VERSION_NUM < PG_VERSION_14 - ResultRelInfo *resultRelInfo = makeNode(ResultRelInfo); - InitResultRelInfo(resultRelInfo, rel, 1, NULL, 0); - - estate->es_result_relations = resultRelInfo; - estate->es_num_result_relations = 1; - estate->es_result_relation_info = resultRelInfo; -#endif - estate->es_output_cid = GetCurrentCommandId(true); /* Prepare to catch AFTER triggers. */ diff --git a/src/backend/columnar/columnar_reader.c b/src/backend/columnar/columnar_reader.c index dec19929b..7917a446a 100644 --- a/src/backend/columnar/columnar_reader.c +++ b/src/backend/columnar/columnar_reader.c @@ -1557,7 +1557,7 @@ DeserializeDatumArray(StringInfo datumBuffer, bool *existsArray, uint32 datumCou datumTypeLength); currentDatumDataOffset = att_addlength_datum(currentDatumDataOffset, datumTypeLength, - currentDatumDataPointer); + datumArray[datumIndex]); currentDatumDataOffset = att_align_nominal(currentDatumDataOffset, datumTypeAlign); diff --git a/src/backend/columnar/columnar_tableam.c b/src/backend/columnar/columnar_tableam.c index 07175dcbe..4a08feb54 100644 --- a/src/backend/columnar/columnar_tableam.c +++ b/src/backend/columnar/columnar_tableam.c @@ -115,9 +115,7 @@ static RangeVar * ColumnarProcessAlterTable(AlterTableStmt *alterTableStmt, List **columnarOptions); static void ColumnarProcessUtility(PlannedStmt *pstmt, const char *queryString, -#if PG_VERSION_NUM >= PG_VERSION_14 bool readOnlyTree, -#endif ProcessUtilityContext context, ParamListInfo params, struct QueryEnvironment *queryEnv, @@ -665,7 +663,6 @@ columnar_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot, } -#if PG_VERSION_NUM >= PG_VERSION_14 static TransactionId columnar_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate) @@ -714,19 +711,6 @@ columnar_index_delete_tuples(Relation rel, } -#else -static TransactionId -columnar_compute_xid_horizon_for_tuples(Relation rel, - ItemPointerData *tids, - int nitems) -{ - elog(ERROR, "columnar_compute_xid_horizon_for_tuples not implemented"); -} - - -#endif - - static void columnar_tuple_insert(Relation relation, TupleTableSlot *slot, CommandId cid, int options, BulkInsertState bistate) @@ -1484,8 +1468,7 @@ columnar_index_build_range_scan(Relation columnarRelation, if (!IsBootstrapProcessingMode() && !indexInfo->ii_Concurrent) { /* ignore lazy VACUUM's */ - OldestXmin = GetOldestNonRemovableTransactionId_compat(columnarRelation, - PROCARRAY_FLAGS_VACUUM); + OldestXmin = GetOldestNonRemovableTransactionId(columnarRelation); } Snapshot snapshot = { 0 }; @@ -1813,8 +1796,8 @@ ColumnarReadMissingRowsIntoIndex(TableScanDesc scan, Relation indexRelation, Relation columnarRelation = scan->rs_rd; IndexUniqueCheck indexUniqueCheck = indexInfo->ii_Unique ? UNIQUE_CHECK_YES : UNIQUE_CHECK_NO; - index_insert_compat(indexRelation, indexValues, indexNulls, columnarItemPointer, - columnarRelation, indexUniqueCheck, false, indexInfo); + index_insert(indexRelation, indexValues, indexNulls, columnarItemPointer, + columnarRelation, indexUniqueCheck, false, indexInfo); validateIndexState->tups_inserted += 1; } @@ -2018,7 +2001,7 @@ columnar_tableam_init() &EnableVersionChecksColumnar, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); } @@ -2240,21 +2223,17 @@ ColumnarProcessAlterTable(AlterTableStmt *alterTableStmt, List **columnarOptions static void ColumnarProcessUtility(PlannedStmt *pstmt, const char *queryString, -#if PG_VERSION_NUM >= PG_VERSION_14 bool readOnlyTree, -#endif ProcessUtilityContext context, ParamListInfo params, struct QueryEnvironment *queryEnv, DestReceiver *dest, QueryCompletion *completionTag) { -#if PG_VERSION_NUM >= PG_VERSION_14 if (readOnlyTree) { pstmt = copyObject(pstmt); } -#endif Node *parsetree = pstmt->utilityStmt; @@ -2371,8 +2350,8 @@ ColumnarProcessUtility(PlannedStmt *pstmt, CheckCitusColumnarAlterExtensionStmt(parsetree); } - PrevProcessUtilityHook_compat(pstmt, queryString, false, context, - params, queryEnv, dest, completionTag); + PrevProcessUtilityHook(pstmt, queryString, false, context, + params, queryEnv, dest, completionTag); if (columnarOptions != NIL) { @@ -2500,11 +2479,7 @@ static const TableAmRoutine columnar_am_methods = { .tuple_get_latest_tid = columnar_get_latest_tid, .tuple_tid_valid = columnar_tuple_tid_valid, .tuple_satisfies_snapshot = columnar_tuple_satisfies_snapshot, -#if PG_VERSION_NUM >= PG_VERSION_14 .index_delete_tuples = columnar_index_delete_tuples, -#else - .compute_xid_horizon_for_tuples = columnar_compute_xid_horizon_for_tuples, -#endif .tuple_insert = columnar_tuple_insert, .tuple_insert_speculative = columnar_tuple_insert_speculative, diff --git a/src/backend/columnar/sql/citus_columnar--11.2-1--11.3-1.sql b/src/backend/columnar/sql/citus_columnar--11.2-1--11.3-1.sql new file mode 100644 index 000000000..18fa4963c --- /dev/null +++ b/src/backend/columnar/sql/citus_columnar--11.2-1--11.3-1.sql @@ -0,0 +1 @@ +-- citus_columnar--11.2-1--11.3-1 diff --git a/src/backend/columnar/sql/downgrades/citus_columnar--11.3-1--11.2-1.sql b/src/backend/columnar/sql/downgrades/citus_columnar--11.3-1--11.2-1.sql new file mode 100644 index 000000000..50f6a2011 --- /dev/null +++ b/src/backend/columnar/sql/downgrades/citus_columnar--11.3-1--11.2-1.sql @@ -0,0 +1 @@ +-- citus_columnar--11.3-1--11.2-1 diff --git a/src/backend/distributed/cdc/cdc_decoder.c b/src/backend/distributed/cdc/cdc_decoder.c index 9dfb8bc12..2beb27772 100644 --- a/src/backend/distributed/cdc/cdc_decoder.c +++ b/src/backend/distributed/cdc/cdc_decoder.c @@ -203,8 +203,7 @@ AddShardIdToHashTable(uint64 shardId, ShardIdHashEntry *entry) { entry->shardId = shardId; entry->distributedTableId = CdcLookupShardRelationFromCatalog(shardId, true); - entry->isReferenceTable = CdcPartitionMethodViaCatalog(entry->distributedTableId) == - 'n'; + entry->isReferenceTable = CdcIsReferenceTableViaCatalog(entry->distributedTableId); return entry->distributedTableId; } @@ -361,12 +360,14 @@ GetTupleForTargetSchemaForCdc(HeapTuple sourceRelationTuple, targetNulls[targetIndex] = true; targetIndex++; } + /* If this source attribute has been dropped, just skip this source attribute.*/ else if (TupleDescAttr(sourceRelDesc, sourceIndex)->attisdropped) { sourceIndex++; continue; } + /* If both source and target attributes are not dropped, add the attribute field to targetValues. */ else if (sourceIndex < sourceRelDesc->natts) { diff --git a/src/backend/distributed/cdc/cdc_decoder_utils.c b/src/backend/distributed/cdc/cdc_decoder_utils.c index 272221a5f..a69f307ba 100644 --- a/src/backend/distributed/cdc/cdc_decoder_utils.c +++ b/src/backend/distributed/cdc/cdc_decoder_utils.c @@ -331,16 +331,16 @@ CdcPgDistPartitionTupleViaCatalog(Oid relationId) /* - * CdcPartitionMethodViaCatalog gets a relationId and returns the partition - * method column from pg_dist_partition via reading from catalog. + * CdcIsReferenceTableViaCatalog gets a relationId and returns true if the relation + * is a reference table and false otherwise. */ char -CdcPartitionMethodViaCatalog(Oid relationId) +CdcIsReferenceTableViaCatalog(Oid relationId) { HeapTuple partitionTuple = CdcPgDistPartitionTupleViaCatalog(relationId); if (!HeapTupleIsValid(partitionTuple)) { - return DISTRIBUTE_BY_INVALID; + return false; } Datum datumArray[Natts_pg_dist_partition]; @@ -351,21 +351,32 @@ CdcPartitionMethodViaCatalog(Oid relationId) TupleDesc tupleDescriptor = RelationGetDescr(pgDistPartition); heap_deform_tuple(partitionTuple, tupleDescriptor, datumArray, isNullArray); - if (isNullArray[Anum_pg_dist_partition_partmethod - 1]) + if (isNullArray[Anum_pg_dist_partition_partmethod - 1] || + isNullArray[Anum_pg_dist_partition_repmodel - 1]) { - /* partition method cannot be NULL, still let's make sure */ + /* + * partition method and replication model cannot be NULL, + * still let's make sure + */ heap_freetuple(partitionTuple); table_close(pgDistPartition, NoLock); - return DISTRIBUTE_BY_INVALID; + return false; } Datum partitionMethodDatum = datumArray[Anum_pg_dist_partition_partmethod - 1]; char partitionMethodChar = DatumGetChar(partitionMethodDatum); + Datum replicationModelDatum = datumArray[Anum_pg_dist_partition_repmodel - 1]; + char replicationModelChar = DatumGetChar(replicationModelDatum); + heap_freetuple(partitionTuple); table_close(pgDistPartition, NoLock); - return partitionMethodChar; + /* + * A table is a reference table when its partition method is 'none' + * and replication model is 'two phase commit' + */ + return partitionMethodChar == 'n' && replicationModelChar == 't'; } diff --git a/src/backend/distributed/cdc/cdc_decoder_utils.h b/src/backend/distributed/cdc/cdc_decoder_utils.h index d30500de4..46d1e4ae5 100644 --- a/src/backend/distributed/cdc/cdc_decoder_utils.h +++ b/src/backend/distributed/cdc/cdc_decoder_utils.h @@ -25,7 +25,7 @@ uint64 CdcExtractShardIdFromTableName(const char *tableName, bool missingOk); Oid CdcLookupShardRelationFromCatalog(int64 shardId, bool missingOk); -char CdcPartitionMethodViaCatalog(Oid relationId); +char CdcIsReferenceTableViaCatalog(Oid relationId); bool CdcCitusHasBeenLoaded(void); diff --git a/src/backend/distributed/commands/alter_table.c b/src/backend/distributed/commands/alter_table.c index 5ed82f760..788a3b8b0 100644 --- a/src/backend/distributed/commands/alter_table.c +++ b/src/backend/distributed/commands/alter_table.c @@ -196,6 +196,7 @@ static void EnsureTableNotReferencing(Oid relationId, char conversionType); static void EnsureTableNotReferenced(Oid relationId, char conversionType); static void EnsureTableNotForeign(Oid relationId); static void EnsureTableNotPartition(Oid relationId); +static void ErrorIfColocateWithTenantTable(char *colocateWith); static TableConversionState * CreateTableConversion(TableConversionParameters *params); static void CreateDistributedTableLike(TableConversionState *con); static void CreateCitusTableLike(TableConversionState *con); @@ -247,7 +248,8 @@ undistribute_table(PG_FUNCTION_ARGS) TableConversionParameters params = { .relationId = relationId, - .cascadeViaForeignKeys = cascadeViaForeignKeys + .cascadeViaForeignKeys = cascadeViaForeignKeys, + .bypassTenantCheck = false }; UndistributeTable(¶ms); @@ -360,6 +362,124 @@ worker_change_sequence_dependency(PG_FUNCTION_ARGS) } +/* + * DropFKeysAndUndistributeTable drops all foreign keys that relation with + * relationId is involved then undistributes it. + * Note that as UndistributeTable changes relationId of relation, this + * function also returns new relationId of relation. + * Also note that callers are responsible for storing & recreating foreign + * keys to be dropped if needed. + */ +Oid +DropFKeysAndUndistributeTable(Oid relationId) +{ + DropFKeysRelationInvolvedWithTableType(relationId, INCLUDE_ALL_TABLE_TYPES); + + /* store them before calling UndistributeTable as it changes relationId */ + char *relationName = get_rel_name(relationId); + Oid schemaId = get_rel_namespace(relationId); + + /* suppress notices messages not to be too verbose */ + TableConversionParameters params = { + .relationId = relationId, + .cascadeViaForeignKeys = false, + .suppressNoticeMessages = true + }; + UndistributeTable(¶ms); + + Oid newRelationId = get_relname_relid(relationName, schemaId); + + /* + * We don't expect this to happen but to be on the safe side let's error + * out here. + */ + EnsureRelationExists(newRelationId); + + return newRelationId; +} + + +/* + * UndistributeTables undistributes given relations. It first collects all foreign keys + * to recreate them after the undistribution. Then, drops the foreign keys and + * undistributes the relations. Finally, it recreates foreign keys. + */ +void +UndistributeTables(List *relationIdList) +{ + /* + * Collect foreign keys for recreation and then drop fkeys and undistribute + * tables. + */ + List *originalForeignKeyRecreationCommands = NIL; + Oid relationId = InvalidOid; + foreach_oid(relationId, relationIdList) + { + List *fkeyCommandsForRelation = + GetFKeyCreationCommandsRelationInvolvedWithTableType(relationId, + INCLUDE_ALL_TABLE_TYPES); + originalForeignKeyRecreationCommands = list_concat( + originalForeignKeyRecreationCommands, fkeyCommandsForRelation); + DropFKeysAndUndistributeTable(relationId); + } + + /* We can skip foreign key validations as we are sure about them at start */ + bool skip_validation = true; + ExecuteForeignKeyCreateCommandList(originalForeignKeyRecreationCommands, + skip_validation); +} + + +/* + * EnsureUndistributeTenantTableSafe ensures that it is safe to undistribute a tenant table. + */ +void +EnsureUndistributeTenantTableSafe(Oid relationId, const char *operationName) +{ + Oid schemaId = get_rel_namespace(relationId); + Assert(IsTenantSchema(schemaId)); + + /* We only allow undistribute while altering schema */ + if (strcmp(operationName, TenantOperationNames[TENANT_SET_SCHEMA]) != 0) + { + ErrorIfTenantTable(relationId, operationName); + } + + char *tableName = get_rel_name(relationId); + char *schemaName = get_namespace_name(schemaId); + + /* + * Partition table cannot be undistributed. Otherwise, its parent table would still + * be a tenant table whereas partition table would be a local table. + */ + if (PartitionTable(relationId)) + { + ereport(ERROR, (errmsg("%s is not allowed for partition table %s in distributed " + "schema %s", operationName, tableName, schemaName), + errdetail("partition table should be under the same distributed " + "schema as its parent and be a " + "distributed schema table."))); + } + + /* + * When table is referenced by or referencing to a table in the same tenant + * schema, we should disallow undistributing the table since we do not allow + * foreign keys from/to Citus local or Postgres local table to/from distributed + * schema. + */ + List *fkeyCommandsWithSingleShardTables = + GetFKeyCreationCommandsRelationInvolvedWithTableType( + relationId, INCLUDE_SINGLE_SHARD_TABLES); + if (fkeyCommandsWithSingleShardTables != NIL) + { + ereport(ERROR, (errmsg("%s is not allowed for table %s in distributed schema %s", + operationName, tableName, schemaName), + errdetail("distributed schemas cannot have foreign keys from/to " + "local tables or different schema"))); + } +} + + /* * UndistributeTable undistributes the given table. It uses ConvertTable function to * create a new local table and move everything to that table. @@ -380,6 +500,14 @@ UndistributeTable(TableConversionParameters *params) "because the table is not distributed"))); } + Oid schemaId = get_rel_namespace(params->relationId); + if (!params->bypassTenantCheck && IsTenantSchema(schemaId) && + IsCitusTableType(params->relationId, SINGLE_SHARD_DISTRIBUTED)) + { + EnsureUndistributeTenantTableSafe(params->relationId, + TenantOperationNames[TENANT_UNDISTRIBUTE_TABLE]); + } + if (!params->cascadeViaForeignKeys) { EnsureTableNotReferencing(params->relationId, UNDISTRIBUTE_TABLE); @@ -435,6 +563,9 @@ AlterDistributedTable(TableConversionParameters *params) "is not distributed"))); } + ErrorIfTenantTable(params->relationId, TenantOperationNames[TENANT_ALTER_TABLE]); + ErrorIfColocateWithTenantTable(params->colocateWith); + EnsureTableNotForeign(params->relationId); EnsureTableNotPartition(params->relationId); EnsureHashDistributedTable(params->relationId); @@ -477,8 +608,11 @@ AlterTableSetAccessMethod(TableConversionParameters *params) EnsureTableNotReferencing(params->relationId, ALTER_TABLE_SET_ACCESS_METHOD); EnsureTableNotReferenced(params->relationId, ALTER_TABLE_SET_ACCESS_METHOD); EnsureTableNotForeign(params->relationId); - if (IsCitusTableType(params->relationId, DISTRIBUTED_TABLE)) + + if (!IsCitusTableType(params->relationId, SINGLE_SHARD_DISTRIBUTED) && + IsCitusTableType(params->relationId, DISTRIBUTED_TABLE)) { + /* we do not support non-hash distributed tables, except single shard tables */ EnsureHashDistributedTable(params->relationId); } @@ -1177,6 +1311,25 @@ EnsureTableNotPartition(Oid relationId) } +/* + * ErrorIfColocateWithTenantTable errors out if given colocateWith text refers to + * a tenant table. + */ +void +ErrorIfColocateWithTenantTable(char *colocateWith) +{ + if (colocateWith != NULL && + !IsColocateWithDefault(colocateWith) && + !IsColocateWithNone(colocateWith)) + { + text *colocateWithTableNameText = cstring_to_text(colocateWith); + Oid colocateWithTableId = ResolveRelationId(colocateWithTableNameText, false); + ErrorIfTenantTable(colocateWithTableId, + TenantOperationNames[TENANT_COLOCATE_WITH]); + } +} + + TableConversionState * CreateTableConversion(TableConversionParameters *params) { @@ -1365,7 +1518,19 @@ CreateCitusTableLike(TableConversionState *con) { if (IsCitusTableType(con->relationId, DISTRIBUTED_TABLE)) { - CreateDistributedTableLike(con); + if (IsCitusTableType(con->relationId, SINGLE_SHARD_DISTRIBUTED)) + { + ColocationParam colocationParam = { + .colocationParamType = COLOCATE_WITH_TABLE_LIKE_OPT, + .colocateWithTableName = quote_qualified_identifier(con->schemaName, + con->relationName) + }; + CreateSingleShardTable(con->newRelationId, colocationParam); + } + else + { + CreateDistributedTableLike(con); + } } else if (IsCitusTableType(con->relationId, REFERENCE_TABLE)) { @@ -1710,20 +1875,13 @@ ReplaceTable(Oid sourceId, Oid targetId, List *justBeforeDropCommands, } else if (ShouldSyncTableMetadata(sourceId)) { - char *qualifiedTableName = quote_qualified_identifier(schemaName, sourceName); - /* * We are converting a citus local table to a distributed/reference table, * so we should prevent dropping the sequence on the table. Otherwise, we'd * lose track of the previous changes in the sequence. */ - StringInfo command = makeStringInfo(); - - appendStringInfo(command, - "SELECT pg_catalog.worker_drop_sequence_dependency(%s);", - quote_literal_cstr(qualifiedTableName)); - - SendCommandToWorkersWithMetadata(command->data); + char *command = WorkerDropSequenceDependencyCommand(sourceId); + SendCommandToWorkersWithMetadata(command); } } @@ -1863,6 +2021,12 @@ CheckAlterDistributedTableConversionParameters(TableConversionState *con) "it is not a distributed table", con->colocateWith))); } + else if (IsCitusTableType(colocateWithTableOid, SINGLE_SHARD_DISTRIBUTED)) + { + ereport(ERROR, (errmsg("cannot colocate with %s because " + "it is a single shard distributed table", + con->colocateWith))); + } } /* shard_count:=0 is not allowed */ diff --git a/src/backend/distributed/commands/cascade_table_operation_for_connected_relations.c b/src/backend/distributed/commands/cascade_table_operation_for_connected_relations.c index 1c01028d3..1102a3a51 100644 --- a/src/backend/distributed/commands/cascade_table_operation_for_connected_relations.c +++ b/src/backend/distributed/commands/cascade_table_operation_for_connected_relations.c @@ -468,7 +468,8 @@ ExecuteCascadeOperationForRelationIdList(List *relationIdList, { TableConversionParameters params = { .relationId = relationId, - .cascadeViaForeignKeys = cascadeViaForeignKeys + .cascadeViaForeignKeys = cascadeViaForeignKeys, + .bypassTenantCheck = false }; UndistributeTable(¶ms); } diff --git a/src/backend/distributed/commands/citus_add_local_table_to_metadata.c b/src/backend/distributed/commands/citus_add_local_table_to_metadata.c index 41ec7e1b1..d1ba25c22 100644 --- a/src/backend/distributed/commands/citus_add_local_table_to_metadata.c +++ b/src/backend/distributed/commands/citus_add_local_table_to_metadata.c @@ -18,6 +18,7 @@ */ #include "postgres.h" +#include "miscadmin.h" #include "access/genam.h" #include "access/htup_details.h" @@ -54,7 +55,7 @@ * This is used after every CREATE TABLE statement in utility_hook.c * If this variable is set to true, we add all created tables to metadata. */ -bool AddAllLocalTablesToMetadata = true; +bool AddAllLocalTablesToMetadata = false; static void citus_add_local_table_to_metadata_internal(Oid relationId, bool cascadeViaForeignKeys); @@ -1500,3 +1501,38 @@ FinalizeCitusLocalTableCreation(Oid relationId) InvalidateForeignKeyGraph(); } } + + +/* + * ShouldAddNewTableToMetadata takes a relationId and returns true if we need to add a + * newly created table to metadata, false otherwise. + * For partitions and temporary tables, ShouldAddNewTableToMetadata returns false. + * For other tables created, returns true, if we are on a coordinator that is added + * as worker, and ofcourse, if the GUC use_citus_managed_tables is set to on. + */ +bool +ShouldAddNewTableToMetadata(Oid relationId) +{ + if (get_rel_persistence(relationId) == RELPERSISTENCE_TEMP || + PartitionTableNoLock(relationId)) + { + /* + * Shouldn't add table to metadata if it's a temp table, or a partition. + * Creating partitions of a table that is added to metadata is already handled. + */ + return false; + } + + if (AddAllLocalTablesToMetadata && !IsBinaryUpgrade && + IsCoordinator() && CoordinatorAddedAsWorkerNode()) + { + /* + * We have verified that the GUC is set to true, and we are not upgrading, + * and we are on the coordinator that is added as worker node. + * So return true here, to add this newly created table to metadata. + */ + return true; + } + + return false; +} diff --git a/src/backend/distributed/commands/citus_global_signal.c b/src/backend/distributed/commands/citus_global_signal.c index 05b210ee2..8183d6673 100644 --- a/src/backend/distributed/commands/citus_global_signal.c +++ b/src/backend/distributed/commands/citus_global_signal.c @@ -81,13 +81,6 @@ CitusSignalBackend(uint64 globalPID, uint64 timeout, int sig) { Assert((sig == SIGINT) || (sig == SIGTERM)); -#if PG_VERSION_NUM < PG_VERSION_14 - if (timeout != 0) - { - elog(ERROR, "timeout parameter is only supported on Postgres 14 or later"); - } -#endif - bool missingOk = false; int nodeId = ExtractNodeIdFromGlobalPID(globalPID, missingOk); int processId = ExtractProcessIdFromGlobalPID(globalPID); @@ -102,14 +95,9 @@ CitusSignalBackend(uint64 globalPID, uint64 timeout, int sig) } else { -#if PG_VERSION_NUM >= PG_VERSION_14 appendStringInfo(cancelQuery, "SELECT pg_terminate_backend(%d::integer, %lu::bigint)", processId, timeout); -#else - appendStringInfo(cancelQuery, "SELECT pg_terminate_backend(%d::integer)", - processId); -#endif } int connectionFlags = 0; diff --git a/src/backend/distributed/commands/cluster.c b/src/backend/distributed/commands/cluster.c index 4cffbaf51..92fcb3ec6 100644 --- a/src/backend/distributed/commands/cluster.c +++ b/src/backend/distributed/commands/cluster.c @@ -114,13 +114,6 @@ PreprocessClusterStmt(Node *node, const char *clusterCommand, static bool IsClusterStmtVerbose_compat(ClusterStmt *clusterStmt) { -#if PG_VERSION_NUM < PG_VERSION_14 - if (clusterStmt->options & CLUOPT_VERBOSE) - { - return true; - } - return false; -#else DefElem *opt = NULL; foreach_ptr(opt, clusterStmt->params) { @@ -130,5 +123,4 @@ IsClusterStmtVerbose_compat(ClusterStmt *clusterStmt) } } return false; -#endif } diff --git a/src/backend/distributed/commands/collation.c b/src/backend/distributed/commands/collation.c index 879dbeeba..eb4dd9654 100644 --- a/src/backend/distributed/commands/collation.c +++ b/src/backend/distributed/commands/collation.c @@ -109,7 +109,7 @@ CreateCollationDDLInternal(Oid collationId, Oid *collowner, char **quotedCollati colliculocale = NULL; } - AssertArg((collcollate && collctype) || colliculocale); + Assert((collcollate && collctype) || colliculocale); #else /* diff --git a/src/backend/distributed/commands/create_distributed_table.c b/src/backend/distributed/commands/create_distributed_table.c index a4fb89b87..8810e6db9 100644 --- a/src/backend/distributed/commands/create_distributed_table.c +++ b/src/backend/distributed/commands/create_distributed_table.c @@ -111,8 +111,8 @@ typedef struct { int shardCount; bool shardCountIsStrict; - char *colocateWithTableName; char *distributionColumnName; + ColocationParam colocationParam; } DistributedTableParams; @@ -141,6 +141,8 @@ static void CreateCitusTable(Oid relationId, CitusTableType tableType, DistributedTableParams *distributedTableParams); static void CreateHashDistributedTableShards(Oid relationId, int shardCount, Oid colocatedTableId, bool localTableEmpty); +static void CreateSingleShardTableShard(Oid relationId, Oid colocatedTableId, + uint32 colocationId); static uint32 ColocationIdForNewTable(Oid relationId, CitusTableType tableType, DistributedTableParams *distributedTableParams, Var *distributionColumn); @@ -157,10 +159,6 @@ static void EnsureCitusTableCanBeCreated(Oid relationOid); static void PropagatePrerequisiteObjectsForDistributedTable(Oid relationId); static void EnsureDistributedSequencesHaveOneType(Oid relationId, List *seqInfoList); -static List * GetFKeyCreationCommandsRelationInvolvedWithTableType(Oid relationId, - int tableTypeFlag); -static Oid DropFKeysAndUndistributeTable(Oid relationId); -static void DropFKeysRelationInvolvedWithTableType(Oid relationId, int tableTypeFlag); static void CopyLocalDataIntoShards(Oid relationId); static List * TupleDescColumnNameList(TupleDesc tupleDescriptor); @@ -216,71 +214,91 @@ create_distributed_table(PG_FUNCTION_ARGS) { CheckCitusVersion(ERROR); - if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) || PG_ARGISNULL(3)) + if (PG_ARGISNULL(0) || PG_ARGISNULL(3)) { PG_RETURN_VOID(); } Oid relationId = PG_GETARG_OID(0); - text *distributionColumnText = PG_GETARG_TEXT_P(1); + text *distributionColumnText = PG_ARGISNULL(1) ? NULL : PG_GETARG_TEXT_P(1); Oid distributionMethodOid = PG_GETARG_OID(2); text *colocateWithTableNameText = PG_GETARG_TEXT_P(3); char *colocateWithTableName = text_to_cstring(colocateWithTableNameText); bool shardCountIsStrict = false; - int shardCount = ShardCount; - if (!PG_ARGISNULL(4)) + if (distributionColumnText) { - if (pg_strncasecmp(colocateWithTableName, "default", NAMEDATALEN) != 0 && - pg_strncasecmp(colocateWithTableName, "none", NAMEDATALEN) != 0) + if (PG_ARGISNULL(2)) { - ereport(ERROR, (errmsg("Cannot use colocate_with with a table " - "and shard_count at the same time"))); + PG_RETURN_VOID(); } - shardCount = PG_GETARG_INT32(4); + int shardCount = ShardCount; + if (!PG_ARGISNULL(4)) + { + if (!IsColocateWithDefault(colocateWithTableName) && + !IsColocateWithNone(colocateWithTableName)) + { + ereport(ERROR, (errmsg("Cannot use colocate_with with a table " + "and shard_count at the same time"))); + } - /* - * if shard_count parameter is given than we have to - * make sure table has that many shards - */ - shardCountIsStrict = true; + shardCount = PG_GETARG_INT32(4); + + /* + * If shard_count parameter is given, then we have to + * make sure table has that many shards. + */ + shardCountIsStrict = true; + } + + char *distributionColumnName = text_to_cstring(distributionColumnText); + Assert(distributionColumnName != NULL); + + char distributionMethod = LookupDistributionMethod(distributionMethodOid); + + if (shardCount < 1 || shardCount > MAX_SHARD_COUNT) + { + ereport(ERROR, (errmsg("%d is outside the valid range for " + "parameter \"shard_count\" (1 .. %d)", + shardCount, MAX_SHARD_COUNT))); + } + + CreateDistributedTable(relationId, distributionColumnName, distributionMethod, + shardCount, shardCountIsStrict, colocateWithTableName); } - - EnsureCitusTableCanBeCreated(relationId); - - /* enable create_distributed_table on an empty node */ - InsertCoordinatorIfClusterEmpty(); - - /* - * Lock target relation with an exclusive lock - there's no way to make - * sense of this table until we've committed, and we don't want multiple - * backends manipulating this relation. - */ - Relation relation = try_relation_open(relationId, ExclusiveLock); - if (relation == NULL) + else { - ereport(ERROR, (errmsg("could not create distributed table: " - "relation does not exist"))); + if (!PG_ARGISNULL(4)) + { + ereport(ERROR, (errmsg("shard_count can't be specified when the " + "distribution column is null because in " + "that case it's automatically set to 1"))); + } + + if (!PG_ARGISNULL(2) && + LookupDistributionMethod(PG_GETARG_OID(2)) != DISTRIBUTE_BY_HASH) + { + /* + * As we do for shard_count parameter, we could throw an error if + * distribution_type is not NULL when creating a single-shard table. + * However, this requires changing the default value of distribution_type + * parameter to NULL and this would mean a breaking change for most + * users because they're mostly using this API to create sharded + * tables. For this reason, here we instead do nothing if the distribution + * method is DISTRIBUTE_BY_HASH. + */ + ereport(ERROR, (errmsg("distribution_type can't be specified " + "when the distribution column is null "))); + } + + ColocationParam colocationParam = { + .colocationParamType = COLOCATE_WITH_TABLE_LIKE_OPT, + .colocateWithTableName = colocateWithTableName, + }; + CreateSingleShardTable(relationId, colocationParam); } - relation_close(relation, NoLock); - - char *distributionColumnName = text_to_cstring(distributionColumnText); - Assert(distributionColumnName != NULL); - - char distributionMethod = LookupDistributionMethod(distributionMethodOid); - - if (shardCount < 1 || shardCount > MAX_SHARD_COUNT) - { - ereport(ERROR, (errmsg("%d is outside the valid range for " - "parameter \"shard_count\" (1 .. %d)", - shardCount, MAX_SHARD_COUNT))); - } - - CreateDistributedTable(relationId, distributionColumnName, distributionMethod, - shardCount, shardCountIsStrict, colocateWithTableName); - PG_RETURN_VOID(); } @@ -295,11 +313,18 @@ create_distributed_table_concurrently(PG_FUNCTION_ARGS) { CheckCitusVersion(ERROR); - if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) || PG_ARGISNULL(3)) + if (PG_ARGISNULL(0) || PG_ARGISNULL(2) || PG_ARGISNULL(3)) { PG_RETURN_VOID(); } + if (PG_ARGISNULL(1)) + { + ereport(ERROR, (errmsg("cannot use create_distributed_table_concurrently " + "to create a distributed table with a null shard " + "key, consider using create_distributed_table()"))); + } + Oid relationId = PG_GETARG_OID(0); text *distributionColumnText = PG_GETARG_TEXT_P(1); char *distributionColumnName = text_to_cstring(distributionColumnText); @@ -887,38 +912,6 @@ create_reference_table(PG_FUNCTION_ARGS) CheckCitusVersion(ERROR); Oid relationId = PG_GETARG_OID(0); - EnsureCitusTableCanBeCreated(relationId); - - /* enable create_reference_table on an empty node */ - InsertCoordinatorIfClusterEmpty(); - - /* - * Lock target relation with an exclusive lock - there's no way to make - * sense of this table until we've committed, and we don't want multiple - * backends manipulating this relation. - */ - Relation relation = try_relation_open(relationId, ExclusiveLock); - if (relation == NULL) - { - ereport(ERROR, (errmsg("could not create reference table: " - "relation does not exist"))); - } - - relation_close(relation, NoLock); - - List *workerNodeList = ActivePrimaryNodeList(ShareLock); - int workerCount = list_length(workerNodeList); - - /* if there are no workers, error out */ - if (workerCount == 0) - { - char *relationName = get_rel_name(relationId); - - ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("cannot create reference table \"%s\"", relationName), - errdetail("There are no active worker nodes."))); - } - CreateReferenceTable(relationId); PG_RETURN_VOID(); } @@ -1013,7 +1006,10 @@ CreateDistributedTable(Oid relationId, char *distributionColumnName, } DistributedTableParams distributedTableParams = { - .colocateWithTableName = colocateWithTableName, + .colocationParam = { + .colocateWithTableName = colocateWithTableName, + .colocationParamType = COLOCATE_WITH_TABLE_LIKE_OPT + }, .shardCount = shardCount, .shardCountIsStrict = shardCountIsStrict, .distributionColumnName = distributionColumnName @@ -1033,6 +1029,23 @@ CreateReferenceTable(Oid relationId) } +/* + * CreateSingleShardTable is a wrapper around CreateCitusTable that creates a + * single shard distributed table that doesn't have a shard key. + */ +void +CreateSingleShardTable(Oid relationId, ColocationParam colocationParam) +{ + DistributedTableParams distributedTableParams = { + .colocationParam = colocationParam, + .shardCount = 1, + .shardCountIsStrict = true, + .distributionColumnName = NULL + }; + CreateCitusTable(relationId, SINGLE_SHARD_DISTRIBUTED, &distributedTableParams); +} + + /* * CreateCitusTable is the internal method that creates a Citus table in * given configuration. @@ -1051,13 +1064,36 @@ CreateCitusTable(Oid relationId, CitusTableType tableType, DistributedTableParams *distributedTableParams) { if ((tableType == HASH_DISTRIBUTED || tableType == APPEND_DISTRIBUTED || - tableType == RANGE_DISTRIBUTED) != (distributedTableParams != NULL)) + tableType == RANGE_DISTRIBUTED || tableType == SINGLE_SHARD_DISTRIBUTED) != + (distributedTableParams != NULL)) { ereport(ERROR, (errmsg("distributed table params must be provided " "when creating a distributed table and must " "not be otherwise"))); } + EnsureCitusTableCanBeCreated(relationId); + + /* allow creating a Citus table on an empty cluster */ + InsertCoordinatorIfClusterEmpty(); + + Relation relation = try_relation_open(relationId, ExclusiveLock); + if (relation == NULL) + { + ereport(ERROR, (errmsg("could not create Citus table: " + "relation does not exist"))); + } + + relation_close(relation, NoLock); + + if (tableType == SINGLE_SHARD_DISTRIBUTED && ShardReplicationFactor > 1) + { + ereport(ERROR, (errmsg("could not create single shard table: " + "citus.shard_replication_factor is greater than 1"), + errhint("Consider setting citus.shard_replication_factor to 1 " + "and try again"))); + } + /* * EnsureTableNotDistributed errors out when relation is a citus table but * we don't want to ask user to first undistribute their citus local tables @@ -1115,7 +1151,7 @@ CreateCitusTable(Oid relationId, CitusTableType tableType, PropagatePrerequisiteObjectsForDistributedTable(relationId); Var *distributionColumn = NULL; - if (distributedTableParams) + if (distributedTableParams && distributedTableParams->distributionColumnName) { distributionColumn = BuildDistributionKeyFromColumnName(relationId, distributedTableParams-> @@ -1130,9 +1166,23 @@ CreateCitusTable(Oid relationId, CitusTableType tableType, * ColocationIdForNewTable assumes caller acquires lock on relationId. In our case, * our caller already acquired lock on relationId. */ - uint32 colocationId = ColocationIdForNewTable(relationId, tableType, - distributedTableParams, - distributionColumn); + uint32 colocationId = INVALID_COLOCATION_ID; + if (distributedTableParams && + distributedTableParams->colocationParam.colocationParamType == + COLOCATE_WITH_COLOCATION_ID) + { + colocationId = distributedTableParams->colocationParam.colocationId; + } + else + { + /* + * ColocationIdForNewTable assumes caller acquires lock on relationId. In our case, + * our caller already acquired lock on relationId. + */ + colocationId = ColocationIdForNewTable(relationId, tableType, + distributedTableParams, + distributionColumn); + } EnsureRelationCanBeDistributed(relationId, distributionColumn, citusTableParams.distributionMethod, @@ -1187,6 +1237,11 @@ CreateCitusTable(Oid relationId, CitusTableType tableType, { CreateReferenceTableShard(relationId); } + else if (tableType == SINGLE_SHARD_DISTRIBUTED) + { + CreateSingleShardTableShard(relationId, colocatedTableId, + colocationId); + } if (ShouldSyncTableMetadata(relationId)) { @@ -1227,7 +1282,10 @@ CreateCitusTable(Oid relationId, CitusTableType tableType, MemoryContextReset(citusPartitionContext); DistributedTableParams childDistributedTableParams = { - .colocateWithTableName = parentRelationName, + .colocationParam = { + .colocationParamType = COLOCATE_WITH_TABLE_LIKE_OPT, + .colocateWithTableName = parentRelationName, + }, .shardCount = distributedTableParams->shardCount, .shardCountIsStrict = false, .distributionColumnName = distributedTableParams->distributionColumnName, @@ -1241,7 +1299,8 @@ CreateCitusTable(Oid relationId, CitusTableType tableType, } /* copy over data for hash distributed and reference tables */ - if (tableType == HASH_DISTRIBUTED || tableType == REFERENCE_TABLE) + if (tableType == HASH_DISTRIBUTED || tableType == SINGLE_SHARD_DISTRIBUTED || + tableType == REFERENCE_TABLE) { if (RegularTable(relationId)) { @@ -1277,34 +1336,50 @@ DecideCitusTableParams(CitusTableType tableType, { case HASH_DISTRIBUTED: { + Assert(distributedTableParams->colocationParam.colocationParamType == + COLOCATE_WITH_TABLE_LIKE_OPT); + citusTableParams.distributionMethod = DISTRIBUTE_BY_HASH; citusTableParams.replicationModel = DecideDistTableReplicationModel(DISTRIBUTE_BY_HASH, - distributedTableParams-> + distributedTableParams->colocationParam. colocateWithTableName); break; } case APPEND_DISTRIBUTED: { + Assert(distributedTableParams->colocationParam.colocationParamType == + COLOCATE_WITH_TABLE_LIKE_OPT); + citusTableParams.distributionMethod = DISTRIBUTE_BY_APPEND; citusTableParams.replicationModel = DecideDistTableReplicationModel(APPEND_DISTRIBUTED, - distributedTableParams-> + distributedTableParams->colocationParam. colocateWithTableName); break; } case RANGE_DISTRIBUTED: { + Assert(distributedTableParams->colocationParam.colocationParamType == + COLOCATE_WITH_TABLE_LIKE_OPT); + citusTableParams.distributionMethod = DISTRIBUTE_BY_RANGE; citusTableParams.replicationModel = DecideDistTableReplicationModel(RANGE_DISTRIBUTED, - distributedTableParams-> + distributedTableParams->colocationParam. colocateWithTableName); break; } + case SINGLE_SHARD_DISTRIBUTED: + { + citusTableParams.distributionMethod = DISTRIBUTE_BY_NONE; + citusTableParams.replicationModel = REPLICATION_MODEL_STREAMING; + break; + } + case REFERENCE_TABLE: { citusTableParams.distributionMethod = DISTRIBUTE_BY_NONE; @@ -1504,85 +1579,6 @@ EnsureDistributedSequencesHaveOneType(Oid relationId, List *seqInfoList) } -/* - * GetFKeyCreationCommandsRelationInvolvedWithTableType returns a list of DDL - * commands to recreate the foreign keys that relation with relationId is involved - * with given table type. - */ -static List * -GetFKeyCreationCommandsRelationInvolvedWithTableType(Oid relationId, int tableTypeFlag) -{ - int referencingFKeysFlag = INCLUDE_REFERENCING_CONSTRAINTS | - tableTypeFlag; - List *referencingFKeyCreationCommands = - GetForeignConstraintCommandsInternal(relationId, referencingFKeysFlag); - - /* already captured self referencing foreign keys, so use EXCLUDE_SELF_REFERENCES */ - int referencedFKeysFlag = INCLUDE_REFERENCED_CONSTRAINTS | - EXCLUDE_SELF_REFERENCES | - tableTypeFlag; - List *referencedFKeyCreationCommands = - GetForeignConstraintCommandsInternal(relationId, referencedFKeysFlag); - return list_concat(referencingFKeyCreationCommands, referencedFKeyCreationCommands); -} - - -/* - * DropFKeysAndUndistributeTable drops all foreign keys that relation with - * relationId is involved then undistributes it. - * Note that as UndistributeTable changes relationId of relation, this - * function also returns new relationId of relation. - * Also note that callers are responsible for storing & recreating foreign - * keys to be dropped if needed. - */ -static Oid -DropFKeysAndUndistributeTable(Oid relationId) -{ - DropFKeysRelationInvolvedWithTableType(relationId, INCLUDE_ALL_TABLE_TYPES); - - /* store them before calling UndistributeTable as it changes relationId */ - char *relationName = get_rel_name(relationId); - Oid schemaId = get_rel_namespace(relationId); - - /* suppress notices messages not to be too verbose */ - TableConversionParameters params = { - .relationId = relationId, - .cascadeViaForeignKeys = false, - .suppressNoticeMessages = true - }; - UndistributeTable(¶ms); - - Oid newRelationId = get_relname_relid(relationName, schemaId); - - /* - * We don't expect this to happen but to be on the safe side let's error - * out here. - */ - EnsureRelationExists(newRelationId); - - return newRelationId; -} - - -/* - * DropFKeysRelationInvolvedWithTableType drops foreign keys that relation - * with relationId is involved with given table type. - */ -static void -DropFKeysRelationInvolvedWithTableType(Oid relationId, int tableTypeFlag) -{ - int referencingFKeysFlag = INCLUDE_REFERENCING_CONSTRAINTS | - tableTypeFlag; - DropRelationForeignKeys(relationId, referencingFKeysFlag); - - /* already captured self referencing foreign keys, so use EXCLUDE_SELF_REFERENCES */ - int referencedFKeysFlag = INCLUDE_REFERENCED_CONSTRAINTS | - EXCLUDE_SELF_REFERENCES | - tableTypeFlag; - DropRelationForeignKeys(relationId, referencedFKeysFlag); -} - - /* * DecideDistTableReplicationModel function decides which replication model should be * used for a distributed table depending on given distribution configuration. @@ -1667,6 +1663,41 @@ CreateHashDistributedTableShards(Oid relationId, int shardCount, } +/* + * CreateHashDistributedTableShards creates the shard of given single-shard + * distributed table. + */ +static void +CreateSingleShardTableShard(Oid relationId, Oid colocatedTableId, + uint32 colocationId) +{ + if (colocatedTableId != InvalidOid) + { + /* + * We currently allow concurrent distribution of colocated tables (which + * we probably should not be allowing because of foreign keys / + * partitioning etc). + * + * We also prevent concurrent shard moves / copy / splits) while creating + * a colocated table. + */ + AcquirePlacementColocationLock(colocatedTableId, ShareLock, + "colocate distributed table"); + + /* + * We don't need to force using exclusive connections because we're anyway + * creating a single shard. + */ + bool useExclusiveConnection = false; + CreateColocatedShards(relationId, colocatedTableId, useExclusiveConnection); + } + else + { + CreateSingleShardTableShardWithRoundRobinPolicy(relationId, colocationId); + } +} + + /* * ColocationIdForNewTable returns a colocation id for given table * according to given configuration. If there is no such configuration, it @@ -1695,12 +1726,16 @@ ColocationIdForNewTable(Oid relationId, CitusTableType tableType, if (tableType == APPEND_DISTRIBUTED || tableType == RANGE_DISTRIBUTED) { - if (!IsColocateWithDefault(distributedTableParams->colocateWithTableName)) + Assert(distributedTableParams->colocationParam.colocationParamType == + COLOCATE_WITH_TABLE_LIKE_OPT); + char *colocateWithTableName = + distributedTableParams->colocationParam.colocateWithTableName; + if (!IsColocateWithDefault(colocateWithTableName)) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot distribute relation"), - errdetail("Currently, colocate_with option is only supported " - "for hash distributed tables."))); + errdetail("Currently, colocate_with option is not supported " + "for append / range distributed tables."))); } return colocationId; @@ -1716,13 +1751,19 @@ ColocationIdForNewTable(Oid relationId, CitusTableType tableType, * can be sure that there will no modifications on the colocation table * until this transaction is committed. */ - Assert(citusTableParams.distributionMethod == DISTRIBUTE_BY_HASH); - Oid distributionColumnType = distributionColumn->vartype; - Oid distributionColumnCollation = get_typcollation(distributionColumnType); + Oid distributionColumnType = + distributionColumn ? distributionColumn->vartype : InvalidOid; + Oid distributionColumnCollation = + distributionColumn ? get_typcollation(distributionColumnType) : InvalidOid; + + Assert(distributedTableParams->colocationParam.colocationParamType == + COLOCATE_WITH_TABLE_LIKE_OPT); + char *colocateWithTableName = + distributedTableParams->colocationParam.colocateWithTableName; /* get an advisory lock to serialize concurrent default group creations */ - if (IsColocateWithDefault(distributedTableParams->colocateWithTableName)) + if (IsColocateWithDefault(colocateWithTableName)) { AcquireColocationDefaultLock(); } @@ -1734,10 +1775,9 @@ ColocationIdForNewTable(Oid relationId, CitusTableType tableType, distributedTableParams->shardCount, distributedTableParams-> shardCountIsStrict, - distributedTableParams-> colocateWithTableName); - if (IsColocateWithDefault(distributedTableParams->colocateWithTableName) && + if (IsColocateWithDefault(colocateWithTableName) && (colocationId != INVALID_COLOCATION_ID)) { /* @@ -1750,7 +1790,7 @@ ColocationIdForNewTable(Oid relationId, CitusTableType tableType, if (colocationId == INVALID_COLOCATION_ID) { - if (IsColocateWithDefault(distributedTableParams->colocateWithTableName)) + if (IsColocateWithDefault(colocateWithTableName)) { /* * Generate a new colocation ID and insert a pg_dist_colocation @@ -1761,7 +1801,7 @@ ColocationIdForNewTable(Oid relationId, CitusTableType tableType, distributionColumnType, distributionColumnCollation); } - else if (IsColocateWithNone(distributedTableParams->colocateWithTableName)) + else if (IsColocateWithNone(colocateWithTableName)) { /* * Generate a new colocation ID and insert a pg_dist_colocation @@ -1795,8 +1835,6 @@ EnsureRelationCanBeDistributed(Oid relationId, Var *distributionColumn, { Oid parentRelationId = InvalidOid; - ErrorIfTableHasUnsupportedIdentityColumn(relationId); - EnsureLocalTableEmptyIfNecessary(relationId, distributionMethod); /* user really wants triggers? */ @@ -1908,8 +1946,15 @@ EnsureRelationCanBeDistributed(Oid relationId, Var *distributionColumn, */ if (PartitionedTableNoLock(relationId)) { - /* distributing partitioned tables in only supported for hash-distribution */ - if (distributionMethod != DISTRIBUTE_BY_HASH) + /* + * Distributing partitioned tables is only supported for hash-distribution + * or single-shard tables. + */ + bool isSingleShardTable = + distributionMethod == DISTRIBUTE_BY_NONE && + replicationModel == REPLICATION_MODEL_STREAMING && + colocationId != INVALID_COLOCATION_ID; + if (distributionMethod != DISTRIBUTE_BY_HASH && !isSingleShardTable) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("distributing partitioned tables in only supported " diff --git a/src/backend/distributed/commands/dependencies.c b/src/backend/distributed/commands/dependencies.c index baa5082d7..ceec83324 100644 --- a/src/backend/distributed/commands/dependencies.c +++ b/src/backend/distributed/commands/dependencies.c @@ -214,13 +214,7 @@ DeferErrorIfCircularDependencyExists(const ObjectAddress *objectAddress) dependency->objectId == objectAddress->objectId && dependency->objectSubId == objectAddress->objectSubId) { - char *objectDescription = NULL; - - #if PG_VERSION_NUM >= PG_VERSION_14 - objectDescription = getObjectDescription(objectAddress, false); - #else - objectDescription = getObjectDescription(objectAddress); - #endif + char *objectDescription = getObjectDescription(objectAddress, false); StringInfo detailInfo = makeStringInfo(); appendStringInfo(detailInfo, "\"%s\" circularly depends itself, resolve " @@ -393,9 +387,17 @@ GetDependencyCreateDDLCommands(const ObjectAddress *dependency) tableDDLCommand)); } - /* we need to drop table, if exists, first to make table creation idempotent */ + /* + * We need to drop table, if exists, first to make table creation + * idempotent. Before dropping the table, we should also break + * dependencies with sequences since `drop cascade table` would also + * drop depended sequences. This is safe as we still record dependency + * with the sequence during table creation. + */ commandList = lcons(DropTableIfExistsCommand(relationId), commandList); + commandList = lcons(WorkerDropSequenceDependencyCommand(relationId), + commandList); } return commandList; @@ -521,9 +523,9 @@ GetDependencyCreateDDLCommands(const ObjectAddress *dependency) */ Assert(false); ereport(ERROR, (errmsg("unsupported object %s for distribution by citus", - getObjectTypeDescription_compat(dependency, + getObjectTypeDescription(dependency, - /* missingOk: */ false)), + /* missingOk: */ false)), errdetail( "citus tries to recreate an unsupported object on its workers"), errhint("please report a bug as this should not be happening"))); diff --git a/src/backend/distributed/commands/distribute_object_ops.c b/src/backend/distributed/commands/distribute_object_ops.c index 017cb6537..3442b07f2 100644 --- a/src/backend/distributed/commands/distribute_object_ops.c +++ b/src/backend/distributed/commands/distribute_object_ops.c @@ -294,8 +294,8 @@ static DistributeObjectOps Any_CreateForeignServer = { static DistributeObjectOps Any_CreateSchema = { .deparse = DeparseCreateSchemaStmt, .qualify = NULL, - .preprocess = PreprocessCreateSchemaStmt, - .postprocess = NULL, + .preprocess = NULL, + .postprocess = PostprocessCreateSchemaStmt, .operationType = DIST_OPS_CREATE, .address = CreateSchemaStmtObjectAddress, .markDistributed = true, @@ -1024,6 +1024,15 @@ static DistributeObjectOps Routine_Rename = { .address = RenameFunctionStmtObjectAddress, .markDistributed = false, }; +static DistributeObjectOps Schema_AlterOwner = { + .deparse = DeparseAlterSchemaOwnerStmt, + .qualify = NULL, + .preprocess = PreprocessAlterDistributedObjectStmt, + .operationType = DIST_OPS_ALTER, + .postprocess = NULL, + .address = AlterSchemaOwnerStmtObjectAddress, + .markDistributed = false, +}; static DistributeObjectOps Schema_Drop = { .deparse = DeparseDropSchemaStmt, .qualify = NULL, @@ -1457,6 +1466,11 @@ GetDistributeObjectOps(Node *node) return &Routine_AlterOwner; } + case OBJECT_SCHEMA: + { + return &Schema_AlterOwner; + } + case OBJECT_STATISTIC_EXT: { return &Statistics_AlterOwner; @@ -1517,7 +1531,7 @@ GetDistributeObjectOps(Node *node) case T_AlterTableStmt: { AlterTableStmt *stmt = castNode(AlterTableStmt, node); - switch (AlterTableStmtObjType_compat(stmt)) + switch (stmt->objtype) { case OBJECT_TYPE: { diff --git a/src/backend/distributed/commands/domain.c b/src/backend/distributed/commands/domain.c index f14157278..392cbd6e2 100644 --- a/src/backend/distributed/commands/domain.c +++ b/src/backend/distributed/commands/domain.c @@ -206,11 +206,7 @@ MakeCollateClauseFromOid(Oid collationOid) List *objName = NIL; List *objArgs = NIL; - #if PG_VERSION_NUM >= PG_VERSION_14 getObjectIdentityParts(&collateAddress, &objName, &objArgs, false); - #else - getObjectIdentityParts(&collateAddress, &objName, &objArgs); - #endif char *name = NULL; foreach_ptr(name, objName) diff --git a/src/backend/distributed/commands/drop_distributed_table.c b/src/backend/distributed/commands/drop_distributed_table.c index 1e214adf3..24dd8e892 100644 --- a/src/backend/distributed/commands/drop_distributed_table.c +++ b/src/backend/distributed/commands/drop_distributed_table.c @@ -90,7 +90,27 @@ master_remove_partition_metadata(PG_FUNCTION_ARGS) DeletePartitionRow(relationId); - DeleteColocationGroupIfNoTablesBelong(colocationId); + /* + * We want to keep using the same colocation group for the tenant even if + * all the tables that belong to it are dropped and new tables are created + * for the tenant etc. For this reason, if a colocation group belongs to a + * tenant schema, we don't delete the colocation group even if there are no + * tables that belong to it. + * + * We do the same if system catalog cannot find the schema of the table + * because this means that the whole schema is dropped. + * + * In that case, we want to delete the colocation group regardless of + * whether the schema is a tenant schema or not. Even more, calling + * IsTenantSchema() with InvalidOid would cause an error, hence we check + * whether the schema is valid beforehand. + */ + bool missingOk = true; + Oid schemaId = get_namespace_oid(schemaName, missingOk); + if (!OidIsValid(schemaId) || !IsTenantSchema(schemaId)) + { + DeleteColocationGroupIfNoTablesBelong(colocationId); + } PG_RETURN_VOID(); } diff --git a/src/backend/distributed/commands/foreign_constraint.c b/src/backend/distributed/commands/foreign_constraint.c index 6f12db13f..40ccb0ddf 100644 --- a/src/backend/distributed/commands/foreign_constraint.c +++ b/src/backend/distributed/commands/foreign_constraint.c @@ -303,6 +303,11 @@ ErrorIfUnsupportedForeignConstraintExists(Relation relation, char referencingDis /* * Foreign keys from citus local tables or reference tables to distributed * tables are not supported. + * + * We could support foreign keys from references tables to single-shard + * tables but this doesn't seem useful a lot. However, if we decide supporting + * this, then we need to expand relation access tracking check for the single-shard + * tables too. */ if (referencingIsCitusLocalOrRefTable && !referencedIsCitusLocalOrRefTable) { @@ -361,7 +366,12 @@ ErrorIfUnsupportedForeignConstraintExists(Relation relation, char referencingDis * if tables are hash-distributed and colocated, we need to make sure that * the distribution key is included in foreign constraint. */ - if (!referencedIsCitusLocalOrRefTable && !foreignConstraintOnDistKey) + bool referencedIsSingleShardTable = + IsSingleShardTableByDistParams(referencedDistMethod, + referencedReplicationModel, + referencedColocationId); + if (!referencedIsCitusLocalOrRefTable && !referencedIsSingleShardTable && + !foreignConstraintOnDistKey) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot create foreign key constraint"), @@ -886,6 +896,48 @@ GetForeignConstraintCommandsInternal(Oid relationId, int flags) } +/* + * GetFKeyCreationCommandsRelationInvolvedWithTableType returns a list of DDL + * commands to recreate the foreign keys that relation with relationId is involved + * with given table type. + */ +List * +GetFKeyCreationCommandsRelationInvolvedWithTableType(Oid relationId, int tableTypeFlag) +{ + int referencingFKeysFlag = INCLUDE_REFERENCING_CONSTRAINTS | + tableTypeFlag; + List *referencingFKeyCreationCommands = + GetForeignConstraintCommandsInternal(relationId, referencingFKeysFlag); + + /* already captured self referencing foreign keys, so use EXCLUDE_SELF_REFERENCES */ + int referencedFKeysFlag = INCLUDE_REFERENCED_CONSTRAINTS | + EXCLUDE_SELF_REFERENCES | + tableTypeFlag; + List *referencedFKeyCreationCommands = + GetForeignConstraintCommandsInternal(relationId, referencedFKeysFlag); + return list_concat(referencingFKeyCreationCommands, referencedFKeyCreationCommands); +} + + +/* + * DropFKeysRelationInvolvedWithTableType drops foreign keys that relation + * with relationId is involved with given table type. + */ +void +DropFKeysRelationInvolvedWithTableType(Oid relationId, int tableTypeFlag) +{ + int referencingFKeysFlag = INCLUDE_REFERENCING_CONSTRAINTS | + tableTypeFlag; + DropRelationForeignKeys(relationId, referencingFKeysFlag); + + /* already captured self referencing foreign keys, so use EXCLUDE_SELF_REFERENCES */ + int referencedFKeysFlag = INCLUDE_REFERENCED_CONSTRAINTS | + EXCLUDE_SELF_REFERENCES | + tableTypeFlag; + DropRelationForeignKeys(relationId, referencedFKeysFlag); +} + + /* * HasForeignKeyWithLocalTable returns true if relation has foreign key * relationship with a local table. @@ -1304,6 +1356,10 @@ IsTableTypeIncluded(Oid relationId, int flags) { return (flags & INCLUDE_LOCAL_TABLES) != 0; } + else if (IsCitusTableType(relationId, SINGLE_SHARD_DISTRIBUTED)) + { + return (flags & INCLUDE_SINGLE_SHARD_TABLES) != 0; + } else if (IsCitusTableType(relationId, DISTRIBUTED_TABLE)) { return (flags & INCLUDE_DISTRIBUTED_TABLES) != 0; diff --git a/src/backend/distributed/commands/function.c b/src/backend/distributed/commands/function.c index c5a0652ae..9f579f5dc 100644 --- a/src/backend/distributed/commands/function.c +++ b/src/backend/distributed/commands/function.c @@ -105,6 +105,9 @@ static void DistributeFunctionColocatedWithDistributedTable(RegProcedure funcOid char *colocateWithTableName, const ObjectAddress * functionAddress); +static void DistributeFunctionColocatedWithSingleShardTable(const + ObjectAddress *functionAddress, + text *colocateWithText); static void DistributeFunctionColocatedWithReferenceTable(const ObjectAddress *functionAddress); static List * FilterDistributedFunctions(GrantStmt *grantStmt); @@ -133,6 +136,7 @@ create_distributed_function(PG_FUNCTION_ARGS) Oid distributionArgumentOid = InvalidOid; bool colocatedWithReferenceTable = false; + bool colocatedWithSingleShardTable = false; char *distributionArgumentName = NULL; char *colocateWithTableName = NULL; @@ -187,6 +191,8 @@ create_distributed_function(PG_FUNCTION_ARGS) Oid colocationRelationId = ResolveRelationId(colocateWithText, false); colocatedWithReferenceTable = IsCitusTableType(colocationRelationId, REFERENCE_TABLE); + colocatedWithSingleShardTable = IsCitusTableType(colocationRelationId, + SINGLE_SHARD_DISTRIBUTED); } } @@ -276,11 +282,16 @@ create_distributed_function(PG_FUNCTION_ARGS) forceDelegationAddress, functionAddress); } - else if (!colocatedWithReferenceTable) + else if (!colocatedWithReferenceTable && !colocatedWithSingleShardTable) { DistributeFunctionColocatedWithDistributedTable(funcOid, colocateWithTableName, functionAddress); } + else if (colocatedWithSingleShardTable) + { + DistributeFunctionColocatedWithSingleShardTable(functionAddress, + colocateWithText); + } else if (colocatedWithReferenceTable) { /* @@ -435,6 +446,25 @@ DistributeFunctionColocatedWithDistributedTable(RegProcedure funcOid, } +/* + * DistributeFunctionColocatedWithSingleShardTable updates pg_dist_object records for + * a function/procedure that is colocated with a single shard table. + */ +static void +DistributeFunctionColocatedWithSingleShardTable(const ObjectAddress *functionAddress, + text *colocateWithText) +{ + /* get the single shard table's colocation id */ + int colocationId = TableColocationId(ResolveRelationId(colocateWithText, false)); + + /* set distribution argument to NULL */ + int *distributionArgumentIndex = NULL; + UpdateFunctionDistributionInfo(functionAddress, distributionArgumentIndex, + &colocationId, + NULL); +} + + /* * DistributeFunctionColocatedWithReferenceTable updates pg_dist_object records for * a function/procedure that is colocated with a reference table. @@ -641,6 +671,19 @@ EnsureFunctionCanBeColocatedWithTable(Oid functionOid, Oid distributionColumnTyp CitusTableCacheEntry *sourceTableEntry = GetCitusTableCacheEntry(sourceRelationId); char sourceReplicationModel = sourceTableEntry->replicationModel; + if (IsCitusTableTypeCacheEntry(sourceTableEntry, SINGLE_SHARD_DISTRIBUTED) && + distributionColumnType != InvalidOid) + { + char *functionName = get_func_name(functionOid); + char *sourceRelationName = get_rel_name(sourceRelationId); + + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot colocate function \"%s\" and table \"%s\" because " + "distribution arguments are not supported when " + "colocating with single shard distributed tables.", + functionName, sourceRelationName))); + } + if (!IsCitusTableTypeCacheEntry(sourceTableEntry, HASH_DISTRIBUTED) && !IsCitusTableTypeCacheEntry(sourceTableEntry, REFERENCE_TABLE)) { @@ -1598,7 +1641,7 @@ PreprocessAlterFunctionDependsStmt(Node *node, const char *queryString, * workers */ const char *functionName = - getObjectIdentity_compat(address, /* missingOk: */ false); + getObjectIdentity(address, /* missingOk: */ false); ereport(ERROR, (errmsg("distrtibuted functions are not allowed to depend on an " "extension"), errdetail("Function \"%s\" is already distributed. Functions from " @@ -1768,8 +1811,8 @@ GenerateBackupNameForProcCollision(const ObjectAddress *address) List *newProcName = list_make2(namespace, makeString(newName)); /* don't need to rename if the input arguments don't match */ - FuncCandidateList clist = FuncnameGetCandidates_compat(newProcName, numargs, NIL, - false, false, false, true); + FuncCandidateList clist = FuncnameGetCandidates(newProcName, numargs, NIL, + false, false, false, true); for (; clist; clist = clist->next) { if (memcmp(clist->args, argtypes, sizeof(Oid) * numargs) == 0) diff --git a/src/backend/distributed/commands/local_multi_copy.c b/src/backend/distributed/commands/local_multi_copy.c index 5cf01baf4..7dbf0ae36 100644 --- a/src/backend/distributed/commands/local_multi_copy.c +++ b/src/backend/distributed/commands/local_multi_copy.c @@ -216,10 +216,10 @@ DoLocalCopy(StringInfo buffer, Oid relationId, int64 shardId, CopyStmt *copyStat ParseState *pState = make_parsestate(NULL); (void) addRangeTableEntryForRelation(pState, shard, AccessShareLock, NULL, false, false); - CopyFromState cstate = BeginCopyFrom_compat(pState, shard, NULL, NULL, false, - ReadFromLocalBufferCallback, - copyStatement->attlist, - copyStatement->options); + CopyFromState cstate = BeginCopyFrom(pState, shard, NULL, NULL, false, + ReadFromLocalBufferCallback, + copyStatement->attlist, + copyStatement->options); CopyFrom(cstate); EndCopyFrom(cstate); diff --git a/src/backend/distributed/commands/multi_copy.c b/src/backend/distributed/commands/multi_copy.c index 6e3d19b68..5d7c279a6 100644 --- a/src/backend/distributed/commands/multi_copy.c +++ b/src/backend/distributed/commands/multi_copy.c @@ -258,9 +258,6 @@ static CopyCoercionData * ColumnCoercionPaths(TupleDesc destTupleDescriptor, Oid *finalColumnTypeArray); static FmgrInfo * TypeOutputFunctions(uint32 columnCount, Oid *typeIdArray, bool binaryFormat); -#if PG_VERSION_NUM < PG_VERSION_14 -static List * CopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist); -#endif static bool CopyStatementHasFormat(CopyStmt *copyStatement, char *formatName); static void CitusCopyFrom(CopyStmt *copyStatement, QueryCompletion *completionTag); static void EnsureCopyCanRunOnRelation(Oid relationId); @@ -609,14 +606,14 @@ CopyToExistingShards(CopyStmt *copyStatement, QueryCompletion *completionTag) } /* initialize copy state to read from COPY data source */ - CopyFromState copyState = BeginCopyFrom_compat(NULL, - copiedDistributedRelation, - NULL, - copyStatement->filename, - copyStatement->is_program, - NULL, - copyStatement->attlist, - copyStatement->options); + CopyFromState copyState = BeginCopyFrom(NULL, + copiedDistributedRelation, + NULL, + copyStatement->filename, + copyStatement->is_program, + NULL, + copyStatement->attlist, + copyStatement->options); /* set up callback to identify error line number */ errorCallback.callback = CopyFromErrorCallback; @@ -648,9 +645,7 @@ CopyToExistingShards(CopyStmt *copyStatement, QueryCompletion *completionTag) ++processedRowCount; -#if PG_VERSION_NUM >= PG_VERSION_14 pgstat_progress_update_param(PROGRESS_COPY_TUPLES_PROCESSED, processedRowCount); -#endif } EndCopyFrom(copyState); @@ -890,28 +885,8 @@ CanUseBinaryCopyFormatForType(Oid typeId) HeapTuple typeTup = typeidType(typeId); Form_pg_type type = (Form_pg_type) GETSTRUCT(typeTup); Oid elementType = type->typelem; -#if PG_VERSION_NUM < PG_VERSION_14 - char typeCategory = type->typcategory; -#endif ReleaseSysCache(typeTup); -#if PG_VERSION_NUM < PG_VERSION_14 - - /* - * In PG versions before PG14 the array_recv function would error out more - * than necessary. - * - * It errors out when the element type its oids don't match with the oid in - * the received data. This happens pretty much always for non built in - * types, because their oids differ between postgres intallations. So we - * skip binary encoding when the element type is a non built in type. - */ - if (typeCategory == TYPCATEGORY_ARRAY && elementType >= FirstNormalObjectId) - { - return false; - } -#endif - /* * Any type that is a wrapper around an element type (e.g. arrays and * ranges) require the element type to also has support for binary @@ -1682,20 +1657,6 @@ AppendCopyBinaryFooters(CopyOutState footerOutputState) static void SendCopyBegin(CopyOutState cstate) { -#if PG_VERSION_NUM < PG_VERSION_14 - if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3) { - /* old way */ - if (cstate->binary) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("COPY BINARY is not supported to stdout or from stdin"))); - pq_putemptymessage('H'); - /* grottiness needed for old COPY OUT protocol */ - pq_startcopyout(); - cstate->copy_dest = COPY_OLD_FE; - return; - } -#endif StringInfoData buf; int natts = list_length(cstate->attnumlist); int16 format = (cstate->binary ? 1 : 0); @@ -1715,16 +1676,6 @@ SendCopyBegin(CopyOutState cstate) static void SendCopyEnd(CopyOutState cstate) { -#if PG_VERSION_NUM < PG_VERSION_14 - if (cstate->copy_dest != COPY_NEW_FE) - { - CopySendData(cstate, "\\.", 2); - /* Need to flush out the trailer (this also appends a newline) */ - CopySendEndOfRow(cstate, true); - pq_endcopyout(false); - return; - } -#endif /* Shouldn't have any unsent data */ Assert(cstate->fe_msgbuf->len == 0); /* Send Copy Done message */ @@ -1782,21 +1733,6 @@ CopySendEndOfRow(CopyOutState cstate, bool includeEndOfLine) switch (cstate->copy_dest) { -#if PG_VERSION_NUM < PG_VERSION_14 - case COPY_OLD_FE: - /* The FE/BE protocol uses \n as newline for all platforms */ - if (!cstate->binary && includeEndOfLine) - CopySendChar(cstate, '\n'); - - if (pq_putbytes(fe_msgbuf->data, fe_msgbuf->len)) - { - /* no hope of recovering connection sync, so FATAL */ - ereport(FATAL, - (errcode(ERRCODE_CONNECTION_FAILURE), - errmsg("connection lost during COPY to stdout"))); - } - break; -#endif case COPY_FRONTEND: /* The FE/BE protocol uses \n as newline for all platforms */ if (!cstate->binary && includeEndOfLine) @@ -2128,12 +2064,36 @@ CitusCopyDestReceiverStartup(DestReceiver *dest, int operation, int columnCount = inputTupleDescriptor->natts; Oid *finalTypeArray = palloc0(columnCount * sizeof(Oid)); - copyDest->columnCoercionPaths = - ColumnCoercionPaths(destTupleDescriptor, inputTupleDescriptor, - tableId, columnNameList, finalTypeArray); - - copyDest->columnOutputFunctions = - TypeOutputFunctions(columnCount, finalTypeArray, copyOutState->binary); + /* + * To ensure the proper co-location and distribution of the target table, + * the entire process of repartitioning intermediate files requires the + * destReceiver to be created on the target rather than the source. + * + * Within this specific code path, it is assumed that the employed model + * is for insert-select. Consequently, it validates the column types of + * destTupleDescriptor(target) during the intermediate result generation + * process. However, this approach varies significantly for MERGE operations, + * where the source tuple(s) can have arbitrary types and are not required to + * align with the target column names. + * + * Despite this minor setback, a significant portion of the code responsible + * for repartitioning intermediate files can be reused for the MERGE + * operation. By leveraging the ability to perform actual coercion during + * the writing process to the target table, we can bypass this specific route. + */ + if (copyDest->skipCoercions) + { + copyDest->columnOutputFunctions = + ColumnOutputFunctions(inputTupleDescriptor, copyOutState->binary); + } + else + { + copyDest->columnCoercionPaths = + ColumnCoercionPaths(destTupleDescriptor, inputTupleDescriptor, + tableId, columnNameList, finalTypeArray); + copyDest->columnOutputFunctions = + TypeOutputFunctions(columnCount, finalTypeArray, copyOutState->binary); + } } /* wrap the column names as Values */ @@ -2146,6 +2106,7 @@ CitusCopyDestReceiverStartup(DestReceiver *dest, int operation, } if (IsCitusTableTypeCacheEntry(cacheEntry, DISTRIBUTED_TABLE) && + !IsCitusTableTypeCacheEntry(cacheEntry, SINGLE_SHARD_DISTRIBUTED) && copyDest->partitionColumnIndex == INVALID_PARTITION_COLUMN_INDEX) { ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), @@ -2596,9 +2557,11 @@ ShardIdForTuple(CitusCopyDestReceiver *copyDest, Datum *columnValues, bool *colu /* find the partition column value */ partitionColumnValue = columnValues[partitionColumnIndex]; - - /* annoyingly this is evaluated twice, but at least we don't crash! */ - partitionColumnValue = CoerceColumnValue(partitionColumnValue, coercePath); + if (!copyDest->skipCoercions) + { + /* annoyingly this is evaluated twice, but at least we don't crash! */ + partitionColumnValue = CoerceColumnValue(partitionColumnValue, coercePath); + } } /* @@ -3229,92 +3192,6 @@ CreateRangeTable(Relation rel, AclMode requiredAccess) } -#if PG_VERSION_NUM < PG_VERSION_14 - -/* Helper for CheckCopyPermissions(), copied from postgres */ -static List * -CopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist) -{ - /* *INDENT-OFF* */ - List *attnums = NIL; - - if (attnamelist == NIL) - { - /* Generate default column list */ - int attr_count = tupDesc->natts; - int i; - - for (i = 0; i < attr_count; i++) - { - if (TupleDescAttr(tupDesc, i)->attisdropped) - continue; - if (TupleDescAttr(tupDesc, i)->attgenerated) - continue; - attnums = lappend_int(attnums, i + 1); - } - } - else - { - /* Validate the user-supplied list and extract attnums */ - ListCell *l; - - foreach(l, attnamelist) - { - char *name = strVal(lfirst(l)); - int attnum; - int i; - - /* Lookup column name */ - attnum = InvalidAttrNumber; - for (i = 0; i < tupDesc->natts; i++) - { - Form_pg_attribute att = TupleDescAttr(tupDesc, i); - - if (att->attisdropped) - continue; - if (namestrcmp(&(att->attname), name) == 0) - { - if (att->attgenerated) - ereport(ERROR, - (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), - errmsg("column \"%s\" is a generated column", - name), - errdetail("Generated columns cannot be used in COPY."))); - attnum = att->attnum; - break; - } - } - if (attnum == InvalidAttrNumber) - { - if (rel != NULL) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("column \"%s\" of relation \"%s\" does not exist", - name, RelationGetRelationName(rel)))); - else - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("column \"%s\" does not exist", - name))); - } - /* Check for duplicates */ - if (list_member_int(attnums, attnum)) - ereport(ERROR, - (errcode(ERRCODE_DUPLICATE_COLUMN), - errmsg("column \"%s\" specified more than once", - name))); - attnums = lappend_int(attnums, attnum); - } - } - - return attnums; - /* *INDENT-ON* */ -} - - -#endif - - /* * CreateConnectionStateHash constructs a hash table which maps from socket * number to CopyConnectionState, passing the provided MemoryContext to diff --git a/src/backend/distributed/commands/schema.c b/src/backend/distributed/commands/schema.c index 66189f39e..966a264d6 100644 --- a/src/backend/distributed/commands/schema.c +++ b/src/backend/distributed/commands/schema.c @@ -19,6 +19,7 @@ #include "catalog/namespace.h" #include "catalog/pg_class.h" #include "catalog/pg_namespace.h" +#include "distributed/colocation_utils.h" #include "distributed/commands.h" #include #include "distributed/commands/utility_hook.h" @@ -33,6 +34,7 @@ #include "distributed/resource_lock.h" #include #include +#include "distributed/tenant_schema_metadata.h" #include "distributed/version_compat.h" #include "nodes/parsenodes.h" #include "utils/fmgroids.h" @@ -45,16 +47,18 @@ static List * FilterDistributedSchemas(List *schemas); static bool SchemaHasDistributedTableWithFKey(char *schemaName); static bool ShouldPropagateCreateSchemaStmt(void); static List * GetGrantCommandsFromCreateSchemaStmt(Node *node); +static bool CreateSchemaStmtCreatesTable(CreateSchemaStmt *stmt); /* - * PreprocessCreateSchemaStmt is called during the planning phase for + * PostprocessCreateSchemaStmt is called during the planning phase for * CREATE SCHEMA .. */ List * -PreprocessCreateSchemaStmt(Node *node, const char *queryString, - ProcessUtilityContext processUtilityContext) +PostprocessCreateSchemaStmt(Node *node, const char *queryString) { + CreateSchemaStmt *createSchemaStmt = castNode(CreateSchemaStmt, node); + if (!ShouldPropagateCreateSchemaStmt()) { return NIL; @@ -64,6 +68,16 @@ PreprocessCreateSchemaStmt(Node *node, const char *queryString, EnsureSequentialMode(OBJECT_SCHEMA); + bool missingOk = createSchemaStmt->if_not_exists; + List *schemaAdressList = CreateSchemaStmtObjectAddress(node, missingOk, true); + Assert(list_length(schemaAdressList) == 1); + ObjectAddress *schemaAdress = linitial(schemaAdressList); + Oid schemaId = schemaAdress->objectId; + if (!OidIsValid(schemaId)) + { + return NIL; + } + /* to prevent recursion with mx we disable ddl propagation */ List *commands = list_make1(DISABLE_DDL_PROPAGATION); @@ -74,6 +88,37 @@ PreprocessCreateSchemaStmt(Node *node, const char *queryString, commands = list_concat(commands, GetGrantCommandsFromCreateSchemaStmt(node)); + char *schemaName = get_namespace_name(schemaId); + if (ShouldUseSchemaBasedSharding(schemaName)) + { + /* for now, we don't allow creating tenant tables when creating the schema itself */ + if (CreateSchemaStmtCreatesTable(createSchemaStmt)) + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create distributed schema and table in a " + "single statement"), + errhint("SET citus.enable_schema_based_sharding TO off, " + "or create the schema and table in separate " + "commands."))); + } + + /* + * Register the tenant schema on the coordinator and save the command + * to register it on the workers. + */ + int shardCount = 1; + int replicationFactor = 1; + Oid distributionColumnType = InvalidOid; + Oid distributionColumnCollation = InvalidOid; + uint32 colocationId = CreateColocationGroup( + shardCount, replicationFactor, distributionColumnType, + distributionColumnCollation); + + InsertTenantSchemaLocally(schemaId, colocationId); + + commands = lappend(commands, TenantSchemaInsertCommand(schemaId, colocationId)); + } + commands = lappend(commands, ENABLE_DDL_PROPAGATION); return NodeDDLTaskList(NON_COORDINATOR_NODES, commands); @@ -214,6 +259,20 @@ CreateSchemaStmtObjectAddress(Node *node, bool missing_ok, bool isPostprocess) } +/* + * AlterSchemaOwnerStmtObjectAddress returns the ObjectAddress of the schema that is + * the object of the AlterOwnerStmt. Errors if missing_ok is false. + */ +List * +AlterSchemaOwnerStmtObjectAddress(Node *node, bool missing_ok, bool isPostprocess) +{ + AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node); + Assert(stmt->objectType == OBJECT_SCHEMA); + + return GetObjectAddressBySchemaName(strVal(stmt->object), missing_ok); +} + + /* * AlterSchemaRenameStmtObjectAddress returns the ObjectAddress of the schema that is * the object of the RenameStmt. Errors if missing_ok is false. @@ -402,3 +461,27 @@ GetGrantCommandsFromCreateSchemaStmt(Node *node) return commands; } + + +/* + * CreateSchemaStmtCreatesTable returns true if given CreateSchemaStmt + * creates a table using "schema_element" list. + */ +static bool +CreateSchemaStmtCreatesTable(CreateSchemaStmt *stmt) +{ + Node *element = NULL; + foreach_ptr(element, stmt->schemaElts) + { + /* + * CREATE TABLE AS and CREATE FOREIGN TABLE commands cannot be + * used as schema_elements anyway, so we don't need to check them. + */ + if (IsA(element, CreateStmt)) + { + return true; + } + } + + return false; +} diff --git a/src/backend/distributed/commands/schema_based_sharding.c b/src/backend/distributed/commands/schema_based_sharding.c new file mode 100644 index 000000000..b717cb5ae --- /dev/null +++ b/src/backend/distributed/commands/schema_based_sharding.c @@ -0,0 +1,774 @@ +/*------------------------------------------------------------------------- + * schema_based_sharding.c + * + * Routines for schema-based sharding. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" +#include "miscadmin.h" + +#include "access/genam.h" +#include "catalog/catalog.h" +#include "catalog/pg_namespace_d.h" +#include "commands/extension.h" +#include "distributed/argutils.h" +#include "distributed/backend_data.h" +#include "distributed/colocation_utils.h" +#include "distributed/commands.h" +#include "distributed/listutils.h" +#include "distributed/metadata_sync.h" +#include "distributed/metadata/distobject.h" +#include "distributed/multi_partitioning_utils.h" +#include "distributed/tenant_schema_metadata.h" +#include "distributed/worker_shard_visibility.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" + + +static void UnregisterTenantSchemaGlobally(Oid schemaId, char *schemaName); +static List * SchemaGetNonShardTableIdList(Oid schemaId); +static void EnsureSchemaCanBeDistributed(Oid schemaId, List *schemaTableIdList); +static void EnsureTenantSchemaNameAllowed(Oid schemaId); +static void EnsureTableKindSupportedForTenantSchema(Oid relationId); +static void EnsureFKeysForTenantTable(Oid relationId); +static void EnsureSchemaExist(Oid schemaId); + +/* controlled via citus.enable_schema_based_sharding GUC */ +bool EnableSchemaBasedSharding = false; + +const char *TenantOperationNames[TOTAL_TENANT_OPERATION] = { + "undistribute_table", + "alter_distributed_table", + "colocate_with", + "update_distributed_table_colocation", + "set schema", +}; + + +PG_FUNCTION_INFO_V1(citus_internal_unregister_tenant_schema_globally); +PG_FUNCTION_INFO_V1(citus_schema_distribute); +PG_FUNCTION_INFO_V1(citus_schema_undistribute); + +/* + * ShouldUseSchemaBasedSharding returns true if schema given name should be + * used as a tenant schema. + */ +bool +ShouldUseSchemaBasedSharding(char *schemaName) +{ + if (!EnableSchemaBasedSharding) + { + return false; + } + + if (IsBinaryUpgrade) + { + return false; + } + + /* + * Citus utility hook skips processing CREATE SCHEMA commands while an + * extension is being created. For this reason, we don't expect to get + * here while an extension is being created. + */ + Assert(!creating_extension); + + /* + * CREATE SCHEMA commands issued by internal backends are not meant to + * create tenant schemas but to sync metadata. + * + * On workers, Citus utility hook skips processing CREATE SCHEMA commands + * because we temporarily disable DDL propagation on workers when sending + * CREATE SCHEMA commands. For this reason, right now this check is a bit + * redundant but we prefer to keep it here to be on the safe side. + */ + if (IsCitusInternalBackend() || IsRebalancerInternalBackend()) + { + return false; + } + + /* + * Not do an oid comparison based on PG_PUBLIC_NAMESPACE because + * we want to treat "public" schema in the same way even if it's + * recreated. + */ + if (strcmp(schemaName, "public") == 0) + { + return false; + } + + return true; +} + + +/* + * ShouldCreateTenantSchemaTable returns true if we should create a tenant + * schema table for given relationId. + */ +bool +ShouldCreateTenantSchemaTable(Oid relationId) +{ + if (IsBinaryUpgrade) + { + return false; + } + + /* + * CREATE TABLE commands issued by internal backends are not meant to + * create tenant tables but to sync metadata. + */ + if (IsCitusInternalBackend() || IsRebalancerInternalBackend()) + { + return false; + } + + Oid schemaId = get_rel_namespace(relationId); + return IsTenantSchema(schemaId); +} + + +/* + * EnsureTableKindSupportedForTenantSchema ensures that given table's kind is + * supported by a tenant schema. + */ +static void +EnsureTableKindSupportedForTenantSchema(Oid relationId) +{ + if (IsForeignTable(relationId)) + { + ereport(ERROR, (errmsg("cannot create a foreign table in a distributed " + "schema"))); + } + + if (PartitionTable(relationId)) + { + ErrorIfIllegalPartitioningInTenantSchema(PartitionParentOid(relationId), + relationId); + } + + if (PartitionedTable(relationId)) + { + List *partitionList = PartitionList(relationId); + + Oid partitionRelationId = InvalidOid; + foreach_oid(partitionRelationId, partitionList) + { + ErrorIfIllegalPartitioningInTenantSchema(relationId, partitionRelationId); + } + } + + if (IsChildTable(relationId) || IsParentTable(relationId)) + { + ereport(ERROR, (errmsg("tables in a distributed schema cannot inherit or " + "be inherited"))); + } +} + + +/* + * EnsureFKeysForTenantTable ensures that all referencing and referenced foreign + * keys are allowed for given table. + */ +static void +EnsureFKeysForTenantTable(Oid relationId) +{ + Oid tenantSchemaId = get_rel_namespace(relationId); + int fKeyReferencingFlags = INCLUDE_REFERENCING_CONSTRAINTS | INCLUDE_ALL_TABLE_TYPES; + List *referencingForeignKeys = GetForeignKeyOids(relationId, fKeyReferencingFlags); + Oid foreignKeyId = InvalidOid; + foreach_oid(foreignKeyId, referencingForeignKeys) + { + Oid referencingTableId = GetReferencingTableId(foreignKeyId); + Oid referencedTableId = GetReferencedTableId(foreignKeyId); + Oid referencedTableSchemaId = get_rel_namespace(referencedTableId); + + /* We allow foreign keys to a table in the same schema */ + if (tenantSchemaId == referencedTableSchemaId) + { + continue; + } + + /* + * Allow foreign keys to the other schema only if the referenced table is + * a reference table. + */ + if (!IsCitusTable(referencedTableId) || + !IsCitusTableType(referencedTableId, REFERENCE_TABLE)) + { + ereport(ERROR, (errmsg("foreign keys from distributed schemas can only " + "point to the same distributed schema or reference " + "tables in regular schemas"), + errdetail("\"%s\" references \"%s\" via foreign key " + "constraint \"%s\"", + generate_qualified_relation_name( + referencingTableId), + generate_qualified_relation_name(referencedTableId), + get_constraint_name(foreignKeyId)))); + } + } + + int fKeyReferencedFlags = INCLUDE_REFERENCED_CONSTRAINTS | INCLUDE_ALL_TABLE_TYPES; + List *referencedForeignKeys = GetForeignKeyOids(relationId, fKeyReferencedFlags); + foreach_oid(foreignKeyId, referencedForeignKeys) + { + Oid referencingTableId = GetReferencingTableId(foreignKeyId); + Oid referencedTableId = GetReferencedTableId(foreignKeyId); + Oid referencingTableSchemaId = get_rel_namespace(referencingTableId); + + /* We allow foreign keys from a table in the same schema */ + if (tenantSchemaId == referencingTableSchemaId) + { + continue; + } + + /* Not allow any foreign keys from the other schema */ + ereport(ERROR, (errmsg("cannot create foreign keys to tables in a distributed " + "schema from another schema"), + errdetail("\"%s\" references \"%s\" via foreign key " + "constraint \"%s\"", + generate_qualified_relation_name(referencingTableId), + generate_qualified_relation_name(referencedTableId), + get_constraint_name(foreignKeyId)))); + } +} + + +/* + * CreateTenantSchemaTable creates a tenant table with given relationId. + * + * This means creating a single shard distributed table without a shard + * key and colocating it with the other tables in its schema. + */ +void +CreateTenantSchemaTable(Oid relationId) +{ + if (!IsCoordinator()) + { + /* + * We don't support creating tenant tables from workers. We could + * let ShouldCreateTenantSchemaTable() to return false to allow users + * to create a local table as usual but that would be confusing because + * it might sound like we allow creating tenant tables from workers. + * For this reason, we prefer to throw an error instead. + * + * Indeed, CreateSingleShardTable() would already do so but we + * prefer to throw an error with a more meaningful message, rather + * than saying "operation is not allowed on this node". + */ + ereport(ERROR, (errmsg("cannot create tables in a distributed schema from " + "a worker node"), + errhint("Connect to the coordinator node and try again."))); + } + + EnsureTableKindSupportedForTenantSchema(relationId); + + /* + * We don't expect this to happen because ShouldCreateTenantSchemaTable() + * should've already verified that; but better to check. + */ + Oid schemaId = get_rel_namespace(relationId); + uint32 colocationId = SchemaIdGetTenantColocationId(schemaId); + if (colocationId == INVALID_COLOCATION_ID) + { + ereport(ERROR, (errmsg("schema \"%s\" is not distributed", + get_namespace_name(schemaId)))); + } + + ColocationParam colocationParam = { + .colocationParamType = COLOCATE_WITH_COLOCATION_ID, + .colocationId = colocationId, + }; + CreateSingleShardTable(relationId, colocationParam); +} + + +/* + * ErrorIfIllegalPartitioningInTenantSchema throws an error if the + * partitioning relationship between the parent and the child is illegal + * because they are in different schemas while one of them is a tenant table. + * + * This function assumes that either the parent or the child are in a tenant + * schema. + */ +void +ErrorIfIllegalPartitioningInTenantSchema(Oid parentRelationId, Oid partitionRelationId) +{ + if (get_rel_namespace(partitionRelationId) != get_rel_namespace(parentRelationId)) + { + ereport(ERROR, (errmsg("partitioning within a distributed schema is not " + "supported when the parent and the child " + "are in different schemas"))); + } +} + + +/* + * CreateTenantSchemaColocationId returns new colocation id for a tenant schema. + */ +uint32 +CreateTenantSchemaColocationId(void) +{ + int shardCount = 1; + int replicationFactor = 1; + Oid distributionColumnType = InvalidOid; + Oid distributionColumnCollation = InvalidOid; + uint32 schemaColocationId = CreateColocationGroup( + shardCount, replicationFactor, distributionColumnType, + distributionColumnCollation); + return schemaColocationId; +} + + +/* + * SchemaGetNonShardTableIdList returns all nonshard relation ids + * inside given schema. + */ +static List * +SchemaGetNonShardTableIdList(Oid schemaId) +{ + List *relationIdList = NIL; + + /* scan all relations in pg_class and return all tables inside given schema */ + Relation relationRelation = relation_open(RelationRelationId, AccessShareLock); + + ScanKeyData scanKey[1] = { 0 }; + ScanKeyInit(&scanKey[0], Anum_pg_class_relnamespace, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(schemaId)); + SysScanDesc scanDescriptor = systable_beginscan(relationRelation, ClassNameNspIndexId, + true, NULL, 1, scanKey); + + HeapTuple heapTuple = NULL; + while (HeapTupleIsValid(heapTuple = systable_getnext(scanDescriptor))) + { + Form_pg_class relationForm = (Form_pg_class) GETSTRUCT(heapTuple); + char *relationName = NameStr(relationForm->relname); + Oid relationId = get_relname_relid(relationName, schemaId); + + if (!OidIsValid(relationId)) + { + ereport(ERROR, errmsg("table %s is dropped by a concurrent operation", + relationName)); + } + + /* skip shards */ + if (RelationIsAKnownShard(relationId)) + { + continue; + } + + if (RegularTable(relationId) || PartitionTable(relationId) || + IsForeignTable(relationId)) + { + relationIdList = lappend_oid(relationIdList, relationId); + } + } + + systable_endscan(scanDescriptor); + relation_close(relationRelation, AccessShareLock); + + return relationIdList; +} + + +/* + * EnsureSchemaCanBeDistributed ensures the schema can be distributed. + * Caller should take required the lock on relations and the schema. + * + * It checks: + * - Schema name is in the allowed-list, + * - Schema does not depend on an extension (created by extension), + * - No extension depends on the schema (CREATE EXTENSION SCHEMA ), + * - Some checks for the table for being a valid tenant table. + */ +static void +EnsureSchemaCanBeDistributed(Oid schemaId, List *schemaTableIdList) +{ + /* Ensure schema name is allowed */ + EnsureTenantSchemaNameAllowed(schemaId); + + /* Any schema owned by extension is not allowed */ + char *schemaName = get_namespace_name(schemaId); + ObjectAddress *schemaAddress = palloc0(sizeof(ObjectAddress)); + ObjectAddressSet(*schemaAddress, NamespaceRelationId, schemaId); + if (IsAnyObjectAddressOwnedByExtension(list_make1(schemaAddress), NULL)) + { + ereport(ERROR, (errmsg("schema %s, which is owned by an extension, cannot " + "be distributed", schemaName))); + } + + /* Extension schemas are not allowed */ + ObjectAddress *extensionAddress = FirstExtensionWithSchema(schemaId); + if (extensionAddress) + { + char *extensionName = get_extension_name(extensionAddress->objectId); + ereport(ERROR, (errmsg("schema %s cannot be distributed since it is the schema " + "of extension %s", schemaName, extensionName))); + } + + Oid relationId = InvalidOid; + foreach_oid(relationId, schemaTableIdList) + { + EnsureTenantTable(relationId, "citus_schema_distribute"); + } +} + + +/* + * EnsureTenantTable ensures the table can be a valid tenant table. + * - Current user should be the owner of table, + * - Table kind is supported, + * - Referencing and referenced foreign keys for the table are supported, + * - Table is not owned by an extension, + * - Table should be Citus local or Postgres local table. + */ +void +EnsureTenantTable(Oid relationId, char *operationName) +{ + /* Ensure table owner */ + EnsureTableOwner(relationId); + + /* Check relation kind */ + EnsureTableKindSupportedForTenantSchema(relationId); + + /* Check foreign keys */ + EnsureFKeysForTenantTable(relationId); + + /* Check table not owned by an extension */ + ObjectAddress *tableAddress = palloc0(sizeof(ObjectAddress)); + ObjectAddressSet(*tableAddress, RelationRelationId, relationId); + if (IsAnyObjectAddressOwnedByExtension(list_make1(tableAddress), NULL)) + { + Oid schemaId = get_rel_namespace(relationId); + char *tableName = get_namespace_name(schemaId); + ereport(ERROR, (errmsg("schema cannot be distributed since it has " + "table %s which is owned by an extension", + tableName))); + } + + /* Postgres local tables are allowed */ + if (!IsCitusTable(relationId)) + { + return; + } + + /* Only Citus local tables, amongst Citus table types, are allowed */ + if (!IsCitusTableType(relationId, CITUS_LOCAL_TABLE)) + { + ereport(ERROR, (errmsg("distributed schema cannot have distributed tables"), + errhint("Undistribute distributed tables before " + "'%s'.", operationName))); + } +} + + +/* + * EnsureTenantSchemaNameAllowed ensures if given schema is applicable for registering + * as a tenant schema. + */ +static void +EnsureTenantSchemaNameAllowed(Oid schemaId) +{ + char *schemaName = get_namespace_name(schemaId); + + /* public schema is not allowed */ + if (strcmp(schemaName, "public") == 0) + { + ereport(ERROR, (errmsg("public schema cannot be distributed"))); + } + + /* information_schema schema is not allowed */ + if (strcmp(schemaName, "information_schema") == 0) + { + ereport(ERROR, (errmsg("information_schema schema cannot be distributed"))); + } + + /* pg_temp_xx and pg_toast_temp_xx schemas are not allowed */ + if (isAnyTempNamespace(schemaId)) + { + ereport(ERROR, (errmsg("temporary schema cannot be distributed"))); + } + + /* pg_catalog schema is not allowed */ + if (IsCatalogNamespace(schemaId)) + { + ereport(ERROR, (errmsg("pg_catalog schema cannot be distributed"))); + } + + /* pg_toast schema is not allowed */ + if (IsToastNamespace(schemaId)) + { + ereport(ERROR, (errmsg("pg_toast schema cannot be distributed"))); + } +} + + +/* + * EnsureSchemaExist ensures that schema exists. Caller is responsible to take + * the required lock on the schema. + */ +static void +EnsureSchemaExist(Oid schemaId) +{ + if (!SearchSysCacheExists1(NAMESPACEOID, ObjectIdGetDatum(schemaId))) + { + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("schema with OID %u does not exist", schemaId))); + } +} + + +/* + * UnregisterTenantSchemaGlobally removes given schema from the tenant schema + * metadata table, deletes the colocation group of the schema and sends the + * command to do the same on the workers. + */ +static void +UnregisterTenantSchemaGlobally(Oid schemaId, char *schemaName) +{ + uint32 tenantSchemaColocationId = SchemaIdGetTenantColocationId(schemaId); + + DeleteTenantSchemaLocally(schemaId); + if (EnableMetadataSync) + { + SendCommandToWorkersWithMetadata(TenantSchemaDeleteCommand(schemaName)); + } + + DeleteColocationGroup(tenantSchemaColocationId); +} + + +/* + * citus_internal_unregister_tenant_schema_globally, called by Citus drop hook, + * unregisters the schema when a tenant schema is dropped. + * + * NOTE: We need to pass schema_name as an argument. We cannot use schema id + * to obtain schema name since the schema would have already been dropped when this + * udf is called by the drop hook. + */ +Datum +citus_internal_unregister_tenant_schema_globally(PG_FUNCTION_ARGS) +{ + PG_ENSURE_ARGNOTNULL(0, "schema_id"); + Oid schemaId = PG_GETARG_OID(0); + + PG_ENSURE_ARGNOTNULL(1, "schema_name"); + text *schemaName = PG_GETARG_TEXT_PP(1); + char *schemaNameStr = text_to_cstring(schemaName); + + /* + * Skip on workers because we expect this to be called from the coordinator + * only via drop hook. + */ + if (!IsCoordinator()) + { + PG_RETURN_VOID(); + } + + /* make sure that the schema is dropped already */ + HeapTuple namespaceTuple = SearchSysCache1(NAMESPACEOID, ObjectIdGetDatum(schemaId)); + if (HeapTupleIsValid(namespaceTuple)) + { + ReleaseSysCache(namespaceTuple); + ereport(ERROR, (errmsg("schema is expected to be already dropped " + "because this function is only expected to " + "be called from Citus drop hook"))); + } + UnregisterTenantSchemaGlobally(schemaId, schemaNameStr); + PG_RETURN_VOID(); +} + + +/* + * citus_schema_distribute gets a regular schema name, then converts it to a tenant + * schema. + */ +Datum +citus_schema_distribute(PG_FUNCTION_ARGS) +{ + CheckCitusVersion(ERROR); + EnsureCoordinator(); + + Oid schemaId = PG_GETARG_OID(0); + EnsureSchemaExist(schemaId); + EnsureSchemaOwner(schemaId); + + /* Prevent concurrent table creation under the schema */ + LockDatabaseObject(NamespaceRelationId, schemaId, 0, AccessExclusiveLock); + + /* + * We should ensure the existence of the schema after taking the lock since + * the schema could have been dropped before we acquired the lock. + */ + EnsureSchemaExist(schemaId); + EnsureSchemaOwner(schemaId); + + /* Return if the schema is already a tenant schema */ + char *schemaName = get_namespace_name(schemaId); + if (IsTenantSchema(schemaId)) + { + ereport(NOTICE, (errmsg("schema %s is already distributed", schemaName))); + PG_RETURN_VOID(); + } + + /* Take lock on the relations and filter out partition tables */ + List *tableIdListInSchema = SchemaGetNonShardTableIdList(schemaId); + List *tableIdListToConvert = NIL; + Oid relationId = InvalidOid; + foreach_oid(relationId, tableIdListInSchema) + { + /* prevent concurrent drop of the relation */ + LockRelationOid(relationId, AccessShareLock); + EnsureRelationExists(relationId); + + /* + * Skip partitions as they would be distributed by the parent table. + * + * We should filter out partitions here before distributing the schema. + * Otherwise, converted partitioned table would change oid of partitions and its + * partition tables would fail with oid not exist. + */ + if (PartitionTable(relationId)) + { + continue; + } + + tableIdListToConvert = lappend_oid(tableIdListToConvert, relationId); + } + + /* Makes sure the schema can be distributed. */ + EnsureSchemaCanBeDistributed(schemaId, tableIdListInSchema); + + ereport(NOTICE, (errmsg("distributing the schema %s", schemaName))); + + /* Create colocation id and then single shard tables with the colocation id */ + uint32 colocationId = CreateTenantSchemaColocationId(); + ColocationParam colocationParam = { + .colocationParamType = COLOCATE_WITH_COLOCATION_ID, + .colocationId = colocationId, + }; + + /* + * Collect foreign keys for recreation and then drop fkeys and create single shard + * tables. + */ + List *originalForeignKeyRecreationCommands = NIL; + foreach_oid(relationId, tableIdListToConvert) + { + List *fkeyCommandsForRelation = + GetFKeyCreationCommandsRelationInvolvedWithTableType(relationId, + INCLUDE_ALL_TABLE_TYPES); + originalForeignKeyRecreationCommands = list_concat( + originalForeignKeyRecreationCommands, fkeyCommandsForRelation); + + DropFKeysRelationInvolvedWithTableType(relationId, INCLUDE_ALL_TABLE_TYPES); + CreateSingleShardTable(relationId, colocationParam); + } + + /* We can skip foreign key validations as we are sure about them at start */ + bool skip_validation = true; + ExecuteForeignKeyCreateCommandList(originalForeignKeyRecreationCommands, + skip_validation); + + /* Register the schema locally and sync it to workers */ + InsertTenantSchemaLocally(schemaId, colocationId); + char *registerSchemaCommand = TenantSchemaInsertCommand(schemaId, colocationId); + if (EnableMetadataSync) + { + SendCommandToWorkersWithMetadata(registerSchemaCommand); + } + + PG_RETURN_VOID(); +} + + +/* + * citus_schema_undistribute gets a tenant schema name, then converts it to a regular + * schema by undistributing all tables under it. + */ +Datum +citus_schema_undistribute(PG_FUNCTION_ARGS) +{ + CheckCitusVersion(ERROR); + EnsureCoordinator(); + + Oid schemaId = PG_GETARG_OID(0); + EnsureSchemaExist(schemaId); + EnsureSchemaOwner(schemaId); + + /* Prevent concurrent table creation under the schema */ + LockDatabaseObject(NamespaceRelationId, schemaId, 0, AccessExclusiveLock); + + /* + * We should ensure the existence of the schema after taking the lock since + * the schema could have been dropped before we acquired the lock. + */ + EnsureSchemaExist(schemaId); + EnsureSchemaOwner(schemaId); + + /* The schema should be a tenant schema */ + char *schemaName = get_namespace_name(schemaId); + if (!IsTenantSchema(schemaId)) + { + ereport(ERROR, (errmsg("schema %s is not distributed", schemaName))); + } + + ereport(NOTICE, (errmsg("undistributing schema %s", schemaName))); + + /* Take lock on the relations and filter out partition tables */ + List *tableIdListInSchema = SchemaGetNonShardTableIdList(schemaId); + List *tableIdListToConvert = NIL; + Oid relationId = InvalidOid; + foreach_oid(relationId, tableIdListInSchema) + { + /* prevent concurrent drop of the relation */ + LockRelationOid(relationId, AccessShareLock); + EnsureRelationExists(relationId); + + /* + * Skip partitions as they would be undistributed by the parent table. + * + * We should filter out partitions here before undistributing the schema. + * Otherwise, converted partitioned table would change oid of partitions and its + * partition tables would fail with oid not exist. + */ + if (PartitionTable(relationId)) + { + continue; + } + + tableIdListToConvert = lappend_oid(tableIdListToConvert, relationId); + + /* Only single shard tables are expected during the undistribution of the schema */ + Assert(IsCitusTableType(relationId, SINGLE_SHARD_DISTRIBUTED)); + } + + /* + * First, we need to delete schema metadata and sync it to workers. Otherwise, + * we would get error from `ErrorIfTenantTable` while undistributing the tables. + */ + UnregisterTenantSchemaGlobally(schemaId, schemaName); + UndistributeTables(tableIdListToConvert); + + PG_RETURN_VOID(); +} + + +/* + * ErrorIfTenantTable errors out with the given operation name, + * if the given relation is a tenant table. + */ +void +ErrorIfTenantTable(Oid relationId, const char *operationName) +{ + if (IsTenantSchema(get_rel_namespace(relationId))) + { + ereport(ERROR, (errmsg("%s is not allowed for %s because it belongs to " + "a distributed schema", + generate_qualified_relation_name(relationId), + operationName))); + } +} diff --git a/src/backend/distributed/commands/sequence.c b/src/backend/distributed/commands/sequence.c index f1757bb62..9ff586c8c 100644 --- a/src/backend/distributed/commands/sequence.c +++ b/src/backend/distributed/commands/sequence.c @@ -222,7 +222,7 @@ ExtractDefaultColumnsAndOwnedSequences(Oid relationId, List **columnNameList, bool ColumnDefaultsToNextVal(Oid relationId, AttrNumber attrNumber) { - AssertArg(AttributeNumberIsValid(attrNumber)); + Assert(AttributeNumberIsValid(attrNumber)); Relation relation = RelationIdGetRelation(relationId); Node *defExpr = build_column_default(relation, attrNumber); @@ -668,7 +668,7 @@ PreprocessAlterSequenceOwnerStmt(Node *node, const char *queryString, ProcessUtilityContext processUtilityContext) { AlterTableStmt *stmt = castNode(AlterTableStmt, node); - Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_SEQUENCE); + Assert(stmt->objtype == OBJECT_SEQUENCE); List *sequenceAddresses = GetObjectAddressListFromParseTree((Node *) stmt, false, false); @@ -701,7 +701,7 @@ List * AlterSequenceOwnerStmtObjectAddress(Node *node, bool missing_ok, bool isPostprocess) { AlterTableStmt *stmt = castNode(AlterTableStmt, node); - Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_SEQUENCE); + Assert(stmt->objtype == OBJECT_SEQUENCE); RangeVar *sequence = stmt->relation; Oid seqOid = RangeVarGetRelid(sequence, NoLock, missing_ok); @@ -721,7 +721,7 @@ List * PostprocessAlterSequenceOwnerStmt(Node *node, const char *queryString) { AlterTableStmt *stmt = castNode(AlterTableStmt, node); - Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_SEQUENCE); + Assert(stmt->objtype == OBJECT_SEQUENCE); List *sequenceAddresses = GetObjectAddressListFromParseTree((Node *) stmt, false, true); @@ -755,7 +755,7 @@ PreprocessAlterSequencePersistenceStmt(Node *node, const char *queryString, ProcessUtilityContext processUtilityContext) { AlterTableStmt *stmt = castNode(AlterTableStmt, node); - Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_SEQUENCE); + Assert(stmt->objtype == OBJECT_SEQUENCE); List *sequenceAddresses = GetObjectAddressListFromParseTree((Node *) stmt, false, false); @@ -788,7 +788,7 @@ List * AlterSequencePersistenceStmtObjectAddress(Node *node, bool missing_ok, bool isPostprocess) { AlterTableStmt *stmt = castNode(AlterTableStmt, node); - Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_SEQUENCE); + Assert(stmt->objtype == OBJECT_SEQUENCE); RangeVar *sequence = stmt->relation; Oid seqOid = RangeVarGetRelid(sequence, NoLock, missing_ok); @@ -811,7 +811,7 @@ PreprocessSequenceAlterTableStmt(Node *node, const char *queryString, ProcessUtilityContext processUtilityContext) { AlterTableStmt *stmt = castNode(AlterTableStmt, node); - Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_SEQUENCE); + Assert(stmt->objtype == OBJECT_SEQUENCE); ListCell *cmdCell = NULL; foreach(cmdCell, stmt->cmds) diff --git a/src/backend/distributed/commands/table.c b/src/backend/distributed/commands/table.c index 6d5fcda3f..cd26a741f 100644 --- a/src/backend/distributed/commands/table.c +++ b/src/backend/distributed/commands/table.c @@ -41,6 +41,7 @@ #include "distributed/resource_lock.h" #include "distributed/version_compat.h" #include "distributed/worker_shard_visibility.h" +#include "distributed/tenant_schema_metadata.h" #include "foreign/foreign.h" #include "lib/stringinfo.h" #include "nodes/parsenodes.h" @@ -229,6 +230,17 @@ PostprocessCreateTableStmt(CreateStmt *createStatement, const char *queryString) { PostprocessCreateTableStmtForeignKeys(createStatement); + bool missingOk = false; + Oid relationId = RangeVarGetRelid(createStatement->relation, NoLock, missingOk); + Oid schemaId = get_rel_namespace(relationId); + if (createStatement->ofTypename && IsTenantSchema(schemaId)) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create tables in a distributed schema using " + "CREATE TABLE OF syntax"))); + } + if (createStatement->inhRelations != NIL) { if (createStatement->partbound != NULL) @@ -239,15 +251,31 @@ PostprocessCreateTableStmt(CreateStmt *createStatement, const char *queryString) else { /* process CREATE TABLE ... INHERITS ... */ + + if (IsTenantSchema(schemaId)) + { + ereport(ERROR, (errmsg("tables in a distributed schema cannot inherit " + "or be inherited"))); + } + RangeVar *parentRelation = NULL; foreach_ptr(parentRelation, createStatement->inhRelations) { - bool missingOk = false; Oid parentRelationId = RangeVarGetRelid(parentRelation, NoLock, missingOk); Assert(parentRelationId != InvalidOid); - if (IsCitusTable(parentRelationId)) + /* + * Throw a better error message if the user tries to inherit a + * tenant table or if the user tries to inherit from a tenant + * table. + */ + if (IsTenantSchema(get_rel_namespace(parentRelationId))) + { + ereport(ERROR, (errmsg("tables in a distributed schema cannot " + "inherit or be inherited"))); + } + else if (IsCitusTable(parentRelationId)) { /* here we error out if inheriting a distributed table */ ereport(ERROR, (errmsg("non-distributed tables cannot inherit " @@ -282,6 +310,15 @@ PostprocessCreateTableStmtForeignKeys(CreateStmt *createStatement) bool missingOk = false; Oid relationId = RangeVarGetRelid(createStatement->relation, NoLock, missingOk); + if (ShouldCreateTenantSchemaTable(relationId)) + { + /* + * Avoid unnecessarily adding the table into metadata if we will + * distribute it as a tenant table later. + */ + return; + } + /* * As we are just creating the table, we cannot have foreign keys that our * relation is referenced. So we use INCLUDE_REFERENCING_CONSTRAINTS here. @@ -378,12 +415,22 @@ PostprocessCreateTableStmtPartitionOf(CreateStmt *createStatement, const } } + if (IsTenantSchema(get_rel_namespace(parentRelationId)) || + IsTenantSchema(get_rel_namespace(relationId))) + { + ErrorIfIllegalPartitioningInTenantSchema(parentRelationId, relationId); + } + /* * If a partition is being created and if its parent is a distributed * table, we will distribute this table as well. */ if (IsCitusTable(parentRelationId)) { + /* + * We can create Citus local tables right away, without switching to + * sequential mode, because they are going to have only one shard. + */ if (IsCitusTableType(parentRelationId, CITUS_LOCAL_TABLE)) { CreateCitusLocalTablePartitionOf(createStatement, relationId, @@ -391,18 +438,7 @@ PostprocessCreateTableStmtPartitionOf(CreateStmt *createStatement, const return; } - Var *parentDistributionColumn = DistPartitionKeyOrError(parentRelationId); - char *distributionColumnName = - ColumnToColumnName(parentRelationId, (Node *) parentDistributionColumn); - char parentDistributionMethod = DISTRIBUTE_BY_HASH; - char *parentRelationName = generate_qualified_relation_name(parentRelationId); - - SwitchToSequentialAndLocalExecutionIfPartitionNameTooLong(parentRelationId, - relationId); - - CreateDistributedTable(relationId, distributionColumnName, - parentDistributionMethod, ShardCount, false, - parentRelationName); + DistributePartitionUsingParent(parentRelationId, relationId); } } @@ -465,6 +501,13 @@ PreprocessAlterTableStmtAttachPartition(AlterTableStmt *alterTableStatement, return NIL; } + if (IsTenantSchema(get_rel_namespace(parentRelationId)) || + IsTenantSchema(get_rel_namespace(partitionRelationId))) + { + ErrorIfIllegalPartitioningInTenantSchema(parentRelationId, + partitionRelationId); + } + if (!IsCitusTable(parentRelationId)) { /* @@ -589,19 +632,45 @@ PreprocessAttachCitusPartitionToCitusTable(Oid parentCitusRelationId, Oid /* * DistributePartitionUsingParent takes a parent and a partition relation and - * distributes the partition, using the same distribution column as the parent. - * It creates a *hash* distributed table by default, as partitioned tables can only be - * distributed by hash. + * distributes the partition, using the same distribution column as the parent, if the + * parent has a distribution column. It creates a *hash* distributed table by default, as + * partitioned tables can only be distributed by hash, unless it's null key distributed. + * + * If the parent has no distribution key, we distribute the partition with null key too. */ static void DistributePartitionUsingParent(Oid parentCitusRelationId, Oid partitionRelationId) { + char *parentRelationName = generate_qualified_relation_name(parentCitusRelationId); + + /* + * We can create tenant tables and single shard tables right away, without + * switching to sequential mode, because they are going to have only one shard. + */ + if (ShouldCreateTenantSchemaTable(partitionRelationId)) + { + CreateTenantSchemaTable(partitionRelationId); + return; + } + else if (!HasDistributionKey(parentCitusRelationId)) + { + /* + * If the parent is null key distributed, we should distribute the partition + * with null distribution key as well. + */ + ColocationParam colocationParam = { + .colocationParamType = COLOCATE_WITH_TABLE_LIKE_OPT, + .colocateWithTableName = parentRelationName, + }; + CreateSingleShardTable(partitionRelationId, colocationParam); + return; + } + Var *distributionColumn = DistPartitionKeyOrError(parentCitusRelationId); char *distributionColumnName = ColumnToColumnName(parentCitusRelationId, (Node *) distributionColumn); char distributionMethod = DISTRIBUTE_BY_HASH; - char *parentRelationName = generate_qualified_relation_name(parentCitusRelationId); SwitchToSequentialAndLocalExecutionIfPartitionNameTooLong( parentCitusRelationId, partitionRelationId); @@ -1066,7 +1135,7 @@ PreprocessAlterTableStmt(Node *node, const char *alterTableCommand, if (relKind == RELKIND_SEQUENCE) { AlterTableStmt *stmtCopy = copyObject(alterTableStatement); - AlterTableStmtObjType_compat(stmtCopy) = OBJECT_SEQUENCE; + stmtCopy->objtype = OBJECT_SEQUENCE; #if (PG_VERSION_NUM >= PG_VERSION_15) /* @@ -1096,7 +1165,7 @@ PreprocessAlterTableStmt(Node *node, const char *alterTableCommand, * passes through an AlterTableStmt */ AlterTableStmt *stmtCopy = copyObject(alterTableStatement); - AlterTableStmtObjType_compat(stmtCopy) = OBJECT_VIEW; + stmtCopy->objtype = OBJECT_VIEW; return PreprocessAlterViewStmt((Node *) stmtCopy, alterTableCommand, processUtilityContext); } @@ -1314,6 +1383,16 @@ PreprocessAlterTableStmt(Node *node, const char *alterTableCommand, constraintName, missingOk); rightRelationId = GetReferencedTableId(foreignKeyId); } + + /* + * We support deparsing for DROP CONSTRAINT, but currently deparsing is only + * possible if all subcommands are supported. + */ + if (list_length(commandList) == 1 && + alterTableStatement->objtype == OBJECT_TABLE) + { + deparseAT = true; + } } else if (alterTableType == AT_AddColumn) { @@ -1521,11 +1600,10 @@ PreprocessAlterTableStmt(Node *node, const char *alterTableCommand, DDLJob *ddlJob = palloc0(sizeof(DDLJob)); ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, leftRelationId); - const char *sqlForTaskList = alterTableCommand; if (deparseAT) { newStmt->cmds = list_make1(newCmd); - sqlForTaskList = DeparseTreeNode((Node *) newStmt); + alterTableCommand = DeparseTreeNode((Node *) newStmt); } ddlJob->metadataSyncCommand = useInitialDDLCommandString ? alterTableCommand : NULL; @@ -1541,13 +1619,13 @@ PreprocessAlterTableStmt(Node *node, const char *alterTableCommand, { /* if foreign key or attaching partition index related, use specialized task list function ... */ ddlJob->taskList = InterShardDDLTaskList(leftRelationId, rightRelationId, - sqlForTaskList); + alterTableCommand); } } else { /* ... otherwise use standard DDL task list function */ - ddlJob->taskList = DDLTaskList(leftRelationId, sqlForTaskList); + ddlJob->taskList = DDLTaskList(leftRelationId, alterTableCommand); if (!propagateCommandToWorkers) { ddlJob->taskList = NIL; @@ -2233,6 +2311,53 @@ PreprocessAlterTableSchemaStmt(Node *node, const char *queryString, return NIL; } + Oid oldSchemaId = get_rel_namespace(relationId); + Oid newSchemaId = get_namespace_oid(stmt->newschema, stmt->missing_ok); + if (!OidIsValid(oldSchemaId) || !OidIsValid(newSchemaId)) + { + return NIL; + } + + /* Do nothing if new schema is the same as old schema */ + if (newSchemaId == oldSchemaId) + { + return NIL; + } + + /* Undistribute table if its old schema is a tenant schema */ + if (IsTenantSchema(oldSchemaId) && IsCoordinator()) + { + EnsureUndistributeTenantTableSafe(relationId, + TenantOperationNames[TENANT_SET_SCHEMA]); + + char *oldSchemaName = get_namespace_name(oldSchemaId); + char *tableName = stmt->relation->relname; + ereport(NOTICE, (errmsg("undistributing table %s in distributed schema %s " + "before altering its schema", tableName, oldSchemaName))); + + /* Undistribute tenant table by suppressing weird notices */ + TableConversionParameters params = { + .relationId = relationId, + .cascadeViaForeignKeys = false, + .bypassTenantCheck = true, + .suppressNoticeMessages = true, + }; + UndistributeTable(¶ms); + + /* relation id changes after undistribute_table */ + relationId = get_relname_relid(tableName, oldSchemaId); + + /* + * After undistribution, the table could be Citus table or Postgres table. + * If it is Postgres table, do not propagate the `ALTER TABLE SET SCHEMA` + * command to workers. + */ + if (!IsCitusTable(relationId)) + { + return NIL; + } + } + DDLJob *ddlJob = palloc0(sizeof(DDLJob)); QualifyTreeNode((Node *) stmt); ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId); @@ -2396,13 +2521,13 @@ PostprocessAlterTableStmt(AlterTableStmt *alterTableStatement) char relKind = get_rel_relkind(relationId); if (relKind == RELKIND_SEQUENCE) { - AlterTableStmtObjType_compat(alterTableStatement) = OBJECT_SEQUENCE; + alterTableStatement->objtype = OBJECT_SEQUENCE; PostprocessAlterSequenceOwnerStmt((Node *) alterTableStatement, NULL); return; } else if (relKind == RELKIND_VIEW) { - AlterTableStmtObjType_compat(alterTableStatement) = OBJECT_VIEW; + alterTableStatement->objtype = OBJECT_VIEW; PostprocessAlterViewStmt((Node *) alterTableStatement, NULL); return; } @@ -3392,7 +3517,6 @@ ErrorIfUnsupportedAlterTableStmt(AlterTableStmt *alterTableStatement) break; } -#if PG_VERSION_NUM >= PG_VERSION_14 case AT_DetachPartitionFinalize: { ereport(ERROR, (errmsg("ALTER TABLE .. DETACH PARTITION .. FINALIZE " @@ -3400,7 +3524,6 @@ ErrorIfUnsupportedAlterTableStmt(AlterTableStmt *alterTableStatement) break; } -#endif case AT_DetachPartition: { /* we only allow partitioning commands if they are only subcommand */ @@ -3412,7 +3535,7 @@ ErrorIfUnsupportedAlterTableStmt(AlterTableStmt *alterTableStatement) errhint("You can issue each subcommand " "separately."))); } - #if PG_VERSION_NUM >= PG_VERSION_14 + PartitionCmd *partitionCommand = (PartitionCmd *) command->def; if (partitionCommand->concurrent) @@ -3421,7 +3544,6 @@ ErrorIfUnsupportedAlterTableStmt(AlterTableStmt *alterTableStatement) "CONCURRENTLY commands are currently " "unsupported."))); } - #endif break; } @@ -3464,20 +3586,18 @@ ErrorIfUnsupportedAlterTableStmt(AlterTableStmt *alterTableStatement) case AT_NoForceRowSecurity: case AT_ValidateConstraint: case AT_DropConstraint: /* we do the check for invalidation in AlterTableDropsForeignKey */ -#if PG_VERSION_NUM >= PG_VERSION_14 case AT_SetCompression: -#endif - { - /* - * We will not perform any special check for: - * ALTER TABLE .. SET ACCESS METHOD .. - * ALTER TABLE .. ALTER COLUMN .. SET NOT NULL - * ALTER TABLE .. REPLICA IDENTITY .. - * ALTER TABLE .. VALIDATE CONSTRAINT .. - * ALTER TABLE .. ALTER COLUMN .. SET COMPRESSION .. - */ - break; - } + { + /* + * We will not perform any special check for: + * ALTER TABLE .. SET ACCESS METHOD .. + * ALTER TABLE .. ALTER COLUMN .. SET NOT NULL + * ALTER TABLE .. REPLICA IDENTITY .. + * ALTER TABLE .. VALIDATE CONSTRAINT .. + * ALTER TABLE .. ALTER COLUMN .. SET COMPRESSION .. + */ + break; + } case AT_SetRelOptions: /* SET (...) */ case AT_ResetRelOptions: /* RESET (...) */ @@ -3977,36 +4097,6 @@ MakeNameListFromRangeVar(const RangeVar *rel) } -/* - * ErrorIfTableHasUnsupportedIdentityColumn errors out if the given table has any identity column other than bigint identity column. - */ -void -ErrorIfTableHasUnsupportedIdentityColumn(Oid relationId) -{ - Relation relation = relation_open(relationId, AccessShareLock); - TupleDesc tupleDescriptor = RelationGetDescr(relation); - - for (int attributeIndex = 0; attributeIndex < tupleDescriptor->natts; - attributeIndex++) - { - Form_pg_attribute attributeForm = TupleDescAttr(tupleDescriptor, attributeIndex); - - if (attributeForm->attidentity && attributeForm->atttypid != INT8OID) - { - char *qualifiedRelationName = generate_qualified_relation_name(relationId); - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg( - "cannot complete operation on %s with smallint/int identity column", - qualifiedRelationName), - errhint( - "Use bigint identity column instead."))); - } - } - - relation_close(relation, NoLock); -} - - /* * ErrorIfTableHasIdentityColumn errors out if the given table has identity column */ @@ -4031,3 +4121,145 @@ ErrorIfTableHasIdentityColumn(Oid relationId) relation_close(relation, NoLock); } + + +/* + * ConvertNewTableIfNecessary converts the given table to a tenant schema + * table or a Citus managed table if necessary. + * + * Input node is expected to be a CreateStmt or a CreateTableAsStmt. + */ +void +ConvertNewTableIfNecessary(Node *createStmt) +{ + /* + * Need to increment command counter so that next command + * can see the new table. + */ + CommandCounterIncrement(); + + if (IsA(createStmt, CreateTableAsStmt)) + { + CreateTableAsStmt *createTableAsStmt = (CreateTableAsStmt *) createStmt; + + bool missingOk = false; + Oid createdRelationId = RangeVarGetRelid(createTableAsStmt->into->rel, + NoLock, missingOk); + + if (ShouldCreateTenantSchemaTable(createdRelationId)) + { + /* not try to convert the table if it already exists and IF NOT EXISTS syntax is used */ + if (createTableAsStmt->if_not_exists && IsCitusTable(createdRelationId)) + { + return; + } + + CreateTenantSchemaTable(createdRelationId); + } + + /* + * We simply ignore the tables created by using that syntax when using + * Citus managed tables. + */ + return; + } + + CreateStmt *baseCreateTableStmt = (CreateStmt *) createStmt; + + bool missingOk = false; + Oid createdRelationId = RangeVarGetRelid(baseCreateTableStmt->relation, + NoLock, missingOk); + + /* not try to convert the table if it already exists and IF NOT EXISTS syntax is used */ + if (baseCreateTableStmt->if_not_exists && IsCitusTable(createdRelationId)) + { + return; + } + + /* + * Check ShouldCreateTenantSchemaTable() before ShouldAddNewTableToMetadata() + * because we don't want to unnecessarily add the table into metadata + * (as a Citus managed table) before distributing it as a tenant table. + */ + if (ShouldCreateTenantSchemaTable(createdRelationId)) + { + /* + * We skip creating tenant schema table if the table is a partition + * table because in that case PostprocessCreateTableStmt() should've + * already created a tenant schema table from the partition table. + */ + if (!PartitionTable(createdRelationId)) + { + CreateTenantSchemaTable(createdRelationId); + } + } + else if (ShouldAddNewTableToMetadata(createdRelationId)) + { + /* + * Here we set autoConverted to false, since the user explicitly + * wants these tables to be added to metadata, by setting the + * GUC use_citus_managed_tables to true. + */ + bool autoConverted = false; + bool cascade = true; + CreateCitusLocalTable(createdRelationId, cascade, autoConverted); + } +} + + +/* + * ConvertToTenantTableIfNecessary converts given relation to a tenant table if its + * schema changed to a distributed schema. + */ +void +ConvertToTenantTableIfNecessary(AlterObjectSchemaStmt *stmt) +{ + Assert(stmt->objectType == OBJECT_TABLE || stmt->objectType == OBJECT_FOREIGN_TABLE); + + if (!IsCoordinator()) + { + return; + } + + /* + * We will let Postgres deal with missing_ok + */ + List *tableAddresses = GetObjectAddressListFromParseTree((Node *) stmt, true, true); + + /* the code-path only supports a single object */ + Assert(list_length(tableAddresses) == 1); + + /* We have already asserted that we have exactly 1 address in the addresses. */ + ObjectAddress *tableAddress = linitial(tableAddresses); + char relKind = get_rel_relkind(tableAddress->objectId); + if (relKind == RELKIND_SEQUENCE || relKind == RELKIND_VIEW) + { + return; + } + + Oid relationId = tableAddress->objectId; + Oid schemaId = get_namespace_oid(stmt->newschema, stmt->missing_ok); + if (!OidIsValid(schemaId)) + { + return; + } + + /* + * Make table a tenant table when its schema actually changed. When its schema + * is not changed as in `ALTER TABLE SET SCHEMA `, we detect + * that by seeing the table is still a single shard table. (i.e. not undistributed + * at `preprocess` step) + */ + if (!IsCitusTableType(relationId, SINGLE_SHARD_DISTRIBUTED) && + IsTenantSchema(schemaId)) + { + EnsureTenantTable(relationId, "ALTER TABLE SET SCHEMA"); + + char *schemaName = get_namespace_name(schemaId); + char *tableName = stmt->relation->relname; + ereport(NOTICE, (errmsg("Moving %s into distributed schema %s", + tableName, schemaName))); + + CreateTenantSchemaTable(relationId); + } +} diff --git a/src/backend/distributed/commands/truncate.c b/src/backend/distributed/commands/truncate.c index 52f769a11..70fee6bd5 100644 --- a/src/backend/distributed/commands/truncate.c +++ b/src/backend/distributed/commands/truncate.c @@ -324,7 +324,8 @@ ExecuteTruncateStmtSequentialIfNecessary(TruncateStmt *command) { Oid relationId = RangeVarGetRelid(rangeVar, NoLock, failOK); - if (IsCitusTable(relationId) && !HasDistributionKey(relationId) && + if ((IsCitusTableType(relationId, REFERENCE_TABLE) || + IsCitusTableType(relationId, CITUS_LOCAL_TABLE)) && TableReferenced(relationId)) { char *relationName = get_rel_name(relationId); diff --git a/src/backend/distributed/commands/type.c b/src/backend/distributed/commands/type.c index 3e641fad0..24ca91aeb 100644 --- a/src/backend/distributed/commands/type.c +++ b/src/backend/distributed/commands/type.c @@ -350,7 +350,7 @@ List * AlterTypeStmtObjectAddress(Node *node, bool missing_ok, bool isPostprocess) { AlterTableStmt *stmt = castNode(AlterTableStmt, node); - Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_TYPE); + Assert(stmt->objtype == OBJECT_TYPE); TypeName *typeName = MakeTypeNameFromRangeVar(stmt->relation); Oid typeOid = LookupTypeNameOid(NULL, typeName, missing_ok); @@ -549,7 +549,7 @@ CreateTypeDDLCommandsIdempotent(const ObjectAddress *typeAddress) const char *username = GetUserNameFromId(GetTypeOwner(typeAddress->objectId), false); initStringInfo(&buf); appendStringInfo(&buf, ALTER_TYPE_OWNER_COMMAND, - getObjectIdentity_compat(typeAddress, false), + getObjectIdentity(typeAddress, false), quote_identifier(username)); ddlCommands = lappend(ddlCommands, buf.data); diff --git a/src/backend/distributed/commands/utility_hook.c b/src/backend/distributed/commands/utility_hook.c index cdef7ab97..888b3dfed 100644 --- a/src/backend/distributed/commands/utility_hook.c +++ b/src/backend/distributed/commands/utility_hook.c @@ -33,9 +33,6 @@ #include "access/attnum.h" #include "access/heapam.h" #include "access/htup_details.h" -#if PG_VERSION_NUM < 140000 -#include "access/xact.h" -#endif #include "catalog/catalog.h" #include "catalog/dependency.h" #include "citus_version.h" @@ -60,9 +57,6 @@ #include "distributed/maintenanced.h" #include "distributed/multi_logical_replication.h" #include "distributed/multi_partitioning_utils.h" -#if PG_VERSION_NUM < 140000 -#include "distributed/metadata_cache.h" -#endif #include "distributed/metadata_sync.h" #include "distributed/metadata/distobject.h" #include "distributed/multi_executor.h" @@ -107,16 +101,13 @@ static void ProcessUtilityInternal(PlannedStmt *pstmt, struct QueryEnvironment *queryEnv, DestReceiver *dest, QueryCompletion *completionTag); -#if PG_VERSION_NUM >= 140000 static void set_indexsafe_procflags(void); -#endif static char * CurrentSearchPath(void); static void IncrementUtilityHookCountersIfNecessary(Node *parsetree); static void PostStandardProcessUtility(Node *parsetree); static void DecrementUtilityHookCountersIfNecessary(Node *parsetree); static bool IsDropSchemaOrDB(Node *parsetree); static bool ShouldCheckUndistributeCitusLocalTables(void); -static bool ShouldAddNewTableToMetadata(Node *parsetree); /* * ProcessUtilityParseTree is a convenience method to create a PlannedStmt out of @@ -132,8 +123,8 @@ ProcessUtilityParseTree(Node *node, const char *queryString, ProcessUtilityConte plannedStmt->commandType = CMD_UTILITY; plannedStmt->utilityStmt = node; - ProcessUtility_compat(plannedStmt, queryString, false, context, params, NULL, dest, - completionTag); + ProcessUtility(plannedStmt, queryString, false, context, params, NULL, dest, + completionTag); } @@ -149,25 +140,19 @@ ProcessUtilityParseTree(Node *node, const char *queryString, ProcessUtilityConte void multi_ProcessUtility(PlannedStmt *pstmt, const char *queryString, -#if PG_VERSION_NUM >= PG_VERSION_14 bool readOnlyTree, -#endif ProcessUtilityContext context, ParamListInfo params, struct QueryEnvironment *queryEnv, DestReceiver *dest, QueryCompletion *completionTag) { - Node *parsetree; - -#if PG_VERSION_NUM >= PG_VERSION_14 if (readOnlyTree) { pstmt = copyObject(pstmt); } -#endif - parsetree = pstmt->utilityStmt; + Node *parsetree = pstmt->utilityStmt; if (IsA(parsetree, TransactionStmt)) { @@ -187,7 +172,9 @@ multi_ProcessUtility(PlannedStmt *pstmt, IsA(parsetree, ExecuteStmt) || IsA(parsetree, PrepareStmt) || IsA(parsetree, DiscardStmt) || - IsA(parsetree, DeallocateStmt)) + IsA(parsetree, DeallocateStmt) || + IsA(parsetree, DeclareCursorStmt) || + IsA(parsetree, FetchStmt)) { /* * Skip additional checks for common commands that do not have any @@ -198,8 +185,8 @@ multi_ProcessUtility(PlannedStmt *pstmt, * that state. Since we never need to intercept transaction statements, * skip our checks and immediately fall into standard_ProcessUtility. */ - PrevProcessUtility_compat(pstmt, queryString, false, context, - params, queryEnv, dest, completionTag); + PrevProcessUtility(pstmt, queryString, false, context, + params, queryEnv, dest, completionTag); return; } @@ -243,8 +230,8 @@ multi_ProcessUtility(PlannedStmt *pstmt, * Ensure that utility commands do not behave any differently until CREATE * EXTENSION is invoked. */ - PrevProcessUtility_compat(pstmt, queryString, false, context, - params, queryEnv, dest, completionTag); + PrevProcessUtility(pstmt, queryString, false, context, + params, queryEnv, dest, completionTag); return; } @@ -275,8 +262,8 @@ multi_ProcessUtility(PlannedStmt *pstmt, PG_TRY(); { - PrevProcessUtility_compat(pstmt, queryString, false, context, - params, queryEnv, dest, completionTag); + PrevProcessUtility(pstmt, queryString, false, context, + params, queryEnv, dest, completionTag); StoredProcedureLevel -= 1; @@ -309,8 +296,8 @@ multi_ProcessUtility(PlannedStmt *pstmt, PG_TRY(); { - PrevProcessUtility_compat(pstmt, queryString, false, context, - params, queryEnv, dest, completionTag); + PrevProcessUtility(pstmt, queryString, false, context, + params, queryEnv, dest, completionTag); DoBlockLevel -= 1; } @@ -344,26 +331,44 @@ multi_ProcessUtility(PlannedStmt *pstmt, } ResetConstraintDropped(); + /* + * We're only interested in top-level CREATE TABLE commands + * to create a tenant schema table or a Citus managed table. + */ if (context == PROCESS_UTILITY_TOPLEVEL && - ShouldAddNewTableToMetadata(parsetree)) + (IsA(parsetree, CreateStmt) || + IsA(parsetree, CreateForeignTableStmt) || + IsA(parsetree, CreateTableAsStmt))) { - /* - * Here we need to increment command counter so that next command - * can see the new table. - */ - CommandCounterIncrement(); - CreateStmt *createTableStmt = (CreateStmt *) parsetree; - Oid relationId = RangeVarGetRelid(createTableStmt->relation, - NoLock, false); + Node *createStmt = NULL; + if (IsA(parsetree, CreateTableAsStmt)) + { + createStmt = parsetree; + } + else + { + /* + * Not directly cast to CreateStmt to guard against the case where + * the definition of CreateForeignTableStmt changes in future. + */ + createStmt = + IsA(parsetree, CreateStmt) ? parsetree : + (Node *) &(((CreateForeignTableStmt *) parsetree)->base); + } - /* - * Here we set autoConverted to false, since the user explicitly - * wants these tables to be added to metadata, by setting the - * GUC use_citus_managed_tables to true. - */ - bool autoConverted = false; - bool cascade = true; - CreateCitusLocalTable(relationId, cascade, autoConverted); + ConvertNewTableIfNecessary(createStmt); + } + + if (context == PROCESS_UTILITY_TOPLEVEL && + IsA(parsetree, AlterObjectSchemaStmt)) + { + AlterObjectSchemaStmt *alterSchemaStmt = castNode(AlterObjectSchemaStmt, + parsetree); + if (alterSchemaStmt->objectType == OBJECT_TABLE || + alterSchemaStmt->objectType == OBJECT_FOREIGN_TABLE) + { + ConvertToTenantTableIfNecessary(alterSchemaStmt); + } } } @@ -496,8 +501,8 @@ ProcessUtilityInternal(PlannedStmt *pstmt, VariableSetStmt *setStmt = (VariableSetStmt *) parsetree; /* at present, we only implement the NONE and LOCAL behaviors */ - AssertState(PropagateSetCommands == PROPSETCMD_NONE || - PropagateSetCommands == PROPSETCMD_LOCAL); + Assert(PropagateSetCommands == PROPSETCMD_NONE || + PropagateSetCommands == PROPSETCMD_LOCAL); if (IsMultiStatementTransaction() && ShouldPropagateSetCommand(setStmt)) { @@ -630,8 +635,8 @@ ProcessUtilityInternal(PlannedStmt *pstmt, if (IsA(parsetree, AlterTableStmt)) { AlterTableStmt *alterTableStmt = (AlterTableStmt *) parsetree; - if (AlterTableStmtObjType_compat(alterTableStmt) == OBJECT_TABLE || - AlterTableStmtObjType_compat(alterTableStmt) == OBJECT_FOREIGN_TABLE) + if (alterTableStmt->objtype == OBJECT_TABLE || + alterTableStmt->objtype == OBJECT_FOREIGN_TABLE) { ErrorIfAlterDropsPartitionColumn(alterTableStmt); @@ -750,8 +755,8 @@ ProcessUtilityInternal(PlannedStmt *pstmt, PreprocessAlterExtensionCitusStmtForCitusColumnar(parsetree); } - PrevProcessUtility_compat(pstmt, queryString, false, context, - params, queryEnv, dest, completionTag); + PrevProcessUtility(pstmt, queryString, false, context, + params, queryEnv, dest, completionTag); if (isAlterExtensionUpdateCitusStmt) { @@ -994,7 +999,8 @@ UndistributeDisconnectedCitusLocalTables(void) TableConversionParameters params = { .relationId = citusLocalTableId, .cascadeViaForeignKeys = true, - .suppressNoticeMessages = true + .suppressNoticeMessages = true, + .bypassTenantCheck = false }; UndistributeTable(¶ms); } @@ -1060,60 +1066,6 @@ ShouldCheckUndistributeCitusLocalTables(void) } -/* - * ShouldAddNewTableToMetadata takes a Node* and returns true if we need to add a - * newly created table to metadata, false otherwise. - * This function checks whether the given Node* is a CREATE TABLE statement. - * For partitions and temporary tables, ShouldAddNewTableToMetadata returns false. - * For other tables created, returns true, if we are on a coordinator that is added - * as worker, and ofcourse, if the GUC use_citus_managed_tables is set to on. - */ -static bool -ShouldAddNewTableToMetadata(Node *parsetree) -{ - CreateStmt *createTableStmt; - - if (IsA(parsetree, CreateStmt)) - { - createTableStmt = (CreateStmt *) parsetree; - } - else if (IsA(parsetree, CreateForeignTableStmt)) - { - CreateForeignTableStmt *createForeignTableStmt = - (CreateForeignTableStmt *) parsetree; - createTableStmt = (CreateStmt *) &(createForeignTableStmt->base); - } - else - { - /* if the command is not CREATE [FOREIGN] TABLE, we can early return false */ - return false; - } - - if (createTableStmt->relation->relpersistence == RELPERSISTENCE_TEMP || - createTableStmt->partbound != NULL) - { - /* - * Shouldn't add table to metadata if it's a temp table, or a partition. - * Creating partitions of a table that is added to metadata is already handled. - */ - return false; - } - - if (AddAllLocalTablesToMetadata && !IsBinaryUpgrade && - IsCoordinator() && CoordinatorAddedAsWorkerNode()) - { - /* - * We have verified that the GUC is set to true, and we are not upgrading, - * and we are on the coordinator that is added as worker node. - * So return true here, to add this newly created table to metadata. - */ - return true; - } - - return false; -} - - /* * NotifyUtilityHookConstraintDropped sets ConstraintDropped to true to tell us * last command dropped a table constraint. @@ -1242,38 +1194,6 @@ ExecuteDistributedDDLJob(DDLJob *ddlJob) */ if (ddlJob->startNewTransaction) { -#if PG_VERSION_NUM < 140000 - - /* - * Older versions of postgres doesn't have PROC_IN_SAFE_IC flag - * so we cannot use set_indexsafe_procflags in those versions. - * - * For this reason, we do our best to ensure not grabbing any - * snapshots later in the executor. - */ - - /* - * If cache is not populated, system catalog lookups will cause - * the xmin of current backend to change. Then the last phase - * of CREATE INDEX CONCURRENTLY, which is in a separate backend, - * will hang waiting for our backend and result in a deadlock. - * - * We populate the cache before starting the next transaction to - * avoid this. Most of the metadata has already been resolved in - * planning phase, we only need to lookup metadata needed for - * connection establishment. - */ - (void) CurrentDatabaseName(); - - /* - * ConnParams (AuthInfo and PoolInfo) gets a snapshot, which - * will blocks the remote connections to localhost. Hence we warm up - * the cache here so that after we start a new transaction, the entries - * will already be in the hash table, hence we won't be holding any snapshots. - */ - WarmUpConnParamsHash(); -#endif - /* * Since it is not certain whether the code-path that we followed * until reaching here caused grabbing any snapshots or not, we @@ -1292,8 +1212,6 @@ ExecuteDistributedDDLJob(DDLJob *ddlJob) CommitTransactionCommand(); StartTransactionCommand(); -#if PG_VERSION_NUM >= 140000 - /* * Tell other backends to ignore us, even if we grab any * snapshots via adaptive executor. @@ -1308,7 +1226,6 @@ ExecuteDistributedDDLJob(DDLJob *ddlJob) * given above. */ Assert(localExecutionSupported == false); -#endif } MemoryContext savedContext = CurrentMemoryContext; @@ -1374,8 +1291,6 @@ ExecuteDistributedDDLJob(DDLJob *ddlJob) } -#if PG_VERSION_NUM >= 140000 - /* * set_indexsafe_procflags sets PROC_IN_SAFE_IC flag in MyProc->statusFlags. * @@ -1398,9 +1313,6 @@ set_indexsafe_procflags(void) } -#endif - - /* * CurrentSearchPath is a C interface for calling current_schemas(bool) that * PostgreSQL exports. @@ -1591,37 +1503,37 @@ DDLTaskList(Oid relationId, const char *commandString) List * NodeDDLTaskList(TargetWorkerSet targets, List *commands) { - /* don't allow concurrent node list changes that require an exclusive lock */ - List *workerNodes = TargetWorkerSetNodeList(targets, RowShareLock); - - if (list_length(workerNodes) <= 0) - { - /* - * if there are no nodes we don't have to plan any ddl tasks. Planning them would - * cause the executor to stop responding. - */ - return NIL; - } - - Task *task = CitusMakeNode(Task); - task->taskType = DDL_TASK; - SetTaskQueryStringList(task, commands); - - WorkerNode *workerNode = NULL; - foreach_ptr(workerNode, workerNodes) - { - ShardPlacement *targetPlacement = CitusMakeNode(ShardPlacement); - targetPlacement->nodeName = workerNode->workerName; - targetPlacement->nodePort = workerNode->workerPort; - targetPlacement->groupId = workerNode->groupId; - - task->taskPlacementList = lappend(task->taskPlacementList, targetPlacement); - } - DDLJob *ddlJob = palloc0(sizeof(DDLJob)); ddlJob->targetObjectAddress = InvalidObjectAddress; ddlJob->metadataSyncCommand = NULL; - ddlJob->taskList = list_make1(task); + + /* don't allow concurrent node list changes that require an exclusive lock */ + List *workerNodes = TargetWorkerSetNodeList(targets, RowShareLock); + + /* + * if there are no nodes we don't have to plan any ddl tasks. Planning them would + * cause the executor to stop responding. + */ + if (list_length(workerNodes) > 0) + { + Task *task = CitusMakeNode(Task); + task->taskType = DDL_TASK; + SetTaskQueryStringList(task, commands); + + WorkerNode *workerNode = NULL; + foreach_ptr(workerNode, workerNodes) + { + ShardPlacement *targetPlacement = CitusMakeNode(ShardPlacement); + targetPlacement->nodeName = workerNode->workerName; + targetPlacement->nodePort = workerNode->workerPort; + targetPlacement->groupId = workerNode->groupId; + + task->taskPlacementList = lappend(task->taskPlacementList, targetPlacement); + } + + ddlJob->taskList = list_make1(task); + } + return list_make1(ddlJob); } diff --git a/src/backend/distributed/commands/vacuum.c b/src/backend/distributed/commands/vacuum.c index 274aebb8f..6bc76b7b8 100644 --- a/src/backend/distributed/commands/vacuum.c +++ b/src/backend/distributed/commands/vacuum.c @@ -359,12 +359,12 @@ DeparseVacuumStmtPrefix(CitusVacuumParams vacuumParams) { appendStringInfoString(vacuumPrefix, "SKIP_LOCKED,"); } - #if PG_VERSION_NUM >= PG_VERSION_14 + if (vacuumFlags & VACOPT_PROCESS_TOAST) { appendStringInfoString(vacuumPrefix, "PROCESS_TOAST,"); } - #endif + if (vacuumParams.truncate != VACOPTVALUE_UNSPECIFIED) { appendStringInfoString(vacuumPrefix, @@ -389,13 +389,11 @@ DeparseVacuumStmtPrefix(CitusVacuumParams vacuumParams) break; } - #if PG_VERSION_NUM >= PG_VERSION_14 case VACOPTVALUE_AUTO: { appendStringInfoString(vacuumPrefix, "INDEX_CLEANUP auto,"); break; } - #endif default: { @@ -501,9 +499,7 @@ VacuumStmtParams(VacuumStmt *vacstmt) bool freeze = false; bool full = false; bool disable_page_skipping = false; - #if PG_VERSION_NUM >= PG_VERSION_14 bool process_toast = false; - #endif /* Set default value */ params.index_cleanup = VACOPTVALUE_UNSPECIFIED; @@ -547,16 +543,12 @@ VacuumStmtParams(VacuumStmt *vacstmt) { disable_page_skipping = defGetBoolean(opt); } - #if PG_VERSION_NUM >= PG_VERSION_14 else if (strcmp(opt->defname, "process_toast") == 0) { process_toast = defGetBoolean(opt); } - #endif else if (strcmp(opt->defname, "index_cleanup") == 0) { - #if PG_VERSION_NUM >= PG_VERSION_14 - /* Interpret no string as the default, which is 'auto' */ if (!opt->arg) { @@ -577,10 +569,6 @@ VacuumStmtParams(VacuumStmt *vacstmt) VACOPTVALUE_DISABLED; } } - #else - params.index_cleanup = defGetBoolean(opt) ? VACOPTVALUE_ENABLED : - VACOPTVALUE_DISABLED; - #endif } else if (strcmp(opt->defname, "truncate") == 0) { @@ -625,9 +613,7 @@ VacuumStmtParams(VacuumStmt *vacstmt) (analyze ? VACOPT_ANALYZE : 0) | (freeze ? VACOPT_FREEZE : 0) | (full ? VACOPT_FULL : 0) | - #if PG_VERSION_NUM >= PG_VERSION_14 (process_toast ? VACOPT_PROCESS_TOAST : 0) | - #endif (disable_page_skipping ? VACOPT_DISABLE_PAGE_SKIPPING : 0); return params; } diff --git a/src/backend/distributed/commands/variableset.c b/src/backend/distributed/commands/variableset.c index a086274e6..277f5b63f 100644 --- a/src/backend/distributed/commands/variableset.c +++ b/src/backend/distributed/commands/variableset.c @@ -89,7 +89,7 @@ PostprocessVariableSetStmt(VariableSetStmt *setStmt, const char *setStmtString) List *connectionList = NIL; /* at present we only support SET LOCAL and SET TRANSACTION */ - AssertArg(ShouldPropagateSetCommand(setStmt)); + Assert(ShouldPropagateSetCommand(setStmt)); /* haven't seen any SET stmts so far in this (sub-)xact: initialize StringInfo */ if (activeSetStmts == NULL) diff --git a/src/backend/distributed/commands/view.c b/src/backend/distributed/commands/view.c index 8219a2907..02d6815d9 100644 --- a/src/backend/distributed/commands/view.c +++ b/src/backend/distributed/commands/view.c @@ -598,7 +598,7 @@ List * PostprocessAlterViewStmt(Node *node, const char *queryString) { AlterTableStmt *stmt = castNode(AlterTableStmt, node); - Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_VIEW); + Assert(stmt->objtype == OBJECT_VIEW); List *viewAddresses = GetObjectAddressListFromParseTree((Node *) stmt, true, true); diff --git a/src/backend/distributed/connection/connection_configuration.c b/src/backend/distributed/connection/connection_configuration.c index 63dfebf8b..bf61f7fac 100644 --- a/src/backend/distributed/connection/connection_configuration.c +++ b/src/backend/distributed/connection/connection_configuration.c @@ -24,7 +24,19 @@ #include "utils/builtins.h" /* stores the string representation of our node connection GUC */ -char *NodeConninfo = ""; +#ifdef USE_SSL +char *NodeConninfo = "sslmode=require"; +#else +char *NodeConninfo = "sslmode=prefer"; +#endif + +/* + * Previously we would use an empty initial value for NodeConnInfo + * PG16 however requires same initial and boot values for configuration parameters + * Therefore we now use this flag in NodeConninfoGucAssignHook + */ +bool checkAtBootPassed = false; + char *LocalHostName = "localhost"; /* represents a list of libpq parameter settings */ @@ -183,7 +195,7 @@ CheckConninfo(const char *conninfo, const char **allowedConninfoKeywords, const char *prev = allowedConninfoKeywords[keywordIdx - 1]; const char *curr = allowedConninfoKeywords[keywordIdx]; - AssertArg(strcmp(prev, curr) < 0); + Assert(strcmp(prev, curr) < 0); } #endif diff --git a/src/backend/distributed/connection/connection_management.c b/src/backend/distributed/connection/connection_management.c index e4aca3ee7..46e757bfe 100644 --- a/src/backend/distributed/connection/connection_management.c +++ b/src/backend/distributed/connection/connection_management.c @@ -1314,33 +1314,6 @@ StartConnectionEstablishment(MultiConnection *connection, ConnectionHashKey *key } -#if PG_VERSION_NUM < 140000 - -/* - * WarmUpConnParamsHash warms up the ConnParamsHash by loading all the - * conn params for active primary nodes. - */ -void -WarmUpConnParamsHash(void) -{ - List *workerNodeList = ActivePrimaryNodeList(AccessShareLock); - WorkerNode *workerNode = NULL; - foreach_ptr(workerNode, workerNodeList) - { - ConnectionHashKey key; - strlcpy(key.hostname, workerNode->workerName, MAX_NODE_LENGTH); - key.port = workerNode->workerPort; - strlcpy(key.database, CurrentDatabaseName(), NAMEDATALEN); - strlcpy(key.user, CurrentUserName(), NAMEDATALEN); - key.replicationConnParam = false; - FindOrCreateConnParamsEntry(&key); - } -} - - -#endif - - /* * FindOrCreateConnParamsEntry searches ConnParamsHash for the given key, * if it is not found, it is created. diff --git a/src/backend/distributed/connection/placement_connection.c b/src/backend/distributed/connection/placement_connection.c index 225bf9708..cc7962e37 100644 --- a/src/backend/distributed/connection/placement_connection.c +++ b/src/backend/distributed/connection/placement_connection.c @@ -958,7 +958,7 @@ ResetShardPlacementAssociation(struct MultiConnection *connection) /* - * ResetPlacementConnectionManagement() disassociates connections from + * ResetPlacementConnectionManagement() dissociates connections from * placements and shards. This will be called at the end of XACT_EVENT_COMMIT * and XACT_EVENT_ABORT. */ diff --git a/src/backend/distributed/deparser/citus_ruleutils.c b/src/backend/distributed/deparser/citus_ruleutils.c index 05e483766..6b865e061 100644 --- a/src/backend/distributed/deparser/citus_ruleutils.c +++ b/src/backend/distributed/deparser/citus_ruleutils.c @@ -22,9 +22,7 @@ #include "access/skey.h" #include "access/stratnum.h" #include "access/sysattr.h" -#if PG_VERSION_NUM >= PG_VERSION_14 #include "access/toast_compression.h" -#endif #include "access/tupdesc.h" #include "catalog/dependency.h" #include "catalog/indexing.h" @@ -386,13 +384,11 @@ pg_get_tableschemadef_string(Oid tableRelationId, IncludeSequenceDefaults atttypmod); appendStringInfoString(&buffer, attributeTypeName); -#if PG_VERSION_NUM >= PG_VERSION_14 if (CompressionMethodIsValid(attributeForm->attcompression)) { appendStringInfo(&buffer, " COMPRESSION %s", GetCompressionMethodName(attributeForm->attcompression)); } -#endif if (attributeForm->attidentity && includeIdentityDefaults) { @@ -939,17 +935,6 @@ deparse_shard_reindex_statement(ReindexStmt *origStmt, Oid distrelid, int64 shar bool IsReindexWithParam_compat(ReindexStmt *reindexStmt, char *param) { -#if PG_VERSION_NUM < PG_VERSION_14 - if (strcmp(param, "concurrently") == 0) - { - return reindexStmt->concurrent; - } - else if (strcmp(param, "verbose") == 0) - { - return reindexStmt->options & REINDEXOPT_VERBOSE; - } - return false; -#else DefElem *opt = NULL; foreach_ptr(opt, reindexStmt->params) { @@ -959,7 +944,6 @@ IsReindexWithParam_compat(ReindexStmt *reindexStmt, char *param) } } return false; -#endif } @@ -974,7 +958,7 @@ AddVacuumParams(ReindexStmt *reindexStmt, StringInfo buffer) { appendStringInfoString(temp, "VERBOSE"); } -#if PG_VERSION_NUM >= PG_VERSION_14 + char *tableSpaceName = NULL; DefElem *opt = NULL; foreach_ptr(opt, reindexStmt->params) @@ -997,7 +981,6 @@ AddVacuumParams(ReindexStmt *reindexStmt, StringInfo buffer) appendStringInfo(temp, "TABLESPACE %s", tableSpaceName); } } -#endif if (temp->len > 0) { @@ -1627,9 +1610,7 @@ RoleSpecString(RoleSpec *spec, bool withQuoteIdentifier) spec->rolename; } - #if PG_VERSION_NUM >= PG_VERSION_14 case ROLESPEC_CURRENT_ROLE: - #endif case ROLESPEC_CURRENT_USER: { return withQuoteIdentifier ? diff --git a/src/backend/distributed/deparser/deparse_schema_stmts.c b/src/backend/distributed/deparser/deparse_schema_stmts.c index 471025b23..cf8bf3418 100644 --- a/src/backend/distributed/deparser/deparse_schema_stmts.c +++ b/src/backend/distributed/deparser/deparse_schema_stmts.c @@ -24,6 +24,7 @@ static void AppendDropSchemaStmt(StringInfo buf, DropStmt *stmt); static void AppendGrantOnSchemaStmt(StringInfo buf, GrantStmt *stmt); static void AppendGrantOnSchemaSchemas(StringInfo buf, GrantStmt *stmt); static void AppendAlterSchemaRenameStmt(StringInfo buf, RenameStmt *stmt); +static void AppendAlterSchemaOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt); char * DeparseCreateSchemaStmt(Node *node) @@ -68,6 +69,31 @@ DeparseGrantOnSchemaStmt(Node *node) } +char * +DeparseAlterSchemaOwnerStmt(Node *node) +{ + AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node); + + StringInfoData str = { 0 }; + initStringInfo(&str); + + AppendAlterSchemaOwnerStmt(&str, stmt); + + return str.data; +} + + +static void +AppendAlterSchemaOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt) +{ + Assert(stmt->objectType == OBJECT_SCHEMA); + + appendStringInfo(buf, "ALTER SCHEMA %s OWNER TO %s;", + quote_identifier(strVal(stmt->object)), + RoleSpecString(stmt->newowner, true)); +} + + char * DeparseAlterSchemaRenameStmt(Node *node) { diff --git a/src/backend/distributed/deparser/deparse_sequence_stmts.c b/src/backend/distributed/deparser/deparse_sequence_stmts.c index 80c4e2dd4..de2afdeec 100644 --- a/src/backend/distributed/deparser/deparse_sequence_stmts.c +++ b/src/backend/distributed/deparser/deparse_sequence_stmts.c @@ -193,7 +193,7 @@ DeparseAlterSequenceOwnerStmt(Node *node) StringInfoData str = { 0 }; initStringInfo(&str); - Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_SEQUENCE); + Assert(stmt->objtype == OBJECT_SEQUENCE); AppendAlterSequenceOwnerStmt(&str, stmt); @@ -208,7 +208,7 @@ DeparseAlterSequenceOwnerStmt(Node *node) static void AppendAlterSequenceOwnerStmt(StringInfo buf, AlterTableStmt *stmt) { - Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_SEQUENCE); + Assert(stmt->objtype == OBJECT_SEQUENCE); RangeVar *seq = stmt->relation; char *qualifiedSequenceName = quote_qualified_identifier(seq->schemaname, seq->relname); @@ -274,7 +274,7 @@ DeparseAlterSequencePersistenceStmt(Node *node) StringInfoData str = { 0 }; initStringInfo(&str); - Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_SEQUENCE); + Assert(stmt->objtype == OBJECT_SEQUENCE); AppendAlterSequencePersistenceStmt(&str, stmt); @@ -289,7 +289,7 @@ DeparseAlterSequencePersistenceStmt(Node *node) static void AppendAlterSequencePersistenceStmt(StringInfo buf, AlterTableStmt *stmt) { - Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_SEQUENCE); + Assert(stmt->objtype == OBJECT_SEQUENCE); RangeVar *seq = stmt->relation; char *qualifiedSequenceName = quote_qualified_identifier(seq->schemaname, diff --git a/src/backend/distributed/deparser/deparse_statistics_stmts.c b/src/backend/distributed/deparser/deparse_statistics_stmts.c index 923af645e..4a165ec72 100644 --- a/src/backend/distributed/deparser/deparse_statistics_stmts.c +++ b/src/backend/distributed/deparser/deparse_statistics_stmts.c @@ -229,7 +229,6 @@ AppendStatTypes(StringInfo buf, CreateStatsStmt *stmt) } -#if PG_VERSION_NUM >= PG_VERSION_14 static void AppendColumnNames(StringInfo buf, CreateStatsStmt *stmt) { @@ -257,36 +256,6 @@ AppendColumnNames(StringInfo buf, CreateStatsStmt *stmt) } -#else -static void -AppendColumnNames(StringInfo buf, CreateStatsStmt *stmt) -{ - ColumnRef *column = NULL; - - foreach_ptr(column, stmt->exprs) - { - if (!IsA(column, ColumnRef) || list_length(column->fields) != 1) - { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg( - "only simple column references are allowed in CREATE STATISTICS"))); - } - - char *columnName = NameListToQuotedString(column->fields); - - appendStringInfoString(buf, columnName); - - if (column != llast(stmt->exprs)) - { - appendStringInfoString(buf, ", "); - } - } -} - - -#endif - static void AppendTableName(StringInfo buf, CreateStatsStmt *stmt) { diff --git a/src/backend/distributed/deparser/deparse_table_stmts.c b/src/backend/distributed/deparser/deparse_table_stmts.c index c12e5401a..1d9ee1739 100644 --- a/src/backend/distributed/deparser/deparse_table_stmts.c +++ b/src/backend/distributed/deparser/deparse_table_stmts.c @@ -31,6 +31,8 @@ static void AppendAlterTableStmt(StringInfo buf, AlterTableStmt *stmt); static void AppendAlterTableCmd(StringInfo buf, AlterTableCmd *alterTableCmd, AlterTableStmt *stmt); static void AppendAlterTableCmdAddColumn(StringInfo buf, AlterTableCmd *alterTableCmd); +static void AppendAlterTableCmdDropConstraint(StringInfo buf, + AlterTableCmd *alterTableCmd); char * DeparseAlterTableSchemaStmt(Node *node) @@ -75,7 +77,7 @@ DeparseAlterTableStmt(Node *node) StringInfoData str = { 0 }; initStringInfo(&str); - Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_TABLE); + Assert(stmt->objtype == OBJECT_TABLE); AppendAlterTableStmt(&str, stmt); return str.data; @@ -94,7 +96,7 @@ AppendAlterTableStmt(StringInfo buf, AlterTableStmt *stmt) stmt->relation->relname); ListCell *cmdCell = NULL; - Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_TABLE); + Assert(stmt->objtype == OBJECT_TABLE); appendStringInfo(buf, "ALTER TABLE %s", identifier); foreach(cmdCell, stmt->cmds) @@ -279,7 +281,9 @@ AppendAlterTableCmdAddConstraint(StringInfo buf, Constraint *constraint, appendStringInfoString(buf, " REFERENCES"); - appendStringInfo(buf, " %s", quote_identifier(constraint->pktable->relname)); + appendStringInfo(buf, " %s", quote_qualified_identifier( + constraint->pktable->schemaname, + constraint->pktable->relname)); if (list_length(constraint->pk_attrs) > 0) { @@ -409,6 +413,12 @@ AppendAlterTableCmd(StringInfo buf, AlterTableCmd *alterTableCmd, AlterTableStmt break; } + case AT_DropConstraint: + { + AppendAlterTableCmdDropConstraint(buf, alterTableCmd); + break; + } + case AT_AddConstraint: { Constraint *constraint = (Constraint *) alterTableCmd->def; @@ -486,3 +496,27 @@ AppendAlterTableCmdAddColumn(StringInfo buf, AlterTableCmd *alterTableCmd) appendStringInfo(buf, " COLLATE %s", identifier); } } + + +/* + * AppendAlterTableCmdDropConstraint builds and appends to the given buffer an + * AT_DropConstraint command from given AlterTableCmd object in the form + * DROP CONSTRAINT ... + */ +static void +AppendAlterTableCmdDropConstraint(StringInfo buf, AlterTableCmd *alterTableCmd) +{ + appendStringInfoString(buf, " DROP CONSTRAINT"); + + if (alterTableCmd->missing_ok) + { + appendStringInfoString(buf, " IF EXISTS"); + } + + appendStringInfo(buf, " %s", quote_identifier(alterTableCmd->name)); + + if (alterTableCmd->behavior == DROP_CASCADE) + { + appendStringInfoString(buf, " CASCADE"); + } +} diff --git a/src/backend/distributed/deparser/deparse_type_stmts.c b/src/backend/distributed/deparser/deparse_type_stmts.c index e12d96ad9..1d70c6791 100644 --- a/src/backend/distributed/deparser/deparse_type_stmts.c +++ b/src/backend/distributed/deparser/deparse_type_stmts.c @@ -122,7 +122,7 @@ DeparseAlterTypeStmt(Node *node) StringInfoData str = { 0 }; initStringInfo(&str); - Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_TYPE); + Assert(stmt->objtype == OBJECT_TYPE); AppendAlterTypeStmt(&str, stmt); @@ -137,7 +137,7 @@ AppendAlterTypeStmt(StringInfo buf, AlterTableStmt *stmt) stmt->relation->relname); ListCell *cmdCell = NULL; - Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_TYPE); + Assert(stmt->objtype == OBJECT_TYPE); appendStringInfo(buf, "ALTER TYPE %s", identifier); foreach(cmdCell, stmt->cmds) diff --git a/src/backend/distributed/deparser/qualify_domain.c b/src/backend/distributed/deparser/qualify_domain.c index b36a0a713..2e163dad0 100644 --- a/src/backend/distributed/deparser/qualify_domain.c +++ b/src/backend/distributed/deparser/qualify_domain.c @@ -245,11 +245,7 @@ QualifyCollate(CollateClause *collClause, bool missing_ok) List *objName = NIL; List *objArgs = NIL; - #if PG_VERSION_NUM >= PG_VERSION_14 getObjectIdentityParts(&collationAddress, &objName, &objArgs, false); - #else - getObjectIdentityParts(&collationAddress, &objName, &objArgs); - #endif collClause->collname = NIL; char *name = NULL; diff --git a/src/backend/distributed/deparser/qualify_sequence_stmt.c b/src/backend/distributed/deparser/qualify_sequence_stmt.c index cece902a6..384e0c953 100644 --- a/src/backend/distributed/deparser/qualify_sequence_stmt.c +++ b/src/backend/distributed/deparser/qualify_sequence_stmt.c @@ -34,7 +34,7 @@ void QualifyAlterSequenceOwnerStmt(Node *node) { AlterTableStmt *stmt = castNode(AlterTableStmt, node); - Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_SEQUENCE); + Assert(stmt->objtype == OBJECT_SEQUENCE); RangeVar *seq = stmt->relation; @@ -62,7 +62,7 @@ void QualifyAlterSequencePersistenceStmt(Node *node) { AlterTableStmt *stmt = castNode(AlterTableStmt, node); - Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_SEQUENCE); + Assert(stmt->objtype == OBJECT_SEQUENCE); RangeVar *seq = stmt->relation; diff --git a/src/backend/distributed/deparser/qualify_type_stmt.c b/src/backend/distributed/deparser/qualify_type_stmt.c index 33c80f527..487e6fc97 100644 --- a/src/backend/distributed/deparser/qualify_type_stmt.c +++ b/src/backend/distributed/deparser/qualify_type_stmt.c @@ -123,7 +123,7 @@ void QualifyAlterTypeStmt(Node *node) { AlterTableStmt *stmt = castNode(AlterTableStmt, node); - Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_TYPE); + Assert(stmt->objtype == OBJECT_TYPE); if (stmt->relation->schemaname == NULL) { diff --git a/src/backend/distributed/deparser/ruleutils_13.c b/src/backend/distributed/deparser/ruleutils_13.c deleted file mode 100644 index 86eeb19a1..000000000 --- a/src/backend/distributed/deparser/ruleutils_13.c +++ /dev/null @@ -1,8153 +0,0 @@ -/*------------------------------------------------------------------------- - * - * ruleutils_13.c - * Functions to convert stored expressions/querytrees back to - * source text - * - * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * - * IDENTIFICATION - * src/backend/distributed/deparser/ruleutils_13.c - * - * This needs to be closely in sync with the core code. - *------------------------------------------------------------------------- - */ -#include "distributed/pg_version_constants.h" - -#include "pg_config.h" - -#if (PG_VERSION_NUM >= PG_VERSION_13) && (PG_VERSION_NUM < PG_VERSION_14) - -#include "postgres.h" - -#include -#include -#include - -#include "access/amapi.h" -#include "access/htup_details.h" -#include "access/relation.h" -#include "access/sysattr.h" -#include "access/table.h" -#include "catalog/dependency.h" -#include "catalog/indexing.h" -#include "catalog/pg_aggregate.h" -#include "catalog/pg_am.h" -#include "catalog/pg_authid.h" -#include "catalog/pg_collation.h" -#include "catalog/pg_constraint.h" -#include "catalog/pg_depend.h" -#include "catalog/pg_extension.h" -#include "catalog/pg_foreign_data_wrapper.h" -#include "catalog/pg_language.h" -#include "catalog/pg_opclass.h" -#include "catalog/pg_operator.h" -#include "catalog/pg_partitioned_table.h" -#include "catalog/pg_proc.h" -#include "catalog/pg_statistic_ext.h" -#include "catalog/pg_trigger.h" -#include "catalog/pg_type.h" -#include "commands/defrem.h" -#include "commands/extension.h" -#include "commands/tablespace.h" -#include "common/keywords.h" -#include "distributed/citus_nodefuncs.h" -#include "distributed/citus_ruleutils.h" -#include "executor/spi.h" -#include "foreign/foreign.h" -#include "funcapi.h" -#include "mb/pg_wchar.h" -#include "miscadmin.h" -#include "nodes/makefuncs.h" -#include "nodes/nodeFuncs.h" -#include "nodes/pathnodes.h" -#include "optimizer/optimizer.h" -#include "parser/parse_node.h" -#include "parser/parse_agg.h" -#include "parser/parse_func.h" -#include "parser/parse_node.h" -#include "parser/parse_oper.h" -#include "parser/parser.h" -#include "parser/parsetree.h" -#include "rewrite/rewriteHandler.h" -#include "rewrite/rewriteManip.h" -#include "rewrite/rewriteSupport.h" -#include "utils/array.h" -#include "utils/builtins.h" -#include "utils/fmgroids.h" -#include "utils/hsearch.h" -#include "utils/lsyscache.h" -#include "utils/rel.h" -#include "utils/ruleutils.h" -#include "utils/snapmgr.h" -#include "utils/syscache.h" -#include "utils/typcache.h" -#include "utils/varlena.h" -#include "utils/xml.h" - - -/* ---------- - * Pretty formatting constants - * ---------- - */ - -/* Indent counts */ -#define PRETTYINDENT_STD 8 -#define PRETTYINDENT_JOIN 4 -#define PRETTYINDENT_VAR 4 - -#define PRETTYINDENT_LIMIT 40 /* wrap limit */ - -/* Pretty flags */ -#define PRETTYFLAG_PAREN 0x0001 -#define PRETTYFLAG_INDENT 0x0002 - -/* Default line length for pretty-print wrapping: 0 means wrap always */ -#define WRAP_COLUMN_DEFAULT 0 - -/* macros to test if pretty action needed */ -#define PRETTY_PAREN(context) ((context)->prettyFlags & PRETTYFLAG_PAREN) -#define PRETTY_INDENT(context) ((context)->prettyFlags & PRETTYFLAG_INDENT) - - -/* ---------- - * Local data types - * ---------- - */ - -/* Context info needed for invoking a recursive querytree display routine */ -typedef struct -{ - StringInfo buf; /* output buffer to append to */ - List *namespaces; /* List of deparse_namespace nodes */ - List *windowClause; /* Current query level's WINDOW clause */ - List *windowTList; /* targetlist for resolving WINDOW clause */ - int prettyFlags; /* enabling of pretty-print functions */ - int wrapColumn; /* max line length, or -1 for no limit */ - int indentLevel; /* current indent level for prettyprint */ - bool varprefix; /* true to print prefixes on Vars */ - Oid distrelid; /* the distributed table being modified, if valid */ - int64 shardid; /* a distributed table's shardid, if positive */ - ParseExprKind special_exprkind; /* set only for exprkinds needing special - * handling */ - Bitmapset *appendparents; /* if not null, map child Vars of these relids - * back to the parent rel */ -} deparse_context; - -/* - * Each level of query context around a subtree needs a level of Var namespace. - * A Var having varlevelsup=N refers to the N'th item (counting from 0) in - * the current context's namespaces list. - * - * The rangetable is the list of actual RTEs from the query tree, and the - * cte list is the list of actual CTEs. - * - * rtable_names holds the alias name to be used for each RTE (either a C - * string, or NULL for nameless RTEs such as unnamed joins). - * rtable_columns holds the column alias names to be used for each RTE. - * - * In some cases we need to make names of merged JOIN USING columns unique - * across the whole query, not only per-RTE. If so, unique_using is true - * and using_names is a list of C strings representing names already assigned - * to USING columns. - * - * When deparsing plan trees, there is always just a single item in the - * deparse_namespace list (since a plan tree never contains Vars with - * varlevelsup > 0). We store the PlanState node that is the immediate - * parent of the expression to be deparsed, as well as a list of that - * PlanState's ancestors. In addition, we store its outer and inner subplan - * state nodes, as well as their plan nodes' targetlists, and the index tlist - * if the current plan node might contain INDEX_VAR Vars. (These fields could - * be derived on-the-fly from the current PlanState, but it seems notationally - * clearer to set them up as separate fields.) - */ -typedef struct -{ - List *rtable; /* List of RangeTblEntry nodes */ - List *rtable_names; /* Parallel list of names for RTEs */ - List *rtable_columns; /* Parallel list of deparse_columns structs */ - List *subplans; /* List of Plan trees for SubPlans */ - List *ctes; /* List of CommonTableExpr nodes */ - AppendRelInfo **appendrels; /* Array of AppendRelInfo nodes, or NULL */ - /* Workspace for column alias assignment: */ - bool unique_using; /* Are we making USING names globally unique */ - List *using_names; /* List of assigned names for USING columns */ - /* Remaining fields are used only when deparsing a Plan tree: */ - Plan *plan; /* immediate parent of current expression */ - List *ancestors; /* ancestors of planstate */ - Plan *outer_plan; /* outer subnode, or NULL if none */ - Plan *inner_plan; /* inner subnode, or NULL if none */ - List *outer_tlist; /* referent for OUTER_VAR Vars */ - List *inner_tlist; /* referent for INNER_VAR Vars */ - List *index_tlist; /* referent for INDEX_VAR Vars */ -} deparse_namespace; - -/* Callback signature for resolve_special_varno() */ -typedef void (*rsv_callback) (Node *node, deparse_context *context, - void *callback_arg); - -/* - * Per-relation data about column alias names. - * - * Selecting aliases is unreasonably complicated because of the need to dump - * rules/views whose underlying tables may have had columns added, deleted, or - * renamed since the query was parsed. We must nonetheless print the rule/view - * in a form that can be reloaded and will produce the same results as before. - * - * For each RTE used in the query, we must assign column aliases that are - * unique within that RTE. SQL does not require this of the original query, - * but due to factors such as *-expansion we need to be able to uniquely - * reference every column in a decompiled query. As long as we qualify all - * column references, per-RTE uniqueness is sufficient for that. - * - * However, we can't ensure per-column name uniqueness for unnamed join RTEs, - * since they just inherit column names from their input RTEs, and we can't - * rename the columns at the join level. Most of the time this isn't an issue - * because we don't need to reference the join's output columns as such; we - * can reference the input columns instead. That approach can fail for merged - * JOIN USING columns, however, so when we have one of those in an unnamed - * join, we have to make that column's alias globally unique across the whole - * query to ensure it can be referenced unambiguously. - * - * Another problem is that a JOIN USING clause requires the columns to be - * merged to have the same aliases in both input RTEs, and that no other - * columns in those RTEs or their children conflict with the USING names. - * To handle that, we do USING-column alias assignment in a recursive - * traversal of the query's jointree. When descending through a JOIN with - * USING, we preassign the USING column names to the child columns, overriding - * other rules for column alias assignment. We also mark each RTE with a list - * of all USING column names selected for joins containing that RTE, so that - * when we assign other columns' aliases later, we can avoid conflicts. - * - * Another problem is that if a JOIN's input tables have had columns added or - * deleted since the query was parsed, we must generate a column alias list - * for the join that matches the current set of input columns --- otherwise, a - * change in the number of columns in the left input would throw off matching - * of aliases to columns of the right input. Thus, positions in the printable - * column alias list are not necessarily one-for-one with varattnos of the - * JOIN, so we need a separate new_colnames[] array for printing purposes. - */ -typedef struct -{ - /* - * colnames is an array containing column aliases to use for columns that - * existed when the query was parsed. Dropped columns have NULL entries. - * This array can be directly indexed by varattno to get a Var's name. - * - * Non-NULL entries are guaranteed unique within the RTE, *except* when - * this is for an unnamed JOIN RTE. In that case we merely copy up names - * from the two input RTEs. - * - * During the recursive descent in set_using_names(), forcible assignment - * of a child RTE's column name is represented by pre-setting that element - * of the child's colnames array. So at that stage, NULL entries in this - * array just mean that no name has been preassigned, not necessarily that - * the column is dropped. - */ - int num_cols; /* length of colnames[] array */ - char **colnames; /* array of C strings and NULLs */ - - /* - * new_colnames is an array containing column aliases to use for columns - * that would exist if the query was re-parsed against the current - * definitions of its base tables. This is what to print as the column - * alias list for the RTE. This array does not include dropped columns, - * but it will include columns added since original parsing. Indexes in - * it therefore have little to do with current varattno values. As above, - * entries are unique unless this is for an unnamed JOIN RTE. (In such an - * RTE, we never actually print this array, but we must compute it anyway - * for possible use in computing column names of upper joins.) The - * parallel array is_new_col marks which of these columns are new since - * original parsing. Entries with is_new_col false must match the - * non-NULL colnames entries one-for-one. - */ - int num_new_cols; /* length of new_colnames[] array */ - char **new_colnames; /* array of C strings */ - bool *is_new_col; /* array of bool flags */ - - /* This flag tells whether we should actually print a column alias list */ - bool printaliases; - - /* This list has all names used as USING names in joins above this RTE */ - List *parentUsing; /* names assigned to parent merged columns */ - - /* - * If this struct is for a JOIN RTE, we fill these fields during the - * set_using_names() pass to describe its relationship to its child RTEs. - * - * leftattnos and rightattnos are arrays with one entry per existing - * output column of the join (hence, indexable by join varattno). For a - * simple reference to a column of the left child, leftattnos[i] is the - * child RTE's attno and rightattnos[i] is zero; and conversely for a - * column of the right child. But for merged columns produced by JOIN - * USING/NATURAL JOIN, both leftattnos[i] and rightattnos[i] are nonzero. - * Also, if the column has been dropped, both are zero. - * - * If it's a JOIN USING, usingNames holds the alias names selected for the - * merged columns (these might be different from the original USING list, - * if we had to modify names to achieve uniqueness). - */ - int leftrti; /* rangetable index of left child */ - int rightrti; /* rangetable index of right child */ - int *leftattnos; /* left-child varattnos of join cols, or 0 */ - int *rightattnos; /* right-child varattnos of join cols, or 0 */ - List *usingNames; /* names assigned to merged columns */ -} deparse_columns; - -/* This macro is analogous to rt_fetch(), but for deparse_columns structs */ -#define deparse_columns_fetch(rangetable_index, dpns) \ - ((deparse_columns *) list_nth((dpns)->rtable_columns, (rangetable_index)-1)) - -/* - * Entry in set_rtable_names' hash table - */ -typedef struct -{ - char name[NAMEDATALEN]; /* Hash key --- must be first */ - int counter; /* Largest addition used so far for name */ -} NameHashEntry; - - -/* ---------- - * Local functions - * - * Most of these functions used to use fixed-size buffers to build their - * results. Now, they take an (already initialized) StringInfo object - * as a parameter, and append their text output to its contents. - * ---------- - */ -static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces, - Bitmapset *rels_used); -static void set_deparse_for_query(deparse_namespace *dpns, Query *query, - List *parent_namespaces); -static bool has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode); -static void set_using_names(deparse_namespace *dpns, Node *jtnode, - List *parentUsing); -static void set_relation_column_names(deparse_namespace *dpns, - RangeTblEntry *rte, - deparse_columns *colinfo); -static void set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte, - deparse_columns *colinfo); -static bool colname_is_unique(const char *colname, deparse_namespace *dpns, - deparse_columns *colinfo); -static char *make_colname_unique(char *colname, deparse_namespace *dpns, - deparse_columns *colinfo); -static void expand_colnames_array_to(deparse_columns *colinfo, int n); -static void identify_join_columns(JoinExpr *j, RangeTblEntry *jrte, - deparse_columns *colinfo); -static char *get_rtable_name(int rtindex, deparse_context *context); -static void set_deparse_plan(deparse_namespace *dpns, Plan *plan); -static void push_child_plan(deparse_namespace *dpns, Plan *plan, - deparse_namespace *save_dpns); -static void pop_child_plan(deparse_namespace *dpns, - deparse_namespace *save_dpns); -static void push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell, - deparse_namespace *save_dpns); -static void pop_ancestor_plan(deparse_namespace *dpns, - deparse_namespace *save_dpns); -static void get_query_def(Query *query, StringInfo buf, List *parentnamespace, - TupleDesc resultDesc, - int prettyFlags, int wrapColumn, int startIndent); -static void get_query_def_extended(Query *query, StringInfo buf, - List *parentnamespace, Oid distrelid, int64 shardid, - TupleDesc resultDesc, int prettyFlags, int wrapColumn, - int startIndent); -static void get_values_def(List *values_lists, deparse_context *context); -static void get_with_clause(Query *query, deparse_context *context); -static void get_select_query_def(Query *query, deparse_context *context, - TupleDesc resultDesc); -static void get_insert_query_def(Query *query, deparse_context *context); -static void get_update_query_def(Query *query, deparse_context *context); -static void get_update_query_targetlist_def(Query *query, List *targetList, - deparse_context *context, - RangeTblEntry *rte); -static void get_delete_query_def(Query *query, deparse_context *context); -static void get_utility_query_def(Query *query, deparse_context *context); -static void get_basic_select_query(Query *query, deparse_context *context, - TupleDesc resultDesc); -static void get_target_list(List *targetList, deparse_context *context, - TupleDesc resultDesc); -static void get_setop_query(Node *setOp, Query *query, - deparse_context *context, - TupleDesc resultDesc); -static Node *get_rule_sortgroupclause(Index ref, List *tlist, - bool force_colno, - deparse_context *context); -static void get_rule_groupingset(GroupingSet *gset, List *targetlist, - bool omit_parens, deparse_context *context); -static void get_rule_orderby(List *orderList, List *targetList, - bool force_colno, deparse_context *context); -static void get_rule_windowclause(Query *query, deparse_context *context); -static void get_rule_windowspec(WindowClause *wc, List *targetList, - deparse_context *context); -static char *get_variable(Var *var, int levelsup, bool istoplevel, - deparse_context *context); -static void get_special_variable(Node *node, deparse_context *context, - void *callback_arg); -static void resolve_special_varno(Node *node, deparse_context *context, - rsv_callback callback, void *callback_arg); -static Node *find_param_referent(Param *param, deparse_context *context, - deparse_namespace **dpns_p, ListCell **ancestor_cell_p); -static void get_parameter(Param *param, deparse_context *context); -static const char *get_simple_binary_op_name(OpExpr *expr); -static bool isSimpleNode(Node *node, Node *parentNode, int prettyFlags); -static void appendContextKeyword(deparse_context *context, const char *str, - int indentBefore, int indentAfter, int indentPlus); -static void removeStringInfoSpaces(StringInfo str); -static void get_rule_expr(Node *node, deparse_context *context, - bool showimplicit); -static void get_rule_expr_toplevel(Node *node, deparse_context *context, - bool showimplicit); -static void get_rule_expr_funccall(Node *node, deparse_context *context, - bool showimplicit); -static bool looks_like_function(Node *node); -static void get_oper_expr(OpExpr *expr, deparse_context *context); -static void get_func_expr(FuncExpr *expr, deparse_context *context, - bool showimplicit); -static void get_agg_expr(Aggref *aggref, deparse_context *context, - Aggref *original_aggref); -static void get_agg_combine_expr(Node *node, deparse_context *context, - void *callback_arg); -static void get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context); -static void get_coercion_expr(Node *arg, deparse_context *context, - Oid resulttype, int32 resulttypmod, - Node *parentNode); -static void get_const_expr(Const *constval, deparse_context *context, - int showtype); -static void get_const_collation(Const *constval, deparse_context *context); -static void simple_quote_literal(StringInfo buf, const char *val); -static void get_sublink_expr(SubLink *sublink, deparse_context *context); -static void get_tablefunc(TableFunc *tf, deparse_context *context, - bool showimplicit); -static void get_from_clause(Query *query, const char *prefix, - deparse_context *context); -static void get_from_clause_item(Node *jtnode, Query *query, - deparse_context *context); -static void get_column_alias_list(deparse_columns *colinfo, - deparse_context *context); -static void get_from_clause_coldeflist(RangeTblFunction *rtfunc, - deparse_columns *colinfo, - deparse_context *context); -static void get_tablesample_def(TableSampleClause *tablesample, - deparse_context *context); -static void get_opclass_name(Oid opclass, Oid actual_datatype, - StringInfo buf); -static Node *processIndirection(Node *node, deparse_context *context); -static void printSubscripts(SubscriptingRef *aref, deparse_context *context); -static char *get_relation_name(Oid relid); -static char *generate_relation_or_shard_name(Oid relid, Oid distrelid, - int64 shardid, List *namespaces); -static char *generate_rte_shard_name(RangeTblEntry *rangeTableEntry); -static char *generate_fragment_name(char *schemaName, char *tableName); -static char *generate_function_name(Oid funcid, int nargs, - List *argnames, Oid *argtypes, - bool has_variadic, bool *use_variadic_p, - ParseExprKind special_exprkind); - -#define only_marker(rte) ((rte)->inh ? "" : "ONLY ") - - - -/* - * pg_get_query_def parses back one query tree, and outputs the resulting query - * string into given buffer. - */ -void -pg_get_query_def(Query *query, StringInfo buffer) -{ - get_query_def(query, buffer, NIL, NULL, 0, WRAP_COLUMN_DEFAULT, 0); -} - -/* - * get_merged_argument_list merges both IN and OUT arguments lists into one and also - * eliminates the INOUT duplicates(present in both the lists). - */ -bool -get_merged_argument_list(CallStmt *stmt, List **mergedNamedArgList, - Oid **mergedNamedArgTypes, - List **mergedArgumentList, - int *totalArguments) -{ - /* No OUT argument support in Postgres 13 */ - return false; -} - -/* - * pg_get_rule_expr deparses an expression and returns the result as a string. - */ -char * -pg_get_rule_expr(Node *expression) -{ - bool showImplicitCasts = true; - deparse_context context; - OverrideSearchPath *overridePath = NULL; - StringInfo buffer = makeStringInfo(); - - /* - * Set search_path to NIL so that all objects outside of pg_catalog will be - * schema-prefixed. pg_catalog will be added automatically when we call - * PushOverrideSearchPath(), since we set addCatalog to true; - */ - overridePath = GetOverrideSearchPath(CurrentMemoryContext); - overridePath->schemas = NIL; - overridePath->addCatalog = true; - PushOverrideSearchPath(overridePath); - - context.buf = buffer; - context.namespaces = NIL; - context.windowClause = NIL; - context.windowTList = NIL; - context.varprefix = false; - context.prettyFlags = 0; - context.wrapColumn = WRAP_COLUMN_DEFAULT; - context.indentLevel = 0; - context.special_exprkind = EXPR_KIND_NONE; - context.distrelid = InvalidOid; - context.shardid = INVALID_SHARD_ID; - - get_rule_expr(expression, &context, showImplicitCasts); - - /* revert back to original search_path */ - PopOverrideSearchPath(); - - return buffer->data; -} - - -/* - * set_rtable_names: select RTE aliases to be used in printing a query - * - * We fill in dpns->rtable_names with a list of names that is one-for-one with - * the already-filled dpns->rtable list. Each RTE name is unique among those - * in the new namespace plus any ancestor namespaces listed in - * parent_namespaces. - * - * If rels_used isn't NULL, only RTE indexes listed in it are given aliases. - * - * Note that this function is only concerned with relation names, not column - * names. - */ -static void -set_rtable_names(deparse_namespace *dpns, List *parent_namespaces, - Bitmapset *rels_used) -{ - HASHCTL hash_ctl; - HTAB *names_hash; - NameHashEntry *hentry; - bool found; - int rtindex; - ListCell *lc; - - dpns->rtable_names = NIL; - /* nothing more to do if empty rtable */ - if (dpns->rtable == NIL) - return; - - /* - * We use a hash table to hold known names, so that this process is O(N) - * not O(N^2) for N names. - */ - MemSet(&hash_ctl, 0, sizeof(hash_ctl)); - hash_ctl.keysize = NAMEDATALEN; - hash_ctl.entrysize = sizeof(NameHashEntry); - hash_ctl.hcxt = CurrentMemoryContext; - names_hash = hash_create("set_rtable_names names", - list_length(dpns->rtable), - &hash_ctl, - HASH_ELEM | HASH_CONTEXT); - /* Preload the hash table with names appearing in parent_namespaces */ - foreach(lc, parent_namespaces) - { - deparse_namespace *olddpns = (deparse_namespace *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, olddpns->rtable_names) - { - char *oldname = (char *) lfirst(lc2); - - if (oldname == NULL) - continue; - hentry = (NameHashEntry *) hash_search(names_hash, - oldname, - HASH_ENTER, - &found); - /* we do not complain about duplicate names in parent namespaces */ - hentry->counter = 0; - } - } - - /* Now we can scan the rtable */ - rtindex = 1; - foreach(lc, dpns->rtable) - { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); - char *refname; - - /* Just in case this takes an unreasonable amount of time ... */ - CHECK_FOR_INTERRUPTS(); - - if (rels_used && !bms_is_member(rtindex, rels_used)) - { - /* Ignore unreferenced RTE */ - refname = NULL; - } - else if (rte->alias) - { - /* If RTE has a user-defined alias, prefer that */ - refname = rte->alias->aliasname; - } - else if (rte->rtekind == RTE_RELATION) - { - /* Use the current actual name of the relation */ - refname = get_rel_name(rte->relid); - } - else if (rte->rtekind == RTE_JOIN) - { - /* Unnamed join has no refname */ - refname = NULL; - } - else - { - /* Otherwise use whatever the parser assigned */ - refname = rte->eref->aliasname; - } - - /* - * If the selected name isn't unique, append digits to make it so, and - * make a new hash entry for it once we've got a unique name. For a - * very long input name, we might have to truncate to stay within - * NAMEDATALEN. - */ - if (refname) - { - hentry = (NameHashEntry *) hash_search(names_hash, - refname, - HASH_ENTER, - &found); - if (found) - { - /* Name already in use, must choose a new one */ - int refnamelen = strlen(refname); - char *modname = (char *) palloc(refnamelen + 16); - NameHashEntry *hentry2; - - do - { - hentry->counter++; - for (;;) - { - /* - * We avoid using %.*s here because it can misbehave - * if the data is not valid in what libc thinks is the - * prevailing encoding. - */ - memcpy(modname, refname, refnamelen); - sprintf(modname + refnamelen, "_%d", hentry->counter); - if (strlen(modname) < NAMEDATALEN) - break; - /* drop chars from refname to keep all the digits */ - refnamelen = pg_mbcliplen(refname, refnamelen, - refnamelen - 1); - } - hentry2 = (NameHashEntry *) hash_search(names_hash, - modname, - HASH_ENTER, - &found); - } while (found); - hentry2->counter = 0; /* init new hash entry */ - refname = modname; - } - else - { - /* Name not previously used, need only initialize hentry */ - hentry->counter = 0; - } - } - - dpns->rtable_names = lappend(dpns->rtable_names, refname); - rtindex++; - } - - hash_destroy(names_hash); -} - -/* - * set_deparse_for_query: set up deparse_namespace for deparsing a Query tree - * - * For convenience, this is defined to initialize the deparse_namespace struct - * from scratch. - */ -static void -set_deparse_for_query(deparse_namespace *dpns, Query *query, - List *parent_namespaces) -{ - ListCell *lc; - ListCell *lc2; - - /* Initialize *dpns and fill rtable/ctes links */ - memset(dpns, 0, sizeof(deparse_namespace)); - dpns->rtable = query->rtable; - dpns->subplans = NIL; - dpns->ctes = query->cteList; - dpns->appendrels = NULL; - - /* Assign a unique relation alias to each RTE */ - set_rtable_names(dpns, parent_namespaces, NULL); - - /* Initialize dpns->rtable_columns to contain zeroed structs */ - dpns->rtable_columns = NIL; - while (list_length(dpns->rtable_columns) < list_length(dpns->rtable)) - dpns->rtable_columns = lappend(dpns->rtable_columns, - palloc0(sizeof(deparse_columns))); - - /* If it's a utility query, it won't have a jointree */ - if (query->jointree) - { - /* Detect whether global uniqueness of USING names is needed */ - dpns->unique_using = - has_dangerous_join_using(dpns, (Node *) query->jointree); - - /* - * Select names for columns merged by USING, via a recursive pass over - * the query jointree. - */ - set_using_names(dpns, (Node *) query->jointree, NIL); - } - - /* - * Now assign remaining column aliases for each RTE. We do this in a - * linear scan of the rtable, so as to process RTEs whether or not they - * are in the jointree (we mustn't miss NEW.*, INSERT target relations, - * etc). JOIN RTEs must be processed after their children, but this is - * okay because they appear later in the rtable list than their children - * (cf Asserts in identify_join_columns()). - */ - forboth(lc, dpns->rtable, lc2, dpns->rtable_columns) - { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); - deparse_columns *colinfo = (deparse_columns *) lfirst(lc2); - - if (rte->rtekind == RTE_JOIN) - set_join_column_names(dpns, rte, colinfo); - else - set_relation_column_names(dpns, rte, colinfo); - } -} - -/* - * has_dangerous_join_using: search jointree for unnamed JOIN USING - * - * Merged columns of a JOIN USING may act differently from either of the input - * columns, either because they are merged with COALESCE (in a FULL JOIN) or - * because an implicit coercion of the underlying input column is required. - * In such a case the column must be referenced as a column of the JOIN not as - * a column of either input. And this is problematic if the join is unnamed - * (alias-less): we cannot qualify the column's name with an RTE name, since - * there is none. (Forcibly assigning an alias to the join is not a solution, - * since that will prevent legal references to tables below the join.) - * To ensure that every column in the query is unambiguously referenceable, - * we must assign such merged columns names that are globally unique across - * the whole query, aliasing other columns out of the way as necessary. - * - * Because the ensuing re-aliasing is fairly damaging to the readability of - * the query, we don't do this unless we have to. So, we must pre-scan - * the join tree to see if we have to, before starting set_using_names(). - */ -static bool -has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode) -{ - if (IsA(jtnode, RangeTblRef)) - { - /* nothing to do here */ - } - else if (IsA(jtnode, FromExpr)) - { - FromExpr *f = (FromExpr *) jtnode; - ListCell *lc; - - foreach(lc, f->fromlist) - { - if (has_dangerous_join_using(dpns, (Node *) lfirst(lc))) - return true; - } - } - else if (IsA(jtnode, JoinExpr)) - { - JoinExpr *j = (JoinExpr *) jtnode; - - /* Is it an unnamed JOIN with USING? */ - if (j->alias == NULL && j->usingClause) - { - /* - * Yes, so check each join alias var to see if any of them are not - * simple references to underlying columns. If so, we have a - * dangerous situation and must pick unique aliases. - */ - RangeTblEntry *jrte = rt_fetch(j->rtindex, dpns->rtable); - - /* We need only examine the merged columns */ - for (int i = 0; i < jrte->joinmergedcols; i++) - { - Node *aliasvar = list_nth(jrte->joinaliasvars, i); - - if (!IsA(aliasvar, Var)) - return true; - } - } - - /* Nope, but inspect children */ - if (has_dangerous_join_using(dpns, j->larg)) - return true; - if (has_dangerous_join_using(dpns, j->rarg)) - return true; - } - else - elog(ERROR, "unrecognized node type: %d", - (int) nodeTag(jtnode)); - return false; -} - -/* - * set_using_names: select column aliases to be used for merged USING columns - * - * We do this during a recursive descent of the query jointree. - * dpns->unique_using must already be set to determine the global strategy. - * - * Column alias info is saved in the dpns->rtable_columns list, which is - * assumed to be filled with pre-zeroed deparse_columns structs. - * - * parentUsing is a list of all USING aliases assigned in parent joins of - * the current jointree node. (The passed-in list must not be modified.) - */ -static void -set_using_names(deparse_namespace *dpns, Node *jtnode, List *parentUsing) -{ - if (IsA(jtnode, RangeTblRef)) - { - /* nothing to do now */ - } - else if (IsA(jtnode, FromExpr)) - { - FromExpr *f = (FromExpr *) jtnode; - ListCell *lc; - - foreach(lc, f->fromlist) - set_using_names(dpns, (Node *) lfirst(lc), parentUsing); - } - else if (IsA(jtnode, JoinExpr)) - { - JoinExpr *j = (JoinExpr *) jtnode; - RangeTblEntry *rte = rt_fetch(j->rtindex, dpns->rtable); - deparse_columns *colinfo = deparse_columns_fetch(j->rtindex, dpns); - int *leftattnos; - int *rightattnos; - deparse_columns *leftcolinfo; - deparse_columns *rightcolinfo; - int i; - ListCell *lc; - - /* Get info about the shape of the join */ - identify_join_columns(j, rte, colinfo); - leftattnos = colinfo->leftattnos; - rightattnos = colinfo->rightattnos; - - /* Look up the not-yet-filled-in child deparse_columns structs */ - leftcolinfo = deparse_columns_fetch(colinfo->leftrti, dpns); - rightcolinfo = deparse_columns_fetch(colinfo->rightrti, dpns); - - /* - * If this join is unnamed, then we cannot substitute new aliases at - * this level, so any name requirements pushed down to here must be - * pushed down again to the children. - */ - if (rte->alias == NULL) - { - for (i = 0; i < colinfo->num_cols; i++) - { - char *colname = colinfo->colnames[i]; - - if (colname == NULL) - continue; - - /* Push down to left column, unless it's a system column */ - if (leftattnos[i] > 0) - { - expand_colnames_array_to(leftcolinfo, leftattnos[i]); - leftcolinfo->colnames[leftattnos[i] - 1] = colname; - } - - /* Same on the righthand side */ - if (rightattnos[i] > 0) - { - expand_colnames_array_to(rightcolinfo, rightattnos[i]); - rightcolinfo->colnames[rightattnos[i] - 1] = colname; - } - } - } - - /* - * If there's a USING clause, select the USING column names and push - * those names down to the children. We have two strategies: - * - * If dpns->unique_using is true, we force all USING names to be - * unique across the whole query level. In principle we'd only need - * the names of dangerous USING columns to be globally unique, but to - * safely assign all USING names in a single pass, we have to enforce - * the same uniqueness rule for all of them. However, if a USING - * column's name has been pushed down from the parent, we should use - * it as-is rather than making a uniqueness adjustment. This is - * necessary when we're at an unnamed join, and it creates no risk of - * ambiguity. Also, if there's a user-written output alias for a - * merged column, we prefer to use that rather than the input name; - * this simplifies the logic and seems likely to lead to less aliasing - * overall. - * - * If dpns->unique_using is false, we only need USING names to be - * unique within their own join RTE. We still need to honor - * pushed-down names, though. - * - * Though significantly different in results, these two strategies are - * implemented by the same code, with only the difference of whether - * to put assigned names into dpns->using_names. - */ - if (j->usingClause) - { - /* Copy the input parentUsing list so we don't modify it */ - parentUsing = list_copy(parentUsing); - - /* USING names must correspond to the first join output columns */ - expand_colnames_array_to(colinfo, list_length(j->usingClause)); - i = 0; - foreach(lc, j->usingClause) - { - char *colname = strVal(lfirst(lc)); - - /* Assert it's a merged column */ - Assert(leftattnos[i] != 0 && rightattnos[i] != 0); - - /* Adopt passed-down name if any, else select unique name */ - if (colinfo->colnames[i] != NULL) - colname = colinfo->colnames[i]; - else - { - /* Prefer user-written output alias if any */ - if (rte->alias && i < list_length(rte->alias->colnames)) - colname = strVal(list_nth(rte->alias->colnames, i)); - /* Make it appropriately unique */ - colname = make_colname_unique(colname, dpns, colinfo); - if (dpns->unique_using) - dpns->using_names = lappend(dpns->using_names, - colname); - /* Save it as output column name, too */ - colinfo->colnames[i] = colname; - } - - /* Remember selected names for use later */ - colinfo->usingNames = lappend(colinfo->usingNames, colname); - parentUsing = lappend(parentUsing, colname); - - /* Push down to left column, unless it's a system column */ - if (leftattnos[i] > 0) - { - expand_colnames_array_to(leftcolinfo, leftattnos[i]); - leftcolinfo->colnames[leftattnos[i] - 1] = colname; - } - - /* Same on the righthand side */ - if (rightattnos[i] > 0) - { - expand_colnames_array_to(rightcolinfo, rightattnos[i]); - rightcolinfo->colnames[rightattnos[i] - 1] = colname; - } - - i++; - } - } - - /* Mark child deparse_columns structs with correct parentUsing info */ - leftcolinfo->parentUsing = parentUsing; - rightcolinfo->parentUsing = parentUsing; - - /* Now recursively assign USING column names in children */ - set_using_names(dpns, j->larg, parentUsing); - set_using_names(dpns, j->rarg, parentUsing); - } - else - elog(ERROR, "unrecognized node type: %d", - (int) nodeTag(jtnode)); -} - -/* - * set_relation_column_names: select column aliases for a non-join RTE - * - * Column alias info is saved in *colinfo, which is assumed to be pre-zeroed. - * If any colnames entries are already filled in, those override local - * choices. - */ -static void -set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte, - deparse_columns *colinfo) -{ - int ncolumns; - char **real_colnames; - bool changed_any; - bool has_anonymous; - int noldcolumns; - int i; - int j; - - /* - * Extract the RTE's "real" column names. This is comparable to - * get_rte_attribute_name, except that it's important to disregard dropped - * columns. We put NULL into the array for a dropped column. - */ - if (rte->rtekind == RTE_RELATION || - GetRangeTblKind(rte) == CITUS_RTE_SHARD) - { - /* Relation --- look to the system catalogs for up-to-date info */ - Relation rel; - TupleDesc tupdesc; - - rel = relation_open(rte->relid, AccessShareLock); - tupdesc = RelationGetDescr(rel); - - ncolumns = tupdesc->natts; - real_colnames = (char **) palloc(ncolumns * sizeof(char *)); - - for (i = 0; i < ncolumns; i++) - { - Form_pg_attribute attr = TupleDescAttr(tupdesc, i); - - if (attr->attisdropped) - real_colnames[i] = NULL; - else - real_colnames[i] = pstrdup(NameStr(attr->attname)); - } - relation_close(rel, AccessShareLock); - } - else - { - /* Otherwise use the column names from eref */ - ListCell *lc; - - ncolumns = list_length(rte->eref->colnames); - real_colnames = (char **) palloc(ncolumns * sizeof(char *)); - - i = 0; - foreach(lc, rte->eref->colnames) - { - /* - * If the column name shown in eref is an empty string, then it's - * a column that was dropped at the time of parsing the query, so - * treat it as dropped. - */ - char *cname = strVal(lfirst(lc)); - - if (cname[0] == '\0') - cname = NULL; - real_colnames[i] = cname; - i++; - } - } - - /* - * Ensure colinfo->colnames has a slot for each column. (It could be long - * enough already, if we pushed down a name for the last column.) Note: - * it's possible that there are now more columns than there were when the - * query was parsed, ie colnames could be longer than rte->eref->colnames. - * We must assign unique aliases to the new columns too, else there could - * be unresolved conflicts when the view/rule is reloaded. - */ - expand_colnames_array_to(colinfo, ncolumns); - Assert(colinfo->num_cols == ncolumns); - - /* - * Make sufficiently large new_colnames and is_new_col arrays, too. - * - * Note: because we leave colinfo->num_new_cols zero until after the loop, - * colname_is_unique will not consult that array, which is fine because it - * would only be duplicate effort. - */ - colinfo->new_colnames = (char **) palloc(ncolumns * sizeof(char *)); - colinfo->is_new_col = (bool *) palloc(ncolumns * sizeof(bool)); - - /* - * Scan the columns, select a unique alias for each one, and store it in - * colinfo->colnames and colinfo->new_colnames. The former array has NULL - * entries for dropped columns, the latter omits them. Also mark - * new_colnames entries as to whether they are new since parse time; this - * is the case for entries beyond the length of rte->eref->colnames. - */ - noldcolumns = list_length(rte->eref->colnames); - changed_any = false; - has_anonymous = false; - j = 0; - for (i = 0; i < ncolumns; i++) - { - char *real_colname = real_colnames[i]; - char *colname = colinfo->colnames[i]; - - /* Skip dropped columns */ - if (real_colname == NULL) - { - Assert(colname == NULL); /* colnames[i] is already NULL */ - continue; - } - - /* If alias already assigned, that's what to use */ - if (colname == NULL) - { - /* If user wrote an alias, prefer that over real column name */ - if (rte->alias && i < list_length(rte->alias->colnames)) - colname = strVal(list_nth(rte->alias->colnames, i)); - else - colname = real_colname; - - /* Unique-ify and insert into colinfo */ - colname = make_colname_unique(colname, dpns, colinfo); - - colinfo->colnames[i] = colname; - } - - /* Put names of non-dropped columns in new_colnames[] too */ - colinfo->new_colnames[j] = colname; - /* And mark them as new or not */ - colinfo->is_new_col[j] = (i >= noldcolumns); - j++; - - /* Remember if any assigned aliases differ from "real" name */ - if (!changed_any && strcmp(colname, real_colname) != 0) - changed_any = true; - - /* - * Remember if there is a reference to an anonymous column as named by - * char * FigureColname(Node *node) - */ - if (!has_anonymous && strcmp(real_colname, "?column?") == 0) - has_anonymous = true; - } - - /* - * Set correct length for new_colnames[] array. (Note: if columns have - * been added, colinfo->num_cols includes them, which is not really quite - * right but is harmless, since any new columns must be at the end where - * they won't affect varattnos of pre-existing columns.) - */ - colinfo->num_new_cols = j; - - /* - * For a relation RTE, we need only print the alias column names if any - * are different from the underlying "real" names. For a function RTE, - * always emit a complete column alias list; this is to protect against - * possible instability of the default column names (eg, from altering - * parameter names). For tablefunc RTEs, we never print aliases, because - * the column names are part of the clause itself. For other RTE types, - * print if we changed anything OR if there were user-written column - * aliases (since the latter would be part of the underlying "reality"). - */ - if (rte->rtekind == RTE_RELATION) - colinfo->printaliases = changed_any; - else if (rte->rtekind == RTE_FUNCTION) - colinfo->printaliases = true; - else if (rte->rtekind == RTE_TABLEFUNC) - colinfo->printaliases = false; - else if (rte->alias && rte->alias->colnames != NIL) - colinfo->printaliases = true; - else - colinfo->printaliases = changed_any || has_anonymous; -} - -/* - * set_join_column_names: select column aliases for a join RTE - * - * Column alias info is saved in *colinfo, which is assumed to be pre-zeroed. - * If any colnames entries are already filled in, those override local - * choices. Also, names for USING columns were already chosen by - * set_using_names(). We further expect that column alias selection has been - * completed for both input RTEs. - */ -static void -set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte, - deparse_columns *colinfo) -{ - deparse_columns *leftcolinfo; - deparse_columns *rightcolinfo; - bool changed_any; - int noldcolumns; - int nnewcolumns; - Bitmapset *leftmerged = NULL; - Bitmapset *rightmerged = NULL; - int i; - int j; - int ic; - int jc; - - /* Look up the previously-filled-in child deparse_columns structs */ - leftcolinfo = deparse_columns_fetch(colinfo->leftrti, dpns); - rightcolinfo = deparse_columns_fetch(colinfo->rightrti, dpns); - - /* - * Ensure colinfo->colnames has a slot for each column. (It could be long - * enough already, if we pushed down a name for the last column.) Note: - * it's possible that one or both inputs now have more columns than there - * were when the query was parsed, but we'll deal with that below. We - * only need entries in colnames for pre-existing columns. - */ - noldcolumns = list_length(rte->eref->colnames); - expand_colnames_array_to(colinfo, noldcolumns); - Assert(colinfo->num_cols == noldcolumns); - - /* - * Scan the join output columns, select an alias for each one, and store - * it in colinfo->colnames. If there are USING columns, set_using_names() - * already selected their names, so we can start the loop at the first - * non-merged column. - */ - changed_any = false; - for (i = list_length(colinfo->usingNames); i < noldcolumns; i++) - { - char *colname = colinfo->colnames[i]; - char *real_colname; - - /* Join column must refer to at least one input column */ - Assert(colinfo->leftattnos[i] != 0 || colinfo->rightattnos[i] != 0); - - /* Get the child column name */ - if (colinfo->leftattnos[i] > 0) - real_colname = leftcolinfo->colnames[colinfo->leftattnos[i] - 1]; - else if (colinfo->rightattnos[i] > 0) - real_colname = rightcolinfo->colnames[colinfo->rightattnos[i] - 1]; - else - { - /* We're joining system columns --- use eref name */ - real_colname = strVal(list_nth(rte->eref->colnames, i)); - } - /* If child col has been dropped, no need to assign a join colname */ - if (real_colname == NULL) - { - colinfo->colnames[i] = NULL; - continue; - } - - /* In an unnamed join, just report child column names as-is */ - if (rte->alias == NULL) - { - colinfo->colnames[i] = real_colname; - continue; - } - - /* If alias already assigned, that's what to use */ - if (colname == NULL) - { - /* If user wrote an alias, prefer that over real column name */ - if (rte->alias && i < list_length(rte->alias->colnames)) - colname = strVal(list_nth(rte->alias->colnames, i)); - else - colname = real_colname; - - /* Unique-ify and insert into colinfo */ - colname = make_colname_unique(colname, dpns, colinfo); - - colinfo->colnames[i] = colname; - } - - /* Remember if any assigned aliases differ from "real" name */ - if (!changed_any && strcmp(colname, real_colname) != 0) - changed_any = true; - } - - /* - * Calculate number of columns the join would have if it were re-parsed - * now, and create storage for the new_colnames and is_new_col arrays. - * - * Note: colname_is_unique will be consulting new_colnames[] during the - * loops below, so its not-yet-filled entries must be zeroes. - */ - nnewcolumns = leftcolinfo->num_new_cols + rightcolinfo->num_new_cols - - list_length(colinfo->usingNames); - colinfo->num_new_cols = nnewcolumns; - colinfo->new_colnames = (char **) palloc0(nnewcolumns * sizeof(char *)); - colinfo->is_new_col = (bool *) palloc0(nnewcolumns * sizeof(bool)); - - /* - * Generating the new_colnames array is a bit tricky since any new columns - * added since parse time must be inserted in the right places. This code - * must match the parser, which will order a join's columns as merged - * columns first (in USING-clause order), then non-merged columns from the - * left input (in attnum order), then non-merged columns from the right - * input (ditto). If one of the inputs is itself a join, its columns will - * be ordered according to the same rule, which means newly-added columns - * might not be at the end. We can figure out what's what by consulting - * the leftattnos and rightattnos arrays plus the input is_new_col arrays. - * - * In these loops, i indexes leftattnos/rightattnos (so it's join varattno - * less one), j indexes new_colnames/is_new_col, and ic/jc have similar - * meanings for the current child RTE. - */ - - /* Handle merged columns; they are first and can't be new */ - i = j = 0; - while (i < noldcolumns && - colinfo->leftattnos[i] != 0 && - colinfo->rightattnos[i] != 0) - { - /* column name is already determined and known unique */ - colinfo->new_colnames[j] = colinfo->colnames[i]; - colinfo->is_new_col[j] = false; - - /* build bitmapsets of child attnums of merged columns */ - if (colinfo->leftattnos[i] > 0) - leftmerged = bms_add_member(leftmerged, colinfo->leftattnos[i]); - if (colinfo->rightattnos[i] > 0) - rightmerged = bms_add_member(rightmerged, colinfo->rightattnos[i]); - - i++, j++; - } - - /* Handle non-merged left-child columns */ - ic = 0; - for (jc = 0; jc < leftcolinfo->num_new_cols; jc++) - { - char *child_colname = leftcolinfo->new_colnames[jc]; - - if (!leftcolinfo->is_new_col[jc]) - { - /* Advance ic to next non-dropped old column of left child */ - while (ic < leftcolinfo->num_cols && - leftcolinfo->colnames[ic] == NULL) - ic++; - Assert(ic < leftcolinfo->num_cols); - ic++; - /* If it is a merged column, we already processed it */ - if (bms_is_member(ic, leftmerged)) - continue; - /* Else, advance i to the corresponding existing join column */ - while (i < colinfo->num_cols && - colinfo->colnames[i] == NULL) - i++; - Assert(i < colinfo->num_cols); - Assert(ic == colinfo->leftattnos[i]); - /* Use the already-assigned name of this column */ - colinfo->new_colnames[j] = colinfo->colnames[i]; - i++; - } - else - { - /* - * Unique-ify the new child column name and assign, unless we're - * in an unnamed join, in which case just copy - */ - if (rte->alias != NULL) - { - colinfo->new_colnames[j] = - make_colname_unique(child_colname, dpns, colinfo); - if (!changed_any && - strcmp(colinfo->new_colnames[j], child_colname) != 0) - changed_any = true; - } - else - colinfo->new_colnames[j] = child_colname; - } - - colinfo->is_new_col[j] = leftcolinfo->is_new_col[jc]; - j++; - } - - /* Handle non-merged right-child columns in exactly the same way */ - ic = 0; - for (jc = 0; jc < rightcolinfo->num_new_cols; jc++) - { - char *child_colname = rightcolinfo->new_colnames[jc]; - - if (!rightcolinfo->is_new_col[jc]) - { - /* Advance ic to next non-dropped old column of right child */ - while (ic < rightcolinfo->num_cols && - rightcolinfo->colnames[ic] == NULL) - ic++; - Assert(ic < rightcolinfo->num_cols); - ic++; - /* If it is a merged column, we already processed it */ - if (bms_is_member(ic, rightmerged)) - continue; - /* Else, advance i to the corresponding existing join column */ - while (i < colinfo->num_cols && - colinfo->colnames[i] == NULL) - i++; - Assert(i < colinfo->num_cols); - Assert(ic == colinfo->rightattnos[i]); - /* Use the already-assigned name of this column */ - colinfo->new_colnames[j] = colinfo->colnames[i]; - i++; - } - else - { - /* - * Unique-ify the new child column name and assign, unless we're - * in an unnamed join, in which case just copy - */ - if (rte->alias != NULL) - { - colinfo->new_colnames[j] = - make_colname_unique(child_colname, dpns, colinfo); - if (!changed_any && - strcmp(colinfo->new_colnames[j], child_colname) != 0) - changed_any = true; - } - else - colinfo->new_colnames[j] = child_colname; - } - - colinfo->is_new_col[j] = rightcolinfo->is_new_col[jc]; - j++; - } - - /* Assert we processed the right number of columns */ -#ifdef USE_ASSERT_CHECKING - while (i < colinfo->num_cols && colinfo->colnames[i] == NULL) - i++; - Assert(i == colinfo->num_cols); - Assert(j == nnewcolumns); -#endif - - /* - * For a named join, print column aliases if we changed any from the child - * names. Unnamed joins cannot print aliases. - */ - if (rte->alias != NULL) - colinfo->printaliases = changed_any; - else - colinfo->printaliases = false; -} - -/* - * colname_is_unique: is colname distinct from already-chosen column names? - * - * dpns is query-wide info, colinfo is for the column's RTE - */ -static bool -colname_is_unique(const char *colname, deparse_namespace *dpns, - deparse_columns *colinfo) -{ - int i; - ListCell *lc; - - /* Check against already-assigned column aliases within RTE */ - for (i = 0; i < colinfo->num_cols; i++) - { - char *oldname = colinfo->colnames[i]; - - if (oldname && strcmp(oldname, colname) == 0) - return false; - } - - /* - * If we're building a new_colnames array, check that too (this will be - * partially but not completely redundant with the previous checks) - */ - for (i = 0; i < colinfo->num_new_cols; i++) - { - char *oldname = colinfo->new_colnames[i]; - - if (oldname && strcmp(oldname, colname) == 0) - return false; - } - - /* Also check against USING-column names that must be globally unique */ - foreach(lc, dpns->using_names) - { - char *oldname = (char *) lfirst(lc); - - if (strcmp(oldname, colname) == 0) - return false; - } - - /* Also check against names already assigned for parent-join USING cols */ - foreach(lc, colinfo->parentUsing) - { - char *oldname = (char *) lfirst(lc); - - if (strcmp(oldname, colname) == 0) - return false; - } - - return true; -} - -/* - * make_colname_unique: modify colname if necessary to make it unique - * - * dpns is query-wide info, colinfo is for the column's RTE - */ -static char * -make_colname_unique(char *colname, deparse_namespace *dpns, - deparse_columns *colinfo) -{ - /* - * If the selected name isn't unique, append digits to make it so. For a - * very long input name, we might have to truncate to stay within - * NAMEDATALEN. - */ - if (!colname_is_unique(colname, dpns, colinfo)) - { - int colnamelen = strlen(colname); - char *modname = (char *) palloc(colnamelen + 16); - int i = 0; - - do - { - i++; - for (;;) - { - /* - * We avoid using %.*s here because it can misbehave if the - * data is not valid in what libc thinks is the prevailing - * encoding. - */ - memcpy(modname, colname, colnamelen); - sprintf(modname + colnamelen, "_%d", i); - if (strlen(modname) < NAMEDATALEN) - break; - /* drop chars from colname to keep all the digits */ - colnamelen = pg_mbcliplen(colname, colnamelen, - colnamelen - 1); - } - } while (!colname_is_unique(modname, dpns, colinfo)); - colname = modname; - } - return colname; -} - -/* - * expand_colnames_array_to: make colinfo->colnames at least n items long - * - * Any added array entries are initialized to zero. - */ -static void -expand_colnames_array_to(deparse_columns *colinfo, int n) -{ - if (n > colinfo->num_cols) - { - if (colinfo->colnames == NULL) - colinfo->colnames = (char **) palloc0(n * sizeof(char *)); - else - { - colinfo->colnames = (char **) repalloc(colinfo->colnames, - n * sizeof(char *)); - memset(colinfo->colnames + colinfo->num_cols, 0, - (n - colinfo->num_cols) * sizeof(char *)); - } - colinfo->num_cols = n; - } -} - -/* - * identify_join_columns: figure out where columns of a join come from - * - * Fills the join-specific fields of the colinfo struct, except for - * usingNames which is filled later. - */ -static void -identify_join_columns(JoinExpr *j, RangeTblEntry *jrte, - deparse_columns *colinfo) -{ - int numjoincols; - int jcolno; - int rcolno; - ListCell *lc; - - /* Extract left/right child RT indexes */ - if (IsA(j->larg, RangeTblRef)) - colinfo->leftrti = ((RangeTblRef *) j->larg)->rtindex; - else if (IsA(j->larg, JoinExpr)) - colinfo->leftrti = ((JoinExpr *) j->larg)->rtindex; - else - elog(ERROR, "unrecognized node type in jointree: %d", - (int) nodeTag(j->larg)); - if (IsA(j->rarg, RangeTblRef)) - colinfo->rightrti = ((RangeTblRef *) j->rarg)->rtindex; - else if (IsA(j->rarg, JoinExpr)) - colinfo->rightrti = ((JoinExpr *) j->rarg)->rtindex; - else - elog(ERROR, "unrecognized node type in jointree: %d", - (int) nodeTag(j->rarg)); - - /* Assert children will be processed earlier than join in second pass */ - Assert(colinfo->leftrti < j->rtindex); - Assert(colinfo->rightrti < j->rtindex); - - /* Initialize result arrays with zeroes */ - numjoincols = list_length(jrte->joinaliasvars); - Assert(numjoincols == list_length(jrte->eref->colnames)); - colinfo->leftattnos = (int *) palloc0(numjoincols * sizeof(int)); - colinfo->rightattnos = (int *) palloc0(numjoincols * sizeof(int)); - - /* - * Deconstruct RTE's joinleftcols/joinrightcols into desired format. - * Recall that the column(s) merged due to USING are the first column(s) - * of the join output. We need not do anything special while scanning - * joinleftcols, but while scanning joinrightcols we must distinguish - * merged from unmerged columns. - */ - jcolno = 0; - foreach(lc, jrte->joinleftcols) - { - int leftattno = lfirst_int(lc); - - colinfo->leftattnos[jcolno++] = leftattno; - } - rcolno = 0; - foreach(lc, jrte->joinrightcols) - { - int rightattno = lfirst_int(lc); - - if (rcolno < jrte->joinmergedcols) /* merged column? */ - colinfo->rightattnos[rcolno] = rightattno; - else - colinfo->rightattnos[jcolno++] = rightattno; - rcolno++; - } - Assert(jcolno == numjoincols); -} - -/* - * get_rtable_name: convenience function to get a previously assigned RTE alias - * - * The RTE must belong to the topmost namespace level in "context". - */ -static char * -get_rtable_name(int rtindex, deparse_context *context) -{ - deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces); - - Assert(rtindex > 0 && rtindex <= list_length(dpns->rtable_names)); - return (char *) list_nth(dpns->rtable_names, rtindex - 1); -} - -/* - * set_deparse_plan: set up deparse_namespace to parse subexpressions - * of a given Plan node - * - * This sets the plan, outer_planstate, inner_planstate, outer_tlist, - * inner_tlist, and index_tlist fields. Caller is responsible for adjusting - * the ancestors list if necessary. Note that the rtable and ctes fields do - * not need to change when shifting attention to different plan nodes in a - * single plan tree. - */ -static void -set_deparse_plan(deparse_namespace *dpns, Plan *plan) -{ - dpns->plan = plan; - - /* - * We special-case Append and MergeAppend to pretend that the first child - * plan is the OUTER referent; we have to interpret OUTER Vars in their - * tlists according to one of the children, and the first one is the most - * natural choice. Likewise special-case ModifyTable to pretend that the - * first child plan is the OUTER referent; this is to support RETURNING - * lists containing references to non-target relations. - */ - if (IsA(plan, Append)) - dpns->outer_plan = linitial(((Append *) plan)->appendplans); - else if (IsA(plan, MergeAppend)) - dpns->outer_plan = linitial(((MergeAppend *) plan)->mergeplans); - else if (IsA(plan, ModifyTable)) - dpns->outer_plan = linitial(((ModifyTable *) plan)->plans); - else - dpns->outer_plan = outerPlan(plan); - - if (dpns->outer_plan) - dpns->outer_tlist = dpns->outer_plan->targetlist; - else - dpns->outer_tlist = NIL; - - /* - * For a SubqueryScan, pretend the subplan is INNER referent. (We don't - * use OUTER because that could someday conflict with the normal meaning.) - * Likewise, for a CteScan, pretend the subquery's plan is INNER referent. - * For ON CONFLICT .. UPDATE we just need the inner tlist to point to the - * excluded expression's tlist. (Similar to the SubqueryScan we don't want - * to reuse OUTER, it's used for RETURNING in some modify table cases, - * although not INSERT .. CONFLICT). - */ - if (IsA(plan, SubqueryScan)) - dpns->inner_plan = ((SubqueryScan *) plan)->subplan; - else if (IsA(plan, CteScan)) - dpns->inner_plan = list_nth(dpns->subplans, - ((CteScan *) plan)->ctePlanId - 1); - else if (IsA(plan, ModifyTable)) - dpns->inner_plan = plan; - else - dpns->inner_plan = innerPlan(plan); - - if (IsA(plan, ModifyTable)) - dpns->inner_tlist = ((ModifyTable *) plan)->exclRelTlist; - else if (dpns->inner_plan) - dpns->inner_tlist = dpns->inner_plan->targetlist; - else - dpns->inner_tlist = NIL; - - /* Set up referent for INDEX_VAR Vars, if needed */ - if (IsA(plan, IndexOnlyScan)) - dpns->index_tlist = ((IndexOnlyScan *) plan)->indextlist; - else if (IsA(plan, ForeignScan)) - dpns->index_tlist = ((ForeignScan *) plan)->fdw_scan_tlist; - else if (IsA(plan, CustomScan)) - dpns->index_tlist = ((CustomScan *) plan)->custom_scan_tlist; - else - dpns->index_tlist = NIL; -} - -/* - * push_child_plan: temporarily transfer deparsing attention to a child plan - * - * When expanding an OUTER_VAR or INNER_VAR reference, we must adjust the - * deparse context in case the referenced expression itself uses - * OUTER_VAR/INNER_VAR. We modify the top stack entry in-place to avoid - * affecting levelsup issues (although in a Plan tree there really shouldn't - * be any). - * - * Caller must provide a local deparse_namespace variable to save the - * previous state for pop_child_plan. - */ -static void -push_child_plan(deparse_namespace *dpns, Plan *plan, - deparse_namespace *save_dpns) -{ - /* Save state for restoration later */ - *save_dpns = *dpns; - - /* Link current plan node into ancestors list */ - dpns->ancestors = lcons(dpns->plan, dpns->ancestors); - - /* Set attention on selected child */ - set_deparse_plan(dpns, plan); -} - -/* - * pop_child_plan: undo the effects of push_child_plan - */ -static void -pop_child_plan(deparse_namespace *dpns, deparse_namespace *save_dpns) -{ - List *ancestors; - - /* Get rid of ancestors list cell added by push_child_plan */ - ancestors = list_delete_first(dpns->ancestors); - - /* Restore fields changed by push_child_plan */ - *dpns = *save_dpns; - - /* Make sure dpns->ancestors is right (may be unnecessary) */ - dpns->ancestors = ancestors; -} - -/* - * push_ancestor_plan: temporarily transfer deparsing attention to an - * ancestor plan - * - * When expanding a Param reference, we must adjust the deparse context - * to match the plan node that contains the expression being printed; - * otherwise we'd fail if that expression itself contains a Param or - * OUTER_VAR/INNER_VAR/INDEX_VAR variable. - * - * The target ancestor is conveniently identified by the ListCell holding it - * in dpns->ancestors. - * - * Caller must provide a local deparse_namespace variable to save the - * previous state for pop_ancestor_plan. - */ -static void -push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell, - deparse_namespace *save_dpns) -{ - Plan *plan = (Plan *) lfirst(ancestor_cell); - - /* Save state for restoration later */ - *save_dpns = *dpns; - - /* Build a new ancestor list with just this node's ancestors */ - dpns->ancestors = - list_copy_tail(dpns->ancestors, - list_cell_number(dpns->ancestors, ancestor_cell) + 1); - - /* Set attention on selected ancestor */ - set_deparse_plan(dpns, plan); -} - -/* - * pop_ancestor_plan: undo the effects of push_ancestor_plan - */ -static void -pop_ancestor_plan(deparse_namespace *dpns, deparse_namespace *save_dpns) -{ - /* Free the ancestor list made in push_ancestor_plan */ - list_free(dpns->ancestors); - - /* Restore fields changed by push_ancestor_plan */ - *dpns = *save_dpns; -} - - -/* ---------- - * deparse_shard_query - Parse back a query for execution on a shard - * - * Builds an SQL string to perform the provided query on a specific shard and - * places this string into the provided buffer. - * ---------- - */ -void -deparse_shard_query(Query *query, Oid distrelid, int64 shardid, - StringInfo buffer) -{ - get_query_def_extended(query, buffer, NIL, distrelid, shardid, NULL, 0, - WRAP_COLUMN_DEFAULT, 0); -} - - -/* ---------- - * get_query_def - Parse back one query parsetree - * - * If resultDesc is not NULL, then it is the output tuple descriptor for - * the view represented by a SELECT query. - * ---------- - */ -static void -get_query_def(Query *query, StringInfo buf, List *parentnamespace, - TupleDesc resultDesc, - int prettyFlags, int wrapColumn, int startIndent) -{ - get_query_def_extended(query, buf, parentnamespace, InvalidOid, 0, resultDesc, - prettyFlags, wrapColumn, startIndent); -} - - -/* ---------- - * get_query_def_extended - Parse back one query parsetree, optionally - * with extension using a shard identifier. - * - * If distrelid is valid and shardid is positive, the provided shardid is added - * any time the provided relid is deparsed, so that the query may be executed - * on a placement for the given shard. - * ---------- - */ -static void -get_query_def_extended(Query *query, StringInfo buf, List *parentnamespace, - Oid distrelid, int64 shardid, TupleDesc resultDesc, - int prettyFlags, int wrapColumn, int startIndent) -{ - deparse_context context; - deparse_namespace dpns; - - OverrideSearchPath *overridePath = NULL; - - /* Guard against excessively long or deeply-nested queries */ - CHECK_FOR_INTERRUPTS(); - check_stack_depth(); - - /* - * Before we begin to examine the query, acquire locks on referenced - * relations, and fix up deleted columns in JOIN RTEs. This ensures - * consistent results. Note we assume it's OK to scribble on the passed - * querytree! - * - * We are only deparsing the query (we are not about to execute it), so we - * only need AccessShareLock on the relations it mentions. - */ - AcquireRewriteLocks(query, false, false); - - /* - * Set search_path to NIL so that all objects outside of pg_catalog will be - * schema-prefixed. pg_catalog will be added automatically when we call - * PushOverrideSearchPath(), since we set addCatalog to true; - */ - overridePath = GetOverrideSearchPath(CurrentMemoryContext); - overridePath->schemas = NIL; - overridePath->addCatalog = true; - PushOverrideSearchPath(overridePath); - - context.buf = buf; - context.namespaces = lcons(&dpns, list_copy(parentnamespace)); - context.windowClause = NIL; - context.windowTList = NIL; - context.varprefix = (parentnamespace != NIL || - list_length(query->rtable) != 1); - context.prettyFlags = prettyFlags; - context.wrapColumn = wrapColumn; - context.indentLevel = startIndent; - context.special_exprkind = EXPR_KIND_NONE; - context.appendparents = NULL; - context.distrelid = distrelid; - context.shardid = shardid; - - set_deparse_for_query(&dpns, query, parentnamespace); - - switch (query->commandType) - { - case CMD_SELECT: - get_select_query_def(query, &context, resultDesc); - break; - - case CMD_UPDATE: - get_update_query_def(query, &context); - break; - - case CMD_INSERT: - get_insert_query_def(query, &context); - break; - - case CMD_DELETE: - get_delete_query_def(query, &context); - break; - - case CMD_NOTHING: - appendStringInfoString(buf, "NOTHING"); - break; - - case CMD_UTILITY: - get_utility_query_def(query, &context); - break; - - default: - elog(ERROR, "unrecognized query command type: %d", - query->commandType); - break; - } - - /* revert back to original search_path */ - PopOverrideSearchPath(); -} - -/* ---------- - * get_values_def - Parse back a VALUES list - * ---------- - */ -static void -get_values_def(List *values_lists, deparse_context *context) -{ - StringInfo buf = context->buf; - bool first_list = true; - ListCell *vtl; - - appendStringInfoString(buf, "VALUES "); - - foreach(vtl, values_lists) - { - List *sublist = (List *) lfirst(vtl); - bool first_col = true; - ListCell *lc; - - if (first_list) - first_list = false; - else - appendStringInfoString(buf, ", "); - - appendStringInfoChar(buf, '('); - foreach(lc, sublist) - { - Node *col = (Node *) lfirst(lc); - - if (first_col) - first_col = false; - else - appendStringInfoChar(buf, ','); - - /* - * Print the value. Whole-row Vars need special treatment. - */ - get_rule_expr_toplevel(col, context, false); - } - appendStringInfoChar(buf, ')'); - } -} - -/* ---------- - * get_with_clause - Parse back a WITH clause - * ---------- - */ -static void -get_with_clause(Query *query, deparse_context *context) -{ - StringInfo buf = context->buf; - const char *sep; - ListCell *l; - - if (query->cteList == NIL) - return; - - if (PRETTY_INDENT(context)) - { - context->indentLevel += PRETTYINDENT_STD; - appendStringInfoChar(buf, ' '); - } - - if (query->hasRecursive) - sep = "WITH RECURSIVE "; - else - sep = "WITH "; - foreach(l, query->cteList) - { - CommonTableExpr *cte = (CommonTableExpr *) lfirst(l); - - appendStringInfoString(buf, sep); - appendStringInfoString(buf, quote_identifier(cte->ctename)); - if (cte->aliascolnames) - { - bool first = true; - ListCell *col; - - appendStringInfoChar(buf, '('); - foreach(col, cte->aliascolnames) - { - if (first) - first = false; - else - appendStringInfoString(buf, ", "); - appendStringInfoString(buf, - quote_identifier(strVal(lfirst(col)))); - } - appendStringInfoChar(buf, ')'); - } - appendStringInfoString(buf, " AS "); - switch (cte->ctematerialized) - { - case CTEMaterializeDefault: - break; - case CTEMaterializeAlways: - appendStringInfoString(buf, "MATERIALIZED "); - break; - case CTEMaterializeNever: - appendStringInfoString(buf, "NOT MATERIALIZED "); - break; - } - appendStringInfoChar(buf, '('); - if (PRETTY_INDENT(context)) - appendContextKeyword(context, "", 0, 0, 0); - get_query_def((Query *) cte->ctequery, buf, context->namespaces, NULL, - context->prettyFlags, context->wrapColumn, - context->indentLevel); - if (PRETTY_INDENT(context)) - appendContextKeyword(context, "", 0, 0, 0); - appendStringInfoChar(buf, ')'); - sep = ", "; - } - - if (PRETTY_INDENT(context)) - { - context->indentLevel -= PRETTYINDENT_STD; - appendContextKeyword(context, "", 0, 0, 0); - } - else - appendStringInfoChar(buf, ' '); -} - -/* ---------- - * get_select_query_def - Parse back a SELECT parsetree - * ---------- - */ -static void -get_select_query_def(Query *query, deparse_context *context, - TupleDesc resultDesc) -{ - StringInfo buf = context->buf; - List *save_windowclause; - List *save_windowtlist; - bool force_colno; - ListCell *l; - - /* Insert the WITH clause if given */ - get_with_clause(query, context); - - /* Set up context for possible window functions */ - save_windowclause = context->windowClause; - context->windowClause = query->windowClause; - save_windowtlist = context->windowTList; - context->windowTList = query->targetList; - - /* - * If the Query node has a setOperations tree, then it's the top level of - * a UNION/INTERSECT/EXCEPT query; only the WITH, ORDER BY and LIMIT - * fields are interesting in the top query itself. - */ - if (query->setOperations) - { - get_setop_query(query->setOperations, query, context, resultDesc); - /* ORDER BY clauses must be simple in this case */ - force_colno = true; - } - else - { - get_basic_select_query(query, context, resultDesc); - force_colno = false; - } - - /* Add the ORDER BY clause if given */ - if (query->sortClause != NIL) - { - appendContextKeyword(context, " ORDER BY ", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); - get_rule_orderby(query->sortClause, query->targetList, - force_colno, context); - } - - /* - * Add the LIMIT/OFFSET clauses if given. If non-default options, use the - * standard spelling of LIMIT. - */ - if (query->limitOffset != NULL) - { - appendContextKeyword(context, " OFFSET ", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); - get_rule_expr(query->limitOffset, context, false); - } - if (query->limitCount != NULL) - { - if (query->limitOption == LIMIT_OPTION_WITH_TIES) - { - // had to add '(' and ')' here because it fails with casting - appendContextKeyword(context, " FETCH FIRST (", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); - get_rule_expr(query->limitCount, context, false); - appendStringInfo(buf, ") ROWS WITH TIES"); - } - else - { - appendContextKeyword(context, " LIMIT ", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); - if (IsA(query->limitCount, Const) && - ((Const *) query->limitCount)->constisnull) - appendStringInfoString(buf, "ALL"); - else - get_rule_expr(query->limitCount, context, false); - } - } - - /* Add FOR [KEY] UPDATE/SHARE clauses if present */ - if (query->hasForUpdate) - { - foreach(l, query->rowMarks) - { - RowMarkClause *rc = (RowMarkClause *) lfirst(l); - - /* don't print implicit clauses */ - if (rc->pushedDown) - continue; - - switch (rc->strength) - { - case LCS_NONE: - /* we intentionally throw an error for LCS_NONE */ - elog(ERROR, "unrecognized LockClauseStrength %d", - (int) rc->strength); - break; - case LCS_FORKEYSHARE: - appendContextKeyword(context, " FOR KEY SHARE", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); - break; - case LCS_FORSHARE: - appendContextKeyword(context, " FOR SHARE", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); - break; - case LCS_FORNOKEYUPDATE: - appendContextKeyword(context, " FOR NO KEY UPDATE", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); - break; - case LCS_FORUPDATE: - appendContextKeyword(context, " FOR UPDATE", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); - break; - } - - appendStringInfo(buf, " OF %s", - quote_identifier(get_rtable_name(rc->rti, - context))); - if (rc->waitPolicy == LockWaitError) - appendStringInfoString(buf, " NOWAIT"); - else if (rc->waitPolicy == LockWaitSkip) - appendStringInfoString(buf, " SKIP LOCKED"); - } - } - - context->windowClause = save_windowclause; - context->windowTList = save_windowtlist; -} - -/* - * Detect whether query looks like SELECT ... FROM VALUES(); - * if so, return the VALUES RTE. Otherwise return NULL. - */ -static RangeTblEntry * -get_simple_values_rte(Query *query, TupleDesc resultDesc) -{ - RangeTblEntry *result = NULL; - ListCell *lc; - int colno; - - /* - * We want to return true even if the Query also contains OLD or NEW rule - * RTEs. So the idea is to scan the rtable and see if there is only one - * inFromCl RTE that is a VALUES RTE. - */ - foreach(lc, query->rtable) - { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); - - if (rte->rtekind == RTE_VALUES && rte->inFromCl) - { - if (result) - return NULL; /* multiple VALUES (probably not possible) */ - result = rte; - } - else if (rte->rtekind == RTE_RELATION && !rte->inFromCl) - continue; /* ignore rule entries */ - else - return NULL; /* something else -> not simple VALUES */ - } - - /* - * We don't need to check the targetlist in any great detail, because - * parser/analyze.c will never generate a "bare" VALUES RTE --- they only - * appear inside auto-generated sub-queries with very restricted - * structure. However, DefineView might have modified the tlist by - * injecting new column aliases; so compare tlist resnames against the - * RTE's names to detect that. - */ - if (result) - { - ListCell *lcn; - - if (list_length(query->targetList) != list_length(result->eref->colnames)) - return NULL; /* this probably cannot happen */ - colno = 0; - forboth(lc, query->targetList, lcn, result->eref->colnames) - { - TargetEntry *tle = (TargetEntry *) lfirst(lc); - char *cname = strVal(lfirst(lcn)); - char *colname; - - if (tle->resjunk) - return NULL; /* this probably cannot happen */ - /* compute name that get_target_list would use for column */ - colno++; - if (resultDesc && colno <= resultDesc->natts) - colname = NameStr(TupleDescAttr(resultDesc, colno - 1)->attname); - else - colname = tle->resname; - - /* does it match the VALUES RTE? */ - if (colname == NULL || strcmp(colname, cname) != 0) - return NULL; /* column name has been changed */ - } - } - - return result; -} - -static void -get_basic_select_query(Query *query, deparse_context *context, - TupleDesc resultDesc) -{ - StringInfo buf = context->buf; - RangeTblEntry *values_rte; - char *sep; - ListCell *l; - - if (PRETTY_INDENT(context)) - { - context->indentLevel += PRETTYINDENT_STD; - appendStringInfoChar(buf, ' '); - } - - /* - * If the query looks like SELECT * FROM (VALUES ...), then print just the - * VALUES part. This reverses what transformValuesClause() did at parse - * time. - */ - values_rte = get_simple_values_rte(query, resultDesc); - if (values_rte) - { - get_values_def(values_rte->values_lists, context); - return; - } - - /* - * Build up the query string - first we say SELECT - */ - appendStringInfoString(buf, "SELECT"); - - /* Add the DISTINCT clause if given */ - if (query->distinctClause != NIL) - { - if (query->hasDistinctOn) - { - appendStringInfoString(buf, " DISTINCT ON ("); - sep = ""; - foreach(l, query->distinctClause) - { - SortGroupClause *srt = (SortGroupClause *) lfirst(l); - - appendStringInfoString(buf, sep); - get_rule_sortgroupclause(srt->tleSortGroupRef, query->targetList, - false, context); - sep = ", "; - } - appendStringInfoChar(buf, ')'); - } - else - appendStringInfoString(buf, " DISTINCT"); - } - - /* Then we tell what to select (the targetlist) */ - get_target_list(query->targetList, context, resultDesc); - - /* Add the FROM clause if needed */ - get_from_clause(query, " FROM ", context); - - /* Add the WHERE clause if given */ - if (query->jointree->quals != NULL) - { - appendContextKeyword(context, " WHERE ", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); - get_rule_expr(query->jointree->quals, context, false); - } - - /* Add the GROUP BY clause if given */ - if (query->groupClause != NULL || query->groupingSets != NULL) - { - ParseExprKind save_exprkind; - - appendContextKeyword(context, " GROUP BY ", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); - - save_exprkind = context->special_exprkind; - context->special_exprkind = EXPR_KIND_GROUP_BY; - - if (query->groupingSets == NIL) - { - sep = ""; - foreach(l, query->groupClause) - { - SortGroupClause *grp = (SortGroupClause *) lfirst(l); - - appendStringInfoString(buf, sep); - get_rule_sortgroupclause(grp->tleSortGroupRef, query->targetList, - false, context); - sep = ", "; - } - } - else - { - sep = ""; - foreach(l, query->groupingSets) - { - GroupingSet *grp = lfirst(l); - - appendStringInfoString(buf, sep); - get_rule_groupingset(grp, query->targetList, true, context); - sep = ", "; - } - } - - context->special_exprkind = save_exprkind; - } - - /* Add the HAVING clause if given */ - if (query->havingQual != NULL) - { - appendContextKeyword(context, " HAVING ", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); - get_rule_expr(query->havingQual, context, false); - } - - /* Add the WINDOW clause if needed */ - if (query->windowClause != NIL) - get_rule_windowclause(query, context); -} - -/* ---------- - * get_target_list - Parse back a SELECT target list - * - * This is also used for RETURNING lists in INSERT/UPDATE/DELETE. - * ---------- - */ -static void -get_target_list(List *targetList, deparse_context *context, - TupleDesc resultDesc) -{ - StringInfo buf = context->buf; - StringInfoData targetbuf; - bool last_was_multiline = false; - char *sep; - int colno; - ListCell *l; - - /* we use targetbuf to hold each TLE's text temporarily */ - initStringInfo(&targetbuf); - - sep = " "; - colno = 0; - foreach(l, targetList) - { - TargetEntry *tle = (TargetEntry *) lfirst(l); - char *colname; - char *attname; - - if (tle->resjunk) - continue; /* ignore junk entries */ - - appendStringInfoString(buf, sep); - sep = ", "; - colno++; - - /* - * Put the new field text into targetbuf so we can decide after we've - * got it whether or not it needs to go on a new line. - */ - resetStringInfo(&targetbuf); - context->buf = &targetbuf; - - /* - * We special-case Var nodes rather than using get_rule_expr. This is - * needed because get_rule_expr will display a whole-row Var as - * "foo.*", which is the preferred notation in most contexts, but at - * the top level of a SELECT list it's not right (the parser will - * expand that notation into multiple columns, yielding behavior - * different from a whole-row Var). We need to call get_variable - * directly so that we can tell it to do the right thing, and so that - * we can get the attribute name which is the default AS label. - */ - if (tle->expr && (IsA(tle->expr, Var))) - { - attname = get_variable((Var *) tle->expr, 0, true, context); - } - else - { - get_rule_expr((Node *) tle->expr, context, true); - /* We'll show the AS name unless it's this: */ - attname = "?column?"; - } - - /* - * Figure out what the result column should be called. In the context - * of a view, use the view's tuple descriptor (so as to pick up the - * effects of any column RENAME that's been done on the view). - * Otherwise, just use what we can find in the TLE. - */ - if (resultDesc && colno <= resultDesc->natts) - colname = NameStr(TupleDescAttr(resultDesc, colno - 1)->attname); - else - colname = tle->resname; - - /* Show AS unless the column's name is correct as-is */ - if (colname) /* resname could be NULL */ - { - if (attname == NULL || strcmp(attname, colname) != 0) - appendStringInfo(&targetbuf, " AS %s", quote_identifier(colname)); - } - - /* Restore context's output buffer */ - context->buf = buf; - - /* Consider line-wrapping if enabled */ - if (PRETTY_INDENT(context) && context->wrapColumn >= 0) - { - int leading_nl_pos; - - /* Does the new field start with a new line? */ - if (targetbuf.len > 0 && targetbuf.data[0] == '\n') - leading_nl_pos = 0; - else - leading_nl_pos = -1; - - /* If so, we shouldn't add anything */ - if (leading_nl_pos >= 0) - { - /* instead, remove any trailing spaces currently in buf */ - removeStringInfoSpaces(buf); - } - else - { - char *trailing_nl; - - /* Locate the start of the current line in the output buffer */ - trailing_nl = strrchr(buf->data, '\n'); - if (trailing_nl == NULL) - trailing_nl = buf->data; - else - trailing_nl++; - - /* - * Add a newline, plus some indentation, if the new field is - * not the first and either the new field would cause an - * overflow or the last field used more than one line. - */ - if (colno > 1 && - ((strlen(trailing_nl) + targetbuf.len > context->wrapColumn) || - last_was_multiline)) - appendContextKeyword(context, "", -PRETTYINDENT_STD, - PRETTYINDENT_STD, PRETTYINDENT_VAR); - } - - /* Remember this field's multiline status for next iteration */ - last_was_multiline = - (strchr(targetbuf.data + leading_nl_pos + 1, '\n') != NULL); - } - - /* Add the new field */ - appendStringInfoString(buf, targetbuf.data); - } - - /* clean up */ - pfree(targetbuf.data); -} - -static void -get_setop_query(Node *setOp, Query *query, deparse_context *context, - TupleDesc resultDesc) -{ - StringInfo buf = context->buf; - bool need_paren; - - /* Guard against excessively long or deeply-nested queries */ - CHECK_FOR_INTERRUPTS(); - check_stack_depth(); - - if (IsA(setOp, RangeTblRef)) - { - RangeTblRef *rtr = (RangeTblRef *) setOp; - RangeTblEntry *rte = rt_fetch(rtr->rtindex, query->rtable); - Query *subquery = rte->subquery; - - Assert(subquery != NULL); - Assert(subquery->setOperations == NULL); - /* Need parens if WITH, ORDER BY, FOR UPDATE, or LIMIT; see gram.y */ - need_paren = (subquery->cteList || - subquery->sortClause || - subquery->rowMarks || - subquery->limitOffset || - subquery->limitCount); - if (need_paren) - appendStringInfoChar(buf, '('); - get_query_def(subquery, buf, context->namespaces, resultDesc, - context->prettyFlags, context->wrapColumn, - context->indentLevel); - if (need_paren) - appendStringInfoChar(buf, ')'); - } - else if (IsA(setOp, SetOperationStmt)) - { - SetOperationStmt *op = (SetOperationStmt *) setOp; - int subindent; - - /* - * We force parens when nesting two SetOperationStmts, except when the - * lefthand input is another setop of the same kind. Syntactically, - * we could omit parens in rather more cases, but it seems best to use - * parens to flag cases where the setop operator changes. If we use - * parens, we also increase the indentation level for the child query. - * - * There are some cases in which parens are needed around a leaf query - * too, but those are more easily handled at the next level down (see - * code above). - */ - if (IsA(op->larg, SetOperationStmt)) - { - SetOperationStmt *lop = (SetOperationStmt *) op->larg; - - if (op->op == lop->op && op->all == lop->all) - need_paren = false; - else - need_paren = true; - } - else - need_paren = false; - - if (need_paren) - { - appendStringInfoChar(buf, '('); - subindent = PRETTYINDENT_STD; - appendContextKeyword(context, "", subindent, 0, 0); - } - else - subindent = 0; - - get_setop_query(op->larg, query, context, resultDesc); - - if (need_paren) - appendContextKeyword(context, ") ", -subindent, 0, 0); - else if (PRETTY_INDENT(context)) - appendContextKeyword(context, "", -subindent, 0, 0); - else - appendStringInfoChar(buf, ' '); - - switch (op->op) - { - case SETOP_UNION: - appendStringInfoString(buf, "UNION "); - break; - case SETOP_INTERSECT: - appendStringInfoString(buf, "INTERSECT "); - break; - case SETOP_EXCEPT: - appendStringInfoString(buf, "EXCEPT "); - break; - default: - elog(ERROR, "unrecognized set op: %d", - (int) op->op); - } - if (op->all) - appendStringInfoString(buf, "ALL "); - - /* Always parenthesize if RHS is another setop */ - need_paren = IsA(op->rarg, SetOperationStmt); - - /* - * The indentation code here is deliberately a bit different from that - * for the lefthand input, because we want the line breaks in - * different places. - */ - if (need_paren) - { - appendStringInfoChar(buf, '('); - subindent = PRETTYINDENT_STD; - } - else - subindent = 0; - appendContextKeyword(context, "", subindent, 0, 0); - - get_setop_query(op->rarg, query, context, resultDesc); - - if (PRETTY_INDENT(context)) - context->indentLevel -= subindent; - if (need_paren) - appendContextKeyword(context, ")", 0, 0, 0); - } - else - { - elog(ERROR, "unrecognized node type: %d", - (int) nodeTag(setOp)); - } -} - -/* - * Display a sort/group clause. - * - * Also returns the expression tree, so caller need not find it again. - */ -static Node * -get_rule_sortgroupclause(Index ref, List *tlist, bool force_colno, - deparse_context *context) -{ - StringInfo buf = context->buf; - TargetEntry *tle; - Node *expr; - - tle = get_sortgroupref_tle(ref, tlist); - expr = (Node *) tle->expr; - - /* - * Use column-number form if requested by caller. Otherwise, if - * expression is a constant, force it to be dumped with an explicit cast - * as decoration --- this is because a simple integer constant is - * ambiguous (and will be misinterpreted by findTargetlistEntry()) if we - * dump it without any decoration. If it's anything more complex than a - * simple Var, then force extra parens around it, to ensure it can't be - * misinterpreted as a cube() or rollup() construct. - */ - if (force_colno) - { - Assert(!tle->resjunk); - appendStringInfo(buf, "%d", tle->resno); - } - else if (expr && IsA(expr, Const)) - get_const_expr((Const *) expr, context, 1); - else if (!expr || IsA(expr, Var)) - get_rule_expr(expr, context, true); - else - { - /* - * We must force parens for function-like expressions even if - * PRETTY_PAREN is off, since those are the ones in danger of - * misparsing. For other expressions we need to force them only if - * PRETTY_PAREN is on, since otherwise the expression will output them - * itself. (We can't skip the parens.) - */ - bool need_paren = (PRETTY_PAREN(context) - || IsA(expr, FuncExpr) - ||IsA(expr, Aggref) - ||IsA(expr, WindowFunc)); - - if (need_paren) - appendStringInfoChar(context->buf, '('); - get_rule_expr(expr, context, true); - if (need_paren) - appendStringInfoChar(context->buf, ')'); - } - - return expr; -} - -/* - * Display a GroupingSet - */ -static void -get_rule_groupingset(GroupingSet *gset, List *targetlist, - bool omit_parens, deparse_context *context) -{ - ListCell *l; - StringInfo buf = context->buf; - bool omit_child_parens = true; - char *sep = ""; - - switch (gset->kind) - { - case GROUPING_SET_EMPTY: - appendStringInfoString(buf, "()"); - return; - - case GROUPING_SET_SIMPLE: - { - if (!omit_parens || list_length(gset->content) != 1) - appendStringInfoChar(buf, '('); - - foreach(l, gset->content) - { - Index ref = lfirst_int(l); - - appendStringInfoString(buf, sep); - get_rule_sortgroupclause(ref, targetlist, - false, context); - sep = ", "; - } - - if (!omit_parens || list_length(gset->content) != 1) - appendStringInfoChar(buf, ')'); - } - return; - - case GROUPING_SET_ROLLUP: - appendStringInfoString(buf, "ROLLUP("); - break; - case GROUPING_SET_CUBE: - appendStringInfoString(buf, "CUBE("); - break; - case GROUPING_SET_SETS: - appendStringInfoString(buf, "GROUPING SETS ("); - omit_child_parens = false; - break; - } - - foreach(l, gset->content) - { - appendStringInfoString(buf, sep); - get_rule_groupingset(lfirst(l), targetlist, omit_child_parens, context); - sep = ", "; - } - - appendStringInfoChar(buf, ')'); -} - -/* - * Display an ORDER BY list. - */ -static void -get_rule_orderby(List *orderList, List *targetList, - bool force_colno, deparse_context *context) -{ - StringInfo buf = context->buf; - const char *sep; - ListCell *l; - - sep = ""; - foreach(l, orderList) - { - SortGroupClause *srt = (SortGroupClause *) lfirst(l); - Node *sortexpr; - Oid sortcoltype; - TypeCacheEntry *typentry; - - appendStringInfoString(buf, sep); - sortexpr = get_rule_sortgroupclause(srt->tleSortGroupRef, targetList, - force_colno, context); - sortcoltype = exprType(sortexpr); - /* See whether operator is default < or > for datatype */ - typentry = lookup_type_cache(sortcoltype, - TYPECACHE_LT_OPR | TYPECACHE_GT_OPR); - if (srt->sortop == typentry->lt_opr) - { - /* ASC is default, so emit nothing for it */ - if (srt->nulls_first) - appendStringInfoString(buf, " NULLS FIRST"); - } - else if (srt->sortop == typentry->gt_opr) - { - appendStringInfoString(buf, " DESC"); - /* DESC defaults to NULLS FIRST */ - if (!srt->nulls_first) - appendStringInfoString(buf, " NULLS LAST"); - } - else - { - appendStringInfo(buf, " USING %s", - generate_operator_name(srt->sortop, - sortcoltype, - sortcoltype)); - /* be specific to eliminate ambiguity */ - if (srt->nulls_first) - appendStringInfoString(buf, " NULLS FIRST"); - else - appendStringInfoString(buf, " NULLS LAST"); - } - sep = ", "; - } -} - -/* - * Display a WINDOW clause. - * - * Note that the windowClause list might contain only anonymous window - * specifications, in which case we should print nothing here. - */ -static void -get_rule_windowclause(Query *query, deparse_context *context) -{ - StringInfo buf = context->buf; - const char *sep; - ListCell *l; - - sep = NULL; - foreach(l, query->windowClause) - { - WindowClause *wc = (WindowClause *) lfirst(l); - - if (wc->name == NULL) - continue; /* ignore anonymous windows */ - - if (sep == NULL) - appendContextKeyword(context, " WINDOW ", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); - else - appendStringInfoString(buf, sep); - - appendStringInfo(buf, "%s AS ", quote_identifier(wc->name)); - - get_rule_windowspec(wc, query->targetList, context); - - sep = ", "; - } -} - -/* - * Display a window definition - */ -static void -get_rule_windowspec(WindowClause *wc, List *targetList, - deparse_context *context) -{ - StringInfo buf = context->buf; - bool needspace = false; - const char *sep; - ListCell *l; - - appendStringInfoChar(buf, '('); - if (wc->refname) - { - appendStringInfoString(buf, quote_identifier(wc->refname)); - needspace = true; - } - /* partition clauses are always inherited, so only print if no refname */ - if (wc->partitionClause && !wc->refname) - { - if (needspace) - appendStringInfoChar(buf, ' '); - appendStringInfoString(buf, "PARTITION BY "); - sep = ""; - foreach(l, wc->partitionClause) - { - SortGroupClause *grp = (SortGroupClause *) lfirst(l); - - appendStringInfoString(buf, sep); - get_rule_sortgroupclause(grp->tleSortGroupRef, targetList, - false, context); - sep = ", "; - } - needspace = true; - } - /* print ordering clause only if not inherited */ - if (wc->orderClause && !wc->copiedOrder) - { - if (needspace) - appendStringInfoChar(buf, ' '); - appendStringInfoString(buf, "ORDER BY "); - get_rule_orderby(wc->orderClause, targetList, false, context); - needspace = true; - } - /* framing clause is never inherited, so print unless it's default */ - if (wc->frameOptions & FRAMEOPTION_NONDEFAULT) - { - if (needspace) - appendStringInfoChar(buf, ' '); - if (wc->frameOptions & FRAMEOPTION_RANGE) - appendStringInfoString(buf, "RANGE "); - else if (wc->frameOptions & FRAMEOPTION_ROWS) - appendStringInfoString(buf, "ROWS "); - else if (wc->frameOptions & FRAMEOPTION_GROUPS) - appendStringInfoString(buf, "GROUPS "); - else - Assert(false); - if (wc->frameOptions & FRAMEOPTION_BETWEEN) - appendStringInfoString(buf, "BETWEEN "); - if (wc->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) - appendStringInfoString(buf, "UNBOUNDED PRECEDING "); - else if (wc->frameOptions & FRAMEOPTION_START_CURRENT_ROW) - appendStringInfoString(buf, "CURRENT ROW "); - else if (wc->frameOptions & FRAMEOPTION_START_OFFSET) - { - get_rule_expr(wc->startOffset, context, false); - if (wc->frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING) - appendStringInfoString(buf, " PRECEDING "); - else if (wc->frameOptions & FRAMEOPTION_START_OFFSET_FOLLOWING) - appendStringInfoString(buf, " FOLLOWING "); - else - Assert(false); - } - else - Assert(false); - if (wc->frameOptions & FRAMEOPTION_BETWEEN) - { - appendStringInfoString(buf, "AND "); - if (wc->frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING) - appendStringInfoString(buf, "UNBOUNDED FOLLOWING "); - else if (wc->frameOptions & FRAMEOPTION_END_CURRENT_ROW) - appendStringInfoString(buf, "CURRENT ROW "); - else if (wc->frameOptions & FRAMEOPTION_END_OFFSET) - { - get_rule_expr(wc->endOffset, context, false); - if (wc->frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING) - appendStringInfoString(buf, " PRECEDING "); - else if (wc->frameOptions & FRAMEOPTION_END_OFFSET_FOLLOWING) - appendStringInfoString(buf, " FOLLOWING "); - else - Assert(false); - } - else - Assert(false); - } - if (wc->frameOptions & FRAMEOPTION_EXCLUDE_CURRENT_ROW) - appendStringInfoString(buf, "EXCLUDE CURRENT ROW "); - else if (wc->frameOptions & FRAMEOPTION_EXCLUDE_GROUP) - appendStringInfoString(buf, "EXCLUDE GROUP "); - else if (wc->frameOptions & FRAMEOPTION_EXCLUDE_TIES) - appendStringInfoString(buf, "EXCLUDE TIES "); - /* we will now have a trailing space; remove it */ - buf->len--; - } - appendStringInfoChar(buf, ')'); -} - -/* ---------- - * get_insert_query_def - Parse back an INSERT parsetree - * ---------- - */ -static void -get_insert_query_def(Query *query, deparse_context *context) -{ - StringInfo buf = context->buf; - RangeTblEntry *select_rte = NULL; - RangeTblEntry *values_rte = NULL; - RangeTblEntry *rte; - char *sep; - ListCell *l; - List *strippedexprs; - - /* Insert the WITH clause if given */ - get_with_clause(query, context); - - /* - * If it's an INSERT ... SELECT or multi-row VALUES, there will be a - * single RTE for the SELECT or VALUES. Plain VALUES has neither. - */ - foreach(l, query->rtable) - { - rte = (RangeTblEntry *) lfirst(l); - - if (rte->rtekind == RTE_SUBQUERY) - { - if (select_rte) - elog(ERROR, "too many subquery RTEs in INSERT"); - select_rte = rte; - } - - if (rte->rtekind == RTE_VALUES) - { - if (values_rte) - elog(ERROR, "too many values RTEs in INSERT"); - values_rte = rte; - } - } - if (select_rte && values_rte) - elog(ERROR, "both subquery and values RTEs in INSERT"); - - /* - * Start the query with INSERT INTO relname - */ - rte = rt_fetch(query->resultRelation, query->rtable); - Assert(rte->rtekind == RTE_RELATION); - - if (PRETTY_INDENT(context)) - { - context->indentLevel += PRETTYINDENT_STD; - appendStringInfoChar(buf, ' '); - } - appendStringInfo(buf, "INSERT INTO %s ", - generate_relation_or_shard_name(rte->relid, - context->distrelid, - context->shardid, NIL)); - /* INSERT requires AS keyword for target alias */ - if (rte->alias != NULL) - appendStringInfo(buf, "AS %s ", - quote_identifier(get_rtable_name(query->resultRelation, context))); - - /* - * Add the insert-column-names list. Any indirection decoration needed on - * the column names can be inferred from the top targetlist. - */ - strippedexprs = NIL; - sep = ""; - if (query->targetList) - appendStringInfoChar(buf, '('); - foreach(l, query->targetList) - { - TargetEntry *tle = (TargetEntry *) lfirst(l); - - if (tle->resjunk) - continue; /* ignore junk entries */ - - appendStringInfoString(buf, sep); - sep = ", "; - - /* - * Put out name of target column; look in the catalogs, not at - * tle->resname, since resname will fail to track RENAME. - */ - appendStringInfoString(buf, - quote_identifier(get_attname(rte->relid, - tle->resno, - false))); - - /* - * Print any indirection needed (subfields or subscripts), and strip - * off the top-level nodes representing the indirection assignments. - * Add the stripped expressions to strippedexprs. (If it's a - * single-VALUES statement, the stripped expressions are the VALUES to - * print below. Otherwise they're just Vars and not really - * interesting.) - */ - strippedexprs = lappend(strippedexprs, - processIndirection((Node *) tle->expr, - context)); - } - if (query->targetList) - appendStringInfoString(buf, ") "); - - if (query->override) - { - if (query->override == OVERRIDING_SYSTEM_VALUE) - appendStringInfoString(buf, "OVERRIDING SYSTEM VALUE "); - else if (query->override == OVERRIDING_USER_VALUE) - appendStringInfoString(buf, "OVERRIDING USER VALUE "); - } - - if (select_rte) - { - /* Add the SELECT */ - get_query_def(select_rte->subquery, buf, NIL, NULL, - context->prettyFlags, context->wrapColumn, - context->indentLevel); - } - else if (values_rte) - { - /* Add the multi-VALUES expression lists */ - get_values_def(values_rte->values_lists, context); - } - else if (strippedexprs) - { - /* Add the single-VALUES expression list */ - appendContextKeyword(context, "VALUES (", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 2); - get_rule_expr((Node *) strippedexprs, context, false); - appendStringInfoChar(buf, ')'); - } - else - { - /* No expressions, so it must be DEFAULT VALUES */ - appendStringInfoString(buf, "DEFAULT VALUES"); - } - - /* Add ON CONFLICT if present */ - if (query->onConflict) - { - OnConflictExpr *confl = query->onConflict; - - appendStringInfoString(buf, " ON CONFLICT"); - - if (confl->arbiterElems) - { - /* Add the single-VALUES expression list */ - appendStringInfoChar(buf, '('); - get_rule_expr((Node *) confl->arbiterElems, context, false); - appendStringInfoChar(buf, ')'); - - /* Add a WHERE clause (for partial indexes) if given */ - if (confl->arbiterWhere != NULL) - { - bool save_varprefix; - - /* - * Force non-prefixing of Vars, since parser assumes that they - * belong to target relation. WHERE clause does not use - * InferenceElem, so this is separately required. - */ - save_varprefix = context->varprefix; - context->varprefix = false; - - appendContextKeyword(context, " WHERE ", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); - get_rule_expr(confl->arbiterWhere, context, false); - - context->varprefix = save_varprefix; - } - } - else if (OidIsValid(confl->constraint)) - { - char *constraint = get_constraint_name(confl->constraint); - int64 shardId = context->shardid; - - if (shardId > 0) - { - AppendShardIdToName(&constraint, shardId); - } - - if (!constraint) - elog(ERROR, "cache lookup failed for constraint %u", - confl->constraint); - appendStringInfo(buf, " ON CONSTRAINT %s", - quote_identifier(constraint)); - } - - if (confl->action == ONCONFLICT_NOTHING) - { - appendStringInfoString(buf, " DO NOTHING"); - } - else - { - appendStringInfoString(buf, " DO UPDATE SET "); - /* Deparse targetlist */ - get_update_query_targetlist_def(query, confl->onConflictSet, - context, rte); - - /* Add a WHERE clause if given */ - if (confl->onConflictWhere != NULL) - { - appendContextKeyword(context, " WHERE ", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); - get_rule_expr(confl->onConflictWhere, context, false); - } - } - } - - /* Add RETURNING if present */ - if (query->returningList) - { - appendContextKeyword(context, " RETURNING", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); - get_target_list(query->returningList, context, NULL); - } -} - - -/* ---------- - * get_update_query_def - Parse back an UPDATE parsetree - * ---------- - */ -static void -get_update_query_def(Query *query, deparse_context *context) -{ - StringInfo buf = context->buf; - RangeTblEntry *rte; - - /* Insert the WITH clause if given */ - get_with_clause(query, context); - - /* - * Start the query with UPDATE relname SET - */ - rte = rt_fetch(query->resultRelation, query->rtable); - - if (PRETTY_INDENT(context)) - { - appendStringInfoChar(buf, ' '); - context->indentLevel += PRETTYINDENT_STD; - } - - /* if it's a shard, do differently */ - if (GetRangeTblKind(rte) == CITUS_RTE_SHARD) - { - char *fragmentSchemaName = NULL; - char *fragmentTableName = NULL; - - ExtractRangeTblExtraData(rte, NULL, &fragmentSchemaName, &fragmentTableName, NULL); - - /* use schema and table name from the remote alias */ - appendStringInfo(buf, "UPDATE %s%s", - only_marker(rte), - generate_fragment_name(fragmentSchemaName, fragmentTableName)); - - if(rte->eref != NULL) - appendStringInfo(buf, " %s", - quote_identifier(get_rtable_name(query->resultRelation, context))); - } - else - { - appendStringInfo(buf, "UPDATE %s%s", - only_marker(rte), - generate_relation_or_shard_name(rte->relid, - context->distrelid, - context->shardid, NIL)); - - if (rte->alias != NULL) - appendStringInfo(buf, " %s", - quote_identifier(get_rtable_name(query->resultRelation, context))); - } - - appendStringInfoString(buf, " SET "); - - /* Deparse targetlist */ - get_update_query_targetlist_def(query, query->targetList, context, rte); - - /* Add the FROM clause if needed */ - get_from_clause(query, " FROM ", context); - - /* Add a WHERE clause if given */ - if (query->jointree->quals != NULL) - { - appendContextKeyword(context, " WHERE ", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); - get_rule_expr(query->jointree->quals, context, false); - } - - /* Add RETURNING if present */ - if (query->returningList) - { - appendContextKeyword(context, " RETURNING", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); - get_target_list(query->returningList, context, NULL); - } -} - - -/* ---------- - * get_update_query_targetlist_def - Parse back an UPDATE targetlist - * ---------- - */ -static void -get_update_query_targetlist_def(Query *query, List *targetList, - deparse_context *context, RangeTblEntry *rte) -{ - StringInfo buf = context->buf; - ListCell *l; - ListCell *next_ma_cell; - int remaining_ma_columns; - const char *sep; - SubLink *cur_ma_sublink; - List *ma_sublinks; - - /* - * Prepare to deal with MULTIEXPR assignments: collect the source SubLinks - * into a list. We expect them to appear, in ID order, in resjunk tlist - * entries. - */ - ma_sublinks = NIL; - if (query->hasSubLinks) /* else there can't be any */ - { - foreach(l, targetList) - { - TargetEntry *tle = (TargetEntry *) lfirst(l); - - if (tle->resjunk && IsA(tle->expr, SubLink)) - { - SubLink *sl = (SubLink *) tle->expr; - - if (sl->subLinkType == MULTIEXPR_SUBLINK) - { - ma_sublinks = lappend(ma_sublinks, sl); - Assert(sl->subLinkId == list_length(ma_sublinks)); - } - } - } - } - next_ma_cell = list_head(ma_sublinks); - cur_ma_sublink = NULL; - remaining_ma_columns = 0; - - /* Add the comma separated list of 'attname = value' */ - sep = ""; - foreach(l, targetList) - { - TargetEntry *tle = (TargetEntry *) lfirst(l); - Node *expr; - - if (tle->resjunk) - continue; /* ignore junk entries */ - - /* Emit separator (OK whether we're in multiassignment or not) */ - appendStringInfoString(buf, sep); - sep = ", "; - - /* - * Check to see if we're starting a multiassignment group: if so, - * output a left paren. - */ - if (next_ma_cell != NULL && cur_ma_sublink == NULL) - { - /* - * We must dig down into the expr to see if it's a PARAM_MULTIEXPR - * Param. That could be buried under FieldStores and - * SubscriptingRefs and CoerceToDomains (cf processIndirection()), - * and underneath those there could be an implicit type coercion. - * Because we would ignore implicit type coercions anyway, we - * don't need to be as careful as processIndirection() is about - * descending past implicit CoerceToDomains. - */ - expr = (Node *) tle->expr; - while (expr) - { - if (IsA(expr, FieldStore)) - { - FieldStore *fstore = (FieldStore *) expr; - - expr = (Node *) linitial(fstore->newvals); - } - else if (IsA(expr, SubscriptingRef)) - { - SubscriptingRef *sbsref = (SubscriptingRef *) expr; - - if (sbsref->refassgnexpr == NULL) - break; - expr = (Node *) sbsref->refassgnexpr; - } - else if (IsA(expr, CoerceToDomain)) - { - CoerceToDomain *cdomain = (CoerceToDomain *) expr; - - if (cdomain->coercionformat != COERCE_IMPLICIT_CAST) - break; - expr = (Node *) cdomain->arg; - } - else - break; - } - expr = strip_implicit_coercions(expr); - - if (expr && IsA(expr, Param) && - ((Param *) expr)->paramkind == PARAM_MULTIEXPR) - { - cur_ma_sublink = (SubLink *) lfirst(next_ma_cell); - next_ma_cell = lnext(ma_sublinks, next_ma_cell); - remaining_ma_columns = count_nonjunk_tlist_entries( - ((Query *) cur_ma_sublink->subselect)->targetList); - Assert(((Param *) expr)->paramid == - ((cur_ma_sublink->subLinkId << 16) | 1)); - appendStringInfoChar(buf, '('); - } - } - - /* - * Put out name of target column; look in the catalogs, not at - * tle->resname, since resname will fail to track RENAME. - */ - appendStringInfoString(buf, - quote_identifier(get_attname(rte->relid, - tle->resno, - false))); - - /* - * Print any indirection needed (subfields or subscripts), and strip - * off the top-level nodes representing the indirection assignments. - */ - expr = processIndirection((Node *) tle->expr, context); - - /* - * If we're in a multiassignment, skip printing anything more, unless - * this is the last column; in which case, what we print should be the - * sublink, not the Param. - */ - if (cur_ma_sublink != NULL) - { - if (--remaining_ma_columns > 0) - continue; /* not the last column of multiassignment */ - appendStringInfoChar(buf, ')'); - expr = (Node *) cur_ma_sublink; - cur_ma_sublink = NULL; - } - - appendStringInfoString(buf, " = "); - - get_rule_expr(expr, context, false); - } -} - - -/* ---------- - * get_delete_query_def - Parse back a DELETE parsetree - * ---------- - */ -static void -get_delete_query_def(Query *query, deparse_context *context) -{ - StringInfo buf = context->buf; - RangeTblEntry *rte; - - /* Insert the WITH clause if given */ - get_with_clause(query, context); - - /* - * Start the query with DELETE FROM relname - */ - rte = rt_fetch(query->resultRelation, query->rtable); - - if (PRETTY_INDENT(context)) - { - appendStringInfoChar(buf, ' '); - context->indentLevel += PRETTYINDENT_STD; - } - - /* if it's a shard, do differently */ - if (GetRangeTblKind(rte) == CITUS_RTE_SHARD) - { - char *fragmentSchemaName = NULL; - char *fragmentTableName = NULL; - - ExtractRangeTblExtraData(rte, NULL, &fragmentSchemaName, &fragmentTableName, NULL); - - /* use schema and table name from the remote alias */ - appendStringInfo(buf, "DELETE FROM %s%s", - only_marker(rte), - generate_fragment_name(fragmentSchemaName, fragmentTableName)); - - if(rte->eref != NULL) - appendStringInfo(buf, " %s", - quote_identifier(get_rtable_name(query->resultRelation, context))); - } - else - { - appendStringInfo(buf, "DELETE FROM %s%s", - only_marker(rte), - generate_relation_or_shard_name(rte->relid, - context->distrelid, - context->shardid, NIL)); - - if (rte->alias != NULL) - appendStringInfo(buf, " %s", - quote_identifier(get_rtable_name(query->resultRelation, context))); - } - - /* Add the USING clause if given */ - get_from_clause(query, " USING ", context); - - /* Add a WHERE clause if given */ - if (query->jointree->quals != NULL) - { - appendContextKeyword(context, " WHERE ", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); - get_rule_expr(query->jointree->quals, context, false); - } - - /* Add RETURNING if present */ - if (query->returningList) - { - appendContextKeyword(context, " RETURNING", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); - get_target_list(query->returningList, context, NULL); - } -} - - -/* ---------- - * get_utility_query_def - Parse back a UTILITY parsetree - * ---------- - */ -static void -get_utility_query_def(Query *query, deparse_context *context) -{ - StringInfo buf = context->buf; - - if (query->utilityStmt && IsA(query->utilityStmt, NotifyStmt)) - { - NotifyStmt *stmt = (NotifyStmt *) query->utilityStmt; - - appendContextKeyword(context, "", - 0, PRETTYINDENT_STD, 1); - appendStringInfo(buf, "NOTIFY %s", - quote_identifier(stmt->conditionname)); - if (stmt->payload) - { - appendStringInfoString(buf, ", "); - simple_quote_literal(buf, stmt->payload); - } - } - else if (query->utilityStmt && IsA(query->utilityStmt, TruncateStmt)) - { - TruncateStmt *stmt = (TruncateStmt *) query->utilityStmt; - List *relationList = stmt->relations; - ListCell *relationCell = NULL; - - appendContextKeyword(context, "", - 0, PRETTYINDENT_STD, 1); - - appendStringInfo(buf, "TRUNCATE TABLE"); - - foreach(relationCell, relationList) - { - RangeVar *relationVar = (RangeVar *) lfirst(relationCell); - Oid relationId = RangeVarGetRelid(relationVar, NoLock, false); - char *relationName = generate_relation_or_shard_name(relationId, - context->distrelid, - context->shardid, NIL); - appendStringInfo(buf, " %s", relationName); - - if (lnext(relationList, relationCell) != NULL) - { - appendStringInfo(buf, ","); - } - } - - if (stmt->restart_seqs) - { - appendStringInfo(buf, " RESTART IDENTITY"); - } - - if (stmt->behavior == DROP_CASCADE) - { - appendStringInfo(buf, " CASCADE"); - } - } - else - { - /* Currently only NOTIFY utility commands can appear in rules */ - elog(ERROR, "unexpected utility statement type"); - } -} - -/* - * Display a Var appropriately. - * - * In some cases (currently only when recursing into an unnamed join) - * the Var's varlevelsup has to be interpreted with respect to a context - * above the current one; levelsup indicates the offset. - * - * If istoplevel is true, the Var is at the top level of a SELECT's - * targetlist, which means we need special treatment of whole-row Vars. - * Instead of the normal "tab.*", we'll print "tab.*::typename", which is a - * dirty hack to prevent "tab.*" from being expanded into multiple columns. - * (The parser will strip the useless coercion, so no inefficiency is added in - * dump and reload.) We used to print just "tab" in such cases, but that is - * ambiguous and will yield the wrong result if "tab" is also a plain column - * name in the query. - * - * Returns the attname of the Var, or NULL if the Var has no attname (because - * it is a whole-row Var or a subplan output reference). - */ -static char * -get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context) -{ - StringInfo buf = context->buf; - RangeTblEntry *rte; - AttrNumber attnum; - Index varno; - AttrNumber varattno; - int netlevelsup; - deparse_namespace *dpns; - deparse_columns *colinfo; - char *refname; - char *attname; - - /* Find appropriate nesting depth */ - netlevelsup = var->varlevelsup + levelsup; - if (netlevelsup >= list_length(context->namespaces)) - elog(ERROR, "bogus varlevelsup: %d offset %d", - var->varlevelsup, levelsup); - dpns = (deparse_namespace *) list_nth(context->namespaces, - netlevelsup); - - varno = var->varno; - varattno = var->varattno; - - - if (var->varnosyn > 0 && var->varnosyn <= list_length(dpns->rtable) && dpns->plan == NULL) { - rte = rt_fetch(var->varnosyn, dpns->rtable); - - /* - * if the rte var->varnosyn points to is not a regular table and it is a join - * then the correct relname will be found with var->varnosyn and var->varattnosyn - */ - if (rte->rtekind == RTE_JOIN && rte->relid == 0 && var->varnosyn != var->varno) { - varno = var->varnosyn; - varattno = var->varattnosyn; - } - } - - /* - * Try to find the relevant RTE in this rtable. In a plan tree, it's - * likely that varno is OUTER_VAR or INNER_VAR, in which case we must dig - * down into the subplans, or INDEX_VAR, which is resolved similarly. Also - * find the aliases previously assigned for this RTE. - */ - if (varno >= 1 && varno <= list_length(dpns->rtable)) - { - - /* - * We might have been asked to map child Vars to some parent relation. - */ - if (context->appendparents && dpns->appendrels) - { - - Index pvarno = varno; - AttrNumber pvarattno = varattno; - AppendRelInfo *appinfo = dpns->appendrels[pvarno]; - bool found = false; - - /* Only map up to inheritance parents, not UNION ALL appendrels */ - while (appinfo && - rt_fetch(appinfo->parent_relid, - dpns->rtable)->rtekind == RTE_RELATION) - { - found = false; - if (pvarattno > 0) /* system columns stay as-is */ - { - if (pvarattno > appinfo->num_child_cols) - break; /* safety check */ - pvarattno = appinfo->parent_colnos[pvarattno - 1]; - if (pvarattno == 0) - break; /* Var is local to child */ - } - - pvarno = appinfo->parent_relid; - found = true; - - /* If the parent is itself a child, continue up. */ - Assert(pvarno > 0 && pvarno <= list_length(dpns->rtable)); - appinfo = dpns->appendrels[pvarno]; - } - - /* - * If we found an ancestral rel, and that rel is included in - * appendparents, print that column not the original one. - */ - if (found && bms_is_member(pvarno, context->appendparents)) - { - varno = pvarno; - varattno = pvarattno; - } - } - - rte = rt_fetch(varno, dpns->rtable); - refname = (char *) list_nth(dpns->rtable_names, varno - 1); - colinfo = deparse_columns_fetch(varno, dpns); - attnum = varattno; - } - else - { - resolve_special_varno((Node *) var, context, get_special_variable, - NULL); - return NULL; - } - - /* - * The planner will sometimes emit Vars referencing resjunk elements of a - * subquery's target list (this is currently only possible if it chooses - * to generate a "physical tlist" for a SubqueryScan or CteScan node). - * Although we prefer to print subquery-referencing Vars using the - * subquery's alias, that's not possible for resjunk items since they have - * no alias. So in that case, drill down to the subplan and print the - * contents of the referenced tlist item. This works because in a plan - * tree, such Vars can only occur in a SubqueryScan or CteScan node, and - * we'll have set dpns->inner_plan to reference the child plan node. - */ - if ((rte->rtekind == RTE_SUBQUERY || rte->rtekind == RTE_CTE) && - attnum > list_length(rte->eref->colnames) && - dpns->inner_plan) - { - TargetEntry *tle; - deparse_namespace save_dpns; - - tle = get_tle_by_resno(dpns->inner_tlist, attnum); - if (!tle) - elog(ERROR, "invalid attnum %d for relation \"%s\"", - attnum, rte->eref->aliasname); - - Assert(netlevelsup == 0); - push_child_plan(dpns, dpns->inner_plan, &save_dpns); - - /* - * Force parentheses because our caller probably assumed a Var is a - * simple expression. - */ - if (!IsA(tle->expr, Var)) - appendStringInfoChar(buf, '('); - get_rule_expr((Node *) tle->expr, context, true); - if (!IsA(tle->expr, Var)) - appendStringInfoChar(buf, ')'); - - pop_child_plan(dpns, &save_dpns); - return NULL; - } - - /* - * If it's an unnamed join, look at the expansion of the alias variable. - * If it's a simple reference to one of the input vars, then recursively - * print the name of that var instead. When it's not a simple reference, - * we have to just print the unqualified join column name. (This can only - * happen with "dangerous" merged columns in a JOIN USING; we took pains - * previously to make the unqualified column name unique in such cases.) - * - * This wouldn't work in decompiling plan trees, because we don't store - * joinaliasvars lists after planning; but a plan tree should never - * contain a join alias variable. - */ - if (rte->rtekind == RTE_JOIN && rte->alias == NULL) - { - if (rte->joinaliasvars == NIL) - elog(ERROR, "cannot decompile join alias var in plan tree"); - if (attnum > 0) - { - Var *aliasvar; - - aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1); - /* we intentionally don't strip implicit coercions here */ - if (aliasvar && IsA(aliasvar, Var)) - { - return get_variable(aliasvar, var->varlevelsup + levelsup, - istoplevel, context); - } - } - - /* - * Unnamed join has no refname. (Note: since it's unnamed, there is - * no way the user could have referenced it to create a whole-row Var - * for it. So we don't have to cover that case below.) - */ - Assert(refname == NULL); - } - - if (attnum == InvalidAttrNumber) - attname = NULL; - else if (attnum > 0) - { - /* Get column name to use from the colinfo struct */ - if (attnum > colinfo->num_cols) - elog(ERROR, "invalid attnum %d for relation \"%s\"", - attnum, rte->eref->aliasname); - attname = colinfo->colnames[attnum - 1]; - if (attname == NULL) /* dropped column? */ - elog(ERROR, "invalid attnum %d for relation \"%s\"", - attnum, rte->eref->aliasname); - } - else if (GetRangeTblKind(rte) == CITUS_RTE_SHARD) - { - /* System column on a Citus shard */ - attname = get_attname(rte->relid, attnum, false); - } - else - { - /* System column - name is fixed, get it from the catalog */ - attname = get_rte_attribute_name(rte, attnum); - } - - if (refname && (context->varprefix || attname == NULL)) - { - appendStringInfoString(buf, quote_identifier(refname)); - appendStringInfoChar(buf, '.'); - } - if (attname) - appendStringInfoString(buf, quote_identifier(attname)); - else - { - appendStringInfoChar(buf, '*'); - - if (istoplevel) - { - if (GetRangeTblKind(rte) == CITUS_RTE_SHARD) - { - /* use rel.*::shard_name instead of rel.*::table_name */ - appendStringInfo(buf, "::%s", - generate_rte_shard_name(rte)); - } - else - { - appendStringInfo(buf, "::%s", - format_type_with_typemod(var->vartype, - var->vartypmod)); - } - } - } - - return attname; -} - -/* - * Deparse a Var which references OUTER_VAR, INNER_VAR, or INDEX_VAR. This - * routine is actually a callback for get_special_varno, which handles finding - * the correct TargetEntry. We get the expression contained in that - * TargetEntry and just need to deparse it, a job we can throw back on - * get_rule_expr. - */ -static void -get_special_variable(Node *node, deparse_context *context, void *callback_arg) -{ - StringInfo buf = context->buf; - - /* - * For a non-Var referent, force parentheses because our caller probably - * assumed a Var is a simple expression. - */ - if (!IsA(node, Var)) - appendStringInfoChar(buf, '('); - get_rule_expr(node, context, true); - if (!IsA(node, Var)) - appendStringInfoChar(buf, ')'); -} - -/* - * Chase through plan references to special varnos (OUTER_VAR, INNER_VAR, - * INDEX_VAR) until we find a real Var or some kind of non-Var node; then, - * invoke the callback provided. - */ -static void -resolve_special_varno(Node *node, deparse_context *context, rsv_callback callback, void *callback_arg) -{ - Var *var; - deparse_namespace *dpns; - - /* This function is recursive, so let's be paranoid. */ - check_stack_depth(); - - /* If it's not a Var, invoke the callback. */ - if (!IsA(node, Var)) - { - (*callback) (node, context, callback_arg); - return; - } - - /* Find appropriate nesting depth */ - var = (Var *) node; - dpns = (deparse_namespace *) list_nth(context->namespaces, - var->varlevelsup); - - /* - * It's a special RTE, so recurse. - */ - if (var->varno == OUTER_VAR && dpns->outer_tlist) - { - TargetEntry *tle; - deparse_namespace save_dpns; - Bitmapset *save_appendparents; - - tle = get_tle_by_resno(dpns->outer_tlist, var->varattno); - if (!tle) - elog(ERROR, "bogus varattno for OUTER_VAR var: %d", var->varattno); - - /* If we're descending to the first child of an Append or MergeAppend, - * update appendparents. This will affect deparsing of all Vars - * appearing within the eventually-resolved subexpression. - */ - save_appendparents = context->appendparents; - - if (IsA(dpns->plan, Append)) - context->appendparents = bms_union(context->appendparents, - ((Append *) dpns->plan)->apprelids); - else if (IsA(dpns->plan, MergeAppend)) - context->appendparents = bms_union(context->appendparents, - ((MergeAppend *) dpns->plan)->apprelids); - - push_child_plan(dpns, dpns->outer_plan, &save_dpns); - resolve_special_varno((Node *) tle->expr, context, - callback, callback_arg); - pop_child_plan(dpns, &save_dpns); - context->appendparents = save_appendparents; - return; - } - else if (var->varno == INNER_VAR && dpns->inner_tlist) - { - TargetEntry *tle; - deparse_namespace save_dpns; - - tle = get_tle_by_resno(dpns->inner_tlist, var->varattno); - if (!tle) - elog(ERROR, "bogus varattno for INNER_VAR var: %d", var->varattno); - - push_child_plan(dpns, dpns->inner_plan, &save_dpns); - resolve_special_varno((Node *) tle->expr, context, callback, callback_arg); - pop_child_plan(dpns, &save_dpns); - return; - } - else if (var->varno == INDEX_VAR && dpns->index_tlist) - { - TargetEntry *tle; - - tle = get_tle_by_resno(dpns->index_tlist, var->varattno); - if (!tle) - elog(ERROR, "bogus varattno for INDEX_VAR var: %d", var->varattno); - - resolve_special_varno((Node *) tle->expr, context, callback, callback_arg); - return; - } - else if (var->varno < 1 || var->varno > list_length(dpns->rtable)) - elog(ERROR, "bogus varno: %d", var->varno); - - /* Not special. Just invoke the callback. */ - (*callback) (node, context, callback_arg); -} - -/* - * Get the name of a field of an expression of composite type. The - * expression is usually a Var, but we handle other cases too. - * - * levelsup is an extra offset to interpret the Var's varlevelsup correctly. - * - * This is fairly straightforward when the expression has a named composite - * type; we need only look up the type in the catalogs. However, the type - * could also be RECORD. Since no actual table or view column is allowed to - * have type RECORD, a Var of type RECORD must refer to a JOIN or FUNCTION RTE - * or to a subquery output. We drill down to find the ultimate defining - * expression and attempt to infer the field name from it. We ereport if we - * can't determine the name. - * - * Similarly, a PARAM of type RECORD has to refer to some expression of - * a determinable composite type. - */ -static const char * -get_name_for_var_field(Var *var, int fieldno, - int levelsup, deparse_context *context) -{ - RangeTblEntry *rte; - AttrNumber attnum; - int netlevelsup; - deparse_namespace *dpns; - Index varno; - AttrNumber varattno; - TupleDesc tupleDesc; - Node *expr; - - /* - * If it's a RowExpr that was expanded from a whole-row Var, use the - * column names attached to it. - */ - if (IsA(var, RowExpr)) - { - RowExpr *r = (RowExpr *) var; - - if (fieldno > 0 && fieldno <= list_length(r->colnames)) - return strVal(list_nth(r->colnames, fieldno - 1)); - } - - /* - * If it's a Param of type RECORD, try to find what the Param refers to. - */ - if (IsA(var, Param)) - { - Param *param = (Param *) var; - ListCell *ancestor_cell; - - expr = find_param_referent(param, context, &dpns, &ancestor_cell); - if (expr) - { - /* Found a match, so recurse to decipher the field name */ - deparse_namespace save_dpns; - const char *result; - - push_ancestor_plan(dpns, ancestor_cell, &save_dpns); - result = get_name_for_var_field((Var *) expr, fieldno, - 0, context); - pop_ancestor_plan(dpns, &save_dpns); - return result; - } - } - - /* - * If it's a Var of type RECORD, we have to find what the Var refers to; - * if not, we can use get_expr_result_tupdesc(). - */ - if (!IsA(var, Var) || - var->vartype != RECORDOID) - { - tupleDesc = get_expr_result_tupdesc((Node *) var, false); - /* Got the tupdesc, so we can extract the field name */ - Assert(fieldno >= 1 && fieldno <= tupleDesc->natts); - return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname); - } - - /* Find appropriate nesting depth */ - netlevelsup = var->varlevelsup + levelsup; - if (netlevelsup >= list_length(context->namespaces)) - elog(ERROR, "bogus varlevelsup: %d offset %d", - var->varlevelsup, levelsup); - dpns = (deparse_namespace *) list_nth(context->namespaces, - netlevelsup); - - varno = var->varno; - varattno = var->varattno; - - if (var->varnosyn > 0 && var->varnosyn <= list_length(dpns->rtable) && dpns->plan == NULL) { - rte = rt_fetch(var->varnosyn, dpns->rtable); - - /* - * if the rte var->varnosyn points to is not a regular table and it is a join - * then the correct relname will be found with var->varnosyn and var->varattnosyn - */ - if (rte->rtekind == RTE_JOIN && rte->relid == 0 && var->varnosyn != var->varno) { - varno = var->varnosyn; - varattno = var->varattnosyn; - } - } - - /* - * Try to find the relevant RTE in this rtable. In a plan tree, it's - * likely that varno is OUTER_VAR or INNER_VAR, in which case we must dig - * down into the subplans, or INDEX_VAR, which is resolved similarly. - */ - if (varno >= 1 && varno <= list_length(dpns->rtable)) - { - rte = rt_fetch(varno, dpns->rtable); - attnum = varattno; - } - else if (varno == OUTER_VAR && dpns->outer_tlist) - { - TargetEntry *tle; - deparse_namespace save_dpns; - const char *result; - - tle = get_tle_by_resno(dpns->outer_tlist, varattno); - if (!tle) - elog(ERROR, "bogus varattno for OUTER_VAR var: %d", varattno); - - Assert(netlevelsup == 0); - push_child_plan(dpns, dpns->outer_plan, &save_dpns); - - result = get_name_for_var_field((Var *) tle->expr, fieldno, - levelsup, context); - - pop_child_plan(dpns, &save_dpns); - return result; - } - else if (varno == INNER_VAR && dpns->inner_tlist) - { - TargetEntry *tle; - deparse_namespace save_dpns; - const char *result; - - tle = get_tle_by_resno(dpns->inner_tlist, varattno); - if (!tle) - elog(ERROR, "bogus varattno for INNER_VAR var: %d", varattno); - - Assert(netlevelsup == 0); - push_child_plan(dpns, dpns->inner_plan, &save_dpns); - - result = get_name_for_var_field((Var *) tle->expr, fieldno, - levelsup, context); - - pop_child_plan(dpns, &save_dpns); - return result; - } - else if (varno == INDEX_VAR && dpns->index_tlist) - { - TargetEntry *tle; - const char *result; - - tle = get_tle_by_resno(dpns->index_tlist, varattno); - if (!tle) - elog(ERROR, "bogus varattno for INDEX_VAR var: %d", varattno); - - Assert(netlevelsup == 0); - - result = get_name_for_var_field((Var *) tle->expr, fieldno, - levelsup, context); - - return result; - } - else - { - elog(ERROR, "bogus varno: %d", varno); - return NULL; /* keep compiler quiet */ - } - - if (attnum == InvalidAttrNumber) - { - /* Var is whole-row reference to RTE, so select the right field */ - return get_rte_attribute_name(rte, fieldno); - } - - /* - * This part has essentially the same logic as the parser's - * expandRecordVariable() function, but we are dealing with a different - * representation of the input context, and we only need one field name - * not a TupleDesc. Also, we need special cases for finding subquery and - * CTE subplans when deparsing Plan trees. - */ - expr = (Node *) var; /* default if we can't drill down */ - - switch (rte->rtekind) - { - case RTE_RELATION: - case RTE_VALUES: - case RTE_NAMEDTUPLESTORE: - case RTE_RESULT: - - /* - * This case should not occur: a column of a table or values list - * shouldn't have type RECORD. Fall through and fail (most - * likely) at the bottom. - */ - break; - case RTE_SUBQUERY: - /* Subselect-in-FROM: examine sub-select's output expr */ - { - if (rte->subquery) - { - TargetEntry *ste = get_tle_by_resno(rte->subquery->targetList, - attnum); - - if (ste == NULL || ste->resjunk) - elog(ERROR, "subquery %s does not have attribute %d", - rte->eref->aliasname, attnum); - expr = (Node *) ste->expr; - if (IsA(expr, Var)) - { - /* - * Recurse into the sub-select to see what its Var - * refers to. We have to build an additional level of - * namespace to keep in step with varlevelsup in the - * subselect. - */ - deparse_namespace mydpns; - const char *result; - - set_deparse_for_query(&mydpns, rte->subquery, - context->namespaces); - - context->namespaces = lcons(&mydpns, - context->namespaces); - - result = get_name_for_var_field((Var *) expr, fieldno, - 0, context); - - context->namespaces = - list_delete_first(context->namespaces); - - return result; - } - /* else fall through to inspect the expression */ - } - else - { - /* - * We're deparsing a Plan tree so we don't have complete - * RTE entries (in particular, rte->subquery is NULL). But - * the only place we'd see a Var directly referencing a - * SUBQUERY RTE is in a SubqueryScan plan node, and we can - * look into the child plan's tlist instead. - */ - TargetEntry *tle; - deparse_namespace save_dpns; - const char *result; - - if (!dpns->inner_plan) - elog(ERROR, "failed to find plan for subquery %s", - rte->eref->aliasname); - tle = get_tle_by_resno(dpns->inner_tlist, attnum); - if (!tle) - elog(ERROR, "bogus varattno for subquery var: %d", - attnum); - Assert(netlevelsup == 0); - push_child_plan(dpns, dpns->inner_plan, &save_dpns); - - result = get_name_for_var_field((Var *) tle->expr, fieldno, - levelsup, context); - - pop_child_plan(dpns, &save_dpns); - return result; - } - } - break; - case RTE_JOIN: - /* Join RTE --- recursively inspect the alias variable */ - if (rte->joinaliasvars == NIL) - elog(ERROR, "cannot decompile join alias var in plan tree"); - Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars)); - expr = (Node *) list_nth(rte->joinaliasvars, attnum - 1); - Assert(expr != NULL); - /* we intentionally don't strip implicit coercions here */ - if (IsA(expr, Var)) - return get_name_for_var_field((Var *) expr, fieldno, - var->varlevelsup + levelsup, - context); - /* else fall through to inspect the expression */ - break; - case RTE_FUNCTION: - case RTE_TABLEFUNC: - - /* - * We couldn't get here unless a function is declared with one of - * its result columns as RECORD, which is not allowed. - */ - break; - case RTE_CTE: - /* CTE reference: examine subquery's output expr */ - { - CommonTableExpr *cte = NULL; - Index ctelevelsup; - ListCell *lc; - - /* - * Try to find the referenced CTE using the namespace stack. - */ - ctelevelsup = rte->ctelevelsup + netlevelsup; - if (ctelevelsup >= list_length(context->namespaces)) - lc = NULL; - else - { - deparse_namespace *ctedpns; - - ctedpns = (deparse_namespace *) - list_nth(context->namespaces, ctelevelsup); - foreach(lc, ctedpns->ctes) - { - cte = (CommonTableExpr *) lfirst(lc); - if (strcmp(cte->ctename, rte->ctename) == 0) - break; - } - } - if (lc != NULL) - { - Query *ctequery = (Query *) cte->ctequery; - TargetEntry *ste = get_tle_by_resno(GetCTETargetList(cte), - attnum); - - if (ste == NULL || ste->resjunk) - elog(ERROR, "subquery %s does not have attribute %d", - rte->eref->aliasname, attnum); - expr = (Node *) ste->expr; - if (IsA(expr, Var)) - { - /* - * Recurse into the CTE to see what its Var refers to. - * We have to build an additional level of namespace - * to keep in step with varlevelsup in the CTE. - * Furthermore it could be an outer CTE, so we may - * have to delete some levels of namespace. - */ - List *save_nslist = context->namespaces; - List *new_nslist; - deparse_namespace mydpns; - const char *result; - - set_deparse_for_query(&mydpns, ctequery, - context->namespaces); - - new_nslist = list_copy_tail(context->namespaces, - ctelevelsup); - context->namespaces = lcons(&mydpns, new_nslist); - - result = get_name_for_var_field((Var *) expr, fieldno, - 0, context); - - context->namespaces = save_nslist; - - return result; - } - /* else fall through to inspect the expression */ - } - else - { - /* - * We're deparsing a Plan tree so we don't have a CTE - * list. But the only place we'd see a Var directly - * referencing a CTE RTE is in a CteScan plan node, and we - * can look into the subplan's tlist instead. - */ - TargetEntry *tle; - deparse_namespace save_dpns; - const char *result; - - if (!dpns->inner_plan) - elog(ERROR, "failed to find plan for CTE %s", - rte->eref->aliasname); - tle = get_tle_by_resno(dpns->inner_tlist, attnum); - if (!tle) - elog(ERROR, "bogus varattno for subquery var: %d", - attnum); - Assert(netlevelsup == 0); - push_child_plan(dpns, dpns->inner_plan, &save_dpns); - - result = get_name_for_var_field((Var *) tle->expr, fieldno, - levelsup, context); - - pop_child_plan(dpns, &save_dpns); - return result; - } - } - break; - } - - /* - * We now have an expression we can't expand any more, so see if - * get_expr_result_tupdesc() can do anything with it. - */ - tupleDesc = get_expr_result_tupdesc(expr, false); - /* Got the tupdesc, so we can extract the field name */ - Assert(fieldno >= 1 && fieldno <= tupleDesc->natts); - return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname); -} - -/* - * Try to find the referenced expression for a PARAM_EXEC Param that might - * reference a parameter supplied by an upper NestLoop or SubPlan plan node. - * - * If successful, return the expression and set *dpns_p and *ancestor_cell_p - * appropriately for calling push_ancestor_plan(). If no referent can be - * found, return NULL. - */ -static Node * -find_param_referent(Param *param, deparse_context *context, - deparse_namespace **dpns_p, ListCell **ancestor_cell_p) -{ - /* Initialize output parameters to prevent compiler warnings */ - *dpns_p = NULL; - *ancestor_cell_p = NULL; - - /* - * If it's a PARAM_EXEC parameter, look for a matching NestLoopParam or - * SubPlan argument. This will necessarily be in some ancestor of the - * current expression's Plan. - */ - if (param->paramkind == PARAM_EXEC) - { - deparse_namespace *dpns; - Plan *child_plan; - bool in_same_plan_level; - ListCell *lc; - - dpns = (deparse_namespace *) linitial(context->namespaces); - child_plan = dpns->plan; - in_same_plan_level = true; - - foreach(lc, dpns->ancestors) - { - Node *ancestor = (Node *) lfirst(lc); - ListCell *lc2; - - /* - * NestLoops transmit params to their inner child only; also, once - * we've crawled up out of a subplan, this couldn't possibly be - * the right match. - */ - if (IsA(ancestor, NestLoop) && - child_plan == innerPlan(ancestor) && - in_same_plan_level) - { - NestLoop *nl = (NestLoop *) ancestor; - - foreach(lc2, nl->nestParams) - { - NestLoopParam *nlp = (NestLoopParam *) lfirst(lc2); - - if (nlp->paramno == param->paramid) - { - /* Found a match, so return it */ - *dpns_p = dpns; - *ancestor_cell_p = lc; - return (Node *) nlp->paramval; - } - } - } - - /* - * Check to see if we're crawling up from a subplan. - */ - if(IsA(ancestor, SubPlan)) - { - SubPlan *subplan = (SubPlan *) ancestor; - ListCell *lc3; - ListCell *lc4; - - /* Matched subplan, so check its arguments */ - forboth(lc3, subplan->parParam, lc4, subplan->args) - { - int paramid = lfirst_int(lc3); - Node *arg = (Node *) lfirst(lc4); - - if (paramid == param->paramid) - { - /* - * Found a match, so return it. But, since Vars in - * the arg are to be evaluated in the surrounding - * context, we have to point to the next ancestor item - * that is *not* a SubPlan. - */ - ListCell *rest; - - for_each_cell(rest, dpns->ancestors, - lnext(dpns->ancestors, lc)) - { - Node *ancestor2 = (Node *) lfirst(rest); - - if (!IsA(ancestor2, SubPlan)) - { - *dpns_p = dpns; - *ancestor_cell_p = rest; - return arg; - } - } - elog(ERROR, "SubPlan cannot be outermost ancestor"); - } - } - - /* We have emerged from a subplan. */ - in_same_plan_level = false; - - /* SubPlan isn't a kind of Plan, so skip the rest */ - continue; - } - - /* - * Check to see if we're emerging from an initplan of the current - * ancestor plan. Initplans never have any parParams, so no need - * to search that list, but we need to know if we should reset - * in_same_plan_level. - */ - foreach(lc2, ((Plan *) ancestor)->initPlan) - { - SubPlan *subplan = castNode(SubPlan, lfirst(lc2)); - - if (child_plan != (Plan *) list_nth(dpns->subplans, - subplan->plan_id - 1)) - continue; - - /* No parameters to be had here. */ - Assert(subplan->parParam == NIL); - - /* We have emerged from an initplan. */ - in_same_plan_level = false; - break; - } - - /* No luck, crawl up to next ancestor */ - child_plan = (Plan *) ancestor; - } - } - - /* No referent found */ - return NULL; -} - -/* - * Display a Param appropriately. - */ -static void -get_parameter(Param *param, deparse_context *context) -{ - Node *expr; - deparse_namespace *dpns; - ListCell *ancestor_cell; - - /* - * If it's a PARAM_EXEC parameter, try to locate the expression from which - * the parameter was computed. Note that failing to find a referent isn't - * an error, since the Param might well be a subplan output rather than an - * input. - */ - expr = find_param_referent(param, context, &dpns, &ancestor_cell); - if (expr) - { - /* Found a match, so print it */ - deparse_namespace save_dpns; - bool save_varprefix; - bool need_paren; - - /* Switch attention to the ancestor plan node */ - push_ancestor_plan(dpns, ancestor_cell, &save_dpns); - - /* - * Force prefixing of Vars, since they won't belong to the relation - * being scanned in the original plan node. - */ - save_varprefix = context->varprefix; - context->varprefix = true; - - /* - * A Param's expansion is typically a Var, Aggref, or upper-level - * Param, which wouldn't need extra parentheses. Otherwise, insert - * parens to ensure the expression looks atomic. - */ - need_paren = !(IsA(expr, Var) || - IsA(expr, Aggref) || - IsA(expr, Param)); - if (need_paren) - appendStringInfoChar(context->buf, '('); - - get_rule_expr(expr, context, false); - - if (need_paren) - appendStringInfoChar(context->buf, ')'); - - context->varprefix = save_varprefix; - - pop_ancestor_plan(dpns, &save_dpns); - - return; - } - - /* - * Not PARAM_EXEC, or couldn't find referent: for base types just print $N. - * For composite types, add cast to the parameter to ease remote node detect - * the type. - */ - if (param->paramtype >= FirstNormalObjectId) - { - char *typeName = format_type_with_typemod(param->paramtype, param->paramtypmod); - - appendStringInfo(context->buf, "$%d::%s", param->paramid, typeName); - } - else - { - appendStringInfo(context->buf, "$%d", param->paramid); - } -} - -/* - * get_simple_binary_op_name - * - * helper function for isSimpleNode - * will return single char binary operator name, or NULL if it's not - */ -static const char * -get_simple_binary_op_name(OpExpr *expr) -{ - List *args = expr->args; - - if (list_length(args) == 2) - { - /* binary operator */ - Node *arg1 = (Node *) linitial(args); - Node *arg2 = (Node *) lsecond(args); - const char *op; - - op = generate_operator_name(expr->opno, exprType(arg1), exprType(arg2)); - if (strlen(op) == 1) - return op; - } - return NULL; -} - - -/* - * isSimpleNode - check if given node is simple (doesn't need parenthesizing) - * - * true : simple in the context of parent node's type - * false : not simple - */ -static bool -isSimpleNode(Node *node, Node *parentNode, int prettyFlags) -{ - if (!node) - return false; - - switch (nodeTag(node)) - { - case T_Var: - case T_Const: - case T_Param: - case T_CoerceToDomainValue: - case T_SetToDefault: - case T_CurrentOfExpr: - /* single words: always simple */ - return true; - - case T_SubscriptingRef: - case T_ArrayExpr: - case T_RowExpr: - case T_CoalesceExpr: - case T_MinMaxExpr: - case T_SQLValueFunction: - case T_XmlExpr: - case T_NextValueExpr: - case T_NullIfExpr: - case T_Aggref: - case T_WindowFunc: - case T_FuncExpr: - /* function-like: name(..) or name[..] */ - return true; - - /* CASE keywords act as parentheses */ - case T_CaseExpr: - return true; - - case T_FieldSelect: - - /* - * appears simple since . has top precedence, unless parent is - * T_FieldSelect itself! - */ - return (IsA(parentNode, FieldSelect) ? false : true); - - case T_FieldStore: - - /* - * treat like FieldSelect (probably doesn't matter) - */ - return (IsA(parentNode, FieldStore) ? false : true); - - case T_CoerceToDomain: - /* maybe simple, check args */ - return isSimpleNode((Node *) ((CoerceToDomain *) node)->arg, - node, prettyFlags); - case T_RelabelType: - return isSimpleNode((Node *) ((RelabelType *) node)->arg, - node, prettyFlags); - case T_CoerceViaIO: - return isSimpleNode((Node *) ((CoerceViaIO *) node)->arg, - node, prettyFlags); - case T_ArrayCoerceExpr: - return isSimpleNode((Node *) ((ArrayCoerceExpr *) node)->arg, - node, prettyFlags); - case T_ConvertRowtypeExpr: - return isSimpleNode((Node *) ((ConvertRowtypeExpr *) node)->arg, - node, prettyFlags); - - case T_OpExpr: - { - /* depends on parent node type; needs further checking */ - if (prettyFlags & PRETTYFLAG_PAREN && IsA(parentNode, OpExpr)) - { - const char *op; - const char *parentOp; - bool is_lopriop; - bool is_hipriop; - bool is_lopriparent; - bool is_hipriparent; - - op = get_simple_binary_op_name((OpExpr *) node); - if (!op) - return false; - - /* We know only the basic operators + - and * / % */ - is_lopriop = (strchr("+-", *op) != NULL); - is_hipriop = (strchr("*/%", *op) != NULL); - if (!(is_lopriop || is_hipriop)) - return false; - - parentOp = get_simple_binary_op_name((OpExpr *) parentNode); - if (!parentOp) - return false; - - is_lopriparent = (strchr("+-", *parentOp) != NULL); - is_hipriparent = (strchr("*/%", *parentOp) != NULL); - if (!(is_lopriparent || is_hipriparent)) - return false; - - if (is_hipriop && is_lopriparent) - return true; /* op binds tighter than parent */ - - if (is_lopriop && is_hipriparent) - return false; - - /* - * Operators are same priority --- can skip parens only if - * we have (a - b) - c, not a - (b - c). - */ - if (node == (Node *) linitial(((OpExpr *) parentNode)->args)) - return true; - - return false; - } - /* else do the same stuff as for T_SubLink et al. */ - } - /* FALLTHROUGH */ - - case T_SubLink: - case T_NullTest: - case T_BooleanTest: - case T_DistinctExpr: - switch (nodeTag(parentNode)) - { - case T_FuncExpr: - { - /* special handling for casts */ - CoercionForm type = ((FuncExpr *) parentNode)->funcformat; - - if (type == COERCE_EXPLICIT_CAST || - type == COERCE_IMPLICIT_CAST) - return false; - return true; /* own parentheses */ - } - case T_BoolExpr: /* lower precedence */ - case T_SubscriptingRef: /* other separators */ - case T_ArrayExpr: /* other separators */ - case T_RowExpr: /* other separators */ - case T_CoalesceExpr: /* own parentheses */ - case T_MinMaxExpr: /* own parentheses */ - case T_XmlExpr: /* own parentheses */ - case T_NullIfExpr: /* other separators */ - case T_Aggref: /* own parentheses */ - case T_WindowFunc: /* own parentheses */ - case T_CaseExpr: /* other separators */ - return true; - default: - return false; - } - - case T_BoolExpr: - switch (nodeTag(parentNode)) - { - case T_BoolExpr: - if (prettyFlags & PRETTYFLAG_PAREN) - { - BoolExprType type; - BoolExprType parentType; - - type = ((BoolExpr *) node)->boolop; - parentType = ((BoolExpr *) parentNode)->boolop; - switch (type) - { - case NOT_EXPR: - case AND_EXPR: - if (parentType == AND_EXPR || parentType == OR_EXPR) - return true; - break; - case OR_EXPR: - if (parentType == OR_EXPR) - return true; - break; - } - } - return false; - case T_FuncExpr: - { - /* special handling for casts */ - CoercionForm type = ((FuncExpr *) parentNode)->funcformat; - - if (type == COERCE_EXPLICIT_CAST || - type == COERCE_IMPLICIT_CAST) - return false; - return true; /* own parentheses */ - } - case T_SubscriptingRef: /* other separators */ - case T_ArrayExpr: /* other separators */ - case T_RowExpr: /* other separators */ - case T_CoalesceExpr: /* own parentheses */ - case T_MinMaxExpr: /* own parentheses */ - case T_XmlExpr: /* own parentheses */ - case T_NullIfExpr: /* other separators */ - case T_Aggref: /* own parentheses */ - case T_WindowFunc: /* own parentheses */ - case T_CaseExpr: /* other separators */ - return true; - default: - return false; - } - - default: - break; - } - /* those we don't know: in dubio complexo */ - return false; -} - - -/* - * appendContextKeyword - append a keyword to buffer - * - * If prettyPrint is enabled, perform a line break, and adjust indentation. - * Otherwise, just append the keyword. - */ -static void -appendContextKeyword(deparse_context *context, const char *str, - int indentBefore, int indentAfter, int indentPlus) -{ - StringInfo buf = context->buf; - - if (PRETTY_INDENT(context)) - { - int indentAmount; - - context->indentLevel += indentBefore; - - /* remove any trailing spaces currently in the buffer ... */ - removeStringInfoSpaces(buf); - /* ... then add a newline and some spaces */ - appendStringInfoChar(buf, '\n'); - - if (context->indentLevel < PRETTYINDENT_LIMIT) - indentAmount = Max(context->indentLevel, 0) + indentPlus; - else - { - /* - * If we're indented more than PRETTYINDENT_LIMIT characters, try - * to conserve horizontal space by reducing the per-level - * indentation. For best results the scale factor here should - * divide all the indent amounts that get added to indentLevel - * (PRETTYINDENT_STD, etc). It's important that the indentation - * not grow unboundedly, else deeply-nested trees use O(N^2) - * whitespace; so we also wrap modulo PRETTYINDENT_LIMIT. - */ - indentAmount = PRETTYINDENT_LIMIT + - (context->indentLevel - PRETTYINDENT_LIMIT) / - (PRETTYINDENT_STD / 2); - indentAmount %= PRETTYINDENT_LIMIT; - /* scale/wrap logic affects indentLevel, but not indentPlus */ - indentAmount += indentPlus; - } - appendStringInfoSpaces(buf, indentAmount); - - appendStringInfoString(buf, str); - - context->indentLevel += indentAfter; - if (context->indentLevel < 0) - context->indentLevel = 0; - } - else - appendStringInfoString(buf, str); -} - -/* - * removeStringInfoSpaces - delete trailing spaces from a buffer. - * - * Possibly this should move to stringinfo.c at some point. - */ -static void -removeStringInfoSpaces(StringInfo str) -{ - while (str->len > 0 && str->data[str->len - 1] == ' ') - str->data[--(str->len)] = '\0'; -} - - -/* - * get_rule_expr_paren - deparse expr using get_rule_expr, - * embracing the string with parentheses if necessary for prettyPrint. - * - * Never embrace if prettyFlags=0, because it's done in the calling node. - * - * Any node that does *not* embrace its argument node by sql syntax (with - * parentheses, non-operator keywords like CASE/WHEN/ON, or comma etc) should - * use get_rule_expr_paren instead of get_rule_expr so parentheses can be - * added. - */ -static void -get_rule_expr_paren(Node *node, deparse_context *context, - bool showimplicit, Node *parentNode) -{ - bool need_paren; - - need_paren = PRETTY_PAREN(context) && - !isSimpleNode(node, parentNode, context->prettyFlags); - - if (need_paren) - appendStringInfoChar(context->buf, '('); - - get_rule_expr(node, context, showimplicit); - - if (need_paren) - appendStringInfoChar(context->buf, ')'); -} - - -/* ---------- - * get_rule_expr - Parse back an expression - * - * Note: showimplicit determines whether we display any implicit cast that - * is present at the top of the expression tree. It is a passed argument, - * not a field of the context struct, because we change the value as we - * recurse down into the expression. In general we suppress implicit casts - * when the result type is known with certainty (eg, the arguments of an - * OR must be boolean). We display implicit casts for arguments of functions - * and operators, since this is needed to be certain that the same function - * or operator will be chosen when the expression is re-parsed. - * ---------- - */ -static void -get_rule_expr(Node *node, deparse_context *context, - bool showimplicit) -{ - StringInfo buf = context->buf; - - if (node == NULL) - return; - - /* Guard against excessively long or deeply-nested queries */ - CHECK_FOR_INTERRUPTS(); - check_stack_depth(); - - /* - * Each level of get_rule_expr must emit an indivisible term - * (parenthesized if necessary) to ensure result is reparsed into the same - * expression tree. The only exception is that when the input is a List, - * we emit the component items comma-separated with no surrounding - * decoration; this is convenient for most callers. - */ - switch (nodeTag(node)) - { - case T_Var: - (void) get_variable((Var *) node, 0, false, context); - break; - - case T_Const: - get_const_expr((Const *) node, context, 0); - break; - - case T_Param: - get_parameter((Param *) node, context); - break; - - case T_Aggref: - get_agg_expr((Aggref *) node, context, (Aggref *) node); - break; - - case T_GroupingFunc: - { - GroupingFunc *gexpr = (GroupingFunc *) node; - - appendStringInfoString(buf, "GROUPING("); - get_rule_expr((Node *) gexpr->args, context, true); - appendStringInfoChar(buf, ')'); - } - break; - - case T_WindowFunc: - get_windowfunc_expr((WindowFunc *) node, context); - break; - - case T_SubscriptingRef: - { - SubscriptingRef *sbsref = (SubscriptingRef *) node; - bool need_parens; - - /* - * If the argument is a CaseTestExpr, we must be inside a - * FieldStore, ie, we are assigning to an element of an array - * within a composite column. Since we already punted on - * displaying the FieldStore's target information, just punt - * here too, and display only the assignment source - * expression. - */ - if (IsA(sbsref->refexpr, CaseTestExpr)) - { - Assert(sbsref->refassgnexpr); - get_rule_expr((Node *) sbsref->refassgnexpr, - context, showimplicit); - break; - } - - /* - * Parenthesize the argument unless it's a simple Var or a - * FieldSelect. (In particular, if it's another - * SubscriptingRef, we *must* parenthesize to avoid - * confusion.) - */ - need_parens = !IsA(sbsref->refexpr, Var) && - !IsA(sbsref->refexpr, FieldSelect); - if (need_parens) - appendStringInfoChar(buf, '('); - get_rule_expr((Node *) sbsref->refexpr, context, showimplicit); - if (need_parens) - appendStringInfoChar(buf, ')'); - - /* - * If there's a refassgnexpr, we want to print the node in the - * format "container[subscripts] := refassgnexpr". This is - * not legal SQL, so decompilation of INSERT or UPDATE - * statements should always use processIndirection as part of - * the statement-level syntax. We should only see this when - * EXPLAIN tries to print the targetlist of a plan resulting - * from such a statement. - */ - if (sbsref->refassgnexpr) - { - Node *refassgnexpr; - - /* - * Use processIndirection to print this node's subscripts - * as well as any additional field selections or - * subscripting in immediate descendants. It returns the - * RHS expr that is actually being "assigned". - */ - refassgnexpr = processIndirection(node, context); - appendStringInfoString(buf, " := "); - get_rule_expr(refassgnexpr, context, showimplicit); - } - else - { - /* Just an ordinary container fetch, so print subscripts */ - printSubscripts(sbsref, context); - } - } - break; - - case T_FuncExpr: - get_func_expr((FuncExpr *) node, context, showimplicit); - break; - - case T_NamedArgExpr: - { - NamedArgExpr *na = (NamedArgExpr *) node; - - appendStringInfo(buf, "%s => ", quote_identifier(na->name)); - get_rule_expr((Node *) na->arg, context, showimplicit); - } - break; - - case T_OpExpr: - get_oper_expr((OpExpr *) node, context); - break; - - case T_DistinctExpr: - { - DistinctExpr *expr = (DistinctExpr *) node; - List *args = expr->args; - Node *arg1 = (Node *) linitial(args); - Node *arg2 = (Node *) lsecond(args); - - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, '('); - get_rule_expr_paren(arg1, context, true, node); - appendStringInfoString(buf, " IS DISTINCT FROM "); - get_rule_expr_paren(arg2, context, true, node); - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, ')'); - } - break; - - case T_NullIfExpr: - { - NullIfExpr *nullifexpr = (NullIfExpr *) node; - - appendStringInfoString(buf, "NULLIF("); - get_rule_expr((Node *) nullifexpr->args, context, true); - appendStringInfoChar(buf, ')'); - } - break; - - case T_ScalarArrayOpExpr: - { - ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node; - List *args = expr->args; - Node *arg1 = (Node *) linitial(args); - Node *arg2 = (Node *) lsecond(args); - - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, '('); - get_rule_expr_paren(arg1, context, true, node); - appendStringInfo(buf, " %s %s (", - generate_operator_name(expr->opno, - exprType(arg1), - get_base_element_type(exprType(arg2))), - expr->useOr ? "ANY" : "ALL"); - get_rule_expr_paren(arg2, context, true, node); - - /* - * There's inherent ambiguity in "x op ANY/ALL (y)" when y is - * a bare sub-SELECT. Since we're here, the sub-SELECT must - * be meant as a scalar sub-SELECT yielding an array value to - * be used in ScalarArrayOpExpr; but the grammar will - * preferentially interpret such a construct as an ANY/ALL - * SubLink. To prevent misparsing the output that way, insert - * a dummy coercion (which will be stripped by parse analysis, - * so no inefficiency is added in dump and reload). This is - * indeed most likely what the user wrote to get the construct - * accepted in the first place. - */ - if (IsA(arg2, SubLink) && - ((SubLink *) arg2)->subLinkType == EXPR_SUBLINK) - appendStringInfo(buf, "::%s", - format_type_with_typemod(exprType(arg2), - exprTypmod(arg2))); - appendStringInfoChar(buf, ')'); - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, ')'); - } - break; - - case T_BoolExpr: - { - BoolExpr *expr = (BoolExpr *) node; - Node *first_arg = linitial(expr->args); - ListCell *arg = list_second_cell(expr->args); - - switch (expr->boolop) - { - case AND_EXPR: - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, '('); - get_rule_expr_paren(first_arg, context, - false, node); - while (arg) - { - appendStringInfoString(buf, " AND "); - get_rule_expr_paren((Node *) lfirst(arg), context, - false, node); - arg = lnext(expr->args, arg); - } - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, ')'); - break; - - case OR_EXPR: - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, '('); - get_rule_expr_paren(first_arg, context, - false, node); - while (arg) - { - appendStringInfoString(buf, " OR "); - get_rule_expr_paren((Node *) lfirst(arg), context, - false, node); - arg = lnext(expr->args, arg); - } - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, ')'); - break; - - case NOT_EXPR: - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, '('); - appendStringInfoString(buf, "NOT "); - get_rule_expr_paren(first_arg, context, - false, node); - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, ')'); - break; - - default: - elog(ERROR, "unrecognized boolop: %d", - (int) expr->boolop); - } - } - break; - - case T_SubLink: - get_sublink_expr((SubLink *) node, context); - break; - - case T_SubPlan: - { - SubPlan *subplan = (SubPlan *) node; - - /* - * We cannot see an already-planned subplan in rule deparsing, - * only while EXPLAINing a query plan. We don't try to - * reconstruct the original SQL, just reference the subplan - * that appears elsewhere in EXPLAIN's result. - */ - if (subplan->useHashTable) - appendStringInfo(buf, "(hashed %s)", subplan->plan_name); - else - appendStringInfo(buf, "(%s)", subplan->plan_name); - } - break; - - case T_AlternativeSubPlan: - { - AlternativeSubPlan *asplan = (AlternativeSubPlan *) node; - ListCell *lc; - - /* As above, this can only happen during EXPLAIN */ - appendStringInfoString(buf, "(alternatives: "); - foreach(lc, asplan->subplans) - { - SubPlan *splan = lfirst_node(SubPlan, lc); - - if (splan->useHashTable) - appendStringInfo(buf, "hashed %s", splan->plan_name); - else - appendStringInfoString(buf, splan->plan_name); - if (lnext(asplan->subplans, lc)) - appendStringInfoString(buf, " or "); - } - appendStringInfoChar(buf, ')'); - } - break; - - case T_FieldSelect: - { - FieldSelect *fselect = (FieldSelect *) node; - Node *arg = (Node *) fselect->arg; - int fno = fselect->fieldnum; - const char *fieldname; - bool need_parens; - - /* - * Parenthesize the argument unless it's an SubscriptingRef or - * another FieldSelect. Note in particular that it would be - * WRONG to not parenthesize a Var argument; simplicity is not - * the issue here, having the right number of names is. - */ - need_parens = !IsA(arg, SubscriptingRef) && - !IsA(arg, FieldSelect); - if (need_parens) - appendStringInfoChar(buf, '('); - get_rule_expr(arg, context, true); - if (need_parens) - appendStringInfoChar(buf, ')'); - - /* - * Get and print the field name. - */ - fieldname = get_name_for_var_field((Var *) arg, fno, - 0, context); - appendStringInfo(buf, ".%s", quote_identifier(fieldname)); - } - break; - - case T_FieldStore: - { - FieldStore *fstore = (FieldStore *) node; - bool need_parens; - - /* - * There is no good way to represent a FieldStore as real SQL, - * so decompilation of INSERT or UPDATE statements should - * always use processIndirection as part of the - * statement-level syntax. We should only get here when - * EXPLAIN tries to print the targetlist of a plan resulting - * from such a statement. The plan case is even harder than - * ordinary rules would be, because the planner tries to - * collapse multiple assignments to the same field or subfield - * into one FieldStore; so we can see a list of target fields - * not just one, and the arguments could be FieldStores - * themselves. We don't bother to try to print the target - * field names; we just print the source arguments, with a - * ROW() around them if there's more than one. This isn't - * terribly complete, but it's probably good enough for - * EXPLAIN's purposes; especially since anything more would be - * either hopelessly confusing or an even poorer - * representation of what the plan is actually doing. - */ - need_parens = (list_length(fstore->newvals) != 1); - if (need_parens) - appendStringInfoString(buf, "ROW("); - get_rule_expr((Node *) fstore->newvals, context, showimplicit); - if (need_parens) - appendStringInfoChar(buf, ')'); - } - break; - - case T_RelabelType: - { - RelabelType *relabel = (RelabelType *) node; - - /* - * This is a Citus specific modification - * The planner converts CollateExpr to RelabelType - * and here we convert back. - */ - if (relabel->resultcollid != InvalidOid) - { - CollateExpr *collate = RelabelTypeToCollateExpr(relabel); - Node *arg = (Node *) collate->arg; - - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, '('); - get_rule_expr_paren(arg, context, showimplicit, node); - appendStringInfo(buf, " COLLATE %s", - generate_collation_name(collate->collOid)); - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, ')'); - } - else - { - Node *arg = (Node *) relabel->arg; - - if (relabel->relabelformat == COERCE_IMPLICIT_CAST && - !showimplicit) - { - /* don't show the implicit cast */ - get_rule_expr_paren(arg, context, false, node); - } - else - { - get_coercion_expr(arg, context, - relabel->resulttype, - relabel->resulttypmod, - node); - } - } - } - break; - - case T_CoerceViaIO: - { - CoerceViaIO *iocoerce = (CoerceViaIO *) node; - Node *arg = (Node *) iocoerce->arg; - - if (iocoerce->coerceformat == COERCE_IMPLICIT_CAST && - !showimplicit) - { - /* don't show the implicit cast */ - get_rule_expr_paren(arg, context, false, node); - } - else - { - get_coercion_expr(arg, context, - iocoerce->resulttype, - -1, - node); - } - } - break; - - case T_ArrayCoerceExpr: - { - ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node; - Node *arg = (Node *) acoerce->arg; - - if (acoerce->coerceformat == COERCE_IMPLICIT_CAST && - !showimplicit) - { - /* don't show the implicit cast */ - get_rule_expr_paren(arg, context, false, node); - } - else - { - get_coercion_expr(arg, context, - acoerce->resulttype, - acoerce->resulttypmod, - node); - } - } - break; - - case T_ConvertRowtypeExpr: - { - ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node; - Node *arg = (Node *) convert->arg; - - if (convert->convertformat == COERCE_IMPLICIT_CAST && - !showimplicit) - { - /* don't show the implicit cast */ - get_rule_expr_paren(arg, context, false, node); - } - else - { - get_coercion_expr(arg, context, - convert->resulttype, -1, - node); - } - } - break; - - case T_CollateExpr: - { - CollateExpr *collate = (CollateExpr *) node; - Node *arg = (Node *) collate->arg; - - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, '('); - get_rule_expr_paren(arg, context, showimplicit, node); - appendStringInfo(buf, " COLLATE %s", - generate_collation_name(collate->collOid)); - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, ')'); - } - break; - - case T_CaseExpr: - { - CaseExpr *caseexpr = (CaseExpr *) node; - ListCell *temp; - - appendContextKeyword(context, "CASE", - 0, PRETTYINDENT_VAR, 0); - if (caseexpr->arg) - { - appendStringInfoChar(buf, ' '); - get_rule_expr((Node *) caseexpr->arg, context, true); - } - foreach(temp, caseexpr->args) - { - CaseWhen *when = (CaseWhen *) lfirst(temp); - Node *w = (Node *) when->expr; - - if (caseexpr->arg) - { - /* - * The parser should have produced WHEN clauses of the - * form "CaseTestExpr = RHS", possibly with an - * implicit coercion inserted above the CaseTestExpr. - * For accurate decompilation of rules it's essential - * that we show just the RHS. However in an - * expression that's been through the optimizer, the - * WHEN clause could be almost anything (since the - * equality operator could have been expanded into an - * inline function). If we don't recognize the form - * of the WHEN clause, just punt and display it as-is. - */ - if (IsA(w, OpExpr)) - { - List *args = ((OpExpr *) w)->args; - - if (list_length(args) == 2 && - IsA(strip_implicit_coercions(linitial(args)), - CaseTestExpr)) - w = (Node *) lsecond(args); - } - } - - if (!PRETTY_INDENT(context)) - appendStringInfoChar(buf, ' '); - appendContextKeyword(context, "WHEN ", - 0, 0, 0); - get_rule_expr(w, context, false); - appendStringInfoString(buf, " THEN "); - get_rule_expr((Node *) when->result, context, true); - } - if (!PRETTY_INDENT(context)) - appendStringInfoChar(buf, ' '); - appendContextKeyword(context, "ELSE ", - 0, 0, 0); - get_rule_expr((Node *) caseexpr->defresult, context, true); - if (!PRETTY_INDENT(context)) - appendStringInfoChar(buf, ' '); - appendContextKeyword(context, "END", - -PRETTYINDENT_VAR, 0, 0); - } - break; - - case T_CaseTestExpr: - { - /* - * Normally we should never get here, since for expressions - * that can contain this node type we attempt to avoid - * recursing to it. But in an optimized expression we might - * be unable to avoid that (see comments for CaseExpr). If we - * do see one, print it as CASE_TEST_EXPR. - */ - appendStringInfoString(buf, "CASE_TEST_EXPR"); - } - break; - - case T_ArrayExpr: - { - ArrayExpr *arrayexpr = (ArrayExpr *) node; - - appendStringInfoString(buf, "ARRAY["); - get_rule_expr((Node *) arrayexpr->elements, context, true); - appendStringInfoChar(buf, ']'); - - /* - * If the array isn't empty, we assume its elements are - * coerced to the desired type. If it's empty, though, we - * need an explicit coercion to the array type. - */ - if (arrayexpr->elements == NIL) - appendStringInfo(buf, "::%s", - format_type_with_typemod(arrayexpr->array_typeid, -1)); - } - break; - - case T_RowExpr: - { - RowExpr *rowexpr = (RowExpr *) node; - TupleDesc tupdesc = NULL; - ListCell *arg; - int i; - char *sep; - - /* - * If it's a named type and not RECORD, we may have to skip - * dropped columns and/or claim there are NULLs for added - * columns. - */ - if (rowexpr->row_typeid != RECORDOID) - { - tupdesc = lookup_rowtype_tupdesc(rowexpr->row_typeid, -1); - Assert(list_length(rowexpr->args) <= tupdesc->natts); - } - - /* - * SQL99 allows "ROW" to be omitted when there is more than - * one column, but for simplicity we always print it. - */ - appendStringInfoString(buf, "ROW("); - sep = ""; - i = 0; - foreach(arg, rowexpr->args) - { - Node *e = (Node *) lfirst(arg); - - if (tupdesc == NULL || - !TupleDescAttr(tupdesc, i)->attisdropped) - { - appendStringInfoString(buf, sep); - /* Whole-row Vars need special treatment here */ - get_rule_expr_toplevel(e, context, true); - sep = ", "; - } - i++; - } - if (tupdesc != NULL) - { - while (i < tupdesc->natts) - { - if (!TupleDescAttr(tupdesc, i)->attisdropped) - { - appendStringInfoString(buf, sep); - appendStringInfoString(buf, "NULL"); - sep = ", "; - } - i++; - } - - ReleaseTupleDesc(tupdesc); - } - appendStringInfoChar(buf, ')'); - if (rowexpr->row_format == COERCE_EXPLICIT_CAST) - appendStringInfo(buf, "::%s", - format_type_with_typemod(rowexpr->row_typeid, -1)); - } - break; - - case T_RowCompareExpr: - { - RowCompareExpr *rcexpr = (RowCompareExpr *) node; - ListCell *arg; - char *sep; - - /* - * SQL99 allows "ROW" to be omitted when there is more than - * one column, but for simplicity we always print it. - */ - appendStringInfoString(buf, "(ROW("); - sep = ""; - foreach(arg, rcexpr->largs) - { - Node *e = (Node *) lfirst(arg); - - appendStringInfoString(buf, sep); - get_rule_expr(e, context, true); - sep = ", "; - } - - /* - * We assume that the name of the first-column operator will - * do for all the rest too. This is definitely open to - * failure, eg if some but not all operators were renamed - * since the construct was parsed, but there seems no way to - * be perfect. - */ - appendStringInfo(buf, ") %s ROW(", - generate_operator_name(linitial_oid(rcexpr->opnos), - exprType(linitial(rcexpr->largs)), - exprType(linitial(rcexpr->rargs)))); - sep = ""; - foreach(arg, rcexpr->rargs) - { - Node *e = (Node *) lfirst(arg); - - appendStringInfoString(buf, sep); - get_rule_expr(e, context, true); - sep = ", "; - } - appendStringInfoString(buf, "))"); - } - break; - - case T_CoalesceExpr: - { - CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; - - appendStringInfoString(buf, "COALESCE("); - get_rule_expr((Node *) coalesceexpr->args, context, true); - appendStringInfoChar(buf, ')'); - } - break; - - case T_MinMaxExpr: - { - MinMaxExpr *minmaxexpr = (MinMaxExpr *) node; - - switch (minmaxexpr->op) - { - case IS_GREATEST: - appendStringInfoString(buf, "GREATEST("); - break; - case IS_LEAST: - appendStringInfoString(buf, "LEAST("); - break; - } - get_rule_expr((Node *) minmaxexpr->args, context, true); - appendStringInfoChar(buf, ')'); - } - break; - - case T_SQLValueFunction: - { - SQLValueFunction *svf = (SQLValueFunction *) node; - - /* - * Note: this code knows that typmod for time, timestamp, and - * timestamptz just prints as integer. - */ - switch (svf->op) - { - case SVFOP_CURRENT_DATE: - appendStringInfoString(buf, "CURRENT_DATE"); - break; - case SVFOP_CURRENT_TIME: - appendStringInfoString(buf, "CURRENT_TIME"); - break; - case SVFOP_CURRENT_TIME_N: - appendStringInfo(buf, "CURRENT_TIME(%d)", svf->typmod); - break; - case SVFOP_CURRENT_TIMESTAMP: - appendStringInfoString(buf, "CURRENT_TIMESTAMP"); - break; - case SVFOP_CURRENT_TIMESTAMP_N: - appendStringInfo(buf, "CURRENT_TIMESTAMP(%d)", - svf->typmod); - break; - case SVFOP_LOCALTIME: - appendStringInfoString(buf, "LOCALTIME"); - break; - case SVFOP_LOCALTIME_N: - appendStringInfo(buf, "LOCALTIME(%d)", svf->typmod); - break; - case SVFOP_LOCALTIMESTAMP: - appendStringInfoString(buf, "LOCALTIMESTAMP"); - break; - case SVFOP_LOCALTIMESTAMP_N: - appendStringInfo(buf, "LOCALTIMESTAMP(%d)", - svf->typmod); - break; - case SVFOP_CURRENT_ROLE: - appendStringInfoString(buf, "CURRENT_ROLE"); - break; - case SVFOP_CURRENT_USER: - appendStringInfoString(buf, "CURRENT_USER"); - break; - case SVFOP_USER: - appendStringInfoString(buf, "USER"); - break; - case SVFOP_SESSION_USER: - appendStringInfoString(buf, "SESSION_USER"); - break; - case SVFOP_CURRENT_CATALOG: - appendStringInfoString(buf, "CURRENT_CATALOG"); - break; - case SVFOP_CURRENT_SCHEMA: - appendStringInfoString(buf, "CURRENT_SCHEMA"); - break; - } - } - break; - - case T_XmlExpr: - { - XmlExpr *xexpr = (XmlExpr *) node; - bool needcomma = false; - ListCell *arg; - ListCell *narg; - Const *con; - - switch (xexpr->op) - { - case IS_XMLCONCAT: - appendStringInfoString(buf, "XMLCONCAT("); - break; - case IS_XMLELEMENT: - appendStringInfoString(buf, "XMLELEMENT("); - break; - case IS_XMLFOREST: - appendStringInfoString(buf, "XMLFOREST("); - break; - case IS_XMLPARSE: - appendStringInfoString(buf, "XMLPARSE("); - break; - case IS_XMLPI: - appendStringInfoString(buf, "XMLPI("); - break; - case IS_XMLROOT: - appendStringInfoString(buf, "XMLROOT("); - break; - case IS_XMLSERIALIZE: - appendStringInfoString(buf, "XMLSERIALIZE("); - break; - case IS_DOCUMENT: - break; - } - if (xexpr->op == IS_XMLPARSE || xexpr->op == IS_XMLSERIALIZE) - { - if (xexpr->xmloption == XMLOPTION_DOCUMENT) - appendStringInfoString(buf, "DOCUMENT "); - else - appendStringInfoString(buf, "CONTENT "); - } - if (xexpr->name) - { - appendStringInfo(buf, "NAME %s", - quote_identifier(map_xml_name_to_sql_identifier(xexpr->name))); - needcomma = true; - } - if (xexpr->named_args) - { - if (xexpr->op != IS_XMLFOREST) - { - if (needcomma) - appendStringInfoString(buf, ", "); - appendStringInfoString(buf, "XMLATTRIBUTES("); - needcomma = false; - } - forboth(arg, xexpr->named_args, narg, xexpr->arg_names) - { - Node *e = (Node *) lfirst(arg); - char *argname = strVal(lfirst(narg)); - - if (needcomma) - appendStringInfoString(buf, ", "); - get_rule_expr((Node *) e, context, true); - appendStringInfo(buf, " AS %s", - quote_identifier(map_xml_name_to_sql_identifier(argname))); - needcomma = true; - } - if (xexpr->op != IS_XMLFOREST) - appendStringInfoChar(buf, ')'); - } - if (xexpr->args) - { - if (needcomma) - appendStringInfoString(buf, ", "); - switch (xexpr->op) - { - case IS_XMLCONCAT: - case IS_XMLELEMENT: - case IS_XMLFOREST: - case IS_XMLPI: - case IS_XMLSERIALIZE: - /* no extra decoration needed */ - get_rule_expr((Node *) xexpr->args, context, true); - break; - case IS_XMLPARSE: - Assert(list_length(xexpr->args) == 2); - - get_rule_expr((Node *) linitial(xexpr->args), - context, true); - - con = lsecond_node(Const, xexpr->args); - Assert(!con->constisnull); - if (DatumGetBool(con->constvalue)) - appendStringInfoString(buf, - " PRESERVE WHITESPACE"); - else - appendStringInfoString(buf, - " STRIP WHITESPACE"); - break; - case IS_XMLROOT: - Assert(list_length(xexpr->args) == 3); - - get_rule_expr((Node *) linitial(xexpr->args), - context, true); - - appendStringInfoString(buf, ", VERSION "); - con = (Const *) lsecond(xexpr->args); - if (IsA(con, Const) && - con->constisnull) - appendStringInfoString(buf, "NO VALUE"); - else - get_rule_expr((Node *) con, context, false); - - con = lthird_node(Const, xexpr->args); - if (con->constisnull) - /* suppress STANDALONE NO VALUE */ ; - else - { - switch (DatumGetInt32(con->constvalue)) - { - case XML_STANDALONE_YES: - appendStringInfoString(buf, - ", STANDALONE YES"); - break; - case XML_STANDALONE_NO: - appendStringInfoString(buf, - ", STANDALONE NO"); - break; - case XML_STANDALONE_NO_VALUE: - appendStringInfoString(buf, - ", STANDALONE NO VALUE"); - break; - default: - break; - } - } - break; - case IS_DOCUMENT: - get_rule_expr_paren((Node *) xexpr->args, context, false, node); - break; - } - - } - if (xexpr->op == IS_XMLSERIALIZE) - appendStringInfo(buf, " AS %s", - format_type_with_typemod(xexpr->type, - xexpr->typmod)); - if (xexpr->op == IS_DOCUMENT) - appendStringInfoString(buf, " IS DOCUMENT"); - else - appendStringInfoChar(buf, ')'); - } - break; - - case T_NullTest: - { - NullTest *ntest = (NullTest *) node; - - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, '('); - get_rule_expr_paren((Node *) ntest->arg, context, true, node); - - /* - * For scalar inputs, we prefer to print as IS [NOT] NULL, - * which is shorter and traditional. If it's a rowtype input - * but we're applying a scalar test, must print IS [NOT] - * DISTINCT FROM NULL to be semantically correct. - */ - if (ntest->argisrow || - !type_is_rowtype(exprType((Node *) ntest->arg))) - { - switch (ntest->nulltesttype) - { - case IS_NULL: - appendStringInfoString(buf, " IS NULL"); - break; - case IS_NOT_NULL: - appendStringInfoString(buf, " IS NOT NULL"); - break; - default: - elog(ERROR, "unrecognized nulltesttype: %d", - (int) ntest->nulltesttype); - } - } - else - { - switch (ntest->nulltesttype) - { - case IS_NULL: - appendStringInfoString(buf, " IS NOT DISTINCT FROM NULL"); - break; - case IS_NOT_NULL: - appendStringInfoString(buf, " IS DISTINCT FROM NULL"); - break; - default: - elog(ERROR, "unrecognized nulltesttype: %d", - (int) ntest->nulltesttype); - } - } - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, ')'); - } - break; - - case T_BooleanTest: - { - BooleanTest *btest = (BooleanTest *) node; - - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, '('); - get_rule_expr_paren((Node *) btest->arg, context, false, node); - switch (btest->booltesttype) - { - case IS_TRUE: - appendStringInfoString(buf, " IS TRUE"); - break; - case IS_NOT_TRUE: - appendStringInfoString(buf, " IS NOT TRUE"); - break; - case IS_FALSE: - appendStringInfoString(buf, " IS FALSE"); - break; - case IS_NOT_FALSE: - appendStringInfoString(buf, " IS NOT FALSE"); - break; - case IS_UNKNOWN: - appendStringInfoString(buf, " IS UNKNOWN"); - break; - case IS_NOT_UNKNOWN: - appendStringInfoString(buf, " IS NOT UNKNOWN"); - break; - default: - elog(ERROR, "unrecognized booltesttype: %d", - (int) btest->booltesttype); - } - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, ')'); - } - break; - - case T_CoerceToDomain: - { - CoerceToDomain *ctest = (CoerceToDomain *) node; - Node *arg = (Node *) ctest->arg; - - if (ctest->coercionformat == COERCE_IMPLICIT_CAST && - !showimplicit) - { - /* don't show the implicit cast */ - get_rule_expr(arg, context, false); - } - else - { - get_coercion_expr(arg, context, - ctest->resulttype, - ctest->resulttypmod, - node); - } - } - break; - - case T_CoerceToDomainValue: - appendStringInfoString(buf, "VALUE"); - break; - - case T_SetToDefault: - appendStringInfoString(buf, "DEFAULT"); - break; - - case T_CurrentOfExpr: - { - CurrentOfExpr *cexpr = (CurrentOfExpr *) node; - - if (cexpr->cursor_name) - appendStringInfo(buf, "CURRENT OF %s", - quote_identifier(cexpr->cursor_name)); - else - appendStringInfo(buf, "CURRENT OF $%d", - cexpr->cursor_param); - } - break; - - case T_NextValueExpr: - { - NextValueExpr *nvexpr = (NextValueExpr *) node; - - /* - * This isn't exactly nextval(), but that seems close enough - * for EXPLAIN's purposes. - */ - appendStringInfoString(buf, "nextval("); - simple_quote_literal(buf, - generate_relation_name(nvexpr->seqid, - NIL)); - appendStringInfoChar(buf, ')'); - } - break; - - case T_InferenceElem: - { - InferenceElem *iexpr = (InferenceElem *) node; - bool save_varprefix; - bool need_parens; - - /* - * InferenceElem can only refer to target relation, so a - * prefix is not useful, and indeed would cause parse errors. - */ - save_varprefix = context->varprefix; - context->varprefix = false; - - /* - * Parenthesize the element unless it's a simple Var or a bare - * function call. Follows pg_get_indexdef_worker(). - */ - need_parens = !IsA(iexpr->expr, Var); - if (IsA(iexpr->expr, FuncExpr) && - ((FuncExpr *) iexpr->expr)->funcformat == - COERCE_EXPLICIT_CALL) - need_parens = false; - - if (need_parens) - appendStringInfoChar(buf, '('); - get_rule_expr((Node *) iexpr->expr, - context, false); - if (need_parens) - appendStringInfoChar(buf, ')'); - - context->varprefix = save_varprefix; - - if (iexpr->infercollid) - appendStringInfo(buf, " COLLATE %s", - generate_collation_name(iexpr->infercollid)); - - /* Add the operator class name, if not default */ - if (iexpr->inferopclass) - { - Oid inferopclass = iexpr->inferopclass; - Oid inferopcinputtype = get_opclass_input_type(iexpr->inferopclass); - - get_opclass_name(inferopclass, inferopcinputtype, buf); - } - } - break; - - case T_PartitionBoundSpec: - { - PartitionBoundSpec *spec = (PartitionBoundSpec *) node; - ListCell *cell; - char *sep; - - if (spec->is_default) - { - appendStringInfoString(buf, "DEFAULT"); - break; - } - - switch (spec->strategy) - { - case PARTITION_STRATEGY_HASH: - Assert(spec->modulus > 0 && spec->remainder >= 0); - Assert(spec->modulus > spec->remainder); - - appendStringInfoString(buf, "FOR VALUES"); - appendStringInfo(buf, " WITH (modulus %d, remainder %d)", - spec->modulus, spec->remainder); - break; - - case PARTITION_STRATEGY_LIST: - Assert(spec->listdatums != NIL); - - appendStringInfoString(buf, "FOR VALUES IN ("); - sep = ""; - foreach(cell, spec->listdatums) - { - Const *val = castNode(Const, lfirst(cell)); - - appendStringInfoString(buf, sep); - get_const_expr(val, context, -1); - sep = ", "; - } - - appendStringInfoChar(buf, ')'); - break; - - case PARTITION_STRATEGY_RANGE: - Assert(spec->lowerdatums != NIL && - spec->upperdatums != NIL && - list_length(spec->lowerdatums) == - list_length(spec->upperdatums)); - - appendStringInfo(buf, "FOR VALUES FROM %s TO %s", - get_range_partbound_string(spec->lowerdatums), - get_range_partbound_string(spec->upperdatums)); - break; - - default: - elog(ERROR, "unrecognized partition strategy: %d", - (int) spec->strategy); - break; - } - } - break; - - case T_List: - { - char *sep; - ListCell *l; - - sep = ""; - foreach(l, (List *) node) - { - appendStringInfoString(buf, sep); - get_rule_expr((Node *) lfirst(l), context, showimplicit); - sep = ", "; - } - } - break; - - case T_TableFunc: - get_tablefunc((TableFunc *) node, context, showimplicit); - break; - - case T_CallStmt: - get_func_expr(((CallStmt *) node)->funcexpr, context, showimplicit); - break; - - default: - elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); - break; - } -} - -/* - * get_rule_expr_toplevel - Parse back a toplevel expression - * - * Same as get_rule_expr(), except that if the expr is just a Var, we pass - * istoplevel = true not false to get_variable(). This causes whole-row Vars - * to get printed with decoration that will prevent expansion of "*". - * We need to use this in contexts such as ROW() and VALUES(), where the - * parser would expand "foo.*" appearing at top level. (In principle we'd - * use this in get_target_list() too, but that has additional worries about - * whether to print AS, so it needs to invoke get_variable() directly anyway.) - */ -static void -get_rule_expr_toplevel(Node *node, deparse_context *context, - bool showimplicit) -{ - if (node && IsA(node, Var)) - (void) get_variable((Var *) node, 0, true, context); - else - get_rule_expr(node, context, showimplicit); -} - -/* - * get_rule_expr_funccall - Parse back a function-call expression - * - * Same as get_rule_expr(), except that we guarantee that the output will - * look like a function call, or like one of the things the grammar treats as - * equivalent to a function call (see the func_expr_windowless production). - * This is needed in places where the grammar uses func_expr_windowless and - * you can't substitute a parenthesized a_expr. If what we have isn't going - * to look like a function call, wrap it in a dummy CAST() expression, which - * will satisfy the grammar --- and, indeed, is likely what the user wrote to - * produce such a thing. - */ -static void -get_rule_expr_funccall(Node *node, deparse_context *context, - bool showimplicit) -{ - if (looks_like_function(node)) - get_rule_expr(node, context, showimplicit); - else - { - StringInfo buf = context->buf; - - appendStringInfoString(buf, "CAST("); - /* no point in showing any top-level implicit cast */ - get_rule_expr(node, context, false); - appendStringInfo(buf, " AS %s)", - format_type_with_typemod(exprType(node), - exprTypmod(node))); - } -} - -/* - * Helper function to identify node types that satisfy func_expr_windowless. - * If in doubt, "false" is always a safe answer. - */ -static bool -looks_like_function(Node *node) -{ - if (node == NULL) - return false; /* probably shouldn't happen */ - switch (nodeTag(node)) - { - case T_FuncExpr: - /* OK, unless it's going to deparse as a cast */ - return (((FuncExpr *) node)->funcformat == COERCE_EXPLICIT_CALL); - case T_NullIfExpr: - case T_CoalesceExpr: - case T_MinMaxExpr: - case T_SQLValueFunction: - case T_XmlExpr: - /* these are all accepted by func_expr_common_subexpr */ - return true; - default: - break; - } - return false; -} - - -/* - * get_oper_expr - Parse back an OpExpr node - */ -static void -get_oper_expr(OpExpr *expr, deparse_context *context) -{ - StringInfo buf = context->buf; - Oid opno = expr->opno; - List *args = expr->args; - - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, '('); - if (list_length(args) == 2) - { - /* binary operator */ - Node *arg1 = (Node *) linitial(args); - Node *arg2 = (Node *) lsecond(args); - - get_rule_expr_paren(arg1, context, true, (Node *) expr); - appendStringInfo(buf, " %s ", - generate_operator_name(opno, - exprType(arg1), - exprType(arg2))); - get_rule_expr_paren(arg2, context, true, (Node *) expr); - } - else - { - /* unary operator --- but which side? */ - Node *arg = (Node *) linitial(args); - HeapTuple tp; - Form_pg_operator optup; - - tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno)); - if (!HeapTupleIsValid(tp)) - elog(ERROR, "cache lookup failed for operator %u", opno); - optup = (Form_pg_operator) GETSTRUCT(tp); - switch (optup->oprkind) - { - case 'l': - appendStringInfo(buf, "%s ", - generate_operator_name(opno, - InvalidOid, - exprType(arg))); - get_rule_expr_paren(arg, context, true, (Node *) expr); - break; - case 'r': - get_rule_expr_paren(arg, context, true, (Node *) expr); - appendStringInfo(buf, " %s", - generate_operator_name(opno, - exprType(arg), - InvalidOid)); - break; - default: - elog(ERROR, "bogus oprkind: %d", optup->oprkind); - } - ReleaseSysCache(tp); - } - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, ')'); -} - -/* - * get_func_expr - Parse back a FuncExpr node - */ -static void -get_func_expr(FuncExpr *expr, deparse_context *context, - bool showimplicit) -{ - StringInfo buf = context->buf; - Oid funcoid = expr->funcid; - Oid argtypes[FUNC_MAX_ARGS]; - int nargs; - List *argnames; - bool use_variadic; - ListCell *l; - - /* - * If the function call came from an implicit coercion, then just show the - * first argument --- unless caller wants to see implicit coercions. - */ - if (expr->funcformat == COERCE_IMPLICIT_CAST && !showimplicit) - { - get_rule_expr_paren((Node *) linitial(expr->args), context, - false, (Node *) expr); - return; - } - - /* - * If the function call came from a cast, then show the first argument - * plus an explicit cast operation. - */ - if (expr->funcformat == COERCE_EXPLICIT_CAST || - expr->funcformat == COERCE_IMPLICIT_CAST) - { - Node *arg = linitial(expr->args); - Oid rettype = expr->funcresulttype; - int32 coercedTypmod; - - /* Get the typmod if this is a length-coercion function */ - (void) exprIsLengthCoercion((Node *) expr, &coercedTypmod); - - get_coercion_expr(arg, context, - rettype, coercedTypmod, - (Node *) expr); - - return; - } - - /* - * Normal function: display as proname(args). First we need to extract - * the argument datatypes. - */ - if (list_length(expr->args) > FUNC_MAX_ARGS) - ereport(ERROR, - (errcode(ERRCODE_TOO_MANY_ARGUMENTS), - errmsg("too many arguments"))); - nargs = 0; - argnames = NIL; - foreach(l, expr->args) - { - Node *arg = (Node *) lfirst(l); - - if (IsA(arg, NamedArgExpr)) - argnames = lappend(argnames, ((NamedArgExpr *) arg)->name); - argtypes[nargs] = exprType(arg); - nargs++; - } - - appendStringInfo(buf, "%s(", - generate_function_name(funcoid, nargs, - argnames, argtypes, - expr->funcvariadic, - &use_variadic, - context->special_exprkind)); - nargs = 0; - foreach(l, expr->args) - { - if (nargs++ > 0) - appendStringInfoString(buf, ", "); - if (use_variadic && lnext(expr->args, l) == NULL) - appendStringInfoString(buf, "VARIADIC "); - get_rule_expr((Node *) lfirst(l), context, true); - } - appendStringInfoChar(buf, ')'); -} - -/* - * get_agg_expr - Parse back an Aggref node - */ -static void -get_agg_expr(Aggref *aggref, deparse_context *context, - Aggref *original_aggref) -{ - StringInfo buf = context->buf; - Oid argtypes[FUNC_MAX_ARGS]; - int nargs; - bool use_variadic; - - /* - * For a combining aggregate, we look up and deparse the corresponding - * partial aggregate instead. This is necessary because our input - * argument list has been replaced; the new argument list always has just - * one element, which will point to a partial Aggref that supplies us with - * transition states to combine. - */ - if (DO_AGGSPLIT_COMBINE(aggref->aggsplit)) - { - TargetEntry *tle; - - - Assert(list_length(aggref->args) == 1); - tle = linitial_node(TargetEntry, aggref->args); - resolve_special_varno((Node *) tle->expr, context, - get_agg_combine_expr, original_aggref); - return; - } - - /* - * Mark as PARTIAL, if appropriate. We look to the original aggref so as - * to avoid printing this when recursing from the code just above. - */ - if (DO_AGGSPLIT_SKIPFINAL(original_aggref->aggsplit)) - appendStringInfoString(buf, "PARTIAL "); - - /* Extract the argument types as seen by the parser */ - nargs = get_aggregate_argtypes(aggref, argtypes); - - /* Print the aggregate name, schema-qualified if needed */ - appendStringInfo(buf, "%s(%s", - generate_function_name(aggref->aggfnoid, nargs, - NIL, argtypes, - aggref->aggvariadic, - &use_variadic, - context->special_exprkind), - (aggref->aggdistinct != NIL) ? "DISTINCT " : ""); - - if (AGGKIND_IS_ORDERED_SET(aggref->aggkind)) - { - /* - * Ordered-set aggregates do not use "*" syntax. Also, we needn't - * worry about inserting VARIADIC. So we can just dump the direct - * args as-is. - */ - Assert(!aggref->aggvariadic); - get_rule_expr((Node *) aggref->aggdirectargs, context, true); - Assert(aggref->aggorder != NIL); - appendStringInfoString(buf, ") WITHIN GROUP (ORDER BY "); - get_rule_orderby(aggref->aggorder, aggref->args, false, context); - } - else - { - /* aggstar can be set only in zero-argument aggregates */ - if (aggref->aggstar) - appendStringInfoChar(buf, '*'); - else - { - ListCell *l; - int i; - - i = 0; - foreach(l, aggref->args) - { - TargetEntry *tle = (TargetEntry *) lfirst(l); - Node *arg = (Node *) tle->expr; - - Assert(!IsA(arg, NamedArgExpr)); - if (tle->resjunk) - continue; - if (i++ > 0) - appendStringInfoString(buf, ", "); - if (use_variadic && i == nargs) - appendStringInfoString(buf, "VARIADIC "); - get_rule_expr(arg, context, true); - } - } - - if (aggref->aggorder != NIL) - { - appendStringInfoString(buf, " ORDER BY "); - get_rule_orderby(aggref->aggorder, aggref->args, false, context); - } - } - - if (aggref->aggfilter != NULL) - { - appendStringInfoString(buf, ") FILTER (WHERE "); - get_rule_expr((Node *) aggref->aggfilter, context, false); - } - - appendStringInfoChar(buf, ')'); -} - -/* - * This is a helper function for get_agg_expr(). It's used when we deparse - * a combining Aggref; resolve_special_varno locates the corresponding partial - * Aggref and then calls this. - */ -static void -get_agg_combine_expr(Node *node, deparse_context *context, void *callback_arg) -{ - Aggref *aggref; - Aggref *original_aggref = callback_arg; - - if (!IsA(node, Aggref)) - elog(ERROR, "combining Aggref does not point to an Aggref"); - - aggref = (Aggref *) node; - get_agg_expr(aggref, context, original_aggref); -} - -/* - * get_windowfunc_expr - Parse back a WindowFunc node - */ -static void -get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context) -{ - StringInfo buf = context->buf; - Oid argtypes[FUNC_MAX_ARGS]; - int nargs; - List *argnames; - ListCell *l; - - if (list_length(wfunc->args) > FUNC_MAX_ARGS) - ereport(ERROR, - (errcode(ERRCODE_TOO_MANY_ARGUMENTS), - errmsg("too many arguments"))); - nargs = 0; - argnames = NIL; - foreach(l, wfunc->args) - { - Node *arg = (Node *) lfirst(l); - - if (IsA(arg, NamedArgExpr)) - argnames = lappend(argnames, ((NamedArgExpr *) arg)->name); - argtypes[nargs] = exprType(arg); - nargs++; - } - - appendStringInfo(buf, "%s(", - generate_function_name(wfunc->winfnoid, nargs, - argnames, argtypes, - false, NULL, - context->special_exprkind)); - /* winstar can be set only in zero-argument aggregates */ - if (wfunc->winstar) - appendStringInfoChar(buf, '*'); - else - get_rule_expr((Node *) wfunc->args, context, true); - - if (wfunc->aggfilter != NULL) - { - appendStringInfoString(buf, ") FILTER (WHERE "); - get_rule_expr((Node *) wfunc->aggfilter, context, false); - } - - appendStringInfoString(buf, ") OVER "); - - foreach(l, context->windowClause) - { - WindowClause *wc = (WindowClause *) lfirst(l); - - if (wc->winref == wfunc->winref) - { - if (wc->name) - appendStringInfoString(buf, quote_identifier(wc->name)); - else - get_rule_windowspec(wc, context->windowTList, context); - break; - } - } - if (l == NULL) - { - if (context->windowClause) - elog(ERROR, "could not find window clause for winref %u", - wfunc->winref); - - /* - * In EXPLAIN, we don't have window context information available, so - * we have to settle for this: - */ - appendStringInfoString(buf, "(?)"); - } -} - -/* ---------- - * get_coercion_expr - * - * Make a string representation of a value coerced to a specific type - * ---------- - */ -static void -get_coercion_expr(Node *arg, deparse_context *context, - Oid resulttype, int32 resulttypmod, - Node *parentNode) -{ - StringInfo buf = context->buf; - - /* - * Since parse_coerce.c doesn't immediately collapse application of - * length-coercion functions to constants, what we'll typically see in - * such cases is a Const with typmod -1 and a length-coercion function - * right above it. Avoid generating redundant output. However, beware of - * suppressing casts when the user actually wrote something like - * 'foo'::text::char(3). - * - * Note: it might seem that we are missing the possibility of needing to - * print a COLLATE clause for such a Const. However, a Const could only - * have nondefault collation in a post-constant-folding tree, in which the - * length coercion would have been folded too. See also the special - * handling of CollateExpr in coerce_to_target_type(): any collation - * marking will be above the coercion node, not below it. - */ - if (arg && IsA(arg, Const) && - ((Const *) arg)->consttype == resulttype && - ((Const *) arg)->consttypmod == -1) - { - /* Show the constant without normal ::typename decoration */ - get_const_expr((Const *) arg, context, -1); - } - else - { - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, '('); - get_rule_expr_paren(arg, context, false, parentNode); - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, ')'); - } - appendStringInfo(buf, "::%s", - format_type_with_typemod(resulttype, resulttypmod)); -} - -/* ---------- - * get_const_expr - * - * Make a string representation of a Const - * - * showtype can be -1 to never show "::typename" decoration, or +1 to always - * show it, or 0 to show it only if the constant wouldn't be assumed to be - * the right type by default. - * - * If the Const's collation isn't default for its type, show that too. - * We mustn't do this when showtype is -1 (since that means the caller will - * print "::typename", and we can't put a COLLATE clause in between). It's - * caller's responsibility that collation isn't missed in such cases. - * ---------- - */ -static void -get_const_expr(Const *constval, deparse_context *context, int showtype) -{ - StringInfo buf = context->buf; - Oid typoutput; - bool typIsVarlena; - char *extval; - bool needlabel = false; - - if (constval->constisnull) - { - /* - * Always label the type of a NULL constant to prevent misdecisions - * about type when reparsing. - */ - appendStringInfoString(buf, "NULL"); - if (showtype >= 0) - { - appendStringInfo(buf, "::%s", - format_type_with_typemod(constval->consttype, - constval->consttypmod)); - get_const_collation(constval, context); - } - return; - } - - getTypeOutputInfo(constval->consttype, - &typoutput, &typIsVarlena); - - extval = OidOutputFunctionCall(typoutput, constval->constvalue); - - switch (constval->consttype) - { - case INT4OID: - - /* - * INT4 can be printed without any decoration, unless it is - * negative; in that case print it as '-nnn'::integer to ensure - * that the output will re-parse as a constant, not as a constant - * plus operator. In most cases we could get away with printing - * (-nnn) instead, because of the way that gram.y handles negative - * literals; but that doesn't work for INT_MIN, and it doesn't - * seem that much prettier anyway. - */ - if (extval[0] != '-') - appendStringInfoString(buf, extval); - else - { - appendStringInfo(buf, "'%s'", extval); - needlabel = true; /* we must attach a cast */ - } - break; - - case NUMERICOID: - - /* - * NUMERIC can be printed without quotes if it looks like a float - * constant (not an integer, and not Infinity or NaN) and doesn't - * have a leading sign (for the same reason as for INT4). - */ - if (isdigit((unsigned char) extval[0]) && - strcspn(extval, "eE.") != strlen(extval)) - { - appendStringInfoString(buf, extval); - } - else - { - appendStringInfo(buf, "'%s'", extval); - needlabel = true; /* we must attach a cast */ - } - break; - - case BITOID: - case VARBITOID: - appendStringInfo(buf, "B'%s'", extval); - break; - - case BOOLOID: - if (strcmp(extval, "t") == 0) - appendStringInfoString(buf, "true"); - else - appendStringInfoString(buf, "false"); - break; - - default: - simple_quote_literal(buf, extval); - break; - } - - pfree(extval); - - if (showtype < 0) - return; - - /* - * For showtype == 0, append ::typename unless the constant will be - * implicitly typed as the right type when it is read in. - * - * XXX this code has to be kept in sync with the behavior of the parser, - * especially make_const. - */ - switch (constval->consttype) - { - case BOOLOID: - case UNKNOWNOID: - /* These types can be left unlabeled */ - needlabel = false; - break; - case INT4OID: - /* We determined above whether a label is needed */ - break; - case NUMERICOID: - - /* - * Float-looking constants will be typed as numeric, which we - * checked above; but if there's a nondefault typmod we need to - * show it. - */ - needlabel |= (constval->consttypmod >= 0); - break; - default: - needlabel = true; - break; - } - if (needlabel || showtype > 0) - appendStringInfo(buf, "::%s", - format_type_with_typemod(constval->consttype, - constval->consttypmod)); - - get_const_collation(constval, context); -} - -/* - * helper for get_const_expr: append COLLATE if needed - */ -static void -get_const_collation(Const *constval, deparse_context *context) -{ - StringInfo buf = context->buf; - - if (OidIsValid(constval->constcollid)) - { - Oid typcollation = get_typcollation(constval->consttype); - - if (constval->constcollid != typcollation) - { - appendStringInfo(buf, " COLLATE %s", - generate_collation_name(constval->constcollid)); - } - } -} - -/* - * simple_quote_literal - Format a string as a SQL literal, append to buf - */ -static void -simple_quote_literal(StringInfo buf, const char *val) -{ - const char *valptr; - - /* - * We form the string literal according to the prevailing setting of - * standard_conforming_strings; we never use E''. User is responsible for - * making sure result is used correctly. - */ - appendStringInfoChar(buf, '\''); - for (valptr = val; *valptr; valptr++) - { - char ch = *valptr; - - if (SQL_STR_DOUBLE(ch, !standard_conforming_strings)) - appendStringInfoChar(buf, ch); - appendStringInfoChar(buf, ch); - } - appendStringInfoChar(buf, '\''); -} - - -/* ---------- - * get_sublink_expr - Parse back a sublink - * ---------- - */ -static void -get_sublink_expr(SubLink *sublink, deparse_context *context) -{ - StringInfo buf = context->buf; - Query *query = (Query *) (sublink->subselect); - char *opname = NULL; - bool need_paren; - - if (sublink->subLinkType == ARRAY_SUBLINK) - appendStringInfoString(buf, "ARRAY("); - else - appendStringInfoChar(buf, '('); - - /* - * Note that we print the name of only the first operator, when there are - * multiple combining operators. This is an approximation that could go - * wrong in various scenarios (operators in different schemas, renamed - * operators, etc) but there is not a whole lot we can do about it, since - * the syntax allows only one operator to be shown. - */ - if (sublink->testexpr) - { - if (IsA(sublink->testexpr, OpExpr)) - { - /* single combining operator */ - OpExpr *opexpr = (OpExpr *) sublink->testexpr; - - get_rule_expr(linitial(opexpr->args), context, true); - opname = generate_operator_name(opexpr->opno, - exprType(linitial(opexpr->args)), - exprType(lsecond(opexpr->args))); - } - else if (IsA(sublink->testexpr, BoolExpr)) - { - /* multiple combining operators, = or <> cases */ - char *sep; - ListCell *l; - - appendStringInfoChar(buf, '('); - sep = ""; - foreach(l, ((BoolExpr *) sublink->testexpr)->args) - { - OpExpr *opexpr = lfirst_node(OpExpr, l); - - appendStringInfoString(buf, sep); - get_rule_expr(linitial(opexpr->args), context, true); - if (!opname) - opname = generate_operator_name(opexpr->opno, - exprType(linitial(opexpr->args)), - exprType(lsecond(opexpr->args))); - sep = ", "; - } - appendStringInfoChar(buf, ')'); - } - else if (IsA(sublink->testexpr, RowCompareExpr)) - { - /* multiple combining operators, < <= > >= cases */ - RowCompareExpr *rcexpr = (RowCompareExpr *) sublink->testexpr; - - appendStringInfoChar(buf, '('); - get_rule_expr((Node *) rcexpr->largs, context, true); - opname = generate_operator_name(linitial_oid(rcexpr->opnos), - exprType(linitial(rcexpr->largs)), - exprType(linitial(rcexpr->rargs))); - appendStringInfoChar(buf, ')'); - } - else - elog(ERROR, "unrecognized testexpr type: %d", - (int) nodeTag(sublink->testexpr)); - } - - need_paren = true; - - switch (sublink->subLinkType) - { - case EXISTS_SUBLINK: - appendStringInfoString(buf, "EXISTS "); - break; - - case ANY_SUBLINK: - if (strcmp(opname, "=") == 0) /* Represent = ANY as IN */ - appendStringInfoString(buf, " IN "); - else - appendStringInfo(buf, " %s ANY ", opname); - break; - - case ALL_SUBLINK: - appendStringInfo(buf, " %s ALL ", opname); - break; - - case ROWCOMPARE_SUBLINK: - appendStringInfo(buf, " %s ", opname); - break; - - case EXPR_SUBLINK: - case MULTIEXPR_SUBLINK: - case ARRAY_SUBLINK: - need_paren = false; - break; - - case CTE_SUBLINK: /* shouldn't occur in a SubLink */ - default: - elog(ERROR, "unrecognized sublink type: %d", - (int) sublink->subLinkType); - break; - } - - if (need_paren) - appendStringInfoChar(buf, '('); - - get_query_def(query, buf, context->namespaces, NULL, - context->prettyFlags, context->wrapColumn, - context->indentLevel); - - if (need_paren) - appendStringInfoString(buf, "))"); - else - appendStringInfoChar(buf, ')'); -} - - -/* ---------- - * get_tablefunc - Parse back a table function - * ---------- - */ -static void -get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit) -{ - StringInfo buf = context->buf; - - /* XMLTABLE is the only existing implementation. */ - - appendStringInfoString(buf, "XMLTABLE("); - - if (tf->ns_uris != NIL) - { - ListCell *lc1, - *lc2; - bool first = true; - - appendStringInfoString(buf, "XMLNAMESPACES ("); - forboth(lc1, tf->ns_uris, lc2, tf->ns_names) - { - Node *expr = (Node *) lfirst(lc1); - char *name = strVal(lfirst(lc2)); - - if (!first) - appendStringInfoString(buf, ", "); - else - first = false; - - if (name != NULL) - { - get_rule_expr(expr, context, showimplicit); - appendStringInfo(buf, " AS %s", name); - } - else - { - appendStringInfoString(buf, "DEFAULT "); - get_rule_expr(expr, context, showimplicit); - } - } - appendStringInfoString(buf, "), "); - } - - appendStringInfoChar(buf, '('); - get_rule_expr((Node *) tf->rowexpr, context, showimplicit); - appendStringInfoString(buf, ") PASSING ("); - get_rule_expr((Node *) tf->docexpr, context, showimplicit); - appendStringInfoChar(buf, ')'); - - if (tf->colexprs != NIL) - { - ListCell *l1; - ListCell *l2; - ListCell *l3; - ListCell *l4; - ListCell *l5; - int colnum = 0; - - appendStringInfoString(buf, " COLUMNS "); - forfive(l1, tf->colnames, l2, tf->coltypes, l3, tf->coltypmods, - l4, tf->colexprs, l5, tf->coldefexprs) - { - char *colname = strVal(lfirst(l1)); - Oid typid = lfirst_oid(l2); - int32 typmod = lfirst_int(l3); - Node *colexpr = (Node *) lfirst(l4); - Node *coldefexpr = (Node *) lfirst(l5); - bool ordinality = (tf->ordinalitycol == colnum); - bool notnull = bms_is_member(colnum, tf->notnulls); - - if (colnum > 0) - appendStringInfoString(buf, ", "); - colnum++; - - appendStringInfo(buf, "%s %s", quote_identifier(colname), - ordinality ? "FOR ORDINALITY" : - format_type_with_typemod(typid, typmod)); - if (ordinality) - continue; - - if (coldefexpr != NULL) - { - appendStringInfoString(buf, " DEFAULT ("); - get_rule_expr((Node *) coldefexpr, context, showimplicit); - appendStringInfoChar(buf, ')'); - } - if (colexpr != NULL) - { - appendStringInfoString(buf, " PATH ("); - get_rule_expr((Node *) colexpr, context, showimplicit); - appendStringInfoChar(buf, ')'); - } - if (notnull) - appendStringInfoString(buf, " NOT NULL"); - } - } - - appendStringInfoChar(buf, ')'); -} - -/* ---------- - * get_from_clause - Parse back a FROM clause - * - * "prefix" is the keyword that denotes the start of the list of FROM - * elements. It is FROM when used to parse back SELECT and UPDATE, but - * is USING when parsing back DELETE. - * ---------- - */ -static void -get_from_clause(Query *query, const char *prefix, deparse_context *context) -{ - StringInfo buf = context->buf; - bool first = true; - ListCell *l; - - /* - * We use the query's jointree as a guide to what to print. However, we - * must ignore auto-added RTEs that are marked not inFromCl. (These can - * only appear at the top level of the jointree, so it's sufficient to - * check here.) This check also ensures we ignore the rule pseudo-RTEs - * for NEW and OLD. - */ - foreach(l, query->jointree->fromlist) - { - Node *jtnode = (Node *) lfirst(l); - - if (IsA(jtnode, RangeTblRef)) - { - int varno = ((RangeTblRef *) jtnode)->rtindex; - RangeTblEntry *rte = rt_fetch(varno, query->rtable); - - if (!rte->inFromCl) - continue; - } - - if (first) - { - appendContextKeyword(context, prefix, - -PRETTYINDENT_STD, PRETTYINDENT_STD, 2); - first = false; - - get_from_clause_item(jtnode, query, context); - } - else - { - StringInfoData itembuf; - - appendStringInfoString(buf, ", "); - - /* - * Put the new FROM item's text into itembuf so we can decide - * after we've got it whether or not it needs to go on a new line. - */ - initStringInfo(&itembuf); - context->buf = &itembuf; - - get_from_clause_item(jtnode, query, context); - - /* Restore context's output buffer */ - context->buf = buf; - - /* Consider line-wrapping if enabled */ - if (PRETTY_INDENT(context) && context->wrapColumn >= 0) - { - /* Does the new item start with a new line? */ - if (itembuf.len > 0 && itembuf.data[0] == '\n') - { - /* If so, we shouldn't add anything */ - /* instead, remove any trailing spaces currently in buf */ - removeStringInfoSpaces(buf); - } - else - { - char *trailing_nl; - - /* Locate the start of the current line in the buffer */ - trailing_nl = strrchr(buf->data, '\n'); - if (trailing_nl == NULL) - trailing_nl = buf->data; - else - trailing_nl++; - - /* - * Add a newline, plus some indentation, if the new item - * would cause an overflow. - */ - if (strlen(trailing_nl) + itembuf.len > context->wrapColumn) - appendContextKeyword(context, "", -PRETTYINDENT_STD, - PRETTYINDENT_STD, - PRETTYINDENT_VAR); - } - } - - /* Add the new item */ - appendStringInfoString(buf, itembuf.data); - - /* clean up */ - pfree(itembuf.data); - } - } -} - -static void -get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) -{ - StringInfo buf = context->buf; - deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces); - - if (IsA(jtnode, RangeTblRef)) - { - int varno = ((RangeTblRef *) jtnode)->rtindex; - RangeTblEntry *rte = rt_fetch(varno, query->rtable); - char *refname = get_rtable_name(varno, context); - deparse_columns *colinfo = deparse_columns_fetch(varno, dpns); - RangeTblFunction *rtfunc1 = NULL; - bool printalias; - CitusRTEKind rteKind = GetRangeTblKind(rte); - - if (rte->lateral) - appendStringInfoString(buf, "LATERAL "); - - /* Print the FROM item proper */ - switch (rte->rtekind) - { - case RTE_RELATION: - /* Normal relation RTE */ - appendStringInfo(buf, "%s%s", - only_marker(rte), - generate_relation_or_shard_name(rte->relid, - context->distrelid, - context->shardid, - context->namespaces)); - break; - case RTE_SUBQUERY: - /* Subquery RTE */ - appendStringInfoChar(buf, '('); - get_query_def(rte->subquery, buf, context->namespaces, NULL, - context->prettyFlags, context->wrapColumn, - context->indentLevel); - appendStringInfoChar(buf, ')'); - break; - case RTE_FUNCTION: - /* if it's a shard, do differently */ - if (GetRangeTblKind(rte) == CITUS_RTE_SHARD) - { - char *fragmentSchemaName = NULL; - char *fragmentTableName = NULL; - - ExtractRangeTblExtraData(rte, NULL, &fragmentSchemaName, &fragmentTableName, NULL); - - /* use schema and table name from the remote alias */ - appendStringInfo(buf, "%s%s", - only_marker(rte), - generate_fragment_name(fragmentSchemaName, - fragmentTableName)); - break; - } - - /* Function RTE */ - rtfunc1 = (RangeTblFunction *) linitial(rte->functions); - - /* - * Omit ROWS FROM() syntax for just one function, unless it - * has both a coldeflist and WITH ORDINALITY. If it has both, - * we must use ROWS FROM() syntax to avoid ambiguity about - * whether the coldeflist includes the ordinality column. - */ - if (list_length(rte->functions) == 1 && - (rtfunc1->funccolnames == NIL || !rte->funcordinality)) - { - get_rule_expr_funccall(rtfunc1->funcexpr, context, true); - /* we'll print the coldeflist below, if it has one */ - } - else - { - bool all_unnest; - ListCell *lc; - - /* - * If all the function calls in the list are to unnest, - * and none need a coldeflist, then collapse the list back - * down to UNNEST(args). (If we had more than one - * built-in unnest function, this would get more - * difficult.) - * - * XXX This is pretty ugly, since it makes not-terribly- - * future-proof assumptions about what the parser would do - * with the output; but the alternative is to emit our - * nonstandard ROWS FROM() notation for what might have - * been a perfectly spec-compliant multi-argument - * UNNEST(). - */ - all_unnest = true; - foreach(lc, rte->functions) - { - RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); - - if (!IsA(rtfunc->funcexpr, FuncExpr) || - ((FuncExpr *) rtfunc->funcexpr)->funcid != F_ARRAY_UNNEST || - rtfunc->funccolnames != NIL) - { - all_unnest = false; - break; - } - } - - if (all_unnest) - { - List *allargs = NIL; - - foreach(lc, rte->functions) - { - RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); - List *args = ((FuncExpr *) rtfunc->funcexpr)->args; - - allargs = list_concat(allargs, args); - } - - appendStringInfoString(buf, "UNNEST("); - get_rule_expr((Node *) allargs, context, true); - appendStringInfoChar(buf, ')'); - } - else - { - int funcno = 0; - - appendStringInfoString(buf, "ROWS FROM("); - foreach(lc, rte->functions) - { - RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); - - if (funcno > 0) - appendStringInfoString(buf, ", "); - get_rule_expr_funccall(rtfunc->funcexpr, context, true); - if (rtfunc->funccolnames != NIL) - { - /* Reconstruct the column definition list */ - appendStringInfoString(buf, " AS "); - get_from_clause_coldeflist(rtfunc, - NULL, - context); - } - funcno++; - } - appendStringInfoChar(buf, ')'); - } - /* prevent printing duplicate coldeflist below */ - rtfunc1 = NULL; - } - if (rte->funcordinality) - appendStringInfoString(buf, " WITH ORDINALITY"); - break; - case RTE_TABLEFUNC: - get_tablefunc(rte->tablefunc, context, true); - break; - case RTE_VALUES: - /* Values list RTE */ - appendStringInfoChar(buf, '('); - get_values_def(rte->values_lists, context); - appendStringInfoChar(buf, ')'); - break; - case RTE_CTE: - appendStringInfoString(buf, quote_identifier(rte->ctename)); - break; - default: - elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind); - break; - } - - /* Print the relation alias, if needed */ - printalias = false; - if (rte->alias != NULL) - { - /* Always print alias if user provided one */ - printalias = true; - } - else if (colinfo->printaliases) - { - /* Always print alias if we need to print column aliases */ - printalias = true; - } - else if (rte->rtekind == RTE_RELATION) - { - /* - * No need to print alias if it's same as relation name (this - * would normally be the case, but not if set_rtable_names had to - * resolve a conflict). - */ - if (strcmp(refname, get_relation_name(rte->relid)) != 0) - printalias = true; - } - else if (rte->rtekind == RTE_FUNCTION) - { - /* - * For a function RTE, always print alias. This covers possible - * renaming of the function and/or instability of the - * FigureColname rules for things that aren't simple functions. - * Note we'd need to force it anyway for the columndef list case. - */ - printalias = true; - } - else if (rte->rtekind == RTE_VALUES) - { - /* Alias is syntactically required for VALUES */ - printalias = true; - } - else if (rte->rtekind == RTE_CTE) - { - /* - * No need to print alias if it's same as CTE name (this would - * normally be the case, but not if set_rtable_names had to - * resolve a conflict). - */ - if (strcmp(refname, rte->ctename) != 0) - printalias = true; - } - else if (rte->rtekind == RTE_SUBQUERY) - { - /* subquery requires alias too */ - printalias = true; - } - if (printalias) - appendStringInfo(buf, " %s", quote_identifier(refname)); - - /* Print the column definitions or aliases, if needed */ - if (rtfunc1 && rtfunc1->funccolnames != NIL) - { - /* Reconstruct the columndef list, which is also the aliases */ - get_from_clause_coldeflist(rtfunc1, colinfo, context); - } - else if (GetRangeTblKind(rte) != CITUS_RTE_SHARD || - (rte->alias != NULL && rte->alias->colnames != NIL)) - { - /* Else print column aliases as needed */ - get_column_alias_list(colinfo, context); - } - /* check if column's are given aliases in distributed tables */ - else if (colinfo->parentUsing != NIL) - { - Assert(colinfo->printaliases); - get_column_alias_list(colinfo, context); - } - - /* Tablesample clause must go after any alias */ - if ((rteKind == CITUS_RTE_RELATION || rteKind == CITUS_RTE_SHARD) && - rte->tablesample) - { - get_tablesample_def(rte->tablesample, context); - } - } - else if (IsA(jtnode, JoinExpr)) - { - JoinExpr *j = (JoinExpr *) jtnode; - deparse_columns *colinfo = deparse_columns_fetch(j->rtindex, dpns); - bool need_paren_on_right; - - need_paren_on_right = PRETTY_PAREN(context) && - !IsA(j->rarg, RangeTblRef) && - !(IsA(j->rarg, JoinExpr) &&((JoinExpr *) j->rarg)->alias != NULL); - - if (!PRETTY_PAREN(context) || j->alias != NULL) - appendStringInfoChar(buf, '('); - - get_from_clause_item(j->larg, query, context); - - switch (j->jointype) - { - case JOIN_INNER: - if (j->quals) - appendContextKeyword(context, " JOIN ", - -PRETTYINDENT_STD, - PRETTYINDENT_STD, - PRETTYINDENT_JOIN); - else - appendContextKeyword(context, " CROSS JOIN ", - -PRETTYINDENT_STD, - PRETTYINDENT_STD, - PRETTYINDENT_JOIN); - break; - case JOIN_LEFT: - appendContextKeyword(context, " LEFT JOIN ", - -PRETTYINDENT_STD, - PRETTYINDENT_STD, - PRETTYINDENT_JOIN); - break; - case JOIN_FULL: - appendContextKeyword(context, " FULL JOIN ", - -PRETTYINDENT_STD, - PRETTYINDENT_STD, - PRETTYINDENT_JOIN); - break; - case JOIN_RIGHT: - appendContextKeyword(context, " RIGHT JOIN ", - -PRETTYINDENT_STD, - PRETTYINDENT_STD, - PRETTYINDENT_JOIN); - break; - default: - elog(ERROR, "unrecognized join type: %d", - (int) j->jointype); - } - - if (need_paren_on_right) - appendStringInfoChar(buf, '('); - get_from_clause_item(j->rarg, query, context); - if (need_paren_on_right) - appendStringInfoChar(buf, ')'); - - if (j->usingClause) - { - ListCell *lc; - bool first = true; - - appendStringInfoString(buf, " USING ("); - /* Use the assigned names, not what's in usingClause */ - foreach(lc, colinfo->usingNames) - { - char *colname = (char *) lfirst(lc); - - if (first) - first = false; - else - appendStringInfoString(buf, ", "); - appendStringInfoString(buf, quote_identifier(colname)); - } - appendStringInfoChar(buf, ')'); - } - else if (j->quals) - { - appendStringInfoString(buf, " ON "); - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, '('); - get_rule_expr(j->quals, context, false); - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, ')'); - } - else if (j->jointype != JOIN_INNER) - { - /* If we didn't say CROSS JOIN above, we must provide an ON */ - appendStringInfoString(buf, " ON TRUE"); - } - - if (!PRETTY_PAREN(context) || j->alias != NULL) - appendStringInfoChar(buf, ')'); - - /* Yes, it's correct to put alias after the right paren ... */ - if (j->alias != NULL) - { - /* - * Note that it's correct to emit an alias clause if and only if - * there was one originally. Otherwise we'd be converting a named - * join to unnamed or vice versa, which creates semantic - * subtleties we don't want. However, we might print a different - * alias name than was there originally. - */ - appendStringInfo(buf, " %s", - quote_identifier(get_rtable_name(j->rtindex, - context))); - get_column_alias_list(colinfo, context); - } - } - else - elog(ERROR, "unrecognized node type: %d", - (int) nodeTag(jtnode)); -} - -/* - * get_column_alias_list - print column alias list for an RTE - * - * Caller must already have printed the relation's alias name. - */ -static void -get_column_alias_list(deparse_columns *colinfo, deparse_context *context) -{ - StringInfo buf = context->buf; - int i; - bool first = true; - - /* Don't print aliases if not needed */ - if (!colinfo->printaliases) - return; - - for (i = 0; i < colinfo->num_new_cols; i++) - { - char *colname = colinfo->new_colnames[i]; - - if (first) - { - appendStringInfoChar(buf, '('); - first = false; - } - else - appendStringInfoString(buf, ", "); - appendStringInfoString(buf, quote_identifier(colname)); - } - if (!first) - appendStringInfoChar(buf, ')'); -} - -/* - * get_from_clause_coldeflist - reproduce FROM clause coldeflist - * - * When printing a top-level coldeflist (which is syntactically also the - * relation's column alias list), use column names from colinfo. But when - * printing a coldeflist embedded inside ROWS FROM(), we prefer to use the - * original coldeflist's names, which are available in rtfunc->funccolnames. - * Pass NULL for colinfo to select the latter behavior. - * - * The coldeflist is appended immediately (no space) to buf. Caller is - * responsible for ensuring that an alias or AS is present before it. - */ -static void -get_from_clause_coldeflist(RangeTblFunction *rtfunc, - deparse_columns *colinfo, - deparse_context *context) -{ - StringInfo buf = context->buf; - ListCell *l1; - ListCell *l2; - ListCell *l3; - ListCell *l4; - int i; - - appendStringInfoChar(buf, '('); - - i = 0; - forfour(l1, rtfunc->funccoltypes, - l2, rtfunc->funccoltypmods, - l3, rtfunc->funccolcollations, - l4, rtfunc->funccolnames) - { - Oid atttypid = lfirst_oid(l1); - int32 atttypmod = lfirst_int(l2); - Oid attcollation = lfirst_oid(l3); - char *attname; - - if (colinfo) - attname = colinfo->colnames[i]; - else - attname = strVal(lfirst(l4)); - - Assert(attname); /* shouldn't be any dropped columns here */ - - if (i > 0) - appendStringInfoString(buf, ", "); - appendStringInfo(buf, "%s %s", - quote_identifier(attname), - format_type_with_typemod(atttypid, atttypmod)); - if (OidIsValid(attcollation) && - attcollation != get_typcollation(atttypid)) - appendStringInfo(buf, " COLLATE %s", - generate_collation_name(attcollation)); - - i++; - } - - appendStringInfoChar(buf, ')'); -} - -/* - * get_tablesample_def - print a TableSampleClause - */ -static void -get_tablesample_def(TableSampleClause *tablesample, deparse_context *context) -{ - StringInfo buf = context->buf; - Oid argtypes[1]; - int nargs; - ListCell *l; - - /* - * We should qualify the handler's function name if it wouldn't be - * resolved by lookup in the current search path. - */ - argtypes[0] = INTERNALOID; - appendStringInfo(buf, " TABLESAMPLE %s (", - generate_function_name(tablesample->tsmhandler, 1, - NIL, argtypes, - false, NULL, EXPR_KIND_NONE)); - - nargs = 0; - foreach(l, tablesample->args) - { - if (nargs++ > 0) - appendStringInfoString(buf, ", "); - get_rule_expr((Node *) lfirst(l), context, false); - } - appendStringInfoChar(buf, ')'); - - if (tablesample->repeatable != NULL) - { - appendStringInfoString(buf, " REPEATABLE ("); - get_rule_expr((Node *) tablesample->repeatable, context, false); - appendStringInfoChar(buf, ')'); - } -} - - -/* - * get_opclass_name - fetch name of an index operator class - * - * The opclass name is appended (after a space) to buf. - * - * Output is suppressed if the opclass is the default for the given - * actual_datatype. (If you don't want this behavior, just pass - * InvalidOid for actual_datatype.) - */ -static void -get_opclass_name(Oid opclass, Oid actual_datatype, - StringInfo buf) -{ - HeapTuple ht_opc; - Form_pg_opclass opcrec; - char *opcname; - char *nspname; - - ht_opc = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass)); - if (!HeapTupleIsValid(ht_opc)) - elog(ERROR, "cache lookup failed for opclass %u", opclass); - opcrec = (Form_pg_opclass) GETSTRUCT(ht_opc); - - if (!OidIsValid(actual_datatype) || - GetDefaultOpClass(actual_datatype, opcrec->opcmethod) != opclass) - { - /* Okay, we need the opclass name. Do we need to qualify it? */ - opcname = NameStr(opcrec->opcname); - if (OpclassIsVisible(opclass)) - appendStringInfo(buf, " %s", quote_identifier(opcname)); - else - { - nspname = get_namespace_name(opcrec->opcnamespace); - appendStringInfo(buf, " %s.%s", - quote_identifier(nspname), - quote_identifier(opcname)); - } - } - ReleaseSysCache(ht_opc); -} - -/* - * processIndirection - take care of array and subfield assignment - * - * We strip any top-level FieldStore or assignment SubscriptingRef nodes that - * appear in the input, printing them as decoration for the base column - * name (which we assume the caller just printed). We might also need to - * strip CoerceToDomain nodes, but only ones that appear above assignment - * nodes. - * - * Returns the subexpression that's to be assigned. - */ -static Node * -processIndirection(Node *node, deparse_context *context) -{ - StringInfo buf = context->buf; - CoerceToDomain *cdomain = NULL; - - for (;;) - { - if (node == NULL) - break; - if (IsA(node, FieldStore)) - { - FieldStore *fstore = (FieldStore *) node; - Oid typrelid; - char *fieldname; - - /* lookup tuple type */ - typrelid = get_typ_typrelid(fstore->resulttype); - if (!OidIsValid(typrelid)) - elog(ERROR, "argument type %s of FieldStore is not a tuple type", - format_type_be(fstore->resulttype)); - - /* - * Print the field name. There should only be one target field in - * stored rules. There could be more than that in executable - * target lists, but this function cannot be used for that case. - */ - Assert(list_length(fstore->fieldnums) == 1); - fieldname = get_attname(typrelid, - linitial_int(fstore->fieldnums), false); - appendStringInfo(buf, ".%s", quote_identifier(fieldname)); - - /* - * We ignore arg since it should be an uninteresting reference to - * the target column or subcolumn. - */ - node = (Node *) linitial(fstore->newvals); - } - else if (IsA(node, SubscriptingRef)) - { - SubscriptingRef *sbsref = (SubscriptingRef *) node; - - if (sbsref->refassgnexpr == NULL) - break; - printSubscripts(sbsref, context); - - /* - * We ignore refexpr since it should be an uninteresting reference - * to the target column or subcolumn. - */ - node = (Node *) sbsref->refassgnexpr; - } - else if (IsA(node, CoerceToDomain)) - { - cdomain = (CoerceToDomain *) node; - /* If it's an explicit domain coercion, we're done */ - if (cdomain->coercionformat != COERCE_IMPLICIT_CAST) - break; - /* Tentatively descend past the CoerceToDomain */ - node = (Node *) cdomain->arg; - } - else - break; - } - - /* - * If we descended past a CoerceToDomain whose argument turned out not to - * be a FieldStore or array assignment, back up to the CoerceToDomain. - * (This is not enough to be fully correct if there are nested implicit - * CoerceToDomains, but such cases shouldn't ever occur.) - */ - if (cdomain && node == (Node *) cdomain->arg) - node = (Node *) cdomain; - - return node; -} - -static void -printSubscripts(SubscriptingRef *sbsref, deparse_context *context) -{ - StringInfo buf = context->buf; - ListCell *lowlist_item; - ListCell *uplist_item; - - lowlist_item = list_head(sbsref->reflowerindexpr); /* could be NULL */ - foreach(uplist_item, sbsref->refupperindexpr) - { - appendStringInfoChar(buf, '['); - if (lowlist_item) - { - /* If subexpression is NULL, get_rule_expr prints nothing */ - get_rule_expr((Node *) lfirst(lowlist_item), context, false); - appendStringInfoChar(buf, ':'); - lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item); - } - /* If subexpression is NULL, get_rule_expr prints nothing */ - get_rule_expr((Node *) lfirst(uplist_item), context, false); - appendStringInfoChar(buf, ']'); - } -} - -/* - * get_relation_name - * Get the unqualified name of a relation specified by OID - * - * This differs from the underlying get_rel_name() function in that it will - * throw error instead of silently returning NULL if the OID is bad. - */ -static char * -get_relation_name(Oid relid) -{ - char *relname = get_rel_name(relid); - - if (!relname) - elog(ERROR, "cache lookup failed for relation %u", relid); - return relname; -} - -/* - * generate_relation_or_shard_name - * Compute the name to display for a relation or shard - * - * If the provided relid is equal to the provided distrelid, this function - * returns a shard-extended relation name; otherwise, it falls through to a - * simple generate_relation_name call. - */ -static char * -generate_relation_or_shard_name(Oid relid, Oid distrelid, int64 shardid, - List *namespaces) -{ - char *relname = NULL; - - if (relid == distrelid) - { - relname = get_relation_name(relid); - - if (shardid > 0) - { - Oid schemaOid = get_rel_namespace(relid); - char *schemaName = get_namespace_name(schemaOid); - - AppendShardIdToName(&relname, shardid); - - relname = quote_qualified_identifier(schemaName, relname); - } - } - else - { - relname = generate_relation_name(relid, namespaces); - } - - return relname; -} - -/* - * generate_relation_name - * Compute the name to display for a relation specified by OID - * - * The result includes all necessary quoting and schema-prefixing. - * - * If namespaces isn't NIL, it must be a list of deparse_namespace nodes. - * We will forcibly qualify the relation name if it equals any CTE name - * visible in the namespace list. - */ -char * -generate_relation_name(Oid relid, List *namespaces) -{ - HeapTuple tp; - Form_pg_class reltup; - bool need_qual; - ListCell *nslist; - char *relname; - char *nspname; - char *result; - - tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); - if (!HeapTupleIsValid(tp)) - elog(ERROR, "cache lookup failed for relation %u", relid); - reltup = (Form_pg_class) GETSTRUCT(tp); - relname = NameStr(reltup->relname); - - /* Check for conflicting CTE name */ - need_qual = false; - foreach(nslist, namespaces) - { - deparse_namespace *dpns = (deparse_namespace *) lfirst(nslist); - ListCell *ctlist; - - foreach(ctlist, dpns->ctes) - { - CommonTableExpr *cte = (CommonTableExpr *) lfirst(ctlist); - - if (strcmp(cte->ctename, relname) == 0) - { - need_qual = true; - break; - } - } - if (need_qual) - break; - } - - /* Otherwise, qualify the name if not visible in search path */ - if (!need_qual) - need_qual = !RelationIsVisible(relid); - - if (need_qual) - nspname = get_namespace_name(reltup->relnamespace); - else - nspname = NULL; - - result = quote_qualified_identifier(nspname, relname); - - ReleaseSysCache(tp); - - return result; -} - - -/* - * generate_rte_shard_name returns the qualified name of the shard given a - * CITUS_RTE_SHARD range table entry. - */ -static char * -generate_rte_shard_name(RangeTblEntry *rangeTableEntry) -{ - char *shardSchemaName = NULL; - char *shardTableName = NULL; - - Assert(GetRangeTblKind(rangeTableEntry) == CITUS_RTE_SHARD); - - ExtractRangeTblExtraData(rangeTableEntry, NULL, &shardSchemaName, &shardTableName, - NULL); - - return generate_fragment_name(shardSchemaName, shardTableName); -} - - -/* - * generate_fragment_name - * Compute the name to display for a shard or merged table - * - * The result includes all necessary quoting and schema-prefixing. The schema - * name can be NULL for regular shards. For merged tables, they are always - * declared within a job-specific schema, and therefore can't have null schema - * names. - */ -static char * -generate_fragment_name(char *schemaName, char *tableName) -{ - StringInfo fragmentNameString = makeStringInfo(); - - if (schemaName != NULL) - { - appendStringInfo(fragmentNameString, "%s.%s", quote_identifier(schemaName), - quote_identifier(tableName)); - } - else - { - appendStringInfoString(fragmentNameString, quote_identifier(tableName)); - } - - return fragmentNameString->data; -} - -/* - * generate_function_name - * Compute the name to display for a function specified by OID, - * given that it is being called with the specified actual arg names and - * types. (Those matter because of ambiguous-function resolution rules.) - * - * If we're dealing with a potentially variadic function (in practice, this - * means a FuncExpr or Aggref, not some other way of calling a function), then - * has_variadic must specify whether variadic arguments have been merged, - * and *use_variadic_p will be set to indicate whether to print VARIADIC in - * the output. For non-FuncExpr cases, has_variadic should be false and - * use_variadic_p can be NULL. - * - * The result includes all necessary quoting and schema-prefixing. - */ -static char * -generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes, - bool has_variadic, bool *use_variadic_p, - ParseExprKind special_exprkind) -{ - char *result; - HeapTuple proctup; - Form_pg_proc procform; - char *proname; - bool use_variadic; - char *nspname; - FuncDetailCode p_result; - Oid p_funcid; - Oid p_rettype; - bool p_retset; - int p_nvargs; - Oid p_vatype; - Oid *p_true_typeids; - bool force_qualify = false; - - proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); - if (!HeapTupleIsValid(proctup)) - elog(ERROR, "cache lookup failed for function %u", funcid); - procform = (Form_pg_proc) GETSTRUCT(proctup); - proname = NameStr(procform->proname); - - /* - * Due to parser hacks to avoid needing to reserve CUBE, we need to force - * qualification in some special cases. - */ - if (special_exprkind == EXPR_KIND_GROUP_BY) - { - if (strcmp(proname, "cube") == 0 || strcmp(proname, "rollup") == 0) - force_qualify = true; - } - - /* - * Determine whether VARIADIC should be printed. We must do this first - * since it affects the lookup rules in func_get_detail(). - * - * Currently, we always print VARIADIC if the function has a merged - * variadic-array argument. Note that this is always the case for - * functions taking a VARIADIC argument type other than VARIADIC ANY. - * - * In principle, if VARIADIC wasn't originally specified and the array - * actual argument is deconstructable, we could print the array elements - * separately and not print VARIADIC, thus more nearly reproducing the - * original input. For the moment that seems like too much complication - * for the benefit, and anyway we do not know whether VARIADIC was - * originally specified if it's a non-ANY type. - */ - if (use_variadic_p) - { - /* Parser should not have set funcvariadic unless fn is variadic */ - Assert(!has_variadic || OidIsValid(procform->provariadic)); - use_variadic = has_variadic; - *use_variadic_p = use_variadic; - } - else - { - Assert(!has_variadic); - use_variadic = false; - } - - /* - * The idea here is to schema-qualify only if the parser would fail to - * resolve the correct function given the unqualified func name with the - * specified argtypes and VARIADIC flag. But if we already decided to - * force qualification, then we can skip the lookup and pretend we didn't - * find it. - */ - if (!force_qualify) - p_result = func_get_detail(list_make1(makeString(proname)), - NIL, argnames, nargs, argtypes, - !use_variadic, true, - &p_funcid, &p_rettype, - &p_retset, &p_nvargs, &p_vatype, - &p_true_typeids, NULL); - else - { - p_result = FUNCDETAIL_NOTFOUND; - p_funcid = InvalidOid; - } - - if ((p_result == FUNCDETAIL_NORMAL || - p_result == FUNCDETAIL_AGGREGATE || - p_result == FUNCDETAIL_WINDOWFUNC) && - p_funcid == funcid) - nspname = NULL; - else - nspname = get_namespace_name(procform->pronamespace); - - result = quote_qualified_identifier(nspname, proname); - - ReleaseSysCache(proctup); - - return result; -} - -/* - * generate_operator_name - * Compute the name to display for an operator specified by OID, - * given that it is being called with the specified actual arg types. - * (Arg types matter because of ambiguous-operator resolution rules. - * Pass InvalidOid for unused arg of a unary operator.) - * - * The result includes all necessary quoting and schema-prefixing, - * plus the OPERATOR() decoration needed to use a qualified operator name - * in an expression. - */ -char * -generate_operator_name(Oid operid, Oid arg1, Oid arg2) -{ - StringInfoData buf; - HeapTuple opertup; - Form_pg_operator operform; - char *oprname; - char *nspname; - - initStringInfo(&buf); - - opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operid)); - if (!HeapTupleIsValid(opertup)) - elog(ERROR, "cache lookup failed for operator %u", operid); - operform = (Form_pg_operator) GETSTRUCT(opertup); - oprname = NameStr(operform->oprname); - - /* - * Unlike generate_operator_name() in postgres/src/backend/utils/adt/ruleutils.c, - * we don't check if the operator is in current namespace or not. This is - * because this check is costly when the operator is not in current namespace. - */ - nspname = get_namespace_name(operform->oprnamespace); - Assert(nspname != NULL); - appendStringInfo(&buf, "OPERATOR(%s.", quote_identifier(nspname)); - appendStringInfoString(&buf, oprname); - appendStringInfoChar(&buf, ')'); - - ReleaseSysCache(opertup); - - return buf.data; -} - -/* - * get_one_range_partition_bound_string - * A C string representation of one range partition bound - */ -char * -get_range_partbound_string(List *bound_datums) -{ - deparse_context context; - StringInfo buf = makeStringInfo(); - ListCell *cell; - char *sep; - - memset(&context, 0, sizeof(deparse_context)); - context.buf = buf; - - appendStringInfoString(buf, "("); - sep = ""; - foreach(cell, bound_datums) - { - PartitionRangeDatum *datum = - castNode(PartitionRangeDatum, lfirst(cell)); - - appendStringInfoString(buf, sep); - if (datum->kind == PARTITION_RANGE_DATUM_MINVALUE) - appendStringInfoString(buf, "MINVALUE"); - else if (datum->kind == PARTITION_RANGE_DATUM_MAXVALUE) - appendStringInfoString(buf, "MAXVALUE"); - else - { - Const *val = castNode(Const, datum->value); - - get_const_expr(val, &context, -1); - } - sep = ", "; - } - appendStringInfoChar(buf, ')'); - - return buf->data; -} - -/* - * Collect a list of OIDs of all sequences owned by the specified relation, - * and column if specified. If deptype is not zero, then only find sequences - * with the specified dependency type. - */ -List * -getOwnedSequences_internal(Oid relid, AttrNumber attnum, char deptype) -{ - List *result = NIL; - Relation depRel; - ScanKeyData key[3]; - SysScanDesc scan; - HeapTuple tup; - - depRel = table_open(DependRelationId, AccessShareLock); - - ScanKeyInit(&key[0], - Anum_pg_depend_refclassid, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(RelationRelationId)); - ScanKeyInit(&key[1], - Anum_pg_depend_refobjid, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(relid)); - if (attnum) - ScanKeyInit(&key[2], - Anum_pg_depend_refobjsubid, - BTEqualStrategyNumber, F_INT4EQ, - Int32GetDatum(attnum)); - - scan = systable_beginscan(depRel, DependReferenceIndexId, true, - NULL, attnum ? 3 : 2, key); - - while (HeapTupleIsValid(tup = systable_getnext(scan))) - { - Form_pg_depend deprec = (Form_pg_depend) GETSTRUCT(tup); - - /* - * We assume any auto or internal dependency of a sequence on a column - * must be what we are looking for. (We need the relkind test because - * indexes can also have auto dependencies on columns.) - */ - if (deprec->classid == RelationRelationId && - deprec->objsubid == 0 && - deprec->refobjsubid != 0 && - (deprec->deptype == DEPENDENCY_AUTO || deprec->deptype == DEPENDENCY_INTERNAL) && - get_rel_relkind(deprec->objid) == RELKIND_SEQUENCE) - { - if (!deptype || deprec->deptype == deptype) - result = lappend_oid(result, deprec->objid); - } - } - - systable_endscan(scan); - - table_close(depRel, AccessShareLock); - - return result; -} - -#endif /* (PG_VERSION_NUM >= PG_VERSION_13) && (PG_VERSION_NUM < PG_VERSION_14) */ diff --git a/src/backend/distributed/deparser/ruleutils_14.c b/src/backend/distributed/deparser/ruleutils_14.c index 79fae9ac0..b364221d8 100644 --- a/src/backend/distributed/deparser/ruleutils_14.c +++ b/src/backend/distributed/deparser/ruleutils_14.c @@ -5470,42 +5470,20 @@ get_rule_expr(Node *node, deparse_context *context, case T_RelabelType: { RelabelType *relabel = (RelabelType *) node; + Node *arg = (Node *) relabel->arg; - /* - * This is a Citus specific modification - * The planner converts CollateExpr to RelabelType - * and here we convert back. - */ - if (relabel->resultcollid != InvalidOid) + if (relabel->relabelformat == COERCE_IMPLICIT_CAST && + !showimplicit) { - CollateExpr *collate = RelabelTypeToCollateExpr(relabel); - Node *arg = (Node *) collate->arg; - - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, '('); - get_rule_expr_paren(arg, context, showimplicit, node); - appendStringInfo(buf, " COLLATE %s", - generate_collation_name(collate->collOid)); - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, ')'); + /* don't show the implicit cast */ + get_rule_expr_paren(arg, context, false, node); } else { - Node *arg = (Node *) relabel->arg; - - if (relabel->relabelformat == COERCE_IMPLICIT_CAST && - !showimplicit) - { - /* don't show the implicit cast */ - get_rule_expr_paren(arg, context, false, node); - } - else - { - get_coercion_expr(arg, context, - relabel->resulttype, - relabel->resulttypmod, - node); - } + get_coercion_expr(arg, context, + relabel->resulttype, + relabel->resulttypmod, + node); } } break; diff --git a/src/backend/distributed/deparser/ruleutils_15.c b/src/backend/distributed/deparser/ruleutils_15.c index 827492d87..2dded9b01 100644 --- a/src/backend/distributed/deparser/ruleutils_15.c +++ b/src/backend/distributed/deparser/ruleutils_15.c @@ -5696,42 +5696,20 @@ get_rule_expr(Node *node, deparse_context *context, case T_RelabelType: { RelabelType *relabel = (RelabelType *) node; + Node *arg = (Node *) relabel->arg; - /* - * This is a Citus specific modification - * The planner converts CollateExpr to RelabelType - * and here we convert back. - */ - if (relabel->resultcollid != InvalidOid) + if (relabel->relabelformat == COERCE_IMPLICIT_CAST && + !showimplicit) { - CollateExpr *collate = RelabelTypeToCollateExpr(relabel); - Node *arg = (Node *) collate->arg; - - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, '('); - get_rule_expr_paren(arg, context, showimplicit, node); - appendStringInfo(buf, " COLLATE %s", - generate_collation_name(collate->collOid)); - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, ')'); + /* don't show the implicit cast */ + get_rule_expr_paren(arg, context, false, node); } else { - Node *arg = (Node *) relabel->arg; - - if (relabel->relabelformat == COERCE_IMPLICIT_CAST && - !showimplicit) - { - /* don't show the implicit cast */ - get_rule_expr_paren(arg, context, false, node); - } - else - { - get_coercion_expr(arg, context, - relabel->resulttype, - relabel->resulttypmod, - node); - } + get_coercion_expr(arg, context, + relabel->resulttype, + relabel->resulttypmod, + node); } } break; diff --git a/src/backend/distributed/executor/adaptive_executor.c b/src/backend/distributed/executor/adaptive_executor.c index 4bb2d5e57..039475735 100644 --- a/src/backend/distributed/executor/adaptive_executor.c +++ b/src/backend/distributed/executor/adaptive_executor.c @@ -496,11 +496,7 @@ struct TaskPlacementExecution; /* GUC, determining whether Citus opens 1 connection per task */ bool ForceMaxQueryParallelization = false; int MaxAdaptiveExecutorPoolSize = 16; -#if PG_VERSION_NUM >= PG_VERSION_14 bool EnableBinaryProtocol = true; -#else -bool EnableBinaryProtocol = false; -#endif /* GUC, number of ms to wait between opening connections to the same worker */ int ExecutorSlowStartInterval = 10; @@ -805,6 +801,8 @@ AdaptiveExecutor(CitusScanState *scanState) TupleDestination *defaultTupleDest = CreateTupleStoreTupleDest(scanState->tuplestorestate, tupleDescriptor); + bool localExecutionSupported = true; + if (RequestedForExplainAnalyze(scanState)) { /* @@ -814,6 +812,12 @@ AdaptiveExecutor(CitusScanState *scanState) UseCoordinatedTransaction(); taskList = ExplainAnalyzeTaskList(taskList, defaultTupleDest, tupleDescriptor, paramListInfo); + + /* + * Multiple queries per task is not supported with local execution. See the Assert in + * TupleDestDestReceiverReceive. + */ + localExecutionSupported = false; } bool hasDependentJobs = job->dependentJobList != NIL; @@ -836,8 +840,6 @@ AdaptiveExecutor(CitusScanState *scanState) TransactionProperties xactProperties = DecideTransactionPropertiesForTaskList( distributedPlan->modLevel, taskList, excludeFromXact); - bool localExecutionSupported = true; - /* * In some rare cases, we have prepared statements that pass a parameter * and never used in the query, mark such parameters' type as Invalid(0), @@ -1009,6 +1011,32 @@ ExecuteTaskListOutsideTransaction(RowModifyLevel modLevel, List *taskList, } +/* + * ExecuteTaskListIntoTupleDestWithParam is a proxy to ExecuteTaskListExtended() which uses + * bind params from executor state, and with defaults for some of the arguments. + */ +uint64 +ExecuteTaskListIntoTupleDestWithParam(RowModifyLevel modLevel, List *taskList, + TupleDestination *tupleDest, + bool expectResults, + ParamListInfo paramListInfo) +{ + int targetPoolSize = MaxAdaptiveExecutorPoolSize; + bool localExecutionSupported = true; + ExecutionParams *executionParams = CreateBasicExecutionParams( + modLevel, taskList, targetPoolSize, localExecutionSupported + ); + + executionParams->xactProperties = DecideTransactionPropertiesForTaskList( + modLevel, taskList, false); + executionParams->expectResults = expectResults; + executionParams->tupleDestination = tupleDest; + executionParams->paramListInfo = paramListInfo; + + return ExecuteTaskListExtended(executionParams); +} + + /* * ExecuteTaskListIntoTupleDest is a proxy to ExecuteTaskListExtended() with defaults * for some of the arguments. @@ -1040,7 +1068,12 @@ ExecuteTaskListIntoTupleDest(RowModifyLevel modLevel, List *taskList, uint64 ExecuteTaskListExtended(ExecutionParams *executionParams) { - ParamListInfo paramListInfo = NULL; + /* if there are no tasks to execute, we can return early */ + if (list_length(executionParams->taskList) == 0) + { + return 0; + } + uint64 locallyProcessedRows = 0; TupleDestination *defaultTupleDest = executionParams->tupleDestination; @@ -1053,7 +1086,7 @@ ExecuteTaskListExtended(ExecutionParams *executionParams) DistributedExecution *execution = CreateDistributedExecution( executionParams->modLevel, executionParams->taskList, - paramListInfo, executionParams->targetPoolSize, + executionParams->paramListInfo, executionParams->targetPoolSize, defaultTupleDest, &executionParams->xactProperties, executionParams->jobIdList, executionParams->localExecutionSupported); @@ -1105,6 +1138,7 @@ CreateBasicExecutionParams(RowModifyLevel modLevel, executionParams->expectResults = false; executionParams->isUtilityCommand = false; executionParams->jobIdList = NIL; + executionParams->paramListInfo = NULL; return executionParams; } diff --git a/src/backend/distributed/executor/citus_custom_scan.c b/src/backend/distributed/executor/citus_custom_scan.c index 28486f23d..a2a2ff6cb 100644 --- a/src/backend/distributed/executor/citus_custom_scan.c +++ b/src/backend/distributed/executor/citus_custom_scan.c @@ -27,6 +27,8 @@ #include "distributed/listutils.h" #include "distributed/local_executor.h" #include "distributed/local_plan_cache.h" +#include "distributed/merge_executor.h" +#include "distributed/merge_planner.h" #include "distributed/multi_executor.h" #include "distributed/multi_server_executor.h" #include "distributed/multi_router_planner.h" @@ -53,6 +55,7 @@ extern AllowedDistributionColumn AllowedDistributionColumnValue; static Node * AdaptiveExecutorCreateScan(CustomScan *scan); static Node * NonPushableInsertSelectCreateScan(CustomScan *scan); static Node * DelayedErrorCreateScan(CustomScan *scan); +static Node * NonPushableMergeCommandCreateScan(CustomScan *scan); /* functions that are common to different scans */ static void CitusBeginScan(CustomScanState *node, EState *estate, int eflags); @@ -88,6 +91,11 @@ CustomScanMethods DelayedErrorCustomScanMethods = { DelayedErrorCreateScan }; +CustomScanMethods NonPushableMergeCommandCustomScanMethods = { + "Citus MERGE INTO ...", + NonPushableMergeCommandCreateScan +}; + /* * Define executor methods for the different executor types. @@ -111,6 +119,16 @@ static CustomExecMethods NonPushableInsertSelectCustomExecMethods = { }; +static CustomExecMethods NonPushableMergeCommandCustomExecMethods = { + .CustomName = "NonPushableMergeCommandScan", + .BeginCustomScan = CitusBeginScan, + .ExecCustomScan = NonPushableMergeCommandExecScan, + .EndCustomScan = CitusEndScan, + .ReScanCustomScan = CitusReScan, + .ExplainCustomScan = NonPushableMergeCommandExplainScan +}; + + /* * IsCitusCustomState returns if a given PlanState node is a CitusCustomState node. */ @@ -124,7 +142,8 @@ IsCitusCustomState(PlanState *planState) CustomScanState *css = castNode(CustomScanState, planState); if (css->methods == &AdaptiveExecutorCustomExecMethods || - css->methods == &NonPushableInsertSelectCustomExecMethods) + css->methods == &NonPushableInsertSelectCustomExecMethods || + css->methods == &NonPushableMergeCommandCustomExecMethods) { return true; } @@ -142,6 +161,7 @@ RegisterCitusCustomScanMethods(void) RegisterCustomScanMethods(&AdaptiveExecutorCustomScanMethods); RegisterCustomScanMethods(&NonPushableInsertSelectCustomScanMethods); RegisterCustomScanMethods(&DelayedErrorCustomScanMethods); + RegisterCustomScanMethods(&NonPushableMergeCommandCustomScanMethods); } @@ -182,7 +202,7 @@ CitusBeginScan(CustomScanState *node, EState *estate, int eflags) node->ss.ps.qual = ExecInitQual(node->ss.ps.plan->qual, (PlanState *) node); DistributedPlan *distributedPlan = scanState->distributedPlan; - if (distributedPlan->insertSelectQuery != NULL) + if (distributedPlan->modifyQueryViaCoordinatorOrRepartition != NULL) { /* * INSERT..SELECT via coordinator or re-partitioning are special because @@ -723,6 +743,26 @@ DelayedErrorCreateScan(CustomScan *scan) } +/* + * NonPushableMergeCommandCreateScan creates the scan state for executing + * MERGE INTO ... into a distributed table with repartition of source rows. + */ +static Node * +NonPushableMergeCommandCreateScan(CustomScan *scan) +{ + CitusScanState *scanState = palloc0(sizeof(CitusScanState)); + + scanState->executorType = MULTI_EXECUTOR_NON_PUSHABLE_MERGE_QUERY; + scanState->customScanState.ss.ps.type = T_CustomScanState; + scanState->distributedPlan = GetDistributedPlan(scan); + scanState->customScanState.methods = &NonPushableMergeCommandCustomExecMethods; + scanState->finishedPreScan = false; + scanState->finishedRemoteScan = false; + + return (Node *) scanState; +} + + /* * CitusEndScan is used to clean up tuple store of the given custom scan state. */ @@ -780,7 +820,19 @@ CitusEndScan(CustomScanState *node) */ static void CitusReScan(CustomScanState *node) -{ } +{ + if (node->ss.ps.ps_ResultTupleSlot) + { + ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); + } + ExecScanReScan(&node->ss); + + CitusScanState *scanState = (CitusScanState *) node; + if (scanState->tuplestorestate) + { + tuplestore_rescan(scanState->tuplestorestate); + } +} /* @@ -896,12 +948,6 @@ SetJobColocationId(Job *job) { uint32 jobColocationId = INVALID_COLOCATION_ID; - if (!job->partitionKeyValue) - { - /* if the Job has no shard key, nothing to do */ - return; - } - List *rangeTableList = ExtractRangeTableEntryList(job->jobQuery); ListCell *rangeTableCell = NULL; foreach(rangeTableCell, rangeTableList) diff --git a/src/backend/distributed/executor/distributed_intermediate_results.c b/src/backend/distributed/executor/distributed_intermediate_results.c index e7a5830e6..c10303e18 100644 --- a/src/backend/distributed/executor/distributed_intermediate_results.c +++ b/src/backend/distributed/executor/distributed_intermediate_results.c @@ -610,6 +610,18 @@ QueryStringForFragmentsTransfer(NodeToNodeFragmentsTransfer *fragmentsTransfer) StringInfo fragmentNamesArrayString = makeStringInfo(); int fragmentCount = 0; NodePair *nodePair = &fragmentsTransfer->nodes; + uint32 sourceNodeId = nodePair->sourceNodeId; + + /* + * If the placement is dummy, for example, queries that generate + * intermediate results at the coordinator that need to be redistributed + * to worker nodes, we need the local id. + */ + if (sourceNodeId == LOCAL_NODE_ID) + { + nodePair->sourceNodeId = GetLocalNodeId(); + } + WorkerNode *sourceNode = LookupNodeByNodeIdOrError(nodePair->sourceNodeId); appendStringInfoString(fragmentNamesArrayString, "ARRAY["); diff --git a/src/backend/distributed/executor/insert_select_executor.c b/src/backend/distributed/executor/insert_select_executor.c index a69ae0f22..4a15289e6 100644 --- a/src/backend/distributed/executor/insert_select_executor.c +++ b/src/backend/distributed/executor/insert_select_executor.c @@ -20,6 +20,7 @@ #include "distributed/insert_select_planner.h" #include "distributed/intermediate_results.h" #include "distributed/local_executor.h" +#include "distributed/merge_planner.h" #include "distributed/multi_executor.h" #include "distributed/multi_partitioning_utils.h" #include "distributed/multi_physical_planner.h" @@ -30,6 +31,7 @@ #include "distributed/distributed_planner.h" #include "distributed/recursive_planning.h" #include "distributed/relation_access_tracking.h" +#include "distributed/repartition_executor.h" #include "distributed/resource_lock.h" #include "distributed/shardinterval_utils.h" #include "distributed/subplan_execution.h" @@ -55,8 +57,6 @@ bool EnableRepartitionedInsertSelect = true; -static List * TwoPhaseInsertSelectTaskList(Oid targetRelationId, Query *insertSelectQuery, - char *resultIdPrefix); static void ExecutePlanIntoRelation(Oid targetRelationId, List *insertTargetList, PlannedStmt *selectPlan, EState *executorState); static HTAB * ExecutePlanIntoColocatedIntermediateResults(Oid targetRelationId, @@ -64,14 +64,7 @@ static HTAB * ExecutePlanIntoColocatedIntermediateResults(Oid targetRelationId, PlannedStmt *selectPlan, EState *executorState, char *intermediateResultIdPrefix); -static List * BuildColumnNameListFromTargetList(Oid targetRelationId, - List *insertTargetList); static int PartitionColumnIndexFromColumnList(Oid relationId, List *columnNameList); -static List * RedistributedInsertSelectTaskList(Query *insertSelectQuery, - CitusTableCacheEntry *targetRelation, - List **redistributedResults, - bool useBinaryFormat); -static int PartitionColumnIndex(List *insertTargetList, Var *partitionColumn); static void WrapTaskListForProjection(List *taskList, List *projectedTargetEntries); @@ -89,7 +82,8 @@ NonPushableInsertSelectExecScan(CustomScanState *node) { EState *executorState = ScanStateGetExecutorState(scanState); DistributedPlan *distributedPlan = scanState->distributedPlan; - Query *insertSelectQuery = copyObject(distributedPlan->insertSelectQuery); + Query *insertSelectQuery = + copyObject(distributedPlan->modifyQueryViaCoordinatorOrRepartition); List *insertTargetList = insertSelectQuery->targetList; RangeTblEntry *selectRte = ExtractSelectRangeTableEntry(insertSelectQuery); RangeTblEntry *insertRte = ExtractResultRelationRTE(insertSelectQuery); @@ -99,7 +93,8 @@ NonPushableInsertSelectExecScan(CustomScanState *node) HTAB *shardStateHash = NULL; Query *selectQuery = selectRte->subquery; - PlannedStmt *selectPlan = copyObject(distributedPlan->selectPlanForInsertSelect); + PlannedStmt *selectPlan = + copyObject(distributedPlan->selectPlanForModifyViaCoordinatorOrRepartition); /* * If we are dealing with partitioned table, we also need to lock its @@ -111,7 +106,7 @@ NonPushableInsertSelectExecScan(CustomScanState *node) LockPartitionRelations(targetRelationId, RowExclusiveLock); } - if (distributedPlan->insertSelectMethod == INSERT_SELECT_REPARTITION) + if (distributedPlan->modifyWithSelectMethod == MODIFY_WITH_SELECT_REPARTITION) { ereport(DEBUG1, (errmsg("performing repartitioned INSERT ... SELECT"))); @@ -142,9 +137,10 @@ NonPushableInsertSelectExecScan(CustomScanState *node) CitusTableCacheEntry *targetRelation = GetCitusTableCacheEntry(targetRelationId); - int partitionColumnIndex = - PartitionColumnIndex(insertTargetList, targetRelation->partitionColumn); - if (partitionColumnIndex == -1) + int distributionColumnIndex = + DistributionColumnIndex(insertTargetList, + targetRelation->partitionColumn); + if (distributionColumnIndex == -1) { char *relationName = get_rel_name(targetRelationId); Oid schemaOid = get_rel_namespace(targetRelationId); @@ -158,13 +154,13 @@ NonPushableInsertSelectExecScan(CustomScanState *node) } TargetEntry *selectPartitionTE = list_nth(selectQuery->targetList, - partitionColumnIndex); + distributionColumnIndex); const char *partitionColumnName = selectPartitionTE->resname ? selectPartitionTE->resname : "(none)"; ereport(DEBUG2, (errmsg( "partitioning SELECT query by column index %d with name %s", - partitionColumnIndex, quote_literal_cstr( + distributionColumnIndex, quote_literal_cstr( partitionColumnName)))); /* @@ -182,7 +178,7 @@ NonPushableInsertSelectExecScan(CustomScanState *node) List **redistributedResults = RedistributeTaskListResults(distResultPrefix, distSelectTaskList, - partitionColumnIndex, + distributionColumnIndex, targetRelation, binaryFormat); @@ -192,10 +188,10 @@ NonPushableInsertSelectExecScan(CustomScanState *node) * target shard. Create and execute a list of tasks of form * INSERT INTO ... SELECT * FROM read_intermediate_results(...); */ - List *taskList = RedistributedInsertSelectTaskList(insertSelectQuery, - targetRelation, - redistributedResults, - binaryFormat); + List *taskList = GenerateTaskListWithRedistributedResults(insertSelectQuery, + targetRelation, + redistributedResults, + binaryFormat); scanState->tuplestorestate = tuplestore_begin_heap(randomAccess, interTransactions, work_mem); @@ -235,9 +231,10 @@ NonPushableInsertSelectExecScan(CustomScanState *node) intermediateResultIdPrefix); /* generate tasks for the INSERT..SELECT phase */ - List *taskList = TwoPhaseInsertSelectTaskList(targetRelationId, - insertSelectQuery, - intermediateResultIdPrefix); + List *taskList = + GenerateTaskListWithColocatedIntermediateResults( + targetRelationId, insertSelectQuery, + intermediateResultIdPrefix); /* * We cannot actually execute INSERT...SELECT tasks that read from @@ -298,94 +295,6 @@ NonPushableInsertSelectExecScan(CustomScanState *node) } -/* - * TwoPhaseInsertSelectTaskList generates a list of tasks for a query that - * inserts into a target relation and selects from a set of co-located - * intermediate results. - */ -static List * -TwoPhaseInsertSelectTaskList(Oid targetRelationId, Query *insertSelectQuery, - char *resultIdPrefix) -{ - List *taskList = NIL; - - /* - * Make a copy of the INSERT ... SELECT. We'll repeatedly replace the - * subquery of insertResultQuery for different intermediate results and - * then deparse it. - */ - Query *insertResultQuery = copyObject(insertSelectQuery); - RangeTblEntry *insertRte = ExtractResultRelationRTE(insertResultQuery); - RangeTblEntry *selectRte = ExtractSelectRangeTableEntry(insertResultQuery); - - CitusTableCacheEntry *targetCacheEntry = GetCitusTableCacheEntry(targetRelationId); - int shardCount = targetCacheEntry->shardIntervalArrayLength; - uint32 taskIdIndex = 1; - uint64 jobId = INVALID_JOB_ID; - - for (int shardOffset = 0; shardOffset < shardCount; shardOffset++) - { - ShardInterval *targetShardInterval = - targetCacheEntry->sortedShardIntervalArray[shardOffset]; - uint64 shardId = targetShardInterval->shardId; - List *columnAliasList = NIL; - StringInfo queryString = makeStringInfo(); - StringInfo resultId = makeStringInfo(); - - /* during COPY, the shard ID is appended to the result name */ - appendStringInfo(resultId, "%s_" UINT64_FORMAT, resultIdPrefix, shardId); - - /* generate the query on the intermediate result */ - Query *resultSelectQuery = BuildSubPlanResultQuery(insertSelectQuery->targetList, - columnAliasList, - resultId->data); - - /* put the intermediate result query in the INSERT..SELECT */ - selectRte->subquery = resultSelectQuery; - - /* setting an alias simplifies deparsing of RETURNING */ - if (insertRte->alias == NULL) - { - Alias *alias = makeAlias(CITUS_TABLE_ALIAS, NIL); - insertRte->alias = alias; - } - - /* - * Generate a query string for the query that inserts into a shard and reads - * from an intermediate result. - * - * Since CTEs have already been converted to intermediate results, they need - * to removed from the query. Otherwise, worker queries include both - * intermediate results and CTEs in the query. - */ - insertResultQuery->cteList = NIL; - deparse_shard_query(insertResultQuery, targetRelationId, shardId, queryString); - ereport(DEBUG2, (errmsg("distributed statement: %s", queryString->data))); - - LockShardDistributionMetadata(shardId, ShareLock); - List *insertShardPlacementList = ActiveShardPlacementList(shardId); - - RelationShard *relationShard = CitusMakeNode(RelationShard); - relationShard->relationId = targetShardInterval->relationId; - relationShard->shardId = targetShardInterval->shardId; - - Task *modifyTask = CreateBasicTask(jobId, taskIdIndex, MODIFY_TASK, - queryString->data); - modifyTask->dependentTaskList = NIL; - modifyTask->anchorShardId = shardId; - modifyTask->taskPlacementList = insertShardPlacementList; - modifyTask->relationShardList = list_make1(relationShard); - modifyTask->replicationModel = targetCacheEntry->replicationModel; - - taskList = lappend(taskList, modifyTask); - - taskIdIndex++; - } - - return taskList; -} - - /* * ExecutePlanIntoColocatedIntermediateResults executes the given PlannedStmt * and inserts tuples into a set of intermediate results that are colocated with @@ -464,7 +373,7 @@ ExecutePlanIntoRelation(Oid targetRelationId, List *insertTargetList, * BuildColumnNameListForCopyStatement build the column name list given the insert * target list. */ -static List * +List * BuildColumnNameListFromTargetList(Oid targetRelationId, List *insertTargetList) { List *columnNameList = NIL; @@ -510,136 +419,17 @@ PartitionColumnIndexFromColumnList(Oid relationId, List *columnNameList) /* - * IsSupportedRedistributionTarget determines whether re-partitioning into the - * given target relation is supported. - */ -bool -IsSupportedRedistributionTarget(Oid targetRelationId) -{ - CitusTableCacheEntry *tableEntry = GetCitusTableCacheEntry(targetRelationId); - - if (!IsCitusTableTypeCacheEntry(tableEntry, HASH_DISTRIBUTED) && - !IsCitusTableTypeCacheEntry(tableEntry, RANGE_DISTRIBUTED)) - { - return false; - } - - return true; -} - - -/* - * RedistributedInsertSelectTaskList returns a task list to insert given - * redistributedResults into the given target relation. - * redistributedResults[shardIndex] is list of cstrings each of which is - * a result name which should be inserted into - * targetRelation->sortedShardIntervalArray[shardIndex]. - */ -static List * -RedistributedInsertSelectTaskList(Query *insertSelectQuery, - CitusTableCacheEntry *targetRelation, - List **redistributedResults, - bool useBinaryFormat) -{ - List *taskList = NIL; - - /* - * Make a copy of the INSERT ... SELECT. We'll repeatedly replace the - * subquery of insertResultQuery for different intermediate results and - * then deparse it. - */ - Query *insertResultQuery = copyObject(insertSelectQuery); - RangeTblEntry *insertRte = ExtractResultRelationRTE(insertResultQuery); - RangeTblEntry *selectRte = ExtractSelectRangeTableEntry(insertResultQuery); - List *selectTargetList = selectRte->subquery->targetList; - Oid targetRelationId = targetRelation->relationId; - - int shardCount = targetRelation->shardIntervalArrayLength; - int shardOffset = 0; - uint32 taskIdIndex = 1; - uint64 jobId = INVALID_JOB_ID; - - for (shardOffset = 0; shardOffset < shardCount; shardOffset++) - { - ShardInterval *targetShardInterval = - targetRelation->sortedShardIntervalArray[shardOffset]; - List *resultIdList = redistributedResults[targetShardInterval->shardIndex]; - uint64 shardId = targetShardInterval->shardId; - StringInfo queryString = makeStringInfo(); - - /* skip empty tasks */ - if (resultIdList == NIL) - { - continue; - } - - /* sort result ids for consistent test output */ - List *sortedResultIds = SortList(resultIdList, pg_qsort_strcmp); - - /* generate the query on the intermediate result */ - Query *fragmentSetQuery = BuildReadIntermediateResultsArrayQuery(selectTargetList, - NIL, - sortedResultIds, - useBinaryFormat); - - /* put the intermediate result query in the INSERT..SELECT */ - selectRte->subquery = fragmentSetQuery; - - /* setting an alias simplifies deparsing of RETURNING */ - if (insertRte->alias == NULL) - { - Alias *alias = makeAlias(CITUS_TABLE_ALIAS, NIL); - insertRte->alias = alias; - } - - /* - * Generate a query string for the query that inserts into a shard and reads - * from an intermediate result. - * - * Since CTEs have already been converted to intermediate results, they need - * to removed from the query. Otherwise, worker queries include both - * intermediate results and CTEs in the query. - */ - insertResultQuery->cteList = NIL; - deparse_shard_query(insertResultQuery, targetRelationId, shardId, queryString); - ereport(DEBUG2, (errmsg("distributed statement: %s", queryString->data))); - - LockShardDistributionMetadata(shardId, ShareLock); - List *insertShardPlacementList = ActiveShardPlacementList(shardId); - - RelationShard *relationShard = CitusMakeNode(RelationShard); - relationShard->relationId = targetShardInterval->relationId; - relationShard->shardId = targetShardInterval->shardId; - - Task *modifyTask = CreateBasicTask(jobId, taskIdIndex, MODIFY_TASK, - queryString->data); - modifyTask->dependentTaskList = NIL; - modifyTask->anchorShardId = shardId; - modifyTask->taskPlacementList = insertShardPlacementList; - modifyTask->relationShardList = list_make1(relationShard); - modifyTask->replicationModel = targetRelation->replicationModel; - - taskList = lappend(taskList, modifyTask); - - taskIdIndex++; - } - - return taskList; -} - - -/* - * PartitionColumnIndex finds the index of given partition column in the + * DistributionColumnIndex finds the index of given distribution column in the * given target list. */ -static int -PartitionColumnIndex(List *insertTargetList, Var *partitionColumn) +int +DistributionColumnIndex(List *insertTargetList, Var *distributionColumn) { TargetEntry *insertTargetEntry = NULL; int targetEntryIndex = 0; foreach_ptr(insertTargetEntry, insertTargetList) { - if (insertTargetEntry->resno == partitionColumn->varattno) + if (insertTargetEntry->resno == distributionColumn->varattno) { return targetEntryIndex; } @@ -651,59 +441,6 @@ PartitionColumnIndex(List *insertTargetList, Var *partitionColumn) } -/* - * IsRedistributablePlan returns true if the given plan is a redistrituable plan. - */ -bool -IsRedistributablePlan(Plan *selectPlan) -{ - if (!EnableRepartitionedInsertSelect) - { - return false; - } - - /* don't redistribute if query is not distributed or requires merge on coordinator */ - if (!IsCitusCustomScan(selectPlan)) - { - return false; - } - - DistributedPlan *distSelectPlan = - GetDistributedPlan((CustomScan *) selectPlan); - Job *distSelectJob = distSelectPlan->workerJob; - List *distSelectTaskList = distSelectJob->taskList; - - /* - * Don't use redistribution if only one task. This is to keep the existing - * behaviour for CTEs that the last step is a read_intermediate_result() - * call. It doesn't hurt much in other cases too. - */ - if (list_length(distSelectTaskList) <= 1) - { - return false; - } - - /* don't use redistribution for repartition joins for now */ - if (distSelectJob->dependentJobList != NIL) - { - return false; - } - - if (distSelectPlan->combineQuery != NULL) - { - Query *combineQuery = (Query *) distSelectPlan->combineQuery; - - if (contain_nextval_expression_walker((Node *) combineQuery->targetList, NULL)) - { - /* nextval needs to be evaluated on the coordinator */ - return false; - } - } - - return true; -} - - /* * WrapTaskListForProjection wraps task query string to only select given * projected columns. It modifies the taskList. diff --git a/src/backend/distributed/executor/local_executor.c b/src/backend/distributed/executor/local_executor.c index 7cdc896e3..5661403b9 100644 --- a/src/backend/distributed/executor/local_executor.c +++ b/src/backend/distributed/executor/local_executor.c @@ -84,6 +84,7 @@ #include "distributed/commands/utility_hook.h" #include "distributed/citus_custom_scan.h" #include "distributed/citus_ruleutils.h" +#include "distributed/colocation_utils.h" #include "distributed/query_utils.h" #include "distributed/deparse_shard_query.h" #include "distributed/listutils.h" @@ -129,6 +130,8 @@ static void LogLocalCommand(Task *task); static uint64 LocallyPlanAndExecuteMultipleQueries(List *queryStrings, TupleDestination *tupleDest, Task *task); +static void SetColocationIdAndPartitionKeyValueForTasks(List *taskList, + Job *distributedPlan); static void LocallyExecuteUtilityTask(Task *task); static void ExecuteUdfTaskQuery(Query *localUdfCommandQuery); static void EnsureTransitionPossible(LocalExecutionStatus from, @@ -228,6 +231,17 @@ ExecuteLocalTaskListExtended(List *taskList, EnsureTaskExecutionAllowed(isRemote); } + /* + * If workerJob has a partitionKeyValue, we need to set the colocation id + * and partition key value for each task before we start executing them + * because tenant stats are collected based on these values of a task. + */ + if (distributedPlan != NULL && distributedPlan->workerJob != NULL && taskList != NIL) + { + SetJobColocationId(distributedPlan->workerJob); + SetColocationIdAndPartitionKeyValueForTasks(taskList, distributedPlan->workerJob); + } + /* * Use a new memory context that gets reset after every task to free * the deparsed query string and query plan. @@ -367,6 +381,25 @@ ExecuteLocalTaskListExtended(List *taskList, } +/* + * SetColocationIdAndPartitionKeyValueForTasks sets colocationId and partitionKeyValue + * for the tasks in the taskList. + */ +static void +SetColocationIdAndPartitionKeyValueForTasks(List *taskList, Job *workerJob) +{ + if (workerJob->colocationId != INVALID_COLOCATION_ID) + { + Task *task = NULL; + foreach_ptr(task, taskList) + { + task->colocationId = workerJob->colocationId; + task->partitionKeyValue = workerJob->partitionKeyValue; + } + } +} + + /* * LocallyPlanAndExecuteMultipleQueries plans and executes the given query strings * one by one. diff --git a/src/backend/distributed/executor/merge_executor.c b/src/backend/distributed/executor/merge_executor.c new file mode 100644 index 000000000..f501497c0 --- /dev/null +++ b/src/backend/distributed/executor/merge_executor.c @@ -0,0 +1,337 @@ +/*------------------------------------------------------------------------- + * + * merge_executor.c + * + * Executor logic for MERGE SQL statement. + * + * Copyright (c) Citus Data, Inc. + *------------------------------------------------------------------------- + */ + +#include "postgres.h" +#include "miscadmin.h" + +#include "distributed/distributed_execution_locks.h" +#include "distributed/insert_select_executor.h" +#include "distributed/intermediate_results.h" +#include "distributed/listutils.h" +#include "distributed/merge_executor.h" +#include "distributed/merge_planner.h" +#include "distributed/multi_executor.h" +#include "distributed/multi_partitioning_utils.h" +#include "distributed/multi_router_planner.h" +#include "distributed/repartition_executor.h" +#include "distributed/subplan_execution.h" + +#include "nodes/execnodes.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" + +static void ExecuteSourceAtWorkerAndRepartition(CitusScanState *scanState); +static void ExecuteSourceAtCoordAndRedistribution(CitusScanState *scanState); +static HTAB * ExecuteMergeSourcePlanIntoColocatedIntermediateResults(Oid targetRelationId, + Query *mergeQuery, + List * + sourceTargetList, + PlannedStmt * + sourcePlan, + EState *executorState, + char * + intermediateResultIdPrefix, + int + partitionColumnIndex); + + +/* + * NonPushableMergeCommandExecScan performs an MERGE INTO distributed_table + * USING (source-query) ... command. This can be done either by aggregating + * task results at the coordinator and repartitioning the results, or by + * repartitioning task results and directly transferring data between nodes. + */ +TupleTableSlot * +NonPushableMergeCommandExecScan(CustomScanState *node) +{ + CitusScanState *scanState = (CitusScanState *) node; + DistributedPlan *distributedPlan = scanState->distributedPlan; + + if (!scanState->finishedRemoteScan) + { + switch (distributedPlan->modifyWithSelectMethod) + { + case MODIFY_WITH_SELECT_REPARTITION: + { + ExecuteSourceAtWorkerAndRepartition(scanState); + break; + } + + case MODIFY_WITH_SELECT_VIA_COORDINATOR: + { + ExecuteSourceAtCoordAndRedistribution(scanState); + break; + } + + default: + { + ereport(ERROR, (errmsg("Unexpected MERGE execution method(%d)", + distributedPlan->modifyWithSelectMethod))); + } + } + + scanState->finishedRemoteScan = true; + } + + TupleTableSlot *resultSlot = ReturnTupleFromTuplestore(scanState); + + return resultSlot; +} + + +/* + * ExecuteSourceAtWorkerAndRepartition Executes the Citus distributed plan, including any + * sub-plans, and captures the results in intermediate files. Subsequently, redistributes + * the result files to ensure colocation with the target, and directs the MERGE SQL + * operation to the target shards on the worker nodes, utilizing the colocated + * intermediate files as the data source. + */ +static void +ExecuteSourceAtWorkerAndRepartition(CitusScanState *scanState) +{ + DistributedPlan *distributedPlan = scanState->distributedPlan; + Query *mergeQuery = + copyObject(distributedPlan->modifyQueryViaCoordinatorOrRepartition); + RangeTblEntry *targetRte = ExtractResultRelationRTE(mergeQuery); + RangeTblEntry *sourceRte = ExtractMergeSourceRangeTableEntry(mergeQuery); + Oid targetRelationId = targetRte->relid; + bool hasReturning = distributedPlan->expectResults; + Query *sourceQuery = sourceRte->subquery; + PlannedStmt *sourcePlan = + copyObject(distributedPlan->selectPlanForModifyViaCoordinatorOrRepartition); + EState *executorState = ScanStateGetExecutorState(scanState); + + /* + * If we are dealing with partitioned table, we also need to lock its + * partitions. Here we only lock targetRelation, we acquire necessary + * locks on source tables during execution of those source queries. + */ + if (PartitionedTable(targetRelationId)) + { + LockPartitionRelations(targetRelationId, RowExclusiveLock); + } + + bool randomAccess = true; + bool interTransactions = false; + DistributedPlan *distSourcePlan = + GetDistributedPlan((CustomScan *) sourcePlan->planTree); + Job *distSourceJob = distSourcePlan->workerJob; + List *distSourceTaskList = distSourceJob->taskList; + bool binaryFormat = + CanUseBinaryCopyFormatForTargetList(sourceQuery->targetList); + + ereport(DEBUG1, (errmsg("Executing subplans of the source query and " + "storing the results at the respective node(s)"))); + + ExecuteSubPlans(distSourcePlan); + + /* + * We have a separate directory for each transaction, so choosing + * the same result prefix won't cause filename conflicts. Results + * directory name also includes node id and database id, so we don't + * need to include them in the filename. We include job id here for + * the case "MERGE USING " is executed recursively. + */ + StringInfo distResultPrefixString = makeStringInfo(); + appendStringInfo(distResultPrefixString, + "repartitioned_results_" UINT64_FORMAT, + distSourceJob->jobId); + char *distResultPrefix = distResultPrefixString->data; + CitusTableCacheEntry *targetRelation = GetCitusTableCacheEntry(targetRelationId); + + ereport(DEBUG1, (errmsg("Redistributing source result rows across nodes"))); + + /* + * partitionColumnIndex determines the column in the selectTaskList to + * use for (re)partitioning of the source result, which will colocate + * the result data with the target. + */ + int partitionColumnIndex = distributedPlan->sourceResultRepartitionColumnIndex; + + /* + * Below call partitions the results using shard ranges and partition method of + * targetRelation, and then colocates the result files with shards. These + * transfers are done by calls to fetch_intermediate_results() between nodes. + */ + List **redistributedResults = + RedistributeTaskListResults(distResultPrefix, + distSourceTaskList, partitionColumnIndex, + targetRelation, binaryFormat); + + ereport(DEBUG1, (errmsg("Executing final MERGE on workers using " + "intermediate results"))); + + /* + * At this point source query has been executed on workers and results + * have been fetched in such a way that they are colocated with corresponding + * target shard(s). Create and execute a list of tasks of form + * MERGE INTO ... USING SELECT * FROM read_intermediate_results(...); + */ + List *taskList = + GenerateTaskListWithRedistributedResults(mergeQuery, + targetRelation, + redistributedResults, + binaryFormat); + + scanState->tuplestorestate = + tuplestore_begin_heap(randomAccess, interTransactions, work_mem); + ParamListInfo paramListInfo = executorState->es_param_list_info; + TupleDesc tupleDescriptor = ScanStateGetTupleDescriptor(scanState); + TupleDestination *tupleDest = + CreateTupleStoreTupleDest(scanState->tuplestorestate, + tupleDescriptor); + uint64 rowsMerged = + ExecuteTaskListIntoTupleDestWithParam(ROW_MODIFY_NONCOMMUTATIVE, taskList, + tupleDest, + hasReturning, + paramListInfo); + executorState->es_processed = rowsMerged; +} + + +/* + * ExecuteSourceAtCoordAndRedistribution Executes the plan that necessitates evaluation + * at the coordinator and redistributes the resulting rows to intermediate files, + * ensuring colocation with the target shards. Directs the MERGE SQL operation to the + * target shards on the worker nodes, utilizing the colocated intermediate files as the + * data source. + */ +void +ExecuteSourceAtCoordAndRedistribution(CitusScanState *scanState) +{ + EState *executorState = ScanStateGetExecutorState(scanState); + DistributedPlan *distributedPlan = scanState->distributedPlan; + Query *mergeQuery = + copyObject(distributedPlan->modifyQueryViaCoordinatorOrRepartition); + RangeTblEntry *targetRte = ExtractResultRelationRTE(mergeQuery); + RangeTblEntry *sourceRte = ExtractMergeSourceRangeTableEntry(mergeQuery); + Query *sourceQuery = sourceRte->subquery; + Oid targetRelationId = targetRte->relid; + PlannedStmt *sourcePlan = + copyObject(distributedPlan->selectPlanForModifyViaCoordinatorOrRepartition); + char *intermediateResultIdPrefix = distributedPlan->intermediateResultIdPrefix; + bool hasReturning = distributedPlan->expectResults; + int partitionColumnIndex = distributedPlan->sourceResultRepartitionColumnIndex; + + /* + * If we are dealing with partitioned table, we also need to lock its + * partitions. Here we only lock targetRelation, we acquire necessary + * locks on source tables during execution of those source queries. + */ + if (PartitionedTable(targetRelationId)) + { + LockPartitionRelations(targetRelationId, RowExclusiveLock); + } + + ereport(DEBUG1, (errmsg("Collect source query results on coordinator"))); + + List *prunedTaskList = NIL; + HTAB *shardStateHash = + ExecuteMergeSourcePlanIntoColocatedIntermediateResults( + targetRelationId, + mergeQuery, + sourceQuery->targetList, + sourcePlan, + executorState, + intermediateResultIdPrefix, + partitionColumnIndex); + + ereport(DEBUG1, (errmsg("Create a MERGE task list that needs to be routed"))); + + /* generate tasks for the .. phase */ + List *taskList = + GenerateTaskListWithColocatedIntermediateResults(targetRelationId, mergeQuery, + intermediateResultIdPrefix); + + /* + * We cannot actually execute MERGE INTO ... tasks that read from + * intermediate results that weren't created because no rows were + * written to them. Prune those tasks out by only including tasks + * on shards with connections. + */ + Task *task = NULL; + foreach_ptr(task, taskList) + { + uint64 shardId = task->anchorShardId; + bool shardModified = false; + + hash_search(shardStateHash, &shardId, HASH_FIND, &shardModified); + if (shardModified) + { + prunedTaskList = lappend(prunedTaskList, task); + } + } + + if (prunedTaskList == NIL) + { + /* No task to execute */ + return; + } + + ereport(DEBUG1, (errmsg("Execute MERGE task list"))); + bool randomAccess = true; + bool interTransactions = false; + Assert(scanState->tuplestorestate == NULL); + scanState->tuplestorestate = tuplestore_begin_heap(randomAccess, interTransactions, + work_mem); + TupleDesc tupleDescriptor = ScanStateGetTupleDescriptor(scanState); + ParamListInfo paramListInfo = executorState->es_param_list_info; + TupleDestination *tupleDest = + CreateTupleStoreTupleDest(scanState->tuplestorestate, tupleDescriptor); + uint64 rowsMerged = + ExecuteTaskListIntoTupleDestWithParam(ROW_MODIFY_NONCOMMUTATIVE, + prunedTaskList, + tupleDest, + hasReturning, + paramListInfo); + executorState->es_processed = rowsMerged; +} + + +/* + * ExecuteMergeSourcePlanIntoColocatedIntermediateResults Executes the given PlannedStmt + * and inserts tuples into a set of intermediate results that are colocated with the + * target table for further processing MERGE INTO. It also returns the hash of shard + * states that were used to insert tuplesinto the target relation. + */ +static HTAB * +ExecuteMergeSourcePlanIntoColocatedIntermediateResults(Oid targetRelationId, + Query *mergeQuery, + List *sourceTargetList, + PlannedStmt *sourcePlan, + EState *executorState, + char *intermediateResultIdPrefix, + int partitionColumnIndex) +{ + ParamListInfo paramListInfo = executorState->es_param_list_info; + + /* Get column name list and partition column index for the target table */ + List *columnNameList = + BuildColumnNameListFromTargetList(targetRelationId, sourceTargetList); + + /* set up a DestReceiver that copies into the intermediate file */ + const bool publishableData = false; + CitusCopyDestReceiver *copyDest = CreateCitusCopyDestReceiver(targetRelationId, + columnNameList, + partitionColumnIndex, + executorState, + intermediateResultIdPrefix, + publishableData); + + /* We can skip when writing to intermediate files */ + copyDest->skipCoercions = true; + + ExecutePlanIntoDestReceiver(sourcePlan, paramListInfo, (DestReceiver *) copyDest); + + executorState->es_processed = copyDest->tuplesSent; + XactModificationLevel = XACT_MODIFICATION_DATA; + + return copyDest->shardStateHash; +} diff --git a/src/backend/distributed/executor/multi_executor.c b/src/backend/distributed/executor/multi_executor.c index 04cb39a58..662eaaf97 100644 --- a/src/backend/distributed/executor/multi_executor.c +++ b/src/backend/distributed/executor/multi_executor.c @@ -455,9 +455,9 @@ ReadFileIntoTupleStore(char *fileName, char *copyFormat, TupleDesc tupleDescript location); copyOptions = lappend(copyOptions, copyOption); - CopyFromState copyState = BeginCopyFrom_compat(NULL, stubRelation, NULL, - fileName, false, NULL, - NULL, copyOptions); + CopyFromState copyState = BeginCopyFrom(NULL, stubRelation, NULL, + fileName, false, NULL, + NULL, copyOptions); while (true) { diff --git a/src/backend/distributed/executor/multi_server_executor.c b/src/backend/distributed/executor/multi_server_executor.c index caf6797da..ac144c350 100644 --- a/src/backend/distributed/executor/multi_server_executor.c +++ b/src/backend/distributed/executor/multi_server_executor.c @@ -24,13 +24,14 @@ #include "distributed/multi_executor.h" #include "distributed/multi_physical_planner.h" #include "distributed/multi_server_executor.h" +#include "distributed/multi_router_planner.h" #include "distributed/coordinator_protocol.h" #include "distributed/subplan_execution.h" #include "distributed/tuple_destination.h" #include "distributed/worker_protocol.h" #include "utils/lsyscache.h" -int RemoteTaskCheckInterval = 100; /* per cycle sleep interval in millisecs */ +int RemoteTaskCheckInterval = 10; /* per cycle sleep interval in millisecs */ int TaskExecutorType = MULTI_EXECUTOR_ADAPTIVE; /* distributed executor type */ bool EnableRepartitionJoins = false; @@ -47,8 +48,13 @@ JobExecutorType(DistributedPlan *distributedPlan) { Job *job = distributedPlan->workerJob; - if (distributedPlan->insertSelectQuery != NULL) + if (distributedPlan->modifyQueryViaCoordinatorOrRepartition != NULL) { + if (IsMergeQuery(distributedPlan->modifyQueryViaCoordinatorOrRepartition)) + { + return MULTI_EXECUTOR_NON_PUSHABLE_MERGE_QUERY; + } + /* * We go through * MULTI_EXECUTOR_NON_PUSHABLE_INSERT_SELECT because diff --git a/src/backend/distributed/executor/query_stats.c b/src/backend/distributed/executor/query_stats.c index 6dd5196f2..1ac70489c 100644 --- a/src/backend/distributed/executor/query_stats.c +++ b/src/backend/distributed/executor/query_stats.c @@ -797,11 +797,7 @@ BuildExistingQueryIdHash(void) { const int userIdAttributeNumber = 1; const int dbIdAttributeNumber = 2; -#if PG_VERSION_NUM >= PG_VERSION_14 const int queryIdAttributeNumber = 4; -#else - const int queryIdAttributeNumber = 3; -#endif Datum commandTypeDatum = (Datum) 0; bool missingOK = true; diff --git a/src/backend/distributed/executor/repartition_executor.c b/src/backend/distributed/executor/repartition_executor.c new file mode 100644 index 000000000..af4f0ac7e --- /dev/null +++ b/src/backend/distributed/executor/repartition_executor.c @@ -0,0 +1,301 @@ +/*------------------------------------------------------------------- + * + * repartition_executor.c + * + * Definitions for public functions and types related to repartition + * of select query results. + * + * Copyright (c) Citus Data, Inc. + *------------------------------------------------------------------- + */ + +#include "postgres.h" +#include "miscadmin.h" + +#include "nodes/makefuncs.h" +#include "nodes/parsenodes.h" + +#include "distributed/citus_custom_scan.h" +#include "distributed/intermediate_results.h" +#include "distributed/listutils.h" +#include "distributed/multi_physical_planner.h" +#include "distributed/multi_router_planner.h" +#include "distributed/recursive_planning.h" +#include "distributed/repartition_executor.h" +#include "distributed/resource_lock.h" + + +/* + * IsSupportedRedistributionTarget determines whether re-partitioning into the + * given target relation is supported. + */ +bool +IsSupportedRedistributionTarget(Oid targetRelationId) +{ + CitusTableCacheEntry *tableEntry = GetCitusTableCacheEntry(targetRelationId); + + if (!IsCitusTableTypeCacheEntry(tableEntry, HASH_DISTRIBUTED) && + !IsCitusTableTypeCacheEntry(tableEntry, RANGE_DISTRIBUTED)) + { + return false; + } + + return true; +} + + +/* + * IsRedistributablePlan returns true if the given plan is a distributable plan. + */ +bool +IsRedistributablePlan(Plan *selectPlan) +{ + if (!EnableRepartitionedInsertSelect) + { + return false; + } + + /* + * Don't redistribute if query is not distributed or requires + * merge on coordinator + */ + if (!IsCitusCustomScan(selectPlan)) + { + return false; + } + + DistributedPlan *distSelectPlan = + GetDistributedPlan((CustomScan *) selectPlan); + Job *distSelectJob = distSelectPlan->workerJob; + List *distSelectTaskList = distSelectJob->taskList; + + /* + * Don't use redistribution if only one task. This is to keep the existing + * behaviour for CTEs that the last step is a read_intermediate_result() + * call. It doesn't hurt much in other cases too. + */ + if (list_length(distSelectTaskList) <= 1) + { + return false; + } + + /* don't use redistribution for repartition joins for now */ + if (distSelectJob->dependentJobList != NIL) + { + return false; + } + + if (distSelectPlan->combineQuery != NULL) + { + Query *combineQuery = (Query *) distSelectPlan->combineQuery; + + if (contain_nextval_expression_walker((Node *) combineQuery->targetList, NULL)) + { + /* nextval needs to be evaluated on the coordinator */ + return false; + } + } + + return true; +} + + +/* + * GenerateTaskListWithColocatedIntermediateResults generates a list of tasks + * for a query that inserts into a target relation and selects from a set of + * co-located intermediate results. + */ +List * +GenerateTaskListWithColocatedIntermediateResults(Oid targetRelationId, + Query * + modifyQueryViaCoordinatorOrRepartition, + char *resultIdPrefix) +{ + List *taskList = NIL; + + /* + * Make a copy of the ... SELECT. We'll repeatedly replace + * the subquery of modifyResultQuery for different intermediate results and + * then deparse it. + */ + Query *modifyWithResultQuery = copyObject(modifyQueryViaCoordinatorOrRepartition); + RangeTblEntry *insertRte = ExtractResultRelationRTE(modifyWithResultQuery); + RangeTblEntry *selectRte = ExtractSourceResultRangeTableEntry(modifyWithResultQuery); + + CitusTableCacheEntry *targetCacheEntry = GetCitusTableCacheEntry(targetRelationId); + int shardCount = targetCacheEntry->shardIntervalArrayLength; + uint32 taskIdIndex = 1; + uint64 jobId = INVALID_JOB_ID; + + for (int shardOffset = 0; shardOffset < shardCount; shardOffset++) + { + ShardInterval *targetShardInterval = + targetCacheEntry->sortedShardIntervalArray[shardOffset]; + uint64 shardId = targetShardInterval->shardId; + List *columnAliasList = NIL; + StringInfo queryString = makeStringInfo(); + StringInfo resultId = makeStringInfo(); + + /* during COPY, the shard ID is appended to the result name */ + appendStringInfo(resultId, "%s_" UINT64_FORMAT, resultIdPrefix, shardId); + + /* + * For MERGE SQL, use the USING clause list, the main query target list + * is NULL + */ + List *targetList = IsMergeQuery(modifyQueryViaCoordinatorOrRepartition) ? + selectRte->subquery->targetList : + modifyQueryViaCoordinatorOrRepartition->targetList; + + /* generate the query on the intermediate result */ + Query *resultSelectQuery = BuildSubPlanResultQuery(targetList, + columnAliasList, + resultId->data); + + /* put the intermediate result query in the INSERT..SELECT */ + selectRte->subquery = resultSelectQuery; + + /* setting an alias simplifies deparsing of RETURNING */ + if (insertRte->alias == NULL) + { + Alias *alias = makeAlias(CITUS_TABLE_ALIAS, NIL); + insertRte->alias = alias; + } + + /* + * Generate a query string for the query that inserts into a shard and reads + * from an intermediate result. + * + * Since CTEs have already been converted to intermediate results, they need + * to removed from the query. Otherwise, worker queries include both + * intermediate results and CTEs in the query. + */ + modifyWithResultQuery->cteList = NIL; + deparse_shard_query(modifyWithResultQuery, targetRelationId, shardId, + queryString); + ereport(DEBUG2, (errmsg("distributed statement: %s", queryString->data))); + + LockShardDistributionMetadata(shardId, ShareLock); + List *insertShardPlacementList = ActiveShardPlacementList(shardId); + + RelationShard *relationShard = CitusMakeNode(RelationShard); + relationShard->relationId = targetShardInterval->relationId; + relationShard->shardId = targetShardInterval->shardId; + + Task *modifyTask = CreateBasicTask(jobId, taskIdIndex, MODIFY_TASK, + queryString->data); + modifyTask->dependentTaskList = NIL; + modifyTask->anchorShardId = shardId; + modifyTask->taskPlacementList = insertShardPlacementList; + modifyTask->relationShardList = list_make1(relationShard); + modifyTask->replicationModel = targetCacheEntry->replicationModel; + + taskList = lappend(taskList, modifyTask); + + taskIdIndex++; + } + + return taskList; +} + + +/* + * GenerateTaskListWithRedistributedResults returns a task list to insert given + * redistributedResults into the given target relation. + * redistributedResults[shardIndex] is list of cstrings each of which is + * a result name which should be inserted into + * targetRelation->sortedShardIntervalArray[shardIndex]. + */ +List * +GenerateTaskListWithRedistributedResults(Query *modifyQueryViaCoordinatorOrRepartition, + CitusTableCacheEntry *targetRelation, + List **redistributedResults, bool + useBinaryFormat) +{ + List *taskList = NIL; + + /* + * Make a copy of the ... SELECT. We'll repeatedly replace + * the subquery of modifyResultQuery for different intermediate results and + * then deparse it. + */ + Query *modifyResultQuery = copyObject(modifyQueryViaCoordinatorOrRepartition); + RangeTblEntry *insertRte = ExtractResultRelationRTE(modifyResultQuery); + Oid targetRelationId = targetRelation->relationId; + + int shardCount = targetRelation->shardIntervalArrayLength; + int shardOffset = 0; + uint32 taskIdIndex = 1; + uint64 jobId = INVALID_JOB_ID; + + RangeTblEntry *selectRte = + ExtractSourceResultRangeTableEntry(modifyResultQuery); + List *selectTargetList = selectRte->subquery->targetList; + + for (shardOffset = 0; shardOffset < shardCount; shardOffset++) + { + ShardInterval *targetShardInterval = + targetRelation->sortedShardIntervalArray[shardOffset]; + List *resultIdList = redistributedResults[targetShardInterval->shardIndex]; + uint64 shardId = targetShardInterval->shardId; + StringInfo queryString = makeStringInfo(); + + /* skip empty tasks */ + if (resultIdList == NIL) + { + continue; + } + + /* sort result ids for consistent test output */ + List *sortedResultIds = SortList(resultIdList, pg_qsort_strcmp); + + /* generate the query on the intermediate result */ + Query *fragmentSetQuery = BuildReadIntermediateResultsArrayQuery(selectTargetList, + NIL, + sortedResultIds, + useBinaryFormat); + + /* put the intermediate result query in the INSERT..SELECT */ + selectRte->subquery = fragmentSetQuery; + + /* setting an alias simplifies deparsing of RETURNING */ + if (insertRte->alias == NULL) + { + Alias *alias = makeAlias(CITUS_TABLE_ALIAS, NIL); + insertRte->alias = alias; + } + + /* + * Generate a query string for the query that inserts into a shard and reads + * from an intermediate result. + * + * Since CTEs have already been converted to intermediate results, they need + * to removed from the query. Otherwise, worker queries include both + * intermediate results and CTEs in the query. + */ + modifyResultQuery->cteList = NIL; + deparse_shard_query(modifyResultQuery, targetRelationId, shardId, queryString); + ereport(DEBUG2, (errmsg("distributed statement: %s", queryString->data))); + + LockShardDistributionMetadata(shardId, ShareLock); + List *insertShardPlacementList = ActiveShardPlacementList(shardId); + + RelationShard *relationShard = CitusMakeNode(RelationShard); + relationShard->relationId = targetShardInterval->relationId; + relationShard->shardId = targetShardInterval->shardId; + + Task *modifyTask = CreateBasicTask(jobId, taskIdIndex, MODIFY_TASK, + queryString->data); + modifyTask->dependentTaskList = NIL; + modifyTask->anchorShardId = shardId; + modifyTask->taskPlacementList = insertShardPlacementList; + modifyTask->relationShardList = list_make1(relationShard); + modifyTask->replicationModel = targetRelation->replicationModel; + + taskList = lappend(taskList, modifyTask); + + taskIdIndex++; + } + + return taskList; +} diff --git a/src/backend/distributed/metadata/dependency.c b/src/backend/distributed/metadata/dependency.c index a58e57be7..c307dc737 100644 --- a/src/backend/distributed/metadata/dependency.c +++ b/src/backend/distributed/metadata/dependency.c @@ -896,18 +896,11 @@ DeferErrorIfHasUnsupportedDependency(const ObjectAddress *objectAddress) return NULL; } - char *objectDescription = NULL; - char *dependencyDescription = NULL; StringInfo errorInfo = makeStringInfo(); StringInfo detailInfo = makeStringInfo(); - #if PG_VERSION_NUM >= PG_VERSION_14 - objectDescription = getObjectDescription(objectAddress, false); - dependencyDescription = getObjectDescription(undistributableDependency, false); - #else - objectDescription = getObjectDescription(objectAddress); - dependencyDescription = getObjectDescription(undistributableDependency); - #endif + char *objectDescription = getObjectDescription(objectAddress, false); + char *dependencyDescription = getObjectDescription(undistributableDependency, false); /* * We expect callers to interpret the error returned from this function @@ -1192,6 +1185,47 @@ IsAnyObjectAddressOwnedByExtension(const List *targets, } +/* + * FirstExtensionWithSchema returns the first extension address whose schema is the same + * as given schema. If no extension depends on the schema, it returns NULL. + * i.e. decide if given schema is an extension schema as in + * `CREATE EXTENSION [WITH] SCHEMA ;` + */ +ObjectAddress * +FirstExtensionWithSchema(Oid schemaId) +{ + ObjectAddress *extensionAddress = NULL; + + Relation relation = table_open(ExtensionRelationId, AccessShareLock); + + ScanKeyData entry[1]; + ScanKeyInit(&entry[0], Anum_pg_extension_extnamespace, BTEqualStrategyNumber, + F_INT4EQ, schemaId); + + SysScanDesc scan = systable_beginscan(relation, InvalidOid, false, NULL, 1, entry); + HeapTuple extensionTuple = systable_getnext(scan); + if (HeapTupleIsValid(extensionTuple)) + { + int extensionIdIndex = Anum_pg_extension_oid; + TupleDesc tupleDescriptor = RelationGetDescr(relation); + bool isNull = false; + Datum extensionIdDatum = heap_getattr(extensionTuple, extensionIdIndex, + tupleDescriptor, &isNull); + Oid extensionId = DatumGetObjectId(extensionIdDatum); + + extensionAddress = palloc0(sizeof(ObjectAddress)); + extensionAddress->objectId = extensionId; + extensionAddress->classId = ExtensionRelationId; + extensionAddress->objectSubId = 0; + } + + systable_endscan(scan); + table_close(relation, AccessShareLock); + + return extensionAddress; +} + + /* * IsObjectAddressOwnedByCitus returns true if the given object address * is owned by the citus or citus_columnar extensions. diff --git a/src/backend/distributed/metadata/distobject.c b/src/backend/distributed/metadata/distobject.c index 55d7c9f33..5997480a0 100644 --- a/src/backend/distributed/metadata/distobject.c +++ b/src/backend/distributed/metadata/distobject.c @@ -85,12 +85,12 @@ citus_unmark_object_distributed(PG_FUNCTION_ARGS) { ereport(ERROR, (errmsg("object still exists"), errdetail("the %s \"%s\" still exists", - getObjectTypeDescription_compat(&address, + getObjectTypeDescription(&address, - /* missingOk: */ false), - getObjectIdentity_compat(&address, + /* missingOk: */ false), + getObjectIdentity(&address, - /* missingOk: */ false)), + /* missingOk: */ false)), errhint("drop the object via a DROP command"))); } diff --git a/src/backend/distributed/metadata/metadata_cache.c b/src/backend/distributed/metadata/metadata_cache.c index 1e73eef6b..ebc048376 100644 --- a/src/backend/distributed/metadata/metadata_cache.c +++ b/src/backend/distributed/metadata/metadata_cache.c @@ -178,6 +178,7 @@ typedef struct MetadataCacheData Oid distColocationRelationId; Oid distColocationConfigurationIndexId; Oid distPartitionRelationId; + Oid distTenantSchemaRelationId; Oid distPartitionLogicalRelidIndexId; Oid distPartitionColocationidIndexId; Oid distShardLogicalRelidIndexId; @@ -188,6 +189,8 @@ typedef struct MetadataCacheData Oid distPlacementGroupidIndexId; Oid distTransactionRelationId; Oid distTransactionGroupIndexId; + Oid distTenantSchemaPrimaryKeyIndexId; + Oid distTenantSchemaUniqueColocationIdIndexId; Oid citusCatalogNamespaceId; Oid copyFormatTypeId; Oid readIntermediateResultFuncId; @@ -508,11 +511,21 @@ IsCitusTableTypeInternal(char partitionMethod, char replicationModel, return partitionMethod == DISTRIBUTE_BY_RANGE; } + case SINGLE_SHARD_DISTRIBUTED: + { + return partitionMethod == DISTRIBUTE_BY_NONE && + replicationModel != REPLICATION_MODEL_2PC && + colocationId != INVALID_COLOCATION_ID; + } + case DISTRIBUTED_TABLE: { return partitionMethod == DISTRIBUTE_BY_HASH || partitionMethod == DISTRIBUTE_BY_RANGE || - partitionMethod == DISTRIBUTE_BY_APPEND; + partitionMethod == DISTRIBUTE_BY_APPEND || + (partitionMethod == DISTRIBUTE_BY_NONE && + replicationModel != REPLICATION_MODEL_2PC && + colocationId != INVALID_COLOCATION_ID); } case STRICTLY_PARTITIONED_DISTRIBUTED_TABLE: @@ -815,6 +828,21 @@ IsCitusLocalTableByDistParams(char partitionMethod, char replicationModel, } +/* + * IsSingleShardTableByDistParams returns true if given partitionMethod, + * replicationModel and colocationId would identify a single-shard distributed + * table that has a null shard key. + */ +bool +IsSingleShardTableByDistParams(char partitionMethod, char replicationModel, + uint32 colocationId) +{ + return partitionMethod == DISTRIBUTE_BY_NONE && + replicationModel != REPLICATION_MODEL_2PC && + colocationId != INVALID_COLOCATION_ID; +} + + /* * CitusTableList returns a list that includes all the valid distributed table * cache entries. @@ -2818,6 +2846,39 @@ DistColocationConfigurationIndexId(void) } +/* return oid of pg_dist_schema relation */ +Oid +DistTenantSchemaRelationId(void) +{ + CachedRelationLookup("pg_dist_schema", + &MetadataCache.distTenantSchemaRelationId); + + return MetadataCache.distTenantSchemaRelationId; +} + + +/* return oid of pg_dist_schema_pkey index */ +Oid +DistTenantSchemaPrimaryKeyIndexId(void) +{ + CachedRelationLookup("pg_dist_schema_pkey", + &MetadataCache.distTenantSchemaPrimaryKeyIndexId); + + return MetadataCache.distTenantSchemaPrimaryKeyIndexId; +} + + +/* return oid of pg_dist_schema_unique_colocationid_index index */ +Oid +DistTenantSchemaUniqueColocationIdIndexId(void) +{ + CachedRelationLookup("pg_dist_schema_unique_colocationid_index", + &MetadataCache.distTenantSchemaUniqueColocationIdIndexId); + + return MetadataCache.distTenantSchemaUniqueColocationIdIndexId; +} + + /* return oid of pg_dist_partition relation */ Oid DistPartitionRelationId(void) diff --git a/src/backend/distributed/metadata/metadata_sync.c b/src/backend/distributed/metadata/metadata_sync.c index e3310c5c8..7dfc30f73 100644 --- a/src/backend/distributed/metadata/metadata_sync.c +++ b/src/backend/distributed/metadata/metadata_sync.c @@ -40,6 +40,7 @@ #include "distributed/backend_data.h" #include "distributed/citus_ruleutils.h" #include "distributed/colocation_utils.h" +#include "distributed/tenant_schema_metadata.h" #include "distributed/commands.h" #include "distributed/deparser.h" #include "distributed/distribution_column.h" @@ -60,6 +61,7 @@ #include "distributed/pg_dist_colocation.h" #include "distributed/pg_dist_node.h" #include "distributed/pg_dist_shard.h" +#include "distributed/pg_dist_schema.h" #include "distributed/relation_access_tracking.h" #include "distributed/remote_commands.h" #include "distributed/resource_lock.h" @@ -144,6 +146,8 @@ static char * ColocationGroupCreateCommand(uint32 colocationId, int shardCount, Oid distributionColumnType, Oid distributionColumnCollation); static char * ColocationGroupDeleteCommand(uint32 colocationId); +static char * RemoteSchemaIdExpressionById(Oid schemaId); +static char * RemoteSchemaIdExpressionByName(char *schemaName); static char * RemoteTypeIdExpression(Oid typeId); static char * RemoteCollationIdExpression(Oid colocationId); @@ -170,6 +174,8 @@ PG_FUNCTION_INFO_V1(citus_internal_update_relation_colocation); PG_FUNCTION_INFO_V1(citus_internal_add_object_metadata); PG_FUNCTION_INFO_V1(citus_internal_add_colocation_metadata); PG_FUNCTION_INFO_V1(citus_internal_delete_colocation_metadata); +PG_FUNCTION_INFO_V1(citus_internal_add_tenant_schema); +PG_FUNCTION_INFO_V1(citus_internal_delete_tenant_schema); static bool got_SIGTERM = false; @@ -515,7 +521,7 @@ ShouldSyncUserCommandForObject(ObjectAddress objectAddress) /* * ShouldSyncTableMetadata checks if the metadata of a distributed table should be * propagated to metadata workers, i.e. the table is a hash distributed table or - * reference/citus local table. + * a Citus table that doesn't have shard key. */ bool ShouldSyncTableMetadata(Oid relationId) @@ -537,10 +543,11 @@ ShouldSyncTableMetadata(Oid relationId) /* - * ShouldSyncTableMetadataViaCatalog checks if the metadata of a distributed table should - * be propagated to metadata workers, i.e. the table is an MX table or reference table. + * ShouldSyncTableMetadataViaCatalog checks if the metadata of a Citus table should + * be propagated to metadata workers, i.e. the table is an MX table or Citus table + * that doesn't have shard key. * Tables with streaming replication model (which means RF=1) and hash distribution are - * considered as MX tables while tables with none distribution are reference tables. + * considered as MX tables. * * ShouldSyncTableMetadataViaCatalog does not use the CitusTableCache and instead reads * from catalog tables directly. @@ -686,7 +693,7 @@ DropMetadataSnapshotOnNode(WorkerNode *workerNode) bool singleTransaction = true; List *dropMetadataCommandList = DetachPartitionCommandList(); dropMetadataCommandList = lappend(dropMetadataCommandList, - BREAK_CITUS_TABLE_SEQUENCE_DEPENDENCY_COMMAND); + BREAK_ALL_CITUS_TABLE_SEQUENCE_DEPENDENCY_COMMAND); dropMetadataCommandList = lappend(dropMetadataCommandList, WorkerDropAllShellTablesCommand(singleTransaction)); dropMetadataCommandList = list_concat(dropMetadataCommandList, @@ -909,15 +916,9 @@ MarkObjectsDistributedCreateCommand(List *addresses, int forceDelegation = list_nth_int(forceDelegations, currentObjectCounter); List *names = NIL; List *args = NIL; - char *objectType = NULL; - #if PG_VERSION_NUM >= PG_VERSION_14 - objectType = getObjectTypeDescription(address, false); + char *objectType = getObjectTypeDescription(address, false); getObjectIdentityParts(address, &names, &args, false); - #else - objectType = getObjectTypeDescription(address); - getObjectIdentityParts(address, &names, &args); - #endif if (!isFirstObject) { @@ -1080,7 +1081,7 @@ EnsureObjectMetadataIsSane(int distributionArgumentIndex, int colocationId) /* * DistributionCreateCommands generates a commands that can be - * executed to replicate the metadata for a distributed table. + * executed to replicate the metadata for a Citus table. */ char * DistributionCreateCommand(CitusTableCacheEntry *cacheEntry) @@ -3701,12 +3702,14 @@ citus_internal_update_relation_colocation(PG_FUNCTION_ARGS) "entry in pg_dist_partition.", get_rel_name(relationId)))); } - else if (partitionMethod != DISTRIBUTE_BY_HASH) + else if (!IsCitusTableType(relationId, HASH_DISTRIBUTED) && + !IsCitusTableType(relationId, SINGLE_SHARD_DISTRIBUTED)) { /* connection from the coordinator operating on a shard */ ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("Updating colocation ids are only allowed for hash " - "distributed tables: %c", partitionMethod))); + "and single shard distributed tables: %c", + partitionMethod))); } int count = 1; @@ -3787,6 +3790,52 @@ citus_internal_delete_colocation_metadata(PG_FUNCTION_ARGS) } +/* + * citus_internal_add_tenant_schema is an internal UDF to + * call InsertTenantSchemaLocally on a remote node. + * + * None of the parameters are allowed to be NULL. To set the colocation + * id to NULL in metadata, use INVALID_COLOCATION_ID. + */ +Datum +citus_internal_add_tenant_schema(PG_FUNCTION_ARGS) +{ + CheckCitusVersion(ERROR); + + PG_ENSURE_ARGNOTNULL(0, "schema_id"); + Oid schemaId = PG_GETARG_OID(0); + + PG_ENSURE_ARGNOTNULL(1, "colocation_id"); + uint32 colocationId = PG_GETARG_INT32(1); + + InsertTenantSchemaLocally(schemaId, colocationId); + + PG_RETURN_VOID(); +} + + +/* + * citus_internal_delete_tenant_schema is an internal UDF to + * call DeleteTenantSchemaLocally on a remote node. + * + * The schemaId parameter is not allowed to be NULL. Morever, input schema is + * expected to be dropped already because this function is called from Citus + * drop hook and only used to clean up metadata after the schema is dropped. + */ +Datum +citus_internal_delete_tenant_schema(PG_FUNCTION_ARGS) +{ + CheckCitusVersion(ERROR); + + PG_ENSURE_ARGNOTNULL(0, "schema_id"); + Oid schemaId = PG_GETARG_OID(0); + + DeleteTenantSchemaLocally(schemaId); + + PG_RETURN_VOID(); +} + + /* * SyncNewColocationGroup synchronizes a new pg_dist_colocation entry to a worker. */ @@ -3936,6 +3985,72 @@ ColocationGroupDeleteCommand(uint32 colocationId) } +/* + * TenantSchemaInsertCommand returns a command to call + * citus_internal_add_tenant_schema(). + */ +char * +TenantSchemaInsertCommand(Oid schemaId, uint32 colocationId) +{ + StringInfo command = makeStringInfo(); + appendStringInfo(command, + "SELECT pg_catalog.citus_internal_add_tenant_schema(%s, %u)", + RemoteSchemaIdExpressionById(schemaId), colocationId); + + return command->data; +} + + +/* + * TenantSchemaDeleteCommand returns a command to call + * citus_internal_delete_tenant_schema(). + */ +char * +TenantSchemaDeleteCommand(char *schemaName) +{ + StringInfo command = makeStringInfo(); + appendStringInfo(command, + "SELECT pg_catalog.citus_internal_delete_tenant_schema(%s)", + RemoteSchemaIdExpressionByName(schemaName)); + + return command->data; +} + + +/* + * RemoteSchemaIdExpressionById returns an expression in text form that + * can be used to obtain the OID of the schema with given schema id on a + * different node when included in a query string. + */ +static char * +RemoteSchemaIdExpressionById(Oid schemaId) +{ + char *schemaName = get_namespace_name(schemaId); + if (schemaName == NULL) + { + ereport(ERROR, (errmsg("schema with OID %u does not exist", schemaId))); + } + + return RemoteSchemaIdExpressionByName(schemaName); +} + + +/* + * RemoteSchemaIdExpressionByName returns an expression in text form that + * can be used to obtain the OID of the schema with given schema name on a + * different node when included in a query string. + */ +static char * +RemoteSchemaIdExpressionByName(char *schemaName) +{ + StringInfo regnamespaceExpr = makeStringInfo(); + appendStringInfo(regnamespaceExpr, "%s::regnamespace", + quote_literal_cstr(quote_identifier(schemaName))); + + return regnamespaceExpr->data; +} + + /* * SetMetadataSyncNodesFromNodeList sets list of nodes that needs to be metadata * synced among given node list into metadataSyncContext. @@ -4235,6 +4350,22 @@ WorkerDropAllShellTablesCommand(bool singleTransaction) } +/* + * WorkerDropSequenceDependencyCommand returns command to drop sequence dependencies for + * given table. + */ +char * +WorkerDropSequenceDependencyCommand(Oid relationId) +{ + char *qualifiedTableName = generate_qualified_relation_name(relationId); + StringInfo breakSequenceDepCommand = makeStringInfo(); + appendStringInfo(breakSequenceDepCommand, + BREAK_CITUS_TABLE_SEQUENCE_DEPENDENCY_COMMAND, + quote_literal_cstr(qualifiedTableName)); + return breakSequenceDepCommand->data; +} + + /* * PropagateNodeWideObjectsCommandList is called during node activation to * propagate any object that should be propagated for every node. These are @@ -4314,6 +4445,14 @@ SyncDistributedObjects(MetadataSyncContext *context) SendDistTableMetadataCommands(context); SendDistObjectCommands(context); + /* + * Commands to insert pg_dist_schema entries. + * + * Need to be done after syncing distributed objects because the schemas + * need to exist on the worker. + */ + SendTenantSchemaMetadataCommands(context); + /* * After creating each table, handle the inter table relationship between * those tables. @@ -4352,8 +4491,8 @@ SendNodeWideObjectsSyncCommands(MetadataSyncContext *context) void SendShellTableDeletionCommands(MetadataSyncContext *context) { - /* break all sequence deps for citus tables and remove all shell tables */ - char *breakSeqDepsCommand = BREAK_CITUS_TABLE_SEQUENCE_DEPENDENCY_COMMAND; + /* break all sequence deps for citus tables */ + char *breakSeqDepsCommand = BREAK_ALL_CITUS_TABLE_SEQUENCE_DEPENDENCY_COMMAND; SendOrCollectCommandListToActivatedNodes(context, list_make1(breakSeqDepsCommand)); /* remove shell tables */ @@ -4386,6 +4525,10 @@ SendMetadataDeletionCommands(MetadataSyncContext *context) /* remove pg_dist_colocation entries */ SendOrCollectCommandListToActivatedNodes(context, list_make1(DELETE_ALL_COLOCATION)); + + /* remove pg_dist_schema entries */ + SendOrCollectCommandListToActivatedNodes(context, + list_make1(DELETE_ALL_TENANT_SCHEMAS)); } @@ -4485,6 +4628,53 @@ SendColocationMetadataCommands(MetadataSyncContext *context) } +/* + * SendTenantSchemaMetadataCommands sends tenant schema metadata entries with + * transactional or nontransactional mode according to transactionMode inside + * metadataSyncContext. + */ +void +SendTenantSchemaMetadataCommands(MetadataSyncContext *context) +{ + ScanKeyData scanKey[1]; + int scanKeyCount = 0; + + Relation pgDistTenantSchema = table_open(DistTenantSchemaRelationId(), + AccessShareLock); + SysScanDesc scanDesc = systable_beginscan(pgDistTenantSchema, InvalidOid, false, NULL, + scanKeyCount, scanKey); + + MemoryContext oldContext = MemoryContextSwitchTo(context->context); + HeapTuple heapTuple = NULL; + while (true) + { + ResetMetadataSyncMemoryContext(context); + + heapTuple = systable_getnext(scanDesc); + if (!HeapTupleIsValid(heapTuple)) + { + break; + } + + Form_pg_dist_schema tenantSchemaForm = + (Form_pg_dist_schema) GETSTRUCT(heapTuple); + + StringInfo insertTenantSchemaCommand = makeStringInfo(); + appendStringInfo(insertTenantSchemaCommand, + "SELECT pg_catalog.citus_internal_add_tenant_schema(%s, %u)", + RemoteSchemaIdExpressionById(tenantSchemaForm->schemaid), + tenantSchemaForm->colocationid); + + List *commandList = list_make1(insertTenantSchemaCommand->data); + SendOrCollectCommandListToActivatedNodes(context, commandList); + } + MemoryContextSwitchTo(oldContext); + + systable_endscan(scanDesc); + table_close(pgDistTenantSchema, AccessShareLock); +} + + /* * SendDependencyCreationCommands sends dependency creation commands to workers * with transactional or nontransactional mode according to transactionMode diff --git a/src/backend/distributed/metadata/metadata_utility.c b/src/backend/distributed/metadata/metadata_utility.c index 9fd4290ba..53a963029 100644 --- a/src/backend/distributed/metadata/metadata_utility.c +++ b/src/backend/distributed/metadata/metadata_utility.c @@ -101,9 +101,9 @@ static char * GenerateAllShardStatisticsQueryForNode(WorkerNode *workerNode, static List * GenerateShardStatisticsQueryList(List *workerNodeList, List *citusTableIds); static void ErrorIfNotSuitableToGetSize(Oid relationId); static List * OpenConnectionToNodes(List *workerNodeList); -static void ReceiveShardNameAndSizeResults(List *connectionList, - Tuplestorestate *tupleStore, - TupleDesc tupleDescriptor); +static void ReceiveShardIdAndSizeResults(List *connectionList, + Tuplestorestate *tupleStore, + TupleDesc tupleDescriptor); static void AppendShardSizeQuery(StringInfo selectQuery, ShardInterval *shardInterval); static HeapTuple CreateDiskSpaceTuple(TupleDesc tupleDesc, uint64 availableBytes, @@ -253,7 +253,7 @@ GetNodeDiskSpaceStatsForConnection(MultiConnection *connection, uint64 *availabl /* - * citus_shard_sizes returns all shard names and their sizes. + * citus_shard_sizes returns all shard ids and their sizes. */ Datum citus_shard_sizes(PG_FUNCTION_ARGS) @@ -271,7 +271,7 @@ citus_shard_sizes(PG_FUNCTION_ARGS) TupleDesc tupleDescriptor = NULL; Tuplestorestate *tupleStore = SetupTuplestore(fcinfo, &tupleDescriptor); - ReceiveShardNameAndSizeResults(connectionList, tupleStore, tupleDescriptor); + ReceiveShardIdAndSizeResults(connectionList, tupleStore, tupleDescriptor); PG_RETURN_VOID(); } @@ -446,12 +446,12 @@ GenerateShardStatisticsQueryList(List *workerNodeList, List *citusTableIds) /* - * ReceiveShardNameAndSizeResults receives shard name and size results from the given + * ReceiveShardIdAndSizeResults receives shard id and size results from the given * connection list. */ static void -ReceiveShardNameAndSizeResults(List *connectionList, Tuplestorestate *tupleStore, - TupleDesc tupleDescriptor) +ReceiveShardIdAndSizeResults(List *connectionList, Tuplestorestate *tupleStore, + TupleDesc tupleDescriptor) { MultiConnection *connection = NULL; foreach_ptr(connection, connectionList) @@ -488,13 +488,9 @@ ReceiveShardNameAndSizeResults(List *connectionList, Tuplestorestate *tupleStore memset(values, 0, sizeof(values)); memset(isNulls, false, sizeof(isNulls)); - /* format is [0] shard id, [1] shard name, [2] size */ - char *tableName = PQgetvalue(result, rowIndex, 1); - Datum resultStringDatum = CStringGetDatum(tableName); - Datum textDatum = DirectFunctionCall1(textin, resultStringDatum); - - values[0] = textDatum; - values[1] = ParseIntField(result, rowIndex, 2); + /* format is [0] shard id, [1] size */ + values[0] = ParseIntField(result, rowIndex, 0); + values[1] = ParseIntField(result, rowIndex, 1); tuplestore_putvalues(tupleStore, tupleDescriptor, values, isNulls); } @@ -942,7 +938,7 @@ GenerateAllShardStatisticsQueryForNode(WorkerNode *workerNode, List *citusTableI } /* Add a dummy entry so that UNION ALL doesn't complain */ - appendStringInfo(allShardStatisticsQuery, "SELECT 0::bigint, NULL::text, 0::bigint;"); + appendStringInfo(allShardStatisticsQuery, "SELECT 0::bigint, 0::bigint;"); return allShardStatisticsQuery->data; } @@ -986,7 +982,6 @@ AppendShardSizeQuery(StringInfo selectQuery, ShardInterval *shardInterval) char *quotedShardName = quote_literal_cstr(shardQualifiedName); appendStringInfo(selectQuery, "SELECT " UINT64_FORMAT " AS shard_id, ", shardId); - appendStringInfo(selectQuery, "%s AS shard_name, ", quotedShardName); appendStringInfo(selectQuery, PG_TOTAL_RELATION_SIZE_FUNCTION, quotedShardName); } @@ -2256,6 +2251,21 @@ EnsureTableOwner(Oid relationId) } +/* + * Check that the current user has owner rights to schemaId, error out if + * not. Superusers are regarded as owners. + */ +void +EnsureSchemaOwner(Oid schemaId) +{ + if (!pg_namespace_ownercheck(schemaId, GetUserId())) + { + aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA, + get_namespace_name(schemaId)); + } +} + + /* * Check that the current user has owner rights to functionId, error out if * not. Superusers are regarded as owners. Functions and procedures are @@ -2288,6 +2298,24 @@ EnsureHashDistributedTable(Oid relationId) } +/* + * EnsureHashOrSingleShardDistributedTable error out if the given relation is not a + * hash or single shard distributed table with the given message. + */ +void +EnsureHashOrSingleShardDistributedTable(Oid relationId) +{ + if (!IsCitusTableType(relationId, HASH_DISTRIBUTED) && + !IsCitusTableType(relationId, SINGLE_SHARD_DISTRIBUTED)) + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("relation %s should be a " + "hash or single shard distributed table", + get_rel_name(relationId)))); + } +} + + /* * EnsureSuperUser check that the current user is a superuser and errors out if not. */ @@ -4003,11 +4031,7 @@ CancelTasksForJob(int64 jobid) errmsg("must be a superuser to cancel superuser tasks"))); } else if (!has_privs_of_role(GetUserId(), taskOwner) && -#if PG_VERSION_NUM >= 140000 !has_privs_of_role(GetUserId(), ROLE_PG_SIGNAL_BACKEND)) -#else - !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID)) -#endif { /* user doesn't have the permissions to cancel this job */ ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), diff --git a/src/backend/distributed/metadata/node_metadata.c b/src/backend/distributed/metadata/node_metadata.c index 1c0314a49..60a5ab92b 100644 --- a/src/backend/distributed/metadata/node_metadata.c +++ b/src/backend/distributed/metadata/node_metadata.c @@ -36,6 +36,7 @@ #include "distributed/multi_join_order.h" #include "distributed/multi_router_planner.h" #include "distributed/pg_dist_node.h" +#include "distributed/pg_dist_node_metadata.h" #include "distributed/reference_table_utils.h" #include "distributed/remote_commands.h" #include "distributed/resource_lock.h" @@ -119,7 +120,6 @@ static char * NodeMetadataSyncedUpdateCommand(uint32 nodeId, bool metadataSynced static void ErrorIfCoordinatorMetadataSetFalse(WorkerNode *workerNode, Datum value, char *field); static WorkerNode * SetShouldHaveShards(WorkerNode *workerNode, bool shouldHaveShards); -static int FindCoordinatorNodeId(void); static WorkerNode * FindNodeAnyClusterByNodeId(uint32 nodeId); static void ErrorIfAnyNodeNotExist(List *nodeList); static void UpdateLocalGroupIdsViaMetadataContext(MetadataSyncContext *context); @@ -1800,7 +1800,7 @@ FindNodeWithNodeId(int nodeId, bool missingOk) /* * FindCoordinatorNodeId returns the node id of the coordinator node */ -static int +int FindCoordinatorNodeId() { bool includeNodesFromOtherClusters = false; @@ -2871,15 +2871,15 @@ TupleToWorkerNode(TupleDesc tupleDescriptor, HeapTuple heapTuple) */ heap_deform_tuple(heapTuple, tupleDescriptor, datumArray, isNullArray); - char *nodeName = DatumGetCString(datumArray[Anum_pg_dist_node_nodename - 1]); - char *nodeRack = DatumGetCString(datumArray[Anum_pg_dist_node_noderack - 1]); + char *nodeName = TextDatumGetCString(datumArray[Anum_pg_dist_node_nodename - 1]); + char *nodeRack = TextDatumGetCString(datumArray[Anum_pg_dist_node_noderack - 1]); WorkerNode *workerNode = (WorkerNode *) palloc0(sizeof(WorkerNode)); workerNode->nodeId = DatumGetUInt32(datumArray[Anum_pg_dist_node_nodeid - 1]); workerNode->workerPort = DatumGetUInt32(datumArray[Anum_pg_dist_node_nodeport - 1]); workerNode->groupId = DatumGetInt32(datumArray[Anum_pg_dist_node_groupid - 1]); - strlcpy(workerNode->workerName, TextDatumGetCString(nodeName), WORKER_LENGTH); - strlcpy(workerNode->workerRack, TextDatumGetCString(nodeRack), WORKER_LENGTH); + strlcpy(workerNode->workerName, nodeName, WORKER_LENGTH); + strlcpy(workerNode->workerRack, nodeRack, WORKER_LENGTH); workerNode->hasMetadata = DatumGetBool(datumArray[Anum_pg_dist_node_hasmetadata - 1]); workerNode->metadataSynced = DatumGetBool(datumArray[Anum_pg_dist_node_metadatasynced - 1]); diff --git a/src/backend/distributed/operations/create_shards.c b/src/backend/distributed/operations/create_shards.c index 3edab94e9..358927a09 100644 --- a/src/backend/distributed/operations/create_shards.c +++ b/src/backend/distributed/operations/create_shards.c @@ -217,9 +217,9 @@ CreateColocatedShards(Oid targetRelationId, Oid sourceRelationId, bool List *insertedShardPlacements = NIL; List *insertedShardIds = NIL; - /* make sure that tables are hash partitioned */ - CheckHashPartitionedTable(targetRelationId); - CheckHashPartitionedTable(sourceRelationId); + CitusTableCacheEntry *targetCacheEntry = GetCitusTableCacheEntry(targetRelationId); + Assert(targetCacheEntry->partitionMethod == DISTRIBUTE_BY_HASH || + targetCacheEntry->partitionMethod == DISTRIBUTE_BY_NONE); /* * In contrast to append/range partitioned tables it makes more sense to @@ -259,10 +259,20 @@ CreateColocatedShards(Oid targetRelationId, Oid sourceRelationId, bool *newShardIdPtr = GetNextShardId(); insertedShardIds = lappend(insertedShardIds, newShardIdPtr); - int32 shardMinValue = DatumGetInt32(sourceShardInterval->minValue); - int32 shardMaxValue = DatumGetInt32(sourceShardInterval->maxValue); - text *shardMinValueText = IntegerToText(shardMinValue); - text *shardMaxValueText = IntegerToText(shardMaxValue); + text *shardMinValueText = NULL; + text *shardMaxValueText = NULL; + if (targetCacheEntry->partitionMethod == DISTRIBUTE_BY_NONE) + { + Assert(list_length(sourceShardIntervalList) == 1); + } + else + { + int32 shardMinValue = DatumGetInt32(sourceShardInterval->minValue); + int32 shardMaxValue = DatumGetInt32(sourceShardInterval->maxValue); + shardMinValueText = IntegerToText(shardMinValue); + shardMaxValueText = IntegerToText(shardMaxValue); + } + List *sourceShardPlacementList = ShardPlacementListSortedByWorker( sourceShardId); @@ -362,6 +372,72 @@ CreateReferenceTableShard(Oid distributedTableId) } +/* + * CreateSingleShardTableShardWithRoundRobinPolicy creates a single + * shard for the given distributedTableId. The created shard does not + * have min/max values. Unlike CreateReferenceTableShard, the shard is + * _not_ replicated to all nodes but would have a single placement like + * Citus local tables. + * + * However, this placement doesn't necessarily need to be placed on + * coordinator. This is determined based on modulo of the colocation + * id that given table has been associated to. + */ +void +CreateSingleShardTableShardWithRoundRobinPolicy(Oid relationId, uint32 colocationId) +{ + EnsureTableOwner(relationId); + + /* we plan to add shards: get an exclusive lock on relation oid */ + LockRelationOid(relationId, ExclusiveLock); + + /* + * Load and sort the worker node list for deterministic placement. + * + * Also take a RowShareLock on pg_dist_node to disallow concurrent + * node list changes that require an exclusive lock. + */ + List *workerNodeList = DistributedTablePlacementNodeList(RowShareLock); + workerNodeList = SortList(workerNodeList, CompareWorkerNodes); + + int32 workerNodeCount = list_length(workerNodeList); + if (workerNodeCount == 0) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("couldn't find any worker nodes"), + errhint("Add more worker nodes"))); + } + + char shardStorageType = ShardStorageType(relationId); + text *minHashTokenText = NULL; + text *maxHashTokenText = NULL; + uint64 shardId = GetNextShardId(); + InsertShardRow(relationId, shardId, shardStorageType, + minHashTokenText, maxHashTokenText); + + /* determine the node index based on colocation id */ + int roundRobinNodeIdx = colocationId % workerNodeCount; + + int replicationFactor = 1; + List *insertedShardPlacements = InsertShardPlacementRows( + relationId, + shardId, + workerNodeList, + roundRobinNodeIdx, + replicationFactor); + + /* + * We don't need to force using exclusive connections because we're anyway + * creating a single shard. + */ + bool useExclusiveConnection = false; + + bool colocatedShard = false; + CreateShardsOnWorkers(relationId, insertedShardPlacements, + useExclusiveConnection, colocatedShard); +} + + /* * CheckHashPartitionedTable looks up the partition information for the given * tableId and checks if the table is hash partitioned. If not, the function diff --git a/src/backend/distributed/operations/shard_rebalancer.c b/src/backend/distributed/operations/shard_rebalancer.c index b5ec9b7ba..20004f5fb 100644 --- a/src/backend/distributed/operations/shard_rebalancer.c +++ b/src/backend/distributed/operations/shard_rebalancer.c @@ -319,6 +319,7 @@ PG_FUNCTION_INFO_V1(citus_rebalance_wait); bool RunningUnderIsolationTest = false; int MaxRebalancerLoggedIgnoredMoves = 5; +int RebalancerByDiskSizeBaseCost = 100 * 1024 * 1024; bool PropagateSessionSettingsForLoopbackConnection = false; static const char *PlacementUpdateTypeNames[] = { @@ -515,6 +516,16 @@ GetRebalanceSteps(RebalanceOptions *options) /* sort the lists to make the function more deterministic */ List *activeWorkerList = SortedActiveWorkers(); + int shardAllowedNodeCount = 0; + WorkerNode *workerNode = NULL; + foreach_ptr(workerNode, activeWorkerList) + { + if (workerNode->shouldHaveShards) + { + shardAllowedNodeCount++; + } + } + List *activeShardPlacementListList = NIL; List *unbalancedShards = NIL; @@ -532,8 +543,7 @@ GetRebalanceSteps(RebalanceOptions *options) shardPlacementList, options->workerNode); } - if (list_length(activeShardPlacementListForRelation) >= list_length( - activeWorkerList)) + if (list_length(activeShardPlacementListForRelation) >= shardAllowedNodeCount) { activeShardPlacementListList = lappend(activeShardPlacementListList, activeShardPlacementListForRelation); @@ -668,6 +678,8 @@ citus_shard_cost_by_disk_size(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldContext); MemoryContextReset(localContext); + colocationSizeInBytes += RebalancerByDiskSizeBaseCost; + if (colocationSizeInBytes <= 0) { PG_RETURN_FLOAT4(1); @@ -1169,6 +1181,11 @@ replicate_table_shards(PG_FUNCTION_ARGS) ArrayType *excludedShardArray = PG_GETARG_ARRAYTYPE_P(3); Oid shardReplicationModeOid = PG_GETARG_OID(4); + if (IsCitusTableType(relationId, SINGLE_SHARD_DISTRIBUTED)) + { + ereport(ERROR, (errmsg("cannot replicate single shard tables' shards"))); + } + char transferMode = LookupShardTransferMode(shardReplicationModeOid); EnsureReferenceTablesExistOnAllNodesExtended(transferMode); @@ -2003,7 +2020,7 @@ GenerateTaskMoveDependencyList(PlacementUpdateEvent *move, int64 colocationId, * overlaps with the current move's target node. * The earlier/first move might make space for the later/second move. * So we could run out of disk space (or at least overload the node) - * if we move the second shard to it before the first one is moved away.  + * if we move the second shard to it before the first one is moved away. */ ShardMoveSourceNodeHashEntry *shardMoveSourceNodeHashEntry = hash_search( shardMoveDependencies.nodeDependencies, &move->targetNode->nodeId, HASH_FIND, @@ -2165,7 +2182,10 @@ RebalanceTableShardsBackground(RebalanceOptions *options, Oid shardReplicationMo quote_literal_cstr(shardTranferModeLabel)); int32 nodesInvolved[] = { 0 }; - BackgroundTask *task = ScheduleBackgroundTask(jobId, GetUserId(), buf.data, 0, + + /* replicate_reference_tables permissions require superuser */ + Oid superUserId = CitusExtensionOwner(); + BackgroundTask *task = ScheduleBackgroundTask(jobId, superUserId, buf.data, 0, NULL, 0, nodesInvolved); replicateRefTablesTaskId = task->taskid; } @@ -2268,7 +2288,7 @@ UpdateShardPlacement(PlacementUpdateEvent *placementUpdateEvent, if (updateType == PLACEMENT_UPDATE_MOVE) { appendStringInfo(placementUpdateCommand, - "SELECT citus_move_shard_placement(%ld,%u,%u,%s)", + "SELECT pg_catalog.citus_move_shard_placement(%ld,%u,%u,%s)", shardId, sourceNode->nodeId, targetNode->nodeId, @@ -2277,7 +2297,7 @@ UpdateShardPlacement(PlacementUpdateEvent *placementUpdateEvent, else if (updateType == PLACEMENT_UPDATE_COPY) { appendStringInfo(placementUpdateCommand, - "SELECT citus_copy_shard_placement(%ld,%u,%u,%s)", + "SELECT pg_catalog.citus_copy_shard_placement(%ld,%u,%u,%s)", shardId, sourceNode->nodeId, targetNode->nodeId, diff --git a/src/backend/distributed/operations/stage_protocol.c b/src/backend/distributed/operations/stage_protocol.c index ce9fe3f31..ddab3453b 100644 --- a/src/backend/distributed/operations/stage_protocol.c +++ b/src/backend/distributed/operations/stage_protocol.c @@ -138,6 +138,13 @@ master_create_empty_shard(PG_FUNCTION_ARGS) errdetail("We currently don't support creating shards " "on hash-partitioned tables"))); } + else if (IsCitusTableType(relationId, SINGLE_SHARD_DISTRIBUTED)) + { + ereport(ERROR, (errmsg("relation \"%s\" is a single shard table", + relationName), + errdetail("We currently don't support creating shards " + "on single shard tables"))); + } else if (IsCitusTableType(relationId, REFERENCE_TABLE)) { ereport(ERROR, (errmsg("relation \"%s\" is a reference table", @@ -521,7 +528,8 @@ RelationShardListForShardCreate(ShardInterval *shardInterval) relationShard->shardId = shardInterval->shardId; List *relationShardList = list_make1(relationShard); - if (IsCitusTableTypeCacheEntry(cacheEntry, HASH_DISTRIBUTED) && + if ((IsCitusTableTypeCacheEntry(cacheEntry, HASH_DISTRIBUTED) || + IsCitusTableTypeCacheEntry(cacheEntry, SINGLE_SHARD_DISTRIBUTED)) && cacheEntry->colocationId != INVALID_COLOCATION_ID) { shardIndex = ShardIndex(shardInterval); @@ -855,7 +863,7 @@ ProcessShardStatisticsRow(PGresult *result, int64 rowIndex, uint64 *shardId, return false; } - *shardSize = ParseIntField(result, rowIndex, 2); + *shardSize = ParseIntField(result, rowIndex, 1); return true; } diff --git a/src/backend/distributed/operations/worker_node_manager.c b/src/backend/distributed/operations/worker_node_manager.c index 658c83269..76f2732ba 100644 --- a/src/backend/distributed/operations/worker_node_manager.c +++ b/src/backend/distributed/operations/worker_node_manager.c @@ -31,11 +31,7 @@ #include "utils/guc.h" #include "utils/hsearch.h" #include "utils/memutils.h" -#if PG_VERSION_NUM < PG_VERSION_13 -#include "utils/hashutils.h" -#else #include "common/hashfn.h" -#endif /* Config variables managed via guc.c */ diff --git a/src/backend/distributed/operations/worker_shard_copy.c b/src/backend/distributed/operations/worker_shard_copy.c index 00a5413c9..ba65635a7 100644 --- a/src/backend/distributed/operations/worker_shard_copy.c +++ b/src/backend/distributed/operations/worker_shard_copy.c @@ -527,13 +527,13 @@ LocalCopyToShard(ShardCopyDestReceiver *copyDest, CopyOutState localCopyOutState false /* inFromCl */); List *options = (isBinaryCopy) ? list_make1(binaryFormatOption) : NULL; - CopyFromState cstate = BeginCopyFrom_compat(pState, shard, - NULL /* whereClause */, - NULL /* fileName */, - false /* is_program */, - ReadFromLocalBufferCallback, - NULL /* attlist (NULL is all columns) */, - options); + CopyFromState cstate = BeginCopyFrom(pState, shard, + NULL /* whereClause */, + NULL /* fileName */, + false /* is_program */, + ReadFromLocalBufferCallback, + NULL /* attlist (NULL is all columns) */, + options); CopyFrom(cstate); EndCopyFrom(cstate); resetStringInfo(localCopyOutState->fe_msgbuf); diff --git a/src/backend/distributed/planner/cte_inline.c b/src/backend/distributed/planner/cte_inline.c index 2356ebf48..ce258916d 100644 --- a/src/backend/distributed/planner/cte_inline.c +++ b/src/backend/distributed/planner/cte_inline.c @@ -43,7 +43,7 @@ static bool contain_dml_walker(Node *node, void *context); /* the following utility functions are related to Citus' logic */ static bool RecursivelyInlineCteWalker(Node *node, void *context); static void InlineCTEsInQueryTree(Query *query); -static bool QueryTreeContainsInlinableCteWalker(Node *node); +static bool QueryTreeContainsInlinableCteWalker(Node *node, void *context); /* @@ -135,7 +135,7 @@ InlineCTEsInQueryTree(Query *query) bool QueryTreeContainsInlinableCTE(Query *queryTree) { - return QueryTreeContainsInlinableCteWalker((Node *) queryTree); + return QueryTreeContainsInlinableCteWalker((Node *) queryTree, NULL); } @@ -144,7 +144,7 @@ QueryTreeContainsInlinableCTE(Query *queryTree) * the (sub)queries in the node contains at least one CTE. */ static bool -QueryTreeContainsInlinableCteWalker(Node *node) +QueryTreeContainsInlinableCteWalker(Node *node, void *context) { if (node == NULL) { diff --git a/src/backend/distributed/planner/distributed_planner.c b/src/backend/distributed/planner/distributed_planner.c index 38962b333..3b6a8f9f7 100644 --- a/src/backend/distributed/planner/distributed_planner.c +++ b/src/backend/distributed/planner/distributed_planner.c @@ -925,6 +925,10 @@ GetRouterPlanType(Query *query, Query *originalQuery, bool hasUnresolvedParams) } else if (IsMergeQuery(originalQuery)) { + if (hasUnresolvedParams) + { + return REPLAN_WITH_BOUND_PARAMETERS; + } return MERGE_QUERY; } else @@ -990,7 +994,8 @@ CreateDistributedPlan(uint64 planId, bool allowRecursivePlanning, Query *origina case MERGE_QUERY: { distributedPlan = - CreateMergePlan(originalQuery, query, plannerRestrictionContext); + CreateMergePlan(planId, originalQuery, query, plannerRestrictionContext, + boundParams); break; } @@ -1377,6 +1382,12 @@ FinalizePlan(PlannedStmt *localPlan, DistributedPlan *distributedPlan) break; } + case MULTI_EXECUTOR_NON_PUSHABLE_MERGE_QUERY: + { + customScan->methods = &NonPushableMergeCommandCustomScanMethods; + break; + } + default: { customScan->methods = &DelayedErrorCustomScanMethods; @@ -2462,6 +2473,18 @@ HasUnresolvedExternParamsWalker(Node *expression, ParamListInfo boundParams) } +/* + * ContainsSingleShardTable returns true if given query contains reference + * to a single-shard table. + */ +bool +ContainsSingleShardTable(Query *query) +{ + RTEListProperties *rteListProperties = GetRTEListPropertiesForQuery(query); + return rteListProperties->hasSingleShardDistTable; +} + + /* * GetRTEListPropertiesForQuery is a wrapper around GetRTEListProperties that * returns RTEListProperties for the rte list retrieved from query. @@ -2538,6 +2561,15 @@ GetRTEListProperties(List *rangeTableList) else if (IsCitusTableTypeCacheEntry(cacheEntry, DISTRIBUTED_TABLE)) { rteListProperties->hasDistributedTable = true; + + if (!HasDistributionKeyCacheEntry(cacheEntry)) + { + rteListProperties->hasSingleShardDistTable = true; + } + else + { + rteListProperties->hasDistTableWithShardKey = true; + } } else { diff --git a/src/backend/distributed/planner/fast_path_router_planner.c b/src/backend/distributed/planner/fast_path_router_planner.c index ecb62478a..7b97d3ff5 100644 --- a/src/backend/distributed/planner/fast_path_router_planner.c +++ b/src/backend/distributed/planner/fast_path_router_planner.c @@ -108,7 +108,7 @@ GeneratePlaceHolderPlannedStmt(Query *parse) Node *distKey PG_USED_FOR_ASSERTS_ONLY = NULL; - AssertArg(FastPathRouterQuery(parse, &distKey)); + Assert(FastPathRouterQuery(parse, &distKey)); /* there is only a single relation rte */ scanNode->scanrelid = 1; @@ -212,6 +212,16 @@ FastPathRouterQuery(Query *query, Node **distributionKeyValue) return false; } + /* + * If the table doesn't have a distribution column, we don't need to + * check anything further. + */ + Var *distributionKey = PartitionColumn(distributedTableId, 1); + if (!distributionKey) + { + return true; + } + /* WHERE clause should not be empty for distributed tables */ if (joinTree == NULL || (IsCitusTableTypeCacheEntry(cacheEntry, DISTRIBUTED_TABLE) && joinTree->quals == @@ -220,13 +230,6 @@ FastPathRouterQuery(Query *query, Node **distributionKeyValue) return false; } - /* if that's a reference table, we don't need to check anything further */ - Var *distributionKey = PartitionColumn(distributedTableId, 1); - if (!distributionKey) - { - return true; - } - /* convert list of expressions into expression tree for further processing */ quals = joinTree->quals; if (quals != NULL && IsA(quals, List)) diff --git a/src/backend/distributed/planner/function_call_delegation.c b/src/backend/distributed/planner/function_call_delegation.c index fe9d2ccef..2f8da29c0 100644 --- a/src/backend/distributed/planner/function_call_delegation.c +++ b/src/backend/distributed/planner/function_call_delegation.c @@ -116,7 +116,6 @@ contain_param_walker(Node *node, void *context) PlannedStmt * TryToDelegateFunctionCall(DistributedPlanningContext *planContext) { - bool colocatedWithReferenceTable = false; ShardPlacement *placement = NULL; struct ParamWalkerContext walkerParamContext = { 0 }; bool inTransactionBlock = false; @@ -337,7 +336,7 @@ TryToDelegateFunctionCall(DistributedPlanningContext *planContext) if (!procedure->forceDelegation) { /* cannot delegate function calls in a multi-statement transaction */ - ereport(DEBUG1, (errmsg("not pushing down function calls in " + ereport(DEBUG4, (errmsg("not pushing down function calls in " "a multi-statement transaction"))); return NULL; } @@ -388,17 +387,10 @@ TryToDelegateFunctionCall(DistributedPlanningContext *planContext) Oid colocatedRelationId = ColocatedTableId(procedure->colocationId); if (colocatedRelationId == InvalidOid) { - ereport(DEBUG1, (errmsg("function does not have co-located tables"))); + ereport(DEBUG4, (errmsg("function does not have co-located tables"))); return NULL; } - CitusTableCacheEntry *distTable = GetCitusTableCacheEntry(colocatedRelationId); - Var *partitionColumn = distTable->partitionColumn; - if (partitionColumn == NULL) - { - colocatedWithReferenceTable = true; - } - /* * This can be called in queries like SELECT ... WHERE EXISTS(SELECT func()), or other * forms of CTEs or subqueries. We don't push-down in those cases. @@ -410,14 +402,20 @@ TryToDelegateFunctionCall(DistributedPlanningContext *planContext) return NULL; } - if (colocatedWithReferenceTable) + CitusTableCacheEntry *distTable = GetCitusTableCacheEntry(colocatedRelationId); + if (IsCitusTableType(colocatedRelationId, REFERENCE_TABLE)) { placement = ShardPlacementForFunctionColocatedWithReferenceTable(distTable); } + else if (IsCitusTableType(colocatedRelationId, SINGLE_SHARD_DISTRIBUTED)) + { + placement = ShardPlacementForFunctionColocatedWithSingleShardTable(distTable); + } else { placement = ShardPlacementForFunctionColocatedWithDistTable(procedure, funcExpr->args, + distTable-> partitionColumn, distTable, planContext->plan); @@ -570,6 +568,34 @@ ShardPlacementForFunctionColocatedWithDistTable(DistObjectCacheEntry *procedure, } +/* + * ShardPlacementForFunctionColocatedWithSingleShardTable decides on a placement + * for delegating a function call that reads from a single shard table. + */ +ShardPlacement * +ShardPlacementForFunctionColocatedWithSingleShardTable(CitusTableCacheEntry *cacheEntry) +{ + const ShardInterval *shardInterval = cacheEntry->sortedShardIntervalArray[0]; + + if (shardInterval == NULL) + { + ereport(DEBUG1, (errmsg("cannot push down call, failed to find shard interval"))); + return NULL; + } + + List *placementList = ActiveShardPlacementList(shardInterval->shardId); + if (list_length(placementList) != 1) + { + /* punt on this for now */ + ereport(DEBUG1, (errmsg( + "cannot push down function call for replicated distributed tables"))); + return NULL; + } + + return (ShardPlacement *) linitial(placementList); +} + + /* * ShardPlacementForFunctionColocatedWithReferenceTable decides on a placement for delegating * a function call that reads from a reference table. diff --git a/src/backend/distributed/planner/insert_select_planner.c b/src/backend/distributed/planner/insert_select_planner.c index 2eab62fc3..c59e920b5 100644 --- a/src/backend/distributed/planner/insert_select_planner.c +++ b/src/backend/distributed/planner/insert_select_planner.c @@ -31,6 +31,7 @@ #include "distributed/pg_dist_partition.h" #include "distributed/query_pushdown_planning.h" #include "distributed/recursive_planning.h" +#include "distributed/repartition_executor.h" #include "distributed/resource_lock.h" #include "distributed/version_compat.h" #include "nodes/makefuncs.h" @@ -73,9 +74,9 @@ static List * CreateTargetListForCombineQuery(List *targetList); static DeferredErrorMessage * DistributedInsertSelectSupported(Query *queryTree, RangeTblEntry *insertRte, RangeTblEntry *subqueryRte, - bool allReferenceTables); -static DeferredErrorMessage * MultiTaskRouterSelectQuerySupported(Query *query); -static bool HasUnsupportedDistinctOn(Query *query); + bool allReferenceTables, + PlannerRestrictionContext * + plannerRestrictionContext); static DeferredErrorMessage * InsertPartitionColumnMatchesSelect(Query *query, RangeTblEntry *insertRte, RangeTblEntry * @@ -85,7 +86,6 @@ static DeferredErrorMessage * InsertPartitionColumnMatchesSelect(Query *query, static DistributedPlan * CreateNonPushableInsertSelectPlan(uint64 planId, Query *parse, ParamListInfo boundParams); static DeferredErrorMessage * NonPushableInsertSelectSupported(Query *insertSelectQuery); -static Query * WrapSubquery(Query *subquery); static void RelabelTargetEntryList(List *selectTargetList, List *insertTargetList); static List * AddInsertSelectCasts(List *insertTargetList, List *selectTargetList, Oid targetRelationId); @@ -292,7 +292,8 @@ CreateDistributedInsertSelectPlan(Query *originalQuery, distributedPlan->planningError = DistributedInsertSelectSupported(originalQuery, insertRte, subqueryRte, - allReferenceTables); + allReferenceTables, + plannerRestrictionContext); if (distributedPlan->planningError) { return distributedPlan; @@ -613,14 +614,15 @@ CreateTargetListForCombineQuery(List *targetList) */ static DeferredErrorMessage * DistributedInsertSelectSupported(Query *queryTree, RangeTblEntry *insertRte, - RangeTblEntry *subqueryRte, bool allReferenceTables) + RangeTblEntry *subqueryRte, bool allReferenceTables, + PlannerRestrictionContext *plannerRestrictionContext) { Oid selectPartitionColumnTableId = InvalidOid; Oid targetRelationId = insertRte->relid; ListCell *rangeTableCell = NULL; /* we only do this check for INSERT ... SELECT queries */ - AssertArg(InsertSelectIntoCitusTable(queryTree)); + Assert(InsertSelectIntoCitusTable(queryTree)); Query *subquery = subqueryRte->subquery; @@ -687,8 +689,16 @@ DistributedInsertSelectSupported(Query *queryTree, RangeTblEntry *insertRte, NULL, NULL); } - /* we don't support LIMIT, OFFSET and WINDOW functions */ - DeferredErrorMessage *error = MultiTaskRouterSelectQuerySupported(subquery); + /* first apply toplevel pushdown checks to SELECT query */ + DeferredErrorMessage *error = DeferErrorIfUnsupportedSubqueryPushdown(subquery, + plannerRestrictionContext); + if (error) + { + return error; + } + + /* then apply subquery pushdown checks to SELECT query */ + error = DeferErrorIfCannotPushdownSubquery(subquery, false); if (error) { return error; @@ -730,26 +740,29 @@ DistributedInsertSelectSupported(Query *queryTree, RangeTblEntry *insertRte, "table", NULL, NULL); } - /* ensure that INSERT's partition column comes from SELECT's partition column */ - error = InsertPartitionColumnMatchesSelect(queryTree, insertRte, subqueryRte, - &selectPartitionColumnTableId); - if (error) + if (HasDistributionKey(targetRelationId)) { - return error; + /* ensure that INSERT's partition column comes from SELECT's partition column */ + error = InsertPartitionColumnMatchesSelect(queryTree, insertRte, subqueryRte, + &selectPartitionColumnTableId); + if (error) + { + return error; + } } + } - /* - * We expect partition column values come from colocated tables. Note that we - * skip this check from the reference table case given that all reference tables - * are already (and by default) co-located. - */ - if (!TablesColocated(insertRte->relid, selectPartitionColumnTableId)) - { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "INSERT target table and the source relation of the SELECT partition " - "column value must be colocated in distributed INSERT ... SELECT", - NULL, NULL); - } + /* All tables in source list and target table should be colocated. */ + List *distributedRelationIdList = DistributedRelationIdList(subquery); + distributedRelationIdList = lappend_oid(distributedRelationIdList, + targetRelationId); + + if (!AllDistributedRelationsInListColocated(distributedRelationIdList)) + { + return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, + "INSERT target relation and all source relations of the " + "SELECT must be colocated in distributed INSERT ... SELECT", + NULL, NULL); } return NULL; @@ -848,8 +861,8 @@ RouterModifyTaskForShardInterval(Query *originalQuery, * Note that this is only the case with PG14 as the parameter doesn't exist * prior to that. */ - shardRestrictionList = make_simple_restrictinfo_compat(NULL, - (Expr *) shardOpExpressions); + shardRestrictionList = make_simple_restrictinfo(NULL, + (Expr *) shardOpExpressions); extendedBaseRestrictInfo = lappend(extendedBaseRestrictInfo, shardRestrictionList); @@ -867,7 +880,7 @@ RouterModifyTaskForShardInterval(Query *originalQuery, */ RTEListProperties *subqueryRteListProperties = GetRTEListPropertiesForQuery( copiedSubquery); - if (subqueryRteListProperties->hasDistributedTable) + if (subqueryRteListProperties->hasDistTableWithShardKey) { AddPartitionKeyNotNullFilterToSelect(copiedSubquery); } @@ -1107,152 +1120,6 @@ ReorderInsertSelectTargetLists(Query *originalQuery, RangeTblEntry *insertRte, } -/* - * MultiTaskRouterSelectQuerySupported returns NULL if the query may be used - * as the source for an INSERT ... SELECT or returns a description why not. - */ -static DeferredErrorMessage * -MultiTaskRouterSelectQuerySupported(Query *query) -{ - List *queryList = NIL; - ListCell *queryCell = NULL; - StringInfo errorDetail = NULL; - bool hasUnsupportedDistinctOn = false; - - ExtractQueryWalker((Node *) query, &queryList); - foreach(queryCell, queryList) - { - Query *subquery = (Query *) lfirst(queryCell); - - Assert(subquery->commandType == CMD_SELECT); - - /* pushing down rtes without relations yields (shardCount * expectedRows) */ - if (HasEmptyJoinTree(subquery)) - { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "Subqueries without relations are not allowed in " - "distributed INSERT ... SELECT queries", - NULL, NULL); - } - - /* pushing down limit per shard would yield wrong results */ - if (subquery->limitCount != NULL) - { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "LIMIT clauses are not allowed in distributed INSERT " - "... SELECT queries", - NULL, NULL); - } - - /* pushing down limit offest per shard would yield wrong results */ - if (subquery->limitOffset != NULL) - { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "OFFSET clauses are not allowed in distributed " - "INSERT ... SELECT queries", - NULL, NULL); - } - - /* group clause list must include partition column */ - if (subquery->groupClause) - { - List *groupClauseList = subquery->groupClause; - List *targetEntryList = subquery->targetList; - List *groupTargetEntryList = GroupTargetEntryList(groupClauseList, - targetEntryList); - bool groupOnPartitionColumn = TargetListOnPartitionColumn(subquery, - groupTargetEntryList); - if (!groupOnPartitionColumn) - { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "Group by list without distribution column is " - "not allowed in distributed INSERT ... " - "SELECT queries", - NULL, NULL); - } - } - - /* - * We support window functions when the window function - * is partitioned on distribution column. - */ - if (subquery->windowClause && !SafeToPushdownWindowFunction(subquery, - &errorDetail)) - { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, errorDetail->data, NULL, - NULL); - } - - if (subquery->setOperations != NULL) - { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "Set operations are not allowed in distributed " - "INSERT ... SELECT queries", - NULL, NULL); - } - - /* - * We currently do not support grouping sets since it could generate NULL - * results even after the restrictions are applied to the query. A solution - * would be to add the whole query into a subquery and add the restrictions - * on that subquery. - */ - if (subquery->groupingSets != NULL) - { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "grouping sets are not allowed in distributed " - "INSERT ... SELECT queries", - NULL, NULL); - } - - /* - * We don't support DISTINCT ON clauses on non-partition columns. - */ - hasUnsupportedDistinctOn = HasUnsupportedDistinctOn(subquery); - if (hasUnsupportedDistinctOn) - { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "DISTINCT ON (non-partition column) clauses are not " - "allowed in distributed INSERT ... SELECT queries", - NULL, NULL); - } - } - - return NULL; -} - - -/* - * HasUnsupportedDistinctOn returns true if the query has distinct on and - * distinct targets do not contain partition column. - */ -static bool -HasUnsupportedDistinctOn(Query *query) -{ - ListCell *distinctCell = NULL; - - if (!query->hasDistinctOn) - { - return false; - } - - foreach(distinctCell, query->distinctClause) - { - SortGroupClause *distinctClause = lfirst(distinctCell); - TargetEntry *distinctEntry = get_sortgroupclause_tle(distinctClause, - query->targetList); - - bool skipOuterVars = true; - if (IsPartitionColumn(distinctEntry->expr, query, skipOuterVars)) - { - return false; - } - } - - return true; -} - - /* * InsertPartitionColumnMatchesSelect returns NULL the partition column in the * table targeted by INSERTed matches with the any of the SELECTed table's @@ -1537,11 +1404,22 @@ CreateNonPushableInsertSelectPlan(uint64 planId, Query *parse, ParamListInfo bou bool repartitioned = IsRedistributablePlan(selectPlan->planTree) && IsSupportedRedistributionTarget(targetRelationId); - distributedPlan->insertSelectQuery = insertSelectQuery; - distributedPlan->selectPlanForInsertSelect = selectPlan; - distributedPlan->insertSelectMethod = repartitioned ? - INSERT_SELECT_REPARTITION : - INSERT_SELECT_VIA_COORDINATOR; + /* + * It's not possible to generate a distributed plan for a SELECT + * having more than one tasks if it references a single-shard table. + * + * For this reason, right now we don't expect an INSERT .. SELECT + * query to go through the repartitioned INSERT .. SELECT logic if the + * SELECT query references a single-shard table. + */ + Assert(!repartitioned || + !ContainsSingleShardTable(selectQueryCopy)); + + distributedPlan->modifyQueryViaCoordinatorOrRepartition = insertSelectQuery; + distributedPlan->selectPlanForModifyViaCoordinatorOrRepartition = selectPlan; + distributedPlan->modifyWithSelectMethod = repartitioned ? + MODIFY_WITH_SELECT_REPARTITION : + MODIFY_WITH_SELECT_VIA_COORDINATOR; distributedPlan->expectResults = insertSelectQuery->returningList != NIL; distributedPlan->intermediateResultIdPrefix = InsertSelectResultIdPrefix(planId); distributedPlan->targetRelationId = targetRelationId; @@ -1598,7 +1476,7 @@ InsertSelectResultIdPrefix(uint64 planId) * WrapSubquery wraps the given query as a subquery in a newly constructed * "SELECT * FROM (...subquery...) citus_insert_select_subquery" query. */ -static Query * +Query * WrapSubquery(Query *subquery) { ParseState *pstate = make_parsestate(NULL); diff --git a/src/backend/distributed/planner/intermediate_result_pruning.c b/src/backend/distributed/planner/intermediate_result_pruning.c index 76aba8321..cefbfb833 100644 --- a/src/backend/distributed/planner/intermediate_result_pruning.c +++ b/src/backend/distributed/planner/intermediate_result_pruning.c @@ -69,7 +69,7 @@ FindSubPlanUsages(DistributedPlan *plan) SUBPLAN_ACCESS_REMOTE); } - if (plan->insertSelectQuery != NULL) + if (plan->modifyQueryViaCoordinatorOrRepartition != NULL) { /* INSERT..SELECT plans currently do not have a workerJob */ Assert(plan->workerJob == NULL); @@ -79,8 +79,9 @@ FindSubPlanUsages(DistributedPlan *plan) * perform pruning. We therefore require all subplans used in the * INSERT..SELECT to be available all nodes. */ - remoteSubPlans = FindSubPlansUsedInNode((Node *) plan->insertSelectQuery, - SUBPLAN_ACCESS_ANYWHERE); + remoteSubPlans = + FindSubPlansUsedInNode((Node *) plan->modifyQueryViaCoordinatorOrRepartition, + SUBPLAN_ACCESS_ANYWHERE); } /* merge the used subplans */ diff --git a/src/backend/distributed/planner/local_distributed_join_planner.c b/src/backend/distributed/planner/local_distributed_join_planner.c index 449b96195..2c6a63de1 100644 --- a/src/backend/distributed/planner/local_distributed_join_planner.c +++ b/src/backend/distributed/planner/local_distributed_join_planner.c @@ -485,6 +485,8 @@ RequiredAttrNumbersForRelation(RangeTblEntry *rangeTableEntry, PlannerInfo *plannerInfo = relationRestriction->plannerInfo; + int rteIndex = relationRestriction->index; + /* * Here we used the query from plannerInfo because it has the optimizations * so that it doesn't have unnecessary columns. The original query doesn't have @@ -492,8 +494,18 @@ RequiredAttrNumbersForRelation(RangeTblEntry *rangeTableEntry, * 'required' attributes. */ Query *queryToProcess = plannerInfo->parse; - int rteIndex = relationRestriction->index; + return RequiredAttrNumbersForRelationInternal(queryToProcess, rteIndex); +} + + +/* + * RequiredAttrNumbersForRelationInternal returns the required attribute numbers + * for the input range-table-index in the query parameter. + */ +List * +RequiredAttrNumbersForRelationInternal(Query *queryToProcess, int rteIndex) +{ List *allVarsInQuery = pull_vars_of_level((Node *) queryToProcess, 0); List *requiredAttrNumbers = NIL; diff --git a/src/backend/distributed/planner/merge_planner.c b/src/backend/distributed/planner/merge_planner.c index c67095624..6a80a7c33 100644 --- a/src/backend/distributed/planner/merge_planner.c +++ b/src/backend/distributed/planner/merge_planner.c @@ -13,72 +13,178 @@ #include "postgres.h" #include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" #include "optimizer/optimizer.h" #include "parser/parsetree.h" +#include "tcop/tcopprot.h" #include "utils/lsyscache.h" #include "distributed/citus_clauses.h" +#include "distributed/citus_custom_scan.h" +#include "distributed/insert_select_planner.h" #include "distributed/listutils.h" +#include "distributed/local_distributed_join_planner.h" #include "distributed/merge_planner.h" #include "distributed/multi_logical_optimizer.h" #include "distributed/multi_router_planner.h" +#include "distributed/pg_dist_node_metadata.h" #include "distributed/pg_version_constants.h" #include "distributed/query_pushdown_planning.h" +#include "distributed/query_colocation_checker.h" +#include "distributed/repartition_executor.h" +#include "distributed/shared_library_init.h" +#include "distributed/shard_pruning.h" #if PG_VERSION_NUM >= PG_VERSION_15 -static DeferredErrorMessage * CheckIfRTETypeIsUnsupported(Query *parse, - RangeTblEntry *rangeTableEntry); -static DeferredErrorMessage * ErrorIfDistTablesNotColocated(Query *parse, - List * - distTablesList, - PlannerRestrictionContext - * - plannerRestrictionContext); -static DeferredErrorMessage * ErrorIfMergeHasUnsupportedTables(Query *parse, - List *rangeTableList, +static int SourceResultPartitionColumnIndex(Query *mergeQuery, + List *sourceTargetList, + CitusTableCacheEntry *targetRelation); +static Var * ValidateAndReturnVarIfSupported(Node *entryExpr); +static DeferredErrorMessage * DeferErrorIfTargetHasFalseClause(Oid targetRelationId, PlannerRestrictionContext * - restrictionContext); + plannerRestrictionContext); +static void ErrorIfMergeQueryQualAndTargetListNotSupported(Oid targetRelationId, + Query *originalQuery); +static void ErrorIfMergeNotSupported(Query *query, Oid targetRelationId, + List *rangeTableList); +static void ErrorIfMergeHasUnsupportedTables(Oid targetRelationId, List *rangeTableList); static bool IsDistributionColumnInMergeSource(Expr *columnExpression, Query *query, bool skipOuterVars); -static DeferredErrorMessage * InsertDistributionColumnMatchesSource(Query *query, - RangeTblEntry * - resultRte); - +static DeferredErrorMessage * DeferErrorIfRoutableMergeNotSupported(Query *query, + List *rangeTableList, + PlannerRestrictionContext + * + plannerRestrictionContext, + Oid targetRelationId); static DeferredErrorMessage * MergeQualAndTargetListFunctionsSupported(Oid resultRelationId, - FromExpr *joinTree, + Query *query, Node *quals, List *targetList, CmdType commandType); + +static DistributedPlan * CreateRouterMergePlan(Oid targetRelationId, Query *originalQuery, + Query *query, + List *rangeTableList, + PlannerRestrictionContext * + plannerRestrictionContext); +static void ErrorIfRepartitionMergeNotSupported(Oid targetRelationId, Query *mergeQuery, + Query *sourceQuery); +static void ConvertSourceRTEIntoSubquery(Query *mergeQuery, + RangeTblEntry *sourceRte, + PlannerRestrictionContext * + plannerRestrictionContext); +static void ConvertSubqueryRTEIntoSubquery(Query *mergeQuery, RangeTblEntry *sourceRte); +static void ConvertCteRTEIntoSubquery(Query *mergeQuery, RangeTblEntry *sourceRte); +static void ConvertRelationRTEIntoSubquery(Query *mergeQuery, + RangeTblEntry *sourceRte, + PlannerRestrictionContext * + plannerRestrictionContext); +static void ErrorIfUnsupportedCTEs(Query *query); +static void ContainsUnsupportedCTEs(Query *query); +static bool MergeQueryCTEWalker(Node *node, void *context); +static DistributedPlan * CreateNonPushableMergePlan(Oid targetRelationId, uint64 planId, + Query *originalQuery, + Query *query, + PlannerRestrictionContext * + plannerRestrictionContext, + ParamListInfo boundParams); +static char * MergeCommandResultIdPrefix(uint64 planId); + #endif /* - * CreateMergePlan attempts to create a plan for the given MERGE SQL - * statement. If planning fails ->planningError is set to a description - * of the failure. + * CreateMergePlan + * 1) Check for conditions that are not supported in MERGE command. + * 2) Try to create a pushable plan + * - Check for conditions suitable for a routable plan, if not found, + * raise deferred error + * 3) Try to create repartition and redistribution plan + * - Check for conditions that prevent repartition strategy, if found, + * raise an exception and quit. */ DistributedPlan * -CreateMergePlan(Query *originalQuery, Query *query, - PlannerRestrictionContext *plannerRestrictionContext) +CreateMergePlan(uint64 planId, Query *originalQuery, Query *query, + PlannerRestrictionContext *plannerRestrictionContext, + ParamListInfo boundParams) +{ + /* function is void for pre-15 versions of Postgres */ + #if PG_VERSION_NUM < PG_VERSION_15 + + ereport(ERROR, (errmsg("MERGE is not supported in pre-15 Postgres versions"))); + + #else + + Oid targetRelationId = ModifyQueryResultRelationId(originalQuery); + + /* + * Step 1: Look for definitive error conditions applicable to both Routable + * and Repartition strategies. + */ + List *rangeTableList = ExtractRangeTableEntryList(originalQuery); + ErrorIfMergeNotSupported(originalQuery, targetRelationId, rangeTableList); + + /* Step 2: Try pushable merge plan */ + DistributedPlan *distributedPlan = + CreateRouterMergePlan(targetRelationId, originalQuery, query, + rangeTableList, plannerRestrictionContext); + + /* Step 3: If the routing plan failed, try for repartition strategy */ + if (distributedPlan->planningError != NULL) + { + RaiseDeferredError(distributedPlan->planningError, DEBUG1); + + /* If MERGE is not routable, try repartitioning */ + distributedPlan = + CreateNonPushableMergePlan(targetRelationId, planId, + originalQuery, query, + plannerRestrictionContext, + boundParams); + } + + return distributedPlan; + + #endif +} + + +#if PG_VERSION_NUM >= PG_VERSION_15 + +/* + * CreateRouterMergePlan attempts to create a pushable plan for the given MERGE + * SQL statement. If the planning fails, the ->planningError is set to a description + * of the failure. + */ +static DistributedPlan * +CreateRouterMergePlan(Oid targetRelationId, Query *originalQuery, Query *query, + List *rangeTableList, + PlannerRestrictionContext *plannerRestrictionContext) { DistributedPlan *distributedPlan = CitusMakeNode(DistributedPlan); - bool multiShardQuery = false; Assert(originalQuery->commandType == CMD_MERGE); + Assert(OidIsValid(targetRelationId)); - distributedPlan->modLevel = RowModifyLevelForQuery(query); - - distributedPlan->planningError = MergeQuerySupported(originalQuery, - multiShardQuery, - plannerRestrictionContext); - + distributedPlan->planningError = DeferErrorIfRoutableMergeNotSupported(originalQuery, + rangeTableList, + plannerRestrictionContext, + targetRelationId); if (distributedPlan->planningError != NULL) { return distributedPlan; } + Var *insertVar = + FetchAndValidateInsertVarIfExists(targetRelationId, originalQuery); + if (insertVar && + !IsDistributionColumnInMergeSource((Expr *) insertVar, originalQuery, true)) + { + ereport(ERROR, (errmsg("MERGE INSERT must use the source table " + "distribution column value"))); + } + Job *job = RouterJob(originalQuery, plannerRestrictionContext, &distributedPlan->planningError); @@ -90,12 +196,14 @@ CreateMergePlan(Query *originalQuery, Query *query, ereport(DEBUG1, (errmsg("Creating MERGE router plan"))); distributedPlan->workerJob = job; + distributedPlan->targetRelationId = targetRelationId; + distributedPlan->modLevel = RowModifyLevelForQuery(query); + + /* There is no coordinator query for MERGE */ distributedPlan->combineQuery = NULL; /* MERGE doesn't support RETURNING clause */ distributedPlan->expectResults = false; - distributedPlan->targetRelationId = ResultRelationOidForQuery(query); - distributedPlan->fastPathRouterPlan = plannerRestrictionContext->fastPathRestrictionContext->fastPathRouterQuery; @@ -104,263 +212,157 @@ CreateMergePlan(Query *originalQuery, Query *query, /* - * MergeQuerySupported does check for a MERGE command in the query, if it finds - * one, it will verify the below criteria - * - Supported tables and combinations in ErrorIfMergeHasUnsupportedTables - * - Distributed tables requirements in ErrorIfDistTablesNotColocated - * - Checks target-lists and functions-in-quals in TargetlistAndFunctionsSupported + * CreateNonPushableMergePlan comes into effect if the router planning fails + * and incorporates two planning strategies. + * + * ExecuteSourceAtWorkerAndRepartition(): Plan the source query independently, + * execute the results into intermediate files, and repartition the files to + * co-locate them with the merge-target table. Subsequently, compile a final + * merge query on the target table using the intermediate results as the data + * source. + * + * ExecuteSourceAtCoordAndRedistribution(): Execute the plan that requires + * evaluation at the coordinator, run the query on the coordinator, and + * redistribute the resulting rows to ensure colocation with the target shards. + * Direct the MERGE SQL operation to the worker nodes' target shards, using the + * intermediate files colocated with the data as the data source. */ -DeferredErrorMessage * -MergeQuerySupported(Query *originalQuery, bool multiShardQuery, - PlannerRestrictionContext *plannerRestrictionContext) +static DistributedPlan * +CreateNonPushableMergePlan(Oid targetRelationId, uint64 planId, Query *originalQuery, + Query *query, + PlannerRestrictionContext *plannerRestrictionContext, + ParamListInfo boundParams) { - /* function is void for pre-15 versions of Postgres */ - #if PG_VERSION_NUM < PG_VERSION_15 + Query *mergeQuery = copyObject(originalQuery); + RangeTblEntry *sourceRte = ExtractMergeSourceRangeTableEntry(mergeQuery); + DistributedPlan *distributedPlan = CitusMakeNode(DistributedPlan); - return NULL; + ereport(DEBUG1, (errmsg("Creating MERGE repartition plan"))); + ConvertSourceRTEIntoSubquery(mergeQuery, sourceRte, plannerRestrictionContext); + Query *sourceQuery = sourceRte->subquery; - #else + ErrorIfRepartitionMergeNotSupported(targetRelationId, mergeQuery, sourceQuery); + + CitusTableCacheEntry *targetRelation = GetCitusTableCacheEntry(targetRelationId); /* - * TODO: For now, we are adding an exception where any volatile or stable - * functions are not allowed in the MERGE query, but this will become too - * restrictive as this will prevent many useful and simple cases, such as, - * INSERT VALUES(ts::timestamp), bigserial column inserts etc. But without - * this restriction, we have a potential danger of some of the function(s) - * getting executed at the worker which will result in incorrect behavior. + * Get the index of the column in the source query that will be utilized + * to repartition the source rows, ensuring colocation with the target */ - if (contain_mutable_functions((Node *) originalQuery)) - { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "non-IMMUTABLE functions are not yet supported " - "in MERGE sql with distributed tables ", - NULL, NULL); - } - - List *rangeTableList = ExtractRangeTableEntryList(originalQuery); - RangeTblEntry *resultRte = ExtractResultRelationRTE(originalQuery); + distributedPlan->sourceResultRepartitionColumnIndex = + SourceResultPartitionColumnIndex(mergeQuery, + sourceQuery->targetList, + targetRelation); /* - * Fast path queries cannot have merge command, and we prevent the remaining here. - * In Citus we have limited support for MERGE, it's allowed only if all - * the tables(target, source or any CTE) tables are are local i.e. a - * combination of Citus local and Non-Citus tables (regular Postgres tables) - * or distributed tables with some restrictions, please see header of routine - * ErrorIfDistTablesNotColocated for details. + * Make a copy of the source query, since following code scribbles it + * but we need to keep the original for EXPLAIN. */ - DeferredErrorMessage *deferredError = - ErrorIfMergeHasUnsupportedTables(originalQuery, - rangeTableList, - plannerRestrictionContext); - if (deferredError) + Query *sourceQueryCopy = copyObject(sourceQuery); + + /* plan the subquery, this may be another distributed query */ + int cursorOptions = CURSOR_OPT_PARALLEL_OK; + PlannedStmt *sourceRowsPlan = pg_plan_query(sourceQueryCopy, NULL, cursorOptions, + boundParams); + bool repartitioned = IsRedistributablePlan(sourceRowsPlan->planTree) && + IsSupportedRedistributionTarget(targetRelationId); + + /* If plan is distributed, no work at the coordinator */ + if (repartitioned) { - /* MERGE's unsupported combination, raise the exception */ - RaiseDeferredError(deferredError, ERROR); + distributedPlan->modifyWithSelectMethod = MODIFY_WITH_SELECT_REPARTITION; + } + else + { + distributedPlan->modifyWithSelectMethod = MODIFY_WITH_SELECT_VIA_COORDINATOR; } - Oid resultRelationId = resultRte->relid; - deferredError = MergeQualAndTargetListFunctionsSupported(resultRelationId, - originalQuery->jointree, - originalQuery->jointree-> - quals, - originalQuery->targetList, - originalQuery->commandType); - if (deferredError) - { - return deferredError; - } + /* There is no coordinator query for MERGE */ + distributedPlan->combineQuery = NULL; - /* - * MERGE is a special case where we have multiple modify statements - * within itself. Check each INSERT/UPDATE/DELETE individually. - */ - MergeAction *action = NULL; - foreach_ptr(action, originalQuery->mergeActionList) - { - Assert(originalQuery->returningList == NULL); - deferredError = MergeQualAndTargetListFunctionsSupported(resultRelationId, - originalQuery->jointree, - action->qual, - action->targetList, - action->commandType); - if (deferredError) - { - /* MERGE's unsupported scenario, raise the exception */ - RaiseDeferredError(deferredError, ERROR); - } - } + /* MERGE doesn't support RETURNING clause */ + distributedPlan->expectResults = false; - deferredError = - InsertDistributionColumnMatchesSource(originalQuery, resultRte); - if (deferredError) - { - /* MERGE's unsupported scenario, raise the exception */ - RaiseDeferredError(deferredError, ERROR); - } + distributedPlan->modLevel = RowModifyLevelForQuery(mergeQuery); + distributedPlan->targetRelationId = targetRelationId; + distributedPlan->intermediateResultIdPrefix = MergeCommandResultIdPrefix(planId); + distributedPlan->modifyQueryViaCoordinatorOrRepartition = mergeQuery; + distributedPlan->selectPlanForModifyViaCoordinatorOrRepartition = sourceRowsPlan; + distributedPlan->fastPathRouterPlan = + plannerRestrictionContext->fastPathRestrictionContext->fastPathRouterQuery; - if (multiShardQuery) - { - deferredError = - DeferErrorIfUnsupportedSubqueryPushdown(originalQuery, - plannerRestrictionContext); - if (deferredError) - { - return deferredError; - } - } - - if (HasDangerousJoinUsing(originalQuery->rtable, (Node *) originalQuery->jointree)) - { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "a join with USING causes an internal naming " - "conflict, use ON instead", NULL, NULL); - } - - return NULL; - - #endif + return distributedPlan; } /* - * IsMergeAllowedOnRelation takes a relation entry and checks if MERGE command is - * permitted on special relations, such as materialized view, returns true only if - * it's a "source" relation. + * ContainsUnsupportedCTEs checks the CTE if it's modifying or recursive CTE, if true, + * raises an exception. */ -bool -IsMergeAllowedOnRelation(Query *parse, RangeTblEntry *rte) +static void +ContainsUnsupportedCTEs(Query *query) { - if (!IsMergeQuery(parse)) + if (query->hasModifyingCTE) + { + ereport(ERROR, (errmsg("CTEs with modifying actions are not yet " + "supported in MERGE"))); + } + + if (query->hasRecursive) + { + ereport(ERROR, (errmsg("Recursive CTEs are not yet " + "supported in MERGE"))); + } +} + + +/* + * MergeQueryCTEWalker descends into the MERGE query to check for any subqueries + */ +static bool +MergeQueryCTEWalker(Node *node, void *context) +{ + if (node == NULL) { return false; } - /* Fetch the MERGE target relation */ - RangeTblEntry *targetRte = rt_fetch(parse->resultRelation, parse->rtable); - - /* Is it a target relation? */ - if (targetRte->relid == rte->relid) + if (IsA(node, Query)) { + Query *query = (Query *) node; + + ContainsUnsupportedCTEs(query); + + query_tree_walker(query, MergeQueryCTEWalker, NULL, 0); + + /* we're done, no need to recurse anymore for this query */ return false; } - return true; -} - - -#if PG_VERSION_NUM >= PG_VERSION_15 - -/* - * ErrorIfDistTablesNotColocated Checks to see if - * - * - There are a minimum of two distributed tables (source and a target). - * - All the distributed tables are indeed colocated. - * - * If any of the conditions are not met, it raises an exception. - */ -static DeferredErrorMessage * -ErrorIfDistTablesNotColocated(Query *parse, List *distTablesList, - PlannerRestrictionContext * - plannerRestrictionContext) -{ - /* All MERGE tables must be distributed */ - if (list_length(distTablesList) < 2) - { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "For MERGE command, both the source and target " - "must be distributed", NULL, NULL); - } - - /* All distributed tables must be colocated */ - if (!AllDistributedRelationsInRTEListColocated(distTablesList)) - { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "For MERGE command, all the distributed tables " - "must be colocated", NULL, NULL); - } - - return NULL; + return expression_tree_walker(node, MergeQueryCTEWalker, context); } /* - * ErrorIfRTETypeIsUnsupported Checks for types of tables that are not supported, such - * as, reference tables, append-distributed tables and materialized view as target relation. - * Routine returns NULL for the supported types, error message for everything else. + * ErrorIfUnsupportedCTEs checks for unsupported CTEs, such as, modifying and recursive */ -static DeferredErrorMessage * -CheckIfRTETypeIsUnsupported(Query *parse, RangeTblEntry *rangeTableEntry) +static void +ErrorIfUnsupportedCTEs(Query *query) { - if (rangeTableEntry->relkind == RELKIND_MATVIEW || - rangeTableEntry->relkind == RELKIND_FOREIGN_TABLE) - { - /* Materialized view or Foreign table as target is not allowed */ - if (IsMergeAllowedOnRelation(parse, rangeTableEntry)) - { - /* Non target relation is ok */ - return NULL; - } - else - { - /* Usually we don't reach this exception as the Postgres parser catches it */ - StringInfo errorMessage = makeStringInfo(); - appendStringInfo(errorMessage, "MERGE command is not allowed on " - "relation type(relkind:%c)", - rangeTableEntry->relkind); - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - errorMessage->data, NULL, NULL); - } - } - - if (rangeTableEntry->relkind != RELKIND_RELATION && - rangeTableEntry->relkind != RELKIND_PARTITIONED_TABLE) - { - StringInfo errorMessage = makeStringInfo(); - appendStringInfo(errorMessage, "Unexpected table type(relkind:%c) " - "in MERGE command", rangeTableEntry->relkind); - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - errorMessage->data, NULL, NULL); - } - - Assert(rangeTableEntry->relid != 0); - - /* Reference tables are not supported yet */ - if (IsCitusTableType(rangeTableEntry->relid, REFERENCE_TABLE)) - { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "MERGE command is not supported on reference " - "tables yet", NULL, NULL); - } - - /* Append/Range tables are not supported */ - if (IsCitusTableType(rangeTableEntry->relid, APPEND_DISTRIBUTED) || - IsCitusTableType(rangeTableEntry->relid, RANGE_DISTRIBUTED)) - { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "For MERGE command, all the distributed tables " - "must be colocated, for append/range distribution, " - "colocation is not supported", NULL, - "Consider using hash distribution instead"); - } - - return NULL; + ContainsUnsupportedCTEs(query); + query_tree_walker(query, MergeQueryCTEWalker, NULL, 0); } /* * ErrorIfMergeHasUnsupportedTables checks if all the tables(target, source or any CTE * present) in the MERGE command are local i.e. a combination of Citus local and Non-Citus - * tables (regular Postgres tables), or distributed tables with some restrictions, please - * see header of routine ErrorIfDistTablesNotColocated for details, raises an exception - * for all other combinations. + * tables (regular Postgres tables), or distributed tables with some restrictions + * raises an exception for all other combinations. */ -static DeferredErrorMessage * -ErrorIfMergeHasUnsupportedTables(Query *parse, List *rangeTableList, - PlannerRestrictionContext *restrictionContext) +static void +ErrorIfMergeHasUnsupportedTables(Oid targetRelationId, List *rangeTableList) { - List *distTablesList = NIL; - bool foundLocalTables = false; - RangeTblEntry *rangeTableEntry = NULL; foreach_ptr(rangeTableEntry, rangeTableList) { @@ -395,74 +397,107 @@ ErrorIfMergeHasUnsupportedTables(Query *parse, List *rangeTableList, case RTE_NAMEDTUPLESTORE: case RTE_RESULT: { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "MERGE command is not supported with " - "Tuplestores and results", - NULL, NULL); + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MERGE command is not supported with " + "Tuplestores and results"))); + break; } default: { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "MERGE command: Unrecognized range table entry.", - NULL, NULL); + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg( + "MERGE command: Unrecognized range table entry(%d) ", + rangeTableEntry->rtekind))); } } /* RTE Relation can be of various types, check them now */ - - /* skip the regular views as they are replaced with subqueries */ - if (rangeTableEntry->relkind == RELKIND_VIEW) + switch (rangeTableEntry->relkind) { - continue; - } + /* skip the regular views as they are replaced with subqueries */ + case RELKIND_VIEW: + { + continue; + } - DeferredErrorMessage *errorMessage = - CheckIfRTETypeIsUnsupported(parse, rangeTableEntry); - if (errorMessage) - { - return errorMessage; + case RELKIND_MATVIEW: + case RELKIND_FOREIGN_TABLE: + { + /* These two cases as a target is not allowed */ + if (relationId == targetRelationId) + { + /* Usually we don't reach this exception as the Postgres parser catches it */ + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MERGE command is not allowed on " + "relation type(relkind:%c)", + rangeTableEntry->relkind))); + } + break; + } + + case RELKIND_RELATION: + case RELKIND_PARTITIONED_TABLE: + { + /* Check for citus/postgres table types */ + Assert(OidIsValid(relationId)); + break; + } + + default: + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Unexpected table type(relkind:%c) " + "in MERGE command", rangeTableEntry->relkind))); + } } /* - * For now, save all distributed tables, later (below) we will - * check for supported combination(s). + * Check for unsupported distributed tables */ - if (IsCitusTableType(relationId, DISTRIBUTED_TABLE)) + if (extern_IsColumnarTableAmTable(relationId) && + relationId == targetRelationId) { - distTablesList = lappend(distTablesList, rangeTableEntry); - continue; + /* Columnar tables are not supported */ + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Columnar table as target is " + "not allowed in MERGE command"))); } - - /* Regular Postgres tables and Citus local tables are allowed */ - if (!IsCitusTable(relationId) || - IsCitusTableType(relationId, CITUS_LOCAL_TABLE)) + else if (IsCitusTableType(relationId, DISTRIBUTED_TABLE)) { - foundLocalTables = true; - continue; + /* Append/Range distributed tables are not supported */ + if (IsCitusTableType(relationId, APPEND_DISTRIBUTED) || + IsCitusTableType(relationId, RANGE_DISTRIBUTED)) + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("For MERGE command, append/range distribution " + "table is not supported yet"))); + } + } + else if (IsCitusTableType(relationId, REFERENCE_TABLE) && + relationId == targetRelationId) + { + /* Reference table as a target is not allowed */ + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Reference table as target is " + "not allowed in MERGE command"))); + } + else if (IsCitusTableType(relationId, CITUS_LOCAL_TABLE)) + { + /* + * All the tables are local/reference, supported as long as + * coordinator is in the metadata. + */ + if (FindCoordinatorNodeId() == -1) + { + ereport(ERROR, (errmsg("Coordinator node is not in " + "the metadata"), + errhint("To ensure that the distributed planner " + "planner the Citus table, please consider " + "configuring a coordinator node"))); + } } - - /* Any other Citus table type missing ? */ } - - /* Ensure all tables are indeed local */ - if (foundLocalTables && list_length(distTablesList) == 0) - { - /* All the tables are local, supported */ - return NULL; - } - else if (foundLocalTables && list_length(distTablesList) > 0) - { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "MERGE command is not supported with " - "combination of distributed/local tables yet", - NULL, NULL); - } - - /* Ensure all distributed tables are indeed co-located */ - return ErrorIfDistTablesNotColocated(parse, - distTablesList, - restrictionContext); } @@ -506,113 +541,21 @@ IsDistributionColumnInMergeSource(Expr *columnExpression, Query *query, bool } -/* - * InsertDistributionColumnMatchesSource check to see if MERGE is inserting a - * value into the target which is not from the source table, if so, it - * raises an exception. - * Note: Inserting random values other than the joined column values will - * result in unexpected behaviour of rows ending up in incorrect shards, to - * prevent such mishaps, we disallow such inserts here. - */ -static DeferredErrorMessage * -InsertDistributionColumnMatchesSource(Query *query, RangeTblEntry *resultRte) -{ - Assert(IsMergeQuery(query)); - - if (!IsCitusTableType(resultRte->relid, DISTRIBUTED_TABLE)) - { - return NULL; - } - - bool foundDistributionColumn = false; - MergeAction *action = NULL; - foreach_ptr(action, query->mergeActionList) - { - /* Skip MATCHED clause as INSERTS are not allowed in it*/ - if (action->matched) - { - continue; - } - - /* NOT MATCHED can have either INSERT or DO NOTHING */ - if (action->commandType == CMD_NOTHING) - { - return NULL; - } - - if (action->targetList == NIL) - { - /* INSERT DEFAULT VALUES is not allowed */ - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "cannot perform MERGE INSERT with DEFAULTS", - NULL, NULL); - } - - Assert(action->commandType == CMD_INSERT); - Var *targetKey = PartitionColumn(resultRte->relid, 1); - - TargetEntry *targetEntry = NULL; - foreach_ptr(targetEntry, action->targetList) - { - AttrNumber originalAttrNo = targetEntry->resno; - - /* skip processing of target table non-partition columns */ - if (originalAttrNo != targetKey->varattno) - { - continue; - } - - foundDistributionColumn = true; - - if (IsA(targetEntry->expr, Var)) - { - if (IsDistributionColumnInMergeSource(targetEntry->expr, query, true)) - { - return NULL; - } - else - { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "MERGE INSERT must use the source table " - "distribution column value", - NULL, NULL); - } - } - else - { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "MERGE INSERT must refer a source column " - "for distribution column ", - NULL, NULL); - } - } - - if (!foundDistributionColumn) - { - return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, - "MERGE INSERT must have distribution column as value", - NULL, NULL); - } - } - - return NULL; -} - - /* * MergeQualAndTargetListFunctionsSupported Checks WHEN/ON clause actions to see what functions * are allowed, if we are updating distribution column, etc. */ static DeferredErrorMessage * -MergeQualAndTargetListFunctionsSupported(Oid resultRelationId, FromExpr *joinTree, +MergeQualAndTargetListFunctionsSupported(Oid resultRelationId, Query *query, Node *quals, List *targetList, CmdType commandType) { - uint32 rangeTableId = 1; + uint32 targetRangeTableIndex = query->resultRelation; + FromExpr *joinTree = query->jointree; Var *distributionColumn = NULL; if (IsCitusTable(resultRelationId) && HasDistributionKey(resultRelationId)) { - distributionColumn = PartitionColumn(resultRelationId, rangeTableId); + distributionColumn = PartitionColumn(resultRelationId, targetRangeTableIndex); } ListCell *targetEntryCell = NULL; @@ -735,4 +678,769 @@ MergeQualAndTargetListFunctionsSupported(Oid resultRelationId, FromExpr *joinTre } +/* + * RepartitionMergeSupported checks if certain conditions cannot accommodate the + * strategy of repartition and redistribution of source rows, the routine will verify + * them and subsequently raises an exception. + */ +static void +ErrorIfRepartitionMergeNotSupported(Oid targetRelationId, Query *mergeQuery, + Query *sourceQuery) +{ + if (!IsCitusTableType(targetRelationId, DISTRIBUTED_TABLE)) + { + ereport(ERROR, + (errmsg("MERGE involving repartition of rows " + "is supported only if the target is distributed"))); + } + + RTEListProperties *queryRteListProperties = GetRTEListPropertiesForQuery(mergeQuery); + if (queryRteListProperties->hasPostgresLocalTable) + { + ereport(ERROR, (errmsg("MERGE INTO an distributed table from " + "Postgres table is not yet supported"))); + } + + queryRteListProperties = GetRTEListPropertiesForQuery(sourceQuery); + if (!queryRteListProperties->hasCitusTable) + { + ereport(ERROR, (errmsg("To MERGE into a distributed table, source must " + "be Citus table(s)"))); + } + + /* + * Sub-queries and CTEs are not allowed in actions and ON clause + */ + if (FindNodeMatchingCheckFunction((Node *) mergeQuery->jointree->quals, + IsNodeSubquery)) + { + ereport(ERROR, + (errmsg("Sub-queries and CTEs are not allowed in ON clause for MERGE " + "with repartitioning"), + errhint("Consider making the source and target colocated " + "and joined on the distribution column to make it a " + "routable query"))); + } + + MergeAction *action = NULL; + foreach_ptr(action, mergeQuery->mergeActionList) + { + if (FindNodeMatchingCheckFunction((Node *) action, IsNodeSubquery)) + { + ereport(ERROR, + (errmsg("Sub-queries and CTEs are not allowed in actions for MERGE " + "with repartitioning"), + errhint("Consider making the source and target colocated " + "and joined on the distribution column to make it a " + "routable query"))); + } + } +} + + +/* + * ConvertCteRTEIntoSubquery takes a RTE_CTE and converts it into a RTE_SUBQUERY. + */ +static void +ConvertCteRTEIntoSubquery(Query *mergeQuery, RangeTblEntry *sourceRte) +{ + CommonTableExpr *sourceCte = NULL; + CommonTableExpr *candidateCte = NULL; + List *cteList = NIL; + + /* + * Presently, CTEs are only permitted within the USING clause, and thus, + * we search for the corresponding one + */ + foreach_ptr(candidateCte, mergeQuery->cteList) + { + if (strcmp(candidateCte->ctename, sourceRte->ctename) == 0) + { + /* The source CTE that will be converted to a subquery */ + sourceCte = candidateCte; + } + else + { + /* + * Save any other CTEs that are referenced, either directly + * or indirectly, in the source CTE. + */ + cteList = lappend(cteList, candidateCte); + } + } + + Assert(sourceCte); + + Query *cteQuery = (Query *) copyObject(sourceCte->ctequery); + + sourceRte->rtekind = RTE_SUBQUERY; + + /* + * As we are delinking the CTE from main query, we have to walk through the + * tree and decrement the ctelevelsup, but by wrapping a subquery, we avoid + * adjusting the ctelevelsup in RTE's + */ + sourceRte->subquery = WrapSubquery(cteQuery); + + /* Copy the rest of the CTEs(if any) and remove them from main query */ + sourceRte->subquery->cteList = copyObject(cteList); + mergeQuery->cteList = NIL; + + /* Zero out CTE-specific fields */ + sourceRte->security_barrier = false; + sourceRte->ctename = NULL; + sourceRte->ctelevelsup = 0; + sourceRte->self_reference = false; + sourceRte->coltypes = NIL; + sourceRte->coltypmods = NIL; + sourceRte->colcollations = NIL; +} + + +/* + * ConvertRelationRTEIntoSubquery takes a RTE_RELATION and converts it into a RTE_SUBQUERY, + * which is basically a SELECT * FROM the relation. + */ +static void +ConvertRelationRTEIntoSubquery(Query *mergeQuery, RangeTblEntry *sourceRte, + PlannerRestrictionContext *plannerRestrictionContext) +{ + Query *sourceResultsQuery = makeNode(Query); + RangeTblRef *newRangeTableRef = makeNode(RangeTblRef); + List *requiredAttributes = NIL; + + RelationRestriction *relationRestriction = + RelationRestrictionForRelation(sourceRte, plannerRestrictionContext); + if (relationRestriction) + { + requiredAttributes = + RequiredAttrNumbersForRelationInternal(mergeQuery, + relationRestriction->index); + } + + sourceResultsQuery->commandType = CMD_SELECT; + + /* we copy the input rteRelation to preserve the rteIdentity */ + RangeTblEntry *newRangeTableEntry = copyObject(sourceRte); + sourceResultsQuery->rtable = list_make1(newRangeTableEntry); + + /* set the FROM expression to the subquery */ + newRangeTableRef->rtindex = SINGLE_RTE_INDEX; + sourceResultsQuery->jointree = makeFromExpr(list_make1(newRangeTableRef), NULL); + sourceResultsQuery->targetList = + CreateAllTargetListForRelation(sourceRte->relid, requiredAttributes); + List *restrictionList = + GetRestrictInfoListForRelation(sourceRte, plannerRestrictionContext); + List *copyRestrictionList = copyObject(restrictionList); + Expr *andedBoundExpressions = make_ands_explicit(copyRestrictionList); + sourceResultsQuery->jointree->quals = (Node *) andedBoundExpressions; + + /* + * Originally the quals were pointing to the RTE and its varno + * was pointing to its index in rtable. However now we converted the RTE + * to a subquery and the quals should be pointing to that subquery, which + * is the only RTE in its rtable, hence we update the varnos so that they + * point to the subquery RTE. + * Originally: rtable: [rte1, current_rte, rte3...] + * Now: rtable: [rte1, subquery[current_rte], rte3...] --subquery[current_rte] refers to its rtable. + */ + Node *quals = sourceResultsQuery->jointree->quals; + UpdateVarNosInNode(quals, SINGLE_RTE_INDEX); + + /* replace the function with the constructed subquery */ + sourceRte->rtekind = RTE_SUBQUERY; + sourceRte->subquery = sourceResultsQuery; + sourceRte->inh = false; +} + + +/* + * ConvertSubqueryRTEIntoSubquery takes a RTE_SUBQUERY and wraps it into a new + * subquery, which eliminates any resjunk columns and adjusts the CTE levelsup. + * In addition, if the subquery happens to be a SET operation, such as, + * (SELECT * from a UNION SELECT * FROM b), it reorders, adds casts and + * prepares a single taget list + */ +static void +ConvertSubqueryRTEIntoSubquery(Query *mergeQuery, RangeTblEntry *sourceRte) +{ + sourceRte->subquery = WrapSubquery(sourceRte->subquery); + + if (list_length(mergeQuery->cteList) > 0) + { + /* copy CTEs from the MERGE ... INTO statement into source subquery */ + sourceRte->subquery->cteList = copyObject(mergeQuery->cteList); + sourceRte->subquery->hasModifyingCTE = mergeQuery->hasModifyingCTE; + mergeQuery->cteList = NIL; + } +} + + +/* + * ConvertSourceRTEIntoSubquery converts MERGE's source RTE into a subquery, + * whose result rows are repartitioned during runtime. + */ +static void +ConvertSourceRTEIntoSubquery(Query *mergeQuery, RangeTblEntry *sourceRte, + PlannerRestrictionContext *plannerRestrictionContext) +{ + switch (sourceRte->rtekind) + { + case RTE_SUBQUERY: + { + ConvertSubqueryRTEIntoSubquery(mergeQuery, sourceRte); + return; + } + + case RTE_RELATION: + { + ConvertRelationRTEIntoSubquery(mergeQuery, + sourceRte, plannerRestrictionContext); + return; + } + + case RTE_CTE: + { + ConvertCteRTEIntoSubquery(mergeQuery, sourceRte); + return; + } + + default: + { + ereport(ERROR, (errmsg("Currently, Citus only supports " + "table, subquery, and CTEs as " + "valid sources for the MERGE " + "operation"))); + } + } +} + + +/* + * ErrorIfMergeNotSupported Checks for conditions that are not supported in either + * the routable or repartition strategies. It checks for + * - Supported table types and their combinations + * - Check the target lists and quals of both the query and merge actions + * - Supported CTEs + */ +static void +ErrorIfMergeNotSupported(Query *query, Oid targetRelationId, List *rangeTableList) +{ + ErrorIfMergeHasUnsupportedTables(targetRelationId, rangeTableList); + ErrorIfMergeQueryQualAndTargetListNotSupported(targetRelationId, query); + ErrorIfUnsupportedCTEs(query); +} + + +/* + * DeferErrorIfTargetHasFalseClause checks for the presence of a false clause in the + * target relation and throws an exception if found. Router planner prunes all the shards + * for relations with such clauses, resulting in no task generation for the job. However, + * in the case of a MERGE query, tasks still need to be generated for the shards of the + * source relation. + */ +static DeferredErrorMessage * +DeferErrorIfTargetHasFalseClause(Oid targetRelationId, + PlannerRestrictionContext *plannerRestrictionContext) +{ + ListCell *restrictionCell = NULL; + foreach(restrictionCell, + plannerRestrictionContext->relationRestrictionContext->relationRestrictionList) + { + RelationRestriction *relationRestriction = + (RelationRestriction *) lfirst(restrictionCell); + Oid relationId = relationRestriction->relationId; + + /* Check only for target relation */ + if (relationId != targetRelationId) + { + continue; + } + + List *baseRestrictionList = relationRestriction->relOptInfo->baserestrictinfo; + List *restrictClauseList = get_all_actual_clauses(baseRestrictionList); + if (ContainsFalseClause(restrictClauseList)) + { + return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, + "Routing query is not possible with " + "no shards for target", NULL, NULL); + } + } + return NULL; +} + + +/* + * DeferErrorIfRoutableMergeNotSupported Checks for conditions that prevent pushable planning, if + * found, raises a deferred error, which then continues to try repartitioning strategy. + */ +static DeferredErrorMessage * +DeferErrorIfRoutableMergeNotSupported(Query *query, List *rangeTableList, + PlannerRestrictionContext *plannerRestrictionContext, + Oid targetRelationId) +{ + List *distTablesList = NIL; + List *refTablesList = NIL; + List *localTablesList = NIL; + RangeTblEntry *rangeTableEntry = NULL; + + foreach_ptr(rangeTableEntry, rangeTableList) + { + Oid relationId = rangeTableEntry->relid; + + if (IsCitusTableType(relationId, DISTRIBUTED_TABLE)) + { + distTablesList = lappend(distTablesList, rangeTableEntry); + } + else if (IsCitusTableType(relationId, REFERENCE_TABLE)) + { + refTablesList = lappend(refTablesList, rangeTableEntry); + } + else if (IsCitusTableType(relationId, CITUS_LOCAL_TABLE)) + { + localTablesList = lappend(localTablesList, rangeTableEntry); + } + } + + if (list_length(distTablesList) > 0 && list_length(refTablesList) > 0) + { + ereport(DEBUG1, (errmsg( + "A mix of distributed and reference table, try repartitioning"))); + return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, + "A mix of distributed and reference table, " + "routable query is not possible", NULL, NULL); + } + + if (list_length(distTablesList) > 0 && list_length(localTablesList) > 0) + { + ereport(DEBUG1, (errmsg( + "A mix of distributed and local table, try repartitioning"))); + return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, + "A mix of distributed and citus-local table, " + "routable query is not possible", NULL, NULL); + } + + /* + * If all tables are either local or reference tables, no need to proceed further down + * as the below checks are applicable for distributed tables only + */ + if (list_length(distTablesList) == 0) + { + return NULL; + } + + /* Only one distributed table is involved in the MERGE */ + if (list_length(distTablesList) == 1) + { + return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, + "There is only one distributed table, merge is not " + "pushable, try repartitioning", NULL, NULL); + } + + /* Ensure all distributed tables are indeed co-located */ + if (!AllDistributedRelationsInRTEListColocated(distTablesList)) + { + ereport(DEBUG1, (errmsg("Distributed tables are not co-located, try " + "repartitioning"))); + return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, + "For MERGE command, all the distributed tables " + "must be colocated", NULL, NULL); + } + + DeferredErrorMessage *deferredError = + DeferErrorIfUnsupportedSubqueryPushdown(query, + plannerRestrictionContext); + if (deferredError) + { + ereport(DEBUG1, (errmsg("Sub-query is not pushable, try repartitioning"))); + return deferredError; + } + + if (HasDangerousJoinUsing(query->rtable, (Node *) query->jointree)) + { + ereport(DEBUG1, (errmsg( + "Query has ambigious joins, merge is not pushable, try repartitioning"))); + return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, + "a join with USING causes an internal naming " + "conflict, use ON instead", NULL, NULL); + } + + deferredError = DeferErrorIfTargetHasFalseClause(targetRelationId, + plannerRestrictionContext); + if (deferredError) + { + ereport(DEBUG1, (errmsg("Target relation has a filter of the " + "form: false (AND ..), which results " + "in empty shards, but we still need " + "to evaluate NOT-MATCHED clause, try " + "repartitioning"))); + return deferredError; + } + return NULL; +} + + +/* + * ErrorIfMergeQueryQualAndTargetListNotSupported does check for a MERGE command in the query, if it finds + * one, it will verify the below criteria + * - Distributed tables co-location requirements + * - Checks target-lists and functions-in-quals in TargetlistAndFunctionsSupported + */ +static void +ErrorIfMergeQueryQualAndTargetListNotSupported(Oid targetRelationId, Query *originalQuery) +{ + /* + * TODO: For now, we are adding an exception where any volatile or stable + * functions are not allowed in the MERGE query, but this will become too + * restrictive as this will prevent many useful and simple cases, such as, + * INSERT VALUES(ts::timestamp), bigserial column inserts etc. But without + * this restriction, we have a potential danger of some of the function(s) + * getting executed at the worker which will result in incorrect behavior. + */ + if (contain_mutable_functions((Node *) originalQuery)) + { + ereport(ERROR, (errmsg("non-IMMUTABLE functions are not yet " + "supported in MERGE sql with distributed tables"))); + } + + DeferredErrorMessage *deferredError = + MergeQualAndTargetListFunctionsSupported(targetRelationId, + originalQuery, + originalQuery->jointree->quals, + originalQuery->targetList, + originalQuery->commandType); + + if (deferredError) + { + RaiseDeferredError(deferredError, ERROR); + } + + /* + * MERGE is a special case where we have multiple modify statements + * within itself. Check each INSERT/UPDATE/DELETE individually. + */ + MergeAction *action = NULL; + foreach_ptr(action, originalQuery->mergeActionList) + { + Assert(originalQuery->returningList == NULL); + deferredError = MergeQualAndTargetListFunctionsSupported(targetRelationId, + originalQuery, + action->qual, + action->targetList, + action->commandType); + if (deferredError) + { + /* MERGE's unsupported scenario, raise the exception */ + RaiseDeferredError(deferredError, ERROR); + } + } +} + + +/* + * MergeCommandResultIdPrefix returns the prefix to use for intermediate results of + * an MERGE INTO ... USING source-query results via the coordinator. + */ +static char * +MergeCommandResultIdPrefix(uint64 planId) +{ + StringInfo resultIdPrefix = makeStringInfo(); + appendStringInfo(resultIdPrefix, "merge_into_" UINT64_FORMAT, planId); + return resultIdPrefix->data; +} + + +/* + * ValidateAndReturnVarIfSupported Checks for valid expressions of type Var, and + * returns the Var if it finds one, for everything else, raises an exception. + */ +static Var * +ValidateAndReturnVarIfSupported(Node *entryExpr) +{ + if (!IsA(entryExpr, Var)) + { + ereport(ERROR, (errmsg("MERGE INSERT is using unsupported expression type " + "for distribution column"), + errdetail("Inserting arbitrary values that don't correspond " + "to the joined column values can lead to unpredictable " + "outcomes where rows are incorrectly distributed " + "among different shards"))); + } + + /* Found a Var inserting into target's distribution column */ + return (Var *) entryExpr; +} + + +/* + * SourceResultPartitionColumnIndex collects all Join conditions from the + * ON clause and verifies if there is a join, either left or right, with + * the distribution column of the given target. Once a match is found, it + * returns the index of that match in the source's target list. + */ +static int +SourceResultPartitionColumnIndex(Query *mergeQuery, List *sourceTargetList, + CitusTableCacheEntry *targetRelation) +{ + if (IsCitusTableType(targetRelation->relationId, SINGLE_SHARD_DISTRIBUTED)) + { + ereport(ERROR, (errmsg("MERGE operation on non-colocated " + "distributed table(s) without a shard " + "key is not yet supported"))); + } + + /* Get all the Join conditions from the ON clause */ + List *mergeJoinConditionList = WhereClauseList(mergeQuery->jointree); + Var *targetColumn = targetRelation->partitionColumn; + Var *sourceRepartitionVar = NULL; + + OpExpr *validJoinClause = + SinglePartitionJoinClause(list_make1(targetColumn), mergeJoinConditionList); + if (!validJoinClause) + { + ereport(ERROR, (errmsg("The required join operation is missing between " + "the target's distribution column and any " + "expression originating from the source. The " + "issue may arise from either a non-equi-join or " + "a mismatch in the datatypes of the columns being " + "joined."), + errdetail("Without a equi-join condition on the target's " + "distribution column, the source rows " + "cannot be efficiently redistributed, and " + "the NOT-MATCHED condition cannot be evaluated " + "unambiguously. This can result in incorrect or " + "unexpected results when attempting to merge " + "tables in a distributed setting"))); + } + + /* both are verified in SinglePartitionJoinClause to not be NULL, assert is to guard */ + Var *leftColumn = LeftColumnOrNULL(validJoinClause); + Var *rightColumn = RightColumnOrNULL(validJoinClause); + + Assert(leftColumn != NULL); + Assert(rightColumn != NULL); + + if (equal(targetColumn, leftColumn)) + { + sourceRepartitionVar = rightColumn; + } + else if (equal(targetColumn, rightColumn)) + { + sourceRepartitionVar = leftColumn; + } + + /* Either we find an insert-action or it's not relevant for certain class of tables */ + Var *insertVar = + FetchAndValidateInsertVarIfExists(targetRelation->relationId, mergeQuery); + if (insertVar) + { + /* INSERT action, must choose joining column for inserted value */ + bool joinedOnInsertColumn = + JoinOnColumns(list_make1(targetColumn), insertVar, mergeJoinConditionList); + if (joinedOnInsertColumn) + { + sourceRepartitionVar = insertVar; + } + else + { + ereport(ERROR, (errmsg("MERGE INSERT must use the " + "source's joining column for " + "target's distribution column"))); + } + } + + Assert(sourceRepartitionVar); + + int sourceResultRepartitionColumnIndex = + DistributionColumnIndex(sourceTargetList, sourceRepartitionVar); + + if (sourceResultRepartitionColumnIndex == -1) + { + ereport(ERROR, + (errmsg("Unexpected column index of the source list"))); + } + else + { + ereport(DEBUG1, (errmsg("Using column - index:%d from the source list " + "to redistribute", sourceResultRepartitionColumnIndex))); + } + + return sourceResultRepartitionColumnIndex; +} + + #endif + + +/* + * ExtractMergeSourceRangeTableEntry returns the range table entry of source + * table or source query in USING clause. + */ +RangeTblEntry * +ExtractMergeSourceRangeTableEntry(Query *query) +{ + /* function is void for pre-15 versions of Postgres */ + #if PG_VERSION_NUM < PG_VERSION_15 + + ereport(ERROR, (errmsg("MERGE is not supported in pre-15 Postgres versions"))); + + #else + + Assert(IsMergeQuery(query)); + + List *fromList = query->jointree->fromlist; + + /* We should have only one RTE(MergeStmt->sourceRelation) in the from-list */ + if (list_length(fromList) != 1) + { + ereport(ERROR, (errmsg("Unexpected source list in MERGE sql USING clause"))); + } + + RangeTblRef *reference = linitial(fromList); + + /* + * The planner sometimes generates JoinExprs internally; these can + * have rtindex = 0 if there are no join alias variables referencing + * such joins. + */ + if (reference->rtindex == 0) + { + ereport(ERROR, (errmsg("Source is not an explicit query"), + errhint("Source query is a Join expression, " + "try converting into a query as SELECT * " + "FROM (..Join..)"))); + } + + Assert(reference->rtindex >= 1); + RangeTblEntry *subqueryRte = rt_fetch(reference->rtindex, query->rtable); + + return subqueryRte; + + #endif +} + + +/* + * FetchAndValidateInsertVarIfExists checks to see if MERGE is inserting a + * value into the target which is not from the source table, if so, it + * raises an exception. The return value is the Var that's being inserted + * into the target's distribution column, If no INSERT action exist, it + * simply returns a NULL. + * Note: Inserting random values other than the joined column values will + * result in unexpected behaviour of rows ending up in incorrect shards, to + * prevent such mishaps, we disallow such inserts here. + */ +Var * +FetchAndValidateInsertVarIfExists(Oid targetRelationId, Query *query) +{ + /* function is void for pre-15 versions of Postgres */ + #if PG_VERSION_NUM < PG_VERSION_15 + + ereport(ERROR, (errmsg("MERGE is not supported in pre-15 Postgres versions"))); + + #else + + Assert(IsMergeQuery(query)); + + if (!IsCitusTableType(targetRelationId, DISTRIBUTED_TABLE)) + { + return NULL; + } + + if (!HasDistributionKey(targetRelationId)) + { + return NULL; + } + + bool foundDistributionColumn = false; + MergeAction *action = NULL; + uint32 targetRangeTableIndex = query->resultRelation; + foreach_ptr(action, query->mergeActionList) + { + /* Skip MATCHED clause as INSERTS are not allowed in it */ + if (action->matched) + { + continue; + } + + /* NOT MATCHED can have either INSERT or DO NOTHING */ + if (action->commandType == CMD_NOTHING) + { + return NULL; + } + + if (action->targetList == NIL) + { + /* INSERT DEFAULT VALUES is not allowed */ + ereport(ERROR, (errmsg("cannot perform MERGE INSERT with DEFAULTS"), + errdetail("Inserting arbitrary values that don't correspond " + "to the joined column values can lead to " + "unpredictable outcomes where rows are " + "incorrectly distributed among different " + "shards"))); + } + + Assert(action->commandType == CMD_INSERT); + Var *targetDistributionKey = + PartitionColumn(targetRelationId, targetRangeTableIndex); + + TargetEntry *targetEntry = NULL; + foreach_ptr(targetEntry, action->targetList) + { + AttrNumber originalAttrNo = targetEntry->resno; + + /* skip processing of target table non-distribution columns */ + if (originalAttrNo != targetDistributionKey->varattno) + { + continue; + } + + foundDistributionColumn = true; + + Node *insertExpr = + strip_implicit_coercions((Node *) copyObject(targetEntry->expr)); + return ValidateAndReturnVarIfSupported(insertExpr); + } + + if (!foundDistributionColumn) + { + ereport(ERROR, + (errmsg("MERGE INSERT must have distribution column as value"))); + } + } + + return NULL; + + #endif +} + + +/* + * IsLocalTableModification returns true if the table modified is a Postgres table. + * We do not support recursive planning for MERGE yet, so we could have a join + * between local and Citus tables. Only allow local tables when it is the target table. + */ +bool +IsLocalTableModification(Oid targetRelationId, Query *query, uint64 shardId, + RTEListProperties *rteProperties) +{ + /* No-op for SELECT command */ + if (!IsModifyCommand(query)) + { + return false; + } + + /* For MERGE, we have to check only the target relation */ + if (IsMergeQuery(query) && !IsCitusTable(targetRelationId)) + { + /* Postgres table */ + return true; + } + + if (shardId == INVALID_SHARD_ID && ContainsOnlyLocalTables(rteProperties)) + { + return true; + } + + return false; +} diff --git a/src/backend/distributed/planner/multi_explain.c b/src/backend/distributed/planner/multi_explain.c index c23509df1..674077b46 100644 --- a/src/backend/distributed/planner/multi_explain.c +++ b/src/backend/distributed/planner/multi_explain.c @@ -33,6 +33,7 @@ #include "distributed/insert_select_planner.h" #include "distributed/insert_select_executor.h" #include "distributed/listutils.h" +#include "distributed/merge_planner.h" #include "distributed/multi_executor.h" #include "distributed/multi_explain.h" #include "distributed/multi_logical_optimizer.h" @@ -234,7 +235,7 @@ NonPushableInsertSelectExplainScan(CustomScanState *node, List *ancestors, { CitusScanState *scanState = (CitusScanState *) node; DistributedPlan *distributedPlan = scanState->distributedPlan; - Query *insertSelectQuery = distributedPlan->insertSelectQuery; + Query *insertSelectQuery = distributedPlan->modifyQueryViaCoordinatorOrRepartition; RangeTblEntry *selectRte = ExtractSelectRangeTableEntry(insertSelectQuery); /* @@ -244,8 +245,8 @@ NonPushableInsertSelectExplainScan(CustomScanState *node, List *ancestors, */ Query *queryCopy = copyObject(selectRte->subquery); - bool repartition = distributedPlan->insertSelectMethod == INSERT_SELECT_REPARTITION; - + bool repartition = + distributedPlan->modifyWithSelectMethod == MODIFY_WITH_SELECT_REPARTITION; if (es->analyze) { @@ -281,6 +282,67 @@ NonPushableInsertSelectExplainScan(CustomScanState *node, List *ancestors, } +/* + * NonPushableMergeSqlExplainScan is a custom scan explain callback function + * which is used to print explain information of a Citus plan for MERGE INTO + * distributed_table USING (source query/table), where source can be any query + * whose results are repartitioned to colocated with the target table. + */ +void +NonPushableMergeCommandExplainScan(CustomScanState *node, List *ancestors, + struct ExplainState *es) +{ + CitusScanState *scanState = (CitusScanState *) node; + DistributedPlan *distributedPlan = scanState->distributedPlan; + Query *mergeQuery = distributedPlan->modifyQueryViaCoordinatorOrRepartition; + RangeTblEntry *sourceRte = ExtractMergeSourceRangeTableEntry(mergeQuery); + + /* + * Create a copy because ExplainOneQuery can modify the query, and later + * executions of prepared statements might require it. See + * https://github.com/citusdata/citus/issues/3947 for what can happen. + */ + Query *sourceQueryCopy = copyObject(sourceRte->subquery); + bool repartition = + distributedPlan->modifyWithSelectMethod == MODIFY_WITH_SELECT_REPARTITION; + + if (es->analyze) + { + ereport(ERROR, (errmsg("EXPLAIN ANALYZE is currently not supported for " + "MERGE INTO ... commands with repartitioning"))); + } + + Oid targetRelationId = ModifyQueryResultRelationId(mergeQuery); + StringInfo mergeMethodMessage = makeStringInfo(); + appendStringInfo(mergeMethodMessage, + "MERGE INTO %s method", get_rel_name(targetRelationId)); + + if (repartition) + { + ExplainPropertyText(mergeMethodMessage->data, "repartition", es); + } + else + { + ExplainPropertyText(mergeMethodMessage->data, "pull to coordinator", es); + } + + ExplainOpenGroup("Source Query", "Source Query", false, es); + + /* explain the MERGE source query */ + IntoClause *into = NULL; + ParamListInfo params = NULL; + + /* + * With PG14, we need to provide a string here, for now we put an empty + * string, which is valid according to postgres. + */ + char *queryString = pstrdup(""); + ExplainOneQuery(sourceQueryCopy, 0, into, es, queryString, params, NULL); + + ExplainCloseGroup("Source Query", "Source Query", false, es); +} + + /* * ExplainSubPlans generates EXPLAIN output for subplans for CTEs * and complex subqueries. Because the planning for these queries @@ -1039,8 +1101,8 @@ worker_save_query_explain_analyze(PG_FUNCTION_ARGS) TupleDesc tupleDescriptor = NULL; Tuplestorestate *tupleStore = SetupTuplestore(fcinfo, &tupleDescriptor); DestReceiver *tupleStoreDest = CreateTuplestoreDestReceiver(); - SetTuplestoreDestReceiverParams_compat(tupleStoreDest, tupleStore, - CurrentMemoryContext, false, NULL, NULL); + SetTuplestoreDestReceiverParams(tupleStoreDest, tupleStore, + CurrentMemoryContext, false, NULL, NULL); List *parseTreeList = pg_parse_query(queryString); if (list_length(parseTreeList) != 1) @@ -1064,15 +1126,9 @@ worker_save_query_explain_analyze(PG_FUNCTION_ARGS) Query *analyzedQuery = parse_analyze_varparams_compat(parseTree, queryString, ¶mTypes, &numParams, NULL); -#if PG_VERSION_NUM >= PG_VERSION_14 - /* pg_rewrite_query is a wrapper around QueryRewrite with some debugging logic */ List *queryList = pg_rewrite_query(analyzedQuery); -#else - /* pg_rewrite_query is not yet public in PostgreSQL 13 */ - List *queryList = QueryRewrite(analyzedQuery); -#endif if (list_length(queryList) != 1) { ereport(ERROR, (errmsg("cannot EXPLAIN ANALYZE a query rewritten " diff --git a/src/backend/distributed/planner/multi_join_order.c b/src/backend/distributed/planner/multi_join_order.c index b1195c664..79007b70d 100644 --- a/src/backend/distributed/planner/multi_join_order.c +++ b/src/backend/distributed/planner/multi_join_order.c @@ -81,8 +81,6 @@ static JoinOrderNode * CartesianProductReferenceJoin(JoinOrderNode *joinNode, JoinType joinType); static JoinOrderNode * LocalJoin(JoinOrderNode *joinNode, TableEntry *candidateTable, List *applicableJoinClauses, JoinType joinType); -static bool JoinOnColumns(List *currentPartitionColumnList, Var *candidatePartitionColumn, - List *joinClauseList); static JoinOrderNode * SinglePartitionJoin(JoinOrderNode *joinNode, TableEntry *candidateTable, List *applicableJoinClauses, @@ -212,7 +210,7 @@ ExtractLeftMostRangeTableIndex(Node *node, int *rangeTableIndex) /* * JoinOnColumns determines whether two columns are joined by a given join clause list. */ -static bool +bool JoinOnColumns(List *currentPartitionColumnList, Var *candidateColumn, List *joinClauseList) { @@ -1404,7 +1402,7 @@ DistPartitionKeyOrError(Oid relationId) if (partitionKey == NULL) { ereport(ERROR, (errmsg( - "no distribution column found for relation %d, because it is a reference table", + "no distribution column found for relation %d", relationId))); } diff --git a/src/backend/distributed/planner/multi_logical_optimizer.c b/src/backend/distributed/planner/multi_logical_optimizer.c index 851afc4b6..455f050a0 100644 --- a/src/backend/distributed/planner/multi_logical_optimizer.c +++ b/src/backend/distributed/planner/multi_logical_optimizer.c @@ -1855,11 +1855,7 @@ MasterAggregateExpression(Aggref *originalAggregate, { /* array_cat_agg() takes anyarray as input */ catAggregateName = ARRAY_CAT_AGGREGATE_NAME; -#if PG_VERSION_NUM >= PG_VERSION_14 catInputType = ANYCOMPATIBLEARRAYOID; -#else - catInputType = ANYARRAYOID; -#endif } else if (aggregateType == AGGREGATE_JSONB_AGG || aggregateType == AGGREGATE_JSONB_OBJECT_AGG) @@ -1897,8 +1893,6 @@ MasterAggregateExpression(Aggref *originalAggregate, if (aggregateType == AGGREGATE_ARRAY_AGG) { -#if PG_VERSION_NUM >= PG_VERSION_14 - /* * Postgres expects the type of the array here such as INT4ARRAYOID. * Hence we set it to workerReturnType. If we set this to @@ -1906,9 +1900,6 @@ MasterAggregateExpression(Aggref *originalAggregate, * "argument declared anycompatiblearray is not an array but type anycompatiblearray" */ newMasterAggregate->aggargtypes = list_make1_oid(workerReturnType); -#else - newMasterAggregate->aggargtypes = list_make1_oid(ANYARRAYOID); -#endif } else { @@ -2985,7 +2976,7 @@ AppendTargetEntryToGroupClause(TargetEntry *targetEntry, Expr *targetExpr PG_USED_FOR_ASSERTS_ONLY = targetEntry->expr; /* we currently only support appending Var target entries */ - AssertArg(IsA(targetExpr, Var)); + Assert(IsA(targetExpr, Var)); Var *targetColumn = (Var *) targetEntry->expr; SortGroupClause *groupByClause = CreateSortGroupClause(targetColumn); @@ -3625,8 +3616,8 @@ static Oid CitusFunctionOidWithSignature(char *functionName, int numargs, Oid *argtypes) { List *aggregateName = list_make2(makeString("pg_catalog"), makeString(functionName)); - FuncCandidateList clist = FuncnameGetCandidates_compat(aggregateName, numargs, NIL, - false, false, false, true); + FuncCandidateList clist = FuncnameGetCandidates(aggregateName, numargs, NIL, + false, false, false, true); for (; clist; clist = clist->next) { diff --git a/src/backend/distributed/planner/multi_logical_planner.c b/src/backend/distributed/planner/multi_logical_planner.c index d9322bf5e..fa9e5bb61 100644 --- a/src/backend/distributed/planner/multi_logical_planner.c +++ b/src/backend/distributed/planner/multi_logical_planner.c @@ -272,7 +272,7 @@ TargetListOnPartitionColumn(Query *query, List *targetEntryList) if (!targetListOnPartitionColumn) { if (!FindNodeMatchingCheckFunctionInRangeTableList(query->rtable, - IsDistributedTableRTE)) + IsTableWithDistKeyRTE)) { targetListOnPartitionColumn = true; } @@ -379,6 +379,20 @@ IsReferenceTableRTE(Node *node) } +/* + * IsTableWithDistKeyRTE gets a node and returns true if the node + * is a range table relation entry that points to a distributed table + * that has a distribution column. + */ +bool +IsTableWithDistKeyRTE(Node *node) +{ + Oid relationId = NodeTryGetRteRelid(node); + return relationId != InvalidOid && IsCitusTable(relationId) && + HasDistributionKey(relationId); +} + + /* * FullCompositeFieldList gets a composite field list, and checks if all fields * of composite type are used in the list. @@ -1014,7 +1028,8 @@ ErrorHintRequired(const char *errorHint, Query *queryTree) { continue; } - else if (IsCitusTableType(relationId, HASH_DISTRIBUTED)) + else if (IsCitusTableType(relationId, HASH_DISTRIBUTED) || + IsCitusTableType(relationId, SINGLE_SHARD_DISTRIBUTED)) { int colocationId = TableColocationId(relationId); colocationIdList = list_append_unique_int(colocationIdList, colocationId); diff --git a/src/backend/distributed/planner/multi_physical_planner.c b/src/backend/distributed/planner/multi_physical_planner.c index f488a1cd5..21befa6f2 100644 --- a/src/backend/distributed/planner/multi_physical_planner.c +++ b/src/backend/distributed/planner/multi_physical_planner.c @@ -28,6 +28,7 @@ #include "access/xlog.h" #include "catalog/pg_aggregate.h" #include "catalog/pg_am.h" +#include "catalog/pg_collation.h" #include "catalog/pg_operator.h" #include "catalog/pg_type.h" #include "commands/defrem.h" @@ -69,6 +70,7 @@ #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "parser/parse_relation.h" +#include "parser/parse_type.h" #include "parser/parsetree.h" #include "rewrite/rewriteManip.h" #include "utils/builtins.h" @@ -79,10 +81,11 @@ #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/typcache.h" /* RepartitionJoinBucketCountPerNode determines bucket amount during repartitions */ -int RepartitionJoinBucketCountPerNode = 8; +int RepartitionJoinBucketCountPerNode = 4; /* Policy to use when assigning tasks to worker nodes */ int TaskAssignmentPolicy = TASK_ASSIGNMENT_GREEDY; @@ -231,6 +234,11 @@ static List * FetchEqualityAttrNumsForRTEBoolExpr(BoolExpr *boolExpr); static List * FetchEqualityAttrNumsForList(List *nodeList); static int PartitionColumnIndex(Var *targetVar, List *targetList); static List * GetColumnOriginalIndexes(Oid relationId); +static bool QueryTreeHasImproperForDeparseNodes(Node *inputNode, void *context); +static Node * AdjustImproperForDeparseNodes(Node *inputNode, void *context); +static bool IsImproperForDeparseRelabelTypeNode(Node *inputNode); +static bool IsImproperForDeparseCoerceViaIONode(Node *inputNode); +static CollateExpr * RelabelTypeToCollateExpr(RelabelType *relabelType); /* @@ -2171,8 +2179,9 @@ QueryPushdownSqlTaskList(Query *query, uint64 jobId, { List *sqlTaskList = NIL; uint32 taskIdIndex = 1; /* 0 is reserved for invalid taskId */ - int shardCount = 0; - bool *taskRequiredForShardIndex = NULL; + int minShardOffset = INT_MAX; + int prevShardCount = 0; + Bitmapset *taskRequiredForShardIndex = NULL; /* error if shards are not co-partitioned */ ErrorIfUnsupportedShardDistribution(query); @@ -2186,10 +2195,6 @@ QueryPushdownSqlTaskList(Query *query, uint64 jobId, return NIL; } - /* defaults to be used if this is a reference table-only query */ - int minShardOffset = 0; - int maxShardOffset = 0; - RelationRestriction *relationRestriction = NULL; List *prunedShardList = NULL; @@ -2205,7 +2210,7 @@ QueryPushdownSqlTaskList(Query *query, uint64 jobId, } /* we expect distributed tables to have the same shard count */ - if (shardCount > 0 && shardCount != cacheEntry->shardIntervalArrayLength) + if (prevShardCount > 0 && prevShardCount != cacheEntry->shardIntervalArrayLength) { *planningError = DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, "shard counts of co-located tables do not " @@ -2213,16 +2218,7 @@ QueryPushdownSqlTaskList(Query *query, uint64 jobId, NULL, NULL); return NIL; } - - if (taskRequiredForShardIndex == NULL) - { - shardCount = cacheEntry->shardIntervalArrayLength; - taskRequiredForShardIndex = (bool *) palloc0(shardCount); - - /* there is a distributed table, find the shard range */ - minShardOffset = shardCount; - maxShardOffset = -1; - } + prevShardCount = cacheEntry->shardIntervalArrayLength; /* * For left joins we don't care about the shards pruned for the right hand side. @@ -2244,32 +2240,26 @@ QueryPushdownSqlTaskList(Query *query, uint64 jobId, { int shardIndex = shardInterval->shardIndex; - taskRequiredForShardIndex[shardIndex] = true; - + taskRequiredForShardIndex = + bms_add_member(taskRequiredForShardIndex, shardIndex); minShardOffset = Min(minShardOffset, shardIndex); - maxShardOffset = Max(maxShardOffset, shardIndex); } } /* - * To avoid iterating through all shards indexes we keep the minimum and maximum - * offsets of shards that were not pruned away. This optimisation is primarily - * relevant for queries on range-distributed tables that, due to range filters, - * prune to a small number of adjacent shards. + * We keep track of minShardOffset to skip over a potentially big amount of pruned + * shards. However, we need to start at minShardOffset - 1 to make sure we don't + * miss to first/min shard recorder as bms_next_member will return the first member + * added after shardOffset. Meaning minShardOffset would be the first member we + * expect. * - * In other cases, such as an OR condition on a hash-distributed table, we may - * still visit most or all shards even if some of them were pruned away. However, - * given that hash-distributed tables typically only have a few shards the - * iteration is still very fast. + * We don't have to keep track of maxShardOffset as the bitmapset will only have been + * allocated till the last shard we have added. Therefore, the iterator will quickly + * identify the end of the bitmapset. */ - for (int shardOffset = minShardOffset; shardOffset <= maxShardOffset; shardOffset++) + int shardOffset = minShardOffset - 1; + while ((shardOffset = bms_next_member(taskRequiredForShardIndex, shardOffset)) >= 0) { - if (taskRequiredForShardIndex != NULL && !taskRequiredForShardIndex[shardOffset]) - { - /* this shard index is pruned away for all relations */ - continue; - } - Task *subqueryTask = QueryPushdownTaskCreate(query, shardOffset, relationRestrictionContext, taskIdIndex, @@ -2359,7 +2349,7 @@ ErrorIfUnsupportedShardDistribution(Query *query) ListCell *relationIdCell = NULL; uint32 relationIndex = 0; uint32 rangeDistributedRelationCount = 0; - uint32 hashDistributedRelationCount = 0; + uint32 hashDistOrSingleShardRelCount = 0; uint32 appendDistributedRelationCount = 0; foreach(relationIdCell, relationIdList) @@ -2371,9 +2361,10 @@ ErrorIfUnsupportedShardDistribution(Query *query) nonReferenceRelations = lappend_oid(nonReferenceRelations, relationId); } - else if (IsCitusTableType(relationId, HASH_DISTRIBUTED)) + else if (IsCitusTableType(relationId, HASH_DISTRIBUTED) || + IsCitusTableType(relationId, SINGLE_SHARD_DISTRIBUTED)) { - hashDistributedRelationCount++; + hashDistOrSingleShardRelCount++; nonReferenceRelations = lappend_oid(nonReferenceRelations, relationId); } @@ -2388,7 +2379,7 @@ ErrorIfUnsupportedShardDistribution(Query *query) } } - if ((rangeDistributedRelationCount > 0) && (hashDistributedRelationCount > 0)) + if ((rangeDistributedRelationCount > 0) && (hashDistOrSingleShardRelCount > 0)) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot push down this subquery"), @@ -2402,7 +2393,7 @@ ErrorIfUnsupportedShardDistribution(Query *query) errdetail("A query including both range and append " "partitioned relations are unsupported"))); } - else if ((appendDistributedRelationCount > 0) && (hashDistributedRelationCount > 0)) + else if ((appendDistributedRelationCount > 0) && (hashDistOrSingleShardRelCount > 0)) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot push down this subquery"), @@ -2431,8 +2422,9 @@ ErrorIfUnsupportedShardDistribution(Query *query) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot push down this subquery"), - errdetail("Shards of relations in subquery need to " - "have 1-to-1 shard partitioning"))); + errdetail("%s and %s are not colocated", + get_rel_name(firstTableRelationId), + get_rel_name(currentRelationId)))); } } } @@ -2487,7 +2479,7 @@ QueryPushdownTaskCreate(Query *originalQuery, int shardIndex, /* non-distributed tables have only one shard */ shardInterval = cacheEntry->sortedShardIntervalArray[0]; - /* only use reference table as anchor shard if none exists yet */ + /* use as anchor shard only if we couldn't find any yet */ if (anchorShardId == INVALID_SHARD_ID) { anchorShardId = shardInterval->shardId; @@ -2683,6 +2675,18 @@ SqlTaskList(Job *job) List *fragmentCombinationList = FragmentCombinationList(rangeTableFragmentsList, jobQuery, dependentJobList); + /* + * Adjust RelabelType and CoerceViaIO nodes that are improper for deparsing. + * We first check if there are any such nodes by using a query tree walker. + * The reason is that a query tree mutator will create a deep copy of all + * the query sublinks, and we don't want to do that unless necessary, as it + * would be inefficient. + */ + if (QueryTreeHasImproperForDeparseNodes((Node *) jobQuery, NULL)) + { + jobQuery = (Query *) AdjustImproperForDeparseNodes((Node *) jobQuery, NULL); + } + ListCell *fragmentCombinationCell = NULL; foreach(fragmentCombinationCell, fragmentCombinationList) { @@ -2733,7 +2737,7 @@ SqlTaskList(Job *job) * RelabelTypeToCollateExpr converts RelabelType's into CollationExpr's. * With that, we will be able to pushdown COLLATE's. */ -CollateExpr * +static CollateExpr * RelabelTypeToCollateExpr(RelabelType *relabelType) { Assert(OidIsValid(relabelType->resultcollid)); @@ -2793,15 +2797,15 @@ AnchorRangeTableId(List *rangeTableList) * have the most number of shards, we have a draw. */ List *baseTableIdList = BaseRangeTableIdList(rangeTableList); - List *anchorTableIdList = AnchorRangeTableIdList(rangeTableList, baseTableIdList); + List *anchorTableRTIList = AnchorRangeTableIdList(rangeTableList, baseTableIdList); ListCell *anchorTableIdCell = NULL; - int anchorTableIdCount = list_length(anchorTableIdList); + int anchorTableIdCount = list_length(anchorTableRTIList); Assert(anchorTableIdCount > 0); if (anchorTableIdCount == 1) { - anchorRangeTableId = (uint32) linitial_int(anchorTableIdList); + anchorRangeTableId = (uint32) linitial_int(anchorTableRTIList); return anchorRangeTableId; } @@ -2809,7 +2813,7 @@ AnchorRangeTableId(List *rangeTableList) * If more than one table has the most number of shards, we break the draw * by comparing table sizes and picking the table with the largest size. */ - foreach(anchorTableIdCell, anchorTableIdList) + foreach(anchorTableIdCell, anchorTableRTIList) { uint32 anchorTableId = (uint32) lfirst_int(anchorTableIdCell); RangeTblEntry *tableEntry = rt_fetch(anchorTableId, rangeTableList); @@ -2837,7 +2841,7 @@ AnchorRangeTableId(List *rangeTableList) if (anchorRangeTableId == 0) { /* all tables have the same shard count and size 0, pick the first */ - anchorRangeTableId = (uint32) linitial_int(anchorTableIdList); + anchorRangeTableId = (uint32) linitial_int(anchorTableRTIList); } return anchorRangeTableId; @@ -2878,7 +2882,7 @@ BaseRangeTableIdList(List *rangeTableList) static List * AnchorRangeTableIdList(List *rangeTableList, List *baseRangeTableIdList) { - List *anchorTableIdList = NIL; + List *anchorTableRTIList = NIL; uint32 maxShardCount = 0; ListCell *baseRangeTableIdCell = NULL; @@ -2888,25 +2892,46 @@ AnchorRangeTableIdList(List *rangeTableList, List *baseRangeTableIdList) return baseRangeTableIdList; } + uint32 referenceTableRTI = 0; + foreach(baseRangeTableIdCell, baseRangeTableIdList) { uint32 baseRangeTableId = (uint32) lfirst_int(baseRangeTableIdCell); RangeTblEntry *tableEntry = rt_fetch(baseRangeTableId, rangeTableList); - List *shardList = LoadShardList(tableEntry->relid); + + Oid citusTableId = tableEntry->relid; + if (IsCitusTableType(citusTableId, REFERENCE_TABLE)) + { + referenceTableRTI = baseRangeTableId; + continue; + } + + List *shardList = LoadShardList(citusTableId); uint32 shardCount = (uint32) list_length(shardList); if (shardCount > maxShardCount) { - anchorTableIdList = list_make1_int(baseRangeTableId); + anchorTableRTIList = list_make1_int(baseRangeTableId); maxShardCount = shardCount; } else if (shardCount == maxShardCount) { - anchorTableIdList = lappend_int(anchorTableIdList, baseRangeTableId); + anchorTableRTIList = lappend_int(anchorTableRTIList, baseRangeTableId); } } - return anchorTableIdList; + /* + * We favor distributed tables over reference tables as anchor tables. But + * in case we cannot find any distributed tables, we let reference table to be + * anchor table. For now, we cannot see a query that might require this, but we + * want to be backward compatiable. + */ + if (list_length(anchorTableRTIList) == 0) + { + return referenceTableRTI > 0 ? list_make1_int(referenceTableRTI) : NIL; + } + + return anchorTableRTIList; } @@ -5593,3 +5618,126 @@ TaskListHighestTaskId(List *taskList) return highestTaskId; } + + +/* + * QueryTreeHasImproperForDeparseNodes walks over the node, + * and returns true if there are RelabelType or + * CoerceViaIONodes which are improper for deparse + */ +static bool +QueryTreeHasImproperForDeparseNodes(Node *inputNode, void *context) +{ + if (inputNode == NULL) + { + return false; + } + else if (IsImproperForDeparseRelabelTypeNode(inputNode) || + IsImproperForDeparseCoerceViaIONode(inputNode)) + { + return true; + } + else if (IsA(inputNode, Query)) + { + return query_tree_walker((Query *) inputNode, + QueryTreeHasImproperForDeparseNodes, + NULL, 0); + } + + return expression_tree_walker(inputNode, + QueryTreeHasImproperForDeparseNodes, + NULL); +} + + +/* + * AdjustImproperForDeparseNodes takes an input rewritten query and modifies + * nodes which, after going through our planner, pose a problem when + * deparsing. So far we have two such type of Nodes that may pose problems: + * RelabelType and CoerceIO nodes. + * Details will be written in comments in the corresponding if conditions. + */ +static Node * +AdjustImproperForDeparseNodes(Node *inputNode, void *context) +{ + if (inputNode == NULL) + { + return NULL; + } + + if (IsImproperForDeparseRelabelTypeNode(inputNode)) + { + /* + * The planner converts CollateExpr to RelabelType + * and here we convert back. + */ + return (Node *) RelabelTypeToCollateExpr((RelabelType *) inputNode); + } + else if (IsImproperForDeparseCoerceViaIONode(inputNode)) + { + /* + * The planner converts some ::text/::varchar casts to ::cstring + * and here we convert back to text because cstring is a pseudotype + * and it cannot be casted to most resulttypes + */ + + CoerceViaIO *iocoerce = (CoerceViaIO *) inputNode; + Node *arg = (Node *) iocoerce->arg; + Const *cstringToText = (Const *) arg; + + cstringToText->consttype = TEXTOID; + cstringToText->constlen = -1; + + Type textType = typeidType(TEXTOID); + char *constvalue = NULL; + + if (!cstringToText->constisnull) + { + constvalue = DatumGetCString(cstringToText->constvalue); + } + + cstringToText->constvalue = stringTypeDatum(textType, + constvalue, + cstringToText->consttypmod); + ReleaseSysCache(textType); + return inputNode; + } + else if (IsA(inputNode, Query)) + { + return (Node *) query_tree_mutator((Query *) inputNode, + AdjustImproperForDeparseNodes, + NULL, QTW_DONT_COPY_QUERY); + } + + return expression_tree_mutator(inputNode, AdjustImproperForDeparseNodes, NULL); +} + + +/* + * Checks if the given node is of Relabel type which is improper for deparsing + * The planner converts some CollateExpr to RelabelType nodes, and we need + * to find these nodes. They would be improperly deparsed without the + * "COLLATE" expression. + */ +static bool +IsImproperForDeparseRelabelTypeNode(Node *inputNode) +{ + return (IsA(inputNode, RelabelType) && + OidIsValid(((RelabelType *) inputNode)->resultcollid) && + ((RelabelType *) inputNode)->resultcollid != DEFAULT_COLLATION_OID); +} + + +/* + * Checks if the given node is of CoerceViaIO type which is improper for deparsing + * The planner converts some ::text/::varchar casts to ::cstring, and we need + * to find these nodes. They would be improperly deparsed with "cstring" which cannot + * be casted to most resulttypes. + */ +static bool +IsImproperForDeparseCoerceViaIONode(Node *inputNode) +{ + return (IsA(inputNode, CoerceViaIO) && + IsA(((CoerceViaIO *) inputNode)->arg, Const) && + ((Const *) ((CoerceViaIO *) inputNode)->arg)->consttype == CSTRINGOID); +} diff --git a/src/backend/distributed/planner/multi_router_planner.c b/src/backend/distributed/planner/multi_router_planner.c index 94691bab9..6ad51e0ae 100644 --- a/src/backend/distributed/planner/multi_router_planner.c +++ b/src/backend/distributed/planner/multi_router_planner.c @@ -152,10 +152,8 @@ static List * ExtractInsertValuesList(Query *query, Var *partitionColumn); static DeferredErrorMessage * DeferErrorIfUnsupportedRouterPlannableSelectQuery( Query *query); static DeferredErrorMessage * ErrorIfQueryHasUnroutableModifyingCTE(Query *queryTree); -#if PG_VERSION_NUM >= PG_VERSION_14 static DeferredErrorMessage * ErrorIfQueryHasCTEWithSearchClause(Query *queryTree); -static bool ContainsSearchClauseWalker(Node *node); -#endif +static bool ContainsSearchClauseWalker(Node *node, void *context); static bool SelectsFromDistributedTable(List *rangeTableList, Query *query); static ShardPlacement * CreateDummyPlacement(bool hasLocalRelation); static ShardPlacement * CreateLocalDummyPlacement(); @@ -388,6 +386,26 @@ AddPartitionKeyNotNullFilterToSelect(Query *subqery) } +/* + * ExtractSourceResultRangeTableEntry Generic wrapper for modification commands that + * utilizes results as input, based on an source query. + */ +RangeTblEntry * +ExtractSourceResultRangeTableEntry(Query *query) +{ + if (IsMergeQuery(query)) + { + return ExtractMergeSourceRangeTableEntry(query); + } + else if (CheckInsertSelectQuery(query)) + { + return ExtractSelectRangeTableEntry(query); + } + + return NULL; +} + + /* * ExtractSelectRangeTableEntry returns the range table entry of the subquery. * Note that the function expects and asserts that the input query be @@ -1098,14 +1116,12 @@ ModifyQuerySupported(Query *queryTree, Query *originalQuery, bool multiShardQuer } } -#if PG_VERSION_NUM >= PG_VERSION_14 DeferredErrorMessage *CTEWithSearchClauseError = ErrorIfQueryHasCTEWithSearchClause(originalQuery); if (CTEWithSearchClauseError != NULL) { return CTEWithSearchClauseError; } -#endif return NULL; } @@ -1863,19 +1879,7 @@ RouterJob(Query *originalQuery, PlannerRestrictionContext *plannerRestrictionCon if (*planningError) { - /* - * For MERGE, we do _not_ plan any other router job than the MERGE job itself, - * let's not continue further down the lane in distributed planning, simply - * bail out. - */ - if (IsMergeQuery(originalQuery)) - { - RaiseDeferredError(*planningError, ERROR); - } - else - { - return NULL; - } + return NULL; } Job *job = CreateJob(originalQuery); @@ -1885,17 +1889,36 @@ RouterJob(Query *originalQuery, PlannerRestrictionContext *plannerRestrictionCon { RangeTblEntry *updateOrDeleteOrMergeRTE = ExtractResultRelationRTE(originalQuery); - /* - * If all of the shards are pruned, we replace the relation RTE into - * subquery RTE that returns no results. However, this is not useful - * for UPDATE and DELETE queries. Therefore, if we detect a UPDATE or - * DELETE RTE with subquery type, we just set task list to empty and return - * the job. - */ if (updateOrDeleteOrMergeRTE->rtekind == RTE_SUBQUERY) { - job->taskList = NIL; - return job; + /* + * Not generating tasks for MERGE target relation might + * result in incorrect behavior as source rows with NOT + * MATCHED clause might qualify for insertion. + */ + if (IsMergeQuery(originalQuery)) + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Merge command is currently " + "unsupported with filters that " + "prunes down to zero shards"), + errhint("Avoid `WHERE false` clause or " + "any equivalent filters that " + "could prune down to zero shards"))); + } + else + { + /* + * If all of the shards are pruned, we replace the + * relation RTE into subquery RTE that returns no + * results. However, this is not useful for UPDATE + * and DELETE queries. Therefore, if we detect a + * UPDATE or DELETE RTE with subquery type, we just + * set task list to empty and return the job. + */ + job->taskList = NIL; + return job; + } } } @@ -2246,10 +2269,8 @@ SelectsFromDistributedTable(List *rangeTableList, Query *query) } -static bool ContainsOnlyLocalTables(RTEListProperties *rteProperties); - /* - * RouterQuery runs router pruning logic for SELECT, UPDATE and DELETE queries. + * RouterQuery runs router pruning logic for SELECT, UPDATE, DELETE, and MERGE queries. * If there are shards present and query is routable, all RTEs have been updated * to point to the relevant shards in the originalQuery. Also, placementList is * filled with the list of worker nodes that has all the required shard placements @@ -2282,6 +2303,7 @@ PlanRouterQuery(Query *originalQuery, DeferredErrorMessage *planningError = NULL; bool shardsPresent = false; CmdType commandType = originalQuery->commandType; + Oid targetRelationId = InvalidOid; bool fastPathRouterQuery = plannerRestrictionContext->fastPathRestrictionContext->fastPathRouterQuery; @@ -2348,13 +2370,7 @@ PlanRouterQuery(Query *originalQuery, Assert(UpdateOrDeleteOrMergeQuery(originalQuery)); - if (IsMergeQuery(originalQuery)) - { - planningError = MergeQuerySupported(originalQuery, - isMultiShardQuery, - plannerRestrictionContext); - } - else + if (!IsMergeQuery(originalQuery)) { planningError = ModifyQuerySupported(originalQuery, originalQuery, isMultiShardQuery, @@ -2403,13 +2419,14 @@ PlanRouterQuery(Query *originalQuery, /* both Postgres tables and materialized tables are locally avaliable */ RTEListProperties *rteProperties = GetRTEListPropertiesForQuery(originalQuery); - if (shardId == INVALID_SHARD_ID && ContainsOnlyLocalTables(rteProperties)) + + if (isLocalTableModification) { - if (commandType != CMD_SELECT) - { - *isLocalTableModification = true; - } + *isLocalTableModification = + IsLocalTableModification(targetRelationId, originalQuery, shardId, + rteProperties); } + bool hasPostgresLocalRelation = rteProperties->hasPostgresLocalTable || rteProperties->hasMaterializedView; List *taskPlacementList = @@ -2447,7 +2464,7 @@ PlanRouterQuery(Query *originalQuery, * ContainsOnlyLocalTables returns true if there is only * local tables and not any distributed or reference table. */ -static bool +bool ContainsOnlyLocalTables(RTEListProperties *rteProperties) { return !rteProperties->hasDistributedTable && !rteProperties->hasReferenceTable; @@ -2683,7 +2700,7 @@ TargetShardIntervalForFastPathQuery(Query *query, bool *isMultiShardQuery, if (!HasDistributionKey(relationId)) { - /* we don't need to do shard pruning for non-distributed tables */ + /* we don't need to do shard pruning for single shard tables */ return list_make1(LoadShardIntervalList(relationId)); } @@ -2973,7 +2990,7 @@ BuildRoutesForInsert(Query *query, DeferredErrorMessage **planningError) Assert(query->commandType == CMD_INSERT); - /* reference tables and citus local tables can only have one shard */ + /* tables that don't have distribution column can only have one shard */ if (!HasDistributionKeyCacheEntry(cacheEntry)) { List *shardIntervalList = LoadShardIntervalList(distributedTableId); @@ -2991,6 +3008,12 @@ BuildRoutesForInsert(Query *query, DeferredErrorMessage **planningError) ereport(ERROR, (errmsg("local table cannot have %d shards", shardCount))); } + else if (IsCitusTableTypeCacheEntry(cacheEntry, SINGLE_SHARD_DISTRIBUTED)) + { + ereport(ERROR, (errmsg("distributed tables having a null shard key " + "cannot have %d shards", + shardCount))); + } } ShardInterval *shardInterval = linitial(shardIntervalList); @@ -3731,14 +3754,12 @@ DeferErrorIfUnsupportedRouterPlannableSelectQuery(Query *query) NULL, NULL); } -#if PG_VERSION_NUM >= PG_VERSION_14 DeferredErrorMessage *CTEWithSearchClauseError = ErrorIfQueryHasCTEWithSearchClause(query); if (CTEWithSearchClauseError != NULL) { return CTEWithSearchClauseError; } -#endif return ErrorIfQueryHasUnroutableModifyingCTE(query); } @@ -3848,7 +3869,8 @@ ErrorIfQueryHasUnroutableModifyingCTE(Query *queryTree) CitusTableCacheEntry *modificationTableCacheEntry = GetCitusTableCacheEntry(distributedTableId); - if (!HasDistributionKeyCacheEntry(modificationTableCacheEntry)) + if (!IsCitusTableTypeCacheEntry(modificationTableCacheEntry, + DISTRIBUTED_TABLE)) { return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, "cannot router plan modification of a non-distributed table", @@ -3872,8 +3894,6 @@ ErrorIfQueryHasUnroutableModifyingCTE(Query *queryTree) } -#if PG_VERSION_NUM >= PG_VERSION_14 - /* * ErrorIfQueryHasCTEWithSearchClause checks if the query contains any common table * expressions with search clause and errors out if it does. @@ -3881,7 +3901,7 @@ ErrorIfQueryHasUnroutableModifyingCTE(Query *queryTree) static DeferredErrorMessage * ErrorIfQueryHasCTEWithSearchClause(Query *queryTree) { - if (ContainsSearchClauseWalker((Node *) queryTree)) + if (ContainsSearchClauseWalker((Node *) queryTree, NULL)) { return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, "CTEs with search clauses are not supported", @@ -3896,7 +3916,7 @@ ErrorIfQueryHasCTEWithSearchClause(Query *queryTree) * CommonTableExprs with search clause */ static bool -ContainsSearchClauseWalker(Node *node) +ContainsSearchClauseWalker(Node *node, void *context) { if (node == NULL) { @@ -3920,9 +3940,6 @@ ContainsSearchClauseWalker(Node *node) } -#endif - - /* * get_all_actual_clauses * diff --git a/src/backend/distributed/planner/query_colocation_checker.c b/src/backend/distributed/planner/query_colocation_checker.c index c5de0ef9e..a6e64b9c1 100644 --- a/src/backend/distributed/planner/query_colocation_checker.c +++ b/src/backend/distributed/planner/query_colocation_checker.c @@ -168,11 +168,10 @@ AnchorRte(Query *subquery) { Oid relationId = currentRte->relid; - if (IsCitusTable(relationId) && !HasDistributionKey(relationId)) + if (!IsCitusTableType(relationId, DISTRIBUTED_TABLE)) { /* - * Non-distributed tables should not be the anchor rte since they - * don't have distribution key. + * We're not interested in non distributed relations. */ continue; } diff --git a/src/backend/distributed/planner/recursive_planning.c b/src/backend/distributed/planner/recursive_planning.c index 936b17364..f582fd9df 100644 --- a/src/backend/distributed/planner/recursive_planning.c +++ b/src/backend/distributed/planner/recursive_planning.c @@ -188,7 +188,6 @@ static Query * BuildReadIntermediateResultsQuery(List *targetEntryList, List *columnAliasList, Const *resultIdConst, Oid functionOid, bool useBinaryCopyFormat); -static void UpdateVarNosInNode(Node *node, Index newVarNo); static Query * CreateOuterSubquery(RangeTblEntry *rangeTableEntry, List *outerSubqueryTargetList); static List * GenerateRequiredColNamesFromTargetList(List *targetList); @@ -1891,7 +1890,7 @@ GenerateRequiredColNamesFromTargetList(List *targetList) * UpdateVarNosInNode iterates the Vars in the * given node and updates the varno's as the newVarNo. */ -static void +void UpdateVarNosInNode(Node *node, Index newVarNo) { List *varList = pull_var_clause(node, PVC_RECURSE_AGGREGATES | diff --git a/src/backend/distributed/planner/relation_restriction_equivalence.c b/src/backend/distributed/planner/relation_restriction_equivalence.c index d39c4affb..b57e37735 100644 --- a/src/backend/distributed/planner/relation_restriction_equivalence.c +++ b/src/backend/distributed/planner/relation_restriction_equivalence.c @@ -155,6 +155,7 @@ static bool AllDistributedRelationsInRestrictionContextColocated( RelationRestrictionContext * restrictionContext); static bool IsNotSafeRestrictionToRecursivelyPlan(Node *node); +static bool HasPlaceHolderVar(Node *node); static JoinRestrictionContext * FilterJoinRestrictionContext( JoinRestrictionContext *joinRestrictionContext, Relids queryRteIdentities); @@ -2142,13 +2143,24 @@ GetRestrictInfoListForRelation(RangeTblEntry *rangeTblEntry, * If the restriction involves multiple tables, we cannot add it to * input relation's expression list. */ - Relids varnos = pull_varnos_compat(relationRestriction->plannerInfo, - (Node *) restrictionClause); + Relids varnos = pull_varnos(relationRestriction->plannerInfo, + (Node *) restrictionClause); if (bms_num_members(varnos) != 1) { continue; } + /* + * PlaceHolderVar is not relevant to be processed inside a restriction clause. + * Otherwise, pull_var_clause_default would throw error. PG would create + * the restriction to physical Var that PlaceHolderVar points anyway, so it is + * safe to skip this restriction. + */ + if (FindNodeMatchingCheckFunction((Node *) restrictionClause, HasPlaceHolderVar)) + { + continue; + } + /* * We're going to add this restriction expression to a subquery * which consists of only one relation in its jointree. Thus, @@ -2214,6 +2226,16 @@ IsNotSafeRestrictionToRecursivelyPlan(Node *node) } +/* + * HasPlaceHolderVar returns true if given node contains any PlaceHolderVar. + */ +static bool +HasPlaceHolderVar(Node *node) +{ + return IsA(node, PlaceHolderVar); +} + + /* * FilterRelationRestrictionContext gets a relation restriction context and * set of rte identities. It returns the relation restrictions that that appear diff --git a/src/backend/distributed/replication/multi_logical_replication.c b/src/backend/distributed/replication/multi_logical_replication.c index e51329f22..550095875 100644 --- a/src/backend/distributed/replication/multi_logical_replication.c +++ b/src/backend/distributed/replication/multi_logical_replication.c @@ -1536,7 +1536,7 @@ CreateSubscriptions(MultiConnection *sourceConnection, quote_identifier(target->publication->name), quote_identifier(target->replicationSlot->name)); - if (EnableBinaryProtocol && PG_VERSION_NUM >= PG_VERSION_14) + if (EnableBinaryProtocol) { appendStringInfoString(createSubscriptionCommand, ", binary=true)"); } diff --git a/src/backend/distributed/shared_library_init.c b/src/backend/distributed/shared_library_init.c index e7fedd1d8..2493a8ea9 100644 --- a/src/backend/distributed/shared_library_init.c +++ b/src/backend/distributed/shared_library_init.c @@ -44,7 +44,7 @@ #include "distributed/cte_inline.h" #include "distributed/distributed_deadlock_detection.h" #include "distributed/errormessage.h" -#include "distributed/insert_select_executor.h" +#include "distributed/repartition_executor.h" #include "distributed/intermediate_result_pruning.h" #include "distributed/local_multi_copy.h" #include "distributed/local_executor.h" @@ -185,13 +185,14 @@ static void CitusObjectAccessHook(ObjectAccessType access, Oid classId, Oid obje static void DoInitialCleanup(void); static void ResizeStackToMaximumDepth(void); static void multi_log_hook(ErrorData *edata); +static bool IsSequenceOverflowError(ErrorData *edata); static void RegisterConnectionCleanup(void); static void RegisterExternalClientBackendCounterDecrement(void); static void CitusCleanupConnectionsAtExit(int code, Datum arg); static void DecrementExternalClientBackendCounterAtExit(int code, Datum arg); static void CreateRequiredDirectories(void); static void RegisterCitusConfigVariables(void); -static void OverridePostgresConfigAssignHooks(void); +static void OverridePostgresConfigProperties(void); static bool ErrorIfNotASuitableDeadlockFactor(double *newval, void **extra, GucSource source); static bool WarnIfDeprecatedExecutorUsed(int *newval, void **extra, GucSource source); @@ -213,6 +214,7 @@ static bool IsSuperuser(char *userName); static void AdjustDynamicLibraryPathForCdcDecoders(void); static ClientAuthentication_hook_type original_client_auth_hook = NULL; +static emit_log_hook_type original_emit_log_hook = NULL; /* *INDENT-OFF* */ /* GUC enum definitions */ @@ -458,6 +460,7 @@ _PG_init(void) ExecutorEnd_hook = CitusAttributeToEnd; /* register hook for error messages */ + original_emit_log_hook = emit_log_hook; emit_log_hook = multi_log_hook; @@ -681,6 +684,15 @@ multi_log_hook(ErrorData *edata) * Show the user a meaningful error message when a backend is cancelled * by the distributed deadlock detection. Also reset the state for this, * since the next cancelation of the backend might have another reason. + * + * We also want to provide a useful hint for sequence overflow errors + * because they're likely to be caused by the way Citus handles smallint/int + * based sequences on worker nodes. Note that we add the hint without checking + * whether we're on a worker node or the sequence was used on a distributed + * table because catalog might not be available at this point. And given + * that this hint might be shown for regular Postgres tables too, we inject + * the hint only when EnableUnsupportedFeatureMessages is set to true. + * Otherwise, vanilla tests would fail. */ bool clearState = true; if (edata->elevel == ERROR && edata->sqlerrcode == ERRCODE_QUERY_CANCELED && @@ -698,6 +710,40 @@ multi_log_hook(ErrorData *edata) edata->message = pstrdup("canceling the transaction since it was " "involved in a distributed deadlock"); } + else if (EnableUnsupportedFeatureMessages && + IsSequenceOverflowError(edata)) + { + edata->detail = pstrdup("nextval(sequence) calls in worker nodes " + "are not supported for column defaults of " + "type int or smallint"); + edata->hint = pstrdup("If the command was issued from a worker node, " + "try issuing it from the coordinator node " + "instead."); + } + + if (original_emit_log_hook) + { + original_emit_log_hook(edata); + } +} + + +/* + * IsSequenceOverflowError returns true if the given error is a sequence + * overflow error. + */ +static bool +IsSequenceOverflowError(ErrorData *edata) +{ + static const char *sequenceOverflowedMsgPrefix = + "nextval: reached maximum value of sequence"; + static const int sequenceOverflowedMsgPrefixLen = 42; + + return edata->elevel == ERROR && + edata->sqlerrcode == ERRCODE_SEQUENCE_GENERATOR_LIMIT_EXCEEDED && + edata->message != NULL && + strncmp(edata->message, sequenceOverflowedMsgPrefix, + sequenceOverflowedMsgPrefixLen) == 0; } @@ -878,7 +924,7 @@ RegisterCitusConfigVariables(void) &AllowModificationsFromWorkersToReplicatedTables, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -895,7 +941,7 @@ RegisterCitusConfigVariables(void) &AllowNestedDistributedExecution, false, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -915,7 +961,7 @@ RegisterCitusConfigVariables(void) &AllowUnsafeConstraints, false, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -935,7 +981,7 @@ RegisterCitusConfigVariables(void) &EnableAcquiringUnsafeLockFromWorkers, false, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomIntVariable( @@ -956,7 +1002,7 @@ RegisterCitusConfigVariables(void) &CheckAvailableSpaceBeforeMove, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomStringVariable( @@ -995,7 +1041,7 @@ RegisterCitusConfigVariables(void) &CopySwitchOverThresholdBytes, 4 * 1024 * 1024, 1, INT_MAX, PGC_USERSET, - GUC_UNIT_BYTE | GUC_NO_SHOW_ALL, + GUC_UNIT_BYTE | GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomRealVariable( @@ -1066,7 +1112,7 @@ RegisterCitusConfigVariables(void) &CreateObjectPropagationMode, CREATE_OBJECT_PROPAGATION_IMMEDIATE, create_object_propagation_options, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1139,7 +1185,7 @@ RegisterCitusConfigVariables(void) &EnableAlterDatabaseOwner, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1150,7 +1196,7 @@ RegisterCitusConfigVariables(void) &EnableAlterRolePropagation, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1160,7 +1206,7 @@ RegisterCitusConfigVariables(void) &EnableAlterRoleSetPropagation, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1169,11 +1215,7 @@ RegisterCitusConfigVariables(void) "Enables communication between nodes using binary protocol when possible"), NULL, &EnableBinaryProtocol, -#if PG_VERSION_NUM >= PG_VERSION_14 true, -#else - false, -#endif PGC_USERSET, GUC_STANDARD, NULL, NULL, NULL); @@ -1199,7 +1241,7 @@ RegisterCitusConfigVariables(void) &EnableClusterClock, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( "citus.enable_cost_based_connection_establishment", @@ -1210,7 +1252,7 @@ RegisterCitusConfigVariables(void) &EnableCostBasedConnectionEstablishment, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1231,7 +1273,7 @@ RegisterCitusConfigVariables(void) &EnableCreateTypePropagation, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1241,7 +1283,7 @@ RegisterCitusConfigVariables(void) &EnableDDLPropagation, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1266,7 +1308,7 @@ RegisterCitusConfigVariables(void) &EnableFastPathRouterPlanner, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1300,7 +1342,7 @@ RegisterCitusConfigVariables(void) &EnableManualChangesToShards, false, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomStringVariable( @@ -1311,7 +1353,7 @@ RegisterCitusConfigVariables(void) &EnableManualMetadataChangesForUser, "", PGC_SIGHUP, - GUC_SUPERUSER_ONLY | GUC_NO_SHOW_ALL, + GUC_SUPERUSER_ONLY | GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1321,7 +1363,7 @@ RegisterCitusConfigVariables(void) &EnableMetadataSync, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1339,9 +1381,9 @@ RegisterCitusConfigVariables(void) "or altering the shard count of one of those distributed " "tables."), &EnableNonColocatedRouterQueryPushdown, - true, + false, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1361,7 +1403,7 @@ RegisterCitusConfigVariables(void) &EnableRepartitionedInsertSelect, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1371,7 +1413,21 @@ RegisterCitusConfigVariables(void) &EnableRouterExecution, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, + NULL, NULL, NULL); + + DefineCustomBoolVariable( + "citus.enable_schema_based_sharding", + gettext_noop("Enables schema based sharding."), + gettext_noop("The schemas created while this is ON will be automatically " + "associated with individual colocation groups such that the " + "tables created in those schemas will be automatically " + "converted to colocated distributed tables without a shard " + "key."), + &EnableSchemaBasedSharding, + false, + PGC_USERSET, + GUC_STANDARD, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1382,7 +1438,7 @@ RegisterCitusConfigVariables(void) &EnableSingleHashRepartitioning, false, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1393,7 +1449,7 @@ RegisterCitusConfigVariables(void) "and operating system name. This configuration value controls " "whether these reports are sent."), &EnableStatisticsCollection, -#if defined(HAVE_LIBCURL) && defined(ENABLE_CITUS_STATISTICS_COLLECTION) +#if defined(HAVE_LIBCURL) true, #else false, @@ -1412,7 +1468,7 @@ RegisterCitusConfigVariables(void) &EnableUniqueJobIds, true, PGC_USERSET, - GUC_SUPERUSER_ONLY | GUC_NO_SHOW_ALL, + GUC_SUPERUSER_ONLY | GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1423,7 +1479,7 @@ RegisterCitusConfigVariables(void) &EnableUnsafeTriggers, false, PGC_USERSET, - GUC_STANDARD | GUC_NO_SHOW_ALL, + GUC_STANDARD | GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1434,7 +1490,7 @@ RegisterCitusConfigVariables(void) &EnableUnsupportedFeatureMessages, true, PGC_SUSET, - GUC_SUPERUSER_ONLY | GUC_NO_SHOW_ALL, + GUC_SUPERUSER_ONLY | GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1444,7 +1500,7 @@ RegisterCitusConfigVariables(void) &EnableVersionChecks, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1457,7 +1513,7 @@ RegisterCitusConfigVariables(void) &EnforceForeignKeyRestrictions, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1468,7 +1524,7 @@ RegisterCitusConfigVariables(void) &EnforceLocalObjectRestrictions, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomIntVariable( @@ -1484,7 +1540,7 @@ RegisterCitusConfigVariables(void) &ExecutorSlowStartInterval, 10, 0, INT_MAX, PGC_USERSET, - GUC_UNIT_MS | GUC_NO_SHOW_ALL, + GUC_UNIT_MS | GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1524,7 +1580,7 @@ RegisterCitusConfigVariables(void) &ExplainDistributedQueries, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1540,7 +1596,7 @@ RegisterCitusConfigVariables(void) &ForceMaxQueryParallelization, false, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1554,7 +1610,7 @@ RegisterCitusConfigVariables(void) &FunctionOpensTransactionBlock, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomStringVariable( @@ -1566,7 +1622,7 @@ RegisterCitusConfigVariables(void) &GrepRemoteCommands, "", PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1578,7 +1634,7 @@ RegisterCitusConfigVariables(void) &HideCitusDependentObjects, false, PGC_USERSET, - GUC_SUPERUSER_ONLY | GUC_NO_SHOW_ALL, + GUC_SUPERUSER_ONLY | GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); /* @@ -1598,7 +1654,7 @@ RegisterCitusConfigVariables(void) &DeprecatedEmptyString, "", PGC_SUSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomIntVariable( @@ -1608,7 +1664,7 @@ RegisterCitusConfigVariables(void) &IsolationTestSessionProcessID, -1, -1, INT_MAX, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomIntVariable( @@ -1618,7 +1674,7 @@ RegisterCitusConfigVariables(void) &IsolationTestSessionRemoteProcessID, -1, -1, INT_MAX, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomIntVariable( @@ -1643,7 +1699,7 @@ RegisterCitusConfigVariables(void) &LocalCopyFlushThresholdByte, 512 * 1024, 1, INT_MAX, PGC_USERSET, - GUC_UNIT_BYTE | GUC_NO_SHOW_ALL, + GUC_UNIT_BYTE | GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomStringVariable( @@ -1700,7 +1756,7 @@ RegisterCitusConfigVariables(void) &LogDistributedDeadlockDetection, false, PGC_SIGHUP, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1710,7 +1766,7 @@ RegisterCitusConfigVariables(void) &LogIntermediateResults, false, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1721,7 +1777,7 @@ RegisterCitusConfigVariables(void) &LogLocalCommands, false, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1732,7 +1788,7 @@ RegisterCitusConfigVariables(void) &LogMultiJoinOrder, false, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -1754,7 +1810,7 @@ RegisterCitusConfigVariables(void) &LogicalReplicationTimeout, 2 * 60 * 60 * 1000, 0, 7 * 24 * 3600 * 1000, PGC_SIGHUP, - GUC_NO_SHOW_ALL | GUC_UNIT_MS, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE | GUC_UNIT_MS, NULL, NULL, NULL); DefineCustomIntVariable( @@ -1889,7 +1945,7 @@ RegisterCitusConfigVariables(void) &MaxRebalancerLoggedIgnoredMoves, 5, -1, INT_MAX, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomIntVariable( @@ -1934,7 +1990,7 @@ RegisterCitusConfigVariables(void) &MetadataSyncInterval, 60 * MS_PER_SECOND, 1, 7 * MS_PER_DAY, PGC_SIGHUP, - GUC_UNIT_MS | GUC_NO_SHOW_ALL, + GUC_UNIT_MS | GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomEnumVariable( @@ -1949,7 +2005,7 @@ RegisterCitusConfigVariables(void) &MetadataSyncTransMode, METADATA_SYNC_TRANSACTIONAL, metadata_sync_mode_options, PGC_SUSET, - GUC_SUPERUSER_ONLY | GUC_NO_SHOW_ALL, + GUC_SUPERUSER_ONLY | GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomIntVariable( @@ -1961,7 +2017,7 @@ RegisterCitusConfigVariables(void) &MetadataSyncRetryInterval, 5 * MS_PER_SECOND, 1, 7 * MS_PER_DAY, PGC_SIGHUP, - GUC_UNIT_MS | GUC_NO_SHOW_ALL, + GUC_UNIT_MS | GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); /* @@ -1979,7 +2035,7 @@ RegisterCitusConfigVariables(void) &MitmfifoEmptyString, "", PGC_SUSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomEnumVariable( @@ -2014,7 +2070,7 @@ RegisterCitusConfigVariables(void) &NextCleanupRecordId, 0, 0, INT_MAX, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomIntVariable( @@ -2029,7 +2085,7 @@ RegisterCitusConfigVariables(void) &NextOperationId, 0, 0, INT_MAX, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomIntVariable( @@ -2044,7 +2100,7 @@ RegisterCitusConfigVariables(void) &NextPlacementId, 0, 0, INT_MAX, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomIntVariable( @@ -2059,7 +2115,7 @@ RegisterCitusConfigVariables(void) &NextShardId, 0, 0, INT_MAX, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomIntVariable( @@ -2098,7 +2154,7 @@ RegisterCitusConfigVariables(void) &OverrideTableVisibility, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -2113,7 +2169,7 @@ RegisterCitusConfigVariables(void) &PreventIncompleteConnectionEstablishment, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -2124,7 +2180,7 @@ RegisterCitusConfigVariables(void) &PropagateSessionSettingsForLoopbackConnection, false, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomEnumVariable( @@ -2138,6 +2194,23 @@ RegisterCitusConfigVariables(void) GUC_STANDARD, NULL, NULL, NULL); + DefineCustomIntVariable( + "citus.rebalancer_by_disk_size_base_cost", + gettext_noop( + "When using the by_disk_size rebalance strategy each shard group " + "will get this cost in bytes added to its actual disk size. This " + "is used to avoid creating a bad balance when there's very little " + "data in some of the shards. The assumption is that even empty " + "shards have some cost, because of parallelism and because empty " + "shard groups will likely grow in the future."), + gettext_noop( + "The main reason this is configurable, is so it can be lowered for Citus its regression tests."), + &RebalancerByDiskSizeBaseCost, + 100 * 1024 * 1024, 0, INT_MAX, + PGC_USERSET, + GUC_UNIT_BYTE | GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, + NULL, NULL, NULL); + DefineCustomIntVariable( "citus.recover_2pc_interval", gettext_noop("Sets the time to wait between recovering 2PCs."), @@ -2162,7 +2235,7 @@ RegisterCitusConfigVariables(void) &RemoteCopyFlushThreshold, 8 * 1024 * 1024, 0, INT_MAX, PGC_USERSET, - GUC_UNIT_BYTE | GUC_NO_SHOW_ALL, + GUC_UNIT_BYTE | GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomIntVariable( @@ -2186,7 +2259,7 @@ RegisterCitusConfigVariables(void) &RepartitionJoinBucketCountPerNode, 4, 1, INT_MAX, PGC_SIGHUP, - GUC_STANDARD | GUC_NO_SHOW_ALL, + GUC_STANDARD | GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); /* deprecated setting */ @@ -2197,7 +2270,7 @@ RegisterCitusConfigVariables(void) &DeprecatedReplicateReferenceTablesOnActivate, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomEnumVariable( @@ -2210,7 +2283,7 @@ RegisterCitusConfigVariables(void) REPLICATION_MODEL_STREAMING, replication_model_options, PGC_SUSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, WarnIfReplicationModelIsSet, NULL, NULL); DefineCustomBoolVariable( @@ -2223,7 +2296,7 @@ RegisterCitusConfigVariables(void) &RunningUnderIsolationTest, false, PGC_SUSET, - GUC_SUPERUSER_ONLY | GUC_NO_SHOW_ALL, + GUC_SUPERUSER_ONLY | GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -2240,7 +2313,7 @@ RegisterCitusConfigVariables(void) &SelectOpensTransactionBlock, true, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomIntVariable( @@ -2297,7 +2370,7 @@ RegisterCitusConfigVariables(void) &SkipAdvisoryLockPermissionChecks, false, GUC_SUPERUSER_ONLY, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable( @@ -2342,7 +2415,7 @@ RegisterCitusConfigVariables(void) &SortReturning, false, PGC_SUSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); /* @@ -2358,7 +2431,7 @@ RegisterCitusConfigVariables(void) &StatStatementsMax, 50000, 1000, 10000000, PGC_POSTMASTER, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomIntVariable( @@ -2369,7 +2442,7 @@ RegisterCitusConfigVariables(void) &StatStatementsPurgeInterval, 10, -1, INT_MAX, PGC_SIGHUP, - GUC_UNIT_MS | GUC_NO_SHOW_ALL, + GUC_UNIT_MS | GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomEnumVariable( @@ -2395,7 +2468,6 @@ RegisterCitusConfigVariables(void) PGC_POSTMASTER, GUC_STANDARD, NULL, NULL, NULL); - DefineCustomEnumVariable( "citus.stat_tenants_log_level", gettext_noop("Sets the level of citus_stat_tenants log messages"), @@ -2430,6 +2502,16 @@ RegisterCitusConfigVariables(void) GUC_STANDARD, NULL, NULL, NULL); + DefineCustomRealVariable( + "citus.stat_tenants_untracked_sample_rate", + gettext_noop("Sampling rate for new tenants in citus_stat_tenants."), + NULL, + &StatTenantsSampleRateForNewTenants, + 1, 0, 1, + PGC_USERSET, + GUC_STANDARD, + NULL, NULL, NULL); + DefineCustomBoolVariable( "citus.subquery_pushdown", gettext_noop("Usage of this GUC is highly discouraged, please read the long " @@ -2445,7 +2527,7 @@ RegisterCitusConfigVariables(void) &SubqueryPushdown, false, PGC_USERSET, - GUC_NO_SHOW_ALL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE, NoticeIfSubqueryPushdownEnabled, NULL, NULL); DefineCustomEnumVariable( @@ -2556,16 +2638,17 @@ RegisterCitusConfigVariables(void) /* warn about config items in the citus namespace that are not registered above */ EmitWarningsOnPlaceholders("citus"); - OverridePostgresConfigAssignHooks(); + OverridePostgresConfigProperties(); } /* - * OverridePostgresConfigAssignHooks overrides GUC assign hooks where we want - * custom behaviour. + * OverridePostgresConfigProperties overrides GUC properties where we want + * custom behaviour. We should consider using Postgres function find_option + * in this function once it is exported by Postgres in a later release. */ static void -OverridePostgresConfigAssignHooks(void) +OverridePostgresConfigProperties(void) { struct config_generic **guc_vars = get_guc_variables(); int gucCount = GetNumConfigOptions(); @@ -2581,6 +2664,17 @@ OverridePostgresConfigAssignHooks(void) OldApplicationNameAssignHook = stringVar->assign_hook; stringVar->assign_hook = ApplicationNameAssignHook; } + + /* + * Turn on GUC_REPORT for search_path. GUC_REPORT provides that an S (Parameter Status) + * packet is appended after the C (Command Complete) packet sent from the server + * for SET command. S packet contains the new value of the parameter + * if its value has been changed. + */ + if (strcmp(var->name, "search_path") == 0) + { + var->flags |= GUC_REPORT; + } } } @@ -2845,12 +2939,14 @@ NodeConninfoGucAssignHook(const char *newval, void *extra) newval = ""; } - if (strcmp(newval, NodeConninfo) == 0) + if (strcmp(newval, NodeConninfo) == 0 && checkAtBootPassed) { /* It did not change, no need to do anything */ return; } + checkAtBootPassed = true; + PQconninfoOption *optionArray = PQconninfoParse(newval, NULL); if (optionArray == NULL) { diff --git a/src/backend/distributed/sql/citus--11.3-1--12.0-1.sql b/src/backend/distributed/sql/citus--11.3-1--12.0-1.sql index 56bbdfb0d..a35d772b7 100644 --- a/src/backend/distributed/sql/citus--11.3-1--12.0-1.sql +++ b/src/backend/distributed/sql/citus--11.3-1--12.0-1.sql @@ -1,3 +1,51 @@ -- citus--11.3-1--12.0-1 -- bump version to 12.0-1 + +CREATE TABLE citus.pg_dist_schema ( + schemaid oid NOT NULL, + colocationid int NOT NULL, + CONSTRAINT pg_dist_schema_pkey PRIMARY KEY (schemaid), + CONSTRAINT pg_dist_schema_unique_colocationid_index UNIQUE (colocationid) +); + +ALTER TABLE citus.pg_dist_schema SET SCHEMA pg_catalog; + +GRANT SELECT ON pg_catalog.pg_dist_schema TO public; + +-- udfs used to modify pg_dist_schema on workers, to sync metadata +#include "udfs/citus_internal_add_tenant_schema/12.0-1.sql" +#include "udfs/citus_internal_delete_tenant_schema/12.0-1.sql" + +#include "udfs/citus_prepare_pg_upgrade/12.0-1.sql" +#include "udfs/citus_finish_pg_upgrade/12.0-1.sql" + +-- udfs used to modify pg_dist_schema globally via drop trigger +#include "udfs/citus_internal_unregister_tenant_schema_globally/12.0-1.sql" +#include "udfs/citus_drop_trigger/12.0-1.sql" + +DROP VIEW citus_shards; +DROP FUNCTION citus_shard_sizes; +#include "udfs/citus_shard_sizes/12.0-1.sql" + +#include "udfs/citus_tables/12.0-1.sql" +#include "udfs/citus_shards/12.0-1.sql" + +#include "udfs/citus_schemas/12.0-1.sql" + +-- udfs used to include schema-based tenants in tenant monitoring +#include "udfs/citus_stat_tenants_local/12.0-1.sql" + +-- udfs to convert a regular/tenant schema to a tenant/regular schema +#include "udfs/citus_schema_distribute/12.0-1.sql" +#include "udfs/citus_schema_undistribute/12.0-1.sql" + +#include "udfs/drop_old_time_partitions/12.0-1.sql" +#include "udfs/get_missing_time_partition_ranges/12.0-1.sql" + +-- Update the default rebalance strategy to 'by_disk_size', but only if the +-- default is currently 'by_shard_count' +SELECT citus_set_default_rebalance_strategy(name) +FROM pg_dist_rebalance_strategy +WHERE name = 'by_disk_size' + AND (SELECT default_strategy FROM pg_dist_rebalance_strategy WHERE name = 'by_shard_count'); diff --git a/src/backend/distributed/sql/downgrades/citus--12.0-1--11.3-1.sql b/src/backend/distributed/sql/downgrades/citus--12.0-1--11.3-1.sql index ded1fa571..4a25cfda0 100644 --- a/src/backend/distributed/sql/downgrades/citus--12.0-1--11.3-1.sql +++ b/src/backend/distributed/sql/downgrades/citus--12.0-1--11.3-1.sql @@ -1,2 +1,83 @@ -- citus--12.0-1--11.3-1 --- this is an empty downgrade path since citus--11.3-1--12.0-1.sql is empty for now + +DO $$ +BEGIN + -- Throw an error if user has created any tenant schemas. + IF EXISTS (SELECT 1 FROM pg_catalog.pg_dist_schema) + THEN + RAISE EXCEPTION 'cannot downgrade Citus because there are ' + 'tenant schemas created.' + USING HINT = 'To downgrade Citus to an older version, you should ' + 'first issue SELECT citus.schema_tenant_unset("%s") ' + 'for each tenant schema.'; + END IF; + + -- Throw an error if user has any distributed tables without a shard key. + IF EXISTS ( + SELECT 1 FROM pg_dist_partition + WHERE repmodel != 't' AND partmethod = 'n' AND colocationid != 0) + THEN + RAISE EXCEPTION 'cannot downgrade Citus because there are ' + 'distributed tables without a shard key.' + USING HINT = 'You can find the distributed tables without a shard ' + 'key in the cluster by using the following query: ' + '"SELECT * FROM citus_tables WHERE distribution_column ' + '= '''' AND colocation_id > 0".', + DETAIL = 'To downgrade Citus to an older version, you should ' + 'first convert those tables to Postgres tables by ' + 'executing SELECT undistribute_table("%s").'; + END IF; +END; +$$ LANGUAGE plpgsql; + +DROP FUNCTION pg_catalog.citus_schema_distribute(regnamespace); +DROP FUNCTION pg_catalog.citus_schema_undistribute(regnamespace); + +DROP FUNCTION pg_catalog.citus_internal_add_tenant_schema(Oid, int); + +#include "../udfs/citus_prepare_pg_upgrade/11.2-1.sql" +#include "../udfs/citus_finish_pg_upgrade/11.2-1.sql" + +DROP FUNCTION pg_catalog.citus_internal_delete_tenant_schema(Oid); +DROP FUNCTION pg_catalog.citus_internal_unregister_tenant_schema_globally(Oid, text); + +#include "../udfs/citus_drop_trigger/10.2-1.sql" + +-- citus_schemas might be created in either of the schemas +DROP VIEW IF EXISTS public.citus_schemas; +DROP VIEW IF EXISTS pg_catalog.citus_schemas; + +DROP VIEW pg_catalog.citus_shards; +DROP FUNCTION pg_catalog.citus_shard_sizes; +#include "../udfs/citus_shard_sizes/10.0-1.sql" +-- citus_shards/11.1-1.sql tries to create citus_shards in pg_catalog but it is not allowed. +-- Here we use citus_shards/10.0-1.sql to properly create the view in citus schema and +-- then alter it to pg_catalog, so citus_shards/11.1-1.sql can REPLACE it without any errors. +#include "../udfs/citus_shards/10.0-1.sql" + +#include "../udfs/citus_tables/11.1-1.sql" +#include "../udfs/citus_shards/11.1-1.sql" + +DROP TABLE pg_catalog.pg_dist_schema; + +DROP VIEW pg_catalog.citus_stat_tenants_local; +DROP FUNCTION pg_catalog.citus_stat_tenants_local_internal( + BOOLEAN, + OUT INT, + OUT TEXT, + OUT INT, + OUT INT, + OUT INT, + OUT INT, + OUT DOUBLE PRECISION, + OUT DOUBLE PRECISION, + OUT BIGINT); +#include "../udfs/citus_stat_tenants_local/11.3-1.sql" + +#include "../udfs/drop_old_time_partitions/10.2-1.sql" +#include "../udfs/get_missing_time_partition_ranges/10.2-1.sql" + +-- This explicitly does not reset the rebalance strategy to by_shard_count, +-- because there's no way of knowing if the rebalance strategy before the +-- upgrade was by_disk_size or by_shard_count. And even in previous versions +-- by_disk_size is considered superior for quite some time. diff --git a/src/backend/distributed/sql/udfs/citus_drop_trigger/12.0-1.sql b/src/backend/distributed/sql/udfs/citus_drop_trigger/12.0-1.sql new file mode 100644 index 000000000..312099aeb --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_drop_trigger/12.0-1.sql @@ -0,0 +1,68 @@ +CREATE OR REPLACE FUNCTION pg_catalog.citus_drop_trigger() + RETURNS event_trigger + LANGUAGE plpgsql + SET search_path = pg_catalog + AS $cdbdt$ +DECLARE + constraint_event_count INTEGER; + v_obj record; + dropped_table_is_a_partition boolean := false; +BEGIN + FOR v_obj IN SELECT * FROM pg_event_trigger_dropped_objects() + WHERE object_type IN ('table', 'foreign table') + LOOP + -- first drop the table and metadata on the workers + -- then drop all the shards on the workers + -- finally remove the pg_dist_partition entry on the coordinator + PERFORM master_remove_distributed_table_metadata_from_workers(v_obj.objid, v_obj.schema_name, v_obj.object_name); + + -- If both original and normal values are false, the dropped table was a partition + -- that was dropped as a result of its parent being dropped + -- NOTE: the other way around is not true: + -- the table being a partition doesn't imply both original and normal values are false + SELECT (v_obj.original = false AND v_obj.normal = false) INTO dropped_table_is_a_partition; + + -- The partition's shards will be dropped when dropping the parent's shards, so we can skip: + -- i.e. we call citus_drop_all_shards with drop_shards_metadata_only parameter set to true + IF dropped_table_is_a_partition + THEN + PERFORM citus_drop_all_shards(v_obj.objid, v_obj.schema_name, v_obj.object_name, drop_shards_metadata_only := true); + ELSE + PERFORM citus_drop_all_shards(v_obj.objid, v_obj.schema_name, v_obj.object_name, drop_shards_metadata_only := false); + END IF; + + PERFORM master_remove_partition_metadata(v_obj.objid, v_obj.schema_name, v_obj.object_name); + END LOOP; + + FOR v_obj IN SELECT * FROM pg_event_trigger_dropped_objects() + LOOP + -- Remove entries from pg_catalog.pg_dist_schema for all dropped tenant schemas. + -- Also delete the corresponding colocation group from pg_catalog.pg_dist_colocation. + -- + -- Although normally we automatically delete the colocation groups when they become empty, + -- we don't do so for the colocation groups that are created for tenant schemas. For this + -- reason, here we need to delete the colocation group when the tenant schema is dropped. + IF v_obj.object_type = 'schema' AND EXISTS (SELECT 1 FROM pg_catalog.pg_dist_schema WHERE schemaid = v_obj.objid) + THEN + PERFORM pg_catalog.citus_internal_unregister_tenant_schema_globally(v_obj.objid, v_obj.object_name); + END IF; + + -- remove entries from citus.pg_dist_object for all dropped root (objsubid = 0) objects + PERFORM master_unmark_object_distributed(v_obj.classid, v_obj.objid, v_obj.objsubid); + END LOOP; + + SELECT COUNT(*) INTO constraint_event_count + FROM pg_event_trigger_dropped_objects() + WHERE object_type IN ('table constraint'); + + IF constraint_event_count > 0 + THEN + -- Tell utility hook that a table constraint is dropped so we might + -- need to undistribute some of the citus local tables that are not + -- connected to any reference tables. + PERFORM notify_constraint_dropped(); + END IF; +END; +$cdbdt$; +COMMENT ON FUNCTION pg_catalog.citus_drop_trigger() + IS 'perform checks and actions at the end of DROP actions'; diff --git a/src/backend/distributed/sql/udfs/citus_drop_trigger/latest.sql b/src/backend/distributed/sql/udfs/citus_drop_trigger/latest.sql index a440766ed..312099aeb 100644 --- a/src/backend/distributed/sql/udfs/citus_drop_trigger/latest.sql +++ b/src/backend/distributed/sql/udfs/citus_drop_trigger/latest.sql @@ -34,9 +34,20 @@ BEGIN PERFORM master_remove_partition_metadata(v_obj.objid, v_obj.schema_name, v_obj.object_name); END LOOP; - -- remove entries from citus.pg_dist_object for all dropped root (objsubid = 0) objects FOR v_obj IN SELECT * FROM pg_event_trigger_dropped_objects() LOOP + -- Remove entries from pg_catalog.pg_dist_schema for all dropped tenant schemas. + -- Also delete the corresponding colocation group from pg_catalog.pg_dist_colocation. + -- + -- Although normally we automatically delete the colocation groups when they become empty, + -- we don't do so for the colocation groups that are created for tenant schemas. For this + -- reason, here we need to delete the colocation group when the tenant schema is dropped. + IF v_obj.object_type = 'schema' AND EXISTS (SELECT 1 FROM pg_catalog.pg_dist_schema WHERE schemaid = v_obj.objid) + THEN + PERFORM pg_catalog.citus_internal_unregister_tenant_schema_globally(v_obj.objid, v_obj.object_name); + END IF; + + -- remove entries from citus.pg_dist_object for all dropped root (objsubid = 0) objects PERFORM master_unmark_object_distributed(v_obj.classid, v_obj.objid, v_obj.objsubid); END LOOP; diff --git a/src/backend/distributed/sql/udfs/citus_finish_pg_upgrade/12.0-1.sql b/src/backend/distributed/sql/udfs/citus_finish_pg_upgrade/12.0-1.sql new file mode 100644 index 000000000..6dd46607a --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_finish_pg_upgrade/12.0-1.sql @@ -0,0 +1,160 @@ +CREATE OR REPLACE FUNCTION pg_catalog.citus_finish_pg_upgrade() + RETURNS void + LANGUAGE plpgsql + SET search_path = pg_catalog + AS $cppu$ +DECLARE + table_name regclass; + command text; + trigger_name text; +BEGIN + + + IF substring(current_Setting('server_version'), '\d+')::int >= 14 THEN + EXECUTE $cmd$ + -- disable propagation to prevent EnsureCoordinator errors + -- the aggregate created here does not depend on Citus extension (yet) + -- since we add the dependency with the next command + SET citus.enable_ddl_propagation TO OFF; + CREATE AGGREGATE array_cat_agg(anycompatiblearray) (SFUNC = array_cat, STYPE = anycompatiblearray); + COMMENT ON AGGREGATE array_cat_agg(anycompatiblearray) + IS 'concatenate input arrays into a single array'; + RESET citus.enable_ddl_propagation; + $cmd$; + ELSE + EXECUTE $cmd$ + SET citus.enable_ddl_propagation TO OFF; + CREATE AGGREGATE array_cat_agg(anyarray) (SFUNC = array_cat, STYPE = anyarray); + COMMENT ON AGGREGATE array_cat_agg(anyarray) + IS 'concatenate input arrays into a single array'; + RESET citus.enable_ddl_propagation; + $cmd$; + END IF; + + -- + -- Citus creates the array_cat_agg but because of a compatibility + -- issue between pg13-pg14, we drop and create it during upgrade. + -- And as Citus creates it, there needs to be a dependency to the + -- Citus extension, so we create that dependency here. + -- We are not using: + -- ALTER EXENSION citus DROP/CREATE AGGREGATE array_cat_agg + -- because we don't have an easy way to check if the aggregate + -- exists with anyarray type or anycompatiblearray type. + + INSERT INTO pg_depend + SELECT + 'pg_proc'::regclass::oid as classid, + (SELECT oid FROM pg_proc WHERE proname = 'array_cat_agg') as objid, + 0 as objsubid, + 'pg_extension'::regclass::oid as refclassid, + (select oid from pg_extension where extname = 'citus') as refobjid, + 0 as refobjsubid , + 'e' as deptype; + + -- + -- restore citus catalog tables + -- + INSERT INTO pg_catalog.pg_dist_partition SELECT * FROM public.pg_dist_partition; + INSERT INTO pg_catalog.pg_dist_shard SELECT * FROM public.pg_dist_shard; + INSERT INTO pg_catalog.pg_dist_placement SELECT * FROM public.pg_dist_placement; + INSERT INTO pg_catalog.pg_dist_node_metadata SELECT * FROM public.pg_dist_node_metadata; + INSERT INTO pg_catalog.pg_dist_node SELECT * FROM public.pg_dist_node; + INSERT INTO pg_catalog.pg_dist_local_group SELECT * FROM public.pg_dist_local_group; + INSERT INTO pg_catalog.pg_dist_transaction SELECT * FROM public.pg_dist_transaction; + INSERT INTO pg_catalog.pg_dist_colocation SELECT * FROM public.pg_dist_colocation; + INSERT INTO pg_catalog.pg_dist_cleanup SELECT * FROM public.pg_dist_cleanup; + INSERT INTO pg_catalog.pg_dist_schema SELECT schemaname::regnamespace, colocationid FROM public.pg_dist_schema; + -- enterprise catalog tables + INSERT INTO pg_catalog.pg_dist_authinfo SELECT * FROM public.pg_dist_authinfo; + INSERT INTO pg_catalog.pg_dist_poolinfo SELECT * FROM public.pg_dist_poolinfo; + + INSERT INTO pg_catalog.pg_dist_rebalance_strategy SELECT + name, + default_strategy, + shard_cost_function::regprocedure::regproc, + node_capacity_function::regprocedure::regproc, + shard_allowed_on_node_function::regprocedure::regproc, + default_threshold, + minimum_threshold, + improvement_threshold + FROM public.pg_dist_rebalance_strategy; + + -- + -- drop backup tables + -- + DROP TABLE public.pg_dist_authinfo; + DROP TABLE public.pg_dist_colocation; + DROP TABLE public.pg_dist_local_group; + DROP TABLE public.pg_dist_node; + DROP TABLE public.pg_dist_node_metadata; + DROP TABLE public.pg_dist_partition; + DROP TABLE public.pg_dist_placement; + DROP TABLE public.pg_dist_poolinfo; + DROP TABLE public.pg_dist_shard; + DROP TABLE public.pg_dist_transaction; + DROP TABLE public.pg_dist_rebalance_strategy; + DROP TABLE public.pg_dist_cleanup; + DROP TABLE public.pg_dist_schema; + -- + -- reset sequences + -- + PERFORM setval('pg_catalog.pg_dist_shardid_seq', (SELECT MAX(shardid)+1 AS max_shard_id FROM pg_dist_shard), false); + PERFORM setval('pg_catalog.pg_dist_placement_placementid_seq', (SELECT MAX(placementid)+1 AS max_placement_id FROM pg_dist_placement), false); + PERFORM setval('pg_catalog.pg_dist_groupid_seq', (SELECT MAX(groupid)+1 AS max_group_id FROM pg_dist_node), false); + PERFORM setval('pg_catalog.pg_dist_node_nodeid_seq', (SELECT MAX(nodeid)+1 AS max_node_id FROM pg_dist_node), false); + PERFORM setval('pg_catalog.pg_dist_colocationid_seq', (SELECT MAX(colocationid)+1 AS max_colocation_id FROM pg_dist_colocation), false); + PERFORM setval('pg_catalog.pg_dist_operationid_seq', (SELECT MAX(operation_id)+1 AS max_operation_id FROM pg_dist_cleanup), false); + PERFORM setval('pg_catalog.pg_dist_cleanup_recordid_seq', (SELECT MAX(record_id)+1 AS max_record_id FROM pg_dist_cleanup), false); + PERFORM setval('pg_catalog.pg_dist_clock_logical_seq', (SELECT last_value FROM public.pg_dist_clock_logical_seq), false); + DROP TABLE public.pg_dist_clock_logical_seq; + + + + -- + -- register triggers + -- + FOR table_name IN SELECT logicalrelid FROM pg_catalog.pg_dist_partition JOIN pg_class ON (logicalrelid = oid) WHERE relkind <> 'f' + LOOP + trigger_name := 'truncate_trigger_' || table_name::oid; + command := 'create trigger ' || trigger_name || ' after truncate on ' || table_name || ' execute procedure pg_catalog.citus_truncate_trigger()'; + EXECUTE command; + command := 'update pg_trigger set tgisinternal = true where tgname = ' || quote_literal(trigger_name); + EXECUTE command; + END LOOP; + + -- + -- set dependencies + -- + INSERT INTO pg_depend + SELECT + 'pg_class'::regclass::oid as classid, + p.logicalrelid::regclass::oid as objid, + 0 as objsubid, + 'pg_extension'::regclass::oid as refclassid, + (select oid from pg_extension where extname = 'citus') as refobjid, + 0 as refobjsubid , + 'n' as deptype + FROM pg_catalog.pg_dist_partition p; + + -- set dependencies for columnar table access method + PERFORM columnar_internal.columnar_ensure_am_depends_catalog(); + + -- restore pg_dist_object from the stable identifiers + TRUNCATE pg_catalog.pg_dist_object; + INSERT INTO pg_catalog.pg_dist_object (classid, objid, objsubid, distribution_argument_index, colocationid) + SELECT + address.classid, + address.objid, + address.objsubid, + naming.distribution_argument_index, + naming.colocationid + FROM + public.pg_dist_object naming, + pg_catalog.pg_get_object_address(naming.type, naming.object_names, naming.object_args) address; + + DROP TABLE public.pg_dist_object; +END; +$cppu$; + +COMMENT ON FUNCTION pg_catalog.citus_finish_pg_upgrade() + IS 'perform tasks to restore citus settings from a location that has been prepared before pg_upgrade'; diff --git a/src/backend/distributed/sql/udfs/citus_finish_pg_upgrade/latest.sql b/src/backend/distributed/sql/udfs/citus_finish_pg_upgrade/latest.sql index 448937c28..6dd46607a 100644 --- a/src/backend/distributed/sql/udfs/citus_finish_pg_upgrade/latest.sql +++ b/src/backend/distributed/sql/udfs/citus_finish_pg_upgrade/latest.sql @@ -63,6 +63,7 @@ BEGIN INSERT INTO pg_catalog.pg_dist_transaction SELECT * FROM public.pg_dist_transaction; INSERT INTO pg_catalog.pg_dist_colocation SELECT * FROM public.pg_dist_colocation; INSERT INTO pg_catalog.pg_dist_cleanup SELECT * FROM public.pg_dist_cleanup; + INSERT INTO pg_catalog.pg_dist_schema SELECT schemaname::regnamespace, colocationid FROM public.pg_dist_schema; -- enterprise catalog tables INSERT INTO pg_catalog.pg_dist_authinfo SELECT * FROM public.pg_dist_authinfo; INSERT INTO pg_catalog.pg_dist_poolinfo SELECT * FROM public.pg_dist_poolinfo; @@ -93,6 +94,7 @@ BEGIN DROP TABLE public.pg_dist_transaction; DROP TABLE public.pg_dist_rebalance_strategy; DROP TABLE public.pg_dist_cleanup; + DROP TABLE public.pg_dist_schema; -- -- reset sequences -- diff --git a/src/backend/distributed/sql/udfs/citus_internal_add_tenant_schema/12.0-1.sql b/src/backend/distributed/sql/udfs/citus_internal_add_tenant_schema/12.0-1.sql new file mode 100644 index 000000000..56b3cae84 --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_internal_add_tenant_schema/12.0-1.sql @@ -0,0 +1,8 @@ +CREATE OR REPLACE FUNCTION pg_catalog.citus_internal_add_tenant_schema(schema_id Oid, colocation_id int) + RETURNS void + LANGUAGE C + VOLATILE + AS 'MODULE_PATHNAME'; + +COMMENT ON FUNCTION pg_catalog.citus_internal_add_tenant_schema(Oid, int) IS + 'insert given tenant schema into pg_dist_schema with given colocation id'; diff --git a/src/backend/distributed/sql/udfs/citus_internal_add_tenant_schema/latest.sql b/src/backend/distributed/sql/udfs/citus_internal_add_tenant_schema/latest.sql new file mode 100644 index 000000000..56b3cae84 --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_internal_add_tenant_schema/latest.sql @@ -0,0 +1,8 @@ +CREATE OR REPLACE FUNCTION pg_catalog.citus_internal_add_tenant_schema(schema_id Oid, colocation_id int) + RETURNS void + LANGUAGE C + VOLATILE + AS 'MODULE_PATHNAME'; + +COMMENT ON FUNCTION pg_catalog.citus_internal_add_tenant_schema(Oid, int) IS + 'insert given tenant schema into pg_dist_schema with given colocation id'; diff --git a/src/backend/distributed/sql/udfs/citus_internal_delete_tenant_schema/12.0-1.sql b/src/backend/distributed/sql/udfs/citus_internal_delete_tenant_schema/12.0-1.sql new file mode 100644 index 000000000..4a2bf0067 --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_internal_delete_tenant_schema/12.0-1.sql @@ -0,0 +1,8 @@ +CREATE OR REPLACE FUNCTION pg_catalog.citus_internal_delete_tenant_schema(schema_id Oid) + RETURNS void + LANGUAGE C + VOLATILE + AS 'MODULE_PATHNAME'; + +COMMENT ON FUNCTION pg_catalog.citus_internal_delete_tenant_schema(Oid) IS + 'delete given tenant schema from pg_dist_schema'; diff --git a/src/backend/distributed/sql/udfs/citus_internal_delete_tenant_schema/latest.sql b/src/backend/distributed/sql/udfs/citus_internal_delete_tenant_schema/latest.sql new file mode 100644 index 000000000..4a2bf0067 --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_internal_delete_tenant_schema/latest.sql @@ -0,0 +1,8 @@ +CREATE OR REPLACE FUNCTION pg_catalog.citus_internal_delete_tenant_schema(schema_id Oid) + RETURNS void + LANGUAGE C + VOLATILE + AS 'MODULE_PATHNAME'; + +COMMENT ON FUNCTION pg_catalog.citus_internal_delete_tenant_schema(Oid) IS + 'delete given tenant schema from pg_dist_schema'; diff --git a/src/backend/distributed/sql/udfs/citus_internal_unregister_tenant_schema_globally/12.0-1.sql b/src/backend/distributed/sql/udfs/citus_internal_unregister_tenant_schema_globally/12.0-1.sql new file mode 100644 index 000000000..1863f1ddf --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_internal_unregister_tenant_schema_globally/12.0-1.sql @@ -0,0 +1,7 @@ +CREATE OR REPLACE FUNCTION pg_catalog.citus_internal_unregister_tenant_schema_globally(schema_id Oid, schema_name text) + RETURNS void + LANGUAGE C + VOLATILE + AS 'MODULE_PATHNAME'; +COMMENT ON FUNCTION pg_catalog.citus_internal_unregister_tenant_schema_globally(schema_id Oid, schema_name text) IS + 'Delete a tenant schema and the corresponding colocation group from metadata tables.'; diff --git a/src/backend/distributed/sql/udfs/citus_internal_unregister_tenant_schema_globally/latest.sql b/src/backend/distributed/sql/udfs/citus_internal_unregister_tenant_schema_globally/latest.sql new file mode 100644 index 000000000..1863f1ddf --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_internal_unregister_tenant_schema_globally/latest.sql @@ -0,0 +1,7 @@ +CREATE OR REPLACE FUNCTION pg_catalog.citus_internal_unregister_tenant_schema_globally(schema_id Oid, schema_name text) + RETURNS void + LANGUAGE C + VOLATILE + AS 'MODULE_PATHNAME'; +COMMENT ON FUNCTION pg_catalog.citus_internal_unregister_tenant_schema_globally(schema_id Oid, schema_name text) IS + 'Delete a tenant schema and the corresponding colocation group from metadata tables.'; diff --git a/src/backend/distributed/sql/udfs/citus_prepare_pg_upgrade/12.0-1.sql b/src/backend/distributed/sql/udfs/citus_prepare_pg_upgrade/12.0-1.sql new file mode 100644 index 000000000..31fa793a6 --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_prepare_pg_upgrade/12.0-1.sql @@ -0,0 +1,82 @@ +CREATE OR REPLACE FUNCTION pg_catalog.citus_prepare_pg_upgrade() + RETURNS void + LANGUAGE plpgsql + SET search_path = pg_catalog + AS $cppu$ +BEGIN + + DELETE FROM pg_depend WHERE + objid IN (SELECT oid FROM pg_proc WHERE proname = 'array_cat_agg') AND + refobjid IN (select oid from pg_extension where extname = 'citus'); + -- + -- We are dropping the aggregates because postgres 14 changed + -- array_cat type from anyarray to anycompatiblearray. When + -- upgrading to pg14, specifically when running pg_restore on + -- array_cat_agg we would get an error. So we drop the aggregate + -- and create the right one on citus_finish_pg_upgrade. + + DROP AGGREGATE IF EXISTS array_cat_agg(anyarray); + DROP AGGREGATE IF EXISTS array_cat_agg(anycompatiblearray); + -- + -- Drop existing backup tables + -- + DROP TABLE IF EXISTS public.pg_dist_partition; + DROP TABLE IF EXISTS public.pg_dist_shard; + DROP TABLE IF EXISTS public.pg_dist_placement; + DROP TABLE IF EXISTS public.pg_dist_node_metadata; + DROP TABLE IF EXISTS public.pg_dist_node; + DROP TABLE IF EXISTS public.pg_dist_local_group; + DROP TABLE IF EXISTS public.pg_dist_transaction; + DROP TABLE IF EXISTS public.pg_dist_colocation; + DROP TABLE IF EXISTS public.pg_dist_authinfo; + DROP TABLE IF EXISTS public.pg_dist_poolinfo; + DROP TABLE IF EXISTS public.pg_dist_rebalance_strategy; + DROP TABLE IF EXISTS public.pg_dist_object; + DROP TABLE IF EXISTS public.pg_dist_cleanup; + DROP TABLE IF EXISTS public.pg_dist_schema; + DROP TABLE IF EXISTS public.pg_dist_clock_logical_seq; + + -- + -- backup citus catalog tables + -- + CREATE TABLE public.pg_dist_partition AS SELECT * FROM pg_catalog.pg_dist_partition; + CREATE TABLE public.pg_dist_shard AS SELECT * FROM pg_catalog.pg_dist_shard; + CREATE TABLE public.pg_dist_placement AS SELECT * FROM pg_catalog.pg_dist_placement; + CREATE TABLE public.pg_dist_node_metadata AS SELECT * FROM pg_catalog.pg_dist_node_metadata; + CREATE TABLE public.pg_dist_node AS SELECT * FROM pg_catalog.pg_dist_node; + CREATE TABLE public.pg_dist_local_group AS SELECT * FROM pg_catalog.pg_dist_local_group; + CREATE TABLE public.pg_dist_transaction AS SELECT * FROM pg_catalog.pg_dist_transaction; + CREATE TABLE public.pg_dist_colocation AS SELECT * FROM pg_catalog.pg_dist_colocation; + CREATE TABLE public.pg_dist_cleanup AS SELECT * FROM pg_catalog.pg_dist_cleanup; + -- save names of the tenant schemas instead of their oids because the oids might change after pg upgrade + CREATE TABLE public.pg_dist_schema AS SELECT schemaid::regnamespace::text AS schemaname, colocationid FROM pg_catalog.pg_dist_schema; + -- enterprise catalog tables + CREATE TABLE public.pg_dist_authinfo AS SELECT * FROM pg_catalog.pg_dist_authinfo; + CREATE TABLE public.pg_dist_poolinfo AS SELECT * FROM pg_catalog.pg_dist_poolinfo; + -- sequences + CREATE TABLE public.pg_dist_clock_logical_seq AS SELECT last_value FROM pg_catalog.pg_dist_clock_logical_seq; + CREATE TABLE public.pg_dist_rebalance_strategy AS SELECT + name, + default_strategy, + shard_cost_function::regprocedure::text, + node_capacity_function::regprocedure::text, + shard_allowed_on_node_function::regprocedure::text, + default_threshold, + minimum_threshold, + improvement_threshold + FROM pg_catalog.pg_dist_rebalance_strategy; + + -- store upgrade stable identifiers on pg_dist_object catalog + CREATE TABLE public.pg_dist_object AS SELECT + address.type, + address.object_names, + address.object_args, + objects.distribution_argument_index, + objects.colocationid + FROM pg_catalog.pg_dist_object objects, + pg_catalog.pg_identify_object_as_address(objects.classid, objects.objid, objects.objsubid) address; +END; +$cppu$; + +COMMENT ON FUNCTION pg_catalog.citus_prepare_pg_upgrade() + IS 'perform tasks to copy citus settings to a location that could later be restored after pg_upgrade is done'; diff --git a/src/backend/distributed/sql/udfs/citus_prepare_pg_upgrade/latest.sql b/src/backend/distributed/sql/udfs/citus_prepare_pg_upgrade/latest.sql index 8fc7d2ee4..31fa793a6 100644 --- a/src/backend/distributed/sql/udfs/citus_prepare_pg_upgrade/latest.sql +++ b/src/backend/distributed/sql/udfs/citus_prepare_pg_upgrade/latest.sql @@ -33,6 +33,7 @@ BEGIN DROP TABLE IF EXISTS public.pg_dist_rebalance_strategy; DROP TABLE IF EXISTS public.pg_dist_object; DROP TABLE IF EXISTS public.pg_dist_cleanup; + DROP TABLE IF EXISTS public.pg_dist_schema; DROP TABLE IF EXISTS public.pg_dist_clock_logical_seq; -- @@ -47,6 +48,8 @@ BEGIN CREATE TABLE public.pg_dist_transaction AS SELECT * FROM pg_catalog.pg_dist_transaction; CREATE TABLE public.pg_dist_colocation AS SELECT * FROM pg_catalog.pg_dist_colocation; CREATE TABLE public.pg_dist_cleanup AS SELECT * FROM pg_catalog.pg_dist_cleanup; + -- save names of the tenant schemas instead of their oids because the oids might change after pg upgrade + CREATE TABLE public.pg_dist_schema AS SELECT schemaid::regnamespace::text AS schemaname, colocationid FROM pg_catalog.pg_dist_schema; -- enterprise catalog tables CREATE TABLE public.pg_dist_authinfo AS SELECT * FROM pg_catalog.pg_dist_authinfo; CREATE TABLE public.pg_dist_poolinfo AS SELECT * FROM pg_catalog.pg_dist_poolinfo; diff --git a/src/backend/distributed/sql/udfs/citus_schema_distribute/12.0-1.sql b/src/backend/distributed/sql/udfs/citus_schema_distribute/12.0-1.sql new file mode 100644 index 000000000..7ca91fbbd --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_schema_distribute/12.0-1.sql @@ -0,0 +1,6 @@ +CREATE OR REPLACE FUNCTION pg_catalog.citus_schema_distribute(schemaname regnamespace) + RETURNS void + LANGUAGE C STRICT + AS 'MODULE_PATHNAME', $$citus_schema_distribute$$; +COMMENT ON FUNCTION pg_catalog.citus_schema_distribute(schemaname regnamespace) + IS 'distributes a schema, allowing it to move between nodes'; diff --git a/src/backend/distributed/sql/udfs/citus_schema_distribute/latest.sql b/src/backend/distributed/sql/udfs/citus_schema_distribute/latest.sql new file mode 100644 index 000000000..7ca91fbbd --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_schema_distribute/latest.sql @@ -0,0 +1,6 @@ +CREATE OR REPLACE FUNCTION pg_catalog.citus_schema_distribute(schemaname regnamespace) + RETURNS void + LANGUAGE C STRICT + AS 'MODULE_PATHNAME', $$citus_schema_distribute$$; +COMMENT ON FUNCTION pg_catalog.citus_schema_distribute(schemaname regnamespace) + IS 'distributes a schema, allowing it to move between nodes'; diff --git a/src/backend/distributed/sql/udfs/citus_schema_undistribute/12.0-1.sql b/src/backend/distributed/sql/udfs/citus_schema_undistribute/12.0-1.sql new file mode 100644 index 000000000..881dd3f5a --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_schema_undistribute/12.0-1.sql @@ -0,0 +1,6 @@ +CREATE OR REPLACE FUNCTION pg_catalog.citus_schema_undistribute(schemaname regnamespace) + RETURNS void + LANGUAGE C STRICT + AS 'MODULE_PATHNAME', $$citus_schema_undistribute$$; +COMMENT ON FUNCTION pg_catalog.citus_schema_undistribute(schemaname regnamespace) + IS 'reverts schema distribution, moving it back to the coordinator'; diff --git a/src/backend/distributed/sql/udfs/citus_schema_undistribute/latest.sql b/src/backend/distributed/sql/udfs/citus_schema_undistribute/latest.sql new file mode 100644 index 000000000..881dd3f5a --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_schema_undistribute/latest.sql @@ -0,0 +1,6 @@ +CREATE OR REPLACE FUNCTION pg_catalog.citus_schema_undistribute(schemaname regnamespace) + RETURNS void + LANGUAGE C STRICT + AS 'MODULE_PATHNAME', $$citus_schema_undistribute$$; +COMMENT ON FUNCTION pg_catalog.citus_schema_undistribute(schemaname regnamespace) + IS 'reverts schema distribution, moving it back to the coordinator'; diff --git a/src/backend/distributed/sql/udfs/citus_schemas/12.0-1.sql b/src/backend/distributed/sql/udfs/citus_schemas/12.0-1.sql new file mode 100644 index 000000000..1760b5ab0 --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_schemas/12.0-1.sql @@ -0,0 +1,42 @@ +DO $$ +declare +citus_schemas_create_query text; +BEGIN +citus_schemas_create_query=$CSCQ$ + CREATE OR REPLACE VIEW %I.citus_schemas AS + SELECT + ts.schemaid::regnamespace AS schema_name, + ts.colocationid AS colocation_id, + CASE + WHEN pg_catalog.has_schema_privilege(CURRENT_USER, ts.schemaid::regnamespace, 'USAGE') + THEN pg_size_pretty(coalesce(schema_sizes.schema_size, 0)) + ELSE NULL + END AS schema_size, + pg_get_userbyid(n.nspowner) AS schema_owner + FROM + pg_dist_schema ts + JOIN + pg_namespace n ON (ts.schemaid = n.oid) + LEFT JOIN ( + SELECT c.relnamespace::regnamespace schema_id, SUM(size) AS schema_size + FROM citus_shard_sizes() css, pg_dist_shard ds, pg_class c + WHERE css.shard_id = ds.shardid AND ds.logicalrelid = c.oid + GROUP BY schema_id + ) schema_sizes ON schema_sizes.schema_id = ts.schemaid + ORDER BY + schema_name; +$CSCQ$; + +IF EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'public') THEN + EXECUTE format(citus_schemas_create_query, 'public'); + REVOKE ALL ON public.citus_schemas FROM public; + GRANT SELECT ON public.citus_schemas TO public; +ELSE + EXECUTE format(citus_schemas_create_query, 'citus'); + ALTER VIEW citus.citus_schemas SET SCHEMA pg_catalog; + REVOKE ALL ON pg_catalog.citus_schemas FROM public; + GRANT SELECT ON pg_catalog.citus_schemas TO public; +END IF; + +END; +$$; diff --git a/src/backend/distributed/sql/udfs/citus_schemas/latest.sql b/src/backend/distributed/sql/udfs/citus_schemas/latest.sql new file mode 100644 index 000000000..1760b5ab0 --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_schemas/latest.sql @@ -0,0 +1,42 @@ +DO $$ +declare +citus_schemas_create_query text; +BEGIN +citus_schemas_create_query=$CSCQ$ + CREATE OR REPLACE VIEW %I.citus_schemas AS + SELECT + ts.schemaid::regnamespace AS schema_name, + ts.colocationid AS colocation_id, + CASE + WHEN pg_catalog.has_schema_privilege(CURRENT_USER, ts.schemaid::regnamespace, 'USAGE') + THEN pg_size_pretty(coalesce(schema_sizes.schema_size, 0)) + ELSE NULL + END AS schema_size, + pg_get_userbyid(n.nspowner) AS schema_owner + FROM + pg_dist_schema ts + JOIN + pg_namespace n ON (ts.schemaid = n.oid) + LEFT JOIN ( + SELECT c.relnamespace::regnamespace schema_id, SUM(size) AS schema_size + FROM citus_shard_sizes() css, pg_dist_shard ds, pg_class c + WHERE css.shard_id = ds.shardid AND ds.logicalrelid = c.oid + GROUP BY schema_id + ) schema_sizes ON schema_sizes.schema_id = ts.schemaid + ORDER BY + schema_name; +$CSCQ$; + +IF EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'public') THEN + EXECUTE format(citus_schemas_create_query, 'public'); + REVOKE ALL ON public.citus_schemas FROM public; + GRANT SELECT ON public.citus_schemas TO public; +ELSE + EXECUTE format(citus_schemas_create_query, 'citus'); + ALTER VIEW citus.citus_schemas SET SCHEMA pg_catalog; + REVOKE ALL ON pg_catalog.citus_schemas FROM public; + GRANT SELECT ON pg_catalog.citus_schemas TO public; +END IF; + +END; +$$; diff --git a/src/backend/distributed/sql/udfs/citus_shard_sizes/12.0-1.sql b/src/backend/distributed/sql/udfs/citus_shard_sizes/12.0-1.sql new file mode 100644 index 000000000..3a647a57b --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_shard_sizes/12.0-1.sql @@ -0,0 +1,6 @@ +CREATE OR REPLACE FUNCTION pg_catalog.citus_shard_sizes(OUT shard_id int, OUT size bigint) + RETURNS SETOF RECORD + LANGUAGE C STRICT + AS 'MODULE_PATHNAME', $$citus_shard_sizes$$; + COMMENT ON FUNCTION pg_catalog.citus_shard_sizes(OUT shard_id int, OUT size bigint) + IS 'returns shards sizes across citus cluster'; diff --git a/src/backend/distributed/sql/udfs/citus_shard_sizes/latest.sql b/src/backend/distributed/sql/udfs/citus_shard_sizes/latest.sql index fd619b0a2..3a647a57b 100644 --- a/src/backend/distributed/sql/udfs/citus_shard_sizes/latest.sql +++ b/src/backend/distributed/sql/udfs/citus_shard_sizes/latest.sql @@ -1,6 +1,6 @@ -CREATE FUNCTION pg_catalog.citus_shard_sizes(OUT table_name text, OUT size bigint) +CREATE OR REPLACE FUNCTION pg_catalog.citus_shard_sizes(OUT shard_id int, OUT size bigint) RETURNS SETOF RECORD LANGUAGE C STRICT AS 'MODULE_PATHNAME', $$citus_shard_sizes$$; - COMMENT ON FUNCTION pg_catalog.citus_shard_sizes(OUT table_name text, OUT size bigint) + COMMENT ON FUNCTION pg_catalog.citus_shard_sizes(OUT shard_id int, OUT size bigint) IS 'returns shards sizes across citus cluster'; diff --git a/src/backend/distributed/sql/udfs/citus_shards/12.0-1.sql b/src/backend/distributed/sql/udfs/citus_shards/12.0-1.sql new file mode 100644 index 000000000..f1be9219d --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_shards/12.0-1.sql @@ -0,0 +1,50 @@ +CREATE OR REPLACE VIEW citus.citus_shards AS +SELECT + pg_dist_shard.logicalrelid AS table_name, + pg_dist_shard.shardid, + shard_name(pg_dist_shard.logicalrelid, pg_dist_shard.shardid) as shard_name, + CASE WHEN colocationid IN (SELECT colocationid FROM pg_dist_schema) THEN 'schema' + WHEN partkey IS NOT NULL THEN 'distributed' + WHEN repmodel = 't' THEN 'reference' + WHEN colocationid = 0 THEN 'local' + ELSE 'distributed' END AS citus_table_type, + colocationid AS colocation_id, + pg_dist_node.nodename, + pg_dist_node.nodeport, + size as shard_size +FROM + pg_dist_shard +JOIN + pg_dist_placement +ON + pg_dist_shard.shardid = pg_dist_placement.shardid +JOIN + pg_dist_node +ON + pg_dist_placement.groupid = pg_dist_node.groupid +JOIN + pg_dist_partition +ON + pg_dist_partition.logicalrelid = pg_dist_shard.logicalrelid +LEFT JOIN + (SELECT shard_id, max(size) as size from citus_shard_sizes() GROUP BY shard_id) as shard_sizes +ON + pg_dist_shard.shardid = shard_sizes.shard_id +WHERE + pg_dist_placement.shardstate = 1 +AND + -- filter out tables owned by extensions + pg_dist_partition.logicalrelid NOT IN ( + SELECT + objid + FROM + pg_depend + WHERE + classid = 'pg_class'::regclass AND refclassid = 'pg_extension'::regclass AND deptype = 'e' + ) +ORDER BY + pg_dist_shard.logicalrelid::text, shardid +; + +ALTER VIEW citus.citus_shards SET SCHEMA pg_catalog; +GRANT SELECT ON pg_catalog.citus_shards TO public; diff --git a/src/backend/distributed/sql/udfs/citus_shards/latest.sql b/src/backend/distributed/sql/udfs/citus_shards/latest.sql index 08e039899..f1be9219d 100644 --- a/src/backend/distributed/sql/udfs/citus_shards/latest.sql +++ b/src/backend/distributed/sql/udfs/citus_shards/latest.sql @@ -1,9 +1,13 @@ -CREATE OR REPLACE VIEW pg_catalog.citus_shards AS +CREATE OR REPLACE VIEW citus.citus_shards AS SELECT pg_dist_shard.logicalrelid AS table_name, pg_dist_shard.shardid, shard_name(pg_dist_shard.logicalrelid, pg_dist_shard.shardid) as shard_name, - CASE WHEN partkey IS NOT NULL THEN 'distributed' WHEN repmodel = 't' THEN 'reference' ELSE 'local' END AS citus_table_type, + CASE WHEN colocationid IN (SELECT colocationid FROM pg_dist_schema) THEN 'schema' + WHEN partkey IS NOT NULL THEN 'distributed' + WHEN repmodel = 't' THEN 'reference' + WHEN colocationid = 0 THEN 'local' + ELSE 'distributed' END AS citus_table_type, colocationid AS colocation_id, pg_dist_node.nodename, pg_dist_node.nodeport, @@ -23,7 +27,7 @@ JOIN ON pg_dist_partition.logicalrelid = pg_dist_shard.logicalrelid LEFT JOIN - (SELECT (regexp_matches(table_name,'_(\d+)$'))[1]::int as shard_id, max(size) as size from citus_shard_sizes() GROUP BY shard_id) as shard_sizes + (SELECT shard_id, max(size) as size from citus_shard_sizes() GROUP BY shard_id) as shard_sizes ON pg_dist_shard.shardid = shard_sizes.shard_id WHERE @@ -42,4 +46,5 @@ ORDER BY pg_dist_shard.logicalrelid::text, shardid ; +ALTER VIEW citus.citus_shards SET SCHEMA pg_catalog; GRANT SELECT ON pg_catalog.citus_shards TO public; diff --git a/src/backend/distributed/sql/udfs/citus_stat_tenants/11.3-1.sql b/src/backend/distributed/sql/udfs/citus_stat_tenants/11.3-1.sql index bd294307c..8eea78b5c 100644 --- a/src/backend/distributed/sql/udfs/citus_stat_tenants/11.3-1.sql +++ b/src/backend/distributed/sql/udfs/citus_stat_tenants/11.3-1.sql @@ -8,6 +8,8 @@ CREATE OR REPLACE FUNCTION pg_catalog.citus_stat_tenants ( OUT read_count_in_last_period INT, OUT query_count_in_this_period INT, OUT query_count_in_last_period INT, + OUT cpu_usage_in_this_period DOUBLE PRECISION, + OUT cpu_usage_in_last_period DOUBLE PRECISION, OUT score BIGINT ) RETURNS SETOF record @@ -51,6 +53,8 @@ AS ( read_count_in_last_period INT, query_count_in_this_period INT, query_count_in_last_period INT, + cpu_usage_in_this_period DOUBLE PRECISION, + cpu_usage_in_last_period DOUBLE PRECISION, score BIGINT ) ORDER BY score DESC @@ -66,7 +70,9 @@ SELECT read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, - query_count_in_last_period + query_count_in_last_period, + cpu_usage_in_this_period, + cpu_usage_in_last_period FROM pg_catalog.citus_stat_tenants(FALSE); ALTER VIEW citus.citus_stat_tenants SET SCHEMA pg_catalog; diff --git a/src/backend/distributed/sql/udfs/citus_stat_tenants/latest.sql b/src/backend/distributed/sql/udfs/citus_stat_tenants/latest.sql index bd294307c..8eea78b5c 100644 --- a/src/backend/distributed/sql/udfs/citus_stat_tenants/latest.sql +++ b/src/backend/distributed/sql/udfs/citus_stat_tenants/latest.sql @@ -8,6 +8,8 @@ CREATE OR REPLACE FUNCTION pg_catalog.citus_stat_tenants ( OUT read_count_in_last_period INT, OUT query_count_in_this_period INT, OUT query_count_in_last_period INT, + OUT cpu_usage_in_this_period DOUBLE PRECISION, + OUT cpu_usage_in_last_period DOUBLE PRECISION, OUT score BIGINT ) RETURNS SETOF record @@ -51,6 +53,8 @@ AS ( read_count_in_last_period INT, query_count_in_this_period INT, query_count_in_last_period INT, + cpu_usage_in_this_period DOUBLE PRECISION, + cpu_usage_in_last_period DOUBLE PRECISION, score BIGINT ) ORDER BY score DESC @@ -66,7 +70,9 @@ SELECT read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, - query_count_in_last_period + query_count_in_last_period, + cpu_usage_in_this_period, + cpu_usage_in_last_period FROM pg_catalog.citus_stat_tenants(FALSE); ALTER VIEW citus.citus_stat_tenants SET SCHEMA pg_catalog; diff --git a/src/backend/distributed/sql/udfs/citus_stat_tenants_local/11.3-1.sql b/src/backend/distributed/sql/udfs/citus_stat_tenants_local/11.3-1.sql index 103ca34b4..c3383241c 100644 --- a/src/backend/distributed/sql/udfs/citus_stat_tenants_local/11.3-1.sql +++ b/src/backend/distributed/sql/udfs/citus_stat_tenants_local/11.3-1.sql @@ -6,6 +6,8 @@ CREATE OR REPLACE FUNCTION pg_catalog.citus_stat_tenants_local( OUT read_count_in_last_period INT, OUT query_count_in_this_period INT, OUT query_count_in_last_period INT, + OUT cpu_usage_in_this_period DOUBLE PRECISION, + OUT cpu_usage_in_last_period DOUBLE PRECISION, OUT score BIGINT) RETURNS SETOF RECORD LANGUAGE C @@ -19,7 +21,9 @@ SELECT read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, - query_count_in_last_period + query_count_in_last_period, + cpu_usage_in_this_period, + cpu_usage_in_last_period FROM pg_catalog.citus_stat_tenants_local() ORDER BY score DESC; diff --git a/src/backend/distributed/sql/udfs/citus_stat_tenants_local/12.0-1.sql b/src/backend/distributed/sql/udfs/citus_stat_tenants_local/12.0-1.sql new file mode 100644 index 000000000..65b398a0a --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_stat_tenants_local/12.0-1.sql @@ -0,0 +1,69 @@ +CREATE OR REPLACE FUNCTION pg_catalog.citus_stat_tenants_local_internal( + return_all_tenants BOOLEAN DEFAULT FALSE, + OUT colocation_id INT, + OUT tenant_attribute TEXT, + OUT read_count_in_this_period INT, + OUT read_count_in_last_period INT, + OUT query_count_in_this_period INT, + OUT query_count_in_last_period INT, + OUT cpu_usage_in_this_period DOUBLE PRECISION, + OUT cpu_usage_in_last_period DOUBLE PRECISION, + OUT score BIGINT) +RETURNS SETOF RECORD +LANGUAGE C +AS 'citus', $$citus_stat_tenants_local$$; + +CREATE OR REPLACE FUNCTION pg_catalog.citus_stat_tenants_local( + return_all_tenants BOOLEAN DEFAULT FALSE, + OUT colocation_id INT, + OUT tenant_attribute TEXT, + OUT read_count_in_this_period INT, + OUT read_count_in_last_period INT, + OUT query_count_in_this_period INT, + OUT query_count_in_last_period INT, + OUT cpu_usage_in_this_period DOUBLE PRECISION, + OUT cpu_usage_in_last_period DOUBLE PRECISION, + OUT score BIGINT) +RETURNS SETOF RECORD +LANGUAGE plpgsql +AS $function$ +BEGIN + RETURN QUERY + SELECT + L.colocation_id, + CASE WHEN L.tenant_attribute IS NULL THEN N.nspname ELSE L.tenant_attribute END COLLATE "default" as tenant_attribute, + L.read_count_in_this_period, + L.read_count_in_last_period, + L.query_count_in_this_period, + L.query_count_in_last_period, + L.cpu_usage_in_this_period, + L.cpu_usage_in_last_period, + L.score + FROM pg_catalog.citus_stat_tenants_local_internal(return_all_tenants) L + LEFT JOIN pg_dist_schema S ON L.tenant_attribute IS NULL AND L.colocation_id = S.colocationid + LEFT JOIN pg_namespace N ON N.oid = S.schemaid + ORDER BY L.score DESC; +END; +$function$; + +CREATE OR REPLACE VIEW pg_catalog.citus_stat_tenants_local AS +SELECT + colocation_id, + tenant_attribute, + read_count_in_this_period, + read_count_in_last_period, + query_count_in_this_period, + query_count_in_last_period, + cpu_usage_in_this_period, + cpu_usage_in_last_period +FROM pg_catalog.citus_stat_tenants_local() +ORDER BY score DESC; + +REVOKE ALL ON FUNCTION pg_catalog.citus_stat_tenants_local_internal(BOOLEAN) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pg_catalog.citus_stat_tenants_local_internal(BOOLEAN) TO pg_monitor; + +REVOKE ALL ON FUNCTION pg_catalog.citus_stat_tenants_local(BOOLEAN) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pg_catalog.citus_stat_tenants_local(BOOLEAN) TO pg_monitor; + +REVOKE ALL ON pg_catalog.citus_stat_tenants_local FROM PUBLIC; +GRANT SELECT ON pg_catalog.citus_stat_tenants_local TO pg_monitor; diff --git a/src/backend/distributed/sql/udfs/citus_stat_tenants_local/latest.sql b/src/backend/distributed/sql/udfs/citus_stat_tenants_local/latest.sql index 103ca34b4..65b398a0a 100644 --- a/src/backend/distributed/sql/udfs/citus_stat_tenants_local/latest.sql +++ b/src/backend/distributed/sql/udfs/citus_stat_tenants_local/latest.sql @@ -1,3 +1,18 @@ +CREATE OR REPLACE FUNCTION pg_catalog.citus_stat_tenants_local_internal( + return_all_tenants BOOLEAN DEFAULT FALSE, + OUT colocation_id INT, + OUT tenant_attribute TEXT, + OUT read_count_in_this_period INT, + OUT read_count_in_last_period INT, + OUT query_count_in_this_period INT, + OUT query_count_in_last_period INT, + OUT cpu_usage_in_this_period DOUBLE PRECISION, + OUT cpu_usage_in_last_period DOUBLE PRECISION, + OUT score BIGINT) +RETURNS SETOF RECORD +LANGUAGE C +AS 'citus', $$citus_stat_tenants_local$$; + CREATE OR REPLACE FUNCTION pg_catalog.citus_stat_tenants_local( return_all_tenants BOOLEAN DEFAULT FALSE, OUT colocation_id INT, @@ -6,24 +21,46 @@ CREATE OR REPLACE FUNCTION pg_catalog.citus_stat_tenants_local( OUT read_count_in_last_period INT, OUT query_count_in_this_period INT, OUT query_count_in_last_period INT, + OUT cpu_usage_in_this_period DOUBLE PRECISION, + OUT cpu_usage_in_last_period DOUBLE PRECISION, OUT score BIGINT) RETURNS SETOF RECORD -LANGUAGE C -AS 'citus', $$citus_stat_tenants_local$$; +LANGUAGE plpgsql +AS $function$ +BEGIN + RETURN QUERY + SELECT + L.colocation_id, + CASE WHEN L.tenant_attribute IS NULL THEN N.nspname ELSE L.tenant_attribute END COLLATE "default" as tenant_attribute, + L.read_count_in_this_period, + L.read_count_in_last_period, + L.query_count_in_this_period, + L.query_count_in_last_period, + L.cpu_usage_in_this_period, + L.cpu_usage_in_last_period, + L.score + FROM pg_catalog.citus_stat_tenants_local_internal(return_all_tenants) L + LEFT JOIN pg_dist_schema S ON L.tenant_attribute IS NULL AND L.colocation_id = S.colocationid + LEFT JOIN pg_namespace N ON N.oid = S.schemaid + ORDER BY L.score DESC; +END; +$function$; - -CREATE OR REPLACE VIEW citus.citus_stat_tenants_local AS +CREATE OR REPLACE VIEW pg_catalog.citus_stat_tenants_local AS SELECT colocation_id, tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, - query_count_in_last_period + query_count_in_last_period, + cpu_usage_in_this_period, + cpu_usage_in_last_period FROM pg_catalog.citus_stat_tenants_local() ORDER BY score DESC; -ALTER VIEW citus.citus_stat_tenants_local SET SCHEMA pg_catalog; +REVOKE ALL ON FUNCTION pg_catalog.citus_stat_tenants_local_internal(BOOLEAN) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pg_catalog.citus_stat_tenants_local_internal(BOOLEAN) TO pg_monitor; REVOKE ALL ON FUNCTION pg_catalog.citus_stat_tenants_local(BOOLEAN) FROM PUBLIC; GRANT EXECUTE ON FUNCTION pg_catalog.citus_stat_tenants_local(BOOLEAN) TO pg_monitor; diff --git a/src/backend/distributed/sql/udfs/citus_tables/12.0-1.sql b/src/backend/distributed/sql/udfs/citus_tables/12.0-1.sql new file mode 100644 index 000000000..608fe8387 --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_tables/12.0-1.sql @@ -0,0 +1,51 @@ +DO $$ +declare +citus_tables_create_query text; +BEGIN +citus_tables_create_query=$CTCQ$ + CREATE OR REPLACE VIEW %I.citus_tables AS + SELECT + logicalrelid AS table_name, + CASE WHEN colocationid IN (SELECT colocationid FROM pg_dist_schema) THEN 'schema' + WHEN partkey IS NOT NULL THEN 'distributed' + WHEN repmodel = 't' THEN 'reference' + WHEN colocationid = 0 THEN 'local' + ELSE 'distributed' + END AS citus_table_type, + coalesce(column_to_column_name(logicalrelid, partkey), '') AS distribution_column, + colocationid AS colocation_id, + pg_size_pretty(citus_total_relation_size(logicalrelid, fail_on_error := false)) AS table_size, + (select count(*) from pg_dist_shard where logicalrelid = p.logicalrelid) AS shard_count, + pg_get_userbyid(relowner) AS table_owner, + amname AS access_method + FROM + pg_dist_partition p + JOIN + pg_class c ON (p.logicalrelid = c.oid) + LEFT JOIN + pg_am a ON (a.oid = c.relam) + WHERE + -- filter out tables owned by extensions + logicalrelid NOT IN ( + SELECT + objid + FROM + pg_depend + WHERE + classid = 'pg_class'::regclass AND refclassid = 'pg_extension'::regclass AND deptype = 'e' + ) + ORDER BY + logicalrelid::text; +$CTCQ$; + +IF EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'public') THEN + EXECUTE format(citus_tables_create_query, 'public'); + GRANT SELECT ON public.citus_tables TO public; +ELSE + EXECUTE format(citus_tables_create_query, 'citus'); + ALTER VIEW citus.citus_tables SET SCHEMA pg_catalog; + GRANT SELECT ON pg_catalog.citus_tables TO public; +END IF; + +END; +$$; diff --git a/src/backend/distributed/sql/udfs/citus_tables/latest.sql b/src/backend/distributed/sql/udfs/citus_tables/latest.sql index ee66852a4..608fe8387 100644 --- a/src/backend/distributed/sql/udfs/citus_tables/latest.sql +++ b/src/backend/distributed/sql/udfs/citus_tables/latest.sql @@ -6,8 +6,11 @@ citus_tables_create_query=$CTCQ$ CREATE OR REPLACE VIEW %I.citus_tables AS SELECT logicalrelid AS table_name, - CASE WHEN partkey IS NOT NULL THEN 'distributed' ELSE - CASE when repmodel = 't' THEN 'reference' ELSE 'local' END + CASE WHEN colocationid IN (SELECT colocationid FROM pg_dist_schema) THEN 'schema' + WHEN partkey IS NOT NULL THEN 'distributed' + WHEN repmodel = 't' THEN 'reference' + WHEN colocationid = 0 THEN 'local' + ELSE 'distributed' END AS citus_table_type, coalesce(column_to_column_name(logicalrelid, partkey), '') AS distribution_column, colocationid AS colocation_id, diff --git a/src/backend/distributed/sql/udfs/drop_old_time_partitions/12.0-1.sql b/src/backend/distributed/sql/udfs/drop_old_time_partitions/12.0-1.sql new file mode 100644 index 000000000..a4bcd45a0 --- /dev/null +++ b/src/backend/distributed/sql/udfs/drop_old_time_partitions/12.0-1.sql @@ -0,0 +1,68 @@ +CREATE OR REPLACE PROCEDURE pg_catalog.drop_old_time_partitions( + table_name regclass, + older_than timestamptz) +LANGUAGE plpgsql +AS $$ +DECLARE + -- properties of the partitioned table + number_of_partition_columns int; + partition_column_index int; + partition_column_type regtype; + + -- used to support dynamic type casting between the partition column type and timestamptz + custom_cast text; + is_partition_column_castable boolean; + older_partitions_query text; + + r record; +BEGIN + -- check whether the table is time partitioned table, if not error out + SELECT partnatts, partattrs[0] + INTO number_of_partition_columns, partition_column_index + FROM pg_catalog.pg_partitioned_table + WHERE partrelid = table_name; + + IF NOT FOUND THEN + RAISE '% is not partitioned', table_name::text; + ELSIF number_of_partition_columns <> 1 THEN + RAISE 'partitioned tables with multiple partition columns are not supported'; + END IF; + + -- get datatype here to check interval-table type + SELECT atttypid + INTO partition_column_type + FROM pg_attribute + WHERE attrelid = table_name::oid + AND attnum = partition_column_index; + + -- we currently only support partitioning by date, timestamp, and timestamptz + custom_cast = ''; + IF partition_column_type <> 'date'::regtype + AND partition_column_type <> 'timestamp'::regtype + AND partition_column_type <> 'timestamptz'::regtype THEN + SELECT EXISTS(SELECT OID FROM pg_cast WHERE castsource = partition_column_type AND casttarget = 'timestamptz'::regtype) AND + EXISTS(SELECT OID FROM pg_cast WHERE castsource = 'timestamptz'::regtype AND casttarget = partition_column_type) + INTO is_partition_column_castable; + IF not is_partition_column_castable THEN + RAISE 'type of the partition column of the table % must be date, timestamp or timestamptz', table_name; + END IF; + custom_cast = format('::%s', partition_column_type); + END IF; + + older_partitions_query = format('SELECT partition, nspname AS schema_name, relname AS table_name, from_value, to_value + FROM pg_catalog.time_partitions, pg_catalog.pg_class c, pg_catalog.pg_namespace n + WHERE parent_table = $1 AND partition = c.oid AND c.relnamespace = n.oid + AND to_value IS NOT NULL + AND to_value%1$s::timestamptz <= $2 + ORDER BY to_value%1$s::timestamptz', custom_cast); + FOR r IN EXECUTE older_partitions_query USING table_name, older_than + LOOP + RAISE NOTICE 'dropping % with start time % and end time %', r.partition, r.from_value, r.to_value; + EXECUTE format('DROP TABLE %I.%I', r.schema_name, r.table_name); + END LOOP; +END; +$$; +COMMENT ON PROCEDURE pg_catalog.drop_old_time_partitions( + table_name regclass, + older_than timestamptz) +IS 'drop old partitions of a time-partitioned table'; diff --git a/src/backend/distributed/sql/udfs/drop_old_time_partitions/latest.sql b/src/backend/distributed/sql/udfs/drop_old_time_partitions/latest.sql index c735d9f67..a4bcd45a0 100644 --- a/src/backend/distributed/sql/udfs/drop_old_time_partitions/latest.sql +++ b/src/backend/distributed/sql/udfs/drop_old_time_partitions/latest.sql @@ -1,6 +1,6 @@ CREATE OR REPLACE PROCEDURE pg_catalog.drop_old_time_partitions( - table_name regclass, - older_than timestamptz) + table_name regclass, + older_than timestamptz) LANGUAGE plpgsql AS $$ DECLARE @@ -9,6 +9,11 @@ DECLARE partition_column_index int; partition_column_type regtype; + -- used to support dynamic type casting between the partition column type and timestamptz + custom_cast text; + is_partition_column_castable boolean; + older_partitions_query text; + r record; BEGIN -- check whether the table is time partitioned table, if not error out @@ -31,19 +36,26 @@ BEGIN AND attnum = partition_column_index; -- we currently only support partitioning by date, timestamp, and timestamptz + custom_cast = ''; IF partition_column_type <> 'date'::regtype AND partition_column_type <> 'timestamp'::regtype AND partition_column_type <> 'timestamptz'::regtype THEN + SELECT EXISTS(SELECT OID FROM pg_cast WHERE castsource = partition_column_type AND casttarget = 'timestamptz'::regtype) AND + EXISTS(SELECT OID FROM pg_cast WHERE castsource = 'timestamptz'::regtype AND casttarget = partition_column_type) + INTO is_partition_column_castable; + IF not is_partition_column_castable THEN RAISE 'type of the partition column of the table % must be date, timestamp or timestamptz', table_name; + END IF; + custom_cast = format('::%s', partition_column_type); END IF; - FOR r IN - SELECT partition, nspname AS schema_name, relname AS table_name, from_value, to_value - FROM pg_catalog.time_partitions, pg_catalog.pg_class c, pg_catalog.pg_namespace n - WHERE parent_table = table_name AND partition = c.oid AND c.relnamespace = n.oid - AND to_value IS NOT NULL - AND to_value::timestamptz <= older_than - ORDER BY to_value::timestamptz + older_partitions_query = format('SELECT partition, nspname AS schema_name, relname AS table_name, from_value, to_value + FROM pg_catalog.time_partitions, pg_catalog.pg_class c, pg_catalog.pg_namespace n + WHERE parent_table = $1 AND partition = c.oid AND c.relnamespace = n.oid + AND to_value IS NOT NULL + AND to_value%1$s::timestamptz <= $2 + ORDER BY to_value%1$s::timestamptz', custom_cast); + FOR r IN EXECUTE older_partitions_query USING table_name, older_than LOOP RAISE NOTICE 'dropping % with start time % and end time %', r.partition, r.from_value, r.to_value; EXECUTE format('DROP TABLE %I.%I', r.schema_name, r.table_name); @@ -51,6 +63,6 @@ BEGIN END; $$; COMMENT ON PROCEDURE pg_catalog.drop_old_time_partitions( - table_name regclass, - older_than timestamptz) + table_name regclass, + older_than timestamptz) IS 'drop old partitions of a time-partitioned table'; diff --git a/src/backend/distributed/sql/udfs/get_missing_time_partition_ranges/12.0-1.sql b/src/backend/distributed/sql/udfs/get_missing_time_partition_ranges/12.0-1.sql new file mode 100644 index 000000000..4cdca999a --- /dev/null +++ b/src/backend/distributed/sql/udfs/get_missing_time_partition_ranges/12.0-1.sql @@ -0,0 +1,239 @@ +CREATE OR REPLACE FUNCTION pg_catalog.get_missing_time_partition_ranges( + table_name regclass, + partition_interval INTERVAL, + to_value timestamptz, + from_value timestamptz DEFAULT now()) +returns table( + partition_name text, + range_from_value text, + range_to_value text) +LANGUAGE plpgsql +AS $$ +DECLARE + -- properties of the partitioned table + table_name_text text; + table_schema_text text; + number_of_partition_columns int; + partition_column_index int; + partition_column_type regtype; + + -- used for generating time ranges + current_range_from_value timestamptz := NULL; + current_range_to_value timestamptz := NULL; + current_range_from_value_text text; + current_range_to_value_text text; + + -- used to check whether there are misaligned (manually created) partitions + manual_partition regclass; + manual_partition_from_value_text text; + manual_partition_to_value_text text; + + -- used for partition naming + partition_name_format text; + max_table_name_length int := current_setting('max_identifier_length'); + + -- used to determine whether the partition_interval is a day multiple + is_day_multiple boolean; + + -- used to support dynamic type casting between the partition column type and timestamptz + custom_cast text; + is_partition_column_castable boolean; + partition regclass; + partition_covers_query text; + partition_exist_query text; +BEGIN + -- check whether the table is time partitioned table, if not error out + SELECT relname, nspname, partnatts, partattrs[0] + INTO table_name_text, table_schema_text, number_of_partition_columns, partition_column_index + FROM pg_catalog.pg_partitioned_table, pg_catalog.pg_class c, pg_catalog.pg_namespace n + WHERE partrelid = c.oid AND c.oid = table_name + AND c.relnamespace = n.oid; + IF NOT FOUND THEN + RAISE '% is not partitioned', table_name; + ELSIF number_of_partition_columns <> 1 THEN + RAISE 'partitioned tables with multiple partition columns are not supported'; + END IF; + + -- to not to have partitions to be created in parallel + EXECUTE format('LOCK TABLE %I.%I IN SHARE UPDATE EXCLUSIVE MODE', table_schema_text, table_name_text); + + -- get datatype here to check interval-table type alignment and generate range values in the right data format + SELECT atttypid + INTO partition_column_type + FROM pg_attribute + WHERE attrelid = table_name::oid + AND attnum = partition_column_index; + + -- we currently only support partitioning by date, timestamp, and timestamptz + custom_cast = ''; + IF partition_column_type <> 'date'::regtype + AND partition_column_type <> 'timestamp'::regtype + AND partition_column_type <> 'timestamptz'::regtype THEN + SELECT EXISTS(SELECT OID FROM pg_cast WHERE castsource = partition_column_type AND casttarget = 'timestamptz'::regtype) AND + EXISTS(SELECT OID FROM pg_cast WHERE castsource = 'timestamptz'::regtype AND casttarget = partition_column_type) + INTO is_partition_column_castable; + IF not is_partition_column_castable THEN + RAISE 'type of the partition column of the table % must be date, timestamp or timestamptz', table_name; + END IF; + custom_cast = format('::%s', partition_column_type); + END IF; + + IF partition_column_type = 'date'::regtype AND partition_interval IS NOT NULL THEN + SELECT date_trunc('day', partition_interval) = partition_interval + INTO is_day_multiple; + + IF NOT is_day_multiple THEN + RAISE 'partition interval of date partitioned table must be day or multiple days'; + END IF; + END IF; + + -- If no partition exists, truncate from_value to find intuitive initial value. + -- If any partition exist, use the initial partition as the pivot partition. + -- tp.to_value and tp.from_value are equal to '', if default partition exists. + EXECUTE format('SELECT tp.from_value%1$s::timestamptz, tp.to_value%1$s::timestamptz + FROM pg_catalog.time_partitions tp + WHERE parent_table = $1 AND tp.to_value <> '' AND tp.from_value <> '' + ORDER BY tp.from_value%1$s::timestamptz ASC + LIMIT 1', custom_cast) + INTO current_range_from_value, current_range_to_value + USING table_name; + + IF current_range_from_value is NULL THEN + -- Decide on the current_range_from_value of the initial partition according to interval of the table. + -- Since we will create all other partitions by adding intervals, truncating given start time will provide + -- more intuitive interval ranges, instead of starting from from_value directly. + IF partition_interval < INTERVAL '1 hour' THEN + current_range_from_value = date_trunc('minute', from_value); + ELSIF partition_interval < INTERVAL '1 day' THEN + current_range_from_value = date_trunc('hour', from_value); + ELSIF partition_interval < INTERVAL '1 week' THEN + current_range_from_value = date_trunc('day', from_value); + ELSIF partition_interval < INTERVAL '1 month' THEN + current_range_from_value = date_trunc('week', from_value); + ELSIF partition_interval = INTERVAL '3 months' THEN + current_range_from_value = date_trunc('quarter', from_value); + ELSIF partition_interval < INTERVAL '1 year' THEN + current_range_from_value = date_trunc('month', from_value); + ELSE + current_range_from_value = date_trunc('year', from_value); + END IF; + + current_range_to_value := current_range_from_value + partition_interval; + ELSE + -- if from_value is newer than pivot's from value, go forward, else go backward + IF from_value >= current_range_from_value THEN + WHILE current_range_from_value < from_value LOOP + current_range_from_value := current_range_from_value + partition_interval; + END LOOP; + ELSE + WHILE current_range_from_value > from_value LOOP + current_range_from_value := current_range_from_value - partition_interval; + END LOOP; + END IF; + current_range_to_value := current_range_from_value + partition_interval; + END IF; + + -- reuse pg_partman naming scheme for back-and-forth migration + IF partition_interval = INTERVAL '3 months' THEN + -- include quarter in partition name + partition_name_format = 'YYYY"q"Q'; + ELSIF partition_interval = INTERVAL '1 week' THEN + -- include week number in partition name + partition_name_format := 'IYYY"w"IW'; + ELSE + -- always start with the year + partition_name_format := 'YYYY'; + + IF partition_interval < INTERVAL '1 year' THEN + -- include month in partition name + partition_name_format := partition_name_format || '_MM'; + END IF; + + IF partition_interval < INTERVAL '1 month' THEN + -- include day of month in partition name + partition_name_format := partition_name_format || '_DD'; + END IF; + + IF partition_interval < INTERVAL '1 day' THEN + -- include time of day in partition name + partition_name_format := partition_name_format || '_HH24MI'; + END IF; + + IF partition_interval < INTERVAL '1 minute' THEN + -- include seconds in time of day in partition name + partition_name_format := partition_name_format || 'SS'; + END IF; + END IF; + + partition_exist_query = format('SELECT partition FROM pg_catalog.time_partitions tp + WHERE tp.from_value%1$s::timestamptz = $1 AND tp.to_value%1$s::timestamptz = $2 AND parent_table = $3', + custom_cast); + partition_covers_query = format('SELECT partition, tp.from_value, tp.to_value + FROM pg_catalog.time_partitions tp + WHERE + (($1 >= tp.from_value%1$s::timestamptz AND $1 < tp.to_value%1$s::timestamptz) OR + ($2 > tp.from_value%1$s::timestamptz AND $2 < tp.to_value%1$s::timestamptz)) AND + parent_table = $3', + custom_cast); + + WHILE current_range_from_value < to_value LOOP + -- Check whether partition with given range has already been created + -- Since partition interval can be given with different types, we are converting + -- all variables to timestamptz to make sure that we are comparing same type of parameters + EXECUTE partition_exist_query into partition using current_range_from_value, current_range_to_value, table_name; + + IF partition is not NULL THEN + current_range_from_value := current_range_to_value; + current_range_to_value := current_range_to_value + partition_interval; + CONTINUE; + END IF; + + -- Check whether any other partition covers from_value or to_value + -- That means some partitions doesn't align with the initial partition. + -- In other words, gap(s) exist between partitions which is not multiple of intervals. + EXECUTE partition_covers_query + INTO manual_partition, manual_partition_from_value_text, manual_partition_to_value_text + using current_range_from_value, current_range_to_value, table_name; + + IF manual_partition is not NULL THEN + RAISE 'partition % with the range from % to % does not align with the initial partition given the partition interval', + manual_partition::text, + manual_partition_from_value_text, + manual_partition_to_value_text + USING HINT = 'Only use partitions of the same size, without gaps between partitions.'; + END IF; + + IF partition_column_type = 'date'::regtype THEN + SELECT current_range_from_value::date::text INTO current_range_from_value_text; + SELECT current_range_to_value::date::text INTO current_range_to_value_text; + ELSIF partition_column_type = 'timestamp without time zone'::regtype THEN + SELECT current_range_from_value::timestamp::text INTO current_range_from_value_text; + SELECT current_range_to_value::timestamp::text INTO current_range_to_value_text; + ELSIF partition_column_type = 'timestamp with time zone'::regtype THEN + SELECT current_range_from_value::timestamptz::text INTO current_range_from_value_text; + SELECT current_range_to_value::timestamptz::text INTO current_range_to_value_text; + ELSE + EXECUTE format('SELECT $1%s::text', custom_cast) INTO current_range_from_value_text using current_range_from_value; + EXECUTE format('SELECT $1%s::text', custom_cast) INTO current_range_to_value_text using current_range_to_value; + END IF; + + -- use range values within the name of partition to have unique partition names + RETURN QUERY + SELECT + substring(table_name_text, 0, max_table_name_length - length(to_char(current_range_from_value, partition_name_format)) - 1) || '_p' || + to_char(current_range_from_value, partition_name_format), + current_range_from_value_text, + current_range_to_value_text; + + current_range_from_value := current_range_to_value; + current_range_to_value := current_range_to_value + partition_interval; + END LOOP; + RETURN; +END; +$$; +COMMENT ON FUNCTION pg_catalog.get_missing_time_partition_ranges( + table_name regclass, + partition_interval INTERVAL, + to_value timestamptz, + from_value timestamptz) +IS 'get missing partitions ranges for table within the range using the given interval'; diff --git a/src/backend/distributed/sql/udfs/get_missing_time_partition_ranges/latest.sql b/src/backend/distributed/sql/udfs/get_missing_time_partition_ranges/latest.sql index 214f03383..4cdca999a 100644 --- a/src/backend/distributed/sql/udfs/get_missing_time_partition_ranges/latest.sql +++ b/src/backend/distributed/sql/udfs/get_missing_time_partition_ranges/latest.sql @@ -34,6 +34,13 @@ DECLARE -- used to determine whether the partition_interval is a day multiple is_day_multiple boolean; + + -- used to support dynamic type casting between the partition column type and timestamptz + custom_cast text; + is_partition_column_castable boolean; + partition regclass; + partition_covers_query text; + partition_exist_query text; BEGIN -- check whether the table is time partitioned table, if not error out SELECT relname, nspname, partnatts, partattrs[0] @@ -58,10 +65,17 @@ BEGIN AND attnum = partition_column_index; -- we currently only support partitioning by date, timestamp, and timestamptz + custom_cast = ''; IF partition_column_type <> 'date'::regtype AND partition_column_type <> 'timestamp'::regtype - AND partition_column_type <> 'timestamptz'::regtype THEN + AND partition_column_type <> 'timestamptz'::regtype THEN + SELECT EXISTS(SELECT OID FROM pg_cast WHERE castsource = partition_column_type AND casttarget = 'timestamptz'::regtype) AND + EXISTS(SELECT OID FROM pg_cast WHERE castsource = 'timestamptz'::regtype AND casttarget = partition_column_type) + INTO is_partition_column_castable; + IF not is_partition_column_castable THEN RAISE 'type of the partition column of the table % must be date, timestamp or timestamptz', table_name; + END IF; + custom_cast = format('::%s', partition_column_type); END IF; IF partition_column_type = 'date'::regtype AND partition_interval IS NOT NULL THEN @@ -76,14 +90,15 @@ BEGIN -- If no partition exists, truncate from_value to find intuitive initial value. -- If any partition exist, use the initial partition as the pivot partition. -- tp.to_value and tp.from_value are equal to '', if default partition exists. - SELECT tp.from_value::timestamptz, tp.to_value::timestamptz + EXECUTE format('SELECT tp.from_value%1$s::timestamptz, tp.to_value%1$s::timestamptz + FROM pg_catalog.time_partitions tp + WHERE parent_table = $1 AND tp.to_value <> '' AND tp.from_value <> '' + ORDER BY tp.from_value%1$s::timestamptz ASC + LIMIT 1', custom_cast) INTO current_range_from_value, current_range_to_value - FROM pg_catalog.time_partitions tp - WHERE parent_table = table_name AND tp.to_value <> '' AND tp.from_value <> '' - ORDER BY tp.from_value::timestamptz ASC - LIMIT 1; + USING table_name; - IF NOT FOUND THEN + IF current_range_from_value is NULL THEN -- Decide on the current_range_from_value of the initial partition according to interval of the table. -- Since we will create all other partitions by adding intervals, truncating given start time will provide -- more intuitive interval ranges, instead of starting from from_value directly. @@ -150,16 +165,24 @@ BEGIN END IF; END IF; + partition_exist_query = format('SELECT partition FROM pg_catalog.time_partitions tp + WHERE tp.from_value%1$s::timestamptz = $1 AND tp.to_value%1$s::timestamptz = $2 AND parent_table = $3', + custom_cast); + partition_covers_query = format('SELECT partition, tp.from_value, tp.to_value + FROM pg_catalog.time_partitions tp + WHERE + (($1 >= tp.from_value%1$s::timestamptz AND $1 < tp.to_value%1$s::timestamptz) OR + ($2 > tp.from_value%1$s::timestamptz AND $2 < tp.to_value%1$s::timestamptz)) AND + parent_table = $3', + custom_cast); + WHILE current_range_from_value < to_value LOOP -- Check whether partition with given range has already been created -- Since partition interval can be given with different types, we are converting -- all variables to timestamptz to make sure that we are comparing same type of parameters - PERFORM * FROM pg_catalog.time_partitions tp - WHERE - tp.from_value::timestamptz = current_range_from_value::timestamptz AND - tp.to_value::timestamptz = current_range_to_value::timestamptz AND - parent_table = table_name; - IF found THEN + EXECUTE partition_exist_query into partition using current_range_from_value, current_range_to_value, table_name; + + IF partition is not NULL THEN current_range_from_value := current_range_to_value; current_range_to_value := current_range_to_value + partition_interval; CONTINUE; @@ -168,20 +191,16 @@ BEGIN -- Check whether any other partition covers from_value or to_value -- That means some partitions doesn't align with the initial partition. -- In other words, gap(s) exist between partitions which is not multiple of intervals. - SELECT partition, tp.from_value::text, tp.to_value::text + EXECUTE partition_covers_query INTO manual_partition, manual_partition_from_value_text, manual_partition_to_value_text - FROM pg_catalog.time_partitions tp - WHERE - ((current_range_from_value::timestamptz >= tp.from_value::timestamptz AND current_range_from_value < tp.to_value::timestamptz) OR - (current_range_to_value::timestamptz > tp.from_value::timestamptz AND current_range_to_value::timestamptz < tp.to_value::timestamptz)) AND - parent_table = table_name; + using current_range_from_value, current_range_to_value, table_name; - IF found THEN + IF manual_partition is not NULL THEN RAISE 'partition % with the range from % to % does not align with the initial partition given the partition interval', manual_partition::text, manual_partition_from_value_text, manual_partition_to_value_text - USING HINT = 'Only use partitions of the same size, without gaps between partitions.'; + USING HINT = 'Only use partitions of the same size, without gaps between partitions.'; END IF; IF partition_column_type = 'date'::regtype THEN @@ -194,7 +213,8 @@ BEGIN SELECT current_range_from_value::timestamptz::text INTO current_range_from_value_text; SELECT current_range_to_value::timestamptz::text INTO current_range_to_value_text; ELSE - RAISE 'type of the partition column of the table % must be date, timestamp or timestamptz', table_name; + EXECUTE format('SELECT $1%s::text', custom_cast) INTO current_range_from_value_text using current_range_from_value; + EXECUTE format('SELECT $1%s::text', custom_cast) INTO current_range_to_value_text using current_range_to_value; END IF; -- use range values within the name of partition to have unique partition names @@ -212,7 +232,7 @@ BEGIN END; $$; COMMENT ON FUNCTION pg_catalog.get_missing_time_partition_ranges( - table_name regclass, + table_name regclass, partition_interval INTERVAL, to_value timestamptz, from_value timestamptz) diff --git a/src/backend/distributed/test/distribution_metadata.c b/src/backend/distributed/test/distribution_metadata.c index cb378e9e3..c3bc7fb51 100644 --- a/src/backend/distributed/test/distribution_metadata.c +++ b/src/backend/distributed/test/distribution_metadata.c @@ -170,6 +170,10 @@ partition_column_id(PG_FUNCTION_ARGS) { Oid distributedTableId = PG_GETARG_OID(0); uint32 rangeTableId = 1; + if (!IsCitusTableType(distributedTableId, HASH_DISTRIBUTED)) + { + ereport(ERROR, (errmsg("table needs to be hash distributed"))); + } Var *partitionColumn = PartitionColumn(distributedTableId, rangeTableId); PG_RETURN_INT16((int16) partitionColumn->varattno); diff --git a/src/backend/distributed/test/fake_am.c b/src/backend/distributed/test/fake_am.c index 5a8ede316..1654bf095 100644 --- a/src/backend/distributed/test/fake_am.c +++ b/src/backend/distributed/test/fake_am.c @@ -169,7 +169,6 @@ fake_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot, } -#if PG_VERSION_NUM >= PG_VERSION_14 static TransactionId fake_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate) @@ -179,20 +178,6 @@ fake_index_delete_tuples(Relation rel, } -#else -static TransactionId -fake_compute_xid_horizon_for_tuples(Relation rel, - ItemPointerData *tids, - int nitems) -{ - elog(ERROR, "fake_compute_xid_horizon_for_tuples not implemented"); - return InvalidTransactionId; -} - - -#endif - - /* ---------------------------------------------------------------------------- * Functions for manipulations of physical tuples for fake AM. * ---------------------------------------------------------------------------- @@ -568,11 +553,7 @@ static const TableAmRoutine fake_methods = { .tuple_get_latest_tid = fake_get_latest_tid, .tuple_tid_valid = fake_tuple_tid_valid, .tuple_satisfies_snapshot = fake_tuple_satisfies_snapshot, -#if PG_VERSION_NUM >= PG_VERSION_14 .index_delete_tuples = fake_index_delete_tuples, -#else - .compute_xid_horizon_for_tuples = fake_compute_xid_horizon_for_tuples, -#endif .relation_set_new_filenode = fake_relation_set_new_filenode, .relation_nontransactional_truncate = fake_relation_nontransactional_truncate, diff --git a/src/backend/distributed/test/foreign_key_relationship_query.c b/src/backend/distributed/test/foreign_key_relationship_query.c index e83e95bac..545c2e970 100644 --- a/src/backend/distributed/test/foreign_key_relationship_query.c +++ b/src/backend/distributed/test/foreign_key_relationship_query.c @@ -119,7 +119,7 @@ get_referencing_relation_id_list(PG_FUNCTION_ARGS) wrapper->listCell = lnext(wrapper->list, wrapper->listCell); - SRF_RETURN_NEXT(functionContext, PointerGetDatum(refId)); + SRF_RETURN_NEXT(functionContext, ObjectIdGetDatum(refId)); } else { @@ -178,7 +178,7 @@ get_referenced_relation_id_list(PG_FUNCTION_ARGS) wrapper->listCell = lnext(wrapper->list, wrapper->listCell); - SRF_RETURN_NEXT(functionContext, PointerGetDatum(refId)); + SRF_RETURN_NEXT(functionContext, ObjectIdGetDatum(refId)); } else { diff --git a/src/backend/distributed/test/prune_shard_list.c b/src/backend/distributed/test/prune_shard_list.c index d83a645dc..a9f5e4a88 100644 --- a/src/backend/distributed/test/prune_shard_list.c +++ b/src/backend/distributed/test/prune_shard_list.c @@ -139,6 +139,10 @@ debug_equality_expression(PG_FUNCTION_ARGS) { Oid distributedTableId = PG_GETARG_OID(0); uint32 rangeTableId = 1; + if (!IsCitusTableType(distributedTableId, HASH_DISTRIBUTED)) + { + ereport(ERROR, (errmsg("table needs to be hash distributed"))); + } Var *partitionColumn = PartitionColumn(distributedTableId, rangeTableId); OpExpr *equalityExpression = MakeOpExpression(partitionColumn, BTEqualStrategyNumber); diff --git a/src/backend/distributed/test/xact_stats.c b/src/backend/distributed/test/xact_stats.c index c31a17b7f..87e15aa64 100644 --- a/src/backend/distributed/test/xact_stats.c +++ b/src/backend/distributed/test/xact_stats.c @@ -48,8 +48,8 @@ MemoryContextTotalSpace(MemoryContext context) Size totalSpace = 0; MemoryContextCounters totals = { 0 }; - TopTransactionContext->methods->stats_compat(TopTransactionContext, NULL, NULL, - &totals, true); + TopTransactionContext->methods->stats(TopTransactionContext, NULL, NULL, + &totals, true); totalSpace += totals.totalspace; for (MemoryContext child = context->firstchild; diff --git a/src/backend/distributed/transaction/backend_data.c b/src/backend/distributed/transaction/backend_data.c index fc89fde9a..3e2ea5ca1 100644 --- a/src/backend/distributed/transaction/backend_data.c +++ b/src/backend/distributed/transaction/backend_data.c @@ -503,11 +503,7 @@ UserHasPermissionToViewStatsOf(Oid currentUserId, Oid backendOwnedId) } if (is_member_of_role(currentUserId, -#if PG_VERSION_NUM >= PG_VERSION_14 ROLE_PG_READ_ALL_STATS)) -#else - DEFAULT_ROLE_READ_ALL_STATS)) -#endif { return true; } diff --git a/src/backend/distributed/transaction/lock_graph.c b/src/backend/distributed/transaction/lock_graph.c index 8c09160b0..0be4bb2e9 100644 --- a/src/backend/distributed/transaction/lock_graph.c +++ b/src/backend/distributed/transaction/lock_graph.c @@ -664,7 +664,7 @@ IsProcessWaitingForSafeOperations(PGPROC *proc) return false; } - if (pgproc_statusflags_compat(proc) & PROC_IS_AUTOVACUUM) + if (proc->statusFlags & PROC_IS_AUTOVACUUM) { return true; } diff --git a/src/backend/distributed/transaction/relation_access_tracking.c b/src/backend/distributed/transaction/relation_access_tracking.c index 2ecbba5b7..b0af4e476 100644 --- a/src/backend/distributed/transaction/relation_access_tracking.c +++ b/src/backend/distributed/transaction/relation_access_tracking.c @@ -195,7 +195,7 @@ RecordRelationAccessIfNonDistTable(Oid relationId, ShardPlacementAccessType acce * recursively calling RecordRelationAccessBase(), so becareful about * removing this check. */ - if (IsCitusTable(relationId) && HasDistributionKey(relationId)) + if (IsCitusTableType(relationId, DISTRIBUTED_TABLE)) { return; } @@ -732,7 +732,7 @@ CheckConflictingRelationAccesses(Oid relationId, ShardPlacementAccessType access CitusTableCacheEntry *cacheEntry = GetCitusTableCacheEntry(relationId); - if (HasDistributionKeyCacheEntry(cacheEntry) || + if (IsCitusTableTypeCacheEntry(cacheEntry, DISTRIBUTED_TABLE) || cacheEntry->referencingRelationsViaForeignKey == NIL) { return; @@ -931,7 +931,7 @@ HoldsConflictingLockWithReferencedRelations(Oid relationId, ShardPlacementAccess * We're only interested in foreign keys to reference tables and citus * local tables. */ - if (IsCitusTable(referencedRelation) && HasDistributionKey(referencedRelation)) + if (IsCitusTableType(referencedRelation, DISTRIBUTED_TABLE)) { continue; } @@ -993,7 +993,7 @@ HoldsConflictingLockWithReferencingRelations(Oid relationId, ShardPlacementAcces CitusTableCacheEntry *cacheEntry = GetCitusTableCacheEntry(relationId); bool holdsConflictingLocks = false; - Assert(!HasDistributionKeyCacheEntry(cacheEntry)); + Assert(!IsCitusTableTypeCacheEntry(cacheEntry, DISTRIBUTED_TABLE)); Oid referencingRelation = InvalidOid; foreach_oid(referencingRelation, cacheEntry->referencingRelationsViaForeignKey) diff --git a/src/backend/distributed/utils/background_jobs.c b/src/backend/distributed/utils/background_jobs.c index 789732d21..2b5ce2dca 100644 --- a/src/backend/distributed/utils/background_jobs.c +++ b/src/backend/distributed/utils/background_jobs.c @@ -395,7 +395,7 @@ citus_task_wait_internal(int64 taskid, BackgroundTaskStatus *desiredStatus) /* sleep for a while, before rechecking the task status */ CHECK_FOR_INTERRUPTS(); - const long delay_ms = 1000; + const long delay_ms = 100; (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, delay_ms, @@ -1436,13 +1436,11 @@ error_severity(int elevel) break; } -#if PG_VERSION_NUM >= PG_VERSION_14 case WARNING_CLIENT_ONLY: { prefix = gettext_noop("WARNING"); break; } -#endif case ERROR: { diff --git a/src/backend/distributed/utils/citus_clauses.c b/src/backend/distributed/utils/citus_clauses.c index c48239548..82900ea1a 100644 --- a/src/backend/distributed/utils/citus_clauses.c +++ b/src/backend/distributed/utils/citus_clauses.c @@ -528,9 +528,9 @@ FixFunctionArgumentsWalker(Node *expr, void *context) elog(ERROR, "cache lookup failed for function %u", funcExpr->funcid); } - funcExpr->args = expand_function_arguments_compat(funcExpr->args, false, - funcExpr->funcresulttype, - func_tuple); + funcExpr->args = expand_function_arguments(funcExpr->args, false, + funcExpr->funcresulttype, + func_tuple); ReleaseSysCache(func_tuple); } diff --git a/src/backend/distributed/utils/citus_copyfuncs.c b/src/backend/distributed/utils/citus_copyfuncs.c index d4e95e16c..7e1379ef3 100644 --- a/src/backend/distributed/utils/citus_copyfuncs.c +++ b/src/backend/distributed/utils/citus_copyfuncs.c @@ -127,9 +127,9 @@ CopyNodeDistributedPlan(COPYFUNC_ARGS) COPY_SCALAR_FIELD(queryId); COPY_NODE_FIELD(relationIdList); COPY_SCALAR_FIELD(targetRelationId); - COPY_NODE_FIELD(insertSelectQuery); - COPY_NODE_FIELD(selectPlanForInsertSelect); - COPY_SCALAR_FIELD(insertSelectMethod); + COPY_NODE_FIELD(modifyQueryViaCoordinatorOrRepartition); + COPY_NODE_FIELD(selectPlanForModifyViaCoordinatorOrRepartition); + COPY_SCALAR_FIELD(modifyWithSelectMethod); COPY_STRING_FIELD(intermediateResultIdPrefix); COPY_NODE_FIELD(subPlanList); diff --git a/src/backend/distributed/utils/citus_depended_object.c b/src/backend/distributed/utils/citus_depended_object.c index 191bbb844..3b5a34b54 100644 --- a/src/backend/distributed/utils/citus_depended_object.c +++ b/src/backend/distributed/utils/citus_depended_object.c @@ -462,21 +462,25 @@ HasDropCommandViolatesOwnership(Node *node) static bool AnyObjectViolatesOwnership(DropStmt *dropStmt) { + bool hasOwnershipViolation = false; volatile ObjectAddress objectAddress = { 0 }; Relation relation = NULL; - bool objectViolatesOwnership = false; ObjectType objectType = dropStmt->removeType; bool missingOk = dropStmt->missing_ok; - Node *object = NULL; - foreach_ptr(object, dropStmt->objects) + MemoryContext savedContext = CurrentMemoryContext; + ResourceOwner savedOwner = CurrentResourceOwner; + BeginInternalSubTransaction(NULL); + MemoryContextSwitchTo(savedContext); + + PG_TRY(); { - PG_TRY(); + Node *object = NULL; + foreach_ptr(object, dropStmt->objects) { objectAddress = get_object_address(objectType, object, &relation, AccessShareLock, missingOk); - if (OidIsValid(objectAddress.objectId)) { /* @@ -487,29 +491,39 @@ AnyObjectViolatesOwnership(DropStmt *dropStmt) objectAddress, object, relation); } - } - PG_CATCH(); - { - if (OidIsValid(objectAddress.objectId)) + + if (relation != NULL) { - /* ownership violation */ - objectViolatesOwnership = true; + relation_close(relation, NoLock); + relation = NULL; } } - PG_END_TRY(); + ReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(savedContext); + CurrentResourceOwner = savedOwner; + } + PG_CATCH(); + { + MemoryContextSwitchTo(savedContext); + ErrorData *edata = CopyErrorData(); + FlushErrorState(); + + hasOwnershipViolation = true; if (relation != NULL) { - relation_close(relation, AccessShareLock); + relation_close(relation, NoLock); relation = NULL; } + RollbackAndReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(savedContext); + CurrentResourceOwner = savedOwner; - /* we found ownership violation, so can return here */ - if (objectViolatesOwnership) - { - return true; - } + /* Rethrow error with LOG_SERVER_ONLY to prevent log to be sent to client */ + edata->elevel = LOG_SERVER_ONLY; + ThrowErrorData(edata); } + PG_END_TRY(); - return false; + return hasOwnershipViolation; } diff --git a/src/backend/distributed/utils/citus_outfuncs.c b/src/backend/distributed/utils/citus_outfuncs.c index b02626233..b4062751a 100644 --- a/src/backend/distributed/utils/citus_outfuncs.c +++ b/src/backend/distributed/utils/citus_outfuncs.c @@ -192,7 +192,9 @@ OutDistributedPlan(OUTFUNC_ARGS) WRITE_UINT64_FIELD(queryId); WRITE_NODE_FIELD(relationIdList); WRITE_OID_FIELD(targetRelationId); - WRITE_NODE_FIELD(insertSelectQuery); + WRITE_NODE_FIELD(modifyQueryViaCoordinatorOrRepartition); + WRITE_NODE_FIELD(selectPlanForModifyViaCoordinatorOrRepartition); + WRITE_ENUM_FIELD(modifyWithSelectMethod, ModifyWithSelectMethod); WRITE_STRING_FIELD(intermediateResultIdPrefix); WRITE_NODE_FIELD(subPlanList); diff --git a/src/backend/distributed/utils/citus_safe_lib.c b/src/backend/distributed/utils/citus_safe_lib.c index 0b830fafc..82fa8f6f2 100644 --- a/src/backend/distributed/utils/citus_safe_lib.c +++ b/src/backend/distributed/utils/citus_safe_lib.c @@ -23,8 +23,6 @@ #include "distributed/citus_safe_lib.h" #include "lib/stringinfo.h" -#define citus_vsnprintf pg_vsnprintf - /* * ereport_constraint_handler is a constraint handler that calls ereport. A @@ -338,7 +336,7 @@ SafeSnprintf(char *restrict buffer, rsize_t bufsz, const char *restrict format, va_list args; va_start(args, format); - size_t result = citus_vsnprintf(buffer, bufsz, format, args); + int result = pg_vsnprintf(buffer, bufsz, format, args); va_end(args); return result; } diff --git a/src/backend/distributed/utils/citus_stat_tenants.c b/src/backend/distributed/utils/citus_stat_tenants.c index 4bf66cb73..aa813e152 100644 --- a/src/backend/distributed/utils/citus_stat_tenants.c +++ b/src/backend/distributed/utils/citus_stat_tenants.c @@ -11,14 +11,17 @@ #include "postgres.h" #include "unistd.h" +#include "access/hash.h" #include "distributed/citus_safe_lib.h" +#include "distributed/colocation_utils.h" +#include "distributed/distributed_planner.h" +#include "distributed/jsonbutils.h" #include "distributed/log_utils.h" #include "distributed/listutils.h" #include "distributed/metadata_cache.h" -#include "distributed/jsonbutils.h" -#include "distributed/colocation_utils.h" +#include "distributed/multi_executor.h" +#include "distributed/tenant_schema_metadata.h" #include "distributed/tuplestore.h" -#include "distributed/colocation_utils.h" #include "distributed/utils/citus_stat_tenants.h" #include "executor/execdesc.h" #include "storage/ipc.h" @@ -28,26 +31,33 @@ #include "utils/builtins.h" #include "utils/datetime.h" #include "utils/json.h" - +#include "utils/lsyscache.h" +#include "utils/syscache.h" #include +#if (PG_VERSION_NUM >= PG_VERSION_15) + #include "common/pg_prng.h" +#endif + static void AttributeMetricsIfApplicable(void); ExecutorEnd_hook_type prev_ExecutorEnd = NULL; -#define ATTRIBUTE_PREFIX "/*{\"tId\":" -#define ATTRIBUTE_STRING_FORMAT "/*{\"tId\":%s,\"cId\":%d}*/" -#define STAT_TENANTS_COLUMNS 7 +#define ATTRIBUTE_PREFIX "/*{\"cId\":" +#define ATTRIBUTE_STRING_FORMAT "/*{\"cId\":%d,\"tId\":%s}*/" +#define ATTRIBUTE_STRING_FORMAT_WITHOUT_TID "/*{\"cId\":%d}*/" +#define STAT_TENANTS_COLUMNS 9 #define ONE_QUERY_SCORE 1000000000 static char AttributeToTenant[MAX_TENANT_ATTRIBUTE_LENGTH] = ""; static CmdType AttributeToCommandType = CMD_UNKNOWN; static int AttributeToColocationGroupId = INVALID_COLOCATION_ID; +static clock_t QueryStartClock = { 0 }; +static clock_t QueryEndClock = { 0 }; static const char *SharedMemoryNameForMultiTenantMonitor = "Shared memory for multi tenant monitor"; -static char *TenantTrancheName = "Tenant Tranche"; static char *MonitorTrancheName = "Multi Tenant Monitor Tranche"; static shmem_startup_hook_type prev_shmem_startup_hook = NULL; @@ -57,12 +67,14 @@ static void UpdatePeriodsIfNecessary(TenantStats *tenantStats, TimestampTz query static void ReduceScoreIfNecessary(TenantStats *tenantStats, TimestampTz queryTime); static void EvictTenantsIfNecessary(TimestampTz queryTime); static void RecordTenantStats(TenantStats *tenantStats, TimestampTz queryTime); -static void CreateMultiTenantMonitor(void); static MultiTenantMonitor * CreateSharedMemoryForMultiTenantMonitor(void); static MultiTenantMonitor * GetMultiTenantMonitor(void); static void MultiTenantMonitorSMInit(void); -static int CreateTenantStats(MultiTenantMonitor *monitor, TimestampTz queryTime); -static int FindTenantStats(MultiTenantMonitor *monitor); +static TenantStats * CreateTenantStats(MultiTenantMonitor *monitor, TimestampTz + queryTime); +static void FillTenantStatsHashKey(TenantStatsHashKey *key, char *tenantAttribute, uint32 + colocationGroupId); +static TenantStats * FindTenantStats(MultiTenantMonitor *monitor); static size_t MultiTenantMonitorshmemSize(void); static char * ExtractTopComment(const char *inputString); static char * EscapeCommentChars(const char *str); @@ -72,7 +84,7 @@ int StatTenantsLogLevel = CITUS_LOG_LEVEL_OFF; int StatTenantsPeriod = (time_t) 60; int StatTenantsLimit = 100; int StatTenantsTrack = STAT_TENANTS_TRACK_NONE; - +double StatTenantsSampleRateForNewTenants = 1; PG_FUNCTION_INFO_V1(citus_stat_tenants_local); PG_FUNCTION_INFO_V1(citus_stat_tenants_local_reset); @@ -110,21 +122,36 @@ citus_stat_tenants_local(PG_FUNCTION_ARGS) LWLockAcquire(&monitor->lock, LW_EXCLUSIVE); int numberOfRowsToReturn = 0; + int tenantStatsCount = hash_get_num_entries(monitor->tenants); if (returnAllTenants) { - numberOfRowsToReturn = monitor->tenantCount; + numberOfRowsToReturn = tenantStatsCount; } else { - numberOfRowsToReturn = Min(monitor->tenantCount, StatTenantsLimit); + numberOfRowsToReturn = Min(tenantStatsCount, + StatTenantsLimit); } - for (int tenantIndex = 0; tenantIndex < monitor->tenantCount; tenantIndex++) + /* Allocate an array to hold the tenants. */ + TenantStats **stats = palloc(tenantStatsCount * + sizeof(TenantStats *)); + + HASH_SEQ_STATUS hash_seq; + TenantStats *stat; + + /* Get all the tenants from the hash table. */ + int j = 0; + hash_seq_init(&hash_seq, monitor->tenants); + while ((stat = hash_seq_search(&hash_seq)) != NULL) { - UpdatePeriodsIfNecessary(&monitor->tenants[tenantIndex], monitoringTime); - ReduceScoreIfNecessary(&monitor->tenants[tenantIndex], monitoringTime); + stats[j++] = stat; + UpdatePeriodsIfNecessary(stat, monitoringTime); + ReduceScoreIfNecessary(stat, monitoringTime); } - SafeQsort(monitor->tenants, monitor->tenantCount, sizeof(TenantStats), + + /* Sort the tenants by their score. */ + SafeQsort(stats, j, sizeof(TenantStats *), CompareTenantScore); for (int i = 0; i < numberOfRowsToReturn; i++) @@ -132,21 +159,35 @@ citus_stat_tenants_local(PG_FUNCTION_ARGS) memset(values, 0, sizeof(values)); memset(isNulls, false, sizeof(isNulls)); - TenantStats *tenantStats = &monitor->tenants[i]; + TenantStats *tenantStats = stats[i]; + + values[0] = Int32GetDatum(tenantStats->key.colocationGroupId); + + if (tenantStats->key.tenantAttribute[0] == '\0') + { + isNulls[1] = true; + } + else + { + values[1] = PointerGetDatum(cstring_to_text( + tenantStats->key.tenantAttribute)); + } - values[0] = Int32GetDatum(tenantStats->colocationGroupId); - values[1] = PointerGetDatum(cstring_to_text(tenantStats->tenantAttribute)); values[2] = Int32GetDatum(tenantStats->readsInThisPeriod); values[3] = Int32GetDatum(tenantStats->readsInLastPeriod); values[4] = Int32GetDatum(tenantStats->readsInThisPeriod + tenantStats->writesInThisPeriod); values[5] = Int32GetDatum(tenantStats->readsInLastPeriod + tenantStats->writesInLastPeriod); - values[6] = Int64GetDatum(tenantStats->score); + values[6] = Float8GetDatum(tenantStats->cpuUsageInThisPeriod); + values[7] = Float8GetDatum(tenantStats->cpuUsageInLastPeriod); + values[8] = Int64GetDatum(tenantStats->score); tuplestore_putvalues(tupleStore, tupleDescriptor, values, isNulls); } + pfree(stats); + LWLockRelease(&monitor->lock); PG_RETURN_VOID(); @@ -161,7 +202,25 @@ Datum citus_stat_tenants_local_reset(PG_FUNCTION_ARGS) { MultiTenantMonitor *monitor = GetMultiTenantMonitor(); - monitor->tenantCount = 0; + + /* if monitor is not created yet, there is nothing to reset */ + if (monitor == NULL) + { + PG_RETURN_VOID(); + } + + HASH_SEQ_STATUS hash_seq; + TenantStats *stats; + + LWLockAcquire(&monitor->lock, LW_EXCLUSIVE); + + hash_seq_init(&hash_seq, monitor->tenants); + while ((stats = hash_seq_search(&hash_seq)) != NULL) + { + hash_search(monitor->tenants, &stats->key, HASH_REMOVE, NULL); + } + + LWLockRelease(&monitor->lock); PG_RETURN_VOID(); } @@ -179,7 +238,7 @@ AttributeQueryIfAnnotated(const char *query_string, CmdType commandType) return; } - strcpy_s(AttributeToTenant, sizeof(AttributeToTenant), ""); + AttributeToColocationGroupId = INVALID_COLOCATION_ID; if (query_string == NULL) { @@ -216,40 +275,115 @@ void AttributeTask(char *tenantId, int colocationId, CmdType commandType) { if (StatTenantsTrack == STAT_TENANTS_TRACK_NONE || - tenantId == NULL || colocationId == INVALID_COLOCATION_ID) + colocationId == INVALID_COLOCATION_ID) { return; } + TenantStatsHashKey key = { 0 }; + FillTenantStatsHashKey(&key, tenantId, colocationId); + + MultiTenantMonitor *monitor = GetMultiTenantMonitor(); + bool found = false; + + /* Acquire the lock in shared mode to check if the tenant is already in the hash table. */ + LWLockAcquire(&monitor->lock, LW_SHARED); + + hash_search(monitor->tenants, &key, HASH_FIND, &found); + + LWLockRelease(&monitor->lock); + + /* If the tenant is not found in the hash table, we will track the query with a probability of StatTenantsSampleRateForNewTenants. */ + if (!found) + { +#if (PG_VERSION_NUM >= PG_VERSION_15) + double randomValue = pg_prng_double(&pg_global_prng_state); +#else + + /* Generate a random double between 0 and 1 */ + double randomValue = (double) random() / MAX_RANDOM_VALUE; +#endif + bool shouldTrackQuery = randomValue <= StatTenantsSampleRateForNewTenants; + if (!shouldTrackQuery) + { + return; + } + } + + /* + * if tenantId is NULL, it must be a schema-based tenant and + * we try to get the tenantId from the colocationId to lookup schema name and use it as a tenantId + */ + if (tenantId == NULL) + { + if (!IsTenantSchemaColocationGroup(colocationId)) + { + return; + } + } + AttributeToColocationGroupId = colocationId; - strncpy_s(AttributeToTenant, MAX_TENANT_ATTRIBUTE_LENGTH, tenantId, - MAX_TENANT_ATTRIBUTE_LENGTH - 1); + if (tenantId != NULL) + { + strncpy_s(AttributeToTenant, MAX_TENANT_ATTRIBUTE_LENGTH, tenantId, + MAX_TENANT_ATTRIBUTE_LENGTH - 1); + } + else + { + strcpy_s(AttributeToTenant, sizeof(AttributeToTenant), ""); + } AttributeToCommandType = commandType; + QueryStartClock = clock(); } /* * AnnotateQuery annotates the query with tenant attributes. + * if the query has a partition key, we annotate it with the partition key value and colocationId + * if the query doesn't have a partition key and if it's a schema-based tenant, we annotate it with the colocationId only. */ char * AnnotateQuery(char *queryString, Const *partitionKeyValue, int colocationId) { - if (StatTenantsTrack == STAT_TENANTS_TRACK_NONE || partitionKeyValue == NULL) + if (StatTenantsTrack == STAT_TENANTS_TRACK_NONE || + colocationId == INVALID_COLOCATION_ID) { return queryString; } - char *partitionKeyValueString = DatumToString(partitionKeyValue->constvalue, - partitionKeyValue->consttype); - - char *commentCharsEscaped = EscapeCommentChars(partitionKeyValueString); - StringInfo escapedSourceName = makeStringInfo(); - - escape_json(escapedSourceName, commentCharsEscaped); - StringInfo newQuery = makeStringInfo(); - appendStringInfo(newQuery, ATTRIBUTE_STRING_FORMAT, escapedSourceName->data, - colocationId); + + /* if the query doesn't have a parititon key value, check if it is a tenant schema */ + if (partitionKeyValue == NULL) + { + if (IsTenantSchemaColocationGroup(colocationId)) + { + /* If it is a schema-based tenant, we only annotate the query with colocationId */ + appendStringInfo(newQuery, ATTRIBUTE_STRING_FORMAT_WITHOUT_TID, + colocationId); + } + else + { + /* If it is not a schema-based tenant query and doesn't have a parititon key, + * we don't annotate it + */ + return queryString; + } + } + else + { + /* if the query has a partition key value, we annotate it with both tenantId and colocationId */ + char *partitionKeyValueString = DatumToString(partitionKeyValue->constvalue, + partitionKeyValue->consttype); + + char *commentCharsEscaped = EscapeCommentChars(partitionKeyValueString); + StringInfo escapedSourceName = makeStringInfo(); + escape_json(escapedSourceName, commentCharsEscaped); + + appendStringInfo(newQuery, ATTRIBUTE_STRING_FORMAT, colocationId, + escapedSourceName->data + ); + } appendStringInfoString(newQuery, queryString); @@ -289,14 +423,14 @@ CitusAttributeToEnd(QueryDesc *queryDesc) static int CompareTenantScore(const void *leftElement, const void *rightElement) { - const TenantStats *leftTenant = (const TenantStats *) leftElement; - const TenantStats *rightTenant = (const TenantStats *) rightElement; + double l_usage = (*(TenantStats *const *) leftElement)->score; + double r_usage = (*(TenantStats *const *) rightElement)->score; - if (leftTenant->score > rightTenant->score) + if (l_usage > r_usage) { return -1; } - else if (leftTenant->score < rightTenant->score) + else if (l_usage < r_usage) { return 1; } @@ -311,11 +445,22 @@ static void AttributeMetricsIfApplicable() { if (StatTenantsTrack == STAT_TENANTS_TRACK_NONE || - AttributeToTenant[0] == '\0') + AttributeToColocationGroupId == INVALID_COLOCATION_ID) { return; } + /* + * return if we are not in the top level to make sure we are not + * stopping counting time for a sub-level execution + */ + if (ExecutorLevel != 0 || PlannerLevel != 0) + { + return; + } + + QueryEndClock = clock(); + TimestampTz queryTime = GetCurrentTimestamp(); MultiTenantMonitor *monitor = GetMultiTenantMonitor(); @@ -336,50 +481,48 @@ AttributeMetricsIfApplicable() */ LWLockAcquire(&monitor->lock, LW_SHARED); - int currentTenantIndex = FindTenantStats(monitor); + TenantStats *tenantStats = FindTenantStats(monitor); - if (currentTenantIndex != -1) + if (tenantStats != NULL) { - TenantStats *tenantStats = &monitor->tenants[currentTenantIndex]; - LWLockAcquire(&tenantStats->lock, LW_EXCLUSIVE); + SpinLockAcquire(&tenantStats->lock); UpdatePeriodsIfNecessary(tenantStats, queryTime); ReduceScoreIfNecessary(tenantStats, queryTime); RecordTenantStats(tenantStats, queryTime); - LWLockRelease(&tenantStats->lock); + SpinLockRelease(&tenantStats->lock); } else { LWLockRelease(&monitor->lock); LWLockAcquire(&monitor->lock, LW_EXCLUSIVE); - currentTenantIndex = FindTenantStats(monitor); + tenantStats = FindTenantStats(monitor); - if (currentTenantIndex == -1) + if (tenantStats == NULL) { - currentTenantIndex = CreateTenantStats(monitor, queryTime); + tenantStats = CreateTenantStats(monitor, queryTime); } LWLockRelease(&monitor->lock); LWLockAcquire(&monitor->lock, LW_SHARED); - currentTenantIndex = FindTenantStats(monitor); - if (currentTenantIndex != -1) + tenantStats = FindTenantStats(monitor); + if (tenantStats != NULL) { - TenantStats *tenantStats = &monitor->tenants[currentTenantIndex]; - LWLockAcquire(&tenantStats->lock, LW_EXCLUSIVE); + SpinLockAcquire(&tenantStats->lock); UpdatePeriodsIfNecessary(tenantStats, queryTime); ReduceScoreIfNecessary(tenantStats, queryTime); RecordTenantStats(tenantStats, queryTime); - LWLockRelease(&tenantStats->lock); + SpinLockRelease(&tenantStats->lock); } } LWLockRelease(&monitor->lock); - strcpy_s(AttributeToTenant, sizeof(AttributeToTenant), ""); + AttributeToColocationGroupId = INVALID_COLOCATION_ID; } @@ -411,6 +554,9 @@ UpdatePeriodsIfNecessary(TenantStats *tenantStats, TimestampTz queryTime) tenantStats->readsInLastPeriod = tenantStats->readsInThisPeriod; tenantStats->readsInThisPeriod = 0; + + tenantStats->cpuUsageInLastPeriod = tenantStats->cpuUsageInThisPeriod; + tenantStats->cpuUsageInThisPeriod = 0; } /* @@ -422,6 +568,8 @@ UpdatePeriodsIfNecessary(TenantStats *tenantStats, TimestampTz queryTime) tenantStats->writesInLastPeriod = 0; tenantStats->readsInLastPeriod = 0; + + tenantStats->cpuUsageInLastPeriod = 0; } } @@ -485,15 +633,29 @@ EvictTenantsIfNecessary(TimestampTz queryTime) * * Every time tenant count hits StatTenantsLimit * 3, we reduce it back to StatTenantsLimit * 2. */ - if (monitor->tenantCount >= StatTenantsLimit * 3) + long tenantStatsCount = hash_get_num_entries(monitor->tenants); + if (tenantStatsCount >= StatTenantsLimit * 3) { - for (int tenantIndex = 0; tenantIndex < monitor->tenantCount; tenantIndex++) + HASH_SEQ_STATUS hash_seq; + TenantStats *stat; + TenantStats **stats = palloc(tenantStatsCount * + sizeof(TenantStats *)); + + int i = 0; + hash_seq_init(&hash_seq, monitor->tenants); + while ((stat = hash_seq_search(&hash_seq)) != NULL) { - ReduceScoreIfNecessary(&monitor->tenants[tenantIndex], queryTime); + stats[i++] = stat; } - SafeQsort(monitor->tenants, monitor->tenantCount, sizeof(TenantStats), - CompareTenantScore); - monitor->tenantCount = StatTenantsLimit * 2; + + SafeQsort(stats, i, sizeof(TenantStats *), CompareTenantScore); + + for (i = StatTenantsLimit * 2; i < tenantStatsCount; i++) + { + hash_search(monitor->tenants, &stats[i]->key, HASH_REMOVE, NULL); + } + + pfree(stats); } } @@ -524,21 +686,13 @@ RecordTenantStats(TenantStats *tenantStats, TimestampTz queryTime) tenantStats->writesInThisPeriod++; } + double queryCpuTime = ((double) (QueryEndClock - QueryStartClock)) / CLOCKS_PER_SEC; + tenantStats->cpuUsageInThisPeriod += queryCpuTime; + tenantStats->lastQueryTime = queryTime; } -/* - * CreateMultiTenantMonitor creates the data structure for multi tenant monitor. - */ -static void -CreateMultiTenantMonitor() -{ - MultiTenantMonitor *monitor = CreateSharedMemoryForMultiTenantMonitor(); - monitor->tenantCount = 0; -} - - /* * CreateSharedMemoryForMultiTenantMonitor creates a dynamic shared memory segment for multi tenant monitor. */ @@ -561,6 +715,17 @@ CreateSharedMemoryForMultiTenantMonitor() monitor->namedLockTranche.trancheName); LWLockInitialize(&monitor->lock, monitor->namedLockTranche.trancheId); + HASHCTL info; + + memset(&info, 0, sizeof(info)); + info.keysize = sizeof(TenantStatsHashKey); + info.entrysize = sizeof(TenantStats); + + monitor->tenants = ShmemInitHash("citus_stats_tenants hash", + StatTenantsLimit * 3, StatTenantsLimit * 3, + &info, HASH_ELEM | + HASH_SHARED_MEM | HASH_BLOBS); + return monitor; } @@ -604,7 +769,7 @@ InitializeMultiTenantMonitorSMHandleManagement() static void MultiTenantMonitorSMInit() { - CreateMultiTenantMonitor(); + CreateSharedMemoryForMultiTenantMonitor(); if (prev_shmem_startup_hook != NULL) { @@ -618,7 +783,7 @@ MultiTenantMonitorSMInit() * * Calling this function should be protected by the monitor->lock in LW_EXCLUSIVE mode. */ -static int +static TenantStats * CreateTenantStats(MultiTenantMonitor *monitor, TimestampTz queryTime) { /* @@ -627,45 +792,55 @@ CreateTenantStats(MultiTenantMonitor *monitor, TimestampTz queryTime) */ EvictTenantsIfNecessary(queryTime); - int tenantIndex = monitor->tenantCount; + TenantStatsHashKey key = { 0 }; + FillTenantStatsHashKey(&key, AttributeToTenant, AttributeToColocationGroupId); - memset(&monitor->tenants[tenantIndex], 0, sizeof(monitor->tenants[tenantIndex])); + TenantStats *stats = (TenantStats *) hash_search(monitor->tenants, &key, + HASH_ENTER, NULL); - strcpy_s(monitor->tenants[tenantIndex].tenantAttribute, - sizeof(monitor->tenants[tenantIndex].tenantAttribute), AttributeToTenant); - monitor->tenants[tenantIndex].colocationGroupId = AttributeToColocationGroupId; + stats->writesInLastPeriod = 0; + stats->writesInThisPeriod = 0; + stats->readsInLastPeriod = 0; + stats->readsInThisPeriod = 0; + stats->cpuUsageInLastPeriod = 0; + stats->cpuUsageInThisPeriod = 0; + stats->score = 0; + stats->lastScoreReduction = 0; - monitor->tenants[tenantIndex].namedLockTranche.trancheId = LWLockNewTrancheId(); - monitor->tenants[tenantIndex].namedLockTranche.trancheName = TenantTrancheName; + SpinLockInit(&stats->lock); - LWLockRegisterTranche(monitor->tenants[tenantIndex].namedLockTranche.trancheId, - monitor->tenants[tenantIndex].namedLockTranche.trancheName); - LWLockInitialize(&monitor->tenants[tenantIndex].lock, - monitor->tenants[tenantIndex].namedLockTranche.trancheId); - - monitor->tenantCount++; - - return tenantIndex; + return stats; } /* - * FindTenantStats finds the index for the current tenant's statistics. + * FindTenantStats finds the current tenant's statistics. */ -static int +static TenantStats * FindTenantStats(MultiTenantMonitor *monitor) { - for (int i = 0; i < monitor->tenantCount; i++) + TenantStatsHashKey key = { 0 }; + FillTenantStatsHashKey(&key, AttributeToTenant, AttributeToColocationGroupId); + + TenantStats *stats = (TenantStats *) hash_search(monitor->tenants, &key, + HASH_FIND, NULL); + + return stats; +} + + +static void +FillTenantStatsHashKey(TenantStatsHashKey *key, char *tenantAttribute, uint32 + colocationGroupId) +{ + memset(key->tenantAttribute, 0, MAX_TENANT_ATTRIBUTE_LENGTH); + + if (tenantAttribute != NULL) { - TenantStats *tenantStats = &monitor->tenants[i]; - if (strcmp(tenantStats->tenantAttribute, AttributeToTenant) == 0 && - tenantStats->colocationGroupId == AttributeToColocationGroupId) - { - return i; - } + strlcpy(key->tenantAttribute, tenantAttribute, MAX_TENANT_ATTRIBUTE_LENGTH); } - return -1; + key->colocationGroupId = colocationGroupId; } diff --git a/src/backend/distributed/utils/colocation_utils.c b/src/backend/distributed/utils/colocation_utils.c index 985d4c38e..c386e9fcf 100644 --- a/src/backend/distributed/utils/colocation_utils.c +++ b/src/backend/distributed/utils/colocation_utils.c @@ -20,6 +20,7 @@ #include "catalog/pg_type.h" #include "commands/sequence.h" #include "distributed/colocation_utils.h" +#include "distributed/commands.h" #include "distributed/listutils.h" #include "distributed/metadata_utility.h" #include "distributed/coordinator_protocol.h" @@ -30,6 +31,7 @@ #include "distributed/pg_dist_colocation.h" #include "distributed/resource_lock.h" #include "distributed/shardinterval_utils.h" +#include "distributed/tenant_schema_metadata.h" #include "distributed/version_compat.h" #include "distributed/utils/array_type.h" #include "distributed/worker_protocol.h" @@ -49,7 +51,6 @@ static bool HashPartitionedShardIntervalsEqual(ShardInterval *leftShardInterval, ShardInterval *rightShardInterval); static int CompareShardPlacementsByNode(const void *leftElement, const void *rightElement); -static void DeleteColocationGroup(uint32 colocationId); static uint32 CreateColocationGroupForRelation(Oid sourceRelationId); static void BreakColocation(Oid sourceRelationId); @@ -115,16 +116,19 @@ update_distributed_table_colocation(PG_FUNCTION_ARGS) text *colocateWithTableNameText = PG_GETARG_TEXT_P(1); EnsureTableOwner(targetRelationId); + ErrorIfTenantTable(targetRelationId, TenantOperationNames[TENANT_UPDATE_COLOCATION]); char *colocateWithTableName = text_to_cstring(colocateWithTableNameText); if (IsColocateWithNone(colocateWithTableName)) { - EnsureHashDistributedTable(targetRelationId); + EnsureHashOrSingleShardDistributedTable(targetRelationId); BreakColocation(targetRelationId); } else { Oid colocateWithTableId = ResolveRelationId(colocateWithTableNameText, false); + ErrorIfTenantTable(colocateWithTableId, + TenantOperationNames[TENANT_COLOCATE_WITH]); EnsureTableOwner(colocateWithTableId); MarkTablesColocated(colocateWithTableId, targetRelationId); } @@ -263,8 +267,8 @@ MarkTablesColocated(Oid sourceRelationId, Oid targetRelationId) "other tables"))); } - EnsureHashDistributedTable(sourceRelationId); - EnsureHashDistributedTable(targetRelationId); + EnsureHashOrSingleShardDistributedTable(sourceRelationId); + EnsureHashOrSingleShardDistributedTable(targetRelationId); CheckReplicationModel(sourceRelationId, targetRelationId); CheckDistributionColumnType(sourceRelationId, targetRelationId); @@ -546,6 +550,13 @@ ColocationId(int shardCount, int replicationFactor, Oid distributionColumnType, Form_pg_dist_colocation colocationForm = (Form_pg_dist_colocation) GETSTRUCT(colocationTuple); + /* avoid chosing a colocation group that belongs to a tenant schema */ + if (IsTenantSchemaColocationGroup(colocationForm->colocationid)) + { + colocationTuple = systable_getnext(scanDescriptor); + continue; + } + if (colocationId == INVALID_COLOCATION_ID || colocationId > colocationForm->colocationid) { @@ -1258,9 +1269,9 @@ DeleteColocationGroupIfNoTablesBelong(uint32 colocationId) /* * DeleteColocationGroup deletes the colocation group from pg_dist_colocation - * throughout the cluster. + * throughout the cluster and dissociates the tenant schema if any. */ -static void +void DeleteColocationGroup(uint32 colocationId) { DeleteColocationGroupLocally(colocationId); @@ -1384,17 +1395,19 @@ EnsureTableCanBeColocatedWith(Oid relationId, char replicationModel, Oid distributionColumnType, Oid sourceRelationId) { CitusTableCacheEntry *sourceTableEntry = GetCitusTableCacheEntry(sourceRelationId); - char sourceReplicationModel = sourceTableEntry->replicationModel; - Var *sourceDistributionColumn = DistPartitionKeyOrError(sourceRelationId); - if (!IsCitusTableTypeCacheEntry(sourceTableEntry, HASH_DISTRIBUTED)) + if (IsCitusTableTypeCacheEntry(sourceTableEntry, APPEND_DISTRIBUTED) || + IsCitusTableTypeCacheEntry(sourceTableEntry, RANGE_DISTRIBUTED) || + IsCitusTableTypeCacheEntry(sourceTableEntry, CITUS_LOCAL_TABLE)) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot distribute relation"), - errdetail("Currently, colocate_with option is only supported " - "for hash distributed tables."))); + errdetail("Currently, colocate_with option is not supported " + "with append / range distributed tables and local " + "tables added to metadata."))); } + char sourceReplicationModel = sourceTableEntry->replicationModel; if (sourceReplicationModel != replicationModel) { char *relationName = get_rel_name(relationId); @@ -1406,7 +1419,9 @@ EnsureTableCanBeColocatedWith(Oid relationId, char replicationModel, sourceRelationName, relationName))); } - Oid sourceDistributionColumnType = sourceDistributionColumn->vartype; + Var *sourceDistributionColumn = DistPartitionKey(sourceRelationId); + Oid sourceDistributionColumnType = !sourceDistributionColumn ? InvalidOid : + sourceDistributionColumn->vartype; if (sourceDistributionColumnType != distributionColumnType) { char *relationName = get_rel_name(relationId); @@ -1418,4 +1433,25 @@ EnsureTableCanBeColocatedWith(Oid relationId, char replicationModel, "%s and %s.", sourceRelationName, relationName))); } + + /* prevent colocating regular tables with tenant tables */ + Oid sourceRelationSchemaId = get_rel_namespace(sourceRelationId); + Oid targetRelationSchemaId = get_rel_namespace(relationId); + if (IsTenantSchema(sourceRelationSchemaId) && + sourceRelationSchemaId != targetRelationSchemaId) + { + char *relationName = get_rel_name(relationId); + char *sourceRelationName = get_rel_name(sourceRelationId); + char *sourceRelationSchemaName = get_namespace_name(sourceRelationSchemaId); + + ereport(ERROR, (errmsg("cannot colocate tables %s and %s", + sourceRelationName, relationName), + errdetail("Cannot colocate tables with distributed schema tables" + " by using colocate_with option."), + errhint("Consider using \"CREATE TABLE\" statement " + "to create this table as a single-shard distributed " + "table in the same schema to automatically colocate " + "it with %s.%s", + sourceRelationSchemaName, sourceRelationName))); + } } diff --git a/src/backend/distributed/utils/distribution_column.c b/src/backend/distributed/utils/distribution_column.c index 7fbab98eb..474133f73 100644 --- a/src/backend/distributed/utils/distribution_column.c +++ b/src/backend/distributed/utils/distribution_column.c @@ -135,7 +135,7 @@ BuildDistributionKeyFromColumnName(Oid relationId, char *columnName, LOCKMODE lo char *tableName = get_rel_name(relationId); - /* short circuit for reference tables */ + /* short circuit for reference tables and single-shard tables */ if (columnName == NULL) { return NULL; diff --git a/src/backend/distributed/utils/enable_ssl.c b/src/backend/distributed/utils/enable_ssl.c index b449fa3e4..cac32f74c 100644 --- a/src/backend/distributed/utils/enable_ssl.c +++ b/src/backend/distributed/utils/enable_ssl.c @@ -19,11 +19,6 @@ * done before including libpq.h. */ #include "distributed/pg_version_constants.h" -#if PG_VERSION_NUM < PG_VERSION_14 -#ifndef OPENSSL_API_COMPAT -#define OPENSSL_API_COMPAT 0x1000100L -#endif -#endif #include "distributed/connection_management.h" #include "distributed/memutils.h" diff --git a/src/backend/distributed/utils/function_utils.c b/src/backend/distributed/utils/function_utils.c index 04750b23f..006e29555 100644 --- a/src/backend/distributed/utils/function_utils.c +++ b/src/backend/distributed/utils/function_utils.c @@ -46,7 +46,7 @@ FunctionOidExtended(const char *schemaName, const char *functionName, int argume const bool findVariadics = false; const bool findDefaults = false; - FuncCandidateList functionList = FuncnameGetCandidates_compat( + FuncCandidateList functionList = FuncnameGetCandidates( qualifiedFunctionNameList, argumentCount, argumentList, diff --git a/src/backend/distributed/utils/listutils.c b/src/backend/distributed/utils/listutils.c index 3279193ef..dd54443c4 100644 --- a/src/backend/distributed/utils/listutils.c +++ b/src/backend/distributed/utils/listutils.c @@ -118,9 +118,7 @@ ListToHashSet(List *itemList, Size keySize, bool isStringList) if (isStringList) { -#if PG_VERSION_NUM >= PG_VERSION_14 flags |= HASH_STRINGS; -#endif } else { diff --git a/src/backend/distributed/utils/log_utils.c b/src/backend/distributed/utils/log_utils.c index ed463c40a..59a090a16 100644 --- a/src/backend/distributed/utils/log_utils.c +++ b/src/backend/distributed/utils/log_utils.c @@ -18,9 +18,7 @@ #include "utils/builtins.h" -#if PG_VERSION_NUM >= PG_VERSION_14 #include "common/cryptohash.h" -#endif /* diff --git a/src/backend/distributed/utils/multi_partitioning_utils.c b/src/backend/distributed/utils/multi_partitioning_utils.c index c5fcd2377..ab36483fd 100644 --- a/src/backend/distributed/utils/multi_partitioning_utils.c +++ b/src/backend/distributed/utils/multi_partitioning_utils.c @@ -1023,7 +1023,7 @@ IsParentTable(Oid relationId) Oid PartitionParentOid(Oid partitionOid) { - Oid partitionParentOid = get_partition_parent_compat(partitionOid, false); + Oid partitionParentOid = get_partition_parent(partitionOid, false); return partitionParentOid; } @@ -1074,7 +1074,7 @@ PartitionList(Oid parentRelationId) ereport(ERROR, (errmsg("\"%s\" is not a parent table", relationName))); } - PartitionDesc partDesc = RelationGetPartitionDesc_compat(rel, true); + PartitionDesc partDesc = RelationGetPartitionDesc(rel, true); Assert(partDesc != NULL); int partitionCount = partDesc->nparts; @@ -1107,7 +1107,7 @@ GenerateDetachPartitionCommand(Oid partitionTableId) ereport(ERROR, (errmsg("\"%s\" is not a partition", relationName))); } - Oid parentId = get_partition_parent_compat(partitionTableId, false); + Oid parentId = get_partition_parent(partitionTableId, false); char *tableQualifiedName = generate_qualified_relation_name(partitionTableId); char *parentTableQualifiedName = generate_qualified_relation_name(parentId); @@ -1221,7 +1221,7 @@ GenerateAlterTableAttachPartitionCommand(Oid partitionTableId) ereport(ERROR, (errmsg("\"%s\" is not a partition", relationName))); } - Oid parentId = get_partition_parent_compat(partitionTableId, false); + Oid parentId = get_partition_parent(partitionTableId, false); char *tableQualifiedName = generate_qualified_relation_name(partitionTableId); char *parentTableQualifiedName = generate_qualified_relation_name(parentId); diff --git a/src/backend/distributed/utils/reference_table_utils.c b/src/backend/distributed/utils/reference_table_utils.c index 687ce02a7..314044ab5 100644 --- a/src/backend/distributed/utils/reference_table_utils.c +++ b/src/backend/distributed/utils/reference_table_utils.c @@ -420,7 +420,7 @@ CopyShardPlacementToWorkerNodeQuery(ShardPlacement *sourceShardPlacement, "auto"; appendStringInfo(queryString, - "SELECT citus_copy_shard_placement(" + "SELECT pg_catalog.citus_copy_shard_placement(" UINT64_FORMAT ", %d, %d, " "transfer_mode := %s)", sourceShardPlacement->shardId, diff --git a/src/backend/distributed/utils/resource_lock.c b/src/backend/distributed/utils/resource_lock.c index 7b8edf758..c76830c1d 100644 --- a/src/backend/distributed/utils/resource_lock.c +++ b/src/backend/distributed/utils/resource_lock.c @@ -491,7 +491,8 @@ SetLocktagForShardDistributionMetadata(int64 shardId, LOCKTAG *tag) uint32 colocationId = citusTable->colocationId; if (colocationId == INVALID_COLOCATION_ID || - !IsCitusTableTypeCacheEntry(citusTable, HASH_DISTRIBUTED)) + (!IsCitusTableTypeCacheEntry(citusTable, HASH_DISTRIBUTED) && + !IsCitusTableTypeCacheEntry(citusTable, SINGLE_SHARD_DISTRIBUTED))) { SET_LOCKTAG_SHARD_METADATA_RESOURCE(*tag, MyDatabaseId, shardId); } diff --git a/src/backend/distributed/utils/shardinterval_utils.c b/src/backend/distributed/utils/shardinterval_utils.c index 12635f9f4..6c18e201e 100644 --- a/src/backend/distributed/utils/shardinterval_utils.c +++ b/src/backend/distributed/utils/shardinterval_utils.c @@ -206,7 +206,7 @@ CompareRelationShards(const void *leftElement, const void *rightElement) * * For hash partitioned tables, it calculates hash value of a number in its * range (e.g. min value) and finds which shard should contain the hashed - * value. For reference tables and citus local tables, it simply returns 0. + * value. For the tables that don't have a shard key, it simply returns 0. * For the other table types, the function errors out. */ int @@ -231,12 +231,11 @@ ShardIndex(ShardInterval *shardInterval) "tables that are added to citus metadata"))); } - /* short-circuit for reference tables */ + /* short-circuit for the tables that don't have a distribution key */ if (!HasDistributionKeyCacheEntry(cacheEntry)) { /* - * Reference tables and citus local tables have only a single shard, - * so the index is fixed to 0. + * Such tables have only a single shard, so the index is fixed to 0. */ shardIndex = 0; diff --git a/src/backend/distributed/utils/tenant_schema_metadata.c b/src/backend/distributed/utils/tenant_schema_metadata.c new file mode 100644 index 000000000..a83842541 --- /dev/null +++ b/src/backend/distributed/utils/tenant_schema_metadata.c @@ -0,0 +1,236 @@ +/*------------------------------------------------------------------------- + * + * tenant_schema_metadata.c + * + * This file contains functions to query and modify tenant schema metadata, + * which is used to track the schemas used for schema-based sharding in + * Citus. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/genam.h" +#include "access/htup.h" +#include "access/table.h" +#include "distributed/colocation_utils.h" +#include "distributed/metadata_cache.h" +#include "distributed/metadata_sync.h" +#include "distributed/pg_dist_schema.h" +#include "distributed/tenant_schema_metadata.h" +#include "storage/lockdefs.h" +#include "utils/relcache.h" +#include "utils/fmgroids.h" + + +/* + * IsTenantSchema returns true if there is a tenant schema with given schemaId. + */ +bool +IsTenantSchema(Oid schemaId) +{ + /* + * We don't allow creating tenant schemas when there is a version + * mismatch. Even more, SchemaIdGetTenantColocationId() would throw an + * error if the underlying pg_dist_schema metadata table has not + * been created yet, which is the case in older versions. For this reason, + * it's safe to assume that it cannot be a tenant schema when there is a + * version mismatch. + * + * But it's a bit tricky that we do the same when version checks are + * disabled because then CheckCitusVersion() returns true even if there + * is a version mismatch. And in that case, the tests that are trying to + * create tables (in multi_extension.sql) in older versions would + * fail when deciding whether we should create a tenant table or not. + * + * The downside of doing so is that, for example, we will skip deleting + * the tenant schema entry from pg_dist_schema when dropping a + * tenant schema while the version checks are disabled even if there was + * no version mismatch. But we're okay with that because we don't expect + * users to disable version checks anyway. + */ + if (!EnableVersionChecks || !CheckCitusVersion(DEBUG4)) + { + return false; + } + + return SchemaIdGetTenantColocationId(schemaId) != INVALID_COLOCATION_ID; +} + + +/* + * IsTenantSchemaColocationGroup returns true if there is a tenant schema + * that is associated with given colocation id. + */ +bool +IsTenantSchemaColocationGroup(uint32 colocationId) +{ + return OidIsValid(ColocationIdGetTenantSchemaId(colocationId)); +} + + +/* + * SchemaIdGetTenantColocationId returns the colocation id associated with + * the tenant schema with given id. + * + * Returns INVALID_COLOCATION_ID if there is no tenant schema with given id. + */ +uint32 +SchemaIdGetTenantColocationId(Oid schemaId) +{ + uint32 colocationId = INVALID_COLOCATION_ID; + + if (!OidIsValid(schemaId)) + { + ereport(ERROR, (errmsg("schema id is invalid"))); + } + + Relation pgDistTenantSchema = table_open(DistTenantSchemaRelationId(), + AccessShareLock); + ScanKeyData scanKey[1]; + ScanKeyInit(&scanKey[0], Anum_pg_dist_schema_schemaid, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(schemaId)); + + bool indexOk = true; + SysScanDesc scanDescriptor = systable_beginscan(pgDistTenantSchema, + DistTenantSchemaPrimaryKeyIndexId(), + indexOk, NULL, 1, scanKey); + + HeapTuple heapTuple = systable_getnext_ordered(scanDescriptor, ForwardScanDirection); + if (HeapTupleIsValid(heapTuple)) + { + bool isNull = false; + colocationId = DatumGetUInt32( + heap_getattr(heapTuple, + Anum_pg_dist_schema_colocationid, + RelationGetDescr(pgDistTenantSchema), + &isNull)); + Assert(!isNull); + } + + systable_endscan(scanDescriptor); + table_close(pgDistTenantSchema, AccessShareLock); + + return colocationId; +} + + +/* + * ColocationIdGetTenantSchemaId returns the oid of the tenant schema that + * is associated with given colocation id. + * + * Returns InvalidOid if there is no such tenant schema. + */ +Oid +ColocationIdGetTenantSchemaId(uint32 colocationId) +{ + if (colocationId == INVALID_COLOCATION_ID) + { + ereport(ERROR, (errmsg("colocation id is invalid"))); + } + + Relation pgDistTenantSchema = table_open(DistTenantSchemaRelationId(), + AccessShareLock); + ScanKeyData scanKey[1]; + ScanKeyInit(&scanKey[0], Anum_pg_dist_schema_colocationid, + BTEqualStrategyNumber, F_INT4EQ, UInt32GetDatum(colocationId)); + + bool indexOk = true; + SysScanDesc scanDescriptor = systable_beginscan(pgDistTenantSchema, + DistTenantSchemaUniqueColocationIdIndexId(), + indexOk, NULL, 1, scanKey); + + HeapTuple heapTuple = systable_getnext_ordered(scanDescriptor, ForwardScanDirection); + Oid schemaId = InvalidOid; + if (HeapTupleIsValid(heapTuple)) + { + bool isNull = false; + schemaId = heap_getattr(heapTuple, Anum_pg_dist_schema_schemaid, + RelationGetDescr(pgDistTenantSchema), &isNull); + Assert(!isNull); + } + + systable_endscan(scanDescriptor); + table_close(pgDistTenantSchema, AccessShareLock); + + return schemaId; +} + + +/* + * InsertTenantSchemaLocally inserts an entry into pg_dist_schema + * with given schemaId and colocationId. + * + * Throws a constraint violation error if there is already an entry with + * given schemaId, or if given colocation id is already associated with + * another tenant schema. + */ +void +InsertTenantSchemaLocally(Oid schemaId, uint32 colocationId) +{ + if (!OidIsValid(schemaId)) + { + ereport(ERROR, (errmsg("schema id is invalid"))); + } + + if (colocationId == INVALID_COLOCATION_ID) + { + ereport(ERROR, (errmsg("colocation id is invalid"))); + } + + Datum values[Natts_pg_dist_schema] = { 0 }; + bool isNulls[Natts_pg_dist_schema] = { 0 }; + + values[Anum_pg_dist_schema_schemaid - 1] = ObjectIdGetDatum(schemaId); + values[Anum_pg_dist_schema_colocationid - 1] = UInt32GetDatum(colocationId); + + Relation pgDistTenantSchema = table_open(DistTenantSchemaRelationId(), + RowExclusiveLock); + TupleDesc tupleDescriptor = RelationGetDescr(pgDistTenantSchema); + HeapTuple heapTuple = heap_form_tuple(tupleDescriptor, values, isNulls); + + CatalogTupleInsert(pgDistTenantSchema, heapTuple); + CommandCounterIncrement(); + + table_close(pgDistTenantSchema, NoLock); +} + + +/* + * DeleteTenantSchemaLocally deletes the entry for given schemaId from + * pg_dist_schema. + * + * Throws an error if there is no such tenant schema. + */ +void +DeleteTenantSchemaLocally(Oid schemaId) +{ + if (!OidIsValid(schemaId)) + { + ereport(ERROR, (errmsg("schema id is invalid"))); + } + + Relation pgDistTenantSchema = table_open(DistTenantSchemaRelationId(), + RowExclusiveLock); + ScanKeyData scanKey[1]; + ScanKeyInit(&scanKey[0], Anum_pg_dist_schema_schemaid, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(schemaId)); + + bool indexOk = true; + SysScanDesc scanDescriptor = systable_beginscan(pgDistTenantSchema, + DistTenantSchemaPrimaryKeyIndexId(), + indexOk, NULL, 1, scanKey); + + HeapTuple heapTuple = systable_getnext_ordered(scanDescriptor, ForwardScanDirection); + if (!HeapTupleIsValid(heapTuple)) + { + ereport(ERROR, (errmsg("could not find tuple for tenant schema %u", schemaId))); + } + + CatalogTupleDelete(pgDistTenantSchema, &heapTuple->t_self); + CommandCounterIncrement(); + + systable_endscan(scanDescriptor); + table_close(pgDistTenantSchema, NoLock); +} diff --git a/src/include/columnar/columnar_version_compat.h b/src/include/columnar/columnar_version_compat.h index c40aa6236..0e0ae3112 100644 --- a/src/include/columnar/columnar_version_compat.h +++ b/src/include/columnar/columnar_version_compat.h @@ -22,29 +22,6 @@ ExecARDeleteTriggers(a, b, c, d, e) #endif -#if PG_VERSION_NUM >= PG_VERSION_14 -#define ColumnarProcessUtility_compat(a, b, c, d, e, f, g, h) \ - ColumnarProcessUtility(a, b, c, d, e, f, g, h) -#define PrevProcessUtilityHook_compat(a, b, c, d, e, f, g, h) \ - PrevProcessUtilityHook(a, b, c, d, e, f, g, h) -#define GetOldestNonRemovableTransactionId_compat(a, b) \ - GetOldestNonRemovableTransactionId(a) -#define ExecSimpleRelationInsert_compat(a, b, c) \ - ExecSimpleRelationInsert(a, b, c) -#define index_insert_compat(a, b, c, d, e, f, g, h) \ - index_insert(a, b, c, d, e, f, g, h) -#else -#define ColumnarProcessUtility_compat(a, b, c, d, e, f, g, h) \ - ColumnarProcessUtility(a, b, d, e, f, g, h) -#define PrevProcessUtilityHook_compat(a, b, c, d, e, f, g, h) \ - PrevProcessUtilityHook(a, b, d, e, f, g, h) -#define GetOldestNonRemovableTransactionId_compat(a, b) GetOldestXmin(a, b) -#define ExecSimpleRelationInsert_compat(a, b, c) \ - ExecSimpleRelationInsert(b, c) -#define index_insert_compat(a, b, c, d, e, f, g, h) \ - index_insert(a, b, c, d, e, f, h) -#endif - #define ACLCHECK_OBJECT_TABLE OBJECT_TABLE #define ExplainPropertyLong(qlabel, value, es) \ diff --git a/src/include/distributed/citus_custom_scan.h b/src/include/distributed/citus_custom_scan.h index f31138ac2..a3da4958c 100644 --- a/src/include/distributed/citus_custom_scan.h +++ b/src/include/distributed/citus_custom_scan.h @@ -34,6 +34,7 @@ typedef struct CitusScanState extern CustomScanMethods AdaptiveExecutorCustomScanMethods; extern CustomScanMethods NonPushableInsertSelectCustomScanMethods; extern CustomScanMethods DelayedErrorCustomScanMethods; +extern CustomScanMethods NonPushableMergeCommandCustomScanMethods; extern void RegisterCitusCustomScanMethods(void); diff --git a/src/include/distributed/citus_safe_lib.h b/src/include/distributed/citus_safe_lib.h index e039072a7..62142df0a 100644 --- a/src/include/distributed/citus_safe_lib.h +++ b/src/include/distributed/citus_safe_lib.h @@ -25,7 +25,8 @@ extern void SafeQsort(void *ptr, rsize_t count, rsize_t size, int (*comp)(const void *, const void *)); void * SafeBsearch(const void *key, const void *ptr, rsize_t count, rsize_t size, int (*comp)(const void *, const void *)); -int SafeSnprintf(char *str, rsize_t count, const char *fmt, ...); +int SafeSnprintf(char *str, rsize_t count, const char *fmt, ...) pg_attribute_printf(3, + 0); #define memset_struct_0(variable) memset(&variable, 0, sizeof(variable)) diff --git a/src/include/distributed/colocation_utils.h b/src/include/distributed/colocation_utils.h index 9e6641cd3..c9fcf4776 100644 --- a/src/include/distributed/colocation_utils.h +++ b/src/include/distributed/colocation_utils.h @@ -48,6 +48,7 @@ extern void UpdateRelationColocationGroup(Oid distributedRelationId, uint32 colo bool localOnly); extern void DeleteColocationGroupIfNoTablesBelong(uint32 colocationId); extern List * ColocationGroupTableList(uint32 colocationId, uint32 count); +extern void DeleteColocationGroup(uint32 colocationId); extern void DeleteColocationGroupLocally(uint32 colocationId); extern uint32 FindColocateWithColocationId(Oid relationId, char replicationModel, Oid distributionColumnType, diff --git a/src/include/distributed/commands.h b/src/include/distributed/commands.h index dba63c659..a013f3977 100644 --- a/src/include/distributed/commands.h +++ b/src/include/distributed/commands.h @@ -15,6 +15,7 @@ #include "postgres.h" +#include "distributed/metadata_utility.h" #include "utils/rel.h" #include "nodes/parsenodes.h" #include "tcop/dest.h" @@ -23,6 +24,7 @@ extern bool AddAllLocalTablesToMetadata; +extern bool EnableSchemaBasedSharding; /* controlled via GUC, should be accessed via EnableLocalReferenceForeignKeys() */ extern bool EnableLocalReferenceForeignKeys; @@ -116,7 +118,7 @@ typedef enum ExtractForeignKeyConstraintsMode /* exclude the self-referencing foreign keys */ EXCLUDE_SELF_REFERENCES = 1 << 2, - /* any combination of the 4 flags below is supported */ + /* any combination of the 5 flags below is supported */ /* include foreign keys when the other table is a distributed table*/ INCLUDE_DISTRIBUTED_TABLES = 1 << 3, @@ -129,9 +131,13 @@ typedef enum ExtractForeignKeyConstraintsMode /* include foreign keys when the other table is a Postgres local table*/ INCLUDE_LOCAL_TABLES = 1 << 6, + /* include foreign keys when the other table is a single shard table*/ + INCLUDE_SINGLE_SHARD_TABLES = 1 << 7, + /* include foreign keys regardless of the other table's type */ INCLUDE_ALL_TABLE_TYPES = INCLUDE_DISTRIBUTED_TABLES | INCLUDE_REFERENCE_TABLES | - INCLUDE_CITUS_LOCAL_TABLES | INCLUDE_LOCAL_TABLES + INCLUDE_CITUS_LOCAL_TABLES | INCLUDE_LOCAL_TABLES | + INCLUDE_SINGLE_SHARD_TABLES } ExtractForeignKeyConstraintMode; @@ -153,6 +159,19 @@ typedef enum SearchForeignKeyColumnFlags /* callers can also pass union of above flags */ } SearchForeignKeyColumnFlags; + +typedef enum TenantOperation +{ + TENANT_UNDISTRIBUTE_TABLE = 0, + TENANT_ALTER_TABLE, + TENANT_COLOCATE_WITH, + TENANT_UPDATE_COLOCATION, + TENANT_SET_SCHEMA, +} TenantOperation; + +#define TOTAL_TENANT_OPERATION 5 +extern const char *TenantOperationNames[TOTAL_TENANT_OPERATION]; + /* begin.c - forward declarations */ extern void SaveBeginCommandProperties(TransactionStmt *transactionStmt); @@ -272,6 +291,10 @@ extern List * GetForeignConstraintToReferenceTablesCommands(Oid relationId); extern List * GetForeignConstraintToDistributedTablesCommands(Oid relationId); extern List * GetForeignConstraintFromDistributedTablesCommands(Oid relationId); extern List * GetForeignConstraintCommandsInternal(Oid relationId, int flags); +extern Oid DropFKeysAndUndistributeTable(Oid relationId); +extern void DropFKeysRelationInvolvedWithTableType(Oid relationId, int tableTypeFlag); +extern List * GetFKeyCreationCommandsRelationInvolvedWithTableType(Oid relationId, + int tableTypeFlag); extern bool AnyForeignKeyDependsOnIndex(Oid indexId); extern bool HasForeignKeyWithLocalTable(Oid relationId); extern bool HasForeignKeyToReferenceTable(Oid relationOid); @@ -458,8 +481,7 @@ extern void UnmarkRolesDistributed(List *roles); extern List * FilterDistributedRoles(List *roles); /* schema.c - forward declarations */ -extern List * PreprocessCreateSchemaStmt(Node *node, const char *queryString, - ProcessUtilityContext processUtilityContext); +extern List * PostprocessCreateSchemaStmt(Node *node, const char *queryString); extern List * PreprocessDropSchemaStmt(Node *dropSchemaStatement, const char *queryString, ProcessUtilityContext processUtilityContext); @@ -469,6 +491,8 @@ extern List * PreprocessGrantOnSchemaStmt(Node *node, const char *queryString, ProcessUtilityContext processUtilityContext); extern List * CreateSchemaStmtObjectAddress(Node *node, bool missing_ok, bool isPostprocess); +extern List * AlterSchemaOwnerStmtObjectAddress(Node *node, bool missing_ok, + bool isPostprocess); extern List * AlterSchemaRenameStmtObjectAddress(Node *node, bool missing_ok, bool isPostprocess); @@ -584,8 +608,9 @@ extern bool ConstrTypeCitusCanDefaultName(ConstrType constrType); extern char * GetAlterColumnWithNextvalDefaultCmd(Oid sequenceOid, Oid relationId, char *colname, bool missingTableOk); -extern void ErrorIfTableHasUnsupportedIdentityColumn(Oid relationId); extern void ErrorIfTableHasIdentityColumn(Oid relationId); +extern void ConvertNewTableIfNecessary(Node *createStmt); +extern void ConvertToTenantTableIfNecessary(AlterObjectSchemaStmt *alterObjectSchemaStmt); /* text_search.c - forward declarations */ extern List * GetCreateTextSearchConfigStatements(const ObjectAddress *address); @@ -770,6 +795,7 @@ extern void ExecuteForeignKeyCreateCommandList(List *ddlCommandList, /* create_citus_local_table.c */ extern void CreateCitusLocalTable(Oid relationId, bool cascadeViaForeignKeys, bool autoConverted); +extern bool ShouldAddNewTableToMetadata(Oid relationId); extern List * GetExplicitIndexOidList(Oid relationId); extern bool ShouldPropagateSetCommand(VariableSetStmt *setStmt); @@ -780,4 +806,15 @@ extern void CreateCitusLocalTablePartitionOf(CreateStmt *createStatement, extern void UpdateAutoConvertedForConnectedRelations(List *relationId, bool autoConverted); +/* schema_based_sharding.c */ +extern bool ShouldUseSchemaBasedSharding(char *schemaName); +extern bool ShouldCreateTenantSchemaTable(Oid relationId); +extern bool IsTenantSchema(Oid schemaId); +extern void EnsureTenantTable(Oid relationId, char *operationName); +extern void ErrorIfIllegalPartitioningInTenantSchema(Oid parentRelationId, + Oid partitionRelationId); +extern void CreateTenantSchemaTable(Oid relationId); +extern void ErrorIfTenantTable(Oid relationId, const char *operationName); +extern uint32 CreateTenantSchemaColocationId(void); + #endif /*CITUS_COMMANDS_H */ diff --git a/src/include/distributed/commands/multi_copy.h b/src/include/distributed/commands/multi_copy.h index 689725e70..4255c952d 100644 --- a/src/include/distributed/commands/multi_copy.h +++ b/src/include/distributed/commands/multi_copy.h @@ -31,12 +31,7 @@ typedef enum CitusCopyDest { COPY_FILE, /* to/from file (or a piped program) */ -#if PG_VERSION_NUM >= PG_VERSION_14 COPY_FRONTEND, /* to frontend */ -#else - COPY_OLD_FE, /* to/from frontend (2.0 protocol) */ - COPY_NEW_FE, /* to/from frontend (3.0 protocol) */ -#endif COPY_CALLBACK /* to/from callback function */ } CitusCopyDest; @@ -152,6 +147,12 @@ typedef struct CitusCopyDestReceiver * upfront. */ uint64 appendShardId; + + /* + * When copying to intermediate files, we can skip coercions and run them + * when merging into the target tables. + */ + bool skipCoercions; } CitusCopyDestReceiver; diff --git a/src/include/distributed/commands/utility_hook.h b/src/include/distributed/commands/utility_hook.h index 7229f7c72..f02f83fe3 100644 --- a/src/include/distributed/commands/utility_hook.h +++ b/src/include/distributed/commands/utility_hook.h @@ -79,9 +79,7 @@ typedef struct DDLJob extern ProcessUtility_hook_type PrevProcessUtility; extern void multi_ProcessUtility(PlannedStmt *pstmt, const char *queryString, -#if PG_VERSION_NUM >= PG_VERSION_14 bool readOnlyTree, -#endif ProcessUtilityContext context, ParamListInfo params, struct QueryEnvironment *queryEnv, DestReceiver *dest, QueryCompletion *completionTag diff --git a/src/include/distributed/connection_management.h b/src/include/distributed/connection_management.h index 8c2584451..f08124123 100644 --- a/src/include/distributed/connection_management.h +++ b/src/include/distributed/connection_management.h @@ -285,6 +285,7 @@ extern int MaxCachedConnectionLifetime; /* parameters used for outbound connections */ extern char *NodeConninfo; extern char *LocalHostName; +extern bool checkAtBootPassed; /* the hash tables are externally accessiable */ extern HTAB *ConnectionHash; @@ -352,7 +353,4 @@ extern bool CitusModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, extern double MillisecondsPassedSince(instr_time moment); extern long MillisecondsToTimeout(instr_time start, long msAfterStart); -#if PG_VERSION_NUM < 140000 -extern void WarmUpConnParamsHash(void); -#endif #endif /* CONNECTION_MANAGMENT_H */ diff --git a/src/include/distributed/coordinator_protocol.h b/src/include/distributed/coordinator_protocol.h index ad8329a6c..7f90eadda 100644 --- a/src/include/distributed/coordinator_protocol.h +++ b/src/include/distributed/coordinator_protocol.h @@ -262,6 +262,8 @@ extern void CreateShardsWithRoundRobinPolicy(Oid distributedTableId, int32 shard extern void CreateColocatedShards(Oid targetRelationId, Oid sourceRelationId, bool useExclusiveConnections); extern void CreateReferenceTableShard(Oid distributedTableId); +extern void CreateSingleShardTableShardWithRoundRobinPolicy(Oid relationId, + uint32 colocationId); extern List * WorkerCreateShardCommandList(Oid relationId, int shardIndex, uint64 shardId, List *ddlCommandList, List *foreignConstraintCommandList); diff --git a/src/include/distributed/deparser.h b/src/include/distributed/deparser.h index 0d4f605d8..7390b486c 100644 --- a/src/include/distributed/deparser.h +++ b/src/include/distributed/deparser.h @@ -105,6 +105,7 @@ extern char * DeparseCreateSchemaStmt(Node *node); extern char * DeparseDropSchemaStmt(Node *node); extern char * DeparseGrantOnSchemaStmt(Node *stmt); extern char * DeparseAlterSchemaRenameStmt(Node *stmt); +extern char * DeparseAlterSchemaOwnerStmt(Node *node); extern void AppendGrantPrivileges(StringInfo buf, GrantStmt *stmt); extern void AppendGrantGrantees(StringInfo buf, GrantStmt *stmt); diff --git a/src/include/distributed/distributed_planner.h b/src/include/distributed/distributed_planner.h index 412859449..aac936a98 100644 --- a/src/include/distributed/distributed_planner.h +++ b/src/include/distributed/distributed_planner.h @@ -147,9 +147,19 @@ typedef struct RTEListProperties bool hasReferenceTable; bool hasCitusLocalTable; - /* includes hash, append and range partitioned tables */ + /* includes hash, single-shard, append and range partitioned tables */ bool hasDistributedTable; + /* + * Effectively, hasDistributedTable is equal to + * "hasDistTableWithShardKey || hasSingleShardDistTable". + * + * We provide below two for the callers that want to know what kind of + * distributed tables that given query has references to. + */ + bool hasDistTableWithShardKey; + bool hasSingleShardDistTable; + /* union of hasReferenceTable, hasCitusLocalTable and hasDistributedTable */ bool hasCitusTable; @@ -243,6 +253,7 @@ extern int32 BlessRecordExpression(Expr *expr); extern void DissuadePlannerFromUsingPlan(PlannedStmt *plan); extern PlannedStmt * FinalizePlan(PlannedStmt *localPlan, struct DistributedPlan *distributedPlan); +extern bool ContainsSingleShardTable(Query *query); extern RTEListProperties * GetRTEListPropertiesForQuery(Query *query); diff --git a/src/include/distributed/insert_select_executor.h b/src/include/distributed/insert_select_executor.h index 6e84b80f2..fd8282014 100644 --- a/src/include/distributed/insert_select_executor.h +++ b/src/include/distributed/insert_select_executor.h @@ -16,11 +16,9 @@ #include "executor/execdesc.h" -extern bool EnableRepartitionedInsertSelect; extern TupleTableSlot * NonPushableInsertSelectExecScan(CustomScanState *node); -extern bool IsSupportedRedistributionTarget(Oid targetRelationId); -extern bool IsRedistributablePlan(Plan *selectPlan); - +extern List * BuildColumnNameListFromTargetList(Oid targetRelationId, + List *insertTargetList); #endif /* INSERT_SELECT_EXECUTOR_H */ diff --git a/src/include/distributed/insert_select_planner.h b/src/include/distributed/insert_select_planner.h index 74b8a0708..771d1d60f 100644 --- a/src/include/distributed/insert_select_planner.h +++ b/src/include/distributed/insert_select_planner.h @@ -44,6 +44,7 @@ extern DistributedPlan * CreateInsertSelectIntoLocalTablePlan(uint64 planId, plannerRestrictionContext); extern char * InsertSelectResultIdPrefix(uint64 planId); extern bool PlanningInsertSelect(void); +extern Query * WrapSubquery(Query *subquery); #endif /* INSERT_SELECT_PLANNER_H */ diff --git a/src/include/distributed/local_distributed_join_planner.h b/src/include/distributed/local_distributed_join_planner.h index f2108f603..dfb45f149 100644 --- a/src/include/distributed/local_distributed_join_planner.h +++ b/src/include/distributed/local_distributed_join_planner.h @@ -33,5 +33,6 @@ extern void RecursivelyPlanLocalTableJoins(Query *query, extern List * RequiredAttrNumbersForRelation(RangeTblEntry *relationRte, PlannerRestrictionContext * plannerRestrictionContext); +extern List * RequiredAttrNumbersForRelationInternal(Query *queryToProcess, int rteIndex); #endif /* LOCAL_DISTRIBUTED_JOIN_PLANNER_H */ diff --git a/src/include/distributed/merge_executor.h b/src/include/distributed/merge_executor.h new file mode 100644 index 000000000..0bc31ab74 --- /dev/null +++ b/src/include/distributed/merge_executor.h @@ -0,0 +1,17 @@ +/*------------------------------------------------------------------------- + * + * merge_executor.h + * + * Declarations for public functions and types related to executing + * MERGE INTO ... SQL commands. + * + * Copyright (c) Citus Data, Inc. + * + *------------------------------------------------------------------------- + */ +#ifndef MERGE_EXECUTOR_H +#define MERGE_EXECUTOR_H + +extern TupleTableSlot * NonPushableMergeCommandExecScan(CustomScanState *node); + +#endif /* MERGE_EXECUTOR_H */ diff --git a/src/include/distributed/merge_planner.h b/src/include/distributed/merge_planner.h index 158f26861..1548dae6a 100644 --- a/src/include/distributed/merge_planner.h +++ b/src/include/distributed/merge_planner.h @@ -19,13 +19,18 @@ #include "distributed/errormessage.h" #include "distributed/multi_physical_planner.h" -extern bool IsMergeAllowedOnRelation(Query *parse, RangeTblEntry *rte); -extern DeferredErrorMessage * MergeQuerySupported(Query *originalQuery, - bool multiShardQuery, - PlannerRestrictionContext * - plannerRestrictionContext); -extern DistributedPlan * CreateMergePlan(Query *originalQuery, Query *query, +extern DistributedPlan * CreateMergePlan(uint64 planId, Query *originalQuery, + Query *query, PlannerRestrictionContext * - plannerRestrictionContext); + plannerRestrictionContext, + ParamListInfo boundParams); +extern bool IsLocalTableModification(Oid targetRelationId, Query *query, + uint64 shardId, + RTEListProperties *rteProperties); +extern void NonPushableMergeCommandExplainScan(CustomScanState *node, List *ancestors, + struct ExplainState *es); +extern Var * FetchAndValidateInsertVarIfExists(Oid targetRelationId, Query *query); +extern RangeTblEntry * ExtractMergeSourceRangeTableEntry(Query *query); + #endif /* MERGE_PLANNER_H */ diff --git a/src/include/distributed/metadata/distobject.h b/src/include/distributed/metadata/distobject.h index 91f4fb630..de56c0e1f 100644 --- a/src/include/distributed/metadata/distobject.h +++ b/src/include/distributed/metadata/distobject.h @@ -30,6 +30,7 @@ extern bool IsTableOwnedByExtension(Oid relationId); extern bool ObjectAddressDependsOnExtension(const ObjectAddress *target); extern bool IsAnyObjectAddressOwnedByExtension(const List *targets, ObjectAddress *extensionAddress); +extern ObjectAddress * FirstExtensionWithSchema(Oid schemaId); extern bool IsObjectAddressOwnedByCitus(const ObjectAddress *objectAddress); extern ObjectAddress PgGetObjectAddress(char *ttype, ArrayType *namearr, ArrayType *argsarr); diff --git a/src/include/distributed/metadata_cache.h b/src/include/distributed/metadata_cache.h index c23a047ec..dd98da764 100644 --- a/src/include/distributed/metadata_cache.h +++ b/src/include/distributed/metadata_cache.h @@ -123,6 +123,7 @@ typedef enum HASH_DISTRIBUTED, APPEND_DISTRIBUTED, RANGE_DISTRIBUTED, + SINGLE_SHARD_DISTRIBUTED, /* hash, range or append distributed table */ DISTRIBUTED_TABLE, @@ -157,6 +158,8 @@ extern uint32 ColocationIdViaCatalog(Oid relationId); bool IsReferenceTableByDistParams(char partitionMethod, char replicationModel); extern bool IsCitusLocalTableByDistParams(char partitionMethod, char replicationModel, uint32 colocationId); +extern bool IsSingleShardTableByDistParams(char partitionMethod, char replicationModel, + uint32 colocationId); extern List * CitusTableList(void); extern ShardInterval * LoadShardInterval(uint64 shardId); extern bool ShardExists(uint64 shardId); @@ -193,6 +196,8 @@ extern bool HasOverlappingShardInterval(ShardInterval **shardIntervalArray, Oid shardIntervalCollation, FmgrInfo *shardIntervalSortCompareFunction); +extern ShardPlacement * ShardPlacementForFunctionColocatedWithSingleShardTable( + CitusTableCacheEntry *cacheEntry); extern ShardPlacement * ShardPlacementForFunctionColocatedWithReferenceTable( CitusTableCacheEntry *cacheEntry); extern ShardPlacement * ShardPlacementForFunctionColocatedWithDistTable( @@ -238,6 +243,7 @@ extern Oid DistRebalanceStrategyRelationId(void); extern Oid DistLocalGroupIdRelationId(void); extern Oid DistObjectRelationId(void); extern Oid DistEnabledCustomAggregatesId(void); +extern Oid DistTenantSchemaRelationId(void); /* index oids */ extern Oid DistNodeNodeIdIndexId(void); @@ -260,6 +266,8 @@ extern Oid DistTransactionGroupIndexId(void); extern Oid DistPlacementGroupidIndexId(void); extern Oid DistObjectPrimaryKeyIndexId(void); extern Oid DistCleanupPrimaryKeyIndexId(void); +extern Oid DistTenantSchemaPrimaryKeyIndexId(void); +extern Oid DistTenantSchemaUniqueColocationIdIndexId(void); /* sequence oids */ extern Oid DistBackgroundJobJobIdSequenceId(void); diff --git a/src/include/distributed/metadata_sync.h b/src/include/distributed/metadata_sync.h index d5878ec71..64e684000 100644 --- a/src/include/distributed/metadata_sync.h +++ b/src/include/distributed/metadata_sync.h @@ -137,6 +137,8 @@ extern void SyncNewColocationGroupToNodes(uint32 colocationId, int shardCount, Oid distributionColumType, Oid distributionColumnCollation); extern void SyncDeleteColocationGroupToNodes(uint32 colocationId); +extern char * TenantSchemaInsertCommand(Oid schemaId, uint32 colocationId); +extern char * TenantSchemaDeleteCommand(char *schemaName); extern MetadataSyncContext * CreateMetadataSyncContext(List *nodeList, bool collectCommands, @@ -156,12 +158,14 @@ extern void SendOrCollectCommandListToSingleNode(MetadataSyncContext *context, extern void ActivateNodeList(MetadataSyncContext *context); extern char * WorkerDropAllShellTablesCommand(bool singleTransaction); +extern char * WorkerDropSequenceDependencyCommand(Oid relationId); extern void SyncDistributedObjects(MetadataSyncContext *context); extern void SendNodeWideObjectsSyncCommands(MetadataSyncContext *context); extern void SendShellTableDeletionCommands(MetadataSyncContext *context); extern void SendMetadataDeletionCommands(MetadataSyncContext *context); extern void SendColocationMetadataCommands(MetadataSyncContext *context); +extern void SendTenantSchemaMetadataCommands(MetadataSyncContext *context); extern void SendDependencyCreationCommands(MetadataSyncContext *context); extern void SendDistTableMetadataCommands(MetadataSyncContext *context); extern void SendDistObjectCommands(MetadataSyncContext *context); @@ -173,6 +177,7 @@ extern void SendInterTableRelationshipCommands(MetadataSyncContext *context); #define DELETE_ALL_DISTRIBUTED_OBJECTS "DELETE FROM pg_catalog.pg_dist_object" #define DELETE_ALL_PARTITIONS "DELETE FROM pg_dist_partition" #define DELETE_ALL_COLOCATION "DELETE FROM pg_catalog.pg_dist_colocation" +#define DELETE_ALL_TENANT_SCHEMAS "DELETE FROM pg_catalog.pg_dist_schema" #define WORKER_DROP_ALL_SHELL_TABLES \ "CALL pg_catalog.worker_drop_all_shell_tables(%s)" #define CITUS_INTERNAL_MARK_NODE_NOT_SYNCED \ @@ -180,8 +185,10 @@ extern void SendInterTableRelationshipCommands(MetadataSyncContext *context); #define REMOVE_ALL_CITUS_TABLES_COMMAND \ "SELECT worker_drop_distributed_table(logicalrelid::regclass::text) FROM pg_dist_partition" -#define BREAK_CITUS_TABLE_SEQUENCE_DEPENDENCY_COMMAND \ +#define BREAK_ALL_CITUS_TABLE_SEQUENCE_DEPENDENCY_COMMAND \ "SELECT pg_catalog.worker_drop_sequence_dependency(logicalrelid::regclass::text) FROM pg_dist_partition" +#define BREAK_CITUS_TABLE_SEQUENCE_DEPENDENCY_COMMAND \ + "SELECT pg_catalog.worker_drop_sequence_dependency(%s);" #define DISABLE_DDL_PROPAGATION "SET citus.enable_ddl_propagation TO 'off'" #define ENABLE_DDL_PROPAGATION "SET citus.enable_ddl_propagation TO 'on'" diff --git a/src/include/distributed/metadata_utility.h b/src/include/distributed/metadata_utility.h index e27f3df22..6536e89bc 100644 --- a/src/include/distributed/metadata_utility.h +++ b/src/include/distributed/metadata_utility.h @@ -41,7 +41,7 @@ #define WORKER_PARTITIONED_RELATION_TOTAL_SIZE_FUNCTION \ "worker_partitioned_relation_total_size(%s)" -#define SHARD_SIZES_COLUMN_COUNT (3) +#define SHARD_SIZES_COLUMN_COUNT (2) /* * Flag to keep track of whether the process is currently in a function converting the @@ -172,6 +172,12 @@ typedef struct TableConversionParameters * messages that we explicitly issue */ bool suppressNoticeMessages; + + /* + * bypassTenantCheck skips tenant table checks to allow some internal + * operations which are normally disallowed + */ + bool bypassTenantCheck; } TableConversionParameters; typedef struct TableConversionReturn @@ -201,6 +207,37 @@ typedef enum SizeQueryType TABLE_SIZE /* pg_table_size() */ } SizeQueryType; + +typedef enum +{ + COLOCATE_WITH_TABLE_LIKE_OPT, + COLOCATE_WITH_COLOCATION_ID +} ColocationParamType; + +/* + * Param used to specify the colocation target of a distributed table. It can + * be either a table name or a colocation id. + * + * When colocationParamType is COLOCATE_WITH_COLOCATION_ID, colocationId is + * expected to be a valid colocation id. When colocationParamType is set to + * COLOCATE_WITH_TABLE_LIKE_OPT, colocateWithTableName is expected to + * be a valid table name, "default" or "none". + * + * Among the functions used to create a Citus table, right now only + * CreateSingleShardTable() accepts a ColocationParam. + */ +typedef struct +{ + union + { + char *colocateWithTableName; + uint32 colocationId; + }; + + ColocationParamType colocationParamType; +} ColocationParam; + + typedef enum BackgroundJobStatus { BACKGROUND_JOB_STATUS_SCHEDULED, @@ -326,12 +363,15 @@ extern void DeletePartitionRow(Oid distributedRelationId); extern void DeleteShardRow(uint64 shardId); extern void UpdatePlacementGroupId(uint64 placementId, int groupId); extern void DeleteShardPlacementRow(uint64 placementId); +extern void CreateSingleShardTable(Oid relationId, ColocationParam colocationParam); extern void CreateDistributedTable(Oid relationId, char *distributionColumnName, char distributionMethod, int shardCount, bool shardCountIsStrict, char *colocateWithTableName); extern void CreateReferenceTable(Oid relationId); extern void CreateTruncateTrigger(Oid relationId); +extern void EnsureUndistributeTenantTableSafe(Oid relationId, const char *operationName); extern TableConversionReturn * UndistributeTable(TableConversionParameters *params); +extern void UndistributeTables(List *relationIdList); extern void EnsureAllObjectDependenciesExistOnAllNodes(const List *targets); extern DeferredErrorMessage * DeferErrorIfCircularDependencyExists(const @@ -349,7 +389,9 @@ extern char * TableOwner(Oid relationId); extern void EnsureTablePermissions(Oid relationId, AclMode mode); extern void EnsureTableOwner(Oid relationId); extern void EnsureHashDistributedTable(Oid relationId); +extern void EnsureHashOrSingleShardDistributedTable(Oid relationId); extern void EnsureFunctionOwner(Oid functionId); +extern void EnsureSchemaOwner(Oid schemaId); extern void EnsureSuperUser(void); extern void ErrorIfTableIsACatalogTable(Relation relation); extern void EnsureTableNotDistributed(Oid relationId); diff --git a/src/include/distributed/multi_executor.h b/src/include/distributed/multi_executor.h index 4e7f13601..5ae010d87 100644 --- a/src/include/distributed/multi_executor.h +++ b/src/include/distributed/multi_executor.h @@ -114,6 +114,9 @@ typedef struct ExecutionParams /* isUtilityCommand is true if the current execution is for a utility * command such as a DDL command.*/ bool isUtilityCommand; + + /* pass bind parameters to the distributed executor for parameterized plans */ + ParamListInfo paramListInfo; } ExecutionParams; ExecutionParams * CreateBasicExecutionParams(RowModifyLevel modLevel, @@ -122,6 +125,11 @@ ExecutionParams * CreateBasicExecutionParams(RowModifyLevel modLevel, bool localExecutionSupported); extern uint64 ExecuteTaskListExtended(ExecutionParams *executionParams); +extern uint64 ExecuteTaskListIntoTupleDestWithParam(RowModifyLevel modLevel, + List *taskList, + TupleDestination *tupleDest, + bool expectResults, + ParamListInfo paramListInfo); extern uint64 ExecuteTaskListIntoTupleDest(RowModifyLevel modLevel, List *taskList, TupleDestination *tupleDest, bool expectResults); diff --git a/src/include/distributed/multi_join_order.h b/src/include/distributed/multi_join_order.h index 92d1edaf2..4e4ba1dd2 100644 --- a/src/include/distributed/multi_join_order.h +++ b/src/include/distributed/multi_join_order.h @@ -108,6 +108,8 @@ extern Var * DistPartitionKey(Oid relationId); extern Var * DistPartitionKeyOrError(Oid relationId); extern char PartitionMethod(Oid relationId); extern char TableReplicationModel(Oid relationId); +extern bool JoinOnColumns(List *currentPartitionColumnList, Var *candidatePartitionColumn, + List *joinClauseList); #endif /* MULTI_JOIN_ORDER_H */ diff --git a/src/include/distributed/multi_logical_planner.h b/src/include/distributed/multi_logical_planner.h index 189170358..de4901ea2 100644 --- a/src/include/distributed/multi_logical_planner.h +++ b/src/include/distributed/multi_logical_planner.h @@ -200,6 +200,7 @@ extern bool IsCitusTableRTE(Node *node); extern bool IsDistributedOrReferenceTableRTE(Node *node); extern bool IsDistributedTableRTE(Node *node); extern bool IsReferenceTableRTE(Node *node); +extern bool IsTableWithDistKeyRTE(Node *node); extern bool IsCitusExtraDataContainerRelation(RangeTblEntry *rte); extern bool ContainsReadIntermediateResultFunction(Node *node); extern bool ContainsReadIntermediateResultArrayFunction(Node *node); diff --git a/src/include/distributed/multi_physical_planner.h b/src/include/distributed/multi_physical_planner.h index ea5d15c83..b7acc0574 100644 --- a/src/include/distributed/multi_physical_planner.h +++ b/src/include/distributed/multi_physical_planner.h @@ -361,19 +361,19 @@ typedef struct JoinSequenceNode /* - * InsertSelectMethod represents the method to use for INSERT INTO ... SELECT - * queries. + * ModifyWithSelectMethod represents the method to use for INSERT INTO ... SELECT + * or MERGE type of queries. * * Note that there is a third method which is not represented here, which is - * pushing down the INSERT INTO ... SELECT to workers. This method is executed - * similar to other distributed queries and doesn't need a special execution - * code, so we don't need to represent it here. + * pushing down the MERGE/INSERT INTO ... SELECT to workers. This method is + * executed similar to other distributed queries and doesn't need a special + * execution code, so we don't need to represent it here. */ -typedef enum InsertSelectMethod +typedef enum ModifyWithSelectMethod { - INSERT_SELECT_VIA_COORDINATOR, - INSERT_SELECT_REPARTITION -} InsertSelectMethod; + MODIFY_WITH_SELECT_VIA_COORDINATOR, + MODIFY_WITH_SELECT_REPARTITION +} ModifyWithSelectMethod; /* @@ -412,18 +412,22 @@ typedef struct DistributedPlan Oid targetRelationId; /* - * INSERT .. SELECT via the coordinator or repartition */ - Query *insertSelectQuery; - PlannedStmt *selectPlanForInsertSelect; - InsertSelectMethod insertSelectMethod; + * Modifications performed using the output of a source query via + * the coordinator or repartition. + */ + Query *modifyQueryViaCoordinatorOrRepartition; + PlannedStmt *selectPlanForModifyViaCoordinatorOrRepartition; + ModifyWithSelectMethod modifyWithSelectMethod; /* - * If intermediateResultIdPrefix is non-null, an INSERT ... SELECT - * via the coordinator is written to a set of intermediate results - * named according to _. - * That way we can run a distributed INSERT ... SELECT with - * RETURNING or ON CONFLICT from the intermediate results to the - * target relation. + * If intermediateResultIdPrefix is non-null, the source query + * results are written to a set of intermediate results named + * according to _. + * That way we can run a distributed modification query which + * requires evaluating source query results at the coordinator. + * Once results are captured in intermediate files, modification + * is done from the intermediate results into the target relation. + * */ char *intermediateResultIdPrefix; @@ -459,6 +463,13 @@ typedef struct DistributedPlan * or if prepared statement parameters prevented successful planning. */ DeferredErrorMessage *planningError; + + /* + * When performing query execution scenarios that require repartitioning + * the source rows, this field stores the index of the column in the list + * of source rows to be repartitioned for colocation with the target. + */ + int sourceResultRepartitionColumnIndex; } DistributedPlan; @@ -543,7 +554,6 @@ extern Node * WrapUngroupedVarsInAnyValueAggregate(Node *expression, List *groupClauseList, List *targetList, bool checkExpressionEquality); -extern CollateExpr * RelabelTypeToCollateExpr(RelabelType *relabelType); /* * Function declarations for building, updating constraints and simple operator diff --git a/src/include/distributed/multi_router_planner.h b/src/include/distributed/multi_router_planner.h index 200c498ef..160cf6605 100644 --- a/src/include/distributed/multi_router_planner.h +++ b/src/include/distributed/multi_router_planner.h @@ -117,5 +117,7 @@ extern bool HasDangerousJoinUsing(List *rtableList, Node *jtnode); extern Job * RouterJob(Query *originalQuery, PlannerRestrictionContext *plannerRestrictionContext, DeferredErrorMessage **planningError); +extern bool ContainsOnlyLocalTables(RTEListProperties *rteProperties); +extern RangeTblEntry * ExtractSourceResultRangeTableEntry(Query *query); #endif /* MULTI_ROUTER_PLANNER_H */ diff --git a/src/include/distributed/multi_server_executor.h b/src/include/distributed/multi_server_executor.h index 56dd6d808..f49ef60c2 100644 --- a/src/include/distributed/multi_server_executor.h +++ b/src/include/distributed/multi_server_executor.h @@ -29,7 +29,8 @@ typedef enum { MULTI_EXECUTOR_INVALID_FIRST = 0, MULTI_EXECUTOR_ADAPTIVE = 1, - MULTI_EXECUTOR_NON_PUSHABLE_INSERT_SELECT = 2 + MULTI_EXECUTOR_NON_PUSHABLE_INSERT_SELECT = 2, + MULTI_EXECUTOR_NON_PUSHABLE_MERGE_QUERY = 3 } MultiExecutorType; diff --git a/src/include/distributed/pg_dist_node_metadata.h b/src/include/distributed/pg_dist_node_metadata.h index f2bdc4801..00ccb9788 100644 --- a/src/include/distributed/pg_dist_node_metadata.h +++ b/src/include/distributed/pg_dist_node_metadata.h @@ -19,4 +19,6 @@ #define Natts_pg_dist_node_metadata 1 #define Anum_pg_dist_node_metadata_metadata 1 +extern int FindCoordinatorNodeId(void); + #endif /* PG_DIST_NODE_METADATA_H */ diff --git a/src/include/distributed/pg_dist_schema.h b/src/include/distributed/pg_dist_schema.h new file mode 100644 index 000000000..528f47ae7 --- /dev/null +++ b/src/include/distributed/pg_dist_schema.h @@ -0,0 +1,41 @@ +/*------------------------------------------------------------------------- + * + * pg_dist_schema.h + * definition of the system catalog for the schemas used for schema-based + * sharding in Citus. + * + *------------------------------------------------------------------------- + */ + +#ifndef PG_DIST_SCHEMA_H +#define PG_DIST_SCHEMA_H + +#include "postgres.h" + + +/* ---------------- + * pg_dist_schema definition. + * ---------------- + */ +typedef struct FormData_pg_dist_schema +{ + Oid schemaid; + uint32 colocationid; +} FormData_pg_dist_schema; + +/* ---------------- + * Form_pg_dist_schema corresponds to a pointer to a tuple with + * the format of pg_dist_schema relation. + * ---------------- + */ +typedef FormData_pg_dist_schema *Form_pg_dist_schema; + +/* ---------------- + * compiler constants for pg_dist_schema + * ---------------- + */ +#define Natts_pg_dist_schema 2 +#define Anum_pg_dist_schema_schemaid 1 +#define Anum_pg_dist_schema_colocationid 2 + +#endif /* PG_DIST_SCHEMA_H */ diff --git a/src/include/distributed/pg_version_constants.h b/src/include/distributed/pg_version_constants.h index 83b1071dd..a85d72d84 100644 --- a/src/include/distributed/pg_version_constants.h +++ b/src/include/distributed/pg_version_constants.h @@ -11,7 +11,6 @@ #ifndef PG_VERSION_CONSTANTS #define PG_VERSION_CONSTANTS -#define PG_VERSION_13 130000 #define PG_VERSION_14 140000 #define PG_VERSION_15 150000 #define PG_VERSION_16 160000 diff --git a/src/include/distributed/recursive_planning.h b/src/include/distributed/recursive_planning.h index e849d7158..8943443aa 100644 --- a/src/include/distributed/recursive_planning.h +++ b/src/include/distributed/recursive_planning.h @@ -46,6 +46,7 @@ extern void ReplaceRTERelationWithRteSubquery(RangeTblEntry *rangeTableEntry, extern bool IsRecursivelyPlannableRelation(RangeTblEntry *rangeTableEntry); extern bool IsRelationLocalTableOrMatView(Oid relationId); extern bool ContainsReferencesToOuterQuery(Query *query); +extern void UpdateVarNosInNode(Node *node, Index newVarNo); #endif /* RECURSIVE_PLANNING_H */ diff --git a/src/include/distributed/repartition_executor.h b/src/include/distributed/repartition_executor.h new file mode 100644 index 000000000..de4ad122a --- /dev/null +++ b/src/include/distributed/repartition_executor.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * repartition_executor.h + * + * Declarations for public functions and types related to repartition of + * select query results. + * + * Copyright (c) Citus Data, Inc. + * + *------------------------------------------------------------------------- + */ + +#ifndef REPARTITION_EXECUTOR_H +#define REPARTITION_EXECUTOR_H + +extern bool EnableRepartitionedInsertSelect; + +extern int DistributionColumnIndex(List *insertTargetList, Var *distributionColumn); +extern List * GenerateTaskListWithColocatedIntermediateResults(Oid targetRelationId, + Query * + modifyQueryViaCoordinatorOrRepartition, + char *resultIdPrefix); +extern List * GenerateTaskListWithRedistributedResults( + Query *modifyQueryViaCoordinatorOrRepartition, + CitusTableCacheEntry * + targetRelation, + List **redistributedResults, + bool useBinaryFormat); +extern bool IsSupportedRedistributionTarget(Oid targetRelationId); +extern bool IsRedistributablePlan(Plan *selectPlan); + +#endif /* REPARTITION_EXECUTOR_H */ diff --git a/src/include/distributed/shard_rebalancer.h b/src/include/distributed/shard_rebalancer.h index 705196ad4..38ce4f485 100644 --- a/src/include/distributed/shard_rebalancer.h +++ b/src/include/distributed/shard_rebalancer.h @@ -188,6 +188,7 @@ typedef struct RebalancePlanFunctions extern char *VariablesToBePassedToNewConnections; extern int MaxRebalancerLoggedIgnoredMoves; +extern int RebalancerByDiskSizeBaseCost; extern bool RunningUnderIsolationTest; extern bool PropagateSessionSettingsForLoopbackConnection; extern int MaxBackgroundTaskExecutorsPerNode; diff --git a/src/include/distributed/tenant_schema_metadata.h b/src/include/distributed/tenant_schema_metadata.h new file mode 100644 index 000000000..1db1c28c5 --- /dev/null +++ b/src/include/distributed/tenant_schema_metadata.h @@ -0,0 +1,33 @@ +/*------------------------------------------------------------------------- + * + * tenant_schema_metadata.h + * + * This file contains functions to query and modify tenant schema metadata, + * which is used to track the schemas used for schema-based sharding in + * Citus. + * + * ------------------------------------------------------------------------- + */ + +#ifndef TENANT_SCHEMA_METADATA_H +#define TENANT_SCHEMA_METADATA_H + +#include "postgres.h" + +/* accessors */ +extern Oid ColocationIdGetTenantSchemaId(uint32 colocationId); +extern uint32 SchemaIdGetTenantColocationId(Oid schemaId); +extern bool IsTenantSchema(Oid schemaId); +extern bool IsTenantSchemaColocationGroup(uint32 colocationId); + +/* + * Local only modifiers. + * + * These functions may not make much sense by themselves. They are mainly + * exported for tenant-schema management (schema_based_sharding.c) and + * metadata-sync layer (metadata_sync.c). + */ +extern void InsertTenantSchemaLocally(Oid schemaId, uint32 colocationId); +extern void DeleteTenantSchemaLocally(Oid schemaId); + +#endif /* TENANT_SCHEMA_METADATA_H */ diff --git a/src/include/distributed/utils/citus_stat_tenants.h b/src/include/distributed/utils/citus_stat_tenants.h index dbc867071..0a482b241 100644 --- a/src/include/distributed/utils/citus_stat_tenants.h +++ b/src/include/distributed/utils/citus_stat_tenants.h @@ -11,24 +11,33 @@ #ifndef CITUS_ATTRIBUTE_H #define CITUS_ATTRIBUTE_H +#include "distributed/hash_helpers.h" #include "executor/execdesc.h" #include "executor/executor.h" #include "storage/lwlock.h" #include "utils/datetime.h" +#include "utils/hsearch.h" #define MAX_TENANT_ATTRIBUTE_LENGTH 100 +/* + * Hashtable key that defines the identity of a hashtable entry. + * The key is the attribute value, e.g distribution column and the colocation group id of the tenant. + */ +typedef struct TenantStatsHashKey +{ + char tenantAttribute[MAX_TENANT_ATTRIBUTE_LENGTH]; + int colocationGroupId; +} TenantStatsHashKey; +assert_valid_hash_key2(TenantStatsHashKey, tenantAttribute, colocationGroupId); + /* * TenantStats is the struct that keeps statistics about one tenant. */ typedef struct TenantStats { - /* - * The attribute value, e.g distribution column, and colocation group id - * of the tenant. - */ - char tenantAttribute[MAX_TENANT_ATTRIBUTE_LENGTH]; - int colocationGroupId; + TenantStatsHashKey key; /* hash key of entry - MUST BE FIRST */ + /* * Number of SELECT queries this tenant ran in this and last periods. @@ -42,6 +51,13 @@ typedef struct TenantStats int writesInLastPeriod; int writesInThisPeriod; + + /* + * CPU time usage of this tenant in this and last periods. + */ + double cpuUsageInLastPeriod; + double cpuUsageInThisPeriod; + /* * The latest time this tenant ran a query. This value is used to update the score later. */ @@ -63,8 +79,7 @@ typedef struct TenantStats /* * Locks needed to update this tenant's statistics. */ - NamedLWLockTranche namedLockTranche; - LWLock lock; + slock_t lock; } TenantStats; /* @@ -82,12 +97,9 @@ typedef struct MultiTenantMonitor LWLock lock; /* - * tenantCount is the number of items in the tenants array. - * The total length of tenants array is set up at CreateSharedMemoryForMultiTenantMonitor - * and is 3 * citus.stat_tenants_limit + * The max length of tenants hashtable is 3 * citus.stat_tenants_limit */ - int tenantCount; - TenantStats tenants[FLEXIBLE_ARRAY_MEMBER]; + HTAB *tenants; } MultiTenantMonitor; typedef enum @@ -109,5 +121,6 @@ extern int StatTenantsLogLevel; extern int StatTenantsPeriod; extern int StatTenantsLimit; extern int StatTenantsTrack; +extern double StatTenantsSampleRateForNewTenants; #endif /*CITUS_ATTRIBUTE_H */ diff --git a/src/include/pg_version_compat.h b/src/include/pg_version_compat.h index eb81bca43..00c5e286b 100644 --- a/src/include/pg_version_compat.h +++ b/src/include/pg_version_compat.h @@ -61,8 +61,7 @@ pg_strtoint64(char *s) * We want to use it in all versions. So we backport it ourselves in earlier * versions, and rely on the Postgres provided version in the later versions. */ -#if PG_VERSION_NUM >= PG_VERSION_13 && PG_VERSION_NUM < 130010 \ - || PG_VERSION_NUM >= PG_VERSION_14 && PG_VERSION_NUM < 140007 +#if PG_VERSION_NUM < 140007 static inline SMgrRelation RelationGetSmgr(Relation rel) { @@ -84,67 +83,6 @@ RelationGetSmgr(Relation rel) #endif -#if PG_VERSION_NUM >= PG_VERSION_14 -#define AlterTableStmtObjType_compat(a) ((a)->objtype) -#define getObjectTypeDescription_compat(a, b) getObjectTypeDescription(a, b) -#define getObjectIdentity_compat(a, b) getObjectIdentity(a, b) - -/* for MemoryContextMethods->stats */ -#define stats_compat(a, b, c, d, e) stats(a, b, c, d, e) -#define FuncnameGetCandidates_compat(a, b, c, d, e, f, g) \ - FuncnameGetCandidates(a, b, c, d, e, f, g) -#define expand_function_arguments_compat(a, b, c, d) expand_function_arguments(a, b, c, d) -#define BeginCopyFrom_compat(a, b, c, d, e, f, g, h) BeginCopyFrom(a, b, c, d, e, f, g, h) -#define standard_ProcessUtility_compat(a, b, c, d, e, f, g, h) \ - standard_ProcessUtility(a, b, c, d, e, f, g, h) -#define ProcessUtility_compat(a, b, c, d, e, f, g, h) \ - ProcessUtility(a, b, c, d, e, f, g, h) -#define PrevProcessUtility_compat(a, b, c, d, e, f, g, h) \ - PrevProcessUtility(a, b, c, d, e, f, g, h) -#define SetTuplestoreDestReceiverParams_compat(a, b, c, d, e, f) \ - SetTuplestoreDestReceiverParams(a, b, c, d, e, f) -#define pgproc_statusflags_compat(pgproc) ((pgproc)->statusFlags) -#define get_partition_parent_compat(a, b) get_partition_parent(a, b) -#define RelationGetPartitionDesc_compat(a, b) RelationGetPartitionDesc(a, b) -#define make_simple_restrictinfo_compat(a, b) make_simple_restrictinfo(a, b) -#define pull_varnos_compat(a, b) pull_varnos(a, b) -#else -#define AlterTableStmtObjType_compat(a) ((a)->relkind) -#define F_NEXTVAL F_NEXTVAL_OID -#define ROLE_PG_MONITOR DEFAULT_ROLE_MONITOR -#define PROC_WAIT_STATUS_WAITING STATUS_WAITING -#define getObjectTypeDescription_compat(a, b) getObjectTypeDescription(a) -#define getObjectIdentity_compat(a, b) getObjectIdentity(a) - -/* for MemoryContextMethods->stats */ -#define stats_compat(a, b, c, d, e) stats(a, b, c, d) -#define FuncnameGetCandidates_compat(a, b, c, d, e, f, g) \ - FuncnameGetCandidates(a, b, c, d, e, g) -#define expand_function_arguments_compat(a, b, c, d) expand_function_arguments(a, c, d) -#define VacOptValue VacOptTernaryValue -#define VACOPTVALUE_UNSPECIFIED VACOPT_TERNARY_DEFAULT -#define VACOPTVALUE_DISABLED VACOPT_TERNARY_DISABLED -#define VACOPTVALUE_ENABLED VACOPT_TERNARY_ENABLED -#define CopyFromState CopyState -#define BeginCopyFrom_compat(a, b, c, d, e, f, g, h) BeginCopyFrom(a, b, d, e, f, g, h) -#define standard_ProcessUtility_compat(a, b, c, d, e, f, g, h) \ - standard_ProcessUtility(a, b, d, e, f, g, h) -#define ProcessUtility_compat(a, b, c, d, e, f, g, h) ProcessUtility(a, b, d, e, f, g, h) -#define PrevProcessUtility_compat(a, b, c, d, e, f, g, h) \ - PrevProcessUtility(a, b, d, e, f, g, h) -#define COPY_FRONTEND COPY_NEW_FE -#define SetTuplestoreDestReceiverParams_compat(a, b, c, d, e, f) \ - SetTuplestoreDestReceiverParams(a, b, c, d) -#define pgproc_statusflags_compat(pgproc) \ - ((&ProcGlobal->allPgXact[(pgproc)->pgprocno])->vacuumFlags) -#define get_partition_parent_compat(a, b) get_partition_parent(a) -#define RelationGetPartitionDesc_compat(a, b) RelationGetPartitionDesc(a) -#define PQ_LARGE_MESSAGE_LIMIT 0 -#define make_simple_restrictinfo_compat(a, b) make_simple_restrictinfo(b) -#define pull_varnos_compat(a, b) pull_varnos(b) -#define ROLE_PG_READ_ALL_STATS DEFAULT_ROLE_READ_ALL_STATS -#endif - #define SetListCellPtr(a, b) ((a)->ptr_value = (b)) #define RangeTableEntryFromNSItem(a) ((a)->p_rte) #define fcGetArgValue(fc, n) ((fc)->args[n].value) diff --git a/src/test/regress/Makefile b/src/test/regress/Makefile index b801f33ff..4bdc7a1b8 100644 --- a/src/test/regress/Makefile +++ b/src/test/regress/Makefile @@ -34,6 +34,7 @@ MULTI_REGRESS_OPTS = --inputdir=$(citus_abs_srcdir) $(pg_regress_locale_flags) - pg_upgrade_check = $(citus_abs_srcdir)/citus_tests/upgrade/pg_upgrade_test.py citus_upgrade_check =CITUS_OLD_VERSION=$(citus-old-version) $(citus_abs_srcdir)/citus_tests/upgrade/citus_upgrade_test.py arbitrary_config_check = $(citus_abs_srcdir)/citus_tests/arbitrary_configs/citus_arbitrary_configs.py +query_generator_check = $(citus_abs_srcdir)/citus_tests/query_generator/bin/run_query_compare_test.py template_isolation_files = $(shell find $(citus_abs_srcdir)/spec/ -name '*.spec') generated_isolation_files = $(patsubst $(citus_abs_srcdir)/spec/%,$(citus_abs_srcdir)/build/specs/%,$(template_isolation_files)) @@ -44,7 +45,7 @@ vanilla_diffs_file = $(citus_abs_srcdir)/pg_vanilla_outputs/$(MAJORVERSION)/regr # intermediate, for muscle memory backward compatibility. check: check-full check-enterprise-full # check-full triggers all tests that ought to be run routinely -check-full: check-multi check-multi-mx check-multi-1 check-operations check-follower-cluster check-isolation check-failure check-split check-vanilla check-columnar check-columnar-isolation check-pg-upgrade check-arbitrary-configs check-citus-upgrade check-citus-upgrade-mixed check-citus-upgrade-local check-citus-upgrade-mixed-local check-pytest +check-full: check-multi check-multi-mx check-multi-1 check-operations check-follower-cluster check-isolation check-failure check-split check-vanilla check-columnar check-columnar-isolation check-pg-upgrade check-arbitrary-configs check-citus-upgrade check-citus-upgrade-mixed check-citus-upgrade-local check-citus-upgrade-mixed-local check-pytest check-query-generator # check-enterprise-full triggers all enterprise specific tests check-enterprise-full: check-enterprise check-enterprise-isolation check-enterprise-failure check-enterprise-isolation-logicalrep-1 check-enterprise-isolation-logicalrep-2 check-enterprise-isolation-logicalrep-3 @@ -262,6 +263,9 @@ check-arbitrary-base: all check-pytest: pytest -n auto +check-query-generator: all + ${query_generator_check} --bindir=$(bindir) --pgxsdir=$(pgxsdir) --seed=$(seed) + check-citus-upgrade: all $(citus_upgrade_check) \ --bindir=$(bindir) \ @@ -291,7 +295,7 @@ check-citus-upgrade-mixed-local: all clean-upgrade-artifacts --mixed clean-upgrade-artifacts: - rm -rf $(citus_abs_srcdir)/tmp_citus_tarballs/ $(citus_abs_srcdir)/tmp_citus_upgrade/ /tmp/citus_copy/ + rm -rf $(citus_abs_srcdir)/tmp_citus_upgrade/ /tmp/citus_copy/ clean distclean maintainer-clean: rm -rf input/ output/ diff --git a/src/test/regress/Pipfile b/src/test/regress/Pipfile index 663785a3d..5bce63004 100644 --- a/src/test/regress/Pipfile +++ b/src/test/regress/Pipfile @@ -15,6 +15,7 @@ pytest-asyncio = "*" pytest-timeout = "*" pytest-xdist = "*" pytest-repeat = "*" +pyyaml = "*" [dev-packages] black = "*" diff --git a/src/test/regress/Pipfile.lock b/src/test/regress/Pipfile.lock index 4a86e09a8..ed604c2c0 100644 --- a/src/test/regress/Pipfile.lock +++ b/src/test/regress/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "eb9ca3a7b05e76c7ac60179a1755f89600dfb215e02bf08c258d548df1d96025" + "sha256": "9568b1f3e4d4fd408e5e263f6346b0a4f479ac88e02f64bb79a9d482096e6a03" }, "pipfile-spec": 6, "requires": { @@ -24,14 +24,6 @@ "markers": "python_version >= '3.6'", "version": "==3.4.1" }, - "attrs": { - "hashes": [ - "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836", - "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99" - ], - "markers": "python_version >= '3.6'", - "version": "==22.2.0" - }, "blinker": { "hashes": [ "sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6" @@ -219,32 +211,28 @@ }, "cryptography": { "hashes": [ - "sha256:103e8f7155f3ce2ffa0049fe60169878d47a4364b277906386f8de21c9234aa1", - "sha256:23df8ca3f24699167daf3e23e51f7ba7334d504af63a94af468f468b975b7dd7", - "sha256:2725672bb53bb92dc7b4150d233cd4b8c59615cd8288d495eaa86db00d4e5c06", - "sha256:30b1d1bfd00f6fc80d11300a29f1d8ab2b8d9febb6ed4a38a76880ec564fae84", - "sha256:35d658536b0a4117c885728d1a7032bdc9a5974722ae298d6c533755a6ee3915", - "sha256:50cadb9b2f961757e712a9737ef33d89b8190c3ea34d0fb6675e00edbe35d074", - "sha256:5f8c682e736513db7d04349b4f6693690170f95aac449c56f97415c6980edef5", - "sha256:6236a9610c912b129610eb1a274bdc1350b5df834d124fa84729ebeaf7da42c3", - "sha256:788b3921d763ee35dfdb04248d0e3de11e3ca8eb22e2e48fef880c42e1f3c8f9", - "sha256:8bc0008ef798231fac03fe7d26e82d601d15bd16f3afaad1c6113771566570f3", - "sha256:8f35c17bd4faed2bc7797d2a66cbb4f986242ce2e30340ab832e5d99ae60e011", - "sha256:b49a88ff802e1993b7f749b1eeb31134f03c8d5c956e3c125c75558955cda536", - "sha256:bc0521cce2c1d541634b19f3ac661d7a64f9555135e9d8af3980965be717fd4a", - "sha256:bc5b871e977c8ee5a1bbc42fa8d19bcc08baf0c51cbf1586b0e87a2694dde42f", - "sha256:c43ac224aabcbf83a947eeb8b17eaf1547bce3767ee2d70093b461f31729a480", - "sha256:d15809e0dbdad486f4ad0979753518f47980020b7a34e9fc56e8be4f60702fac", - "sha256:d7d84a512a59f4412ca8549b01f94be4161c94efc598bf09d027d67826beddc0", - "sha256:e029b844c21116564b8b61216befabca4b500e6816fa9f0ba49527653cae2108", - "sha256:e8a0772016feeb106efd28d4a328e77dc2edae84dfbac06061319fdb669ff828", - "sha256:e944fe07b6f229f4c1a06a7ef906a19652bdd9fd54c761b0ff87e83ae7a30354", - "sha256:eb40fe69cfc6f5cdab9a5ebd022131ba21453cf7b8a7fd3631f45bbf52bed612", - "sha256:fa507318e427169ade4e9eccef39e9011cdc19534f55ca2f36ec3f388c1f70f3", - "sha256:ffd394c7896ed7821a6d13b24657c6a34b6e2650bd84ae063cf11ccffa4f1a97" + "sha256:05dc219433b14046c476f6f09d7636b92a1c3e5808b9a6536adf4932b3b2c440", + "sha256:0dcca15d3a19a66e63662dc8d30f8036b07be851a8680eda92d079868f106288", + "sha256:142bae539ef28a1c76794cca7f49729e7c54423f615cfd9b0b1fa90ebe53244b", + "sha256:3daf9b114213f8ba460b829a02896789751626a2a4e7a43a28ee77c04b5e4958", + "sha256:48f388d0d153350f378c7f7b41497a54ff1513c816bcbbcafe5b829e59b9ce5b", + "sha256:4df2af28d7bedc84fe45bd49bc35d710aede676e2a4cb7fc6d103a2adc8afe4d", + "sha256:4f01c9863da784558165f5d4d916093737a75203a5c5286fde60e503e4276c7a", + "sha256:7a38250f433cd41df7fcb763caa3ee9362777fdb4dc642b9a349721d2bf47404", + "sha256:8f79b5ff5ad9d3218afb1e7e20ea74da5f76943ee5edb7f76e56ec5161ec782b", + "sha256:956ba8701b4ffe91ba59665ed170a2ebbdc6fc0e40de5f6059195d9f2b33ca0e", + "sha256:a04386fb7bc85fab9cd51b6308633a3c271e3d0d3eae917eebab2fac6219b6d2", + "sha256:a95f4802d49faa6a674242e25bfeea6fc2acd915b5e5e29ac90a32b1139cae1c", + "sha256:adc0d980fd2760c9e5de537c28935cc32b9353baaf28e0814df417619c6c8c3b", + "sha256:aecbb1592b0188e030cb01f82d12556cf72e218280f621deed7d806afd2113f9", + "sha256:b12794f01d4cacfbd3177b9042198f3af1c856eedd0a98f10f141385c809a14b", + "sha256:c0764e72b36a3dc065c155e5b22f93df465da9c39af65516fe04ed3c68c92636", + "sha256:c33c0d32b8594fa647d2e01dbccc303478e16fdd7cf98652d5b3ed11aa5e5c99", + "sha256:cbaba590180cba88cb99a5f76f90808a624f18b169b90a4abb40c1fd8c19420e", + "sha256:d5a1bd0e9e2031465761dfa920c16b0065ad77321d8a8c1f5ee331021fda65e9" ], "index": "pypi", - "version": "==39.0.2" + "version": "==40.0.2" }, "docopt": { "hashes": [ @@ -271,11 +259,11 @@ }, "filelock": { "hashes": [ - "sha256:3199fd0d3faea8b911be52b663dfccceb84c95949dd13179aa21436d1a79c4ce", - "sha256:e90b34656470756edf8b19656785c5fea73afa1953f3e1b0d645cef11cab3182" + "sha256:ad98852315c2ab702aeb628412cbf7e95b7ce8c3bf9565670b4eaecf1db370a9", + "sha256:fc03ae43288c013d2ea83c8597001b1129db351aad9c57fe2409327916b8e718" ], "index": "pypi", - "version": "==3.10.0" + "version": "==3.12.0" }, "flask": { "hashes": [ @@ -488,11 +476,11 @@ }, "packaging": { "hashes": [ - "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2", - "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97" + "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", + "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" ], "markers": "python_version >= '3.7'", - "version": "==23.0" + "version": "==23.1" }, "passlib": { "hashes": [ @@ -553,21 +541,11 @@ }, "pyasn1": { "hashes": [ - "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", - "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576", - "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", - "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", - "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", - "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", - "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", - "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86", - "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12", - "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776", - "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", - "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2", - "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3" + "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57", + "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde" ], - "version": "==0.4.8" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==0.5.0" }, "pycparser": { "hashes": [ @@ -578,11 +556,11 @@ }, "pyopenssl": { "hashes": [ - "sha256:c1cc5f86bcacefc84dada7d31175cae1b1518d5f60d3d0bb595a67822a868a6f", - "sha256:df5fc28af899e74e19fccb5510df423581047e10ab6f1f4ba1763ff5fde844c0" + "sha256:841498b9bec61623b1b6c47ebbc02367c07d60e0e195f19790817f10cc8db0b7", + "sha256:9e0c526404a210df9d2b18cd33364beadb0dc858a739b885677bc65e105d4a4c" ], "markers": "python_version >= '3.6'", - "version": "==23.0.0" + "version": "==23.1.1" }, "pyparsing": { "hashes": [ @@ -600,19 +578,19 @@ }, "pytest": { "hashes": [ - "sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e", - "sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4" + "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362", + "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3" ], "index": "pypi", - "version": "==7.2.2" + "version": "==7.3.1" }, "pytest-asyncio": { "hashes": [ - "sha256:83cbf01169ce3e8eb71c6c278ccb0574d1a7a3bb8eaaf5e50e0ad342afb33b36", - "sha256:f129998b209d04fcc65c96fc85c11e5316738358909a8399e93be553d7656442" + "sha256:2b38a496aef56f56b0e87557ec313e11e1ab9276fc3863f6a7be0f1d0e415e1b", + "sha256:f2b3366b7cd501a4056858bd39349d5af19742aed2d81660b7998b6341c7eb9c" ], "index": "pypi", - "version": "==0.20.3" + "version": "==0.21.0" }, "pytest-repeat": { "hashes": [ @@ -638,6 +616,52 @@ "index": "pypi", "version": "==3.2.1" }, + "pyyaml": { + "hashes": [ + "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", + "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", + "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", + "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", + "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", + "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", + "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", + "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", + "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", + "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", + "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", + "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", + "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", + "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", + "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", + "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", + "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", + "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", + "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", + "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", + "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", + "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", + "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", + "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", + "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", + "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", + "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", + "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", + "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", + "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", + "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", + "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", + "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", + "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", + "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", + "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", + "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", + "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", + "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", + "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" + ], + "index": "pypi", + "version": "==6.0" + }, "ruamel.yaml": { "hashes": [ "sha256:1a771fc92d3823682b7f0893ad56cb5a5c87c48e62b5399d6f42c8759a583b33", @@ -705,20 +729,20 @@ }, "tornado": { "hashes": [ - "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca", - "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72", - "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23", - "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8", - "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b", - "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9", - "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13", - "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75", - "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac", - "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e", - "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b" + "sha256:4546003dc8b5733489139d3bff5fa6a0211be505faf819bd9970e7c2b32e8122", + "sha256:4d349846931557b7ec92f224b5d598b160e2ba26ae1812480b42e9622c884bf7", + "sha256:6164571f5b9f73143d1334df4584cb9ac86d20c461e17b6c189a19ead8bb93c1", + "sha256:6cfff1e9c15c79e106b8352269d201f8fc0815914a6260f3893ca18b724ea94b", + "sha256:720f53e6367b38190ae7fa398c25c086c69d88b3c6535bd6021a126b727fb5cd", + "sha256:912df5712024564e362ecce43c8d5862e14c78c8dd3846c9d889d44fbd7f4951", + "sha256:c37b6a384d54ce6a31168d40ab21ad2591ddaf34973075cc0cad154402ecd9e8", + "sha256:c659ab04d5aa477dbe44152c67d93f3ad3243b992d94f795ca1d5c73c37337ce", + "sha256:c9114a61a4588c09065b9996ae05462350d17160b92b9bf9a1e93689cc0424dc", + "sha256:d68f3192936ff2c4add04dc21a436a43b4408d466746b78bb2b9d0a53a18683f", + "sha256:d7b737e18f701de3e4a3b0824260b4d740e4d60607b8089bb80e80ffd464780e" ], - "markers": "python_version >= '3.7'", - "version": "==6.2" + "markers": "python_version >= '3.8'", + "version": "==6.3" }, "typing-extensions": { "hashes": [ @@ -808,42 +832,42 @@ "develop": { "attrs": { "hashes": [ - "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836", - "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99" + "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04", + "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015" ], - "markers": "python_version >= '3.6'", - "version": "==22.2.0" + "markers": "python_version >= '3.7'", + "version": "==23.1.0" }, "black": { "hashes": [ - "sha256:0052dba51dec07ed029ed61b18183942043e00008ec65d5028814afaab9a22fd", - "sha256:0680d4380db3719ebcfb2613f34e86c8e6d15ffeabcf8ec59355c5e7b85bb555", - "sha256:121ca7f10b4a01fd99951234abdbd97728e1240be89fde18480ffac16503d481", - "sha256:162e37d49e93bd6eb6f1afc3e17a3d23a823042530c37c3c42eeeaf026f38468", - "sha256:2a951cc83ab535d248c89f300eccbd625e80ab880fbcfb5ac8afb5f01a258ac9", - "sha256:2bf649fda611c8550ca9d7592b69f0637218c2369b7744694c5e4902873b2f3a", - "sha256:382998821f58e5c8238d3166c492139573325287820963d2f7de4d518bd76958", - "sha256:49f7b39e30f326a34b5c9a4213213a6b221d7ae9d58ec70df1c4a307cf2a1580", - "sha256:57c18c5165c1dbe291d5306e53fb3988122890e57bd9b3dcb75f967f13411a26", - "sha256:7a0f701d314cfa0896b9001df70a530eb2472babb76086344e688829efd97d32", - "sha256:8178318cb74f98bc571eef19068f6ab5613b3e59d4f47771582f04e175570ed8", - "sha256:8b70eb40a78dfac24842458476135f9b99ab952dd3f2dab738c1881a9b38b753", - "sha256:9880d7d419bb7e709b37e28deb5e68a49227713b623c72b2b931028ea65f619b", - "sha256:9afd3f493666a0cd8f8df9a0200c6359ac53940cbde049dcb1a7eb6ee2dd7074", - "sha256:a29650759a6a0944e7cca036674655c2f0f63806ddecc45ed40b7b8aa314b651", - "sha256:a436e7881d33acaf2536c46a454bb964a50eff59b21b51c6ccf5a40601fbef24", - "sha256:a59db0a2094d2259c554676403fa2fac3473ccf1354c1c63eccf7ae65aac8ab6", - "sha256:a8471939da5e824b891b25751955be52ee7f8a30a916d570a5ba8e0f2eb2ecad", - "sha256:b0bd97bea8903f5a2ba7219257a44e3f1f9d00073d6cc1add68f0beec69692ac", - "sha256:b6a92a41ee34b883b359998f0c8e6eb8e99803aa8bf3123bf2b2e6fec505a221", - "sha256:bb460c8561c8c1bec7824ecbc3ce085eb50005883a6203dcfb0122e95797ee06", - "sha256:bfffba28dc52a58f04492181392ee380e95262af14ee01d4bc7bb1b1c6ca8d27", - "sha256:c1c476bc7b7d021321e7d93dc2cbd78ce103b84d5a4cf97ed535fbc0d6660648", - "sha256:c91dfc2c2a4e50df0026f88d2215e166616e0c80e86004d0003ece0488db2739", - "sha256:e6663f91b6feca5d06f2ccd49a10f254f9298cc1f7f49c46e498a0771b507104" + "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5", + "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915", + "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326", + "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940", + "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b", + "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30", + "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c", + "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c", + "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab", + "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27", + "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2", + "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961", + "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9", + "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb", + "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70", + "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331", + "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2", + "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266", + "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d", + "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6", + "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b", + "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925", + "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8", + "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4", + "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3" ], "index": "pypi", - "version": "==23.1.0" + "version": "==23.3.0" }, "click": { "hashes": [ @@ -863,11 +887,11 @@ }, "flake8-bugbear": { "hashes": [ - "sha256:beb5c7efcd7ccc2039ef66a77bb8db925e7be3531ff1cb4d0b7030d0e2113d72", - "sha256:e3e7f74c8a49ad3794a7183353026dabd68c74030d5f46571f84c1fb0eb79363" + "sha256:8a218d13abd6904800970381057ce8e72cde8eea743240c4ef3ede4dd0bc9cfb", + "sha256:ea565bdb87b96b56dc499edd6cc3ba7f695373d902a5f56c989b74fad7c7719d" ], "index": "pypi", - "version": "==23.3.12" + "version": "==23.3.23" }, "isort": { "hashes": [ @@ -895,11 +919,11 @@ }, "packaging": { "hashes": [ - "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2", - "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97" + "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", + "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" ], "markers": "python_version >= '3.7'", - "version": "==23.0" + "version": "==23.1" }, "pathspec": { "hashes": [ @@ -911,11 +935,11 @@ }, "platformdirs": { "hashes": [ - "sha256:024996549ee88ec1a9aa99ff7f8fc819bb59e2c3477b410d90a16d32d6e707aa", - "sha256:e5986afb596e4bb5bde29a79ac9061aa955b94fca2399b7aaac4090860920dd8" + "sha256:d5b638ca397f25f979350ff789db335903d7ea010ab28903f57b27e1b16c2b08", + "sha256:ebe11c0d7a805086e99506aa331612429a72ca7cd52a1f0d277dc4adc20cb10e" ], "markers": "python_version >= '3.7'", - "version": "==3.1.1" + "version": "==3.2.0" }, "pycodestyle": { "hashes": [ diff --git a/src/test/regress/after_citus_upgrade_coord_schedule b/src/test/regress/after_citus_upgrade_coord_schedule index 6a2a5255a..f4f6bb29f 100644 --- a/src/test/regress/after_citus_upgrade_coord_schedule +++ b/src/test/regress/after_citus_upgrade_coord_schedule @@ -3,7 +3,4 @@ test: upgrade_citus_finish_citus_upgrade test: upgrade_pg_dist_cleanup_after test: upgrade_basic_after -test: upgrade_partition_constraints_after -test: upgrade_pg_dist_object_test_after -test: upgrade_columnar_metapage_after test: upgrade_post_11_after diff --git a/src/test/regress/after_pg_upgrade_schedule b/src/test/regress/after_pg_upgrade_schedule index 6b8c3973f..b47763bdb 100644 --- a/src/test/regress/after_pg_upgrade_schedule +++ b/src/test/regress/after_pg_upgrade_schedule @@ -1,4 +1,10 @@ -test: upgrade_basic_after upgrade_type_after upgrade_ref2ref_after upgrade_distributed_function_after upgrade_rebalance_strategy_after upgrade_list_citus_objects upgrade_autoconverted_after upgrade_citus_stat_activity upgrade_citus_locks upgrade_distributed_triggers_after +test: upgrade_basic_after upgrade_ref2ref_after upgrade_type_after upgrade_distributed_function_after upgrade_rebalance_strategy_after upgrade_list_citus_objects upgrade_autoconverted_after upgrade_citus_stat_activity upgrade_citus_locks upgrade_single_shard_table_after upgrade_schema_based_sharding_after + +# This test cannot be run with run_test.py currently due to its dependence on +# the specific PG versions that we use to run upgrade tests. For now we leave +# it out of the parallel line, so that flaky test detection can at least work +# for the other tests. +test: upgrade_distributed_triggers_after # This attempts dropping citus extension (and rollbacks), so please do # not run in parallel with any other tests. diff --git a/src/test/regress/before_citus_upgrade_coord_schedule b/src/test/regress/before_citus_upgrade_coord_schedule index 0e0eaa091..1195058d6 100644 --- a/src/test/regress/before_citus_upgrade_coord_schedule +++ b/src/test/regress/before_citus_upgrade_coord_schedule @@ -1,8 +1,5 @@ # this schedule is to be run on only coordinators test: upgrade_basic_before -test: upgrade_partition_constraints_before -test: upgrade_pg_dist_object_test_before -test: upgrade_columnar_metapage_before test: upgrade_pg_dist_cleanup_before test: upgrade_post_11_before diff --git a/src/test/regress/before_pg_upgrade_schedule b/src/test/regress/before_pg_upgrade_schedule index 671d6fa6f..05810d3d5 100644 --- a/src/test/regress/before_pg_upgrade_schedule +++ b/src/test/regress/before_pg_upgrade_schedule @@ -5,7 +5,7 @@ test: upgrade_basic_before test: upgrade_ref2ref_before test: upgrade_type_before test: upgrade_distributed_function_before upgrade_rebalance_strategy_before -test: upgrade_autoconverted_before +test: upgrade_autoconverted_before upgrade_single_shard_table_before upgrade_schema_based_sharding_before test: upgrade_citus_stat_activity test: upgrade_citus_locks test: upgrade_distributed_triggers_before diff --git a/src/test/regress/bin/normalize.sed b/src/test/regress/bin/normalize.sed index 65692e1c9..a374c73f5 100644 --- a/src/test/regress/bin/normalize.sed +++ b/src/test/regress/bin/normalize.sed @@ -11,6 +11,7 @@ s/localhost:[0-9]+/localhost:xxxxx/g s/ port=[0-9]+ / port=xxxxx /g s/placement [0-9]+/placement xxxxx/g s/shard [0-9]+/shard xxxxx/g +s/Shard [0-9]+/Shard xxxxx/g s/assigned task [0-9]+ to node/assigned task to node/ s/node group [12] (but|does)/node group \1/ @@ -97,34 +98,7 @@ s/of relation ".*" violates not-null constraint/violates not-null constraint/g s/partition ".*" would be violated by some row/partition would be violated by some row/g s/of relation ".*" contains null values/contains null values/g -#if (PG_VERSION_NUM >= PG_VERSION_13) && (PG_VERSION_NUM < PG_VERSION_14) -# (This is not preprocessor directive, but a reminder for the developer that will drop PG13 support ) -# libpq message changes for minor versions of pg13 - -# We ignore multiline error messages, and substitute first line with a single line -# alternative that is used in some older libpq versions. -s/(ERROR: |WARNING: |error:) server closed the connection unexpectedly/\1 connection not open/g -/^\s*This probably means the server terminated abnormally$/d -/^\s*before or while processing the request.$/d -/^\s*connection not open$/d - -s/ERROR: fake_fetch_row_version not implemented/ERROR: fake_tuple_update not implemented/g -s/ERROR: COMMIT is not allowed in an SQL function/ERROR: COMMIT is not allowed in a SQL function/g -s/ERROR: ROLLBACK is not allowed in an SQL function/ERROR: ROLLBACK is not allowed in a SQL function/g -/.*Async-Capable.*/d -/.*Async Capable.*/d -/Parent Relationship/d -/Parent-Relationship/d -s/function array_cat_agg\(anyarray\) anyarray/function array_cat_agg\(anycompatiblearray\) anycompatiblearray/g -s/function array_cat_agg\(anyarray\)/function array_cat_agg\(anycompatiblearray\)/g -s/TRIM\(BOTH FROM value\)/btrim\(value\)/g -/DETAIL: Subqueries are not supported in policies on distributed tables/d -s/ERROR: unexpected non-SELECT command in SubLink/ERROR: cannot create policy/g - -# PG13 changes bgworker sigterm message, we can drop that line with PG13 drop -s/(FATAL: terminating).*Citus Background Task Queue Executor.*(due to administrator command)\+/\1 connection \2 \+/g - -#endif /* (PG_VERSION_NUM >= PG_VERSION_13) && (PG_VERSION_NUM < PG_VERSION_14) */ +s/(Citus Background Task Queue Executor: regression\/postgres for \()[0-9]+\/[0-9]+\)/\1xxxxx\/xxxxx\)/g # Changed outputs after minor bump to PG14.5 and PG13.8 s/(ERROR: |WARNING: |error:) invalid socket/\1 connection not open/g @@ -134,9 +108,18 @@ s/(ERROR: |WARNING: |error:) invalid socket/\1 connection not open/g # pg15 changes # can be removed when dropping PG13&14 support +#if (PG_VERSION_NUM >= PG_VERSION_14) && (PG_VERSION_NUM < PG_VERSION_15) +# (This is not preprocessor directive, but a reminder for the developer that will drop PG14 support ) s/is not a PostgreSQL server process/is not a PostgreSQL backend process/g s/ AS "\?column\?"//g s/".*\.(.*)": (found .* removable)/"\1": \2/g +# We ignore multiline error messages, and substitute first line with a single line +# alternative that is used in some older libpq versions. +s/(ERROR: |WARNING: |error:) server closed the connection unexpectedly/\1 connection not open/g +/^\s*This probably means the server terminated abnormally$/d +/^\s*before or while processing the request.$/d +/^\s*connection not open$/d +#endif /* (PG_VERSION_NUM >= PG_VERSION_13) && (PG_VERSION_NUM < PG_VERSION_14) */ # intermediate_results s/(ERROR.*)pgsql_job_cache\/([0-9]+_[0-9]+_[0-9]+)\/(.*).data/\1pgsql_job_cache\/xx_x_xxx\/\3.data/g @@ -158,6 +141,8 @@ s/Subplan [0-9]+\_/Subplan XXX\_/g # Plan numbers in insert select s/read_intermediate_result\('insert_select_[0-9]+_/read_intermediate_result('insert_select_XXX_/g +# Plan numbers in merge into +s/read_intermediate_result\('merge_into_[0-9]+_/read_intermediate_result('merge_into_XXX_/g # ignore job id in repartitioned insert/select s/repartitioned_results_[0-9]+/repartitioned_results_xxxxx/g @@ -210,9 +195,6 @@ s/ERROR: cannot append to shardId [0-9]+/ERROR: cannot append to shardId xxxxx /local tables that are added to metadata automatically by citus, but not chained with reference tables via foreign keys might be automatically converted back to postgres tables$/d /Executing citus_add_local_table_to_metadata(.*) prevents this for the given relation, and all of the connected relations$/d -# normalize partitioned table shard constraint name errors for upgrade_partition_constraints_(before|after) -s/^(ERROR: child table is missing constraint "\w+)_([0-9])+"/\1_xxxxxx"/g - # normalize for distributed deadlock delay in isolation_metadata_sync_deadlock # isolation tester first detects a lock, but then deadlock detector cancels the # session. Sometimes happens that deadlock detector cancels the session before @@ -257,7 +239,7 @@ s/pg_cancel_backend\('[0-9]+'::bigint\)/pg_cancel_backend('xxxxx'::bigint)/g s/issuing SELECT pg_cancel_backend\([0-9]+::integer\)/issuing SELECT pg_cancel_backend(xxxxx::integer)/g # shard_rebalancer output for flaky nodeIds -s/issuing SELECT citus_copy_shard_placement\(43[0-9]+,[0-9]+,[0-9]+,'block_writes'\)/issuing SELECT citus_copy_shard_placement(43xxxx,xx,xx,'block_writes')/g +s/issuing SELECT pg_catalog.citus_copy_shard_placement\(43[0-9]+,[0-9]+,[0-9]+,'block_writes'\)/issuing SELECT pg_catalog.citus_copy_shard_placement(43xxxx,xx,xx,'block_writes')/g # node id in run_command_on_all_nodes warning s/Error on node with node id [0-9]+/Error on node with node id xxxxx/g @@ -308,4 +290,7 @@ s/(NOTICE: issuing SET LOCAL application_name TO 'citus_rebalancer gpid=)[0-9]+ # shard_rebalancer output, flaky improvement number s/improvement of 0.1[0-9]* is lower/improvement of 0.1xxxxx is lower/g # normalize tenants statistics annotations -s/\/\*\{"tId":.*\*\///g +s/\/\*\{"cId":.*\*\///g + +# Notice message that contains current columnar version that makes it harder to bump versions +s/(NOTICE: issuing CREATE EXTENSION IF NOT EXISTS citus_columnar WITH SCHEMA pg_catalog VERSION )"[0-9]+\.[0-9]+-[0-9]+"/\1 "x.y-z"/ diff --git a/src/test/regress/citus_tests/arbitrary_configs/citus_arbitrary_configs.py b/src/test/regress/citus_tests/arbitrary_configs/citus_arbitrary_configs.py index 6c9863434..52924aa11 100755 --- a/src/test/regress/citus_tests/arbitrary_configs/citus_arbitrary_configs.py +++ b/src/test/regress/citus_tests/arbitrary_configs/citus_arbitrary_configs.py @@ -76,6 +76,19 @@ def run_for_config(config, lock, sql_schedule_name): cfg.SUPER_USER_NAME, ) common.save_regression_diff("postgres", config.output_dir) + elif config.all_null_dist_key: + exitCode |= common.run_pg_regress_without_exit( + config.bindir, + config.pg_srcdir, + config.coordinator_port(), + cfg.SINGLE_SHARD_PREP_SCHEDULE, + config.output_dir, + config.input_dir, + cfg.SUPER_USER_NAME, + ) + common.save_regression_diff( + "single_shard_table_prep_regression", config.output_dir + ) exitCode |= _run_pg_regress_on_port( config, config.coordinator_port(), cfg.CREATE_SCHEDULE diff --git a/src/test/regress/citus_tests/common.py b/src/test/regress/citus_tests/common.py index d751bad5a..9ff30ebf9 100644 --- a/src/test/regress/citus_tests/common.py +++ b/src/test/regress/citus_tests/common.py @@ -25,6 +25,9 @@ import utils from psycopg import sql from utils import USER +# This SQL returns true ( 't' ) if the Citus version >= 11.0. +IS_CITUS_VERSION_11_SQL = "SELECT (split_part(extversion, '.', 1)::int >= 11) as is_11 FROM pg_extension WHERE extname = 'citus';" + LINUX = False MACOS = False FREEBSD = False @@ -45,6 +48,53 @@ TIMEOUT_DEFAULT = timedelta(seconds=int(os.getenv("PG_TEST_TIMEOUT_DEFAULT", "10 FORCE_PORTS = os.getenv("PG_FORCE_PORTS", "NO").lower() not in ("no", "0", "n", "") REGRESS_DIR = pathlib.Path(os.path.realpath(__file__)).parent.parent +REPO_ROOT = REGRESS_DIR.parent.parent.parent +CI = os.environ.get("CI") == "true" + + +def eprint(*args, **kwargs): + """eprint prints to stderr""" + + print(*args, file=sys.stderr, **kwargs) + + +def run(command, *args, check=True, shell=True, silent=False, **kwargs): + """run runs the given command and prints it to stderr""" + + if not silent: + eprint(f"+ {command} ") + if silent: + kwargs.setdefault("stdout", subprocess.DEVNULL) + return subprocess.run(command, *args, check=check, shell=shell, **kwargs) + + +def capture(command, *args, **kwargs): + """runs the given command and returns its output as a string""" + return run(command, *args, stdout=subprocess.PIPE, text=True, **kwargs).stdout + + +PG_CONFIG = os.environ.get("PG_CONFIG", "pg_config") +PG_BINDIR = capture([PG_CONFIG, "--bindir"], shell=False).rstrip() +os.environ["PATH"] = PG_BINDIR + os.pathsep + os.environ["PATH"] + + +def get_pg_major_version(): + full_version_string = run( + "initdb --version", stdout=subprocess.PIPE, encoding="utf-8", silent=True + ).stdout + major_version_string = re.search("[0-9]+", full_version_string) + assert major_version_string is not None + return int(major_version_string.group(0)) + + +PG_MAJOR_VERSION = get_pg_major_version() + +OLDEST_SUPPORTED_CITUS_VERSION_MATRIX = { + 14: "10.2.0", + 15: "11.1.5", +} + +OLDEST_SUPPORTED_CITUS_VERSION = OLDEST_SUPPORTED_CITUS_VERSION_MATRIX[PG_MAJOR_VERSION] def initialize_temp_dir(temp_dir): @@ -272,9 +322,7 @@ def stop_metadata_to_workers(pg_path, worker_ports, coordinator_port): def add_coordinator_to_metadata(pg_path, coordinator_port): - command = "SELECT citus_add_node('localhost', {}, groupId := 0)".format( - coordinator_port - ) + command = "SELECT citus_set_coordinator_host('localhost');" utils.psql(pg_path, coordinator_port, command) @@ -327,6 +375,10 @@ def stop_databases( stop(node_name) +def is_citus_set_coordinator_host_udf_exist(pg_path, port): + return utils.psql_capture(pg_path, port, IS_CITUS_VERSION_11_SQL) == b" t\n\n" + + def initialize_citus_cluster(bindir, datadir, settings, config): # In case there was a leftover from previous runs, stop the databases stop_databases( @@ -339,35 +391,19 @@ def initialize_citus_cluster(bindir, datadir, settings, config): bindir, datadir, config.node_name_to_ports, config.name, config.env_variables ) create_citus_extension(bindir, config.node_name_to_ports.values()) + + # In upgrade tests, it is possible that Citus version < 11.0 + # where the citus_set_coordinator_host UDF does not exist. + if is_citus_set_coordinator_host_udf_exist(bindir, config.coordinator_port()): + add_coordinator_to_metadata(bindir, config.coordinator_port()) + add_workers(bindir, config.worker_ports, config.coordinator_port()) if not config.is_mx: stop_metadata_to_workers(bindir, config.worker_ports, config.coordinator_port()) - if config.add_coordinator_to_metadata: - add_coordinator_to_metadata(bindir, config.coordinator_port()) + config.setup_steps() -def eprint(*args, **kwargs): - """eprint prints to stderr""" - - print(*args, file=sys.stderr, **kwargs) - - -def run(command, *args, check=True, shell=True, silent=False, **kwargs): - """run runs the given command and prints it to stderr""" - - if not silent: - eprint(f"+ {command} ") - if silent: - kwargs.setdefault("stdout", subprocess.DEVNULL) - return subprocess.run(command, *args, check=check, shell=shell, **kwargs) - - -def capture(command, *args, **kwargs): - """runs the given command and returns its output as a string""" - return run(command, *args, stdout=subprocess.PIPE, text=True, **kwargs).stdout - - def sudo(command, *args, shell=True, **kwargs): """ A version of run that prefixes the command with sudo when the process is diff --git a/src/test/regress/citus_tests/config.py b/src/test/regress/citus_tests/config.py index f25f0a477..560806962 100644 --- a/src/test/regress/citus_tests/config.py +++ b/src/test/regress/citus_tests/config.py @@ -15,11 +15,14 @@ WORKER2 = "worker2" REGULAR_USER_NAME = "regularuser" SUPER_USER_NAME = "postgres" +DATABASE_NAME = "postgres" + ARBITRARY_SCHEDULE_NAMES = [ "create_schedule", "sql_schedule", "sql_base_schedule", "postgres_schedule", + "single_shard_table_prep_schedule", ] BEFORE_PG_UPGRADE_SCHEDULE = "./before_pg_upgrade_schedule" @@ -27,6 +30,7 @@ AFTER_PG_UPGRADE_SCHEDULE = "./after_pg_upgrade_schedule" CREATE_SCHEDULE = "./create_schedule" POSTGRES_SCHEDULE = "./postgres_schedule" +SINGLE_SHARD_PREP_SCHEDULE = "./single_shard_table_prep_schedule" SQL_SCHEDULE = "./sql_schedule" SQL_BASE_SCHEDULE = "./sql_base_schedule" @@ -96,8 +100,10 @@ class CitusBaseClusterConfig(object, metaclass=NewInitCaller): self.temp_dir = CITUS_ARBITRARY_TEST_DIR self.worker_amount = 2 self.user = REGULAR_USER_NAME + self.dbname = DATABASE_NAME self.is_mx = True self.is_citus = True + self.all_null_dist_key = False self.name = type(self).__name__ self.settings = { "shared_preload_libraries": "citus", @@ -110,7 +116,6 @@ class CitusBaseClusterConfig(object, metaclass=NewInitCaller): "max_connections": 1200, } self.new_settings = {} - self.add_coordinator_to_metadata = False self.env_variables = {} self.skip_tests = [] @@ -166,7 +171,6 @@ class CitusDefaultClusterConfig(CitusBaseClusterConfig): "citus.use_citus_managed_tables": True, } self.settings.update(new_settings) - self.add_coordinator_to_metadata = True self.skip_tests = [ # Alter Table statement cannot be run from an arbitrary node so this test will fail "arbitrary_configs_alter_table_add_constraint_without_name_create", @@ -175,10 +179,10 @@ class CitusDefaultClusterConfig(CitusBaseClusterConfig): class CitusUpgradeConfig(CitusBaseClusterConfig): - def __init__(self, arguments): + def __init__(self, arguments, pre_tar, post_tar): super().__init__(arguments) - self.pre_tar_path = arguments["--citus-pre-tar"] - self.post_tar_path = arguments["--citus-post-tar"] + self.pre_tar_path = pre_tar + self.post_tar_path = post_tar self.temp_dir = "./tmp_citus_upgrade" self.new_settings = {"citus.enable_version_checks": "false"} self.user = SUPER_USER_NAME @@ -202,6 +206,24 @@ class PostgresConfig(CitusDefaultClusterConfig): ] +class AllSingleShardTableDefaultConfig(CitusDefaultClusterConfig): + def __init__(self, arguments): + super().__init__(arguments) + self.all_null_dist_key = True + self.skip_tests += [ + # One of the distributed functions created in "function_create" + # requires setting a distribution column, which cannot be the + # case with single shard tables. + "function_create", + "functions", + # In "nested_execution", one of the tests that query + # "dist_query_single_shard" table acts differently when the table + # has a single shard. This is explained with a comment in the test. + "nested_execution", + "merge_arbitrary", + ] + + class CitusSingleNodeClusterConfig(CitusDefaultClusterConfig): def __init__(self, arguments): super().__init__(arguments) @@ -380,4 +402,3 @@ class PGUpgradeConfig(CitusBaseClusterConfig): self.old_datadir = self.temp_dir + "/oldData" self.new_datadir = self.temp_dir + "/newData" self.user = SUPER_USER_NAME - self.add_coordinator_to_metadata = True diff --git a/src/test/regress/citus_tests/query_generator/.gitignore b/src/test/regress/citus_tests/query_generator/.gitignore new file mode 100644 index 000000000..c397a501d --- /dev/null +++ b/src/test/regress/citus_tests/query_generator/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +*-env diff --git a/src/test/regress/citus_tests/query_generator/README.md b/src/test/regress/citus_tests/query_generator/README.md new file mode 100644 index 000000000..b35a96c0a --- /dev/null +++ b/src/test/regress/citus_tests/query_generator/README.md @@ -0,0 +1,225 @@ +## Goal of the Tool +Tool supports a simple syntax to be useful to generate queries with different join orders. Main motivation for me to create the tool was to compare results of the generated queries for different [Citus](https://github.com/citusdata/citus) tables and Postgres tables. That is why we support a basic syntax for now. It can be extended to support different queries. + +## Query Generator for Postgres +Tool generates SELECT queries, whose depth can be configured, with different join orders. It also generates DDLs required for query execution. +You can also tweak configuration parameters for data inserting command generation. + +## How to Run Citus Join Verification? +You can verify if Citus breaks any default PG join behaviour via `citus_compare_dist_local_joins.sh`. It creates +tables specified in config. Then, it runs generated queries on those tables and saves the results into `out/dist_queries.out`. +After running those queries for Citus tables, it creates PG tables with the same names as previous run, executes the same +queries, and saves the results into `out/local_queries.out`. In final step, it generates diff between local and distributed results. +You can see the contents of `out/local_dist_diffs` to see if there is any Citus unsupported query. + +1. Create a Citus local cluster with 2 workers by using [citus_dev](https://github.com/citusdata/tools/tree/develop/citus_dev) tool +(Note: make sure you do not configure psql via .psqlrc file as it would fail the test.) +```bash +citus_dev make testCluster --destroy +``` +2. Run the test, +```bash +cd src/test/regress/citus_tests/query_generator/bin +bash citus_compare_dist_local_joins.sh Optional: +``` +3. See the diff content in `src/test/regress/citus_tests/query_generator/out/local_dist_diffs` + +Note: `seed` can be used to reproduce a run of Citus test by generating the same queries and results via the given seed. + +### Configuration +You can configure 3 different parts: + +- [DDL Configuration](#ddl-configuration) +- [Data Insertion Configuration](#data-insertion-configuration) +- [Query Configuration](#query-configuration) + +## DDL Configuration +Tool generates related ddl commands before generating queries. + +Schema for DDL configuration: +```yaml +ddlOutFile: +commonColName: +targetTables: + - Table: + name: + citusType: + maxAllowedUseOnQuery: + rowCount: + nullRate: + duplicateRate: + columns: + - Column: + name: + type: + distinctCopyCount: +``` + +Explanation: +```yaml +ddlOutFile: "file to write generated DDL commands" +commonColName: "name of the column that will be used as distribution column, filter column in restrictions and target column in selections" +targetTables: "array of tables that will be used in generated queries" + - Table: + name: "name prefix of table" + citusType: "citus type of table" + maxAllowedUseOnQuery: "limits how many times table can appear in query" + rowCount: "total # of rows that will be inserted into table" + nullRate: "percentage of null rows in rowCount that will be inserted into table" + duplicateRate: "percentage of duplicates in rowCount that will be inserted into table" + columns: "array of columns in table" + - Column: + name: "name of column" + type: "name of data type of column(only support 'int' now)" + distinctCopyCount: "how many tables with the same configuration we should create(only by changing full name, still using the same name prefix)" +``` + + +## Data Insertion Configuration +Tool generates data insertion commands if you want tables with filled data. You can configure total number of rows, what percentage of them should +be null and what percentage of them should be duplicated. For related configuration see Table schema at [DDL Configuration](#ddl-configuration). You +can also configure range of the randomly generated data. See `dataRange` at [Query Configuration](#query-configuration). + +## Query Configuration +After generation of ddls and data insertion commands, the tool generates queries. + +Schema for Query configuration: +```yaml +queryCount: +queryOutFile: +semiAntiJoin: +cartesianProduct: +limit: +orderby: +forceOrderbyWithLimit: +useAvgAtTopLevelTarget: +dataRange: + from: + to: +filterRange: + from: + to: +limitRange: + from: + to: +targetRteCount: +targetCteCount: +targetCteRteCount: +targetJoinTypes: +targetRteTypes: +targetRestrictOps: +``` + +Explanation: +```yaml +queryCount: "number of queries to generate" +queryOutFile: "file to write generated queries" +semiAntiJoin: "should we support semi joins (WHERE col IN (Subquery))" +cartesianProduct: "should we support cartesian joins" +limit: "should we support limit clause" +orderby: "should we support order by clause" +forceOrderbyWithLimit: "should we force order by when we use limit" +useAvgAtTopLevelTarget: "should we make top level query as select avg() from (subquery)" +dataRange: + from: "starting boundary for data generation" + to: "end boundary for data generation" +filterRange: + from: "starting boundary for restriction clause" + to: "end boundary for restriction clause" +limitRange: + from: "starting boundary for limit clause" + to: "end boundary for data limit clause" +targetRteCount: "limits how many rtes should exist in non-cte part of the query" +targetCteCount: "limits how many ctes should exist in query" +targetCteRteCount: "limits how many rtes should exist in cte part of the query" +targetJoinTypes: "supported join types" +targetRteTypes: "supported rte types" +targetRestrictOps: "supported restrict ops" +``` + +## Misc Configuration +Tool has some configuration options which does not suit above 3 parts. + +Schema for misc configuration: +```yaml +interactiveMode: +``` + +Explanation: +```yaml +interactiveMode: "when true, interactively prints generated ddls and queries. Otherwise, it writes them to configured files." +``` + +## Supported Operations +It uses `commonColName` for any kind of target selection required for any supported query clause. + +### Column Type Support +Tool currently supports only int data type, but plans to support other basic types. + +### Join Support +Tool supports following joins: +```yaml +targetJoinTypes: + - INNER + - LEFT + - RIGHT + - FULL +``` + +### Citus Table Support +Tool supports following citus table types: +```yaml +targetTables: + - Table: + ... + citusType: + ... +``` + +### Restrict Operation Support +Tool supports following restrict operations: +```yaml +targetRestrictOps: + - LT + - GT + - EQ +``` + +### Rte Support +Tool supports following rtes: +```yaml +targetRteTypes: + - RELATION + - SUBQUERY + - CTE + - VALUES +``` + +## How to Generate Queries? +You have 2 different options. + +- [Interactive Mode](#interactive-mode) +- [File Mode](#file-mode) + +### Interactive Mode +In this mode, you will be prompted to continue generating a query. When you hit to `Enter`, it will continue generating them. +You will need to hit to `x` to exit. + +1. Configure `interactiveMode: true` in config.yml, +2. Run the command shown below +```bash +cd src/test/regress/citus_tests/query_generator +python generate_queries.py +``` + +### File Mode +In this mode, generated ddls and queries will be written into the files configured in [config.yml](./config/config.yaml). + +1. Configure `interactiveMode: false`, +2. Configure `queryCount: `, +3. Configure `queryOutFile: ` and `ddlOutFile: ` +4. Run the command shown below +```bash +cd src/test/regress/citus_tests/query_generator +python generate_queries.py +``` diff --git a/src/test/regress/citus_tests/query_generator/bin/citus_compare_dist_local_joins.sh b/src/test/regress/citus_tests/query_generator/bin/citus_compare_dist_local_joins.sh new file mode 100644 index 000000000..c5a97b80c --- /dev/null +++ b/src/test/regress/citus_tests/query_generator/bin/citus_compare_dist_local_joins.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# make bash behave +set -euo pipefail + +psql_user=$1 +psql_db=$2 +psql_port=$3 +seed=${4:-""} + +runDDLs() +{ + # run ddls + psql -U "${psql_user}" -d "${psql_db}" -p "${psql_port}" -f "${out_folder}"/ddls.sql > /dev/null +} + +runUndistributeTables() +{ + undistribute_all_tables_command='SELECT undistribute_table(logicalrelid) FROM pg_dist_partition;' + # run undistribute all tables + psql -U "${psql_user}" -d "${psql_db}" -p "${psql_port}" -c "${undistribute_all_tables_command}" > /dev/null +} + +runQueries() +{ + out_filename=$1 + + # run dmls + # echo queries and comments for query tracing + psql -U "${psql_user}" -d "${psql_db}" -p "${psql_port}" \ + --echo-all \ + -f "${out_folder}"/queries.sql > "${out_filename}" 2>&1 +} + +showDiffs() +{ + pushd . && cd "${script_folder}" && python3 diff-checker.py && popd +} + +# run query generator and let it create output ddls and queries +script_folder=$(dirname "$0") +out_folder="${script_folder}"/../out +pushd . && cd "${script_folder}"/.. && python3 generate_queries.py --seed="${seed}" && popd + +# remove result files if exists +rm -rf "${out_folder}"/dist_queries.out "${out_folder}"/local_queries.out + +# run ddls +runDDLs + +# runs dmls for distributed tables +runQueries "${out_folder}"/dist_queries.out + +# undistribute all dist tables +runUndistributeTables + +# runs the same dmls for pg local tables +runQueries "${out_folder}"/local_queries.out + +# see diffs in results +showDiffs diff --git a/src/test/regress/citus_tests/query_generator/bin/diff-checker.py b/src/test/regress/citus_tests/query_generator/bin/diff-checker.py new file mode 100644 index 000000000..5bd2898a9 --- /dev/null +++ b/src/test/regress/citus_tests/query_generator/bin/diff-checker.py @@ -0,0 +1,122 @@ +import os +import re +import subprocess +import sys + + +def createPatternForFailedQueryBlock(acceptableErrors): + totalAcceptableOrders = len(acceptableErrors) + + failedQueryBlockPattern = "-- queryId: [0-9]+\n.*\npsql:.*(?:" + for acceptableErrorIdx in range(totalAcceptableOrders): + failedQueryBlockPattern += acceptableErrors[acceptableErrorIdx] + if acceptableErrorIdx < totalAcceptableOrders - 1: + failedQueryBlockPattern += "|" + failedQueryBlockPattern += ")" + + return failedQueryBlockPattern + + +def findFailedQueriesFromFile(queryOutFile, acceptableErrors): + failedQueryIds = [] + outFileContent = "" + failedQueryBlockPattern = createPatternForFailedQueryBlock(acceptableErrors) + queryIdPattern = "queryId: ([0-9]+)" + with open(queryOutFile, "r") as f: + outFileContent = f.read() + failedQueryContents = re.findall(failedQueryBlockPattern, outFileContent) + failedQueryIds = [ + re.search(queryIdPattern, failedQueryContent)[1] + for failedQueryContent in failedQueryContents + ] + + return failedQueryIds + + +def removeFailedQueryOutputFromFile(outFile, failedQueryIds): + if len(failedQueryIds) == 0: + return + + distOutFileContentAsLines = [] + with open(outFile, "r") as f: + distOutFileContentAsLines = f.readlines() + + with open(outFile, "w") as f: + clear = False + nextIdx = 0 + nextQueryIdToDelete = failedQueryIds[nextIdx] + queryIdPattern = "queryId: ([0-9]+)" + + for line in distOutFileContentAsLines: + matched = re.search(queryIdPattern, line) + # founded line which contains query id + if matched: + # query id matches with the next failed query's id + if nextQueryIdToDelete == matched[1]: + # clear lines until we find succesfull query + clear = True + nextIdx += 1 + if nextIdx < len(failedQueryIds): + nextQueryIdToDelete = failedQueryIds[nextIdx] + else: + # we found successfull query + clear = False + + if not clear: + f.write(line) + return + + +def removeFailedQueryOutputFromFiles(distQueryOutFile, localQueryOutFile): + # remove the failed distributed query from both local and distributed query files to prevent diff for acceptable errors + # some of generated queries fails with below errors due to https://github.com/citusdata/citus/issues/6653, we skip those until we support them + acceptableErrors = [ + "ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns", + "ERROR: recursive complex joins are only supported when all distributed tables are co-located and joined on their distribution columns", + ] + failedDistQueryIds = findFailedQueriesFromFile(distQueryOutFile, acceptableErrors) + removeFailedQueryOutputFromFile(distQueryOutFile, failedDistQueryIds) + removeFailedQueryOutputFromFile(localQueryOutFile, failedDistQueryIds) + return + + +def showDiffs(distQueryOutFile, localQueryOutFile, diffFile): + exitCode = 1 + with open(diffFile, "w") as f: + diffCommand = "diff -u {} {}".format(localQueryOutFile, distQueryOutFile) + process = subprocess.Popen(diffCommand.split(), stdout=f, stderr=f, shell=False) + process.wait() + exitCode = process.returncode + + print("diff exit {}".format(exitCode)) + return exitCode + + +def exitIfAnyLocalQueryFailed(localQueryOutFile): + allErrors = ["ERROR:"] + failedLocalQueryIds = findFailedQueriesFromFile(localQueryOutFile, allErrors) + assert ( + len(failedLocalQueryIds) == 0 + ), """There might be an internal error related to query generator or + we might find a Postgres bug. Check local_queries.out to see the error.""" + return + + +if __name__ == "__main__": + scriptDirPath = os.path.dirname(os.path.abspath(__file__)) + outFolderPath = scriptDirPath + "/../out" + localQueryOutFile = "{}/local_queries.out".format(outFolderPath) + distQueryOutFile = "{}/dist_queries.out".format(outFolderPath) + diffFile = "{}/local_dist.diffs".format(outFolderPath) + + # exit if we have any error from local queries + exitIfAnyLocalQueryFailed(localQueryOutFile) + + # find failed queries from distQueryOutFile and then remove failed queries and their results from + # both distQueryOutFile and localQueryOutFile + removeFailedQueryOutputFromFiles(distQueryOutFile, localQueryOutFile) + + # show diffs in unified format + exitCode = showDiffs(distQueryOutFile, localQueryOutFile, diffFile) + + sys.exit(exitCode) diff --git a/src/test/regress/citus_tests/query_generator/bin/run_query_compare_test.py b/src/test/regress/citus_tests/query_generator/bin/run_query_compare_test.py new file mode 100755 index 000000000..dc2f8808c --- /dev/null +++ b/src/test/regress/citus_tests/query_generator/bin/run_query_compare_test.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +"""query_gen_test +Usage: + run_query_compare_test --bindir= --pgxsdir= --seed= + +Options: + --bindir= PostgreSQL executable directory(ex: '~/.pgenv/pgsql-10.4/bin') + --pgxsdir= Path to the PGXS directory(ex: ~/.pgenv/src/postgresql-11.3) + --seed= Seed number used by the query generator.(ex: 123) +""" + +import os +import subprocess +import sys + +from docopt import docopt + +# https://stackoverflow.com/questions/14132789/relative-imports-for-the-billionth-time/14132912#14132912 +sys.path.append( + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +) + + +# ignore E402 because these imports require addition to path +import common # noqa: E402 + +import config as cfg # noqa: E402 + + +def run_test(config, seed): + # start cluster + common.initialize_temp_dir(cfg.CITUS_ARBITRARY_TEST_DIR) + common.initialize_citus_cluster( + config.bindir, config.datadir, config.settings, config + ) + + # run test + scriptDirPath = os.path.dirname(os.path.abspath(__file__)) + testRunCommand = "bash {}/citus_compare_dist_local_joins.sh {} {} {} {}".format( + scriptDirPath, config.user, config.dbname, config.coordinator_port(), seed + ) + process = subprocess.Popen( + testRunCommand.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + stdout, stderr = process.communicate() + + # stop cluster + common.stop_databases( + config.bindir, config.datadir, config.node_name_to_ports, config.name + ) + + print(stdout) + print(stderr) + print(process.returncode) + sys.exit(process.returncode) + + +if __name__ == "__main__": + arguments = docopt(__doc__, version="run_query_compare_test") + citusClusterConfig = cfg.CitusSuperUserDefaultClusterConfig(arguments) + + seed = "" + if "--seed" in arguments and arguments["--seed"] != "": + seed = arguments["--seed"] + run_test(citusClusterConfig, seed) diff --git a/src/test/regress/citus_tests/query_generator/config/config.py b/src/test/regress/citus_tests/query_generator/config/config.py new file mode 100644 index 000000000..fec93ef19 --- /dev/null +++ b/src/test/regress/citus_tests/query_generator/config/config.py @@ -0,0 +1,132 @@ +import copy +import os + +import yaml +from config.config_parser import ( + parseJoinTypeArray, + parseRange, + parseRestrictOpArray, + parseRteTypeArray, + parseTableArray, +) +from node_defs import CitusType + + +class Config: + def __init__(self): + configObj = Config.parseConfigFile( + f"{os.path.dirname(os.path.abspath(__file__))}/config.yaml" + ) + + self.targetTables = _distinctCopyTables( + parseTableArray(configObj["targetTables"]) + ) + self.targetJoinTypes = parseJoinTypeArray(configObj["targetJoinTypes"]) + self.targetRteTypes = parseRteTypeArray(configObj["targetRteTypes"]) + self.targetRestrictOps = parseRestrictOpArray(configObj["targetRestrictOps"]) + self.commonColName = configObj["commonColName"] + self.targetRteCount = configObj["targetRteCount"] + self.targetCteCount = configObj["targetCteCount"] + self.targetCteRteCount = configObj["targetCteRteCount"] + self.semiAntiJoin = configObj["semiAntiJoin"] + self.cartesianProduct = configObj["cartesianProduct"] + self.limit = configObj["limit"] + self.orderby = configObj["orderby"] + self.forceOrderbyWithLimit = configObj["forceOrderbyWithLimit"] + self.useAvgAtTopLevelTarget = configObj["useAvgAtTopLevelTarget"] + self.interactiveMode = configObj["interactiveMode"] + self.queryOutFile = configObj["queryOutFile"] + self.ddlOutFile = configObj["ddlOutFile"] + self.queryCount = configObj["queryCount"] + self.dataRange = parseRange(configObj["dataRange"]) + self.filterRange = parseRange(configObj["filterRange"]) + self.limitRange = parseRange(configObj["limitRange"]) + self._ensureRteLimitsAreSane() + # print(self) + + def __repr__(self): + rep = "targetRteCount: {}\n".format(self.targetRteCount) + rep += "targetCteCount: {}\n".format(self.targetCteCount) + rep += "targetCteRteCount: {}\n".format(self.targetCteRteCount) + + rep += "targetRteTypes:\n" + for rteType in self.targetRteTypes: + rep += "\t{}\n".format(rteType) + + rep += "targetJoinTypes:\n" + for joinType in self.targetJoinTypes: + rep += "\t{}\n".format(joinType) + + rep += "restrictOps:\n" + for restrictOp in self.targetRestrictOps: + rep += "\t{}\n".format(restrictOp) + + return rep + + def _ensureRteLimitsAreSane(self): + totalAllowedUseForAllTables = 0 + for table in self.targetTables: + totalAllowedUseForAllTables += table.maxAllowedUseOnQuery + assert ( + totalAllowedUseForAllTables >= self.targetRteCount + ), """targetRteCount cannot be greater than sum of maxAllowedUseOnQuery for all tables. + Set targetRteCount to a lower value or increase maxAllowedUseOnQuery for tables at config.yml.""" + + @staticmethod + def parseConfigFile(path): + try: + with open(path, "r") as configFile: + return yaml.load(configFile, yaml.Loader) + except IOError as e: + print(f"I/O error({0}): {1}".format(e.errno, e.strerror)) + raise BaseException("cannot parse config.yaml") + except Exception: + print("Unexpected error while parsing config.yml.") + + +_config = None + + +def resetConfig(): + global _config + _config = Config() + + +def getConfig(): + return _config + + +def getAllTableNames(): + """returns table names from target tables given at config""" + tables = getConfig().targetTables + tableNames = [table.name for table in tables] + return tableNames + + +def getMaxAllowedCountForTable(tableName): + tables = getConfig().targetTables + filtered = filter(lambda el: el.name == tableName, tables) + filtered = list(filtered) + assert len(filtered) == 1 + return filtered[0].maxAllowedUseOnQuery + + +def isTableDistributed(table): + return table.citusType == CitusType.DISTRIBUTED + + +def isTableReference(table): + return table.citusType == CitusType.REFERENCE + + +def _distinctCopyTables(tables): + distinctCopyTables = [] + for table in tables: + distinctCopyCount = table.distinctCopyCount + for tblIdx in range(1, distinctCopyCount): + distinctCopyTable = copy.deepcopy(table) + distinctCopyTable.name += str(tblIdx) + distinctCopyTables.append(distinctCopyTable) + table.name += "0" + tables.extend(distinctCopyTables) + return tables diff --git a/src/test/regress/citus_tests/query_generator/config/config.yaml b/src/test/regress/citus_tests/query_generator/config/config.yaml new file mode 100644 index 000000000..1920966ee --- /dev/null +++ b/src/test/regress/citus_tests/query_generator/config/config.yaml @@ -0,0 +1,65 @@ +interactiveMode: false +queryCount: 250 +queryOutFile: queries.sql +ddlOutFile: ddls.sql +semiAntiJoin: true +cartesianProduct: false +limit: true +orderby: true +forceOrderbyWithLimit: true +useAvgAtTopLevelTarget: true +dataRange: + from: 0 + to: 10 +filterRange: + from: 0 + to: 10 +limitRange: + from: 0 + to: 10 +targetRteCount: 40 +targetCteCount: 3 +targetCteRteCount: 2 + +commonColName: id + +targetTables: + - Table: + name: dist + citusType: DISTRIBUTED + maxAllowedUseOnQuery: 10 + rowCount: 10 + nullRate: 0.1 + duplicateRate: 0.1 + columns: + - Column: + name: id + type: int + distinctCopyCount: 2 + - Table: + name: ref + citusType: REFERENCE + maxAllowedUseOnQuery: 10 + rowCount: 10 + nullRate: 0.1 + duplicateRate: 0.1 + columns: + - Column: + name: id + type: int + distinctCopyCount: 2 + +targetJoinTypes: + - INNER + - LEFT + - RIGHT + - FULL + +targetRteTypes: + - RELATION + - SUBQUERY + - CTE + +targetRestrictOps: + - LT + - GT diff --git a/src/test/regress/citus_tests/query_generator/config/config_parser.py b/src/test/regress/citus_tests/query_generator/config/config_parser.py new file mode 100755 index 000000000..0b4f3837f --- /dev/null +++ b/src/test/regress/citus_tests/query_generator/config/config_parser.py @@ -0,0 +1,81 @@ +from node_defs import CitusType, Column, JoinType, RestrictOp, RTEType, Table + + +def parseJoinType(joinTypeText): + return JoinType[joinTypeText] + + +def parseJoinTypeArray(joinTypeTexts): + joinTypes = [] + for joinTypeText in joinTypeTexts: + joinType = parseJoinType(joinTypeText) + joinTypes.append(joinType) + return joinTypes + + +def parseRteType(rteTypeText): + return RTEType[rteTypeText] + + +def parseRteTypeArray(rteTypeTexts): + rteTypes = [] + for rteTypeText in rteTypeTexts: + rteType = parseRteType(rteTypeText) + rteTypes.append(rteType) + return rteTypes + + +def parseRestrictOp(restrictOpText): + return RestrictOp[restrictOpText] + + +def parseRestrictOpArray(restrictOpTexts): + restrictOps = [] + for restrictOpText in restrictOpTexts: + restrictOp = parseRestrictOp(restrictOpText) + restrictOps.append(restrictOp) + return restrictOps + + +def parseTable(targetTableDict): + name = targetTableDict["name"] + citusType = CitusType[targetTableDict["citusType"]] + maxAllowedUseOnQuery = targetTableDict["maxAllowedUseOnQuery"] + rowCount = targetTableDict["rowCount"] + nullRate = targetTableDict["nullRate"] + duplicateRate = targetTableDict["duplicateRate"] + columns = [] + for columnDict in targetTableDict["columns"]: + col = parseColumn(columnDict) + columns.append(col) + distinctCopyCount = targetTableDict["distinctCopyCount"] + return Table( + name, + citusType, + maxAllowedUseOnQuery, + rowCount, + nullRate, + duplicateRate, + columns, + distinctCopyCount, + ) + + +def parseTableArray(targetTableDicts): + tables = [] + for targetTableDict in targetTableDicts: + table = parseTable(targetTableDict["Table"]) + tables.append(table) + return tables + + +def parseColumn(targetColumnDict): + name = targetColumnDict["name"] + type = targetColumnDict["type"] + return Column(name, type) + + +def parseRange(rangeDict): + fromVal = rangeDict["from"] + toVal = rangeDict["to"] + return (fromVal, toVal) diff --git a/src/test/regress/citus_tests/query_generator/data_gen.py b/src/test/regress/citus_tests/query_generator/data_gen.py new file mode 100644 index 000000000..96f7a1366 --- /dev/null +++ b/src/test/regress/citus_tests/query_generator/data_gen.py @@ -0,0 +1,82 @@ +from node_defs import CitusType + +from config.config import getConfig + + +def getTableData(): + dataGenerationSql = "" + + tableIdx = 1 + (fromVal, toVal) = getConfig().dataRange + tables = getConfig().targetTables + for table in tables: + # generate base rows + dataGenerationSql += _genOverlappingData(table.name, fromVal, table.rowCount) + dataGenerationSql += "\n" + dataGenerationSql += _genNonOverlappingData(table.name, toVal, tableIdx) + dataGenerationSql += "\n" + + # generate null rows + if not table.citusType == CitusType.DISTRIBUTED: + targetNullRows = int(table.rowCount * table.nullRate) + dataGenerationSql += _genNullData(table.name, targetNullRows) + dataGenerationSql += "\n" + + # generate duplicate rows + targetDuplicateRows = int(table.rowCount * table.duplicateRate) + dataGenerationSql += _genDupData(table.name, targetDuplicateRows) + dataGenerationSql += "\n\n" + tableIdx += 1 + return dataGenerationSql + + +def _genOverlappingData(tableName, startVal, rowCount): + """returns string to fill table with [startVal,startVal+rowCount] range of integers""" + dataGenerationSql = "" + dataGenerationSql += "INSERT INTO " + tableName + dataGenerationSql += ( + " SELECT i FROM generate_series(" + + str(startVal) + + "," + + str(startVal + rowCount) + + ") i;" + ) + return dataGenerationSql + + +def _genNullData(tableName, nullCount): + """returns string to fill table with NULLs""" + dataGenerationSql = "" + dataGenerationSql += "INSERT INTO " + tableName + dataGenerationSql += ( + " SELECT NULL FROM generate_series(0," + str(nullCount) + ") i;" + ) + return dataGenerationSql + + +def _genDupData(tableName, dupRowCount): + """returns string to fill table with duplicate integers which are fetched from given table""" + dataGenerationSql = "" + dataGenerationSql += "INSERT INTO " + tableName + dataGenerationSql += ( + " SELECT * FROM " + + tableName + + " ORDER BY " + + getConfig().commonColName + + " LIMIT " + + str(dupRowCount) + + ";" + ) + return dataGenerationSql + + +def _genNonOverlappingData(tableName, startVal, tableIdx): + """returns string to fill table with different integers for given table""" + startVal = startVal + tableIdx * 20 + endVal = startVal + 20 + dataGenerationSql = "" + dataGenerationSql += "INSERT INTO " + tableName + dataGenerationSql += ( + " SELECT i FROM generate_series(" + str(startVal) + "," + str(endVal) + ") i;" + ) + return dataGenerationSql diff --git a/src/test/regress/citus_tests/query_generator/ddl_gen.py b/src/test/regress/citus_tests/query_generator/ddl_gen.py new file mode 100755 index 000000000..b2f97f694 --- /dev/null +++ b/src/test/regress/citus_tests/query_generator/ddl_gen.py @@ -0,0 +1,48 @@ +from config.config import getConfig, isTableDistributed, isTableReference + + +def getTableDDLs(): + ddls = "" + tables = getConfig().targetTables + for table in tables: + ddls += _genTableDDL(table) + ddls += "\n" + return ddls + + +def _genTableDDL(table): + ddl = "" + ddl += "DROP TABLE IF EXISTS " + table.name + ";" + ddl += "\n" + + ddl += "CREATE TABLE " + table.name + "(" + for column in table.columns[:-1]: + ddl += _genColumnDDL(column) + ddl += ",\n" + if len(table.columns) > 0: + ddl += _genColumnDDL(table.columns[-1]) + ddl += ");\n" + + if isTableDistributed(table): + ddl += ( + "SELECT create_distributed_table(" + + "'" + + table.name + + "','" + + getConfig().commonColName + + "'" + + ");" + ) + ddl += "\n" + elif isTableReference(table): + ddl += "SELECT create_reference_table(" + "'" + table.name + "'" + ");" + ddl += "\n" + return ddl + + +def _genColumnDDL(column): + ddl = "" + ddl += column.name + ddl += " " + ddl += column.type + return ddl diff --git a/src/test/regress/citus_tests/query_generator/generate_queries.py b/src/test/regress/citus_tests/query_generator/generate_queries.py new file mode 100755 index 000000000..dd63b17ec --- /dev/null +++ b/src/test/regress/citus_tests/query_generator/generate_queries.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 + +"""generate_queries +Usage: + generate_queries --seed= + +Options: + --seed= Seed number used by the query generator.(ex: 123) +""" + +import os +import random +import signal +import sys + +from data_gen import getTableData +from ddl_gen import getTableDDLs +from docopt import docopt +from query_gen import newQuery +from random_selections import currentMilliSecs + +from config.config import getConfig, resetConfig + + +def _signal_handler(sig, frame): + sys.exit(0) + + +def _interactiveMode(ddls, data): + print(ddls) + print(data) + + while True: + res = input("Press x to exit or Enter to generate more") + if res.lower() == "x": + print("Exit from query generation mode!") + sys.exit(0) + + query = newQuery() + print(query) + + resetConfig() + + +def _fileMode(ddls, data): + ddlFileName = ( + f"{os.path.dirname(os.path.abspath(__file__))}/out/{getConfig().ddlOutFile}" + ) + with open(ddlFileName, "w") as ddlFile: + ddlFile.writelines([ddls, data]) + + queryCount = getConfig().queryCount + fileName = ( + f"{os.path.dirname(os.path.abspath(__file__))}/out/{getConfig().queryOutFile}" + ) + with open(fileName, "w") as f: + # enable repartition joins due to https://github.com/citusdata/citus/issues/6865 + enableRepartitionJoinCommand = "SET citus.enable_repartition_joins TO on;\n" + queryLines = [enableRepartitionJoinCommand] + queryId = 1 + for _ in range(queryCount): + query = newQuery() + + queryLine = "-- queryId: " + str(queryId) + "\n" + queryLine += query + "\n\n" + + queryLines.append(queryLine) + queryId += 1 + f.writelines(queryLines) + + +if __name__ == "__main__": + signal.signal(signal.SIGINT, _signal_handler) + + arguments = docopt(__doc__, version="generate_queries") + seed = -1 + if "--seed" in arguments and arguments["--seed"] != "": + seed = int(arguments["--seed"]) + else: + seed = currentMilliSecs() + assert seed > 0 + + random.seed(seed) + print(f"---SEED: {seed} ---") + + resetConfig() + + ddls = getTableDDLs() + data = getTableData() + + if getConfig().interactiveMode: + _interactiveMode(ddls, data) + else: + _fileMode(ddls, data) diff --git a/src/test/regress/citus_tests/query_generator/node_defs.py b/src/test/regress/citus_tests/query_generator/node_defs.py new file mode 100755 index 000000000..b0db1da63 --- /dev/null +++ b/src/test/regress/citus_tests/query_generator/node_defs.py @@ -0,0 +1,55 @@ +from enum import Enum + + +class JoinType(Enum): + INNER = 1 + LEFT = 2 + RIGHT = 3 + FULL = 4 + + +class RTEType(Enum): + RELATION = 1 + SUBQUERY = 2 + CTE = 3 + VALUES = 4 + + +class RestrictOp(Enum): + LT = 1 + GT = 2 + EQ = 3 + + +class CitusType(Enum): + DISTRIBUTED = 1 + REFERENCE = 2 + POSTGRES = 3 + + +class Table: + def __init__( + self, + name, + citusType, + maxAllowedUseOnQuery, + rowCount, + nullRate, + duplicateRate, + columns, + distinctCopyCount, + ): + self.name = name + self.citusType = citusType + self.maxAllowedUseOnQuery = maxAllowedUseOnQuery + self.rowCount = rowCount + self.nullRate = nullRate + self.duplicateRate = duplicateRate + self.columns = columns + self.distinctCopyCount = distinctCopyCount + + +class Column: + def __init__(self, name, type): + self.name = name + self.type = type diff --git a/src/test/regress/citus_tests/query_generator/out/.gitignore b/src/test/regress/citus_tests/query_generator/out/.gitignore new file mode 100644 index 000000000..d6b7ef32c --- /dev/null +++ b/src/test/regress/citus_tests/query_generator/out/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/src/test/regress/citus_tests/query_generator/query_gen.py b/src/test/regress/citus_tests/query_generator/query_gen.py new file mode 100644 index 000000000..e25525d29 --- /dev/null +++ b/src/test/regress/citus_tests/query_generator/query_gen.py @@ -0,0 +1,434 @@ +import random + +from node_defs import RTEType +from random_selections import ( + randomJoinOp, + randomRestrictOp, + randomRteType, + shouldSelectThatBranch, +) + +from config.config import getAllTableNames, getConfig, getMaxAllowedCountForTable + +# query_gen.py generates a new query from grammar rules below. It randomly chooses allowed rules +# to generate a query. Here are some important notes about the query generation: +# +# - We enforce the max allowed # of usage for each table. It also enforces global total # of tables. +# - Table names, restriction types and any other selections are chosen between the values provided by +# configuration file (config.yml). +# - Entry point for the generator is newQuery and all other methods are internal methods. We pass a context +# object named GeneratorContext to all internal methods as parameter to perform checks and generations +# on the query via the context. +# - shouldSelectThatBranch() is useful utility method to randomly chose a grammar rule. Some of the rules +# are only selected if we allowed them in configuration file. +# - We enforce table limits separately if we are inside cte part of the query(see targetCteRteCount). +# We also enforce max # of ctes for a query. +# - commonColName from the config is used at select and where clauses. +# - useAvgAtTopLevelTarget is useful to return single row as query result. It is also useful to track Citus +# query bugs via run_query_compare_test.py. +# - '=' restriction is removed from the config by default to return values different than null most of the time. +# - 'RTE.VALUES' is also removed from the config for the same reason as above. +# - Filter range is selected as same with data range for the same reason as above. +# - aliasStack at GeneratorContext is useful to put correct table names into where clause. + +# grammar syntax +# +# ======Assumptions====== +# 1. Tables has common dist col +# 2. All operations execute on common column for all tables. +# +# TODO: RTE_FUNCTION, RTE_TABLEFUNC +# +# ====SYNTAX==== +# ===Nonterminals=== +# Query +# SelectExpr +# FromExpr +# RestrictExpr +# RteList +# Rte +# SubqueryRte +# RelationRte +# JoinList +# JoinOp +# Using +# RestrictList +# Restrict +# CteRte +# CteList +# Cte +# ValuesRte +# Limit +# OrderBy +# +# ===Terminals=== +# e 'SELECT' 'FROM' 'INNER JOIN' 'LEFT JOIN' 'RIGHT JOIN' 'FULL JOIN' 'WHERE' 'LIMIT' 'USING' 'WITH' +# 'ORDER BY' 'VALUES' 'IN' 'NOT' 'AS' '<' '>' '=' '*' ',' ';' '(' ')' +# +# ===Rules=== +# Start -> Query ';' || 'WITH' CteList Query ';' +# Query -> SelectExpr FromExpr [OrderBy] [Limit] || 'SELECT' 'avg(avgsub.DistColName)' 'FROM' SubqueryRte 'AS avgsub' +# SelectExpr -> 'SELECT' 'curAlias()' '.' DistColName +# FromExpr -> 'FROM' (Rte JoinList JoinOp Rte Using || RteList) ['WHERE' 'nextRandomAlias()' '.' DistColName RestrictExpr] +# RestrictExpr -> ('<' || '>' || '=') Int || ['NOT'] 'IN' SubqueryRte +# JoinList -> JoinOp Rte Using JoinList || e +# Using -> 'USING' '(' DistColName ')' +# RteList -> Rte [, RteList] || Rte +# Rte -> SubqueryRte 'AS' 'nextRandomAlias()' || RelationRte 'AS' 'nextRandomAlias()' || +# CteRte 'AS' 'nextRandomAlias()' || ValuesRte 'AS' 'nextRandomAlias()' +# SubqueryRte -> '(' Query ')' +# RelationRte -> 'nextRandomTableName()' +# CteRte -> 'randomCteName()' +# CteList -> Cte [',' CteList] || Cte +# Cte -> 'nextRandomAlias()' 'AS' '(' Query ')' +# ValuesRte -> '(' 'VALUES' '(' 'random()' ')' ')' +# JoinOp -> 'INNER JOIN' || 'LEFT JOIN' || 'RIGHT JOIN' || 'FULL JOIN' +# Limit -> 'LIMIT' 'random()' +# OrderBy -> 'ORDER BY' DistColName +# DistColName -> 'hardwired(get from config)' + + +class GeneratorContext: + """context to store some variables which should be available during generation""" + + def __init__(self): + # each level's last table is used in WHERE clause for the level + self.aliasStack = [] + # tracks if we are inside cte as we should not refer cte inside cte + self.insideCte = False + # total rtes in cte + non-cte parts + self.totalRteCount = 0 + # rte count in non-cte part to enforce non-cte rte limit + self.currentRteCount = 0 + # cte count to enforce cte limit + self.currentCteCount = 0 + # rte count in cte part to enforce rte limit in cte + self.currentCteRteCount = 0 + # rte counts per table to enforce rte limit per table + self.perTableRtes = {} + # tables which hit count limit + self.disallowedTables = set() + # useful to track usage avg only at first select + self.usedAvg = False + + def randomCteName(self): + """returns a randomly selected cte name""" + randCteRef = random.randint(0, self.currentCteCount - 1) + return " cte_" + str(randCteRef) + + def curAlias(self): + """returns current alias name to be used for the current table""" + return " table_" + str(self.totalRteCount) + + def curCteAlias(self): + """returns current alias name to be used for the current cte""" + return " cte_" + str(self.currentCteCount) + + def hasAnyCte(self): + """returns if context has any cte""" + return self.currentCteCount > 0 + + def canGenerateNewRte(self): + """checks if context exceeds allowed rte count""" + return self.currentRteCount < getConfig().targetRteCount + + def canGenerateNewCte(self): + """checks if context exceeds allowed cte count""" + return self.currentCteCount < getConfig().targetCteCount + + def canGenerateNewRteInsideCte(self): + """checks if context exceeds allowed rte count inside a cte""" + return self.currentCteRteCount < getConfig().targetCteRteCount + + def addAlias(self, alias): + """adds given alias to the stack""" + self.aliasStack.append(alias) + + def removeLastAlias(self): + """removes the latest added alias from the stack""" + return self.aliasStack.pop() + + def getRteNameEnforcingRteLimits(self): + """returns rteName by enforcing rte count limits for tables""" + # do not enforce per table rte limit if we are inside cte + if self.insideCte: + rteName = random.choice(getAllTableNames()) + return " " + rteName + " " + + while True: + # keep trying to find random table by eliminating the ones which hit table limit + allowedNames = set(getAllTableNames()) - self.disallowedTables + assert len(allowedNames) > 0 + rteName = random.choice(list(allowedNames)) + + # not yet added to rte count map, so we can allow the name + if rteName not in self.perTableRtes: + self.perTableRtes[rteName] = 0 + break + # limit is not exceeded, so we can allow the name + if self.perTableRtes[rteName] < getMaxAllowedCountForTable(rteName): + break + else: + self.disallowedTables.add(rteName) + + # increment rte count for the table name + self.perTableRtes[rteName] += 1 + return " " + rteName + " " + + +def newQuery(): + """returns generated query""" + genCtx = GeneratorContext() + return _start(genCtx) + + +def _start(genCtx): + # Query ';' || 'WITH' CteList Query ';' + query = "" + if not genCtx.canGenerateNewCte() or shouldSelectThatBranch(): + query += _genQuery(genCtx) + else: + genCtx.insideCte = True + query += " WITH " + query += _genCteList(genCtx) + genCtx.insideCte = False + query += _genQuery(genCtx) + query += ";" + return query + + +def _genQuery(genCtx): + # SelectExpr FromExpr [OrderBy] [Limit] || 'SELECT' 'avg(avgsub.DistColName)' 'FROM' SubqueryRte 'AS avgsub' + query = "" + if ( + getConfig().useAvgAtTopLevelTarget + and not genCtx.insideCte + and not genCtx.usedAvg + ): + genCtx.usedAvg = True + query += "SELECT " + query += "count(*), avg(avgsub." + getConfig().commonColName + ") FROM " + query += _genSubqueryRte(genCtx) + query += " AS avgsub" + else: + query += _genSelectExpr(genCtx) + query += _genFromExpr(genCtx) + choseOrderby = False + if getConfig().orderby and shouldSelectThatBranch(): + query += _genOrderBy(genCtx) + choseOrderby = True + if getConfig().limit and shouldSelectThatBranch(): + if not choseOrderby and getConfig().forceOrderbyWithLimit: + query += _genOrderBy(genCtx) + query += _genLimit(genCtx) + return query + + +def _genOrderBy(genCtx): + # 'ORDER BY' DistColName + query = "" + query += " ORDER BY " + query += getConfig().commonColName + " " + return query + + +def _genLimit(genCtx): + # 'LIMIT' 'random()' + query = "" + query += " LIMIT " + (fromVal, toVal) = getConfig().limitRange + query += str(random.randint(fromVal, toVal)) + return query + + +def _genSelectExpr(genCtx): + # 'SELECT' 'curAlias()' '.' DistColName + query = "" + query += " SELECT " + commonColName = getConfig().commonColName + query += genCtx.curAlias() + "." + commonColName + " " + + return query + + +def _genFromExpr(genCtx): + # 'FROM' (Rte JoinList JoinOp Rte Using || RteList) ['WHERE' 'nextRandomAlias()' '.' DistColName RestrictExpr] + query = "" + query += " FROM " + + if shouldSelectThatBranch(): + query += _genRte(genCtx) + query += _genJoinList(genCtx) + query += randomJoinOp() + query += _genRte(genCtx) + query += _genUsing(genCtx) + else: + query += _genRteList(genCtx) + + alias = genCtx.removeLastAlias() + if shouldSelectThatBranch(): + query += " WHERE " + query += alias + "." + getConfig().commonColName + query += _genRestrictExpr(genCtx) + return query + + +def _genRestrictExpr(genCtx): + # ('<' || '>' || '=') Int || ['NOT'] 'IN' '(' SubqueryRte ')' + query = "" + if ( + not getConfig().semiAntiJoin + or not genCtx.canGenerateNewRte() + or shouldSelectThatBranch() + ): + query += randomRestrictOp() + (fromVal, toVal) = getConfig().filterRange + query += str(random.randint(fromVal, toVal)) + else: + if shouldSelectThatBranch(): + query += " NOT" + query += " IN " + query += _genSubqueryRte(genCtx) + return query + + +def _genCteList(genCtx): + # Cte [',' CteList] || Cte + query = "" + + if shouldSelectThatBranch(): + query += _genCte(genCtx) + if not genCtx.canGenerateNewCte(): + return query + query += "," + query += _genCteList(genCtx) + else: + query += _genCte(genCtx) + return query + + +def _genCte(genCtx): + # 'nextRandomAlias()' 'AS' '(' Query ')' + query = "" + query += genCtx.curCteAlias() + genCtx.currentCteCount += 1 + query += " AS " + query += " ( " + query += _genQuery(genCtx) + query += " ) " + return query + + +def _genRteList(genCtx): + # RteList -> Rte [, RteList] || Rte + query = "" + if getConfig().cartesianProduct and shouldSelectThatBranch(): + query += _genRte(genCtx) + if not genCtx.canGenerateNewRte(): + return query + if genCtx.insideCte and not genCtx.canGenerateNewRteInsideCte(): + return query + query += "," + query += _genRteList(genCtx) + else: + query += _genRte(genCtx) + return query + + +def _genJoinList(genCtx): + # JoinOp Rte Using JoinList || e + query = "" + + if shouldSelectThatBranch(): + if not genCtx.canGenerateNewRte(): + return query + if genCtx.insideCte and not genCtx.canGenerateNewRteInsideCte(): + return query + query += randomJoinOp() + query += _genRte(genCtx) + query += _genUsing(genCtx) + query += _genJoinList(genCtx) + return query + + +def _genUsing(genCtx): + # 'USING' '(' DistColName ')' + query = "" + query += " USING (" + getConfig().commonColName + " ) " + return query + + +def _genRte(genCtx): + # SubqueryRte as 'nextRandomAlias()' || RelationRte as 'curAlias()' || + # CteRte as 'curAlias()' || ValuesRte 'AS' 'nextRandomAlias()' + alias = genCtx.curAlias() + modifiedAlias = None + + if genCtx.insideCte: + genCtx.currentCteRteCount += 1 + else: + genCtx.currentRteCount += 1 + genCtx.totalRteCount += 1 + + # donot dive into recursive subquery further if we hit into rte limit, replace it with relation rte + rteType = randomRteType() + if not genCtx.canGenerateNewRte(): + rteType = RTEType.RELATION + + # donot dive into recursive subquery further if we hit into rte in cte limit, replace it with relation rte + if genCtx.insideCte and not genCtx.canGenerateNewRteInsideCte(): + rteType = RTEType.RELATION + + # we cannot refer to cte if we are inside it or we donot have any cte + if (genCtx.insideCte or not genCtx.hasAnyCte()) and rteType == RTEType.CTE: + rteType = RTEType.RELATION + + query = "" + if rteType == RTEType.SUBQUERY: + query += _genSubqueryRte(genCtx) + elif rteType == RTEType.RELATION: + query += _genRelationRte(genCtx) + elif rteType == RTEType.CTE: + query += _genCteRte(genCtx) + elif rteType == RTEType.VALUES: + query += _genValuesRte(genCtx) + modifiedAlias = alias + "(" + getConfig().commonColName + ") " + else: + raise BaseException("unknown RTE type") + + query += " AS " + query += alias if not modifiedAlias else modifiedAlias + genCtx.addAlias(alias) + + return query + + +def _genSubqueryRte(genCtx): + # '(' Query ')' + query = "" + query += " ( " + query += _genQuery(genCtx) + query += " ) " + return query + + +def _genRelationRte(genCtx): + # 'randomAllowedTableName()' + query = "" + query += genCtx.getRteNameEnforcingRteLimits() + return query + + +def _genCteRte(genCtx): + # 'randomCteName()' + query = "" + query += genCtx.randomCteName() + return query + + +def _genValuesRte(genCtx): + # '( VALUES(random()) )' + query = "" + (fromVal, toVal) = getConfig().dataRange + query += " ( VALUES(" + str(random.randint(fromVal, toVal)) + " ) ) " + return query diff --git a/src/test/regress/citus_tests/query_generator/random_selections.py b/src/test/regress/citus_tests/query_generator/random_selections.py new file mode 100644 index 000000000..ee32c620a --- /dev/null +++ b/src/test/regress/citus_tests/query_generator/random_selections.py @@ -0,0 +1,45 @@ +import random +import time + +from node_defs import RestrictOp + +from config.config import getConfig + + +def shouldSelectThatBranch(): + """returns 0 or 1 randomly""" + return random.randint(0, 1) + + +def currentMilliSecs(): + """returns total milliseconds since epoch""" + return round(time.time() * 1000) + + +def randomRteType(): + """returns a randomly selected RteType given at config""" + rtes = getConfig().targetRteTypes + return random.choice(rtes) + + +def randomJoinOp(): + """returns a randomly selected JoinOp given at config""" + joinTypes = getConfig().targetJoinTypes + return " " + random.choice(joinTypes).name + " JOIN" + + +def randomRestrictOp(): + """returns a randomly selected RestrictOp given at config""" + restrictOps = getConfig().targetRestrictOps + restrictOp = random.choice(restrictOps) + opText = "" + if restrictOp == RestrictOp.LT: + opText = "<" + elif restrictOp == RestrictOp.GT: + opText = ">" + elif restrictOp == RestrictOp.EQ: + opText = "=" + else: + raise BaseException("Unknown restrict op") + + return " " + opText + " " diff --git a/src/test/regress/citus_tests/run_test.py b/src/test/regress/citus_tests/run_test.py index a34a6e0a7..00d5638d9 100755 --- a/src/test/regress/citus_tests/run_test.py +++ b/src/test/regress/citus_tests/run_test.py @@ -13,9 +13,10 @@ from contextlib import contextmanager from typing import Optional import common -from common import REGRESS_DIR, capture, run +from common import OLDEST_SUPPORTED_CITUS_VERSION, PG_BINDIR, REGRESS_DIR, capture, run +from upgrade import generate_citus_tarball, run_citus_upgrade_tests -from config import ARBITRARY_SCHEDULE_NAMES, MASTER_VERSION, CitusDefaultClusterConfig +from config import ARBITRARY_SCHEDULE_NAMES, CitusBaseClusterConfig, CitusUpgradeConfig def main(): @@ -75,11 +76,19 @@ class TestDeps: schedule: Optional[str] direct_extra_tests: list[str] - def __init__(self, schedule, extra_tests=None, repeatable=True, worker_count=2): + def __init__( + self, + schedule, + extra_tests=None, + repeatable=True, + worker_count=2, + citus_upgrade_infra=False, + ): self.schedule = schedule self.direct_extra_tests = extra_tests or [] self.repeatable = repeatable self.worker_count = worker_count + self.citus_upgrade_infra = citus_upgrade_infra def extra_tests(self): all_deps = OrderedDict() @@ -96,13 +105,20 @@ DEPS = { "multi_cluster_management": TestDeps( None, ["multi_test_helpers_superuser"], repeatable=False ), + "minimal_cluster_management": TestDeps( + None, ["multi_test_helpers_superuser"], repeatable=False + ), "create_role_propagation": TestDeps(None, ["multi_cluster_management"]), "single_node_enterprise": TestDeps(None), "single_node": TestDeps(None), "single_node_truncate": TestDeps(None), + "multi_explain": TestDeps( + "base_schedule", ["multi_insert_select_non_pushable_queries"] + ), "multi_extension": TestDeps(None, repeatable=False), "multi_test_helpers": TestDeps(None), "multi_insert_select": TestDeps("base_schedule"), + "multi_partitioning": TestDeps("base_schedule"), "multi_mx_create_table": TestDeps( None, [ @@ -112,6 +128,17 @@ DEPS = { "multi_mx_function_table_reference", ], ), + "alter_distributed_table": TestDeps( + "minimal_schedule", ["multi_behavioral_analytics_create_table"] + ), + "background_rebalance": TestDeps( + None, + [ + "multi_test_helpers", + "multi_cluster_management", + ], + worker_count=3, + ), "background_rebalance_parallel": TestDeps( None, [ @@ -120,11 +147,27 @@ DEPS = { ], worker_count=6, ), + "function_propagation": TestDeps("minimal_schedule"), + "grant_on_foreign_server_propagation": TestDeps("minimal_schedule"), "multi_mx_modifying_xacts": TestDeps(None, ["multi_mx_create_table"]), "multi_mx_router_planner": TestDeps(None, ["multi_mx_create_table"]), "multi_mx_copy_data": TestDeps(None, ["multi_mx_create_table"]), "multi_mx_schema_support": TestDeps(None, ["multi_mx_copy_data"]), "multi_simple_queries": TestDeps("base_schedule"), + "create_single_shard_table": TestDeps("minimal_schedule"), + "isolation_extension_commands": TestDeps( + None, ["isolation_setup", "isolation_add_remove_node"] + ), + "schema_based_sharding": TestDeps("minimal_schedule"), + "multi_sequence_default": TestDeps( + None, + [ + "multi_test_helpers", + "multi_cluster_management", + "multi_table_ddl", + ], + ), + "grant_on_schema_propagation": TestDeps("minimal_schedule"), } @@ -151,31 +194,36 @@ def run_python_test(test_name, args): def run_regress_test(test_name, args): original_schedule, schedule_line = find_test_schedule_and_line(test_name, args) + print(f"SCHEDULE: {original_schedule}") + print(f"SCHEDULE_LINE: {schedule_line}") dependencies = test_dependencies(test_name, original_schedule, schedule_line, args) with tmp_schedule(test_name, dependencies, schedule_line, args) as schedule: if "upgrade" in original_schedule: - run_schedule_with_python(schedule) + run_schedule_with_python(test_name, schedule, dependencies) else: run_schedule_with_multiregress(test_name, schedule, dependencies, args) -def run_schedule_with_python(schedule): - bindir = capture("pg_config --bindir").rstrip() +def run_schedule_with_python(test_name, schedule, dependencies): pgxs_path = pathlib.Path(capture("pg_config --pgxs").rstrip()) os.chdir(REGRESS_DIR) os.environ["PATH"] = str(REGRESS_DIR / "bin") + os.pathsep + os.environ["PATH"] os.environ["PG_REGRESS_DIFF_OPTS"] = "-dU10 -w" - os.environ["CITUS_OLD_VERSION"] = f"v{MASTER_VERSION}.0" - args = { + fake_config_args = { "--pgxsdir": str(pgxs_path.parent.parent.parent), - "--bindir": bindir, + "--bindir": PG_BINDIR, + "--mixed": False, } - config = CitusDefaultClusterConfig(args) + if dependencies.citus_upgrade_infra: + run_single_citus_upgrade_test(test_name, schedule, fake_config_args) + return + + config = CitusBaseClusterConfig(fake_config_args) common.initialize_temp_dir(config.temp_dir) common.initialize_citus_cluster( config.bindir, config.datadir, config.settings, config @@ -185,13 +233,36 @@ def run_schedule_with_python(schedule): ) +def run_single_citus_upgrade_test(test_name, schedule, fake_config_args): + os.environ["CITUS_OLD_VERSION"] = f"v{OLDEST_SUPPORTED_CITUS_VERSION}" + citus_tarball_path = generate_citus_tarball(f"v{OLDEST_SUPPORTED_CITUS_VERSION}") + config = CitusUpgradeConfig(fake_config_args, citus_tarball_path, None) + + # Before tests are a simple case, because no actual upgrade is needed + if "_before" in test_name: + run_citus_upgrade_tests(config, schedule, None) + return + + before_schedule_name = f"{schedule}_before" + before_schedule_path = REGRESS_DIR / before_schedule_name + + before_test_name = test_name.replace("_after", "_before") + with open(before_schedule_path, "w") as before_schedule_file: + before_schedule_file.write(f"test: {before_test_name}\n") + + try: + run_citus_upgrade_tests(config, before_schedule_name, schedule) + finally: + os.remove(before_schedule_path) + + def run_schedule_with_multiregress(test_name, schedule, dependencies, args): worker_count = needed_worker_count(test_name, dependencies) # find suitable make recipe - if dependencies.schedule == "base_isolation_schedule": + if dependencies.schedule == "base_isolation_schedule" or "isolation" in test_name: make_recipe = "check-isolation-custom-schedule" - elif dependencies.schedule == "failure_base_schedule": + elif dependencies.schedule == "failure_base_schedule" or "failure" in test_name: make_recipe = "check-failure-custom-schedule" else: make_recipe = "check-custom-schedule" @@ -229,17 +300,8 @@ def default_base_schedule(test_schedule, args): if "operations" in test_schedule: return "minimal_schedule" - if "after_citus_upgrade" in test_schedule: - print( - f"WARNING: After citus upgrade schedule ({test_schedule}) is not supported." - ) - sys.exit(0) - - if "citus_upgrade" in test_schedule: - return None - if "pg_upgrade" in test_schedule: - return "minimal_schedule" + return "minimal_pg_upgrade_schedule" if test_schedule in ARBITRARY_SCHEDULE_NAMES: print(f"WARNING: Arbitrary config schedule ({test_schedule}) is not supported.") @@ -284,7 +346,7 @@ def get_test_name(args): def find_test_schedule_and_line(test_name, args): for schedule_file_path in sorted(REGRESS_DIR.glob("*_schedule")): for schedule_line in open(schedule_file_path, "r"): - if re.search(r"\b" + test_name + r"\b", schedule_line): + if re.search(r"^test:.*\b" + test_name + r"\b", schedule_line): test_schedule = pathlib.Path(schedule_file_path).stem if args["use_whole_schedule_line"]: return test_schedule, schedule_line @@ -296,11 +358,26 @@ def test_dependencies(test_name, test_schedule, schedule_line, args): if test_name in DEPS: return DEPS[test_name] + if "citus_upgrade" in test_schedule: + return TestDeps(None, citus_upgrade_infra=True) + if schedule_line_is_upgrade_after(schedule_line): # upgrade_xxx_after tests always depend on upgrade_xxx_before + test_names = schedule_line.split()[1:] + before_tests = [] + # _after tests have implicit dependencies on _before tests + for test_name in test_names: + if "_after" in test_name: + before_tests.append(test_name.replace("_after", "_before")) + + # the upgrade_columnar_before renames the schema, on which other + # "after" tests depend. So we make sure to execute it always. + if "upgrade_columnar_before" not in before_tests: + before_tests.append("upgrade_columnar_before") + return TestDeps( default_base_schedule(test_schedule, args), - [test_name.replace("_after", "_before")], + before_tests, ) # before_ tests leave stuff around on purpose for the after tests. So they diff --git a/src/test/regress/citus_tests/upgrade/__init__.py b/src/test/regress/citus_tests/upgrade/__init__.py index e69de29bb..672b7a32c 100644 --- a/src/test/regress/citus_tests/upgrade/__init__.py +++ b/src/test/regress/citus_tests/upgrade/__init__.py @@ -0,0 +1 @@ +from .citus_upgrade_test import generate_citus_tarball, run_citus_upgrade_tests # noqa diff --git a/src/test/regress/citus_tests/upgrade/citus_upgrade_test.py b/src/test/regress/citus_tests/upgrade/citus_upgrade_test.py index 3ea51d5d9..87b00c83c 100755 --- a/src/test/regress/citus_tests/upgrade/citus_upgrade_test.py +++ b/src/test/regress/citus_tests/upgrade/citus_upgrade_test.py @@ -13,7 +13,7 @@ Options: --mixed Run the verification phase with one node not upgraded. """ -import atexit +import multiprocessing import os import re import subprocess @@ -27,6 +27,7 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # ignore E402 because these imports require addition to path import common # noqa: E402 import utils # noqa: E402 +from common import CI, PG_MAJOR_VERSION, REPO_ROOT, run # noqa: E402 from utils import USER # noqa: E402 from config import ( # noqa: E402 @@ -41,6 +42,12 @@ from config import ( # noqa: E402 def main(config): + before_upgrade_schedule = get_before_upgrade_schedule(config.mixed_mode) + after_upgrade_schedule = get_after_upgrade_schedule(config.mixed_mode) + run_citus_upgrade_tests(config, before_upgrade_schedule, after_upgrade_schedule) + + +def run_citus_upgrade_tests(config, before_upgrade_schedule, after_upgrade_schedule): install_citus(config.pre_tar_path) common.initialize_temp_dir(config.temp_dir) common.initialize_citus_cluster( @@ -48,23 +55,28 @@ def main(config): ) report_initial_version(config) - before_upgrade_schedule = get_before_upgrade_schedule(config.mixed_mode) run_test_on_coordinator(config, before_upgrade_schedule) remove_citus(config.pre_tar_path) + if after_upgrade_schedule is None: + return + install_citus(config.post_tar_path) restart_databases(config.bindir, config.datadir, config.mixed_mode, config) run_alter_citus(config.bindir, config.mixed_mode, config) verify_upgrade(config, config.mixed_mode, config.node_name_to_ports.values()) - after_upgrade_schedule = get_after_upgrade_schedule(config.mixed_mode) run_test_on_coordinator(config, after_upgrade_schedule) remove_citus(config.post_tar_path) def install_citus(tar_path): - with utils.cd("/"): - subprocess.run(["tar", "xvf", tar_path], check=True) + if tar_path: + with utils.cd("/"): + run(["tar", "xvf", tar_path], shell=False) + else: + with utils.cd(REPO_ROOT): + run(f"make -j{multiprocessing.cpu_count()} -s install") def report_initial_version(config): @@ -90,8 +102,9 @@ def run_test_on_coordinator(config, schedule): def remove_citus(tar_path): - with utils.cd("/"): - remove_tar_files(tar_path) + if tar_path: + with utils.cd("/"): + remove_tar_files(tar_path) def remove_tar_files(tar_path): @@ -171,43 +184,29 @@ def get_after_upgrade_schedule(mixed_mode): return AFTER_CITUS_UPGRADE_COORD_SCHEDULE -# IsRunningOnLocalMachine returns true if the upgrade test is run on -# local machine, in which case the old citus version will be installed -# and it will be upgraded to the current code. -def IsRunningOnLocalMachine(arguments): - return arguments["--citus-old-version"] - - -def generate_citus_tarballs(citus_version): +def generate_citus_tarball(citus_version): tmp_dir = "tmp_citus_tarballs" citus_old_tarpath = os.path.abspath( - os.path.join(tmp_dir, "install-citus{}.tar".format(citus_version)) - ) - citus_new_tarpath = os.path.abspath( - os.path.join(tmp_dir, "install-citusmaster.tar") + os.path.join(tmp_dir, f"install-pg{PG_MAJOR_VERSION}-citus{citus_version}.tar") ) common.initialize_temp_dir_if_not_exists(tmp_dir) dirpath = os.path.dirname(os.path.realpath(__file__)) local_script_path = os.path.join(dirpath, "generate_citus_tarballs.sh") with utils.cd(tmp_dir): - subprocess.check_call([local_script_path, citus_version]) + subprocess.check_call([local_script_path, str(PG_MAJOR_VERSION), citus_version]) - return [citus_old_tarpath, citus_new_tarpath] + return citus_old_tarpath if __name__ == "__main__": args = docopt(__doc__, version="citus_upgrade_test") - if IsRunningOnLocalMachine(args): - citus_tarball_paths = generate_citus_tarballs(args["--citus-old-version"]) - args["--citus-pre-tar"] = citus_tarball_paths[0] - args["--citus-post-tar"] = citus_tarball_paths[1] - config = CitusUpgradeConfig(args) - atexit.register( - common.stop_databases, - config.bindir, - config.datadir, - config.node_name_to_ports, - config.name, - ) + if not CI: + citus_tarball_path = generate_citus_tarball(args["--citus-old-version"]) + config = CitusUpgradeConfig(args, citus_tarball_path, None) + else: + config = CitusUpgradeConfig( + args, args["--citus-pre-tar"], args["--citus-post-tar"] + ) + main(config) diff --git a/src/test/regress/citus_tests/upgrade/generate_citus_tarballs.sh b/src/test/regress/citus_tests/upgrade/generate_citus_tarballs.sh index 440154aa3..08636adb3 100755 --- a/src/test/regress/citus_tests/upgrade/generate_citus_tarballs.sh +++ b/src/test/regress/citus_tests/upgrade/generate_citus_tarballs.sh @@ -2,7 +2,8 @@ set -euxo pipefail -citus_old_version=$1 +pg_version=$1 +citus_old_version=$2 base="$(pwd)" @@ -17,38 +18,22 @@ install_citus_and_tar() { cd "${installdir}" && find . -type f -print >"${builddir}/files.lst" - tar cvf "${basedir}/install-citus${citus_version}.tar" $(cat "${builddir}"/files.lst) - mv "${basedir}/install-citus${citus_version}.tar" "${base}/install-citus${citus_version}.tar" + tar cvf "${basedir}/install-pg${pg_version}-citus${citus_version}.tar" $(cat "${builddir}"/files.lst) + mv "${basedir}/install-pg${pg_version}-citus${citus_version}.tar" "${base}/install-pg${pg_version}-citus${citus_version}.tar" cd "${builddir}" && rm -rf install files.lst && make clean } -build_current() { - citus_version="$1" - basedir="${base}/${citus_version}" - - mkdir -p "${basedir}" - citus_repo=$(git rev-parse --show-toplevel) - - cd "$citus_repo" && cp -R . /tmp/citus_copy - # https://stackoverflow.com/questions/957928/is-there-a-way-to-get-the-git-root-directory-in-one-command - mv /tmp/citus_copy "${basedir}/citus_${citus_version}" - builddir="${basedir}/build" - cd "${basedir}" - - citus_dir=${basedir}/citus_$citus_version - - make -C "${citus_dir}" clean - cd "${citus_dir}" - ./configure --without-libcurl - - install_citus_and_tar -} - build_ext() { citus_version="$1" + # If tarball already exsists we're good + if [ -f "${base}/install-pg${pg_version}-citus${citus_version}.tar" ]; then + return + fi + basedir="${base}/${citus_version}" + rm -rf "${basedir}" mkdir -p "${basedir}" cd "${basedir}" citus_dir=${basedir}/citus_$citus_version @@ -58,5 +43,4 @@ build_ext() { install_citus_and_tar } -build_current "master" build_ext "${citus_old_version}" diff --git a/src/test/regress/columnar_schedule b/src/test/regress/columnar_schedule index 11e9494d2..602af0fc7 100644 --- a/src/test/regress/columnar_schedule +++ b/src/test/regress/columnar_schedule @@ -2,6 +2,7 @@ test: multi_test_helpers multi_test_helpers_superuser columnar_test_helpers test: multi_cluster_management test: multi_test_catalog_views +test: remove_coordinator_from_metadata test: columnar_create test: columnar_load test: columnar_query diff --git a/src/test/regress/enterprise_schedule b/src/test/regress/enterprise_schedule index 55791d43a..9a832c4d6 100644 --- a/src/test/regress/enterprise_schedule +++ b/src/test/regress/enterprise_schedule @@ -16,11 +16,11 @@ test: add_coordinator test: citus_local_tables_ent -test: remove_coordinator # -------- test: publication test: logical_replication +test: check_cluster_state test: multi_create_table test: multi_create_table_superuser test: multi_create_role_dependency diff --git a/src/test/regress/expected/adaptive_executor_repartition.out b/src/test/regress/expected/adaptive_executor_repartition.out index a84677a35..b575e4baf 100644 --- a/src/test/regress/expected/adaptive_executor_repartition.out +++ b/src/test/regress/expected/adaptive_executor_repartition.out @@ -167,11 +167,5 @@ select count(*) from trips t1, cars r1, trips t2, cars r2 where t1.trip_id = t2. 829 (1 row) +SET client_min_messages TO WARNING; DROP SCHEMA adaptive_executor CASCADE; -NOTICE: drop cascades to 6 other objects -DETAIL: drop cascades to table ab -drop cascades to table single_hash_repartition_first -drop cascades to table single_hash_repartition_second -drop cascades to table ref_table -drop cascades to table cars -drop cascades to table trips diff --git a/src/test/regress/expected/add_coordinator.out b/src/test/regress/expected/add_coordinator.out index 01f3a682d..499669385 100644 --- a/src/test/regress/expected/add_coordinator.out +++ b/src/test/regress/expected/add_coordinator.out @@ -2,6 +2,13 @@ -- ADD_COORDINATOR -- -- node trying to add itself without specifying groupid => 0 should error out +-- first remove the coordinator to for testing master_add_node for coordinator +SELECT master_remove_node('localhost', :master_port); + master_remove_node +--------------------------------------------------------------------- + +(1 row) + SELECT master_add_node('localhost', :master_port); ERROR: Node cannot add itself as a worker. HINT: Add the node as a coordinator by using: SELECT citus_set_coordinator_host('localhost', 57636); diff --git a/src/test/regress/expected/alter_distributed_table.out b/src/test/regress/expected/alter_distributed_table.out index 1a99f1964..9d968dbb1 100644 --- a/src/test/regress/expected/alter_distributed_table.out +++ b/src/test/regress/expected/alter_distributed_table.out @@ -528,8 +528,8 @@ SELECT COUNT(DISTINCT colocationid) FROM pg_dist_partition WHERE logicalrelid::r -- test references CREATE TABLE referenced_dist_table (a INT UNIQUE); CREATE TABLE referenced_ref_table (a INT UNIQUE); -CREATE TABLE table_with_references (a1 INT UNIQUE REFERENCES referenced_dist_table(a), a2 INT REFERENCES referenced_ref_table(a)); -CREATE TABLE referencing_dist_table (a INT REFERENCES table_with_references(a1)); +CREATE TABLE table_with_references (a1 INT UNIQUE, a2 INT); +CREATE TABLE referencing_dist_table (a INT); SELECT create_distributed_table('referenced_dist_table', 'a', colocate_with:='none'); create_distributed_table --------------------------------------------------------------------- @@ -554,6 +554,9 @@ SELECT create_distributed_table('referencing_dist_table', 'a', colocate_with:='r (1 row) +ALTER TABLE table_with_references ADD FOREIGN KEY (a1) REFERENCES referenced_dist_table(a); +ALTER TABLE table_with_references ADD FOREIGN KEY (a2) REFERENCES referenced_ref_table(a); +ALTER TABLE referencing_dist_table ADD FOREIGN KEY (a) REFERENCES table_with_references(a1); SET client_min_messages TO WARNING; SELECT conrelid::regclass::text AS "Referencing Table", pg_get_constraintdef(oid, true) AS "Definition" FROM pg_constraint WHERE (conrelid::regclass::text = 'table_with_references' OR confrelid::regclass::text = 'table_with_references') AND contype = 'f' ORDER BY 1,2; @@ -1020,6 +1023,16 @@ SELECT create_reference_table('reference_table'); SELECT alter_distributed_table('dist_table', colocate_with:='reference_table'); ERROR: cannot colocate with reference_table because it is not a distributed table +-- test colocating with single shard table +CREATE TABLE single_shard_table (a INT); +SELECT create_distributed_table('single_shard_table', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT alter_distributed_table('dist_table', colocate_with:='single_shard_table'); +ERROR: cannot colocate with single_shard_table because it is a single shard distributed table -- test append table CREATE TABLE append_table (a INT); SELECT create_distributed_table('append_table', 'a', 'append'); @@ -1245,3 +1258,4 @@ SELECT run_command_on_workers($$SELECT count(*) FROM pg_matviews WHERE matviewna RESET search_path; DROP SCHEMA alter_distributed_table CASCADE; DROP SCHEMA schema_to_test_alter_dist_table CASCADE; +DROP USER alter_dist_table_test_user; diff --git a/src/test/regress/expected/alter_table_set_access_method.out b/src/test/regress/expected/alter_table_set_access_method.out index 63d0990d4..8a6f335a7 100644 --- a/src/test/regress/expected/alter_table_set_access_method.out +++ b/src/test/regress/expected/alter_table_set_access_method.out @@ -802,9 +802,3 @@ select alter_table_set_access_method('view_test_view','columnar'); ERROR: you cannot alter access method of a view SET client_min_messages TO WARNING; DROP SCHEMA alter_table_set_access_method CASCADE; -SELECT 1 FROM master_remove_node('localhost', :master_port); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - diff --git a/src/test/regress/expected/alter_table_single_shard_table.out b/src/test/regress/expected/alter_table_single_shard_table.out new file mode 100644 index 000000000..1812c33cb --- /dev/null +++ b/src/test/regress/expected/alter_table_single_shard_table.out @@ -0,0 +1,154 @@ +CREATE SCHEMA alter_null_dist_key; +SET search_path TO alter_null_dist_key; +SET citus.next_shard_id TO 1720000; +SET citus.shard_count TO 32; +SET citus.shard_replication_factor TO 1; +CREATE SEQUENCE dist_seq; +CREATE TABLE null_dist_table(a bigint DEFAULT nextval('dist_seq') UNIQUE, "b" text, c bigint GENERATED BY DEFAULT AS IDENTITY); +INSERT INTO null_dist_table("b") VALUES ('test'); +SELECT create_distributed_table('null_dist_table', null, colocate_with=>'none', distribution_type=>null); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$alter_null_dist_key.null_dist_table$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- add column +ALTER TABLE null_dist_table ADD COLUMN d bigint DEFAULT 2; +SELECT * FROM null_dist_table ORDER BY c; + a | b | c | d +--------------------------------------------------------------------- + 1 | test | 1 | 2 +(1 row) + +-- alter default, set to 3 +ALTER TABLE null_dist_table ALTER COLUMN d SET DEFAULT 3; +INSERT INTO null_dist_table("b") VALUES ('test'); +SELECT * FROM null_dist_table ORDER BY c; + a | b | c | d +--------------------------------------------------------------------- + 1 | test | 1 | 2 + 2 | test | 2 | 3 +(2 rows) + +-- drop default, see null +ALTER TABLE null_dist_table ALTER COLUMN d DROP DEFAULT; +INSERT INTO null_dist_table("b") VALUES ('test'); +SELECT * FROM null_dist_table ORDER BY c; + a | b | c | d +--------------------------------------------------------------------- + 1 | test | 1 | 2 + 2 | test | 2 | 3 + 3 | test | 3 | +(3 rows) + +-- cleanup the rows that were added to test the default behavior +DELETE FROM null_dist_table WHERE "b" = 'test' AND a > 1; +-- alter column type +ALTER TABLE null_dist_table ALTER COLUMN d TYPE text; +UPDATE null_dist_table SET d = 'this is a text' WHERE d = '2'; +SELECT * FROM null_dist_table ORDER BY c; + a | b | c | d +--------------------------------------------------------------------- + 1 | test | 1 | this is a text +(1 row) + +-- drop seq column +ALTER TABLE null_dist_table DROP COLUMN a; +SELECT * FROM null_dist_table ORDER BY c; + b | c | d +--------------------------------------------------------------------- + test | 1 | this is a text +(1 row) + +-- add not null constraint +ALTER TABLE null_dist_table ALTER COLUMN b SET NOT NULL; +-- not null constraint violation, error out +INSERT INTO null_dist_table VALUES (NULL, 2, 'test'); +ERROR: null value in column "b" violates not-null constraint +DETAIL: Failing row contains (null, 2, test). +CONTEXT: while executing command on localhost:xxxxx +-- drop not null constraint and try again +ALTER TABLE null_dist_table ALTER COLUMN b DROP NOT NULL; +INSERT INTO null_dist_table VALUES (NULL, 3, 'test'); +SELECT * FROM null_dist_table ORDER BY c; + b | c | d +--------------------------------------------------------------------- + test | 1 | this is a text + | 3 | test +(2 rows) + +-- add exclusion constraint +ALTER TABLE null_dist_table ADD CONSTRAINT exc_b EXCLUDE USING btree (b with =); +-- rename the exclusion constraint, errors out +ALTER TABLE null_dist_table RENAME CONSTRAINT exc_b TO exc_b_1; +ERROR: renaming constraints belonging to distributed tables is currently unsupported +-- create exclusion constraint without a name +ALTER TABLE null_dist_table ADD EXCLUDE USING btree (b with =); +-- test setting autovacuum option +ALTER TABLE null_dist_table SET (autovacuum_enabled = false); +-- test multiple subcommands +ALTER TABLE null_dist_table ADD COLUMN int_column1 INTEGER, + DROP COLUMN d; +SELECT * FROM null_dist_table ORDER BY c; + b | c | int_column1 +--------------------------------------------------------------------- + test | 1 | + | 3 | +(2 rows) + +-- test policy and row level security +CREATE TABLE null_dist_key_with_policy (table_user text); +INSERT INTO null_dist_key_with_policy VALUES ('user_1'); +SELECT create_distributed_table('null_dist_key_with_policy', null); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$alter_null_dist_key.null_dist_key_with_policy$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- enable rls +ALTER TABLE null_dist_key_with_policy ENABLE ROW LEVEL SECURITY; +-- user_1 will be allowed to see the inserted row +CREATE ROLE user_1 WITH LOGIN; +GRANT ALL ON SCHEMA alter_null_dist_key TO user_1; +GRANT ALL ON TABLE alter_null_dist_key.null_dist_key_with_policy TO user_1; +CREATE POLICY table_policy ON null_dist_key_with_policy TO user_1 + USING (table_user = current_user); +-- user_2 will not be allowed to see the inserted row +CREATE ROLE user_2 WITH LOGIN; +GRANT ALL ON SCHEMA alter_null_dist_key TO user_2; +GRANT ALL ON TABLE alter_null_dist_key.null_dist_key_with_policy TO user_2; +CREATE POLICY table_policy_1 ON null_dist_key_with_policy TO user_2 + USING (table_user = current_user); +\c - user_1 - +SELECT * FROM alter_null_dist_key.null_dist_key_with_policy; + table_user +--------------------------------------------------------------------- + user_1 +(1 row) + +\c - user_2 - +SELECT * FROM alter_null_dist_key.null_dist_key_with_policy; + table_user +--------------------------------------------------------------------- +(0 rows) + +-- postgres will always be allowed to see the row as a superuser +\c - postgres - +SELECT * FROM alter_null_dist_key.null_dist_key_with_policy; + table_user +--------------------------------------------------------------------- + user_1 +(1 row) + +-- cleanup +SET client_min_messages TO ERROR; +DROP SCHEMA alter_null_dist_key CASCADE; +DROP ROLE user_1, user_2; diff --git a/src/test/regress/expected/auto_undist_citus_local.out b/src/test/regress/expected/auto_undist_citus_local.out index afb725f3e..0eaec17e5 100644 --- a/src/test/regress/expected/auto_undist_citus_local.out +++ b/src/test/regress/expected/auto_undist_citus_local.out @@ -453,7 +453,9 @@ SELECT logicalrelid, partmethod, repmodel FROM pg_dist_partition WHERE logicalre DROP TABLE IF EXISTS citus_local_table_1, citus_local_table_2, citus_local_table_3; -- this GUC will add the next three tables to metadata automatically SET citus.use_citus_managed_tables TO ON; -CREATE TABLE citus_local_table_1(a INT UNIQUE); +-- try to create the table twice by using IF NOT EXISTS syntax +CREATE TABLE IF NOT EXISTS citus_local_table_1(a INT UNIQUE); +CREATE TABLE IF NOT EXISTS citus_local_table_1(a INT UNIQUE); CREATE TABLE citus_local_table_2(a INT UNIQUE); CREATE TABLE citus_local_table_3(a INT UNIQUE); RESET citus.use_citus_managed_tables; @@ -1340,3 +1342,4 @@ SELECT logicalrelid, partmethod, repmodel FROM pg_dist_partition WHERE logicalre (1 row) DROP SCHEMA drop_fkey_cascade CASCADE; +DROP USER another_user; diff --git a/src/test/regress/expected/background_rebalance.out b/src/test/regress/expected/background_rebalance.out index e4495ccf9..1b8dfdd08 100644 --- a/src/test/regress/expected/background_rebalance.out +++ b/src/test/regress/expected/background_rebalance.out @@ -310,6 +310,61 @@ SELECT public.wait_until_metadata_sync(30000); (1 row) +-- make sure a non-super user can rebalance when there are reference tables to replicate +CREATE TABLE ref_table(a int primary key); +SELECT create_reference_table('ref_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +-- add a new node to trigger replicate_reference_tables task +SELECT 1 FROM citus_add_node('localhost', :worker_3_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SET ROLE non_super_user_rebalance; +SELECT 1 FROM citus_rebalance_start(shard_transfer_mode := 'force_logical'); +NOTICE: Scheduled 1 moves as job xxx +DETAIL: Rebalance scheduled as background job +HINT: To monitor progress, run: SELECT * FROM citus_rebalance_status(); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +-- wait for success +SELECT citus_rebalance_wait(); + citus_rebalance_wait +--------------------------------------------------------------------- + +(1 row) + +SELECT state, details from citus_rebalance_status(); + state | details +--------------------------------------------------------------------- + finished | {"tasks": [], "task_state_counts": {"done": 2}} +(1 row) + +RESET ROLE; +-- reset the the number of nodes by removing the previously added node +SELECT 1 FROM citus_drain_node('localhost', :worker_3_port); +NOTICE: Moving shard xxxxx from localhost:xxxxx to localhost:xxxxx ... + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +CALL citus_cleanup_orphaned_resources(); +NOTICE: cleaned up 1 orphaned resources +SELECT 1 FROM citus_remove_node('localhost', :worker_3_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + SET client_min_messages TO WARNING; DROP SCHEMA background_rebalance CASCADE; DROP USER non_super_user_rebalance; diff --git a/src/test/regress/expected/background_rebalance_parallel.out b/src/test/regress/expected/background_rebalance_parallel.out index 9c43fab9b..cc3470de9 100644 --- a/src/test/regress/expected/background_rebalance_parallel.out +++ b/src/test/regress/expected/background_rebalance_parallel.out @@ -466,17 +466,16 @@ SELECT citus_rebalance_start AS job_id from citus_rebalance_start() \gset -- see dependent tasks to understand which tasks remain runnable because of -- citus.max_background_task_executors_per_node -- and which tasks are actually blocked from colocation group dependencies -SELECT D.task_id, - (SELECT T.command FROM pg_dist_background_task T WHERE T.task_id = D.task_id), - D.depends_on, - (SELECT T.command FROM pg_dist_background_task T WHERE T.task_id = D.depends_on) -FROM pg_dist_background_task_depend D WHERE job_id in (:job_id) ORDER BY D.task_id, D.depends_on ASC; - task_id | command | depends_on | command +SELECT task_id, depends_on +FROM pg_dist_background_task_depend +WHERE job_id in (:job_id) +ORDER BY 1, 2 ASC; + task_id | depends_on --------------------------------------------------------------------- - 1014 | SELECT pg_catalog.citus_move_shard_placement(85674026,50,57,'auto') | 1013 | SELECT pg_catalog.citus_move_shard_placement(85674025,50,56,'auto') - 1016 | SELECT pg_catalog.citus_move_shard_placement(85674032,50,57,'auto') | 1015 | SELECT pg_catalog.citus_move_shard_placement(85674031,50,56,'auto') - 1018 | SELECT pg_catalog.citus_move_shard_placement(85674038,50,57,'auto') | 1017 | SELECT pg_catalog.citus_move_shard_placement(85674037,50,56,'auto') - 1020 | SELECT pg_catalog.citus_move_shard_placement(85674044,50,57,'auto') | 1019 | SELECT pg_catalog.citus_move_shard_placement(85674043,50,56,'auto') + 1014 | 1013 + 1016 | 1015 + 1018 | 1017 + 1020 | 1019 (4 rows) -- default citus.max_background_task_executors_per_node is 1 @@ -503,6 +502,12 @@ FROM pg_dist_background_task WHERE job_id in (:job_id) ORDER BY task_id; (8 rows) -- increase citus.max_background_task_executors_per_node +SELECT citus_task_wait(1013, desired_status => 'done'); + citus_task_wait +--------------------------------------------------------------------- + +(1 row) + ALTER SYSTEM SET citus.max_background_task_executors_per_node = 2; SELECT pg_reload_conf(); pg_reload_conf @@ -510,13 +515,13 @@ SELECT pg_reload_conf(); t (1 row) -SELECT citus_task_wait(1015, desired_status => 'running'); +SELECT citus_task_wait(1014, desired_status => 'running'); citus_task_wait --------------------------------------------------------------------- (1 row) -SELECT citus_task_wait(1013, desired_status => 'done'); +SELECT citus_task_wait(1015, desired_status => 'running'); citus_task_wait --------------------------------------------------------------------- diff --git a/src/test/regress/expected/background_task_queue_monitor.out b/src/test/regress/expected/background_task_queue_monitor.out index 2b4f7de37..1d4006377 100644 --- a/src/test/regress/expected/background_task_queue_monitor.out +++ b/src/test/regress/expected/background_task_queue_monitor.out @@ -495,11 +495,11 @@ SELECT task_id, status, retry_count, message FROM pg_dist_background_task ORDER BY task_id; -- show that all tasks are runnable by retry policy after termination signal task_id | status | retry_count | message --------------------------------------------------------------------- - 1450019 | runnable | 1 | FATAL: terminating connection due to administrator command + - | | | CONTEXT: Citus Background Task Queue Executor: regression/postgres for (1450011/1450019) + + 1450019 | runnable | 1 | FATAL: terminating background worker "Citus Background Task Queue Executor: regression/postgres for (xxxxx/xxxxx)" due to administrator command+ + | | | CONTEXT: Citus Background Task Queue Executor: regression/postgres for (xxxxx/xxxxx) + | | | - 1450020 | runnable | 1 | FATAL: terminating connection due to administrator command + - | | | CONTEXT: Citus Background Task Queue Executor: regression/postgres for (1450012/1450020) + + 1450020 | runnable | 1 | FATAL: terminating background worker "Citus Background Task Queue Executor: regression/postgres for (xxxxx/xxxxx)" due to administrator command+ + | | | CONTEXT: Citus Background Task Queue Executor: regression/postgres for (xxxxx/xxxxx) + | | | (2 rows) diff --git a/src/test/regress/expected/check_cluster_state.out b/src/test/regress/expected/check_cluster_state.out new file mode 100644 index 000000000..c66119d52 --- /dev/null +++ b/src/test/regress/expected/check_cluster_state.out @@ -0,0 +1,6 @@ +SELECT count(*) >= 1 as coordinator_exists FROM pg_dist_node WHERE groupid = 0 AND isactive; + coordinator_exists +--------------------------------------------------------------------- + t +(1 row) + diff --git a/src/test/regress/expected/citus_local_dist_joins.out b/src/test/regress/expected/citus_local_dist_joins.out index 25833fc05..44101c925 100644 --- a/src/test/regress/expected/citus_local_dist_joins.out +++ b/src/test/regress/expected/citus_local_dist_joins.out @@ -1,7 +1,6 @@ CREATE SCHEMA citus_local_dist_joins; SET search_path TO citus_local_dist_joins; SET client_min_messages to ERROR; -SELECT master_add_node('localhost', :master_port, groupId => 0) AS coordinator_nodeid \gset CREATE TABLE citus_local(key int, value text); SELECT citus_add_local_table_to_metadata('citus_local'); citus_add_local_table_to_metadata @@ -523,11 +522,5 @@ ERROR: recursive complex joins are only supported when all distributed tables a RESET citus.local_table_join_policy; SET client_min_messages to ERROR; DROP TABLE citus_local; -SELECT master_remove_node('localhost', :master_port); - master_remove_node ---------------------------------------------------------------------- - -(1 row) - \set VERBOSITY terse DROP SCHEMA citus_local_dist_joins CASCADE; diff --git a/src/test/regress/expected/citus_local_tables.out b/src/test/regress/expected/citus_local_tables.out index 0ece7ba91..cfa6410ba 100644 --- a/src/test/regress/expected/citus_local_tables.out +++ b/src/test/regress/expected/citus_local_tables.out @@ -693,7 +693,7 @@ BEGIN; ROLLBACK; -- should fail -- SELECT update_distributed_table_colocation('citus_local_table_4', colocate_with => 'none'); -ERROR: relation citus_local_table_4 should be a hash distributed table +ERROR: relation citus_local_table_4 should be a hash or single shard distributed table SELECT master_create_empty_shard('citus_local_table_4'); ERROR: relation "citus_local_table_4" is a local table -- return true diff --git a/src/test/regress/expected/citus_local_tables_mx.out b/src/test/regress/expected/citus_local_tables_mx.out index c1539ad7c..363e87e58 100644 --- a/src/test/regress/expected/citus_local_tables_mx.out +++ b/src/test/regress/expected/citus_local_tables_mx.out @@ -805,11 +805,11 @@ SELECT run_command_on_workers($$SELECT COUNT(*) FROM pg_views WHERE viewname LIK (localhost,57638,t,2) (2 rows) -SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid IN('viewsc.prop_view'::regclass::oid, 'viewsc.prop_view2'::regclass::oid); +SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid IN('viewsc.prop_view'::regclass::oid, 'viewsc.prop_view2'::regclass::oid) ORDER BY 1; pg_identify_object_as_address --------------------------------------------------------------------- - (view,"{viewsc,prop_view2}",{}) (view,"{viewsc,prop_view}",{}) + (view,"{viewsc,prop_view2}",{}) (2 rows) -- drop views @@ -857,7 +857,7 @@ SELECT run_command_on_workers($$SELECT COUNT(*) FROM pg_views WHERE viewname LIK (localhost,57638,t,2) (2 rows) -SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid IN('viewsc.prop_view3'::regclass::oid, 'viewsc.prop_view4'::regclass::oid); +SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid IN('viewsc.prop_view3'::regclass::oid, 'viewsc.prop_view4'::regclass::oid) ORDER BY 1; pg_identify_object_as_address --------------------------------------------------------------------- (view,"{viewsc,prop_view3}",{}) diff --git a/src/test/regress/expected/citus_schema_distribute_undistribute.out b/src/test/regress/expected/citus_schema_distribute_undistribute.out new file mode 100644 index 000000000..ae08b6c6a --- /dev/null +++ b/src/test/regress/expected/citus_schema_distribute_undistribute.out @@ -0,0 +1,923 @@ +SET citus.next_shard_id TO 1730000; +SET citus.shard_replication_factor TO 1; +SET client_min_messages TO WARNING; +SET citus.enable_schema_based_sharding TO off; +SELECT 1 FROM citus_add_node('localhost', :master_port, groupid => 0); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +CREATE USER tenantuser superuser; +SET role tenantuser; +-- check invalid input +SELECT citus_schema_distribute(1); +ERROR: schema with OID 1 does not exist +SELECT citus_schema_undistribute(1); +ERROR: schema with OID 1 does not exist +-- noop +SELECT citus_schema_distribute(null); + citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_schema_undistribute(null); + citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +-- public and some others cannot be distributed as a tenant schema, but check what happens +-- if we try to call citus_schema_undistribute() for such a schema. +SELECT citus_schema_undistribute('public'); +ERROR: schema public is not distributed +-- create non-tenant schema +CREATE SCHEMA citus_schema_distribute_undistribute; +-- create tenant schema +CREATE SCHEMA tenant1; +CREATE TABLE tenant1.table1(id int PRIMARY KEY, name text); +INSERT INTO tenant1.table1 SELECT i, 'asd'::text FROM generate_series(1,20) i; +CREATE TABLE tenant1.table2(id int REFERENCES tenant1.table1(id), num bigint UNIQUE); +INSERT INTO tenant1.table2 SELECT i, i FROM generate_series(1,20) i; +CREATE TABLE citus_schema_distribute_undistribute.ref(id int PRIMARY KEY); +SELECT create_reference_table('citus_schema_distribute_undistribute.ref'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO citus_schema_distribute_undistribute.ref SELECT i FROM generate_series(1,100) i; +-- autoconverted to Citus local table due to foreign key to reference table +CREATE TABLE tenant1.table3(id int REFERENCES citus_schema_distribute_undistribute.ref(id)); +INSERT INTO tenant1.table3 SELECT i FROM generate_series(1,100) i; +-- Citus local table with autoconverted=false +CREATE TABLE tenant1.table3x(id int PRIMARY KEY REFERENCES citus_schema_distribute_undistribute.ref(id)); +SELECT citus_add_local_table_to_metadata('tenant1.table3x'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO tenant1.table3x SELECT i FROM generate_series(1,100) i; +-- foreign key to another local table in the same schema +CREATE TABLE tenant1.table3y(id int PRIMARY KEY REFERENCES tenant1.table3x(id)); +SELECT citus_add_local_table_to_metadata('tenant1.table3y'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO tenant1.table3y SELECT i FROM generate_series(1,100) i; +-- table with composite type +CREATE TYPE tenant1.catname AS ENUM ('baby', 'teen', 'mid'); +CREATE TYPE tenant1.agecat AS (below_age int, name tenant1.catname); +CREATE TABLE tenant1.table4(id int, age tenant1.agecat); +-- create autoconverted partitioned table +CREATE TABLE tenant1.partitioned_table(id int REFERENCES citus_schema_distribute_undistribute.ref(id)) PARTITION BY RANGE(id); +CREATE TABLE tenant1.partition1 PARTITION OF tenant1.partitioned_table FOR VALUES FROM (1) TO (11); +CREATE TABLE tenant1.partition2 PARTITION OF tenant1.partitioned_table FOR VALUES FROM (11) TO (21); +INSERT INTO tenant1.partitioned_table SELECT i FROM generate_series(1,20) i; +-- create view +CREATE VIEW tenant1.view1 AS SELECT * FROM tenant1.table1 JOIN tenant1.table2 USING(id); +WARNING: "view tenant1.view1" has dependency to "table tenant1.table2" that is not in Citus' metadata +DETAIL: "view tenant1.view1" will be created only locally +HINT: Distribute "table tenant1.table2" first to distribute "view tenant1.view1" +-- create view in regular schema +CREATE VIEW citus_schema_distribute_undistribute.view2 AS SELECT * FROM tenant1.view1; +WARNING: "view citus_schema_distribute_undistribute.view2" has dependency to "table tenant1.table2" that is not in Citus' metadata +DETAIL: "view citus_schema_distribute_undistribute.view2" will be created only locally +HINT: Distribute "table tenant1.table2" first to distribute "view citus_schema_distribute_undistribute.view2" +-- create materialized view +CREATE MATERIALIZED VIEW tenant1.view2 AS SELECT * FROM tenant1.table1; +-- create collation +CREATE COLLATION citus_schema_distribute_undistribute.german_phonebook (provider = icu, locale = 'de-u-co-phonebk'); +-- create type +CREATE TYPE citus_schema_distribute_undistribute.pair_type AS (a int, b int); +-- Create function +CREATE FUNCTION citus_schema_distribute_undistribute.one_as_result() RETURNS INT LANGUAGE SQL AS +$$ + SELECT 1; +$$; +-- create text search dictionary +CREATE TEXT SEARCH DICTIONARY citus_schema_distribute_undistribute.my_german_dict ( + template = snowball, + language = german, + stopwords = german +); +-- create text search config +CREATE TEXT SEARCH CONFIGURATION citus_schema_distribute_undistribute.my_ts_config ( parser = default ); +ALTER TEXT SEARCH CONFIGURATION citus_schema_distribute_undistribute.my_ts_config ALTER MAPPING FOR asciiword WITH citus_schema_distribute_undistribute.my_german_dict; +-- create sequence +CREATE SEQUENCE citus_schema_distribute_undistribute.seq; +-- create complex table +CREATE TABLE tenant1.complextable (id int PRIMARY KEY default nextval('citus_schema_distribute_undistribute.seq'), col int default (citus_schema_distribute_undistribute.one_as_result()), myserial serial, phone text COLLATE citus_schema_distribute_undistribute.german_phonebook, initials citus_schema_distribute_undistribute.pair_type); +CREATE SEQUENCE tenant1.seq_owned OWNED BY tenant1.complextable.id; +-- not allowed from workers +SELECT run_command_on_workers($$SELECT citus_schema_distribute('tenant1');$$); + run_command_on_workers +--------------------------------------------------------------------- + (localhost,57637,f,"ERROR: operation is not allowed on this node") + (localhost,57638,f,"ERROR: operation is not allowed on this node") +(2 rows) + +SELECT run_command_on_workers($$SELECT citus_schema_undistribute('tenant1');$$); + run_command_on_workers +--------------------------------------------------------------------- + (localhost,57637,f,"ERROR: operation is not allowed on this node") + (localhost,57638,f,"ERROR: operation is not allowed on this node") +(2 rows) + +-- inherited table not allowed +CREATE TABLE citus_schema_distribute_undistribute.cities ( + name text, + population real, + elevation int +); +CREATE TABLE citus_schema_distribute_undistribute.capitals ( + state char(2) UNIQUE NOT NULL +) INHERITS (citus_schema_distribute_undistribute.cities); +-- temporarily move "cities" into tenant1 (not a tenant schema yet) to test citus_schema_distribute() with a table that is inherited +ALTER TABLE citus_schema_distribute_undistribute.cities SET SCHEMA tenant1; +SELECT citus_schema_distribute('tenant1'); +ERROR: tables in a distributed schema cannot inherit or be inherited +ALTER TABLE tenant1.cities SET SCHEMA citus_schema_distribute_undistribute; +-- temporarily move "capitals" into tenant1 (not a tenant schema yet) to test citus_schema_distribute() with a table that inherits +ALTER TABLE citus_schema_distribute_undistribute.capitals SET SCHEMA tenant1; +SELECT citus_schema_distribute('tenant1'); +ERROR: tables in a distributed schema cannot inherit or be inherited +ALTER TABLE tenant1.capitals SET SCHEMA citus_schema_distribute_undistribute; +SELECT citus_schema_distribute('tenant1'); + citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_schema_undistribute('tenant1'); + citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE citus_schema_distribute_undistribute.illegal_partitioned_table(id int) PARTITION BY RANGE(id); +CREATE TABLE citus_schema_distribute_undistribute.illegal_partition1 PARTITION OF citus_schema_distribute_undistribute.illegal_partitioned_table FOR VALUES FROM (1) TO (11); +-- temporarily move "illegal_partitioned_table" into tenant1 (not a tenant schema yet) to test citus_schema_distribute() with a partition table whose parent is created in another schema +ALTER TABLE citus_schema_distribute_undistribute.illegal_partitioned_table SET SCHEMA tenant1; +SELECT citus_schema_distribute('tenant1'); +ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas +ALTER TABLE tenant1.illegal_partitioned_table SET SCHEMA citus_schema_distribute_undistribute; +-- temporarily move "illegal_partition1" into tenant1 (not a tenant schema yet) to test citus_schema_distribute() with a parent table whose partition is created in another schema +ALTER TABLE citus_schema_distribute_undistribute.illegal_partition1 SET SCHEMA tenant1; +SELECT citus_schema_distribute('tenant1'); +ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas +ALTER TABLE tenant1.illegal_partition1 SET SCHEMA citus_schema_distribute_undistribute; +SELECT citus_schema_distribute('tenant1'); + citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_schema_undistribute('tenant1'); + citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +-- foreign key to a local table in another schema is not allowed +CREATE TABLE citus_schema_distribute_undistribute.tbl1(id int PRIMARY KEY); +CREATE TABLE tenant1.table3z(id int PRIMARY KEY REFERENCES citus_schema_distribute_undistribute.tbl1(id)); +SELECT citus_schema_distribute('tenant1'); +ERROR: foreign keys from distributed schemas can only point to the same distributed schema or reference tables in regular schemas +DETAIL: "tenant1.table3z" references "citus_schema_distribute_undistribute.tbl1" via foreign key constraint "table3z_id_fkey" +SELECT create_reference_table('citus_schema_distribute_undistribute.tbl1'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_schema_distribute('tenant1'); + citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_schema_undistribute('tenant1'); + citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +-- foreign key to a distributed table in another schema is not allowed +CREATE TABLE citus_schema_distribute_undistribute.tbl2(id int PRIMARY KEY); +SELECT create_distributed_table('citus_schema_distribute_undistribute.tbl2','id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE tenant1.table3w(id int PRIMARY KEY REFERENCES citus_schema_distribute_undistribute.tbl2(id)); +SELECT citus_schema_distribute('tenant1'); +ERROR: foreign keys from distributed schemas can only point to the same distributed schema or reference tables in regular schemas +DETAIL: "tenant1.table3w" references "citus_schema_distribute_undistribute.tbl2" via foreign key constraint "table3w_id_fkey" +DROP TABLE citus_schema_distribute_undistribute.tbl2 CASCADE; +SELECT citus_schema_distribute('tenant1'); + citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_schema_undistribute('tenant1'); + citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +-- foreign key from a local table in another schema is not allowed +CREATE TABLE tenant1.table3q(id int PRIMARY KEY); +CREATE TABLE citus_schema_distribute_undistribute.tbl3(id int PRIMARY KEY REFERENCES tenant1.table3q(id)); +SELECT citus_schema_distribute('tenant1'); +ERROR: cannot create foreign keys to tables in a distributed schema from another schema +DETAIL: "citus_schema_distribute_undistribute.tbl3" references "tenant1.table3q" via foreign key constraint "tbl3_id_fkey" +DROP TABLE citus_schema_distribute_undistribute.tbl3 CASCADE; +SELECT citus_schema_distribute('tenant1'); + citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_schema_undistribute('tenant1'); + citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +-- foreign key from a reference table in another schema is not allowed +CREATE TABLE tenant1.table3t(id int PRIMARY KEY); +CREATE TABLE citus_schema_distribute_undistribute.tbl4(id int PRIMARY KEY REFERENCES tenant1.table3t(id)); +SELECT create_reference_table('citus_schema_distribute_undistribute.tbl4'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_schema_distribute('tenant1'); +ERROR: cannot create foreign keys to tables in a distributed schema from another schema +DETAIL: "citus_schema_distribute_undistribute.tbl4" references "tenant1.table3t" via foreign key constraint "tbl4_id_fkey" +DROP TABLE citus_schema_distribute_undistribute.tbl4 CASCADE; +SELECT citus_schema_distribute('tenant1'); + citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_schema_undistribute('tenant1'); + citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +-- only allowed for schema owner or superuser +CREATE USER dummyregular; +SET role dummyregular; +SELECT citus_schema_distribute('tenant1'); +ERROR: must be owner of schema tenant1 +SELECT citus_schema_undistribute('tenant1'); +ERROR: must be owner of schema tenant1 +-- assign all tables to dummyregular except table5 +SET role tenantuser; +SELECT result FROM run_command_on_all_nodes($$ REASSIGN OWNED BY tenantuser TO dummyregular; $$); + result +--------------------------------------------------------------------- + REASSIGN OWNED + REASSIGN OWNED + REASSIGN OWNED +(3 rows) + +CREATE TABLE tenant1.table5(id int); +-- table owner check fails the distribution +SET role dummyregular; +SELECT citus_schema_distribute('tenant1'); +ERROR: must be owner of table table5 +-- alter table owner, then redistribute +SET role tenantuser; +ALTER TABLE tenant1.table5 OWNER TO dummyregular; +SET role dummyregular; +SELECT citus_schema_distribute('tenant1'); + citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +-- show the schema is a tenant schema now +SELECT colocationid AS tenant1_colocid FROM pg_dist_schema schemaid \gset +-- below query verifies the same colocationid in pg_dist_schema, pg_dist_colocation and all entries in pg_dist_partition at the same time +SELECT '$$' || + ' SELECT colocationid = ' || :tenant1_colocid || + ' FROM pg_dist_schema JOIN pg_dist_colocation USING(colocationid)' || + ' WHERE colocationid = ALL(SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE ''tenant1%'')' || + '$$' +AS verify_tenant_query \gset +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*) AS total FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1%' $$); + result +--------------------------------------------------------------------- + 15 + 15 + 15 +(3 rows) + +SELECT result FROM run_command_on_all_nodes(:verify_tenant_query); + result +--------------------------------------------------------------------- + t + t + t +(3 rows) + +SELECT citus_schema_undistribute('tenant1'); + citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +-- show the schema is a regular schema +SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_schema $$); + result +--------------------------------------------------------------------- + + + +(3 rows) + +-- below query verifies the tenant colocationid is removed from both pg_dist_colocation and all entries in pg_dist_partition at the same time +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$); + result +--------------------------------------------------------------------- + t + t + t +(3 rows) + +SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$); + result +--------------------------------------------------------------------- + + + +(3 rows) + +RESET role; +SELECT result FROM run_command_on_all_nodes($$ REASSIGN OWNED BY dummyregular TO tenantuser; $$); + result +--------------------------------------------------------------------- + REASSIGN OWNED + REASSIGN OWNED + REASSIGN OWNED +(3 rows) + +DROP USER dummyregular; +CREATE USER dummysuper superuser; +SET role dummysuper; +SELECT citus_schema_distribute('tenant1'); + citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +-- show the schema is a tenant schema now +SELECT colocationid AS tenant1_colocid FROM pg_dist_schema schemaid \gset +SELECT '$$' || + ' SELECT colocationid = ' || :tenant1_colocid || + ' FROM pg_dist_schema JOIN pg_dist_colocation USING(colocationid)' || + ' WHERE colocationid = ALL(SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE ''tenant1%'')' || + '$$' +AS verify_tenant_query \gset +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*) AS total FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1%' $$); + result +--------------------------------------------------------------------- + 15 + 15 + 15 +(3 rows) + +SELECT result FROM run_command_on_all_nodes(:verify_tenant_query); + result +--------------------------------------------------------------------- + t + t + t +(3 rows) + +SELECT citus_schema_undistribute('tenant1'); + citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +-- show the schema is a regular schema +SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_schema $$); + result +--------------------------------------------------------------------- + + + +(3 rows) + +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$); + result +--------------------------------------------------------------------- + t + t + t +(3 rows) + +SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$); + result +--------------------------------------------------------------------- + + + +(3 rows) + +RESET role; +DROP USER dummysuper; +-- foreign table +CREATE TABLE tenant1.foreign_table_test (id integer NOT NULL, data text, a bigserial); +INSERT INTO tenant1.foreign_table_test SELECT i FROM generate_series(1,100) i; +CREATE EXTENSION postgres_fdw; +CREATE SERVER foreign_server + FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (host 'localhost', port :'master_port', dbname 'regression'); +CREATE USER MAPPING FOR CURRENT_USER + SERVER foreign_server + OPTIONS (user 'postgres'); +CREATE FOREIGN TABLE tenant1.foreign_table ( + id integer NOT NULL, + data text, + a bigserial +) + SERVER foreign_server + OPTIONS (schema_name 'tenant1', table_name 'foreign_table_test'); +-- foreign table not allowed +SELECT citus_schema_distribute('tenant1'); +ERROR: cannot create a foreign table in a distributed schema +ALTER FOREIGN TABLE tenant1.foreign_table SET SCHEMA citus_schema_distribute_undistribute; +SELECT citus_schema_distribute('tenant1'); + citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_schema_undistribute('tenant1'); + citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +-- already have distributed table error +CREATE TABLE tenant1.dist(id int); +SELECT create_distributed_table('tenant1.dist', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_schema_distribute('tenant1'); +ERROR: distributed schema cannot have distributed tables +HINT: Undistribute distributed tables before 'citus_schema_distribute'. +SELECT undistribute_table('tenant1.dist'); + undistribute_table +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_schema_distribute('tenant1'); + citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_schema_undistribute('tenant1'); + citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE tenant1.ref2(id int); +SELECT create_reference_table('tenant1.ref2'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_schema_distribute('tenant1'); +ERROR: distributed schema cannot have distributed tables +HINT: Undistribute distributed tables before 'citus_schema_distribute'. +SELECT undistribute_table('tenant1.ref2'); + undistribute_table +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_schema_distribute('tenant1'); + citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_schema_undistribute('tenant1'); + citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +BEGIN; +SELECT citus_schema_distribute('tenant1'); + citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +ROLLBACK; +-- show the schema is a regular schema +SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_schema $$); + result +--------------------------------------------------------------------- + + + +(3 rows) + +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$); + result +--------------------------------------------------------------------- + t + t + t +(3 rows) + +SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$); + result +--------------------------------------------------------------------- + + + +(3 rows) + +-- errors not a tenant schema +SELECT citus_schema_undistribute('tenant1'); +ERROR: schema tenant1 is not distributed +-- make it a tenant schema +BEGIN; +SELECT citus_schema_distribute('tenant1'); + citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +COMMIT; +-- show the schema is a tenant schema now +SELECT colocationid AS tenant1_colocid FROM pg_dist_schema schemaid \gset +SELECT '$$' || + ' SELECT colocationid = ' || :tenant1_colocid || + ' FROM pg_dist_schema JOIN pg_dist_colocation USING(colocationid)' || + ' WHERE colocationid = ALL(SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE ''tenant1%'')' || + '$$' +AS verify_tenant_query \gset +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*) AS total FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1%' $$); + result +--------------------------------------------------------------------- + 18 + 18 + 18 +(3 rows) + +SELECT result FROM run_command_on_all_nodes(:verify_tenant_query); + result +--------------------------------------------------------------------- + t + t + t +(3 rows) + +-- already a tenant schema notice +SET client_min_messages TO NOTICE; +SELECT citus_schema_distribute('tenant1'); +NOTICE: schema tenant1 is already distributed + citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +SET client_min_messages TO WARNING; +-- convert back to a regular schema +SELECT citus_schema_undistribute('tenant1'); + citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +-- show the schema is a regular schema now +SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_schema $$); + result +--------------------------------------------------------------------- + + + +(3 rows) + +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$); + result +--------------------------------------------------------------------- + t + t + t +(3 rows) + +SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$); + result +--------------------------------------------------------------------- + + + +(3 rows) + +-- tables still have valid data +SELECT COUNT(*) FROM tenant1.partitioned_table; + count +--------------------------------------------------------------------- + 20 +(1 row) + +SELECT COUNT(*) FROM citus_schema_distribute_undistribute.foreign_table; + count +--------------------------------------------------------------------- + 100 +(1 row) + +SELECT COUNT(*) FROM tenant1.table3; + count +--------------------------------------------------------------------- + 100 +(1 row) + +TRUNCATE citus_schema_distribute_undistribute.ref CASCADE; +SELECT COUNT(*) FROM tenant1.table3; + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- disallowed namespaces +SELECT citus_schema_distribute('public'); +ERROR: public schema cannot be distributed +SELECT citus_schema_distribute('pg_catalog'); +ERROR: pg_catalog schema cannot be distributed +SELECT citus_schema_distribute('pg_toast'); +ERROR: pg_toast schema cannot be distributed +CREATE TEMP TABLE xx(id int); -- create a temp table in case we do not have any pg_temp_xx schema yet +SELECT nspname AS temp_schema_name FROM pg_namespace WHERE nspname LIKE 'pg_temp%' LIMIT 1 \gset +SELECT nspname AS temp_toast_schema_name FROM pg_namespace WHERE nspname LIKE 'pg_toast_temp%' LIMIT 1 \gset +SELECT citus_schema_distribute(:'temp_schema_name'); +ERROR: temporary schema cannot be distributed +SELECT citus_schema_distribute(:'temp_toast_schema_name'); +ERROR: temporary schema cannot be distributed +SELECT citus_schema_distribute('citus'); +ERROR: schema citus, which is owned by an extension, cannot be distributed +CREATE SCHEMA extensionschema; +CREATE EXTENSION citext SCHEMA extensionschema; +SELECT citus_schema_distribute('extensionschema'); +ERROR: schema extensionschema cannot be distributed since it is the schema of extension citext +DROP SCHEMA extensionschema CASCADE; +ALTER EXTENSION citus ADD TABLE tenant1.table1; +SELECT citus_schema_distribute('tenant1'); +ERROR: schema cannot be distributed since it has table tenant1 which is owned by an extension +ALTER EXTENSION citus DROP TABLE tenant1.table1; +-- weird schema and table names +CREATE SCHEMA "CiTuS.TeeN"; +CREATE TABLE "CiTuS.TeeN"."TeeNTabLE.1!?!"(id int PRIMARY KEY, name text); +INSERT INTO "CiTuS.TeeN"."TeeNTabLE.1!?!" SELECT i, 'asd'::text FROM generate_series(1,20) i; +SELECT citus_schema_distribute('"CiTuS.TeeN"'); + citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +-- show the schema is a tenant schema now +SELECT colocationid AS tenant1_colocid FROM pg_dist_schema schemaid \gset +SELECT '$$' || + ' SELECT colocationid = ' || :tenant1_colocid || + ' FROM pg_dist_schema JOIN pg_dist_colocation USING(colocationid)' || + ' WHERE colocationid = ALL(SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE ''"CiTuS.TeeN"%'')' || + '$$' +AS verify_tenant_query \gset +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*) AS total FROM pg_dist_partition WHERE logicalrelid::text LIKE '"CiTuS.TeeN"%' $$); + result +--------------------------------------------------------------------- + 1 + 1 + 1 +(3 rows) + +SELECT result FROM run_command_on_all_nodes(:verify_tenant_query); + result +--------------------------------------------------------------------- + t + t + t +(3 rows) + +SELECT citus_schema_undistribute('"CiTuS.TeeN"'); + citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +-- show the schema is a regular schema +SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_schema $$); + result +--------------------------------------------------------------------- + + + +(3 rows) + +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$); + result +--------------------------------------------------------------------- + t + t + t +(3 rows) + +SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$); + result +--------------------------------------------------------------------- + + + +(3 rows) + +-- try setting the schema again after adding a distributed table into the schema. It should complain about distributed table. +CREATE TABLE tenant1.new_dist(id int); +SELECT create_distributed_table('tenant1.new_dist', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_schema_distribute('tenant1'); +ERROR: distributed schema cannot have distributed tables +HINT: Undistribute distributed tables before 'citus_schema_distribute'. +SELECT undistribute_table('tenant1.new_dist'); + undistribute_table +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_schema_distribute('tenant1'); + citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_schema_undistribute('tenant1'); + citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +-- try setting the schema again after adding a single shard table into the schema. It should complain about distributed table. +CREATE TABLE tenant1.single_shard_t(id int); +SELECT create_distributed_table('tenant1.single_shard_t', NULL); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_schema_distribute('tenant1'); +ERROR: distributed schema cannot have distributed tables +HINT: Undistribute distributed tables before 'citus_schema_distribute'. +SELECT undistribute_table('tenant1.single_shard_t'); + undistribute_table +--------------------------------------------------------------------- + +(1 row) + +-- try setting the schema again. It should succeed now. +SELECT citus_schema_distribute('tenant1'); + citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +-- show the schema is a tenant schema now +SELECT colocationid AS tenant1_colocid FROM pg_dist_schema schemaid \gset +SELECT '$$' || + ' SELECT colocationid = ' || :tenant1_colocid || + ' FROM pg_dist_schema JOIN pg_dist_colocation USING(colocationid)' || + ' WHERE colocationid = ALL(SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE ''tenant1%'')' || + '$$' +AS verify_tenant_query \gset +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*) AS total FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1%' $$); + result +--------------------------------------------------------------------- + 20 + 20 + 20 +(3 rows) + +SELECT result FROM run_command_on_all_nodes(:verify_tenant_query); + result +--------------------------------------------------------------------- + t + t + t +(3 rows) + +SELECT citus_schema_undistribute('tenant1'); + citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +-- show the schema is a regular schema now +SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_schema $$); + result +--------------------------------------------------------------------- + + + +(3 rows) + +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$); + result +--------------------------------------------------------------------- + t + t + t +(3 rows) + +SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$); + result +--------------------------------------------------------------------- + + + +(3 rows) + +-- create an empty tenant schema to verify colocation id is removed successfully after we undistribute it +CREATE SCHEMA empty_tenant; +SELECT citus_schema_distribute('empty_tenant'); + citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +-- show the schema is a tenant schema now +SELECT colocationid AS empty_tenant_colocid FROM pg_dist_schema schemaid \gset +SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace FROM pg_dist_colocation JOIN pg_dist_schema USING(colocationid) $$); + result +--------------------------------------------------------------------- + empty_tenant + empty_tenant + empty_tenant +(3 rows) + +SELECT citus_schema_undistribute('empty_tenant'); + citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +-- show the schema is a regular schema now +SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_schema $$); + result +--------------------------------------------------------------------- + + + +(3 rows) + +SELECT '$$' || 'SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = ' || :empty_tenant_colocid || '$$' +AS verify_empty_tenant_query \gset +SELECT result FROM run_command_on_all_nodes(:verify_empty_tenant_query); + result +--------------------------------------------------------------------- + t + t + t +(3 rows) + +-- cleanup +DROP SCHEMA "CiTuS.TeeN" CASCADE; +DROP SCHEMA tenant1 CASCADE; +DROP SCHEMA empty_tenant CASCADE; +DROP EXTENSION postgres_fdw CASCADE; +DROP SCHEMA citus_schema_distribute_undistribute CASCADE; +DROP USER tenantuser; +SELECT citus_remove_node('localhost', :master_port); + citus_remove_node +--------------------------------------------------------------------- + +(1 row) + diff --git a/src/test/regress/expected/citus_stat_tenants.out b/src/test/regress/expected/citus_stat_tenants.out index 44376dc3d..14582e55b 100644 --- a/src/test/regress/expected/citus_stat_tenants.out +++ b/src/test/regress/expected/citus_stat_tenants.out @@ -71,14 +71,17 @@ INSERT INTO dist_tbl VALUES (2, 'abcd'); UPDATE dist_tbl SET b = a + 1 WHERE a = 3; UPDATE dist_tbl SET b = a + 1 WHERE a = 4; DELETE FROM dist_tbl WHERE a = 5; -SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants(true) ORDER BY tenant_attribute; - tenant_attribute | read_count_in_this_period | read_count_in_last_period | query_count_in_this_period | query_count_in_last_period +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period, + (cpu_usage_in_this_period>0) AS cpu_is_used_in_this_period, (cpu_usage_in_last_period>0) AS cpu_is_used_in_last_period +FROM citus_stat_tenants(true) +ORDER BY tenant_attribute; + tenant_attribute | read_count_in_this_period | read_count_in_last_period | query_count_in_this_period | query_count_in_last_period | cpu_is_used_in_this_period | cpu_is_used_in_last_period --------------------------------------------------------------------- - 1 | 0 | 0 | 1 | 0 - 2 | 0 | 0 | 1 | 0 - 3 | 0 | 0 | 1 | 0 - 4 | 0 | 0 | 1 | 0 - 5 | 0 | 0 | 1 | 0 + 1 | 0 | 0 | 1 | 0 | t | f + 2 | 0 | 0 | 1 | 0 | t | f + 3 | 0 | 0 | 1 | 0 | t | f + 4 | 0 | 0 | 1 | 0 | t | f + 5 | 0 | 0 | 1 | 0 | t | f (5 rows) SELECT citus_stat_tenants_reset(); @@ -134,6 +137,18 @@ SELECT count(*)>=0 FROM dist_tbl WHERE a = 2; t (1 row) +SELECT count(*)>=0 FROM dist_tbl WHERE a = 2; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT count(*)>=0 FROM dist_tbl WHERE a = 3; + ?column? +--------------------------------------------------------------------- + t +(1 row) + SELECT count(*)>=0 FROM dist_tbl WHERE a = 3; ?column? --------------------------------------------------------------------- @@ -155,8 +170,8 @@ SELECT count(*)>=0 FROM dist_tbl_text WHERE a = 'abcd'; SELECT tenant_attribute, query_count_in_this_period, score FROM citus_stat_tenants(true) WHERE nodeid = :worker_2_nodeid ORDER BY score DESC, tenant_attribute; tenant_attribute | query_count_in_this_period | score --------------------------------------------------------------------- - 2 | 1 | 1000000000 - 3 | 1 | 1000000000 + 2 | 2 | 2000000000 + 3 | 2 | 2000000000 4 | 1 | 1000000000 abcd | 1 | 1000000000 (4 rows) @@ -189,8 +204,8 @@ SELECT tenant_attribute, query_count_in_this_period, score FROM citus_stat_tenan tenant_attribute | query_count_in_this_period | score --------------------------------------------------------------------- abcd | 3 | 3000000000 - 2 | 1 | 1000000000 - 3 | 1 | 1000000000 + 2 | 2 | 2000000000 + 3 | 2 | 2000000000 4 | 1 | 1000000000 bcde | 1 | 1000000000 cdef | 1 | 1000000000 @@ -219,20 +234,27 @@ SELECT tenant_attribute, query_count_in_this_period, score FROM citus_stat_tenan --------------------------------------------------------------------- abcd | 3 | 3000000000 bcde | 3 | 3000000000 - 2 | 1 | 1000000000 - 3 | 1 | 1000000000 - 4 | 1 | 1000000000 - cdef | 1 | 1000000000 + 2 | 2 | 2000000000 + 3 | 2 | 2000000000 defg | 1 | 1000000000 -(7 rows) +(5 rows) -- test period passing +\c - - - :worker_1_port +SET search_path TO citus_stat_tenants; +SET citus.stat_tenants_period TO 2; SELECT citus_stat_tenants_reset(); citus_stat_tenants_reset --------------------------------------------------------------------- (1 row) +SELECT sleep_until_next_period(); + sleep_until_next_period +--------------------------------------------------------------------- + +(1 row) + SELECT count(*)>=0 FROM dist_tbl WHERE a = 1; ?column? --------------------------------------------------------------------- @@ -240,27 +262,37 @@ SELECT count(*)>=0 FROM dist_tbl WHERE a = 1; (1 row) INSERT INTO dist_tbl VALUES (5, 'abcd'); -\c - - - :worker_1_port -SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants_local ORDER BY tenant_attribute; - tenant_attribute | read_count_in_this_period | read_count_in_last_period | query_count_in_this_period | query_count_in_last_period +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period, + (cpu_usage_in_this_period>0) AS cpu_is_used_in_this_period, (cpu_usage_in_last_period>0) AS cpu_is_used_in_last_period +FROM citus_stat_tenants_local +ORDER BY tenant_attribute; + tenant_attribute | read_count_in_this_period | read_count_in_last_period | query_count_in_this_period | query_count_in_last_period | cpu_is_used_in_this_period | cpu_is_used_in_last_period --------------------------------------------------------------------- - 1 | 1 | 0 | 1 | 0 - 5 | 0 | 0 | 1 | 0 + 1 | 1 | 0 | 1 | 0 | t | f + 5 | 0 | 0 | 1 | 0 | t | f (2 rows) -- simulate passing the period -SET citus.stat_tenants_period TO 2; SELECT sleep_until_next_period(); sleep_until_next_period --------------------------------------------------------------------- (1 row) -SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants_local ORDER BY tenant_attribute; - tenant_attribute | read_count_in_this_period | read_count_in_last_period | query_count_in_this_period | query_count_in_last_period +SELECT pg_sleep(1); + pg_sleep --------------------------------------------------------------------- - 1 | 0 | 1 | 0 | 1 - 5 | 0 | 0 | 0 | 1 + +(1 row) + +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period, + (cpu_usage_in_this_period>0) AS cpu_is_used_in_this_period, (cpu_usage_in_last_period>0) AS cpu_is_used_in_last_period +FROM citus_stat_tenants_local +ORDER BY tenant_attribute; + tenant_attribute | read_count_in_this_period | read_count_in_last_period | query_count_in_this_period | query_count_in_last_period | cpu_is_used_in_this_period | cpu_is_used_in_last_period +--------------------------------------------------------------------- + 1 | 0 | 1 | 0 | 1 | f | t + 5 | 0 | 0 | 0 | 1 | f | t (2 rows) SELECT sleep_until_next_period(); @@ -269,11 +301,20 @@ SELECT sleep_until_next_period(); (1 row) -SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants_local ORDER BY tenant_attribute; - tenant_attribute | read_count_in_this_period | read_count_in_last_period | query_count_in_this_period | query_count_in_last_period +SELECT pg_sleep(1); + pg_sleep --------------------------------------------------------------------- - 1 | 0 | 0 | 0 | 0 - 5 | 0 | 0 | 0 | 0 + +(1 row) + +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period, + (cpu_usage_in_this_period>0) AS cpu_is_used_in_this_period, (cpu_usage_in_last_period>0) AS cpu_is_used_in_last_period +FROM citus_stat_tenants_local +ORDER BY tenant_attribute; + tenant_attribute | read_count_in_this_period | read_count_in_last_period | query_count_in_this_period | query_count_in_last_period | cpu_is_used_in_this_period | cpu_is_used_in_last_period +--------------------------------------------------------------------- + 1 | 0 | 0 | 0 | 0 | f | f + 5 | 0 | 0 | 0 | 0 | f | f (2 rows) \c - - - :master_port @@ -427,7 +468,7 @@ SELECT count(*)>=0 FROM dist_tbl_text WHERE a = U&'\0061\0308bc'; (1 row) \c - - - :worker_1_port -SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants ORDER BY tenant_attribute; +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants(true) ORDER BY tenant_attribute; tenant_attribute | read_count_in_this_period | read_count_in_last_period | query_count_in_this_period | query_count_in_last_period --------------------------------------------------------------------- /*bcde | 1 | 0 | 1 | 0 @@ -444,7 +485,7 @@ SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, q \c - - - :worker_2_port SET search_path TO citus_stat_tenants; -SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants ORDER BY tenant_attribute; +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants(true) ORDER BY tenant_attribute; tenant_attribute | read_count_in_this_period | read_count_in_last_period | query_count_in_this_period | query_count_in_last_period --------------------------------------------------------------------- /*bcde | 1 | 0 | 1 | 0 @@ -491,13 +532,17 @@ SELECT count(*)>=0 FROM dist_tbl_text WHERE a = 'bcde*'; t (1 row) -SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants_local ORDER BY tenant_attribute; +DELETE FROM dist_tbl_text WHERE a = '/b*c/de'; +DELETE FROM dist_tbl_text WHERE a = '/bcde'; +DELETE FROM dist_tbl_text WHERE a = U&'\0061\0308bc'; +DELETE FROM dist_tbl_text WHERE a = 'bcde*'; +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants_local(true) ORDER BY tenant_attribute; tenant_attribute | read_count_in_this_period | read_count_in_last_period | query_count_in_this_period | query_count_in_last_period --------------------------------------------------------------------- - /b*c/de | 1 | 0 | 1 | 0 - /bcde | 1 | 0 | 1 | 0 - äbc | 1 | 0 | 1 | 0 - bcde* | 1 | 0 | 1 | 0 + /b*c/de | 1 | 0 | 2 | 0 + /bcde | 1 | 0 | 2 | 0 + äbc | 1 | 0 | 2 | 0 + bcde* | 1 | 0 | 2 | 0 (4 rows) -- test local cached queries & prepared statements @@ -574,13 +619,13 @@ EXECUTE dist_tbl_text_select_plan('bcde*'); t (1 row) -SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants_local ORDER BY tenant_attribute; +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants_local(true) ORDER BY tenant_attribute; tenant_attribute | read_count_in_this_period | read_count_in_last_period | query_count_in_this_period | query_count_in_last_period --------------------------------------------------------------------- - /b*c/de | 4 | 0 | 4 | 0 - /bcde | 4 | 0 | 4 | 0 - äbc | 4 | 0 | 4 | 0 - bcde* | 4 | 0 | 4 | 0 + /b*c/de | 4 | 0 | 5 | 0 + /bcde | 4 | 0 | 5 | 0 + äbc | 4 | 0 | 5 | 0 + bcde* | 4 | 0 | 5 | 0 (4 rows) \c - - - :master_port @@ -660,13 +705,13 @@ EXECUTE dist_tbl_text_select_plan('bcde*'); \c - - - :worker_2_port SET search_path TO citus_stat_tenants; -SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants ORDER BY tenant_attribute; +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants(true) ORDER BY tenant_attribute; tenant_attribute | read_count_in_this_period | read_count_in_last_period | query_count_in_this_period | query_count_in_last_period --------------------------------------------------------------------- - /b*c/de | 7 | 0 | 7 | 0 - /bcde | 7 | 0 | 7 | 0 - äbc | 7 | 0 | 7 | 0 - bcde* | 7 | 0 | 7 | 0 + /b*c/de | 7 | 0 | 8 | 0 + /bcde | 7 | 0 | 8 | 0 + äbc | 7 | 0 | 8 | 0 + bcde* | 7 | 0 | 8 | 0 (4 rows) \c - - - :master_port @@ -729,5 +774,343 @@ SELECT count(*)>=0 FROM citus_stat_tenants_local(); RESET ROLE; DROP ROLE stats_non_superuser; +-- test function push down +CREATE OR REPLACE FUNCTION + select_from_dist_tbl_text(p_keyword text) +RETURNS boolean LANGUAGE plpgsql AS $fn$ +BEGIN + RETURN(SELECT count(*)>=0 FROM citus_stat_tenants.dist_tbl_text WHERE a = $1); +END; +$fn$; +SELECT create_distributed_function( + 'select_from_dist_tbl_text(text)', 'p_keyword', colocate_with => 'dist_tbl_text' +); + create_distributed_function +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_stat_tenants_reset(); + citus_stat_tenants_reset +--------------------------------------------------------------------- + +(1 row) + +SELECT select_from_dist_tbl_text('/b*c/de'); + select_from_dist_tbl_text +--------------------------------------------------------------------- + t +(1 row) + +SELECT select_from_dist_tbl_text('/b*c/de'); + select_from_dist_tbl_text +--------------------------------------------------------------------- + t +(1 row) + +SELECT select_from_dist_tbl_text(U&'\0061\0308bc'); + select_from_dist_tbl_text +--------------------------------------------------------------------- + t +(1 row) + +SELECT select_from_dist_tbl_text(U&'\0061\0308bc'); + select_from_dist_tbl_text +--------------------------------------------------------------------- + t +(1 row) + +SELECT tenant_attribute, query_count_in_this_period FROM citus_stat_tenants ORDER BY tenant_attribute; + tenant_attribute | query_count_in_this_period +--------------------------------------------------------------------- + /b*c/de | 2 + äbc | 2 +(2 rows) + +CREATE OR REPLACE PROCEDURE select_from_dist_tbl_text_proc( + p_keyword text +) +LANGUAGE plpgsql +AS $$ +BEGIN + PERFORM select_from_dist_tbl_text(p_keyword); + PERFORM count(*)>=0 FROM citus_stat_tenants.dist_tbl_text WHERE b < 0; + PERFORM count(*)>=0 FROM citus_stat_tenants.dist_tbl_text; + PERFORM count(*)>=0 FROM citus_stat_tenants.dist_tbl_text WHERE a = p_keyword; + COMMIT; +END;$$; +CALL citus_stat_tenants.select_from_dist_tbl_text_proc('/b*c/de'); +CALL citus_stat_tenants.select_from_dist_tbl_text_proc('/b*c/de'); +CALL citus_stat_tenants.select_from_dist_tbl_text_proc('/b*c/de'); +CALL citus_stat_tenants.select_from_dist_tbl_text_proc(U&'\0061\0308bc'); +CALL citus_stat_tenants.select_from_dist_tbl_text_proc(U&'\0061\0308bc'); +CALL citus_stat_tenants.select_from_dist_tbl_text_proc(U&'\0061\0308bc'); +CALL citus_stat_tenants.select_from_dist_tbl_text_proc(NULL); +SELECT tenant_attribute, query_count_in_this_period FROM citus_stat_tenants ORDER BY tenant_attribute; + tenant_attribute | query_count_in_this_period +--------------------------------------------------------------------- + /b*c/de | 8 + äbc | 8 +(2 rows) + +CREATE OR REPLACE VIEW + select_from_dist_tbl_text_view +AS + SELECT * FROM citus_stat_tenants.dist_tbl_text; +SELECT count(*)>=0 FROM select_from_dist_tbl_text_view WHERE a = '/b*c/de'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT count(*)>=0 FROM select_from_dist_tbl_text_view WHERE a = '/b*c/de'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT count(*)>=0 FROM select_from_dist_tbl_text_view WHERE a = '/b*c/de'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT count(*)>=0 FROM select_from_dist_tbl_text_view WHERE a = U&'\0061\0308bc'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT count(*)>=0 FROM select_from_dist_tbl_text_view WHERE a = U&'\0061\0308bc'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT count(*)>=0 FROM select_from_dist_tbl_text_view WHERE a = U&'\0061\0308bc'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT tenant_attribute, query_count_in_this_period FROM citus_stat_tenants ORDER BY tenant_attribute; + tenant_attribute | query_count_in_this_period +--------------------------------------------------------------------- + /b*c/de | 11 + äbc | 11 +(2 rows) + +-- single shard distributed table, which is not part of a tenant schema +SELECT citus_stat_tenants_reset(); + citus_stat_tenants_reset +--------------------------------------------------------------------- + +(1 row) + +SET citus.shard_replication_factor TO 1; +CREATE TABLE dist_tbl_text_single_shard(a text, b int); +select create_distributed_table('dist_tbl_text_single_shard', NULL); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO dist_tbl_text_single_shard VALUES ('/b*c/de', 1); +SELECT count(*)>=0 FROM dist_tbl_text_single_shard WHERE a = '/b*c/de'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +DELETE FROM dist_tbl_text_single_shard WHERE a = '/b*c/de'; +UPDATE dist_tbl_text_single_shard SET b = 1 WHERE a = '/b*c/de'; +SELECT tenant_attribute, query_count_in_this_period FROM citus_stat_tenants; + tenant_attribute | query_count_in_this_period +--------------------------------------------------------------------- +(0 rows) + +-- schema based tenants +SELECT citus_stat_tenants_reset(); + citus_stat_tenants_reset +--------------------------------------------------------------------- + +(1 row) + +SET citus.enable_schema_based_sharding TO ON; +CREATE SCHEMA citus_stat_tenants_t1; +CREATE TABLE citus_stat_tenants_t1.users(id int); +SELECT id FROM citus_stat_tenants_t1.users WHERE id = 2; + id +--------------------------------------------------------------------- +(0 rows) + +INSERT INTO citus_stat_tenants_t1.users VALUES (1); +UPDATE citus_stat_tenants_t1.users SET id = 2 WHERE id = 1; +DELETE FROM citus_stat_tenants_t1.users WHERE id = 2; +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants ORDER BY tenant_attribute; + tenant_attribute | read_count_in_this_period | read_count_in_last_period | query_count_in_this_period | query_count_in_last_period +--------------------------------------------------------------------- + citus_stat_tenants_t1 | 1 | 0 | 4 | 0 +(1 row) + +SELECT citus_stat_tenants_reset(); + citus_stat_tenants_reset +--------------------------------------------------------------------- + +(1 row) + +PREPARE schema_tenant_insert_plan (int) AS insert into citus_stat_tenants_t1.users values ($1); +EXECUTE schema_tenant_insert_plan(1); +PREPARE schema_tenant_select_plan (int) AS SELECT count(*) > 1 FROM citus_stat_tenants_t1.users where Id = $1; +EXECUTE schema_tenant_select_plan(1); + ?column? +--------------------------------------------------------------------- + f +(1 row) + +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants ORDER BY tenant_attribute; + tenant_attribute | read_count_in_this_period | read_count_in_last_period | query_count_in_this_period | query_count_in_last_period +--------------------------------------------------------------------- + citus_stat_tenants_t1 | 1 | 0 | 2 | 0 +(1 row) + +SELECT citus_stat_tenants_reset(); + citus_stat_tenants_reset +--------------------------------------------------------------------- + +(1 row) + +-- local execution & prepared statements +\c - - - :worker_2_port +SET search_path TO citus_stat_tenants; +PREPARE schema_tenant_insert_plan (int) AS insert into citus_stat_tenants_t1.users values ($1); +EXECUTE schema_tenant_insert_plan(1); +EXECUTE schema_tenant_insert_plan(1); +EXECUTE schema_tenant_insert_plan(1); +EXECUTE schema_tenant_insert_plan(1); +EXECUTE schema_tenant_insert_plan(1); +PREPARE schema_tenant_select_plan (int) AS SELECT count(*) > 1 FROM citus_stat_tenants_t1.users where Id = $1; +EXECUTE schema_tenant_select_plan(1); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +EXECUTE schema_tenant_select_plan(1); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +EXECUTE schema_tenant_select_plan(1); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +EXECUTE schema_tenant_select_plan(1); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +EXECUTE schema_tenant_select_plan(1); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants ORDER BY tenant_attribute; + tenant_attribute | read_count_in_this_period | read_count_in_last_period | query_count_in_this_period | query_count_in_last_period +--------------------------------------------------------------------- + citus_stat_tenants_t1 | 5 | 0 | 10 | 0 +(1 row) + +\c - - - :master_port +SET search_path TO citus_stat_tenants; +SET citus.enable_schema_based_sharding TO OFF; +SELECT citus_stat_tenants_reset(); + citus_stat_tenants_reset +--------------------------------------------------------------------- + +(1 row) + +-- test sampling +-- set rate to 0 to disable sampling +SELECT result FROM run_command_on_all_nodes('ALTER SYSTEM set citus.stat_tenants_untracked_sample_rate to 0;'); + result +--------------------------------------------------------------------- + ALTER SYSTEM + ALTER SYSTEM + ALTER SYSTEM +(3 rows) + +SELECT result FROM run_command_on_all_nodes('SELECT pg_reload_conf()'); + result +--------------------------------------------------------------------- + t + t + t +(3 rows) + +INSERT INTO dist_tbl VALUES (1, 'abcd'); +INSERT INTO dist_tbl VALUES (2, 'abcd'); +UPDATE dist_tbl SET b = a + 1 WHERE a = 3; +UPDATE dist_tbl SET b = a + 1 WHERE a = 4; +DELETE FROM dist_tbl WHERE a = 5; +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants ORDER BY tenant_attribute; + tenant_attribute | read_count_in_this_period | read_count_in_last_period | query_count_in_this_period | query_count_in_last_period +--------------------------------------------------------------------- +(0 rows) + +-- test sampling +-- set rate to 1 to track all tenants +SELECT result FROM run_command_on_all_nodes('ALTER SYSTEM set citus.stat_tenants_untracked_sample_rate to 1;'); + result +--------------------------------------------------------------------- + ALTER SYSTEM + ALTER SYSTEM + ALTER SYSTEM +(3 rows) + +SELECT result FROM run_command_on_all_nodes('SELECT pg_reload_conf()'); + result +--------------------------------------------------------------------- + t + t + t +(3 rows) + +SELECT sleep_until_next_period(); + sleep_until_next_period +--------------------------------------------------------------------- + +(1 row) + +SELECT pg_sleep(0.1); + pg_sleep +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO dist_tbl VALUES (1, 'abcd'); +INSERT INTO dist_tbl VALUES (2, 'abcd'); +UPDATE dist_tbl SET b = a + 1 WHERE a = 3; +UPDATE dist_tbl SET b = a + 1 WHERE a = 4; +DELETE FROM dist_tbl WHERE a = 5; +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period, + (cpu_usage_in_this_period>0) AS cpu_is_used_in_this_period, (cpu_usage_in_last_period>0) AS cpu_is_used_in_last_period +FROM citus_stat_tenants(true) +ORDER BY tenant_attribute; + tenant_attribute | read_count_in_this_period | read_count_in_last_period | query_count_in_this_period | query_count_in_last_period | cpu_is_used_in_this_period | cpu_is_used_in_last_period +--------------------------------------------------------------------- + 1 | 0 | 0 | 1 | 0 | t | f + 2 | 0 | 0 | 1 | 0 | t | f + 3 | 0 | 0 | 1 | 0 | t | f + 4 | 0 | 0 | 1 | 0 | t | f + 5 | 0 | 0 | 1 | 0 | t | f +(5 rows) + SET client_min_messages TO ERROR; DROP SCHEMA citus_stat_tenants CASCADE; +DROP SCHEMA citus_stat_tenants_t1 CASCADE; diff --git a/src/test/regress/expected/citus_table_triggers.out b/src/test/regress/expected/citus_table_triggers.out index 80273121e..80954f70f 100644 --- a/src/test/regress/expected/citus_table_triggers.out +++ b/src/test/regress/expected/citus_table_triggers.out @@ -155,4 +155,4 @@ SELECT master_get_table_ddl_events('test_table'); -- cleanup at exit DROP SCHEMA table_triggers_schema CASCADE; -NOTICE: drop cascades to 8 other objects +NOTICE: drop cascades to 9 other objects diff --git a/src/test/regress/expected/citus_update_table_statistics.out b/src/test/regress/expected/citus_update_table_statistics.out index 031104c53..a8f90945b 100644 --- a/src/test/regress/expected/citus_update_table_statistics.out +++ b/src/test/regress/expected/citus_update_table_statistics.out @@ -64,11 +64,17 @@ SET citus.multi_shard_modify_mode TO sequential; SELECT citus_update_table_statistics('test_table_statistics_hash'); NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing SELECT 981000 AS shard_id, 'public.test_table_statistics_hash_981000' AS shard_name, pg_total_relation_size('public.test_table_statistics_hash_981000') UNION ALL SELECT 981001 AS shard_id, 'public.test_table_statistics_hash_981001' AS shard_name, pg_total_relation_size('public.test_table_statistics_hash_981001') UNION ALL SELECT 981002 AS shard_id, 'public.test_table_statistics_hash_981002' AS shard_name, pg_total_relation_size('public.test_table_statistics_hash_981002') UNION ALL SELECT 981003 AS shard_id, 'public.test_table_statistics_hash_981003' AS shard_name, pg_total_relation_size('public.test_table_statistics_hash_981003') UNION ALL SELECT 981004 AS shard_id, 'public.test_table_statistics_hash_981004' AS shard_name, pg_total_relation_size('public.test_table_statistics_hash_981004') UNION ALL SELECT 981005 AS shard_id, 'public.test_table_statistics_hash_981005' AS shard_name, pg_total_relation_size('public.test_table_statistics_hash_981005') UNION ALL SELECT 981006 AS shard_id, 'public.test_table_statistics_hash_981006' AS shard_name, pg_total_relation_size('public.test_table_statistics_hash_981006') UNION ALL SELECT 981007 AS shard_id, 'public.test_table_statistics_hash_981007' AS shard_name, pg_total_relation_size('public.test_table_statistics_hash_981007') UNION ALL SELECT 0::bigint, NULL::text, 0::bigint; +NOTICE: issuing SELECT 0::bigint, 0::bigint; DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing SELECT 981000 AS shard_id, 'public.test_table_statistics_hash_981000' AS shard_name, pg_total_relation_size('public.test_table_statistics_hash_981000') UNION ALL SELECT 981001 AS shard_id, 'public.test_table_statistics_hash_981001' AS shard_name, pg_total_relation_size('public.test_table_statistics_hash_981001') UNION ALL SELECT 981002 AS shard_id, 'public.test_table_statistics_hash_981002' AS shard_name, pg_total_relation_size('public.test_table_statistics_hash_981002') UNION ALL SELECT 981003 AS shard_id, 'public.test_table_statistics_hash_981003' AS shard_name, pg_total_relation_size('public.test_table_statistics_hash_981003') UNION ALL SELECT 981004 AS shard_id, 'public.test_table_statistics_hash_981004' AS shard_name, pg_total_relation_size('public.test_table_statistics_hash_981004') UNION ALL SELECT 981005 AS shard_id, 'public.test_table_statistics_hash_981005' AS shard_name, pg_total_relation_size('public.test_table_statistics_hash_981005') UNION ALL SELECT 981006 AS shard_id, 'public.test_table_statistics_hash_981006' AS shard_name, pg_total_relation_size('public.test_table_statistics_hash_981006') UNION ALL SELECT 981007 AS shard_id, 'public.test_table_statistics_hash_981007' AS shard_name, pg_total_relation_size('public.test_table_statistics_hash_981007') UNION ALL SELECT 0::bigint, NULL::text, 0::bigint; +NOTICE: issuing SELECT 981000 AS shard_id, pg_total_relation_size('public.test_table_statistics_hash_981000') UNION ALL SELECT 981001 AS shard_id, pg_total_relation_size('public.test_table_statistics_hash_981001') UNION ALL SELECT 981002 AS shard_id, pg_total_relation_size('public.test_table_statistics_hash_981002') UNION ALL SELECT 981003 AS shard_id, pg_total_relation_size('public.test_table_statistics_hash_981003') UNION ALL SELECT 981004 AS shard_id, pg_total_relation_size('public.test_table_statistics_hash_981004') UNION ALL SELECT 981005 AS shard_id, pg_total_relation_size('public.test_table_statistics_hash_981005') UNION ALL SELECT 981006 AS shard_id, pg_total_relation_size('public.test_table_statistics_hash_981006') UNION ALL SELECT 981007 AS shard_id, pg_total_relation_size('public.test_table_statistics_hash_981007') UNION ALL SELECT 0::bigint, 0::bigint; +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +NOTICE: issuing SELECT 981000 AS shard_id, pg_total_relation_size('public.test_table_statistics_hash_981000') UNION ALL SELECT 981001 AS shard_id, pg_total_relation_size('public.test_table_statistics_hash_981001') UNION ALL SELECT 981002 AS shard_id, pg_total_relation_size('public.test_table_statistics_hash_981002') UNION ALL SELECT 981003 AS shard_id, pg_total_relation_size('public.test_table_statistics_hash_981003') UNION ALL SELECT 981004 AS shard_id, pg_total_relation_size('public.test_table_statistics_hash_981004') UNION ALL SELECT 981005 AS shard_id, pg_total_relation_size('public.test_table_statistics_hash_981005') UNION ALL SELECT 981006 AS shard_id, pg_total_relation_size('public.test_table_statistics_hash_981006') UNION ALL SELECT 981007 AS shard_id, pg_total_relation_size('public.test_table_statistics_hash_981007') UNION ALL SELECT 0::bigint, 0::bigint; +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +NOTICE: issuing COMMIT DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing COMMIT DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx @@ -152,11 +158,17 @@ SET citus.multi_shard_modify_mode TO sequential; SELECT citus_update_table_statistics('test_table_statistics_append'); NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing SELECT 981008 AS shard_id, 'public.test_table_statistics_append_981008' AS shard_name, pg_total_relation_size('public.test_table_statistics_append_981008') UNION ALL SELECT 981009 AS shard_id, 'public.test_table_statistics_append_981009' AS shard_name, pg_total_relation_size('public.test_table_statistics_append_981009') UNION ALL SELECT 0::bigint, NULL::text, 0::bigint; +NOTICE: issuing SELECT 0::bigint, 0::bigint; DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing SELECT 981008 AS shard_id, 'public.test_table_statistics_append_981008' AS shard_name, pg_total_relation_size('public.test_table_statistics_append_981008') UNION ALL SELECT 981009 AS shard_id, 'public.test_table_statistics_append_981009' AS shard_name, pg_total_relation_size('public.test_table_statistics_append_981009') UNION ALL SELECT 0::bigint, NULL::text, 0::bigint; +NOTICE: issuing SELECT 981008 AS shard_id, pg_total_relation_size('public.test_table_statistics_append_981008') UNION ALL SELECT 981009 AS shard_id, pg_total_relation_size('public.test_table_statistics_append_981009') UNION ALL SELECT 0::bigint, 0::bigint; +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +NOTICE: issuing SELECT 981008 AS shard_id, pg_total_relation_size('public.test_table_statistics_append_981008') UNION ALL SELECT 981009 AS shard_id, pg_total_relation_size('public.test_table_statistics_append_981009') UNION ALL SELECT 0::bigint, 0::bigint; +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +NOTICE: issuing COMMIT DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing COMMIT DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx diff --git a/src/test/regress/expected/columnar_permissions.out b/src/test/regress/expected/columnar_permissions.out index d8b70d830..7f9e4e2c6 100644 --- a/src/test/regress/expected/columnar_permissions.out +++ b/src/test/regress/expected/columnar_permissions.out @@ -123,7 +123,7 @@ select relation, stripe_num, row_count, first_row_number select relation, stripe_num, attr_num, chunk_group_num, value_count from columnar.chunk where relation in ('no_access'::regclass, 'columnar_permissions'::regclass) - order by relation, stripe_num; + order by relation, stripe_num, attr_num; relation | stripe_num | attr_num | chunk_group_num | value_count --------------------------------------------------------------------- no_access | 1 | 1 | 0 | 1 diff --git a/src/test/regress/expected/coordinator_shouldhaveshards.out b/src/test/regress/expected/coordinator_shouldhaveshards.out index 3307e6bb6..46d5bf6a9 100644 --- a/src/test/regress/expected/coordinator_shouldhaveshards.out +++ b/src/test/regress/expected/coordinator_shouldhaveshards.out @@ -908,7 +908,7 @@ key FROM a JOIN table_2 USING (key) GROUP BY key HAVING (max(table_2.value) >= (SELECT value FROM a)); -DEBUG: Group by list without distribution column is not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: generating subplan XXX_1 for CTE a: SELECT key, value FROM coordinator_shouldhaveshards.table_1 ORDER BY key, value DESC LIMIT 1 DEBUG: push down of limit count: 1 DEBUG: generating subplan XXX_2 for subquery SELECT int4(count(*)) AS auto_coerced_by_citus_0, (a.key)::text AS auto_coerced_by_citus_1 FROM ((SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) a JOIN coordinator_shouldhaveshards.table_2 USING (key)) GROUP BY a.key HAVING (max(table_2.value) OPERATOR(pg_catalog.>=) (SELECT a_1.value FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) a_1)) @@ -939,7 +939,8 @@ inserts AS ( ) SELECT count(*) FROM inserts; DEBUG: generating subplan XXX_1 for CTE stats: SELECT count(key) AS m FROM coordinator_shouldhaveshards.table_1 DEBUG: generating subplan XXX_2 for CTE inserts: INSERT INTO coordinator_shouldhaveshards.table_2 (key, value) SELECT table_1.key, count(*) AS count FROM coordinator_shouldhaveshards.table_1 WHERE (table_1.key OPERATOR(pg_catalog.>=) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) GROUP BY table_1.key HAVING (count(*) OPERATOR(pg_catalog.<=) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) LIMIT 1 RETURNING table_2.key, table_2.value -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 1 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) inserts DEBUG: Subplan XXX_1 will be written to local file diff --git a/src/test/regress/expected/coordinator_shouldhaveshards_0.out b/src/test/regress/expected/coordinator_shouldhaveshards_0.out index 9b81a6a72..4c9dc0d18 100644 --- a/src/test/regress/expected/coordinator_shouldhaveshards_0.out +++ b/src/test/regress/expected/coordinator_shouldhaveshards_0.out @@ -908,7 +908,7 @@ key FROM a JOIN table_2 USING (key) GROUP BY key HAVING (max(table_2.value) >= (SELECT value FROM a)); -DEBUG: Group by list without distribution column is not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: generating subplan XXX_1 for CTE a: SELECT key, value FROM coordinator_shouldhaveshards.table_1 ORDER BY key, value DESC LIMIT 1 DEBUG: push down of limit count: 1 DEBUG: generating subplan XXX_2 for subquery SELECT int4(count(*)) AS auto_coerced_by_citus_0, (a.key)::text AS auto_coerced_by_citus_1 FROM ((SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) a JOIN coordinator_shouldhaveshards.table_2 USING (key)) GROUP BY a.key HAVING (max(table_2.value) OPERATOR(pg_catalog.>=) (SELECT a_1.value FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) a_1)) @@ -939,7 +939,8 @@ inserts AS ( ) SELECT count(*) FROM inserts; DEBUG: generating subplan XXX_1 for CTE stats: SELECT count(key) AS m FROM coordinator_shouldhaveshards.table_1 DEBUG: generating subplan XXX_2 for CTE inserts: INSERT INTO coordinator_shouldhaveshards.table_2 (key, value) SELECT key, count(*) AS count FROM coordinator_shouldhaveshards.table_1 WHERE (key OPERATOR(pg_catalog.>=) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) GROUP BY key HAVING (count(*) OPERATOR(pg_catalog.<=) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) LIMIT 1 RETURNING table_2.key, table_2.value -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 1 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) inserts DEBUG: Subplan XXX_1 will be written to local file diff --git a/src/test/regress/expected/cpu_priority.out b/src/test/regress/expected/cpu_priority.out index ad05e09f5..04bd8e0f4 100644 --- a/src/test/regress/expected/cpu_priority.out +++ b/src/test/regress/expected/cpu_priority.out @@ -85,17 +85,14 @@ SET search_path TO cpu_priority; -- in their CREATE SUBSCRIPTION commands. SET citus.log_remote_commands TO ON; SET citus.grep_remote_commands = '%CREATE SUBSCRIPTION%'; --- We disable binary protocol, so we have consistent output between PG13 and --- PG14, beacuse PG13 doesn't support binary logical replication. -SET citus.enable_binary_protocol = false; SELECT master_move_shard_placement(11568900, 'localhost', :worker_1_port, 'localhost', :worker_2_port, 'force_logical'); -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx, binary=true) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx, binary=true) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx, binary=true) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx, binary=true) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx master_move_shard_placement --------------------------------------------------------------------- @@ -104,13 +101,13 @@ DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx SET citus.cpu_priority_for_logical_replication_senders = 15; SELECT master_move_shard_placement(11568900, 'localhost', :worker_2_port, 'localhost', :worker_1_port, 'force_logical'); -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx, binary=true) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx, binary=true) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx, binary=true) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx, binary=true) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx master_move_shard_placement --------------------------------------------------------------------- @@ -119,13 +116,13 @@ DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx SET citus.max_high_priority_background_processes = 3; SELECT master_move_shard_placement(11568900, 'localhost', :worker_1_port, 'localhost', :worker_2_port, 'force_logical'); -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx, binary=true) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx, binary=true) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx, binary=true) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx_xxxxxxx, binary=true) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx master_move_shard_placement --------------------------------------------------------------------- @@ -145,21 +142,21 @@ SELECT pg_catalog.citus_split_shard_by_split_points( ARRAY['-1500000000'], ARRAY[:worker_1_node, :worker_2_node], 'force_logical'); -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx_xxxxxxx, binary=true) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx_xxxxxxx, binary=true) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx_xxxxxxx, binary=true) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx_xxxxxxx, binary=true) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx_xxxxxxx, binary=true) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx_xxxxxxx, binary=true) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx_xxxxxxx, binary=true) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx_xxxxxxx, binary=true) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx citus_split_shard_by_split_points --------------------------------------------------------------------- diff --git a/src/test/regress/expected/create_single_shard_table.out b/src/test/regress/expected/create_single_shard_table.out new file mode 100644 index 000000000..248f196ff --- /dev/null +++ b/src/test/regress/expected/create_single_shard_table.out @@ -0,0 +1,1847 @@ +CREATE SCHEMA create_single_shard_table; +SET search_path TO create_single_shard_table; +SET citus.next_shard_id TO 1720000; +SET citus.shard_count TO 32; +SET citus.shard_replication_factor TO 1; +SELECT 1 FROM citus_remove_node('localhost', :worker_1_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT 1 FROM citus_remove_node('localhost', :worker_2_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +CREATE TABLE add_node_test(a int, "b" text); +-- add a node before creating the single-shard table +SELECT 1 FROM citus_add_node('localhost', :worker_1_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT create_distributed_table('add_node_test', null, colocate_with=>'none', distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- add a node after creating the single-shard table +SELECT 1 FROM citus_add_node('localhost', :worker_2_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +-- make sure that table is created on the worker nodes added before/after create_distributed_table +SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=1 FROM pg_class WHERE relnamespace = 'create_single_shard_table'::regnamespace AND + relname='add_node_test' +$$); + result +--------------------------------------------------------------------- + t + t +(2 rows) + +-- and check the metadata tables +SELECT result FROM run_command_on_workers($$ + SELECT (partmethod, partkey, repmodel, autoconverted) FROM pg_dist_partition + WHERE logicalrelid = 'create_single_shard_table.add_node_test'::regclass +$$); + result +--------------------------------------------------------------------- + (n,,s,f) + (n,,s,f) +(2 rows) + +SELECT result FROM run_command_on_workers($$ + SELECT (shardstorage, shardminvalue, shardmaxvalue) FROM pg_dist_shard + WHERE logicalrelid = 'create_single_shard_table.add_node_test'::regclass +$$); + result +--------------------------------------------------------------------- + (t,,) + (t,,) +(2 rows) + +SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=1 FROM pg_dist_placement + WHERE shardid = ( + SELECT shardid FROM pg_dist_shard + WHERE logicalrelid = 'create_single_shard_table.add_node_test'::regclass + ); +$$); + result +--------------------------------------------------------------------- + t + t +(2 rows) + +SELECT result FROM run_command_on_workers($$ + SELECT (shardcount, replicationfactor, distributioncolumntype, distributioncolumncollation) FROM pg_dist_colocation + WHERE colocationid = ( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'create_single_shard_table.add_node_test'::regclass + ); +$$); + result +--------------------------------------------------------------------- + (1,1,0,0) + (1,1,0,0) +(2 rows) + +SET client_min_messages TO WARNING; +SELECT 1 FROM citus_add_node('localhost', :master_port, groupid => 0); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SET client_min_messages TO NOTICE; +CREATE TABLE invalid_configs_1(a int primary key); +SELECT create_distributed_table('invalid_configs_1', null, shard_count=>2); +ERROR: shard_count can't be specified when the distribution column is null because in that case it's automatically set to 1 +SELECT create_distributed_table('invalid_configs_1', null, shard_count=>1); +ERROR: shard_count can't be specified when the distribution column is null because in that case it's automatically set to 1 +CREATE TABLE nullkey_c1_t1(a int, b int); +CREATE TABLE nullkey_c1_t2(a int, b int); +CREATE TABLE nullkey_c1_t3(a int, b int); +SELECT create_distributed_table('nullkey_c1_t1', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT colocationid AS nullkey_c1_t1_colocationid FROM pg_dist_partition WHERE logicalrelid = 'create_single_shard_table.nullkey_c1_t1'::regclass \gset +BEGIN; + DROP TABLE nullkey_c1_t1; + -- make sure that we delete the colocation group after dropping the last table that belongs to it + SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = :'nullkey_c1_t1_colocationid'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +ROLLBACK; +SELECT create_distributed_table('nullkey_c1_t2', null, colocate_with=>'nullkey_c1_t1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('nullkey_c1_t3', null, colocate_with=>'nullkey_c1_t1', distribution_type=>'append'); +ERROR: distribution_type can't be specified when the distribution column is null +SELECT create_distributed_table('nullkey_c1_t3', null, colocate_with=>'nullkey_c1_t1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE nullkey_c2_t1(a int, b int); +CREATE TABLE nullkey_c2_t2(a int, b int); +CREATE TABLE nullkey_c2_t3(a int, b int); +-- create_distributed_table_concurrently is not yet supported yet +SELECT create_distributed_table_concurrently('nullkey_c2_t1', null); +ERROR: cannot use create_distributed_table_concurrently to create a distributed table with a null shard key, consider using create_distributed_table() +SELECT create_distributed_table_concurrently('nullkey_c2_t1', null, colocate_with=>'none'); +ERROR: cannot use create_distributed_table_concurrently to create a distributed table with a null shard key, consider using create_distributed_table() +SELECT create_distributed_table('nullkey_c2_t1', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('nullkey_c2_t2', null, colocate_with=>'nullkey_c2_t1', distribution_type=>'hash'); -- distribution_type is ignored anyway + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('nullkey_c2_t3', null, colocate_with=>'nullkey_c2_t2', distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- check the metadata for the colocated tables whose names start with nullkey_c1_ +SELECT logicalrelid, partmethod, partkey, repmodel, autoconverted FROM pg_dist_partition +WHERE logicalrelid IN ( + SELECT oid FROM pg_class + WHERE relnamespace = 'create_single_shard_table'::regnamespace AND + relname LIKE 'nullkey_c1_%' +) +ORDER BY 1; + logicalrelid | partmethod | partkey | repmodel | autoconverted +--------------------------------------------------------------------- + nullkey_c1_t1 | n | | s | f + nullkey_c1_t2 | n | | s | f + nullkey_c1_t3 | n | | s | f +(3 rows) + +-- make sure that all those 3 tables belong to same colocation group +SELECT COUNT(*) FROM pg_dist_partition +WHERE logicalrelid IN ( + SELECT oid FROM pg_class + WHERE relnamespace = 'create_single_shard_table'::regnamespace AND + relname LIKE 'nullkey_c1_%' +) +GROUP BY colocationid; + count +--------------------------------------------------------------------- + 3 +(1 row) + +SELECT logicalrelid, shardstorage, shardminvalue, shardmaxvalue FROM pg_dist_shard +WHERE logicalrelid IN ( + SELECT oid FROM pg_class + WHERE relnamespace = 'create_single_shard_table'::regnamespace AND + relname LIKE 'nullkey_c1_%' +) +ORDER BY 1; + logicalrelid | shardstorage | shardminvalue | shardmaxvalue +--------------------------------------------------------------------- + nullkey_c1_t1 | t | | + nullkey_c1_t2 | t | | + nullkey_c1_t3 | t | | +(3 rows) + +-- make sure that all those 3 shards are created on the same node group +SELECT COUNT(*) FROM pg_dist_placement +WHERE shardid IN ( + SELECT shardid FROM pg_dist_shard + WHERE logicalrelid IN ( + SELECT oid FROM pg_class + WHERE relnamespace = 'create_single_shard_table'::regnamespace AND + relname LIKE 'nullkey_c1_%' + ) +) +GROUP BY groupid; + count +--------------------------------------------------------------------- + 3 +(1 row) + +-- check the metadata for the colocated tables whose names start with nullkey_c2_ +SELECT logicalrelid, partmethod, partkey, repmodel, autoconverted FROM pg_dist_partition +WHERE logicalrelid IN ( + SELECT oid FROM pg_class + WHERE relnamespace = 'create_single_shard_table'::regnamespace AND + relname LIKE 'nullkey_c2_%' +) +ORDER BY 1; + logicalrelid | partmethod | partkey | repmodel | autoconverted +--------------------------------------------------------------------- + nullkey_c2_t1 | n | | s | f + nullkey_c2_t2 | n | | s | f + nullkey_c2_t3 | n | | s | f +(3 rows) + +-- make sure that all those 3 tables belong to same colocation group +SELECT COUNT(*) FROM pg_dist_partition +WHERE logicalrelid IN ( + SELECT oid FROM pg_class + WHERE relnamespace = 'create_single_shard_table'::regnamespace AND + relname LIKE 'nullkey_c2_%' +) +GROUP BY colocationid; + count +--------------------------------------------------------------------- + 3 +(1 row) + +SELECT logicalrelid, shardstorage, shardminvalue, shardmaxvalue FROM pg_dist_shard +WHERE logicalrelid IN ( + SELECT oid FROM pg_class + WHERE relnamespace = 'create_single_shard_table'::regnamespace AND + relname LIKE 'nullkey_c2_%' +) +ORDER BY 1; + logicalrelid | shardstorage | shardminvalue | shardmaxvalue +--------------------------------------------------------------------- + nullkey_c2_t1 | t | | + nullkey_c2_t2 | t | | + nullkey_c2_t3 | t | | +(3 rows) + +-- make sure that all those 3 shards created on the same node group +SELECT COUNT(*) FROM pg_dist_placement +WHERE shardid IN ( + SELECT shardid FROM pg_dist_shard + WHERE logicalrelid IN ( + SELECT oid FROM pg_class + WHERE relnamespace = 'create_single_shard_table'::regnamespace AND + relname LIKE 'nullkey_c2_%' + ) +) +GROUP BY groupid; + count +--------------------------------------------------------------------- + 3 +(1 row) + +-- Make sure that the colocated tables whose names start with nullkey_c1_ +-- belong to a different colocation group than the ones whose names start +-- with nullkey_c2_. +-- +-- It's ok to only compare nullkey_c1_t1 and nullkey_c2_t1 because we already +-- verified that null_dist_key.nullkey_c1_t1 is colocated with the other two +-- and null_dist_key.nullkey_c2_t1 is colocated with the other two. +SELECT +( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'create_single_shard_table.nullkey_c1_t1'::regclass +) +!= +( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'create_single_shard_table.nullkey_c2_t1'::regclass +); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- Since we determine node for the placement based on the module of colocation id, +-- we don't expect those two colocation groups to get assigned to same node. +SELECT +( + SELECT groupid FROM pg_dist_placement + WHERE shardid = ( + SELECT shardid FROM pg_dist_shard + WHERE logicalrelid = 'create_single_shard_table.nullkey_c1_t1'::regclass + ) +) +!= +( + SELECT groupid FROM pg_dist_placement + WHERE shardid = ( + SELECT shardid FROM pg_dist_shard + WHERE logicalrelid = 'create_single_shard_table.nullkey_c2_t1'::regclass + ) +); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- It's ok to only check nullkey_c1_t1 and nullkey_c2_t1 because we already +-- verified that null_dist_key.nullkey_c1_t1 is colocated with the other two +-- and null_dist_key.nullkey_c2_t1 is colocated with the other two. +SELECT shardcount, replicationfactor, distributioncolumntype, distributioncolumncollation FROM pg_dist_colocation +WHERE colocationid = ( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'create_single_shard_table.nullkey_c1_t1'::regclass +); + shardcount | replicationfactor | distributioncolumntype | distributioncolumncollation +--------------------------------------------------------------------- + 1 | 1 | 0 | 0 +(1 row) + +SELECT shardcount, replicationfactor, distributioncolumntype, distributioncolumncollation FROM pg_dist_colocation +WHERE colocationid = ( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'create_single_shard_table.nullkey_c2_t1'::regclass +); + shardcount | replicationfactor | distributioncolumntype | distributioncolumncollation +--------------------------------------------------------------------- + 1 | 1 | 0 | 0 +(1 row) + +CREATE TABLE round_robin_test_c1(a int, b int); +SELECT create_distributed_table('round_robin_test_c1', null, colocate_with=>'none', distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +\c - - - :master_port +SET search_path TO create_single_shard_table; +SET citus.next_shard_id TO 1730000; +SET citus.shard_count TO 32; +SET citus.shard_replication_factor TO 1; +SET client_min_messages TO NOTICE; +CREATE TABLE round_robin_test_c2(a int, b int); +SELECT create_distributed_table('round_robin_test_c2', null, colocate_with=>'none', distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- Since we determine node for the placement based on the module of colocation id, +-- we don't expect those two colocation groups to get assigned to same node even +-- after reconnecting to the coordinator. +SELECT +( + SELECT groupid FROM pg_dist_placement + WHERE shardid = ( + SELECT shardid FROM pg_dist_shard + WHERE logicalrelid = 'create_single_shard_table.round_robin_test_c1'::regclass + ) +) +!= +( + SELECT groupid FROM pg_dist_placement + WHERE shardid = ( + SELECT shardid FROM pg_dist_shard + WHERE logicalrelid = 'create_single_shard_table.round_robin_test_c2'::regclass + ) +); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +CREATE TABLE distributed_table(a int, b int); +-- cannot colocate a sharded table with single-shard table +SELECT create_distributed_table('distributed_table', 'a', colocate_with=>'nullkey_c1_t1'); +ERROR: cannot colocate tables nullkey_c1_t1 and distributed_table +DETAIL: Distribution column types don't match for nullkey_c1_t1 and distributed_table. +CREATE TABLE reference_table(a int, b int); +CREATE TABLE local(a int, b int); +SELECT create_reference_table('reference_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('distributed_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- cannot colocate single-shard tables with other table types +CREATE TABLE cannot_colocate_with_other_types (a int, b int); +SELECT create_distributed_table('cannot_colocate_with_other_types', null, colocate_with=>'reference_table'); +ERROR: cannot colocate tables reference_table and cannot_colocate_with_other_types +DETAIL: Replication models don't match for reference_table and cannot_colocate_with_other_types. +SELECT create_distributed_table('cannot_colocate_with_other_types', null, colocate_with=>'distributed_table'); +ERROR: cannot colocate tables distributed_table and cannot_colocate_with_other_types +DETAIL: Distribution column types don't match for distributed_table and cannot_colocate_with_other_types. +SELECT create_distributed_table('cannot_colocate_with_other_types', null, colocate_with=>'local'); -- postgres local +ERROR: relation local is not distributed +SELECT citus_add_local_table_to_metadata('local'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +-- cannot colocate single-shard tables with citus local tables +SELECT create_distributed_table('cannot_colocate_with_other_types', null, colocate_with=>'local'); -- citus local +ERROR: cannot distribute relation +DETAIL: Currently, colocate_with option is not supported with append / range distributed tables and local tables added to metadata. +SET client_min_messages TO WARNING; +-- can't create such a distributed table from another Citus table, except Citus local tables +SELECT create_distributed_table('reference_table', null, colocate_with=>'none'); +ERROR: table "reference_table" is already distributed +SELECT create_distributed_table('distributed_table', null, colocate_with=>'none'); +ERROR: table "distributed_table" is already distributed +SELECT create_distributed_table('local', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +BEGIN; + -- creating a single-shard table from a temporary table is not supported + CREATE TEMPORARY TABLE temp_table (a int); + SELECT create_distributed_table('temp_table', null, colocate_with=>'none', distribution_type=>null); +ERROR: cannot distribute a temporary table +ROLLBACK; +-- creating a single-shard table from a catalog table is not supported +SELECT create_distributed_table('pg_catalog.pg_index', NULL, distribution_type=>null); +ERROR: cannot create a citus table from a catalog table +-- creating a single-shard table from an unlogged table is supported +CREATE UNLOGGED TABLE unlogged_table (a int); +SELECT create_distributed_table('unlogged_table', null, colocate_with=>'none', distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- creating a single-shard table from a foreign table is not supported +CREATE FOREIGN TABLE foreign_table ( + id bigint not null, + full_name text not null default '' +) SERVER fake_fdw_server OPTIONS (encoding 'utf-8', compression 'true', table_name 'foreign_table'); +SELECT create_distributed_table('foreign_table', null, colocate_with=>'none', distribution_type=>null); +ERROR: foreign tables cannot be distributed +HINT: Can add foreign table "foreign_table" to metadata by running: SELECT citus_add_local_table_to_metadata($$create_single_shard_table.foreign_table$$); +-- create a single-shard table that has no tuples +CREATE TABLE null_dist_key_table_1 (a int primary key); +SELECT create_distributed_table('null_dist_key_table_1', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- create a single-shard table that has some tuples +CREATE TABLE null_dist_key_table_2(a int primary key); +INSERT INTO null_dist_key_table_2 VALUES(1); +SELECT create_distributed_table('null_dist_key_table_2', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT * FROM null_dist_key_table_2 ORDER BY a; + a +--------------------------------------------------------------------- + 1 +(1 row) + +DROP TABLE null_dist_key_table_1, null_dist_key_table_2; +-- create indexes before creating the single-shard tables +-- .. for an initially empty table +CREATE TABLE null_dist_key_table_1(a int, b int); +CREATE STATISTICS s1 (dependencies) ON a, b FROM null_dist_key_table_1; +CREATE INDEX null_dist_key_table_1_idx ON null_dist_key_table_1(a); +SELECT create_distributed_table('null_dist_key_table_1', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE STATISTICS s2 (dependencies) ON a, b FROM null_dist_key_table_1; +-- .. and for another table having data in it before creating single-shard table +CREATE TABLE null_dist_key_table_2(a int); +INSERT INTO null_dist_key_table_2 VALUES(1); +CREATE INDEX null_dist_key_table_2_idx ON null_dist_key_table_2(a); +SELECT create_distributed_table('null_dist_key_table_2', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- test create index concurrently, then reindex +CREATE INDEX CONCURRENTLY ind_conc ON null_dist_key_table_2(a); +REINDEX INDEX ind_conc; +REINDEX INDEX CONCURRENTLY ind_conc; +DROP INDEX ind_conc; +SELECT * FROM null_dist_key_table_2 ORDER BY a; + a +--------------------------------------------------------------------- + 1 +(1 row) + +-- show that we do not support inheritance relationships +CREATE TABLE parent_table (a int, b text); +CREATE TABLE child_table () INHERITS (parent_table); +-- both of below should error out +SELECT create_distributed_table('parent_table', null, colocate_with=>'none'); +ERROR: parent_table is not a regular, foreign or partitioned table +SELECT create_distributed_table('child_table', null, colocate_with=>'none'); +ERROR: child_table is not a regular, foreign or partitioned table +-- show that we support policies +BEGIN; + CREATE TABLE null_dist_key_table_3 (table_user text); + ALTER TABLE null_dist_key_table_3 ENABLE ROW LEVEL SECURITY; + CREATE ROLE table_users; + CREATE POLICY table_policy ON null_dist_key_table_3 TO table_users + USING (table_user = current_user); + GRANT ALL ON TABLE null_dist_key_table_3 TO table_users; + ALTER TABLE null_dist_key_table_3 OWNER TO table_users; + SELECT create_distributed_table('null_dist_key_table_3', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + REVOKE ALL ON TABLE null_dist_key_table_3 FROM table_users; + ALTER TABLE null_dist_key_table_3 OWNER TO postgres; + GRANT ALL ON TABLE null_dist_key_table_3 TO table_users; +ROLLBACK; +ALTER STATISTICS s2 SET STATISTICS 46; +ALTER TABLE null_dist_key_table_1 SET SCHEMA public; +DROP STATISTICS s1, s2; +-- drop them for next tests +DROP TABLE public.null_dist_key_table_1, null_dist_key_table_2, distributed_table; +-- tests for object names that should be escaped properly +CREATE SCHEMA "NULL_!_dist_key"; +CREATE TABLE "NULL_!_dist_key"."my_TABLE.1!?!"(id int, "Second_Id" int); +SELECT create_distributed_table('"NULL_!_dist_key"."my_TABLE.1!?!"', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- drop the table before creating it when the search path is set +SET search_path to "NULL_!_dist_key" ; +DROP TABLE "my_TABLE.1!?!"; +CREATE TYPE int_jsonb_type AS (key int, value jsonb); +CREATE DOMAIN age_with_default AS int CHECK (value >= 0) DEFAULT 0; +CREATE TYPE yes_no_enum AS ENUM ('yes', 'no'); +CREATE EXTENSION btree_gist; +CREATE SEQUENCE my_seq_1 START WITH 10; +CREATE TABLE "Table?!.1Table"( + id int PRIMARY KEY, + "Second_Id" int, + "local_Type" int_jsonb_type, + "jsondata" jsonb NOT NULL, + name text, + price numeric CHECK (price > 0), + age_with_default_col age_with_default, + yes_no_enum_col yes_no_enum, + seq_col_1 bigserial, + seq_col_2 int DEFAULT nextval('my_seq_1'), + generated_column int GENERATED ALWAYS AS (seq_col_1 * seq_col_2 + 4) STORED, + UNIQUE (id, price), + EXCLUDE USING GIST (name WITH =)); +-- create some objects before create_distributed_table +CREATE INDEX "my!Index1" ON "Table?!.1Table"(id) WITH ( fillfactor = 80 ) WHERE id > 10; +CREATE INDEX text_index ON "Table?!.1Table"(name); +CREATE UNIQUE INDEX uniqueIndex ON "Table?!.1Table" (id); +CREATE STATISTICS stats_1 ON id, price FROM "Table?!.1Table"; +CREATE TEXT SEARCH CONFIGURATION text_search_cfg (parser = default); +CREATE INDEX text_search_index ON "Table?!.1Table" +USING gin (to_tsvector('text_search_cfg'::regconfig, (COALESCE(name, ''::character varying))::text)); +-- ingest some data before create_distributed_table +INSERT INTO "Table?!.1Table" VALUES (1, 1, (1, row_to_json(row(1,1)))::int_jsonb_type, row_to_json(row(1,1), true)), + (2, 1, (2, row_to_json(row(2,2)))::int_jsonb_type, row_to_json(row(2,2), 'false')); +-- create a replica identity before create_distributed_table +ALTER TABLE "Table?!.1Table" REPLICA IDENTITY USING INDEX uniqueIndex; +SELECT create_distributed_table('"Table?!.1Table"', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO "Table?!.1Table" VALUES (10, 15, (150, row_to_json(row(4,8)))::int_jsonb_type, '{}', 'text_1', 10, 27, 'yes', 60, 70); +INSERT INTO "Table?!.1Table" VALUES (5, 5, (5, row_to_json(row(5,5)))::int_jsonb_type, row_to_json(row(5,5), true)); +-- tuples that are supposed to violate different data type / check constraints +INSERT INTO "Table?!.1Table"(id, jsondata, name) VALUES (101, '{"a": 1}', 'text_1'); +ERROR: conflicting key value violates exclusion constraint "Table?!.1Table_name_excl_1730043" +DETAIL: Key (name)=(text_1) conflicts with existing key (name)=(text_1). +CONTEXT: while executing command on localhost:xxxxx +INSERT INTO "Table?!.1Table"(id, jsondata, price) VALUES (101, '{"a": 1}', -1); +ERROR: new row for relation "Table?!.1Table_1730043" violates check constraint "Table?!.1Table_price_check" +DETAIL: Failing row contains (101, null, null, {"a": 1}, null, -1, 0, null, 5, 14, 74). +CONTEXT: while executing command on localhost:xxxxx +INSERT INTO "Table?!.1Table"(id, jsondata, age_with_default_col) VALUES (101, '{"a": 1}', -1); +ERROR: value for domain age_with_default violates check constraint "age_with_default_check" +INSERT INTO "Table?!.1Table"(id, jsondata, yes_no_enum_col) VALUES (101, '{"a": 1}', 'what?'); +ERROR: invalid input value for enum yes_no_enum: "what?" +SELECT * FROM "Table?!.1Table" ORDER BY id; + id | Second_Id | local_Type | jsondata | name | price | age_with_default_col | yes_no_enum_col | seq_col_1 | seq_col_2 | generated_column +--------------------------------------------------------------------- + 1 | 1 | (1,"{""f1"": 1, ""f2"": 1}") | {"f1": 1, "f2": 1} | | | 0 | | 1 | 10 | 14 + 2 | 1 | (2,"{""f1"": 2, ""f2"": 2}") | {"f1": 2, "f2": 2} | | | 0 | | 2 | 11 | 26 + 5 | 5 | (5,"{""f1"": 5, ""f2"": 5}") | {"f1": 5, "f2": 5} | | | 0 | | 3 | 12 | 40 + 10 | 15 | (150,"{""f1"": 4, ""f2"": 8}") | {} | text_1 | 10 | 27 | yes | 60 | 70 | 4204 +(4 rows) + +SET search_path TO create_single_shard_table; +-- create a partitioned table with some columns that +-- are going to be dropped within the tests +CREATE TABLE sensors( + col_to_drop_1 text, + measureid integer, + eventdatetime date, + measure_data jsonb, +PRIMARY KEY (measureid, eventdatetime, measure_data)) +PARTITION BY RANGE(eventdatetime); +-- drop column even before attaching any partitions +ALTER TABLE sensors DROP COLUMN col_to_drop_1; +CREATE TABLE sensors_2000 PARTITION OF sensors FOR VALUES FROM ('2000-01-01') TO ('2001-01-01'); +-- cannot distribute child table without distributing the parent +SELECT create_distributed_table('sensors_2000', NULL, distribution_type=>null); +ERROR: cannot distribute relation "sensors_2000" which is partition of "sensors" +DETAIL: Citus does not support distributing partitions if their parent is not distributed table. +HINT: Distribute the partitioned table "sensors" instead. +SELECT create_distributed_table('sensors', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- verify we can create new partitions after distributing the parent table +CREATE TABLE sensors_2001 PARTITION OF sensors FOR VALUES FROM ('2001-01-01') TO ('2002-01-01'); +-- verify we can attach to a single-shard table +CREATE TABLE sensors_2002 (measureid integer, eventdatetime date, measure_data jsonb, PRIMARY KEY (measureid, eventdatetime, measure_data)); +ALTER TABLE sensors ATTACH PARTITION sensors_2002 FOR VALUES FROM ('2002-01-01') TO ('2003-01-01'); +-- verify we can detach from a single-shard table +ALTER TABLE sensors DETACH PARTITION sensors_2001; +-- error out when attaching a noncolocated partition +CREATE TABLE sensors_2003 (measureid integer, eventdatetime date, measure_data jsonb, PRIMARY KEY (measureid, eventdatetime, measure_data)); +SELECT create_distributed_table('sensors_2003', NULL, distribution_type=>null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE sensors ATTACH PARTITION sensors_2003 FOR VALUES FROM ('2003-01-01') TO ('2004-01-01'); +ERROR: distributed tables cannot have non-colocated distributed tables as a partition +DROP TABLE sensors_2003; +-- verify we can attach after distributing, if the parent and partition are colocated +CREATE TABLE sensors_2004 (measureid integer, eventdatetime date, measure_data jsonb, PRIMARY KEY (measureid, eventdatetime, measure_data)); +SELECT create_distributed_table('sensors_2004', NULL, distribution_type=>null, colocate_with=>'sensors'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE sensors ATTACH PARTITION sensors_2004 FOR VALUES FROM ('2004-01-01') TO ('2005-01-01'); +-- verify we can attach a citus local table +CREATE TABLE sensors_2005 (measureid integer, eventdatetime date, measure_data jsonb, PRIMARY KEY (measureid, eventdatetime, measure_data)); +SELECT citus_add_local_table_to_metadata('sensors_2005'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE sensors ATTACH PARTITION sensors_2005 FOR VALUES FROM ('2005-01-01') TO ('2006-01-01'); +-- check metadata +-- check all partitions and the parent on pg_dist_partition +SELECT logicalrelid::text FROM pg_dist_partition WHERE logicalrelid::text IN ('sensors', 'sensors_2000', 'sensors_2001', 'sensors_2002', 'sensors_2004', 'sensors_2005') ORDER BY logicalrelid::text; + logicalrelid +--------------------------------------------------------------------- + sensors + sensors_2000 + sensors_2001 + sensors_2002 + sensors_2004 + sensors_2005 +(6 rows) + +-- verify they are all colocated +SELECT COUNT(DISTINCT(colocationid)) FROM pg_dist_partition WHERE logicalrelid::text IN ('sensors', 'sensors_2000', 'sensors_2001', 'sensors_2002', 'sensors_2004', 'sensors_2005'); + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- verify all partitions are placed on the same node +SELECT COUNT(DISTINCT(groupid)) FROM pg_dist_placement WHERE shardid IN + (SELECT shardid FROM pg_dist_shard WHERE logicalrelid::text IN ('sensors', 'sensors_2000', 'sensors_2001', 'sensors_2002', 'sensors_2004', 'sensors_2005')); + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- verify the shard of sensors_2000 is attached to the parent shard, on the worker node +SELECT COUNT(*) FROM run_command_on_workers($$ + SELECT relpartbound FROM pg_class WHERE relname LIKE 'sensors_2000_1______';$$) + WHERE length(result) > 0; + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- verify the shard of sensors_2001 is detached from the parent shard, on the worker node +SELECT COUNT(*) FROM run_command_on_workers($$ + SELECT relpartbound FROM pg_class WHERE relname LIKE 'sensors_2001_1______';$$) + WHERE length(result) > 0; + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- verify the shard of sensors_2002 is attached to the parent shard, on the worker node +SELECT COUNT(*) FROM run_command_on_workers($$ + SELECT relpartbound FROM pg_class WHERE relname LIKE 'sensors_2002_1______';$$) + WHERE length(result) > 0; + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- create a partitioned citus local table and verify we error out when attaching a partition with single-shard +CREATE TABLE partitioned_citus_local_tbl( + measureid integer, + eventdatetime date, + measure_data jsonb, +PRIMARY KEY (measureid, eventdatetime, measure_data)) +PARTITION BY RANGE(eventdatetime); +SELECT citus_add_local_table_to_metadata('partitioned_citus_local_tbl'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE partition_with_null_key (measureid integer, eventdatetime date, measure_data jsonb, PRIMARY KEY (measureid, eventdatetime, measure_data)); +SELECT create_distributed_table('partition_with_null_key', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE partitioned_citus_local_tbl ATTACH PARTITION partition_with_null_key FOR VALUES FROM ('2004-01-01') TO ('2005-01-01'); +ERROR: non-distributed partitioned tables cannot have distributed partitions +-- test partitioned tables + indexes with long names +CREATE TABLE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"( + id int PRIMARY KEY, + "TeNANt_Id" int, + "jsondata" jsonb NOT NULL, + name text, + price numeric CHECK (price > 0), + serial_data bigserial, UNIQUE (id, price)) + PARTITION BY LIST(id); +CREATE TABLE "NULL_!_dist_key"."partition1_nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + PARTITION OF "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + FOR VALUES IN (1); +CREATE TABLE "NULL_!_dist_key"."partition2_nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + PARTITION OF "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + FOR VALUES IN (2); +CREATE TABLE "NULL_!_dist_key"."partition100_nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + PARTITION OF "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + FOR VALUES IN (100); +-- create some objects before create_distributed_table +CREATE INDEX "my!Index1New" ON "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"(id) WITH ( fillfactor = 80 ) WHERE id > 10; +CREATE UNIQUE INDEX uniqueIndexNew ON "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" (id); +-- ingest some data before create_distributed_table +set client_min_messages to ERROR; +INSERT INTO "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" VALUES (1, 1, row_to_json(row(1,1), true)), + (2, 1, row_to_json(row(2,2), 'false')); +reset client_min_messages; +-- create a replica identity before create_distributed_table +ALTER TABLE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" REPLICA IDENTITY USING INDEX uniqueIndexNew; +NOTICE: identifier "nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" will be truncated to "nullKeyTable.1!?!9012345678901234567890123456789012345678901234" +-- test triggers +SET client_min_messages TO ERROR; +CREATE FUNCTION insert_id_100() RETURNS trigger AS $insert_100$ +BEGIN + INSERT INTO "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" VALUES (100, 1, row_to_json(row(1,1), true)); + RETURN NEW; +END; +$insert_100$ LANGUAGE plpgsql; +CREATE TABLE null_key_table_with_trigger(a INT); +SELECT create_distributed_table('null_key_table_with_trigger', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- try to add a trigger after distributing the table, fails +CREATE TRIGGER insert_100_trigger + AFTER UPDATE ON null_key_table_with_trigger + FOR EACH STATEMENT EXECUTE FUNCTION insert_id_100(); +ERROR: triggers are not supported on distributed tables +-- now try to distribute a table that already has a trigger on it +CREATE TRIGGER insert_100_trigger + AFTER UPDATE ON "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + FOR EACH STATEMENT EXECUTE FUNCTION insert_id_100(); +-- error out because of the trigger +SELECT create_distributed_table('"NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"', null); +ERROR: cannot distribute relation "nullKeyTable.1!?!9012345678901234567890123456789012345678901234" because it has triggers +HINT: Consider dropping all the triggers on "nullKeyTable.1!?!9012345678901234567890123456789012345678901234" and retry. +SET citus.enable_unsafe_triggers TO ON; +RESET client_min_messages; +-- this shouldn't give any syntax errors +SELECT create_distributed_table('"NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"', null); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$"NULL_!_dist_key"."partition1_nullKeyTable.1!?!90123456789012345678901234567890123"$$) +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$"NULL_!_dist_key"."partition2_nullKeyTable.1!?!90123456789012345678901234567890123"$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- now we can add triggers on distributed tables, because we set the GUC to on +CREATE TRIGGER insert_100_trigger_2 + AFTER UPDATE ON null_key_table_with_trigger + FOR EACH STATEMENT EXECUTE FUNCTION insert_id_100(); +SET client_min_messages TO ERROR; +UPDATE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" SET "TeNANt_Id"="TeNANt_Id"+1; +-- we should see one row with id = 100 +SELECT COUNT(*) FROM "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" WHERE id = 100; + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- create some objects after create_distributed_table +CREATE INDEX "my!Index2New" ON "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"(id) WITH ( fillfactor = 90 ) WHERE id < 20; +CREATE UNIQUE INDEX uniqueIndex2New ON "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"(id); +-- error out for already existing, because of the unique index +INSERT INTO "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" VALUES (1, 1, row_to_json(row(1,1), true)); +ERROR: duplicate key value violates unique constraint "partition1_nullKeyTable.1!?!901234567890123456_bf4a8ac1_1730056" +DETAIL: Key (id)=(X) already exists. +CONTEXT: while executing command on localhost:xxxxx +-- verify all 4 shard indexes are created on the same node +SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*) FROM pg_indexes WHERE indexname LIKE '%my!Index_New_1%' OR indexname LIKE '%uniqueindex%new_1%';$$) + ORDER BY nodeport; + result +--------------------------------------------------------------------- + 4 + 0 +(2 rows) + +-- foreign key to a ref table +CREATE TABLE dummy_reference_table (a INT PRIMARY KEY); +SELECT create_reference_table('dummy_reference_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +TRUNCATE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"; +ALTER TABLE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + ADD CONSTRAINT fkey_to_dummy_ref FOREIGN KEY (id) REFERENCES dummy_reference_table(a); +BEGIN; -- try to add the same fkey, reversed + ALTER TABLE dummy_reference_table + ADD CONSTRAINT fkey_to_dummy_ref FOREIGN KEY (a) REFERENCES "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"(id); +ERROR: cannot create foreign key constraint since foreign keys from reference tables and local tables to distributed tables are not supported +DETAIL: Reference tables and local tables can only have foreign keys to reference tables and local tables +ROLLBACK; +-- errors out because of foreign key violation +INSERT INTO "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" VALUES (100, 1, row_to_json(row(1,1), true)); +ERROR: insert or update on table "partition100_nullKeyTable.1!?!9012345678901234_0aba0bf3_1730058" violates foreign key constraint "fkey_to_dummy_ref_1730055" +DETAIL: Key (id)=(X) is not present in table "dummy_reference_table_1730059". +CONTEXT: while executing command on localhost:xxxxx +-- now inserts successfully +INSERT INTO dummy_reference_table VALUES (100); +INSERT INTO "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" VALUES (100, 1, row_to_json(row(1,1), true)); +DELETE FROM "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" WHERE id = 100; +-- foreign key to a local table, errors out +CREATE TABLE local_table_for_fkey (a INT PRIMARY KEY); +ALTER TABLE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + ADD CONSTRAINT fkey_to_dummy_local FOREIGN KEY (id) REFERENCES local_table_for_fkey(a); +ERROR: referenced table "local_table_for_fkey" must be a distributed table or a reference table +DETAIL: To enforce foreign keys, the referencing and referenced rows need to be stored on the same node. +HINT: You could use SELECT create_reference_table('local_table_for_fkey') to replicate the referenced table to all nodes or consider dropping the foreign key +-- foreign key from a local table +ALTER TABLE local_table_for_fkey + ADD CONSTRAINT fkey_from_dummy_local FOREIGN KEY (a) REFERENCES "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"(id); +SELECT create_distributed_table('local_table_for_fkey', null, colocate_with=>'none'); +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table +SELECT create_distributed_table('local_table_for_fkey', null, colocate_with=>'"NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- foreign key to a citus local table, errors out +CREATE TABLE citus_local_table_for_fkey (a INT PRIMARY KEY); +SELECT citus_add_local_table_to_metadata('citus_local_table_for_fkey'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + ADD CONSTRAINT fkey_to_dummy_citus_local FOREIGN KEY (id) REFERENCES citus_local_table_for_fkey(a); +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table +-- reversed, still fails +ALTER TABLE citus_local_table_for_fkey + ADD CONSTRAINT fkey_from_dummy_citus_local FOREIGN KEY (a) REFERENCES "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"(id); +ERROR: cannot create foreign key constraint since foreign keys from reference tables and local tables to distributed tables are not supported +DETAIL: Reference tables and local tables can only have foreign keys to reference tables and local tables +-- foreign key to a distributed table, errors out because not colocated +CREATE TABLE dist_table_for_fkey (a INT PRIMARY KEY); +SELECT create_distributed_table('dist_table_for_fkey', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + ADD CONSTRAINT fkey_to_dummy_dist FOREIGN KEY (id) REFERENCES dist_table_for_fkey(a); +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table +-- reversed, still fails +ALTER TABLE dist_table_for_fkey + ADD CONSTRAINT fkey_to_dummy_dist FOREIGN KEY (a) REFERENCES "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"(id); +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table +-- create a null key distributed table, not colocated with the partitioned table, and then try to create a fkey +CREATE TABLE null_key_dist_not_colocated (a INT PRIMARY KEY); +SELECT create_distributed_table('null_key_dist_not_colocated', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + ADD CONSTRAINT fkey_to_dummy_dist FOREIGN KEY (id) REFERENCES null_key_dist_not_colocated(a); +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table +-- create a null key distributed table, colocated with the partitioned table, and then create a fkey +CREATE TABLE null_key_dist (a INT PRIMARY KEY); +SELECT create_distributed_table('null_key_dist', null, colocate_with=>'"NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + ADD CONSTRAINT fkey_to_dummy_dist FOREIGN KEY (id) REFERENCES null_key_dist(a); +-- check supported ON DELETE and ON UPDATE commands +ALTER TABLE null_key_dist ADD CONSTRAINT fkey_add_test_1 FOREIGN KEY(a) + REFERENCES "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"(id) ON DELETE SET DEFAULT; +ALTER TABLE null_key_dist ADD CONSTRAINT fkey_add_test_2 FOREIGN KEY(a) + REFERENCES "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"(id) ON UPDATE CASCADE; +ALTER TABLE null_key_dist ADD CONSTRAINT fkey_add_test_3 FOREIGN KEY(a) + REFERENCES dummy_reference_table(a) ON DELETE SET DEFAULT; +ALTER TABLE null_key_dist ADD CONSTRAINT fkey_add_test_4 FOREIGN KEY(a) + REFERENCES dummy_reference_table(a) ON UPDATE CASCADE; +ALTER TABLE null_key_dist DROP CONSTRAINT fkey_add_test_1; +ALTER TABLE null_key_dist DROP CONSTRAINT fkey_add_test_2; +ALTER TABLE null_key_dist DROP CONSTRAINT fkey_add_test_3; +-- mimic a conjoined django command +CREATE FUNCTION drop_fkey_4() + RETURNS void + LANGUAGE plpgsql +AS $function$ +BEGIN + EXECUTE $$ + SET CONSTRAINTS fkey_add_test_4 IMMEDIATE; + ALTER TABLE null_key_dist DROP CONSTRAINT fkey_add_test_4; + $$; +END +$function$; +SELECT drop_fkey_4(); + drop_fkey_4 +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" DROP CONSTRAINT fkey_to_dummy_dist; +-- create a view that depends on the single-shard table +CREATE VIEW public.v1 AS SELECT * FROM null_key_dist; +SELECT * FROM public.v1; + a +--------------------------------------------------------------------- +(0 rows) + +DELETE FROM null_key_dist; +VACUUM null_key_dist; +TRUNCATE null_key_dist; +DROP TABLE null_key_dist CASCADE; +RESET client_min_messages; +CREATE TABLE multi_level_partitioning_parent( + measureid integer, + eventdatetime date, + measure_data jsonb) +PARTITION BY RANGE(eventdatetime); +CREATE TABLE multi_level_partitioning_level_1( + measureid integer, + eventdatetime date, + measure_data jsonb) +PARTITION BY RANGE(eventdatetime); +ALTER TABLE multi_level_partitioning_parent ATTACH PARTITION multi_level_partitioning_level_1 +FOR VALUES FROM ('2000-01-01') TO ('2001-01-01'); +CREATE TABLE multi_level_partitioning_level_2 PARTITION OF multi_level_partitioning_level_1 +FOR VALUES FROM ('2000-01-01') TO ('2000-06-06'); +-- multi-level partitioning is not supported +SELECT create_distributed_table('multi_level_partitioning_parent', NULL, distribution_type=>null); +ERROR: distributing multi-level partitioned tables is not supported +DETAIL: Relation "multi_level_partitioning_level_1" is partitioned table itself and it is also partition of relation "multi_level_partitioning_parent". +CREATE FUNCTION normalize_generate_always_as_error(query text) RETURNS void AS $$ +BEGIN + EXECUTE query; + EXCEPTION WHEN OTHERS THEN + IF SQLERRM LIKE 'cannot insert into column %' OR + SQLERRM LIKE 'cannot insert a non-DEFAULT value into column %' + THEN + RAISE 'cannot insert a non-DEFAULT value into column'; + ELSE + RAISE 'unknown error'; + END IF; +END; +$$LANGUAGE plpgsql; +CREATE TABLE identity_test ( + a int GENERATED BY DEFAULT AS IDENTITY (START WITH 10 INCREMENT BY 10), + b bigint GENERATED ALWAYS AS IDENTITY (START WITH 100 INCREMENT BY 100), + c bigint GENERATED BY DEFAULT AS IDENTITY (START WITH 1000 INCREMENT BY 1000) +); +SELECT create_distributed_table('identity_test', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO identity_test (a) VALUES (DEFAULT) RETURNING a; + a +--------------------------------------------------------------------- + 10 +(1 row) + +SELECT result FROM run_command_on_workers($$ + INSERT INTO create_single_shard_table.identity_test (a) VALUES (DEFAULT) +$$); + result +--------------------------------------------------------------------- + ERROR: nextval: reached maximum value of sequence "identity_test_a_seq" (2147483647) + ERROR: nextval: reached maximum value of sequence "identity_test_a_seq" (2147483647) +(2 rows) + +DROP TABLE identity_test; +CREATE TABLE identity_test ( + a bigint GENERATED BY DEFAULT AS IDENTITY (START WITH 10 INCREMENT BY 10), + b bigint GENERATED ALWAYS AS IDENTITY (START WITH 100 INCREMENT BY 100), + c bigint GENERATED BY DEFAULT AS IDENTITY (START WITH 1000 INCREMENT BY 1000) +); +SELECT create_distributed_table('identity_test', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO identity_test (a) VALUES (5); +SELECT normalize_generate_always_as_error($$INSERT INTO identity_test (b) VALUES (5)$$); -- fails due to missing OVERRIDING SYSTEM VALUE +ERROR: cannot insert a non-DEFAULT value into column +CONTEXT: PL/pgSQL function normalize_generate_always_as_error(text) line XX at RAISE +INSERT INTO identity_test (b) OVERRIDING SYSTEM VALUE VALUES (5); +INSERT INTO identity_test (c) VALUES (5); +SELECT result, success FROM run_command_on_workers($$ + INSERT INTO create_single_shard_table.identity_test (a) VALUES (6) +$$); + result | success +--------------------------------------------------------------------- + INSERT 0 1 | t + INSERT 0 1 | t +(2 rows) + +SELECT result, success FROM run_command_on_workers($$ + SELECT create_single_shard_table.normalize_generate_always_as_error('INSERT INTO create_single_shard_table.identity_test (b) VALUES (1)') +$$); + result | success +--------------------------------------------------------------------- + ERROR: cannot insert a non-DEFAULT value into column | f + ERROR: cannot insert a non-DEFAULT value into column | f +(2 rows) + +-- This should fail due to missing OVERRIDING SYSTEM VALUE. +SELECT result, success FROM run_command_on_workers($$ + SELECT create_single_shard_table.normalize_generate_always_as_error('INSERT INTO create_single_shard_table.identity_test (a, b) VALUES (1, 1)') +$$); + result | success +--------------------------------------------------------------------- + ERROR: cannot insert a non-DEFAULT value into column | f + ERROR: cannot insert a non-DEFAULT value into column | f +(2 rows) + +SELECT result, success FROM run_command_on_workers($$ + INSERT INTO create_single_shard_table.identity_test (a, b) OVERRIDING SYSTEM VALUE VALUES (7, 7) +$$); + result | success +--------------------------------------------------------------------- + INSERT 0 1 | t + INSERT 0 1 | t +(2 rows) + +SELECT result, success FROM run_command_on_workers($$ + INSERT INTO create_single_shard_table.identity_test (c, a) OVERRIDING SYSTEM VALUE VALUES (8, 8) +$$); + result | success +--------------------------------------------------------------------- + INSERT 0 1 | t + INSERT 0 1 | t +(2 rows) + +-- test foreign keys +CREATE TABLE referenced_table(a int UNIQUE, b int); +CREATE TABLE referencing_table(a int, b int, + FOREIGN KEY (a) REFERENCES referenced_table(a)); +-- to a colocated single-shard table +BEGIN; + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'referenced_table'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + INSERT INTO referenced_table VALUES (1, 1); + INSERT INTO referencing_table VALUES (1, 2); + -- fails + INSERT INTO referencing_table VALUES (2, 2); +ERROR: insert or update on table "referencing_table_xxxxxxx" violates foreign key constraint "referencing_table_a_fkey_1730100" +DETAIL: Key (a)=(2) is not present in table "referenced_table_xxxxxxx". +CONTEXT: while executing command on localhost:xxxxx +ROLLBACK; +-- to a non-colocated single-shard table +BEGIN; + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'none'); +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table +ROLLBACK; +-- to a sharded table +BEGIN; + SELECT create_distributed_table('referenced_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null); +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table +ROLLBACK; +-- to a reference table +BEGIN; + SELECT create_reference_table('referenced_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + INSERT INTO referenced_table VALUES (1, 1); + INSERT INTO referencing_table VALUES (1, 2); + -- fails + INSERT INTO referencing_table VALUES (2, 2); +ERROR: insert or update on table "referencing_table_xxxxxxx" violates foreign key constraint "referencing_table_a_fkey_1730136" +DETAIL: Key (a)=(2) is not present in table "referenced_table_xxxxxxx". +CONTEXT: while executing command on localhost:xxxxx +ROLLBACK; +-- to a citus local table +BEGIN; + SELECT citus_add_local_table_to_metadata('referenced_table', cascade_via_foreign_keys=>true); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null); +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table +ROLLBACK; +-- to a postgres table +SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null); +ERROR: referenced table "referenced_table" must be a distributed table or a reference table +DETAIL: To enforce foreign keys, the referencing and referenced rows need to be stored on the same node. +HINT: You could use SELECT create_reference_table('referenced_table') to replicate the referenced table to all nodes or consider dropping the foreign key +-- from a reference table +BEGIN; + SELECT create_reference_table('referencing_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); +ERROR: cannot create foreign key constraint since foreign keys from reference tables and local tables to distributed tables are not supported +DETAIL: Reference tables and local tables can only have foreign keys to reference tables and local tables +ROLLBACK; +BEGIN; + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + SELECT create_reference_table('referencing_table'); +ERROR: cannot create foreign key constraint since foreign keys from reference tables and local tables to distributed tables are not supported +DETAIL: Reference tables and local tables can only have foreign keys to reference tables and local tables +ROLLBACK; +-- from a sharded table +BEGIN; + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + SELECT create_distributed_table('referencing_table', 'a'); +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table +ROLLBACK; +-- from a citus local table +BEGIN; + SELECT citus_add_local_table_to_metadata('referencing_table', cascade_via_foreign_keys=>true); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); +ERROR: cannot create foreign key constraint since foreign keys from reference tables and local tables to distributed tables are not supported +DETAIL: Reference tables and local tables can only have foreign keys to reference tables and local tables +ROLLBACK; +BEGIN; + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + SELECT citus_add_local_table_to_metadata('referencing_table', cascade_via_foreign_keys=>true); +ERROR: cannot create foreign key constraint since foreign keys from reference tables and local tables to distributed tables are not supported +DETAIL: Reference tables and local tables can only have foreign keys to reference tables and local tables +ROLLBACK; +-- from a postgres table (only useful to preserve legacy behavior) +BEGIN; + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +ROLLBACK; +-- make sure that we enforce the foreign key constraint when inserting from workers too +SELECT create_reference_table('referenced_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO referenced_table VALUES (1, 1); +-- ok +SELECT result, success FROM run_command_on_workers($$ + INSERT INTO create_single_shard_table.referencing_table VALUES (1, 2) +$$); + result | success +--------------------------------------------------------------------- + INSERT 0 1 | t + INSERT 0 1 | t +(2 rows) + +-- fails +SELECT result, success FROM run_command_on_workers($$ + INSERT INTO create_single_shard_table.referencing_table VALUES (2, 2) +$$); + result | success +--------------------------------------------------------------------- + ERROR: insert or update on table "referencing_table_xxxxxxx" violates foreign key constraint "referencing_table_a_fkey_1730153" | f + ERROR: insert or update on table "referencing_table_xxxxxxx" violates foreign key constraint "referencing_table_a_fkey_1730153" | f +(2 rows) + +DROP TABLE referencing_table, referenced_table; +CREATE TABLE self_fkey_test(a int UNIQUE, b int, + FOREIGN KEY (b) REFERENCES self_fkey_test(a), + FOREIGN KEY (a) REFERENCES self_fkey_test(a)); +SELECT create_distributed_table('self_fkey_test', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO self_fkey_test VALUES (1, 1); -- ok +INSERT INTO self_fkey_test VALUES (2, 3); -- fails +ERROR: insert or update on table "self_fkey_test_1730154" violates foreign key constraint "self_fkey_test_b_fkey_1730154" +DETAIL: Key (b)=(3) is not present in table "self_fkey_test_1730154". +CONTEXT: while executing command on localhost:xxxxx +-- similar foreign key tests but this time create the referencing table later on +-- referencing table is a single-shard table +-- to a colocated single-shard table +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a)); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'referenced_table'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + INSERT INTO referenced_table VALUES (1, 1); + INSERT INTO referencing_table VALUES (1, 2); + -- fails + INSERT INTO referencing_table VALUES (2, 2); +ERROR: insert or update on table "referencing_table_xxxxxxx" violates foreign key constraint "referencing_table_a_fkey_1730156" +DETAIL: Key (a)=(2) is not present in table "referenced_table_xxxxxxx". +CONTEXT: while executing command on localhost:xxxxx +ROLLBACK; +BEGIN; + CREATE TABLE referenced_table(a int, b int, UNIQUE(b, a)); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a, b) REFERENCES referenced_table(b, a)); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'referenced_table'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + INSERT INTO referenced_table VALUES (1, 2); + INSERT INTO referencing_table VALUES (2, 1); + -- fails + INSERT INTO referencing_table VALUES (1, 2); +ERROR: insert or update on table "referencing_table_xxxxxxx" violates foreign key constraint "referencing_table_a_b_fkey_1730158" +DETAIL: Key (a, b)=(1, 2) is not present in table "referenced_table_xxxxxxx". +CONTEXT: while executing command on localhost:xxxxx +ROLLBACK; +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a) ON UPDATE SET NULL); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'referenced_table'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + INSERT INTO referenced_table VALUES (1, 1); + INSERT INTO referencing_table VALUES (1, 2); + UPDATE referenced_table SET a = 5; + SELECT * FROM referencing_table; + a | b +--------------------------------------------------------------------- + | 2 +(1 row) + +ROLLBACK; +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + CREATE TABLE referencing_table(a serial, b int, FOREIGN KEY (a) REFERENCES referenced_table(a) ON UPDATE SET DEFAULT); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'referenced_table'); +ERROR: cannot create foreign key constraint since Citus does not support ON DELETE / UPDATE SET DEFAULT actions on the columns that default to sequences +ROLLBACK; +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + CREATE TABLE referencing_table(a serial, b int); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'referenced_table'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + ALTER TABLE referencing_table ADD CONSTRAINT fkey_to_dummy_ref_on_update FOREIGN KEY (a) REFERENCES referenced_table(a) ON UPDATE SET DEFAULT; +ERROR: cannot create foreign key constraint since Citus does not support ON DELETE / UPDATE SET DEFAULT actions on the columns that default to sequences +ROLLBACK; +-- to a non-colocated single-shard table +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a)); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'none'); +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table +ROLLBACK; +-- to a sharded table +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_distributed_table('referenced_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a)); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'none'); +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table +ROLLBACK; +-- to a reference table +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_reference_table('referenced_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a)); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + INSERT INTO referenced_table VALUES (1, 1); + INSERT INTO referencing_table VALUES (1, 2); + -- fails + INSERT INTO referencing_table VALUES (2, 2); +ERROR: insert or update on table "referencing_table_xxxxxxx" violates foreign key constraint "referencing_table_a_fkey_1730199" +DETAIL: Key (a)=(2) is not present in table "referenced_table_xxxxxxx". +CONTEXT: while executing command on localhost:xxxxx +ROLLBACK; +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_reference_table('referenced_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a) ON DELETE CASCADE); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + INSERT INTO referenced_table VALUES (1, 1); + INSERT INTO referencing_table VALUES (1, 2); + DELETE FROM referenced_table CASCADE; + SELECT * FROM referencing_table; + a | b +--------------------------------------------------------------------- +(0 rows) + +ROLLBACK; +-- to a citus local table +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT citus_add_local_table_to_metadata('referenced_table'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a)); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'none'); +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table +ROLLBACK; +-- to a postgres table +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a)); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'none'); +ERROR: referenced table "referenced_table" must be a distributed table or a reference table +DETAIL: To enforce foreign keys, the referencing and referenced rows need to be stored on the same node. +HINT: You could use SELECT create_reference_table('referenced_table') to replicate the referenced table to all nodes or consider dropping the foreign key +ROLLBACK; +-- referenced table is a single-shard table +-- from a sharded table +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a)); + SELECT create_distributed_table('referencing_table', 'a'); +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table +ROLLBACK; +-- from a reference table +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a)); + SELECT create_reference_table('referencing_table'); +ERROR: cannot create foreign key constraint since foreign keys from reference tables and local tables to distributed tables are not supported +DETAIL: Reference tables and local tables can only have foreign keys to reference tables and local tables +ROLLBACK; +-- from a citus local table +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a)); + SELECT citus_add_local_table_to_metadata('referencing_table', cascade_via_foreign_keys=>true); +ERROR: cannot create foreign key constraint since foreign keys from reference tables and local tables to distributed tables are not supported +DETAIL: Reference tables and local tables can only have foreign keys to reference tables and local tables +ROLLBACK; +-- from a postgres table (only useful to preserve legacy behavior) +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a)); +ROLLBACK; +-- Test whether we switch to sequential execution to enforce foreign +-- key restrictions. +CREATE TABLE referenced_table(id int PRIMARY KEY, value_1 int); +SELECT create_reference_table('referenced_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE referencing_table(id int PRIMARY KEY, value_1 int, CONSTRAINT fkey FOREIGN KEY(value_1) REFERENCES referenced_table(id) ON UPDATE CASCADE); +SELECT create_distributed_table('referencing_table', null, colocate_with=>'none', distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SET client_min_messages TO DEBUG1; +BEGIN; + -- Switches to sequential execution because referenced_table is a reference table + -- and referenced by a single-shard table. + -- + -- Given that we cannot do parallel access on a single-shard table, this is not useful. + -- However, this is already what we're doing for, e.g., a foreign key from a + -- reference table to another reference table. + TRUNCATE referenced_table CASCADE; +DEBUG: switching to sequential query execution mode +DETAIL: Table "referenced_table" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed tables due to foreign keys. Any parallel modification to those hash distributed tables in the same transaction can only be executed in sequential query execution mode +NOTICE: truncate cascades to table "referencing_table" +DEBUG: truncate cascades to table "referencing_table_xxxxxxx" +DETAIL: from localhost:xxxxx + SELECT COUNT(*) FROM referencing_table; + count +--------------------------------------------------------------------- + 0 +(1 row) + +COMMIT; +BEGIN; + SELECT COUNT(*) FROM referencing_table; + count +--------------------------------------------------------------------- + 0 +(1 row) + + -- Doesn't fail because the SELECT didn't perform parallel execution. + TRUNCATE referenced_table CASCADE; +DEBUG: switching to sequential query execution mode +DETAIL: Table "referenced_table" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed tables due to foreign keys. Any parallel modification to those hash distributed tables in the same transaction can only be executed in sequential query execution mode +NOTICE: truncate cascades to table "referencing_table" +DEBUG: truncate cascades to table "referencing_table_xxxxxxx" +DETAIL: from localhost:xxxxx +COMMIT; +BEGIN; + UPDATE referencing_table SET value_1 = 15; + -- Doesn't fail because the UPDATE didn't perform parallel execution. + TRUNCATE referenced_table CASCADE; +DEBUG: switching to sequential query execution mode +DETAIL: Table "referenced_table" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed tables due to foreign keys. Any parallel modification to those hash distributed tables in the same transaction can only be executed in sequential query execution mode +NOTICE: truncate cascades to table "referencing_table" +DEBUG: truncate cascades to table "referencing_table_xxxxxxx" +DETAIL: from localhost:xxxxx +COMMIT; +BEGIN; + SELECT COUNT(*) FROM referenced_table; + count +--------------------------------------------------------------------- + 0 +(1 row) + + -- doesn't switch to sequential execution + ALTER TABLE referencing_table ADD COLUMN X INT; +ROLLBACK; +BEGIN; + -- Switches to sequential execution because referenced_table is a reference table + -- and referenced by a single-shard table. + -- + -- Given that we cannot do parallel access on a single-shard table, this is not useful. + -- However, this is already what we're doing for, e.g., a foreign key from a + -- reference table to another reference table. + UPDATE referenced_table SET id = 101 WHERE id = 99; +DEBUG: switching to sequential query execution mode +DETAIL: Table "referenced_table" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed tables due to foreign keys. Any parallel modification to those hash distributed tables in the same transaction can only be executed in sequential query execution mode + UPDATE referencing_table SET value_1 = 15; +ROLLBACK; +BEGIN; + UPDATE referencing_table SET value_1 = 15; + -- Doesn't fail because prior UPDATE didn't perform parallel execution. + UPDATE referenced_table SET id = 101 WHERE id = 99; +DEBUG: switching to sequential query execution mode +DETAIL: Table "referenced_table" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed tables due to foreign keys. Any parallel modification to those hash distributed tables in the same transaction can only be executed in sequential query execution mode +ROLLBACK; +SET client_min_messages TO WARNING; +DROP TABLE referenced_table, referencing_table; +-- Test whether we unnecessarily switch to sequential execution +-- when the referenced relation is a single-shard table. +CREATE TABLE referenced_table(id int PRIMARY KEY, value_1 int); +SELECT create_distributed_table('referenced_table', null, colocate_with=>'none', distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE referencing_table(id int PRIMARY KEY, value_1 int, CONSTRAINT fkey FOREIGN KEY(value_1) REFERENCES referenced_table(id) ON UPDATE CASCADE); +SELECT create_distributed_table('referencing_table', null, colocate_with=>'referenced_table', distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SET client_min_messages TO DEBUG1; +BEGIN; + SELECT COUNT(*) FROM referenced_table; + count +--------------------------------------------------------------------- + 0 +(1 row) + + -- Doesn't switch to sequential execution because the referenced_table is + -- a single-shard table. + ALTER TABLE referencing_table ADD COLUMN X INT; +ROLLBACK; +BEGIN; + -- Doesn't switch to sequential execution because the referenced_table is + -- a single-shard table. + TRUNCATE referenced_table CASCADE; +NOTICE: truncate cascades to table "referencing_table" +DEBUG: truncate cascades to table "referencing_table_xxxxxxx" +DETAIL: from localhost:xxxxx + SELECT COUNT(*) FROM referencing_table; + count +--------------------------------------------------------------------- + 0 +(1 row) + +COMMIT; +SET client_min_messages TO WARNING; +CREATE FUNCTION increment_value() RETURNS trigger AS $increment_value$ +BEGIN + NEW.value := NEW.value+1; + RETURN NEW; +END; +$increment_value$ LANGUAGE plpgsql; +CREATE TABLE trigger_table_1 (value int); +CREATE TRIGGER trigger_1 +BEFORE INSERT ON trigger_table_1 +FOR EACH ROW EXECUTE FUNCTION increment_value(); +SELECT create_distributed_table('trigger_table_1', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO trigger_table_1 VALUES(1), (2); +SELECT * FROM trigger_table_1 ORDER BY value; + value +--------------------------------------------------------------------- + 2 + 3 +(2 rows) + +CREATE FUNCTION insert_some() RETURNS trigger AS $insert_some$ +BEGIN + RAISE NOTICE 'inserted some rows'; + RETURN NEW; +END; +$insert_some$ LANGUAGE plpgsql; +CREATE TABLE trigger_table_2 (value int); +CREATE TRIGGER trigger_2 +AFTER INSERT ON trigger_table_2 +FOR EACH STATEMENT EXECUTE FUNCTION insert_some(); +ALTER TABLE trigger_table_2 DISABLE TRIGGER trigger_2; +SELECT create_distributed_table('trigger_table_2', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SET client_min_messages TO NOTICE; +INSERT INTO trigger_table_2 VALUES(3), (4); +SET client_min_messages TO WARNING; +SELECT * FROM trigger_table_2 ORDER BY value; + value +--------------------------------------------------------------------- + 3 + 4 +(2 rows) + +CREATE FUNCTION combine_old_new_val() RETURNS trigger AS $combine_old_new_val$ +BEGIN + NEW.value = NEW.value * 10 + OLD.value; + RETURN NEW; +END; +$combine_old_new_val$ LANGUAGE plpgsql; +CREATE FUNCTION notice_truncate() RETURNS trigger AS $notice_truncate$ +BEGIN + RAISE NOTICE 'notice_truncate()'; + RETURN NEW; +END; +$notice_truncate$ LANGUAGE plpgsql; +CREATE TABLE trigger_table_3 (value int); +CREATE TRIGGER trigger_3 +BEFORE UPDATE ON trigger_table_3 +FOR EACH ROW EXECUTE FUNCTION combine_old_new_val(); +CREATE TRIGGER trigger_4 +AFTER TRUNCATE ON trigger_table_3 +FOR EACH STATEMENT EXECUTE FUNCTION notice_truncate(); +INSERT INTO trigger_table_3 VALUES(3), (4); +SELECT create_distributed_table('trigger_table_3', NULL, distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +UPDATE trigger_table_3 SET value = 5; +SELECT * FROM trigger_table_3 ORDER BY value; + value +--------------------------------------------------------------------- + 53 + 54 +(2 rows) + +SET client_min_messages TO NOTICE; +TRUNCATE trigger_table_3; +NOTICE: notice_truncate() +CONTEXT: PL/pgSQL function notice_truncate() line XX at RAISE +SET client_min_messages TO WARNING; +-- test rename, disable and drop trigger +ALTER TRIGGER trigger_4 ON trigger_table_3 RENAME TO trigger_new_name; +ALTER TABLE trigger_table_3 DISABLE TRIGGER ALL; +DROP TRIGGER trigger_new_name ON trigger_table_3; +-- enable the remaining triggers +ALTER TABLE trigger_table_3 ENABLE TRIGGER ALL; +-- try a few simple queries at least to make sure that we don't crash +BEGIN; + INSERT INTO nullkey_c1_t1 SELECT * FROM nullkey_c2_t1; +ROLLBACK; +DROP TRIGGER IF EXISTS trigger_1 ON trigger_table_1; +DROP TRIGGER trigger_2 ON trigger_table_2 CASCADE; +DROP TRIGGER trigger_3 ON trigger_table_3 RESTRICT; +-- cleanup at exit +SET client_min_messages TO ERROR; +DROP SCHEMA create_single_shard_table, "NULL_!_dist_key" CASCADE; diff --git a/src/test/regress/expected/distributed_collations.out b/src/test/regress/expected/distributed_collations.out index 0c03c8be7..5e7247b0a 100644 --- a/src/test/regress/expected/distributed_collations.out +++ b/src/test/regress/expected/distributed_collations.out @@ -75,9 +75,9 @@ NOTICE: renaming the new table to collation_tests.test_collate_pushed_down_aggr SET citus.log_remote_commands TO true; SELECT ALL MIN((lower(CAST(test_collate_pushed_down_aggregate.a AS VARCHAR)) COLLATE "C")) FROM ONLY test_collate_pushed_down_aggregate; -NOTICE: issuing SELECT min((lower(((a)::character varying COLLATE "default")) COLLATE "C")) AS min FROM ONLY collation_tests.test_collate_pushed_down_aggregate_20060004 test_collate_pushed_down_aggregate WHERE true +NOTICE: issuing SELECT min((lower(((a)::character varying)::text) COLLATE "C")) AS min FROM ONLY collation_tests.test_collate_pushed_down_aggregate_20060004 test_collate_pushed_down_aggregate WHERE true DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing SELECT min((lower(((a)::character varying COLLATE "default")) COLLATE "C")) AS min FROM ONLY collation_tests.test_collate_pushed_down_aggregate_20060005 test_collate_pushed_down_aggregate WHERE true +NOTICE: issuing SELECT min((lower(((a)::character varying)::text) COLLATE "C")) AS min FROM ONLY collation_tests.test_collate_pushed_down_aggregate_20060005 test_collate_pushed_down_aggregate WHERE true DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx min --------------------------------------------------------------------- diff --git a/src/test/regress/expected/distributed_functions.out b/src/test/regress/expected/distributed_functions.out index 516ff97e6..668f97c3f 100644 --- a/src/test/regress/expected/distributed_functions.out +++ b/src/test/regress/expected/distributed_functions.out @@ -704,6 +704,25 @@ WHERE pg_dist_partition.logicalrelid = 'replicated_table_func_test_4'::regclass t (1 row) +-- a function cannot be colocated with a single shard distributed table when a distribution column is provided +SELECT create_distributed_table('replicated_table_func_test_3', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_function('eq_with_param_names(macaddr, macaddr)', 'val1', colocate_with:='replicated_table_func_test_3'); +ERROR: cannot colocate function "eq_with_param_names" and table "replicated_table_func_test_3" because distribution arguments are not supported when colocating with single shard distributed tables. +SELECT undistribute_table('replicated_table_func_test_3'); +NOTICE: creating a new table for function_tests.replicated_table_func_test_3 +NOTICE: moving the data of function_tests.replicated_table_func_test_3 +NOTICE: dropping the old function_tests.replicated_table_func_test_3 +NOTICE: renaming the new table to function_tests.replicated_table_func_test_3 + undistribute_table +--------------------------------------------------------------------- + +(1 row) + -- a function cannot be colocated with a reference table when a distribution column is provided SELECT create_reference_table('replicated_table_func_test_3'); create_reference_table @@ -1099,7 +1118,7 @@ SET client_min_messages TO error; -- suppress cascading objects dropping DROP SCHEMA function_tests CASCADE; DROP SCHEMA function_tests2 CASCADE; -- clear objects -SELECT stop_metadata_sync_to_node(nodename,nodeport) FROM pg_dist_node WHERE isactive AND noderole = 'primary'; +SELECT stop_metadata_sync_to_node(nodename,nodeport) FROM pg_dist_node WHERE isactive AND noderole = 'primary' AND groupid <> 0; stop_metadata_sync_to_node --------------------------------------------------------------------- @@ -1116,6 +1135,7 @@ TRUNCATE pg_dist_node; \c - - - :master_port SET client_min_messages TO ERROR; DROP USER functionuser; +DROP ROLE r1; SELECT 1 FROM run_command_on_workers($$DROP USER functionuser$$); ?column? --------------------------------------------------------------------- @@ -1124,7 +1144,7 @@ SELECT 1 FROM run_command_on_workers($$DROP USER functionuser$$); (2 rows) -- sync metadata again -SELECT start_metadata_sync_to_node(nodename,nodeport) FROM pg_dist_node WHERE isactive AND noderole = 'primary'; +SELECT start_metadata_sync_to_node(nodename,nodeport) FROM pg_dist_node WHERE isactive AND noderole = 'primary' AND groupid <> 0; start_metadata_sync_to_node --------------------------------------------------------------------- diff --git a/src/test/regress/expected/dml_recursive.out b/src/test/regress/expected/dml_recursive.out index b1d521ca2..cc4058def 100644 --- a/src/test/regress/expected/dml_recursive.out +++ b/src/test/regress/expected/dml_recursive.out @@ -357,9 +357,10 @@ DEBUG: generating subplan XXX_1 for subquery SELECT tenant_id FROM recursive_dm DEBUG: Plan XXX query after replacing subqueries and CTEs: UPDATE recursive_dml_queries.local_table SET id = 'citus_test'::text FROM (SELECT distributed_table_1.tenant_id, NULL::integer AS dept, NULL::jsonb AS info FROM (SELECT intermediate_result.tenant_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(tenant_id text)) distributed_table_1) distributed_table WHERE (distributed_table.tenant_id OPERATOR(pg_catalog.=) local_table.id) RESET client_min_messages; DROP SCHEMA recursive_dml_queries CASCADE; -NOTICE: drop cascades to 5 other objects +NOTICE: drop cascades to 6 other objects DETAIL: drop cascades to table distributed_table drop cascades to table second_distributed_table drop cascades to table reference_table +drop cascades to table reference_table_2370008 drop cascades to table local_table drop cascades to view tenant_ids diff --git a/src/test/regress/expected/failure_add_disable_node.out b/src/test/regress/expected/failure_add_disable_node.out index d2a389d96..395ad5097 100644 --- a/src/test/regress/expected/failure_add_disable_node.out +++ b/src/test/regress/expected/failure_add_disable_node.out @@ -187,6 +187,8 @@ ORDER BY placementid; (1 row) -- reset cluster to original state +ALTER SEQUENCE pg_dist_node_nodeid_seq RESTART 2; +ALTER SEQUENCE pg_dist_groupid_seq RESTART 2; SELECT citus.mitmproxy('conn.allow()'); mitmproxy --------------------------------------------------------------------- @@ -196,7 +198,7 @@ SELECT citus.mitmproxy('conn.allow()'); SELECT master_add_node('localhost', :worker_2_proxy_port); master_add_node --------------------------------------------------------------------- - 4 + 2 (1 row) -- verify node is added diff --git a/src/test/regress/expected/failure_create_distributed_table_concurrently.out b/src/test/regress/expected/failure_create_distributed_table_concurrently.out index bd3382256..cf1df651a 100644 --- a/src/test/regress/expected/failure_create_distributed_table_concurrently.out +++ b/src/test/regress/expected/failure_create_distributed_table_concurrently.out @@ -12,6 +12,8 @@ SET citus.shard_count TO 2; SET citus.shard_replication_factor TO 1; SET citus.max_adaptive_executor_pool_size TO 1; SELECT pg_backend_pid() as pid \gset +ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 222222; +ALTER SEQUENCE pg_catalog.pg_dist_placement_placementid_seq RESTART 333333; -- make sure coordinator is in the metadata SELECT citus_set_coordinator_host('localhost', 57636); citus_set_coordinator_host @@ -189,8 +191,8 @@ SELECT create_distributed_table_concurrently('table_1', 'id'); SELECT * FROM pg_dist_shard WHERE logicalrelid = 'table_1'::regclass; logicalrelid | shardid | shardstorage | shardminvalue | shardmaxvalue --------------------------------------------------------------------- - table_1 | 1880080 | t | -2147483648 | -1 - table_1 | 1880081 | t | 0 | 2147483647 + table_1 | 222247 | t | -2147483648 | -1 + table_1 | 222248 | t | 0 | 2147483647 (2 rows) DROP SCHEMA create_dist_tbl_con CASCADE; @@ -201,3 +203,5 @@ SELECT citus_remove_node('localhost', 57636); (1 row) +ALTER SEQUENCE pg_dist_node_nodeid_seq RESTART 3; +ALTER SEQUENCE pg_dist_groupid_seq RESTART 3; diff --git a/src/test/regress/expected/failure_mx_metadata_sync_multi_trans.out b/src/test/regress/expected/failure_mx_metadata_sync_multi_trans.out index fd0fc632b..541bce5c5 100644 --- a/src/test/regress/expected/failure_mx_metadata_sync_multi_trans.out +++ b/src/test/regress/expected/failure_mx_metadata_sync_multi_trans.out @@ -19,10 +19,30 @@ SET client_min_messages TO ERROR; -- Create roles CREATE ROLE foo1; CREATE ROLE foo2; +-- Create collation +CREATE COLLATION german_phonebook (provider = icu, locale = 'de-u-co-phonebk'); +-- Create type +CREATE TYPE pair_type AS (a int, b int); +-- Create function +CREATE FUNCTION one_as_result() RETURNS INT LANGUAGE SQL AS +$$ + SELECT 1; +$$; +-- Create text search dictionary +CREATE TEXT SEARCH DICTIONARY my_german_dict ( + template = snowball, + language = german, + stopwords = german +); +-- Create text search config +CREATE TEXT SEARCH CONFIGURATION my_ts_config ( parser = default ); +ALTER TEXT SEARCH CONFIGURATION my_ts_config ALTER MAPPING FOR asciiword WITH my_german_dict; -- Create sequence CREATE SEQUENCE seq; -- Create colocated distributed tables -CREATE TABLE dist1 (id int PRIMARY KEY default nextval('seq')); +CREATE TABLE dist1 (id int PRIMARY KEY default nextval('seq'), col int default (one_as_result()), myserial serial, phone text COLLATE german_phonebook, initials pair_type); +CREATE SEQUENCE seq_owned OWNED BY dist1.id; +CREATE INDEX dist1_search_phone_idx ON dist1 USING gin (to_tsvector('my_ts_config'::regconfig, (COALESCE(phone, ''::text))::text)); SELECT create_distributed_table('dist1', 'id'); create_distributed_table --------------------------------------------------------------------- @@ -52,6 +72,8 @@ CREATE TABLE loc1 (id int PRIMARY KEY); INSERT INTO loc1 SELECT i FROM generate_series(1,100) i; CREATE TABLE loc2 (id int REFERENCES loc1(id)); INSERT INTO loc2 SELECT i FROM generate_series(1,100) i; +-- Create publication +CREATE PUBLICATION pub_all; -- citus_set_coordinator_host with wrong port SELECT citus_set_coordinator_host('localhost', 9999); citus_set_coordinator_host @@ -168,8 +190,8 @@ SELECT citus.mitmproxy('conn.onQuery(query="INSERT INTO pg_dist_node").kill()'); SELECT citus_activate_node('localhost', :worker_2_proxy_port); ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open --- Failure to drop sequence -SELECT citus.mitmproxy('conn.onQuery(query="SELECT pg_catalog.worker_drop_sequence_dependency").cancel(' || :pid || ')'); +-- Failure to drop sequence dependency for all tables +SELECT citus.mitmproxy('conn.onQuery(query="SELECT pg_catalog.worker_drop_sequence_dependency.*FROM pg_dist_partition").cancel(' || :pid || ')'); mitmproxy --------------------------------------------------------------------- @@ -177,7 +199,7 @@ SELECT citus.mitmproxy('conn.onQuery(query="SELECT pg_catalog.worker_drop_sequen SELECT citus_activate_node('localhost', :worker_2_proxy_port); ERROR: canceling statement due to user request -SELECT citus.mitmproxy('conn.onQuery(query="SELECT pg_catalog.worker_drop_sequence_dependency").kill()'); +SELECT citus.mitmproxy('conn.onQuery(query="SELECT pg_catalog.worker_drop_sequence_dependency.*FROM pg_dist_partition").kill()'); mitmproxy --------------------------------------------------------------------- @@ -321,7 +343,7 @@ SELECT citus.mitmproxy('conn.onQuery(query="ALTER DATABASE.*OWNER TO").kill()'); SELECT citus_activate_node('localhost', :worker_2_proxy_port); ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open --- Filure to create schema +-- Failure to create schema SELECT citus.mitmproxy('conn.onQuery(query="CREATE SCHEMA IF NOT EXISTS mx_metadata_sync_multi_trans AUTHORIZATION").cancel(' || :pid || ')'); mitmproxy --------------------------------------------------------------------- @@ -336,6 +358,108 @@ SELECT citus.mitmproxy('conn.onQuery(query="CREATE SCHEMA IF NOT EXISTS mx_metad (1 row) +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +-- Failure to create collation +SELECT citus.mitmproxy('conn.onQuery(query="SELECT worker_create_or_replace_object.*CREATE COLLATION mx_metadata_sync_multi_trans.german_phonebook").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: canceling statement due to user request +SELECT citus.mitmproxy('conn.onQuery(query="SELECT worker_create_or_replace_object.*CREATE COLLATION mx_metadata_sync_multi_trans.german_phonebook").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +-- Failure to create function +SELECT citus.mitmproxy('conn.onQuery(query="CREATE OR REPLACE FUNCTION mx_metadata_sync_multi_trans.one_as_result").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: canceling statement due to user request +SELECT citus.mitmproxy('conn.onQuery(query="CREATE OR REPLACE FUNCTION mx_metadata_sync_multi_trans.one_as_result").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +-- Failure to create text search dictionary +SELECT citus.mitmproxy('conn.onQuery(query="SELECT worker_create_or_replace_object.*my_german_dict").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: canceling statement due to user request +SELECT citus.mitmproxy('conn.onQuery(query="SELECT worker_create_or_replace_object.*my_german_dict").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +-- Failure to create text search config +SELECT citus.mitmproxy('conn.onQuery(query="SELECT worker_create_or_replace_object.*my_ts_config").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: canceling statement due to user request +SELECT citus.mitmproxy('conn.onQuery(query="SELECT worker_create_or_replace_object.*my_ts_config").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +-- Failure to create type +SELECT citus.mitmproxy('conn.onQuery(query="SELECT worker_create_or_replace_object.*pair_type").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: canceling statement due to user request +SELECT citus.mitmproxy('conn.onQuery(query="SELECT worker_create_or_replace_object.*pair_type").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +-- Failure to create publication +SELECT citus.mitmproxy('conn.onQuery(query="CREATE PUBLICATION.*pub_all").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: canceling statement due to user request +SELECT citus.mitmproxy('conn.onQuery(query="CREATE PUBLICATION.*pub_all").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + SELECT citus_activate_node('localhost', :worker_2_proxy_port); ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open -- Failure to create sequence @@ -353,6 +477,40 @@ SELECT citus.mitmproxy('conn.onQuery(query="SELECT worker_apply_sequence_command (1 row) +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +-- Failure to drop sequence dependency for distributed table +SELECT citus.mitmproxy('conn.onQuery(query="SELECT pg_catalog.worker_drop_sequence_dependency.*mx_metadata_sync_multi_trans.dist1").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: canceling statement due to user request +SELECT citus.mitmproxy('conn.onQuery(query="SELECT pg_catalog.worker_drop_sequence_dependency.*mx_metadata_sync_multi_trans.dist1").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +-- Failure to drop distributed table if exists +SELECT citus.mitmproxy('conn.onQuery(query="DROP TABLE IF EXISTS mx_metadata_sync_multi_trans.dist1").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: canceling statement due to user request +SELECT citus.mitmproxy('conn.onQuery(query="DROP TABLE IF EXISTS mx_metadata_sync_multi_trans.dist1").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + SELECT citus_activate_node('localhost', :worker_2_proxy_port); ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open -- Failure to create distributed table @@ -370,6 +528,40 @@ SELECT citus.mitmproxy('conn.onQuery(query="CREATE TABLE mx_metadata_sync_multi_ (1 row) +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +-- Failure to record sequence dependency for table +SELECT citus.mitmproxy('conn.onQuery(query="SELECT pg_catalog.worker_record_sequence_dependency").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: canceling statement due to user request +SELECT citus.mitmproxy('conn.onQuery(query="SELECT pg_catalog.worker_record_sequence_dependency").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +-- Failure to create index for table +SELECT citus.mitmproxy('conn.onQuery(query="CREATE INDEX dist1_search_phone_idx ON mx_metadata_sync_multi_trans.dist1 USING gin").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: canceling statement due to user request +SELECT citus.mitmproxy('conn.onQuery(query="CREATE INDEX dist1_search_phone_idx ON mx_metadata_sync_multi_trans.dist1 USING gin").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + SELECT citus_activate_node('localhost', :worker_2_proxy_port); ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open -- Failure to create reference table @@ -540,6 +732,125 @@ SELECT citus.mitmproxy('conn.onQuery(query="SELECT citus_internal_add_object_met (1 row) +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +-- Failure to mark function as distributed +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*one_as_result").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: canceling statement due to user request +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*one_as_result").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +-- Failure to mark collation as distributed +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*german_phonebook").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: canceling statement due to user request +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*german_phonebook").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +-- Failure to mark text search dictionary as distributed +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*my_german_dict").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: canceling statement due to user request +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*my_german_dict").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +-- Failure to mark text search configuration as distributed +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*my_ts_config").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: canceling statement due to user request +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*my_ts_config").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +-- Failure to mark type as distributed +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*pair_type").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: canceling statement due to user request +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*pair_type").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +-- Failure to mark sequence as distributed +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*seq_owned").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: canceling statement due to user request +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*seq_owned").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +-- Failure to mark publication as distributed +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*pub_all").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +ERROR: canceling statement due to user request +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*pub_all").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + SELECT citus_activate_node('localhost', :worker_2_proxy_port); ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open -- Failure to set isactive to true @@ -597,8 +908,8 @@ ERROR: connection not open SELECT * FROM pg_dist_node ORDER BY nodeport; nodeid | groupid | nodename | nodeport | noderack | hasmetadata | isactive | noderole | nodecluster | metadatasynced | shouldhaveshards --------------------------------------------------------------------- - 4 | 4 | localhost | 9060 | default | f | t | primary | default | f | t - 6 | 0 | localhost | 57636 | default | t | t | primary | default | t | f + 2 | 2 | localhost | 9060 | default | f | t | primary | default | f | t + 3 | 0 | localhost | 57636 | default | t | t | primary | default | t | f 1 | 1 | localhost | 57637 | default | t | t | primary | default | t | t (3 rows) @@ -626,24 +937,14 @@ UPDATE dist1 SET id = :failed_node_val WHERE id = :failed_node_val; -- Show that we can still delete from a shard at the node from coordinator DELETE FROM dist1 WHERE id = :failed_node_val; -- Show that DDL would still propagate to the node -SET client_min_messages TO NOTICE; -SET citus.log_remote_commands TO 1; CREATE SCHEMA dummy; -NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); -NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); -NOTICE: issuing SET citus.enable_ddl_propagation TO 'off' -NOTICE: issuing CREATE SCHEMA dummy -NOTICE: issuing SET citus.enable_ddl_propagation TO 'on' -NOTICE: issuing SET citus.enable_ddl_propagation TO 'off' -NOTICE: issuing CREATE SCHEMA dummy -NOTICE: issuing SET citus.enable_ddl_propagation TO 'on' -NOTICE: issuing WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('schema', ARRAY['dummy']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; -NOTICE: issuing PREPARE TRANSACTION 'citus_xx_xx_xx_xx' -NOTICE: issuing PREPARE TRANSACTION 'citus_xx_xx_xx_xx' -NOTICE: issuing COMMIT PREPARED 'citus_xx_xx_xx_xx' -NOTICE: issuing COMMIT PREPARED 'citus_xx_xx_xx_xx' -SET citus.log_remote_commands TO 0; -SET client_min_messages TO ERROR; +SELECT * FROM run_command_on_workers($$SELECT nspname FROM pg_namespace WHERE nspname = 'dummy'$$); + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 9060 | t | dummy + localhost | 57637 | t | dummy +(2 rows) + -- Successfully activate the node after many failures SELECT citus.mitmproxy('conn.allow()'); mitmproxy @@ -654,14 +955,14 @@ SELECT citus.mitmproxy('conn.allow()'); SELECT citus_activate_node('localhost', :worker_2_proxy_port); citus_activate_node --------------------------------------------------------------------- - 4 + 2 (1 row) -- Activate the node once more to verify it works again with already synced metadata SELECT citus_activate_node('localhost', :worker_2_proxy_port); citus_activate_node --------------------------------------------------------------------- - 4 + 2 (1 row) -- Show node metadata info on worker2 and coordinator after success @@ -669,8 +970,8 @@ SELECT citus_activate_node('localhost', :worker_2_proxy_port); SELECT * FROM pg_dist_node ORDER BY nodeport; nodeid | groupid | nodename | nodeport | noderack | hasmetadata | isactive | noderole | nodecluster | metadatasynced | shouldhaveshards --------------------------------------------------------------------- - 4 | 4 | localhost | 9060 | default | t | t | primary | default | t | t - 6 | 0 | localhost | 57636 | default | t | t | primary | default | t | f + 2 | 2 | localhost | 9060 | default | t | t | primary | default | t | t + 3 | 0 | localhost | 57636 | default | t | t | primary | default | t | f 1 | 1 | localhost | 57637 | default | t | t | primary | default | t | t (3 rows) @@ -678,8 +979,8 @@ SELECT * FROM pg_dist_node ORDER BY nodeport; SELECT * FROM pg_dist_node ORDER BY nodeport; nodeid | groupid | nodename | nodeport | noderack | hasmetadata | isactive | noderole | nodecluster | metadatasynced | shouldhaveshards --------------------------------------------------------------------- - 4 | 4 | localhost | 9060 | default | t | t | primary | default | t | t - 6 | 0 | localhost | 57636 | default | t | t | primary | default | t | f + 2 | 2 | localhost | 9060 | default | t | t | primary | default | t | t + 3 | 0 | localhost | 57636 | default | t | t | primary | default | t | f 1 | 1 | localhost | 57637 | default | t | t | primary | default | t | t (3 rows) @@ -690,9 +991,10 @@ SELECT citus.mitmproxy('conn.allow()'); (1 row) RESET citus.metadata_sync_mode; +DROP PUBLICATION pub_all; DROP SCHEMA dummy; DROP SCHEMA mx_metadata_sync_multi_trans CASCADE; -NOTICE: drop cascades to 10 other objects +NOTICE: drop cascades to 15 other objects DROP ROLE foo1; DROP ROLE foo2; SELECT citus_remove_node('localhost', :master_port); @@ -701,3 +1003,5 @@ SELECT citus_remove_node('localhost', :master_port); (1 row) +ALTER SEQUENCE pg_dist_node_nodeid_seq RESTART 3; +ALTER SEQUENCE pg_dist_groupid_seq RESTART 3; diff --git a/src/test/regress/expected/fast_path_router_modify.out b/src/test/regress/expected/fast_path_router_modify.out index bf7e07362..6934e6434 100644 --- a/src/test/regress/expected/fast_path_router_modify.out +++ b/src/test/regress/expected/fast_path_router_modify.out @@ -231,7 +231,6 @@ $$ LANGUAGE plpgsql; DEBUG: switching to sequential query execution mode DETAIL: A command for a distributed function is run. To make sure subsequent commands see the function correctly we need to make sure to use only one connection for all future commands SELECT modify_fast_path_plpsql(1,1); -DEBUG: function does not have co-located tables DEBUG: Deferred pruning for a fast-path router query CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" PL/pgSQL function modify_fast_path_plpsql(integer,integer) line XX at SQL statement @@ -244,7 +243,6 @@ PL/pgSQL function modify_fast_path_plpsql(integer,integer) line XX at SQL statem (1 row) SELECT modify_fast_path_plpsql(2,2); -DEBUG: function does not have co-located tables DEBUG: Deferred pruning for a fast-path router query CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" PL/pgSQL function modify_fast_path_plpsql(integer,integer) line XX at SQL statement @@ -257,7 +255,6 @@ PL/pgSQL function modify_fast_path_plpsql(integer,integer) line XX at SQL statem (1 row) SELECT modify_fast_path_plpsql(3,3); -DEBUG: function does not have co-located tables DEBUG: Deferred pruning for a fast-path router query CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" PL/pgSQL function modify_fast_path_plpsql(integer,integer) line XX at SQL statement @@ -270,7 +267,6 @@ PL/pgSQL function modify_fast_path_plpsql(integer,integer) line XX at SQL statem (1 row) SELECT modify_fast_path_plpsql(4,4); -DEBUG: function does not have co-located tables DEBUG: Deferred pruning for a fast-path router query CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" PL/pgSQL function modify_fast_path_plpsql(integer,integer) line XX at SQL statement @@ -283,7 +279,6 @@ PL/pgSQL function modify_fast_path_plpsql(integer,integer) line XX at SQL statem (1 row) SELECT modify_fast_path_plpsql(5,5); -DEBUG: function does not have co-located tables DEBUG: Deferred pruning for a fast-path router query CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" PL/pgSQL function modify_fast_path_plpsql(integer,integer) line XX at SQL statement @@ -296,7 +291,6 @@ PL/pgSQL function modify_fast_path_plpsql(integer,integer) line XX at SQL statem (1 row) SELECT modify_fast_path_plpsql(6,6); -DEBUG: function does not have co-located tables DEBUG: Deferred pruning for a fast-path router query CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" PL/pgSQL function modify_fast_path_plpsql(integer,integer) line XX at SQL statement @@ -309,7 +303,6 @@ PL/pgSQL function modify_fast_path_plpsql(integer,integer) line XX at SQL statem (1 row) SELECT modify_fast_path_plpsql(6,6); -DEBUG: function does not have co-located tables modify_fast_path_plpsql --------------------------------------------------------------------- @@ -496,8 +489,9 @@ RESET citus.enable_fast_path_router_planner; RESET client_min_messages; RESET citus.log_remote_commands; DROP SCHEMA fast_path_router_modify CASCADE; -NOTICE: drop cascades to 4 other objects +NOTICE: drop cascades to 5 other objects DETAIL: drop cascades to table modify_fast_path drop cascades to table modify_fast_path_replication_2 drop cascades to table modify_fast_path_reference +drop cascades to table modify_fast_path_reference_1840008 drop cascades to function modify_fast_path_plpsql(integer,integer) diff --git a/src/test/regress/expected/forcedelegation_functions.out b/src/test/regress/expected/forcedelegation_functions.out index 103742c5e..2282bf626 100644 --- a/src/test/regress/expected/forcedelegation_functions.out +++ b/src/test/regress/expected/forcedelegation_functions.out @@ -322,7 +322,6 @@ SELECT public.wait_until_metadata_sync(30000); BEGIN; SELECT func_calls_forcepush_func(); -DEBUG: not pushing down function calls in a multi-statement transaction DEBUG: pushing down function call in a multi-statement transaction CONTEXT: SQL statement "SELECT inner_force_delegation_function FROM inner_force_delegation_function(100)" PL/pgSQL function func_calls_forcepush_func() line XX at SQL statement @@ -340,7 +339,6 @@ PL/pgSQL function func_calls_forcepush_func() line XX at SQL statement COMMIT; SELECT func_calls_forcepush_func(); -DEBUG: function does not have co-located tables NOTICE: inner_force_delegation_function():101 DETAIL: from localhost:xxxxx CONTEXT: SQL statement "SELECT inner_force_delegation_function FROM inner_force_delegation_function(100)" @@ -380,7 +378,6 @@ $$ LANGUAGE plpgsql; DEBUG: switching to sequential query execution mode DETAIL: A command for a distributed function is run. To make sure subsequent commands see the function correctly we need to make sure to use only one connection for all future commands SELECT func_calls_forcepush_func_infrom(); -DEBUG: function does not have co-located tables DEBUG: pushing down function call in a multi-statement transaction CONTEXT: SQL statement "SELECT inner_force_delegation_function FROM inner_force_delegation_function(add_val + 100)" PL/pgSQL function func_calls_forcepush_func_infrom() line XX at SQL statement @@ -398,7 +395,6 @@ PL/pgSQL function func_calls_forcepush_func_infrom() line XX at SQL statement BEGIN; SELECT func_calls_forcepush_func_infrom(); -DEBUG: not pushing down function calls in a multi-statement transaction DEBUG: pushing down function call in a multi-statement transaction CONTEXT: SQL statement "SELECT inner_force_delegation_function FROM inner_force_delegation_function(add_val + 100)" PL/pgSQL function func_calls_forcepush_func_infrom() line XX at SQL statement @@ -435,7 +431,6 @@ $$ LANGUAGE plpgsql; DEBUG: switching to sequential query execution mode DETAIL: A command for a distributed function is run. To make sure subsequent commands see the function correctly we need to make sure to use only one connection for all future commands SELECT func_calls_forcepush_func_intarget(); -DEBUG: function does not have co-located tables DEBUG: pushing down function call in a multi-statement transaction CONTEXT: SQL statement "SELECT inner_force_delegation_function(100 + 100) OFFSET 0" PL/pgSQL function func_calls_forcepush_func_intarget() line XX at SQL statement @@ -453,7 +448,6 @@ PL/pgSQL function func_calls_forcepush_func_intarget() line XX at SQL statement BEGIN; SELECT func_calls_forcepush_func_intarget(); -DEBUG: not pushing down function calls in a multi-statement transaction NOTICE: inner_force_delegation_function():201 DETAIL: from localhost:xxxxx CONTEXT: SQL statement "SELECT inner_force_delegation_function(100 + 100) OFFSET 0" @@ -648,7 +642,6 @@ DETAIL: A command for a distributed function is run. To make sure subsequent co (1 row) SELECT outer_emp(); -DEBUG: function does not have co-located tables DEBUG: Skipping pushdown of function from a PL/PgSQL simple expression CONTEXT: SQL statement "SELECT inner_emp('hello')" PL/pgSQL function outer_emp() line XX at PERFORM @@ -1282,7 +1275,6 @@ DETAIL: A command for a distributed function is run. To make sure subsequent co -- First 5 get delegated and succeeds BEGIN; SELECT outer_test_prepare(1,1); -DEBUG: not pushing down function calls in a multi-statement transaction DEBUG: pushing down function call in a multi-statement transaction CONTEXT: SQL statement "SELECT FROM test_prepare(x, y)" PL/pgSQL function outer_test_prepare(integer,integer) line XX at PERFORM @@ -1301,7 +1293,6 @@ PL/pgSQL function outer_test_prepare(integer,integer) line XX at PERFORM (1 row) SELECT outer_test_prepare(1,1); -DEBUG: not pushing down function calls in a multi-statement transaction DEBUG: pushing down function call in a multi-statement transaction CONTEXT: SQL statement "SELECT FROM test_prepare(x, y)" PL/pgSQL function outer_test_prepare(integer,integer) line XX at PERFORM @@ -1320,7 +1311,6 @@ PL/pgSQL function outer_test_prepare(integer,integer) line XX at PERFORM (1 row) SELECT outer_test_prepare(1,1); -DEBUG: not pushing down function calls in a multi-statement transaction DEBUG: pushing down function call in a multi-statement transaction CONTEXT: SQL statement "SELECT FROM test_prepare(x, y)" PL/pgSQL function outer_test_prepare(integer,integer) line XX at PERFORM @@ -1339,7 +1329,6 @@ PL/pgSQL function outer_test_prepare(integer,integer) line XX at PERFORM (1 row) SELECT outer_test_prepare(1,1); -DEBUG: not pushing down function calls in a multi-statement transaction DEBUG: pushing down function call in a multi-statement transaction CONTEXT: SQL statement "SELECT FROM test_prepare(x, y)" PL/pgSQL function outer_test_prepare(integer,integer) line XX at PERFORM @@ -1358,7 +1347,6 @@ PL/pgSQL function outer_test_prepare(integer,integer) line XX at PERFORM (1 row) SELECT outer_test_prepare(1,1); -DEBUG: not pushing down function calls in a multi-statement transaction DEBUG: pushing down function call in a multi-statement transaction CONTEXT: SQL statement "SELECT FROM test_prepare(x, y)" PL/pgSQL function outer_test_prepare(integer,integer) line XX at PERFORM @@ -1385,7 +1373,6 @@ SELECT COUNT(*) FROM table_test_prepare; -- 6th execution will be generic plan and should get delegated SELECT outer_test_prepare(1,1); -DEBUG: not pushing down function calls in a multi-statement transaction DEBUG: pushing down function call in a multi-statement transaction CONTEXT: SQL statement "SELECT FROM test_prepare(x, y)" PL/pgSQL function outer_test_prepare(integer,integer) line XX at PERFORM @@ -1404,7 +1391,6 @@ PL/pgSQL function outer_test_prepare(integer,integer) line XX at PERFORM (1 row) SELECT outer_test_prepare(1,1); -DEBUG: not pushing down function calls in a multi-statement transaction DEBUG: pushing down function call in a multi-statement transaction CONTEXT: SQL statement "SELECT FROM test_prepare(x, y)" PL/pgSQL function outer_test_prepare(integer,integer) line XX at PERFORM @@ -1425,7 +1411,6 @@ PL/pgSQL function outer_test_prepare(integer,integer) line XX at PERFORM END; -- Fails as expected SELECT outer_test_prepare(1,2); -DEBUG: function does not have co-located tables DEBUG: pushing down function call in a multi-statement transaction CONTEXT: SQL statement "SELECT FROM test_prepare(x, y)" PL/pgSQL function outer_test_prepare(integer,integer) line XX at PERFORM @@ -1525,7 +1510,6 @@ DETAIL: A command for a distributed function is run. To make sure subsequent co (1 row) SELECT outer_local_fn(); -DEBUG: function does not have co-located tables DEBUG: pushing down function call in a multi-statement transaction CONTEXT: SQL statement "SELECT 1 FROM inner_fn(1)" PL/pgSQL function outer_local_fn() line XX at PERFORM @@ -1555,7 +1539,6 @@ SELECT * FROM testnested_table ORDER BY 1; BEGIN; SELECT outer_local_fn(); -DEBUG: not pushing down function calls in a multi-statement transaction outer_local_fn --------------------------------------------------------------------- diff --git a/src/test/regress/expected/foreign_key_restriction_enforcement.out b/src/test/regress/expected/foreign_key_restriction_enforcement.out index 15facd198..f8c9339a7 100644 --- a/src/test/regress/expected/foreign_key_restriction_enforcement.out +++ b/src/test/regress/expected/foreign_key_restriction_enforcement.out @@ -616,12 +616,15 @@ BEGIN; ALTER TABLE reference_table ALTER COLUMN id SET DATA TYPE smallint; DEBUG: rewriting table "reference_table" DEBUG: validating foreign key constraint "fkey" +DEBUG: rewriting table "reference_table_2380001" CREATE INDEX fkey_test_index_1 ON on_update_fkey_table(value_1); ROLLBACK; BEGIN; ALTER TABLE transitive_reference_table ALTER COLUMN id SET DATA TYPE smallint; DEBUG: rewriting table "transitive_reference_table" DEBUG: validating foreign key constraint "fkey" +DEBUG: rewriting table "transitive_reference_table_2380000" +DEBUG: validating foreign key constraint "fkey_xxxxxxx" CREATE INDEX fkey_test_index_1 ON on_update_fkey_table(value_1); ROLLBACK; -- case 4.6: DDL to reference table followed by a DDL to dist table, both touching fkey columns @@ -629,6 +632,7 @@ BEGIN; ALTER TABLE reference_table ALTER COLUMN id SET DATA TYPE smallint; DEBUG: rewriting table "reference_table" DEBUG: validating foreign key constraint "fkey" +DEBUG: rewriting table "reference_table_2380001" ALTER TABLE on_update_fkey_table ALTER COLUMN value_1 SET DATA TYPE smallint; DEBUG: rewriting table "on_update_fkey_table" DEBUG: validating foreign key constraint "fkey" @@ -637,6 +641,8 @@ BEGIN; ALTER TABLE transitive_reference_table ALTER COLUMN id SET DATA TYPE smallint; DEBUG: rewriting table "transitive_reference_table" DEBUG: validating foreign key constraint "fkey" +DEBUG: rewriting table "transitive_reference_table_2380000" +DEBUG: validating foreign key constraint "fkey_xxxxxxx" ALTER TABLE on_update_fkey_table ALTER COLUMN value_1 SET DATA TYPE smallint; DEBUG: rewriting table "on_update_fkey_table" DEBUG: validating foreign key constraint "fkey" @@ -672,12 +678,15 @@ BEGIN; ALTER TABLE reference_table ALTER COLUMN id SET DATA TYPE smallint; DEBUG: rewriting table "reference_table" DEBUG: validating foreign key constraint "fkey" +DEBUG: rewriting table "reference_table_2380001" TRUNCATE on_update_fkey_table; ROLLBACK; BEGIN; ALTER TABLE transitive_reference_table ALTER COLUMN id SET DATA TYPE smallint; DEBUG: rewriting table "transitive_reference_table" DEBUG: validating foreign key constraint "fkey" +DEBUG: rewriting table "transitive_reference_table_2380000" +DEBUG: validating foreign key constraint "fkey_xxxxxxx" TRUNCATE on_update_fkey_table; ROLLBACK; --------------------------------------------------------------------- @@ -834,6 +843,7 @@ BEGIN; TRUNCATE transitive_reference_table CASCADE; NOTICE: truncate cascades to table "reference_table" NOTICE: truncate cascades to table "on_update_fkey_table" +NOTICE: truncate cascades to table "reference_table_xxxxx" ROLLBACK; -- case 4.7: SELECT to a dist table is followed by a DROP -- DROP following SELECT is important as we error out after @@ -1101,6 +1111,12 @@ ROLLBACK; -- the fails since we're trying to switch sequential mode after -- already executed a parallel query BEGIN; + SELECT master_remove_node('localhost', :master_port); + master_remove_node +--------------------------------------------------------------------- + +(1 row) + CREATE TABLE test_table_1(id int PRIMARY KEY); SELECT create_reference_table('test_table_1'); create_reference_table @@ -1129,6 +1145,12 @@ ROLLBACK; -- same test with the above, but this time using -- sequential mode, succeeds BEGIN; + SELECT master_remove_node('localhost', :master_port); + master_remove_node +--------------------------------------------------------------------- + +(1 row) + SET LOCAL citus.multi_shard_modify_mode TO 'sequential'; CREATE TABLE test_table_1(id int PRIMARY KEY); SELECT create_reference_table('test_table_1'); @@ -1499,6 +1521,6 @@ ROLLBACK; RESET client_min_messages; \set VERBOSITY terse DROP SCHEMA test_fkey_to_ref_in_tx CASCADE; -NOTICE: drop cascades to 5 other objects +NOTICE: drop cascades to 7 other objects \set VERBOSITY default SET search_path TO public; diff --git a/src/test/regress/expected/function_propagation.out b/src/test/regress/expected/function_propagation.out index 2787e7daf..d49f4fd10 100644 --- a/src/test/regress/expected/function_propagation.out +++ b/src/test/regress/expected/function_propagation.out @@ -864,13 +864,6 @@ BEGIN; (0 rows) CREATE TABLE citus_local_table_to_test_func(l1 int DEFAULT func_in_transaction_for_local_table()); - SELECT 1 FROM master_add_node('localhost', :master_port, groupid => 0); -NOTICE: localhost:xxxxx is the coordinator and already contains metadata, skipping syncing the metadata - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - SELECT citus_add_local_table_to_metadata('citus_local_table_to_test_func'); citus_add_local_table_to_metadata --------------------------------------------------------------------- @@ -1097,6 +1090,14 @@ SELECT create_reference_table('tbl_to_colocate_ref'); (1 row) +-- test colocating function with single shard table +CREATE TABLE single_shard_tbl (a int); +SELECT create_distributed_table('single_shard_tbl', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + CREATE FUNCTION func_to_colocate (a int) returns int as $$select 1;$$ language sql; -- see the empty pg_dist_object entries SELECT distribution_argument_index, colocationid, force_delegation FROM pg_catalog.pg_dist_object WHERE objid = 'func_to_colocate'::regproc; @@ -1119,6 +1120,20 @@ SELECT distribution_argument_index, colocationid, force_delegation FROM pg_catal | 10002 | (1 row) +-- colocate the function with single shard table table +SELECT create_distributed_function('func_to_colocate(int)', colocate_with:='single_shard_tbl'); + create_distributed_function +--------------------------------------------------------------------- + +(1 row) + +-- see the pg_dist_object entry +SELECT distribution_argument_index, colocationid, force_delegation FROM pg_catalog.pg_dist_object WHERE objid = 'func_to_colocate'::regproc; + distribution_argument_index | colocationid | force_delegation +--------------------------------------------------------------------- + | 10003 | +(1 row) + -- convert to non-delegated SELECT create_distributed_function('func_to_colocate(int)'); create_distributed_function diff --git a/src/test/regress/expected/generated_identity.out b/src/test/regress/expected/generated_identity.out index 865012af0..8fe7a0dc6 100644 --- a/src/test/regress/expected/generated_identity.out +++ b/src/test/regress/expected/generated_identity.out @@ -1,11 +1,3 @@ --- This test file has an alternative output because of error messages vary for PG13 -SHOW server_version \gset -SELECT substring(:'server_version', '\d+')::int <= 13 AS server_version_le_13; - server_version_le_13 ---------------------------------------------------------------------- - f -(1 row) - CREATE SCHEMA generated_identities; SET search_path TO generated_identities; SET client_min_messages to ERROR; @@ -16,44 +8,185 @@ SELECT 1 from citus_add_node('localhost', :master_port, groupId=>0); 1 (1 row) --- smallint identity column can not be distributed CREATE TABLE smallint_identity_column ( a smallint GENERATED BY DEFAULT AS IDENTITY ); -SELECT create_distributed_table('smallint_identity_column', 'a'); -ERROR: cannot complete operation on generated_identities.smallint_identity_column with smallint/int identity column -HINT: Use bigint identity column instead. -SELECT create_distributed_table_concurrently('smallint_identity_column', 'a'); -ERROR: cannot complete operation on generated_identities.smallint_identity_column with smallint/int identity column -HINT: Use bigint identity column instead. -SELECT create_reference_table('smallint_identity_column'); -ERROR: cannot complete operation on a table with identity column -SELECT citus_add_local_table_to_metadata('smallint_identity_column'); +CREATE VIEW verify_smallint_identity_column AS +SELECT attidentity, attgenerated FROM pg_attribute WHERE attrelid = 'smallint_identity_column'::regclass AND attname = 'a'; +BEGIN; + SELECT create_distributed_table('smallint_identity_column', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + SELECT * FROM verify_smallint_identity_column; + attidentity | attgenerated +--------------------------------------------------------------------- + d | +(1 row) + +ROLLBACK; +BEGIN; + SELECT create_reference_table('smallint_identity_column'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + + SELECT * FROM verify_smallint_identity_column; + attidentity | attgenerated +--------------------------------------------------------------------- + d | +(1 row) + +ROLLBACK; +BEGIN; + SELECT citus_add_local_table_to_metadata('smallint_identity_column'); citus_add_local_table_to_metadata --------------------------------------------------------------------- (1 row) -DROP TABLE smallint_identity_column; --- int identity column can not be distributed + SELECT * FROM verify_smallint_identity_column; + attidentity | attgenerated +--------------------------------------------------------------------- + d | +(1 row) + +ROLLBACK; +SELECT create_distributed_table_concurrently('smallint_identity_column', 'a'); + create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +SELECT * FROM verify_smallint_identity_column; + attidentity | attgenerated +--------------------------------------------------------------------- + d | +(1 row) + +SELECT result FROM run_command_on_workers('INSERT INTO generated_identities.smallint_identity_column (a) VALUES (DEFAULT);'); + result +--------------------------------------------------------------------- + ERROR: nextval: reached maximum value of sequence "smallint_identity_column_a_seq" (32767) + ERROR: nextval: reached maximum value of sequence "smallint_identity_column_a_seq" (32767) +(2 rows) + +DROP TABLE smallint_identity_column CASCADE; CREATE TABLE int_identity_column ( a int GENERATED BY DEFAULT AS IDENTITY ); -SELECT create_distributed_table('int_identity_column', 'a'); -ERROR: cannot complete operation on generated_identities.int_identity_column with smallint/int identity column -HINT: Use bigint identity column instead. -SELECT create_distributed_table_concurrently('int_identity_column', 'a'); -ERROR: cannot complete operation on generated_identities.int_identity_column with smallint/int identity column -HINT: Use bigint identity column instead. -SELECT create_reference_table('int_identity_column'); -ERROR: cannot complete operation on a table with identity column -SELECT citus_add_local_table_to_metadata('int_identity_column'); +CREATE VIEW verify_int_identity_column AS +SELECT attidentity, attgenerated FROM pg_attribute WHERE attrelid = 'int_identity_column'::regclass AND attname = 'a'; +BEGIN; + SELECT create_distributed_table('int_identity_column', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + SELECT * FROM verify_int_identity_column; + attidentity | attgenerated +--------------------------------------------------------------------- + d | +(1 row) + +ROLLBACK; +BEGIN; + SELECT create_reference_table('int_identity_column'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + + SELECT * FROM verify_int_identity_column; + attidentity | attgenerated +--------------------------------------------------------------------- + d | +(1 row) + +ROLLBACK; +BEGIN; + SELECT citus_add_local_table_to_metadata('int_identity_column'); citus_add_local_table_to_metadata --------------------------------------------------------------------- (1 row) -DROP TABLE int_identity_column; + SELECT * FROM verify_int_identity_column; + attidentity | attgenerated +--------------------------------------------------------------------- + d | +(1 row) + +ROLLBACK; +SELECT create_distributed_table_concurrently('int_identity_column', 'a'); + create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +SELECT * FROM verify_int_identity_column; + attidentity | attgenerated +--------------------------------------------------------------------- + d | +(1 row) + +SELECT result FROM run_command_on_workers('INSERT INTO generated_identities.int_identity_column (a) VALUES (DEFAULT);'); + result +--------------------------------------------------------------------- + ERROR: nextval: reached maximum value of sequence "int_identity_column_a_seq" (2147483647) + ERROR: nextval: reached maximum value of sequence "int_identity_column_a_seq" (2147483647) +(2 rows) + +DROP TABLE int_identity_column CASCADE; +CREATE TABLE reference_int_identity_column ( + a int GENERATED BY DEFAULT AS IDENTITY +); +SELECT create_reference_table('reference_int_identity_column'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO generated_identities.reference_int_identity_column (a) VALUES (DEFAULT) RETURNING a; + a +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT result FROM run_command_on_workers('INSERT INTO generated_identities.reference_int_identity_column (a) VALUES (DEFAULT);'); + result +--------------------------------------------------------------------- + ERROR: nextval: reached maximum value of sequence "reference_int_identity_column_a_seq" (2147483647) + ERROR: nextval: reached maximum value of sequence "reference_int_identity_column_a_seq" (2147483647) +(2 rows) + +CREATE TABLE citus_local_int_identity_column ( + a int GENERATED BY DEFAULT AS IDENTITY +); +SELECT citus_add_local_table_to_metadata('citus_local_int_identity_column'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO generated_identities.citus_local_int_identity_column (a) VALUES (DEFAULT) RETURNING a; + a +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT result FROM run_command_on_workers('INSERT INTO generated_identities.citus_local_int_identity_column (a) VALUES (DEFAULT);'); + result +--------------------------------------------------------------------- + ERROR: nextval: reached maximum value of sequence "citus_local_int_identity_column_a_seq" (2147483647) + ERROR: nextval: reached maximum value of sequence "citus_local_int_identity_column_a_seq" (2147483647) +(2 rows) + +DROP TABLE reference_int_identity_column, citus_local_int_identity_column; RESET citus.shard_replication_factor; CREATE TABLE bigint_identity_column ( a bigint GENERATED BY DEFAULT AS IDENTITY, diff --git a/src/test/regress/expected/generated_identity_0.out b/src/test/regress/expected/generated_identity_0.out deleted file mode 100644 index 1bff7f68f..000000000 --- a/src/test/regress/expected/generated_identity_0.out +++ /dev/null @@ -1,431 +0,0 @@ --- This test file has an alternative output because of error messages vary for PG13 -SHOW server_version \gset -SELECT substring(:'server_version', '\d+')::int <= 13 AS server_version_le_13; - server_version_le_13 ---------------------------------------------------------------------- - t -(1 row) - -CREATE SCHEMA generated_identities; -SET search_path TO generated_identities; -SET client_min_messages to ERROR; -SET citus.shard_replication_factor TO 1; -SELECT 1 from citus_add_node('localhost', :master_port, groupId=>0); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - --- smallint identity column can not be distributed -CREATE TABLE smallint_identity_column ( - a smallint GENERATED BY DEFAULT AS IDENTITY -); -SELECT create_distributed_table('smallint_identity_column', 'a'); -ERROR: cannot complete operation on generated_identities.smallint_identity_column with smallint/int identity column -HINT: Use bigint identity column instead. -SELECT create_distributed_table_concurrently('smallint_identity_column', 'a'); -ERROR: cannot complete operation on generated_identities.smallint_identity_column with smallint/int identity column -HINT: Use bigint identity column instead. -SELECT create_reference_table('smallint_identity_column'); -ERROR: cannot complete operation on a table with identity column -SELECT citus_add_local_table_to_metadata('smallint_identity_column'); - citus_add_local_table_to_metadata ---------------------------------------------------------------------- - -(1 row) - -DROP TABLE smallint_identity_column; --- int identity column can not be distributed -CREATE TABLE int_identity_column ( - a int GENERATED BY DEFAULT AS IDENTITY -); -SELECT create_distributed_table('int_identity_column', 'a'); -ERROR: cannot complete operation on generated_identities.int_identity_column with smallint/int identity column -HINT: Use bigint identity column instead. -SELECT create_distributed_table_concurrently('int_identity_column', 'a'); -ERROR: cannot complete operation on generated_identities.int_identity_column with smallint/int identity column -HINT: Use bigint identity column instead. -SELECT create_reference_table('int_identity_column'); -ERROR: cannot complete operation on a table with identity column -SELECT citus_add_local_table_to_metadata('int_identity_column'); - citus_add_local_table_to_metadata ---------------------------------------------------------------------- - -(1 row) - -DROP TABLE int_identity_column; -RESET citus.shard_replication_factor; -CREATE TABLE bigint_identity_column ( - a bigint GENERATED BY DEFAULT AS IDENTITY, - b int -); -SELECT citus_add_local_table_to_metadata('bigint_identity_column'); - citus_add_local_table_to_metadata ---------------------------------------------------------------------- - -(1 row) - -DROP TABLE bigint_identity_column; -CREATE TABLE bigint_identity_column ( - a bigint GENERATED BY DEFAULT AS IDENTITY, - b int -); -SELECT create_distributed_table('bigint_identity_column', 'a'); - create_distributed_table ---------------------------------------------------------------------- - -(1 row) - -\d bigint_identity_column - Table "generated_identities.bigint_identity_column" - Column | Type | Collation | Nullable | Default ---------------------------------------------------------------------- - a | bigint | | not null | generated by default as identity - b | integer | | | - -\c - - - :worker_1_port -SET search_path TO generated_identities; -SET client_min_messages to ERROR; -INSERT INTO bigint_identity_column (b) -SELECT s FROM generate_series(1,10) s; -\d generated_identities.bigint_identity_column - Table "generated_identities.bigint_identity_column" - Column | Type | Collation | Nullable | Default ---------------------------------------------------------------------- - a | bigint | | not null | generated by default as identity - b | integer | | | - -\c - - - :master_port -SET search_path TO generated_identities; -SET client_min_messages to ERROR; -INSERT INTO bigint_identity_column (b) -SELECT s FROM generate_series(11,20) s; -SELECT * FROM bigint_identity_column ORDER BY B ASC; - a | b ---------------------------------------------------------------------- - 3940649673949185 | 1 - 3940649673949186 | 2 - 3940649673949187 | 3 - 3940649673949188 | 4 - 3940649673949189 | 5 - 3940649673949190 | 6 - 3940649673949191 | 7 - 3940649673949192 | 8 - 3940649673949193 | 9 - 3940649673949194 | 10 - 1 | 11 - 2 | 12 - 3 | 13 - 4 | 14 - 5 | 15 - 6 | 16 - 7 | 17 - 8 | 18 - 9 | 19 - 10 | 20 -(20 rows) - --- table with identity column cannot be altered. -SELECT alter_distributed_table('bigint_identity_column', 'b'); -ERROR: cannot complete operation on a table with identity column --- table with identity column cannot be undistributed. -SELECT undistribute_table('bigint_identity_column'); -ERROR: cannot complete operation on a table with identity column -DROP TABLE bigint_identity_column; --- create a partitioned table for testing. -CREATE TABLE partitioned_table ( - a bigint CONSTRAINT myconname GENERATED BY DEFAULT AS IDENTITY (START WITH 10 INCREMENT BY 10), - b bigint GENERATED ALWAYS AS IDENTITY (START WITH 10 INCREMENT BY 10), - c int -) -PARTITION BY RANGE (c); -CREATE TABLE partitioned_table_1_50 PARTITION OF partitioned_table FOR VALUES FROM (1) TO (50); -CREATE TABLE partitioned_table_50_500 PARTITION OF partitioned_table FOR VALUES FROM (50) TO (1000); -SELECT create_distributed_table('partitioned_table', 'a'); - create_distributed_table ---------------------------------------------------------------------- - -(1 row) - -\d partitioned_table - Partitioned table "generated_identities.partitioned_table" - Column | Type | Collation | Nullable | Default ---------------------------------------------------------------------- - a | bigint | | not null | generated by default as identity - b | bigint | | not null | generated always as identity - c | integer | | | -Partition key: RANGE (c) -Number of partitions: 2 (Use \d+ to list them.) - -\c - - - :worker_1_port -SET search_path TO generated_identities; -SET client_min_messages to ERROR; -\d generated_identities.partitioned_table - Partitioned table "generated_identities.partitioned_table" - Column | Type | Collation | Nullable | Default ---------------------------------------------------------------------- - a | bigint | | not null | generated by default as identity - b | bigint | | not null | generated always as identity - c | integer | | | -Partition key: RANGE (c) -Number of partitions: 2 (Use \d+ to list them.) - -insert into partitioned_table (c) values (1); -insert into partitioned_table (c) SELECT 2; -INSERT INTO partitioned_table (c) -SELECT s FROM generate_series(3,7) s; -\c - - - :master_port -SET search_path TO generated_identities; -SET client_min_messages to ERROR; -INSERT INTO partitioned_table (c) -SELECT s FROM generate_series(10,20) s; -INSERT INTO partitioned_table (a,c) VALUES (998,998); -INSERT INTO partitioned_table (a,b,c) OVERRIDING SYSTEM VALUE VALUES (999,999,999); -SELECT * FROM partitioned_table ORDER BY c ASC; - a | b | c ---------------------------------------------------------------------- - 3940649673949185 | 3940649673949185 | 1 - 3940649673949195 | 3940649673949195 | 2 - 3940649673949205 | 3940649673949205 | 3 - 3940649673949215 | 3940649673949215 | 4 - 3940649673949225 | 3940649673949225 | 5 - 3940649673949235 | 3940649673949235 | 6 - 3940649673949245 | 3940649673949245 | 7 - 10 | 10 | 10 - 20 | 20 | 11 - 30 | 30 | 12 - 40 | 40 | 13 - 50 | 50 | 14 - 60 | 60 | 15 - 70 | 70 | 16 - 80 | 80 | 17 - 90 | 90 | 18 - 100 | 100 | 19 - 110 | 110 | 20 - 998 | 120 | 998 - 999 | 999 | 999 -(20 rows) - --- alter table .. alter column .. add is unsupported -ALTER TABLE partitioned_table ALTER COLUMN g ADD GENERATED ALWAYS AS IDENTITY; -ERROR: alter table command is currently unsupported -DETAIL: Only ADD|DROP COLUMN, SET|DROP NOT NULL, SET|DROP DEFAULT, ADD|DROP|VALIDATE CONSTRAINT, SET (), RESET (), ENABLE|DISABLE|NO FORCE|FORCE ROW LEVEL SECURITY, ATTACH|DETACH PARTITION and TYPE subcommands are supported. --- alter table .. alter column is unsupported -ALTER TABLE partitioned_table ALTER COLUMN b TYPE int; -ERROR: cannot execute ALTER COLUMN command involving identity column -DROP TABLE partitioned_table; --- create a table for reference table testing. -CREATE TABLE reference_table ( - a bigint CONSTRAINT myconname GENERATED BY DEFAULT AS IDENTITY (START WITH 10 INCREMENT BY 10), - b bigint GENERATED ALWAYS AS IDENTITY (START WITH 10 INCREMENT BY 10) UNIQUE, - c int -); -SELECT create_reference_table('reference_table'); - create_reference_table ---------------------------------------------------------------------- - -(1 row) - -\d reference_table - Table "generated_identities.reference_table" - Column | Type | Collation | Nullable | Default ---------------------------------------------------------------------- - a | bigint | | not null | generated by default as identity - b | bigint | | not null | generated always as identity - c | integer | | | -Indexes: - "reference_table_b_key" UNIQUE CONSTRAINT, btree (b) - -\c - - - :worker_1_port -SET search_path TO generated_identities; -\d generated_identities.reference_table - Table "generated_identities.reference_table" - Column | Type | Collation | Nullable | Default ---------------------------------------------------------------------- - a | bigint | | not null | generated by default as identity - b | bigint | | not null | generated always as identity - c | integer | | | -Indexes: - "reference_table_b_key" UNIQUE CONSTRAINT, btree (b) - -INSERT INTO reference_table (c) -SELECT s FROM generate_series(1,10) s; ---on master -select * from reference_table; - a | b | c ---------------------------------------------------------------------- - 3940649673949185 | 3940649673949185 | 1 - 3940649673949195 | 3940649673949195 | 2 - 3940649673949205 | 3940649673949205 | 3 - 3940649673949215 | 3940649673949215 | 4 - 3940649673949225 | 3940649673949225 | 5 - 3940649673949235 | 3940649673949235 | 6 - 3940649673949245 | 3940649673949245 | 7 - 3940649673949255 | 3940649673949255 | 8 - 3940649673949265 | 3940649673949265 | 9 - 3940649673949275 | 3940649673949275 | 10 -(10 rows) - -\c - - - :master_port -SET search_path TO generated_identities; -SET client_min_messages to ERROR; -INSERT INTO reference_table (c) -SELECT s FROM generate_series(11,20) s; -SELECT * FROM reference_table ORDER BY c ASC; - a | b | c ---------------------------------------------------------------------- - 3940649673949185 | 3940649673949185 | 1 - 3940649673949195 | 3940649673949195 | 2 - 3940649673949205 | 3940649673949205 | 3 - 3940649673949215 | 3940649673949215 | 4 - 3940649673949225 | 3940649673949225 | 5 - 3940649673949235 | 3940649673949235 | 6 - 3940649673949245 | 3940649673949245 | 7 - 3940649673949255 | 3940649673949255 | 8 - 3940649673949265 | 3940649673949265 | 9 - 3940649673949275 | 3940649673949275 | 10 - 10 | 10 | 11 - 20 | 20 | 12 - 30 | 30 | 13 - 40 | 40 | 14 - 50 | 50 | 15 - 60 | 60 | 16 - 70 | 70 | 17 - 80 | 80 | 18 - 90 | 90 | 19 - 100 | 100 | 20 -(20 rows) - -DROP TABLE reference_table; -CREATE TABLE color ( - color_id BIGINT GENERATED ALWAYS AS IDENTITY UNIQUE, - color_name VARCHAR NOT NULL -); --- https://github.com/citusdata/citus/issues/6694 -CREATE USER identity_test_user; -GRANT INSERT ON color TO identity_test_user; -GRANT USAGE ON SCHEMA generated_identities TO identity_test_user; -SET ROLE identity_test_user; -SELECT create_distributed_table('color', 'color_id'); -ERROR: must be owner of table color -SET ROLE postgres; -SET citus.shard_replication_factor TO 1; -SELECT create_distributed_table_concurrently('color', 'color_id'); - create_distributed_table_concurrently ---------------------------------------------------------------------- - -(1 row) - -RESET citus.shard_replication_factor; -\c - identity_test_user - :worker_1_port -SET search_path TO generated_identities; -SET client_min_messages to ERROR; -INSERT INTO color(color_name) VALUES ('Blue'); -\c - postgres - :master_port -SET search_path TO generated_identities; -SET client_min_messages to ERROR; -SET citus.next_shard_id TO 12400000; -DROP TABLE Color; -CREATE TABLE color ( - color_id BIGINT GENERATED ALWAYS AS IDENTITY UNIQUE, - color_name VARCHAR NOT NULL -) USING columnar; -SELECT create_distributed_table('color', 'color_id'); - create_distributed_table ---------------------------------------------------------------------- - -(1 row) - -INSERT INTO color(color_name) VALUES ('Blue'); -\d+ color - Table "generated_identities.color" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------------------------------------------------------------------- - color_id | bigint | | not null | generated always as identity | plain | | - color_name | character varying | | not null | | extended | | -Indexes: - "color_color_id_key" UNIQUE CONSTRAINT, btree (color_id) - -\c - - - :worker_1_port -SET search_path TO generated_identities; -\d+ color - Table "generated_identities.color" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------------------------------------------------------------------- - color_id | bigint | | not null | generated always as identity | plain | | - color_name | character varying | | not null | | extended | | -Indexes: - "color_color_id_key" UNIQUE CONSTRAINT, btree (color_id) - -INSERT INTO color(color_name) VALUES ('Red'); --- alter sequence .. restart -ALTER SEQUENCE color_color_id_seq RESTART WITH 1000; -ERROR: Altering a distributed sequence is currently not supported. --- override system value -INSERT INTO color(color_id, color_name) VALUES (1, 'Red'); -ERROR: cannot insert into column "color_id" -DETAIL: Column "color_id" is an identity column defined as GENERATED ALWAYS. -HINT: Use OVERRIDING SYSTEM VALUE to override. -INSERT INTO color(color_id, color_name) VALUES (NULL, 'Red'); -ERROR: cannot insert into column "color_id" -DETAIL: Column "color_id" is an identity column defined as GENERATED ALWAYS. -HINT: Use OVERRIDING SYSTEM VALUE to override. -INSERT INTO color(color_id, color_name) OVERRIDING SYSTEM VALUE VALUES (1, 'Red'); -ERROR: duplicate key value violates unique constraint "color_color_id_key_12400000" -DETAIL: Key (color_id)=(1) already exists. -CONTEXT: while executing command on localhost:xxxxx --- update null or custom value -UPDATE color SET color_id = NULL; -ERROR: column "color_id" can only be updated to DEFAULT -DETAIL: Column "color_id" is an identity column defined as GENERATED ALWAYS. -UPDATE color SET color_id = 1; -ERROR: column "color_id" can only be updated to DEFAULT -DETAIL: Column "color_id" is an identity column defined as GENERATED ALWAYS. -\c - postgres - :master_port -SET search_path TO generated_identities; -SET client_min_messages to ERROR; --- alter table .. add column .. GENERATED .. AS IDENTITY -ALTER TABLE color ADD COLUMN color_id BIGINT GENERATED ALWAYS AS IDENTITY; -ERROR: cannot execute ADD COLUMN commands involving identity columns when metadata is synchronized to workers --- alter sequence .. restart -ALTER SEQUENCE color_color_id_seq RESTART WITH 1000; -ERROR: Altering a distributed sequence is currently not supported. --- override system value -INSERT INTO color(color_id, color_name) VALUES (1, 'Red'); -ERROR: cannot insert into column "color_id" -DETAIL: Column "color_id" is an identity column defined as GENERATED ALWAYS. -HINT: Use OVERRIDING SYSTEM VALUE to override. -INSERT INTO color(color_id, color_name) VALUES (NULL, 'Red'); -ERROR: cannot insert into column "color_id" -DETAIL: Column "color_id" is an identity column defined as GENERATED ALWAYS. -HINT: Use OVERRIDING SYSTEM VALUE to override. -INSERT INTO color(color_id, color_name) OVERRIDING SYSTEM VALUE VALUES (1, 'Red'); -ERROR: duplicate key value violates unique constraint "color_color_id_key_12400000" -DETAIL: Key (color_id)=(1) already exists. -CONTEXT: while executing command on localhost:xxxxx --- update null or custom value -UPDATE color SET color_id = NULL; -ERROR: column "color_id" can only be updated to DEFAULT -DETAIL: Column "color_id" is an identity column defined as GENERATED ALWAYS. -UPDATE color SET color_id = 1; -ERROR: column "color_id" can only be updated to DEFAULT -DETAIL: Column "color_id" is an identity column defined as GENERATED ALWAYS. -DROP TABLE IF EXISTS test; -CREATE TABLE test (x int, y int, z bigint generated by default as identity); -SELECT create_distributed_table('test', 'x', colocate_with := 'none'); - create_distributed_table ---------------------------------------------------------------------- - -(1 row) - -INSERT INTO test VALUES (1,2); -INSERT INTO test SELECT x, y FROM test WHERE x = 1; -SELECT * FROM test; - x | y | z ---------------------------------------------------------------------- - 1 | 2 | 1 - 1 | 2 | 2 -(2 rows) - -DROP SCHEMA generated_identities CASCADE; -DROP USER identity_test_user; diff --git a/src/test/regress/expected/grant_on_foreign_server_propagation.out b/src/test/regress/expected/grant_on_foreign_server_propagation.out index b98130404..7c47a5524 100644 --- a/src/test/regress/expected/grant_on_foreign_server_propagation.out +++ b/src/test/regress/expected/grant_on_foreign_server_propagation.out @@ -5,6 +5,12 @@ CREATE SCHEMA "grant on server"; SET search_path TO "grant on server"; -- remove one of the worker nodes to test adding a new node later +SELECT 1 FROM citus_remove_node('localhost', :master_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + SELECT 1 FROM citus_remove_node('localhost', :worker_2_port); ?column? --------------------------------------------------------------------- @@ -173,9 +179,3 @@ SET client_min_messages TO ERROR; DROP SERVER "Foreign Server" CASCADE; DROP SCHEMA "grant on server" CASCADE; DROP ROLE role_test_servers, role_test_servers_2, ownerrole; -SELECT 1 FROM citus_remove_node('localhost', :master_port); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - diff --git a/src/test/regress/expected/grant_on_schema_propagation.out b/src/test/regress/expected/grant_on_schema_propagation.out index 410865d49..77447c2dd 100644 --- a/src/test/regress/expected/grant_on_schema_propagation.out +++ b/src/test/regress/expected/grant_on_schema_propagation.out @@ -1,7 +1,7 @@ -- -- GRANT_ON_SCHEMA_PROPAGATION -- --- this test has different output for PG13/14 compared to PG15 +-- this test has different output for PG14 compared to PG15 -- In PG15, public schema is owned by pg_database_owner role -- Relevant PG commit: b073c3ccd06e4cb845e121387a43faa8c68a7b62 SHOW server_version \gset @@ -327,6 +327,8 @@ SELECT master_remove_node('localhost', :worker_2_port); (1 row) +-- to avoid different output in PG15 +GRANT CREATE ON SCHEMA public TO public; -- distribute the public schema (it has to be distributed by now but just in case) CREATE TABLE public_schema_table (id INT); SELECT create_distributed_table('public_schema_table', 'id'); diff --git a/src/test/regress/expected/grant_on_schema_propagation_0.out b/src/test/regress/expected/grant_on_schema_propagation_0.out index 6b8b782ca..9806a0dbd 100644 --- a/src/test/regress/expected/grant_on_schema_propagation_0.out +++ b/src/test/regress/expected/grant_on_schema_propagation_0.out @@ -1,7 +1,7 @@ -- -- GRANT_ON_SCHEMA_PROPAGATION -- --- this test has different output for PG13/14 compared to PG15 +-- this test has different output for PG14 compared to PG15 -- In PG15, public schema is owned by pg_database_owner role -- Relevant PG commit: b073c3ccd06e4cb845e121387a43faa8c68a7b62 SHOW server_version \gset @@ -327,6 +327,8 @@ SELECT master_remove_node('localhost', :worker_2_port); (1 row) +-- to avoid different output in PG15 +GRANT CREATE ON SCHEMA public TO public; -- distribute the public schema (it has to be distributed by now but just in case) CREATE TABLE public_schema_table (id INT); SELECT create_distributed_table('public_schema_table', 'id'); diff --git a/src/test/regress/expected/insert_select_into_local_table.out b/src/test/regress/expected/insert_select_into_local_table.out index f53348272..0e919b7cd 100644 --- a/src/test/regress/expected/insert_select_into_local_table.out +++ b/src/test/regress/expected/insert_select_into_local_table.out @@ -1112,4 +1112,4 @@ RETURNING *; ROLLBACK; \set VERBOSITY terse DROP SCHEMA insert_select_into_local_table CASCADE; -NOTICE: drop cascades to 12 other objects +NOTICE: drop cascades to 13 other objects diff --git a/src/test/regress/expected/insert_select_repartition.out b/src/test/regress/expected/insert_select_repartition.out index 5d9ddca6a..88acc49e3 100644 --- a/src/test/regress/expected/insert_select_repartition.out +++ b/src/test/regress/expected/insert_select_repartition.out @@ -549,7 +549,7 @@ SELECT create_distributed_table('target_table', 'a'); INSERT INTO source_table SELECT floor(i/4), i*i FROM generate_series(1, 20) i; SET client_min_messages TO DEBUG1; INSERT INTO target_table SELECT a, max(b) FROM source_table GROUP BY a; -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT RESET client_min_messages; SELECT * FROM target_table ORDER BY a; @@ -622,40 +622,40 @@ INSERT INTO target_table WHERE a BETWEEN $1 AND $2 GROUP BY a; SET client_min_messages TO DEBUG1; EXECUTE insert_plan(0, 2); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(0, 2); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(0, 2); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(0, 2); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(0, 2); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(0, 2); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(2, 4); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(2, 4); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(2, 4); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(2, 4); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(2, 4); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(2, 4); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT RESET client_min_messages; SELECT a, count(*), count(distinct b) distinct_values FROM target_table GROUP BY a ORDER BY a; @@ -680,25 +680,25 @@ INSERT INTO target_table WHERE a=$1 GROUP BY a; SET client_min_messages TO DEBUG1; EXECUTE insert_plan(0); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Collecting INSERT ... SELECT results on coordinator EXECUTE insert_plan(0); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Collecting INSERT ... SELECT results on coordinator EXECUTE insert_plan(0); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Collecting INSERT ... SELECT results on coordinator EXECUTE insert_plan(0); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Collecting INSERT ... SELECT results on coordinator EXECUTE insert_plan(0); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Collecting INSERT ... SELECT results on coordinator EXECUTE insert_plan(0); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Collecting INSERT ... SELECT results on coordinator EXECUTE insert_plan(0); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Collecting INSERT ... SELECT results on coordinator RESET client_min_messages; SELECT a, count(*), count(distinct b) distinct_values FROM target_table GROUP BY a ORDER BY a; @@ -756,14 +756,15 @@ DEALLOCATE insert_plan; -- TRUNCATE target_table; SET client_min_messages TO DEBUG2; +SET citus.enable_non_colocated_router_query_pushdown TO ON; WITH r AS ( INSERT INTO target_table SELECT * FROM source_table RETURNING * ) INSERT INTO target_table SELECT source_table.a, max(source_table.b) FROM source_table NATURAL JOIN r GROUP BY source_table.a; -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: only SELECT, UPDATE, or DELETE common table expressions may be router planned DEBUG: generating subplan XXX_1 for CTE r: INSERT INTO insert_select_repartition.target_table (a, b) SELECT source_table.a, source_table.b FROM insert_select_repartition.source_table RETURNING target_table.a, target_table.b -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Router planner cannot handle multi-shard select queries DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT a, max AS b FROM (SELECT source_table.a, max(source_table.b) AS max FROM (insert_select_repartition.source_table JOIN (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) r USING (a, b)) GROUP BY source_table.a) citus_insert_select_subquery DEBUG: Router planner cannot handle multi-shard select queries @@ -777,6 +778,7 @@ DEBUG: partitioning SELECT query by column index 0 with name 'a' DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213610 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213606_to_0,repartitioned_results_xxxxx_from_4213607_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213611 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213607_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213612 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213609_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) +RESET citus.enable_non_colocated_router_query_pushdown; RESET client_min_messages; SELECT * FROM target_table ORDER BY a, b; a | b @@ -1013,7 +1015,7 @@ SELECT create_distributed_table('target_table', 'a'); INSERT INTO source_table SELECT i, i * i FROM generate_series(1, 10) i; SET client_min_messages TO DEBUG2; INSERT INTO target_table SELECT a AS aa, b AS aa, 1 AS aa, 2 AS aa FROM source_table; -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Router planner cannot handle multi-shard select queries DEBUG: performing repartitioned INSERT ... SELECT DEBUG: partitioning SELECT query by column index 0 with name 'a' @@ -1047,7 +1049,7 @@ EXPLAIN (costs off) INSERT INTO target_table SELECT a AS aa, b AS aa, 1 AS aa, 2 SET client_min_messages TO DEBUG2; INSERT INTO target_table SELECT a AS aa, b AS aa, 1 AS aa, 2 AS aa FROM source_table; -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Router planner cannot handle multi-shard select queries DEBUG: Collecting INSERT ... SELECT results on coordinator RESET client_min_messages; @@ -1090,19 +1092,19 @@ EXPLAIN (costs off) INSERT INTO test(y, x) SELECT a.x, b.y FROM test a JOIN test Custom Scan (Citus INSERT ... SELECT) INSERT/SELECT method: pull to coordinator -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 Tasks Shown: None, not supported for re-partition queries -> MapMergeJob Map Task Count: 3 - Merge Task Count: 4 + Merge Task Count: 6 -> MapMergeJob Map Task Count: 3 - Merge Task Count: 4 + Merge Task Count: 6 (11 rows) SET client_min_messages TO DEBUG1; INSERT INTO test(y, x) SELECT a.x, b.y FROM test a JOIN test b USING (y); -DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: Collecting INSERT ... SELECT results on coordinator RESET client_min_messages; SELECT count(*) FROM test; @@ -1119,19 +1121,19 @@ EXPLAIN (costs off) INSERT INTO test SELECT a.* FROM test a JOIN test b USING (y Custom Scan (Citus INSERT ... SELECT) INSERT/SELECT method: pull to coordinator -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 Tasks Shown: None, not supported for re-partition queries -> MapMergeJob Map Task Count: 3 - Merge Task Count: 4 + Merge Task Count: 6 -> MapMergeJob Map Task Count: 3 - Merge Task Count: 4 + Merge Task Count: 6 (11 rows) SET client_min_messages TO DEBUG1; INSERT INTO test SELECT a.* FROM test a JOIN test b USING (y); -DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: Collecting INSERT ... SELECT results on coordinator RESET client_min_messages; SELECT count(*) FROM test; @@ -1217,7 +1219,7 @@ ON CONFLICT(c1, c2, c3, c4, c5, c6) DO UPDATE SET cardinality = enriched.cardinality + excluded.cardinality, sum = enriched.sum + excluded.sum; -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Router planner cannot handle multi-shard select queries DEBUG: performing repartitioned INSERT ... SELECT DEBUG: partitioning SELECT query by column index 0 with name 'c1' @@ -1300,6 +1302,33 @@ explain (costs off) insert into table_with_user_sequences select y, x from table -> Seq Scan on table_with_user_sequences_4213652 table_with_user_sequences (8 rows) +CREATE TABLE dist_table_1(id int); +SELECT create_distributed_table('dist_table_1','id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE dist_table_2(id int); +SELECT create_distributed_table('dist_table_2','id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- verify that insert select with union can be repartitioned. We cannot push down the query +-- since UNION clause has no FROM clause at top level query. +SELECT public.coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_1(id) SELECT id FROM dist_table_1 UNION SELECT id FROM dist_table_2; +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: repartition + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(4 rows) + -- clean-up SET client_min_messages TO WARNING; DROP SCHEMA insert_select_repartition CASCADE; diff --git a/src/test/regress/expected/insert_select_repartition_0.out b/src/test/regress/expected/insert_select_repartition_0.out index 2eea30bdf..7217be3e9 100644 --- a/src/test/regress/expected/insert_select_repartition_0.out +++ b/src/test/regress/expected/insert_select_repartition_0.out @@ -549,7 +549,7 @@ SELECT create_distributed_table('target_table', 'a'); INSERT INTO source_table SELECT floor(i/4), i*i FROM generate_series(1, 20) i; SET client_min_messages TO DEBUG1; INSERT INTO target_table SELECT a, max(b) FROM source_table GROUP BY a; -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT RESET client_min_messages; SELECT * FROM target_table ORDER BY a; @@ -622,40 +622,40 @@ INSERT INTO target_table WHERE a BETWEEN $1 AND $2 GROUP BY a; SET client_min_messages TO DEBUG1; EXECUTE insert_plan(0, 2); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(0, 2); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(0, 2); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(0, 2); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(0, 2); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(0, 2); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(2, 4); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(2, 4); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(2, 4); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(2, 4); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(2, 4); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT EXECUTE insert_plan(2, 4); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT RESET client_min_messages; SELECT a, count(*), count(distinct b) distinct_values FROM target_table GROUP BY a ORDER BY a; @@ -680,25 +680,25 @@ INSERT INTO target_table WHERE a=$1 GROUP BY a; SET client_min_messages TO DEBUG1; EXECUTE insert_plan(0); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Collecting INSERT ... SELECT results on coordinator EXECUTE insert_plan(0); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Collecting INSERT ... SELECT results on coordinator EXECUTE insert_plan(0); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Collecting INSERT ... SELECT results on coordinator EXECUTE insert_plan(0); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Collecting INSERT ... SELECT results on coordinator EXECUTE insert_plan(0); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Collecting INSERT ... SELECT results on coordinator EXECUTE insert_plan(0); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Collecting INSERT ... SELECT results on coordinator EXECUTE insert_plan(0); -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Collecting INSERT ... SELECT results on coordinator RESET client_min_messages; SELECT a, count(*), count(distinct b) distinct_values FROM target_table GROUP BY a ORDER BY a; @@ -756,14 +756,15 @@ DEALLOCATE insert_plan; -- TRUNCATE target_table; SET client_min_messages TO DEBUG2; +SET citus.enable_non_colocated_router_query_pushdown TO ON; WITH r AS ( INSERT INTO target_table SELECT * FROM source_table RETURNING * ) INSERT INTO target_table SELECT source_table.a, max(source_table.b) FROM source_table NATURAL JOIN r GROUP BY source_table.a; -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: only SELECT, UPDATE, or DELETE common table expressions may be router planned DEBUG: generating subplan XXX_1 for CTE r: INSERT INTO insert_select_repartition.target_table (a, b) SELECT a, b FROM insert_select_repartition.source_table RETURNING target_table.a, target_table.b -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Router planner cannot handle multi-shard select queries DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT a, max AS b FROM (SELECT source_table.a, max(source_table.b) AS max FROM (insert_select_repartition.source_table JOIN (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) r USING (a, b)) GROUP BY source_table.a) citus_insert_select_subquery DEBUG: Router planner cannot handle multi-shard select queries @@ -777,6 +778,7 @@ DEBUG: partitioning SELECT query by column index 0 with name 'a' DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213610 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213606_to_0,repartitioned_results_xxxxx_from_4213607_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213611 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213607_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213612 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213609_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) +RESET citus.enable_non_colocated_router_query_pushdown; RESET client_min_messages; SELECT * FROM target_table ORDER BY a, b; a | b @@ -1013,7 +1015,7 @@ SELECT create_distributed_table('target_table', 'a'); INSERT INTO source_table SELECT i, i * i FROM generate_series(1, 10) i; SET client_min_messages TO DEBUG2; INSERT INTO target_table SELECT a AS aa, b AS aa, 1 AS aa, 2 AS aa FROM source_table; -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Router planner cannot handle multi-shard select queries DEBUG: performing repartitioned INSERT ... SELECT DEBUG: partitioning SELECT query by column index 0 with name 'a' @@ -1047,7 +1049,7 @@ EXPLAIN (costs off) INSERT INTO target_table SELECT a AS aa, b AS aa, 1 AS aa, 2 SET client_min_messages TO DEBUG2; INSERT INTO target_table SELECT a AS aa, b AS aa, 1 AS aa, 2 AS aa FROM source_table; -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Router planner cannot handle multi-shard select queries DEBUG: Collecting INSERT ... SELECT results on coordinator RESET client_min_messages; @@ -1090,19 +1092,19 @@ EXPLAIN (costs off) INSERT INTO test(y, x) SELECT a.x, b.y FROM test a JOIN test Custom Scan (Citus INSERT ... SELECT) INSERT/SELECT method: pull to coordinator -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 Tasks Shown: None, not supported for re-partition queries -> MapMergeJob Map Task Count: 3 - Merge Task Count: 4 + Merge Task Count: 6 -> MapMergeJob Map Task Count: 3 - Merge Task Count: 4 + Merge Task Count: 6 (11 rows) SET client_min_messages TO DEBUG1; INSERT INTO test(y, x) SELECT a.x, b.y FROM test a JOIN test b USING (y); -DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: Collecting INSERT ... SELECT results on coordinator RESET client_min_messages; SELECT count(*) FROM test; @@ -1119,19 +1121,19 @@ EXPLAIN (costs off) INSERT INTO test SELECT a.* FROM test a JOIN test b USING (y Custom Scan (Citus INSERT ... SELECT) INSERT/SELECT method: pull to coordinator -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 Tasks Shown: None, not supported for re-partition queries -> MapMergeJob Map Task Count: 3 - Merge Task Count: 4 + Merge Task Count: 6 -> MapMergeJob Map Task Count: 3 - Merge Task Count: 4 + Merge Task Count: 6 (11 rows) SET client_min_messages TO DEBUG1; INSERT INTO test SELECT a.* FROM test a JOIN test b USING (y); -DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: Collecting INSERT ... SELECT results on coordinator RESET client_min_messages; SELECT count(*) FROM test; @@ -1217,7 +1219,7 @@ ON CONFLICT(c1, c2, c3, c4, c5, c6) DO UPDATE SET cardinality = enriched.cardinality + excluded.cardinality, sum = enriched.sum + excluded.sum; -DEBUG: INSERT target table and the source relation of the SELECT partition column value must be colocated in distributed INSERT ... SELECT +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT DEBUG: Router planner cannot handle multi-shard select queries DEBUG: performing repartitioned INSERT ... SELECT DEBUG: partitioning SELECT query by column index 0 with name 'c1' @@ -1300,6 +1302,33 @@ explain (costs off) insert into table_with_user_sequences select y, x from table -> Seq Scan on table_with_user_sequences_4213652 table_with_user_sequences (8 rows) +CREATE TABLE dist_table_1(id int); +SELECT create_distributed_table('dist_table_1','id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE dist_table_2(id int); +SELECT create_distributed_table('dist_table_2','id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- verify that insert select with union can be repartitioned. We cannot push down the query +-- since UNION clause has no FROM clause at top level query. +SELECT public.coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_1(id) SELECT id FROM dist_table_1 UNION SELECT id FROM dist_table_2; +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: repartition + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(4 rows) + -- clean-up SET client_min_messages TO WARNING; DROP SCHEMA insert_select_repartition CASCADE; diff --git a/src/test/regress/expected/insert_select_single_shard_table.out b/src/test/regress/expected/insert_select_single_shard_table.out new file mode 100644 index 000000000..8dbb1cf9a --- /dev/null +++ b/src/test/regress/expected/insert_select_single_shard_table.out @@ -0,0 +1,928 @@ +CREATE SCHEMA insert_select_single_shard_table; +SET search_path TO insert_select_single_shard_table; +SET citus.next_shard_id TO 1820000; +SET citus.shard_count TO 32; +SET citus.shard_replication_factor TO 1; +SET client_min_messages TO NOTICE; +CREATE TABLE nullkey_c1_t1(a int, b int); +CREATE TABLE nullkey_c1_t2(a int, b int); +SELECT create_distributed_table('nullkey_c1_t1', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('nullkey_c1_t2', null, colocate_with=>'nullkey_c1_t1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE nullkey_c2_t1(a int, b int); +SELECT create_distributed_table('nullkey_c2_t1', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE reference_table(a int, b int); +SELECT create_reference_table('reference_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE distributed_table_c1_t1(a int, b int); +SELECT create_distributed_table('distributed_table_c1_t1', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE distributed_table_c1_t2(a int, b int); +SELECT create_distributed_table('distributed_table_c1_t2', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE distributed_table_c2_t1(a int, b int); +SELECT create_distributed_table('distributed_table_c2_t1', 'a', colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE citus_local_table(a int, b int); +SELECT citus_add_local_table_to_metadata('citus_local_table'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE postgres_local_table(a int, b int); +CREATE FUNCTION reload_tables() RETURNS void AS $$ + BEGIN + SET LOCAL client_min_messages TO WARNING; + + TRUNCATE nullkey_c1_t1, nullkey_c1_t2, nullkey_c2_t1, reference_table, distributed_table_c1_t1, + distributed_table_c1_t2, distributed_table_c2_t1, citus_local_table, postgres_local_table; + + INSERT INTO nullkey_c1_t1 SELECT i, i FROM generate_series(1, 8) i; + INSERT INTO nullkey_c1_t2 SELECT i, i FROM generate_series(2, 7) i; + INSERT INTO nullkey_c2_t1 SELECT i, i FROM generate_series(2, 7) i; + INSERT INTO reference_table SELECT i, i FROM generate_series(0, 5) i; + INSERT INTO distributed_table_c1_t1 SELECT i, i FROM generate_series(3, 8) i; + INSERT INTO distributed_table_c1_t2 SELECT i, i FROM generate_series(2, 9) i; + INSERT INTO distributed_table_c2_t1 SELECT i, i FROM generate_series(5, 10) i; + INSERT INTO citus_local_table SELECT i, i FROM generate_series(0, 10) i; + INSERT INTO postgres_local_table SELECT i, i FROM generate_series(5, 10) i; + END; +$$ LANGUAGE plpgsql; +SELECT reload_tables(); + reload_tables +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE append_table (a int, b int); +SELECT create_distributed_table('append_table', 'a', 'append'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT master_create_empty_shard('append_table') AS shardid1 \gset +SELECT master_create_empty_shard('append_table') AS shardid2 \gset +SELECT master_create_empty_shard('append_table') AS shardid3 \gset +COPY append_table (a, b) FROM STDIN WITH (format 'csv', append_to_shard :shardid1); +COPY append_table (a, b) FROM STDIN WITH (format 'csv', append_to_shard :shardid2); +CREATE TABLE range_table(a int, b int); +SELECT create_distributed_table('range_table', 'a', 'range'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CALL public.create_range_partitioned_shards('range_table', '{"0","25"}','{"24","49"}'); +INSERT INTO range_table VALUES (0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 50); +CREATE MATERIALIZED VIEW matview AS SELECT b*2+a AS a, a*a AS b FROM nullkey_c1_t1; +SET client_min_messages TO DEBUG2; +-- Test inserting into a distributed table by selecting from a combination of +-- different table types together with single-shard tables. +-- use a single-shard table +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: The target table's partition column should correspond to a partition column in the subquery. +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- use a reference table +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN reference_table USING (a); +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: The target table's partition column should correspond to a partition column in the subquery. +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 RIGHT JOIN reference_table USING (b) WHERE reference_table.a >= 1 AND reference_table.a <= 5; +DEBUG: cannot perform a lateral outer join when a distributed subquery references a reference table +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM nullkey_c1_t2 LEFT JOIN reference_table USING (b); +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: The target table's partition column should correspond to a partition column in the subquery. +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 INTERSECT SELECT * FROM reference_table; +DEBUG: cannot push down this subquery +DETAIL: Intersect and Except are currently unsupported +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- use a colocated single-shard table +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN nullkey_c1_t2 USING (b); +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: The target table's partition column should correspond to a partition column in the subquery. +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 FULL JOIN nullkey_c1_t2 USING (a); +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: The target table's partition column should correspond to a partition column in the subquery. +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO distributed_table_c1_t1 SELECT COALESCE(nullkey_c1_t1.a, 1), nullkey_c1_t1.b FROM nullkey_c1_t1 FULL JOIN matview USING (a); +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "matview" to a subquery +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM insert_select_single_shard_table.matview WHERE true +DEBUG: recursively planning left side of the full join since the other side is a recurring rel +DEBUG: recursively planning distributed relation "nullkey_c1_t1" since it is part of a distributed join node that is outer joined with a recurring rel +DEBUG: Wrapping relation "nullkey_c1_t1" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_2 for subquery SELECT a, b FROM insert_select_single_shard_table.nullkey_c1_t1 WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT COALESCE(nullkey_c1_t1.a, 1) AS a, nullkey_c1_t1.b FROM ((SELECT nullkey_c1_t1_1.a, nullkey_c1_t1_1.b FROM (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) nullkey_c1_t1_1) nullkey_c1_t1 FULL JOIN (SELECT matview_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) matview_1) matview USING (a)) +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO distributed_table_c1_t1 SELECT * FROM nullkey_c1_t1 UNION SELECT * FROM nullkey_c1_t2; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: The target table's partition column should correspond to a partition column in the subquery. +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- use a non-colocated single-shard table +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM nullkey_c1_t2 LEFT JOIN nullkey_c2_t1 USING (a); +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: The target table's partition column should correspond to a partition column in the subquery. +DEBUG: router planner does not support queries that reference non-colocated distributed tables +ERROR: cannot push down this subquery +DETAIL: nullkey_c1_t2 and nullkey_c2_t1 are not colocated +INSERT INTO distributed_table_c1_t1 SELECT * FROM nullkey_c1_t1 UNION SELECT * FROM nullkey_c2_t1; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: The target table's partition column should correspond to a partition column in the subquery. +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT a, b FROM insert_select_single_shard_table.nullkey_c1_t1 +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_2 for subquery SELECT a, b FROM insert_select_single_shard_table.nullkey_c2_t1 +DEBUG: Creating router plan +DEBUG: generating subplan XXX_3 for subquery SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) UNION SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT a, b FROM (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) citus_insert_select_subquery +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +SET client_min_messages TO DEBUG1; +SET citus.enable_repartition_joins TO ON; +-- use a distributed table that is colocated with the target table, with repartition joins enabled +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN distributed_table_c1_t2 USING (a); +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO distributed_table_c1_t1 SELECT distributed_table_c1_t2.a, distributed_table_c1_t2.b FROM nullkey_c1_t1 JOIN distributed_table_c1_t2 USING (a); +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO distributed_table_c1_t1 SELECT distributed_table_c1_t2.a, distributed_table_c1_t2.b FROM nullkey_c1_t1 JOIN distributed_table_c1_t2 USING (b); +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO distributed_table_c1_t1 SELECT distributed_table_c1_t2.a, distributed_table_c1_t2.b FROM nullkey_c1_t1 JOIN distributed_table_c1_t2 USING (a) WHERE distributed_table_c1_t2.a = 1; +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- use a distributed table that is not colocated with the target table, with repartition joins enabled +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM nullkey_c1_t2 JOIN distributed_table_c2_t1 USING (a); +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: Collecting INSERT ... SELECT results on coordinator +RESET citus.enable_repartition_joins; +SET client_min_messages TO DEBUG2; +-- use a citus local table +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN citus_local_table USING (a); +DEBUG: distributed INSERT ... SELECT cannot select from distributed tables and local tables at the same time +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "citus_local_table" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM insert_select_single_shard_table.citus_local_table WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM (insert_select_single_shard_table.nullkey_c1_t1 JOIN (SELECT citus_local_table_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) citus_local_table_1) citus_local_table USING (a)) +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- use a postgres local table +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM nullkey_c1_t2 JOIN postgres_local_table USING (a); +DEBUG: distributed INSERT ... SELECT cannot select from distributed tables and local tables at the same time +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "postgres_local_table" to a subquery +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM insert_select_single_shard_table.postgres_local_table WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM (insert_select_single_shard_table.nullkey_c1_t2 JOIN (SELECT postgres_local_table_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) postgres_local_table_1) postgres_local_table USING (a)) +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- use append / range distributed tables +INSERT INTO range_table SELECT * FROM nullkey_c1_t1; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: The target table's partition column should correspond to a partition column in the subquery. +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO append_table SELECT * FROM nullkey_c1_t1; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: The target table's partition column should correspond to a partition column in the subquery. +DEBUG: INSERT ... SELECT into an append-distributed table is not supported +ERROR: INSERT ... SELECT into an append-distributed table is not supported +SELECT avg(a), avg(b) FROM distributed_table_c1_t1 ORDER BY 1, 2; +DEBUG: Router planner cannot handle multi-shard select queries + avg | avg +--------------------------------------------------------------------- + 4.3421052631578947 | 4.5277777777777778 +(1 row) + +TRUNCATE distributed_table_c1_t1; +INSERT INTO distributed_table_c1_t1 SELECT i, i FROM generate_series(3, 8) i; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- Test inserting into a reference table by selecting from a combination of +-- different table types together with single-shard tables. +-- use a single-shard table +INSERT INTO reference_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1; +DEBUG: only reference tables may be queried when targeting a reference table with distributed INSERT ... SELECT +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- use a reference table +INSERT INTO reference_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN reference_table USING (a); +DEBUG: only reference tables may be queried when targeting a reference table with distributed INSERT ... SELECT +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO reference_table SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM nullkey_c1_t2 LEFT JOIN reference_table USING (b); +DEBUG: only reference tables may be queried when targeting a reference table with distributed INSERT ... SELECT +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO reference_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 UNION SELECT * FROM reference_table; +DEBUG: cannot push down this subquery +DETAIL: Reference tables are not supported with union operator +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO reference_table SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM nullkey_c1_t2 LEFT JOIN reference_table USING (b) WHERE b IN (SELECT b FROM matview); +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: generating subplan XXX_1 for subquery SELECT b FROM insert_select_single_shard_table.matview +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM (insert_select_single_shard_table.nullkey_c1_t2 LEFT JOIN insert_select_single_shard_table.reference_table USING (b)) WHERE (nullkey_c1_t2.b OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(b integer))) +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- use a colocated single-shard table +INSERT INTO reference_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN nullkey_c1_t2 USING (b); +DEBUG: only reference tables may be queried when targeting a reference table with distributed INSERT ... SELECT +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO reference_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 FULL JOIN nullkey_c1_t2 USING (a); +DEBUG: only reference tables may be queried when targeting a reference table with distributed INSERT ... SELECT +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- use a non-colocated single-shard table +INSERT INTO reference_table SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM nullkey_c1_t2 LEFT JOIN nullkey_c2_t1 USING (a); +DEBUG: only reference tables may be queried when targeting a reference table with distributed INSERT ... SELECT +DEBUG: router planner does not support queries that reference non-colocated distributed tables +ERROR: cannot push down this subquery +DETAIL: nullkey_c1_t2 and nullkey_c2_t1 are not colocated +-- use a distributed table +SET client_min_messages TO DEBUG1; +SET citus.enable_repartition_joins TO ON; +INSERT INTO reference_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN distributed_table_c1_t2 USING (a); +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO reference_table SELECT distributed_table_c1_t2.a, distributed_table_c1_t2.b FROM nullkey_c1_t1 JOIN distributed_table_c1_t2 USING (a); +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO reference_table SELECT distributed_table_c1_t2.a, distributed_table_c1_t2.b FROM nullkey_c1_t1 JOIN distributed_table_c1_t2 USING (b); +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO reference_table SELECT distributed_table_c1_t2.a, distributed_table_c1_t2.b FROM nullkey_c1_t1 JOIN distributed_table_c1_t2 USING (a) WHERE distributed_table_c1_t2.a = 1; +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: Collecting INSERT ... SELECT results on coordinator +RESET citus.enable_repartition_joins; +SET client_min_messages TO DEBUG2; +-- use a citus local table +INSERT INTO reference_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN citus_local_table USING (a); +DEBUG: distributed INSERT ... SELECT cannot select from distributed tables and local tables at the same time +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "citus_local_table" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM insert_select_single_shard_table.citus_local_table WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM (insert_select_single_shard_table.nullkey_c1_t1 JOIN (SELECT citus_local_table_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) citus_local_table_1) citus_local_table USING (a)) +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- use a postgres local table +INSERT INTO reference_table SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM nullkey_c1_t2 JOIN postgres_local_table USING (a); +DEBUG: distributed INSERT ... SELECT cannot select from distributed tables and local tables at the same time +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "postgres_local_table" to a subquery +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM insert_select_single_shard_table.postgres_local_table WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM (insert_select_single_shard_table.nullkey_c1_t2 JOIN (SELECT postgres_local_table_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) postgres_local_table_1) postgres_local_table USING (a)) +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +SELECT avg(a), avg(b) FROM reference_table ORDER BY 1, 2; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + avg | avg +--------------------------------------------------------------------- + 4.3063063063063063 | 4.3063063063063063 +(1 row) + +TRUNCATE reference_table; +INSERT INTO reference_table SELECT i, i FROM generate_series(0, 5) i; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- Test inserting into a citus local table by selecting from a combination of +-- different table types together with single-shard tables. +-- use a single-shard table +INSERT INTO citus_local_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1; +DEBUG: distributed INSERT ... SELECT cannot insert into a local table that is added to metadata +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- use a reference table +INSERT INTO citus_local_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN reference_table USING (a); +DEBUG: distributed INSERT ... SELECT cannot insert into a local table that is added to metadata +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- use a colocated single-shard table +INSERT INTO citus_local_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN nullkey_c1_t2 USING (b); +DEBUG: distributed INSERT ... SELECT cannot insert into a local table that is added to metadata +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- use a distributed table +SET client_min_messages TO DEBUG1; +SET citus.enable_repartition_joins TO ON; +INSERT INTO citus_local_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN distributed_table_c1_t2 USING (a); +DEBUG: distributed INSERT ... SELECT cannot insert into a local table that is added to metadata +DEBUG: Collecting INSERT ... SELECT results on coordinator +RESET citus.enable_repartition_joins; +SET client_min_messages TO DEBUG2; +-- use a citus local table +INSERT INTO citus_local_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN citus_local_table USING (a); +DEBUG: distributed INSERT ... SELECT cannot select from distributed tables and local tables at the same time +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "citus_local_table" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM insert_select_single_shard_table.citus_local_table WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM (insert_select_single_shard_table.nullkey_c1_t1 JOIN (SELECT citus_local_table_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) citus_local_table_1) citus_local_table USING (a)) +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- use a postgres local table +INSERT INTO citus_local_table SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM nullkey_c1_t2 JOIN postgres_local_table USING (a); +DEBUG: distributed INSERT ... SELECT cannot select from distributed tables and local tables at the same time +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "postgres_local_table" to a subquery +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM insert_select_single_shard_table.postgres_local_table WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM (insert_select_single_shard_table.nullkey_c1_t2 JOIN (SELECT postgres_local_table_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) postgres_local_table_1) postgres_local_table USING (a)) +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +SELECT avg(a), avg(b) FROM citus_local_table ORDER BY 1, 2; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + avg | avg +--------------------------------------------------------------------- + 4.5270270270270270 | 4.5270270270270270 +(1 row) + +TRUNCATE citus_local_table; +INSERT INTO citus_local_table SELECT i, i FROM generate_series(0, 10) i; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- Test inserting into a single-shard table by selecting from a combination of +-- different table types, together with or without single-shard tables. +-- use a postgres local table +INSERT INTO nullkey_c1_t1 SELECT postgres_local_table.a, postgres_local_table.b FROM postgres_local_table; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO nullkey_c1_t1 SELECT postgres_local_table.a, postgres_local_table.b FROM postgres_local_table JOIN reference_table USING (a); +DEBUG: distributed INSERT ... SELECT cannot select from a local table +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO nullkey_c1_t1 SELECT postgres_local_table.a, postgres_local_table.b FROM postgres_local_table LEFT JOIN nullkey_c1_t1 USING (a); +DEBUG: distributed INSERT ... SELECT cannot select from distributed tables and local tables at the same time +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "postgres_local_table" to a subquery +DEBUG: generating subplan XXX_1 for subquery SELECT a, b FROM insert_select_single_shard_table.postgres_local_table WHERE true +DEBUG: recursively planning right side of the left join since the outer side is a recurring rel +DEBUG: recursively planning distributed relation "nullkey_c1_t1" since it is part of a distributed join node that is outer joined with a recurring rel +DEBUG: Wrapping relation "nullkey_c1_t1" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_2 for subquery SELECT a FROM insert_select_single_shard_table.nullkey_c1_t1 WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT postgres_local_table.a, postgres_local_table.b FROM ((SELECT postgres_local_table_1.a, postgres_local_table_1.b FROM (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) postgres_local_table_1) postgres_local_table LEFT JOIN (SELECT nullkey_c1_t1_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) nullkey_c1_t1_1) nullkey_c1_t1 USING (a)) +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- use a citus local table +INSERT INTO nullkey_c1_t1 SELECT citus_local_table.a, citus_local_table.b FROM citus_local_table; +DEBUG: distributed INSERT ... SELECT cannot select from a local relation when inserting into a distributed table +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO nullkey_c1_t1 SELECT citus_local_table.a, citus_local_table.b FROM citus_local_table JOIN reference_table USING (a) JOIN postgres_local_table USING (a) ORDER BY 1,2 OFFSET 7; +DEBUG: distributed INSERT ... SELECT cannot select from a local table +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO nullkey_c1_t1 SELECT citus_local_table.a, citus_local_table.b FROM citus_local_table JOIN nullkey_c1_t1 USING (a); +DEBUG: distributed INSERT ... SELECT cannot select from distributed tables and local tables at the same time +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "citus_local_table" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT a, b FROM insert_select_single_shard_table.citus_local_table WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT citus_local_table.a, citus_local_table.b FROM ((SELECT citus_local_table_1.a, citus_local_table_1.b FROM (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) citus_local_table_1) citus_local_table JOIN insert_select_single_shard_table.nullkey_c1_t1 USING (a)) +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- use a distributed table +INSERT INTO nullkey_c1_t1 SELECT distributed_table_c1_t2.a, distributed_table_c1_t2.b FROM distributed_table_c1_t2; +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO nullkey_c1_t1 SELECT distributed_table_c1_t2.a, distributed_table_c1_t2.b FROM distributed_table_c1_t2 JOIN reference_table USING (a); +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Collecting INSERT ... SELECT results on coordinator +SET client_min_messages TO DEBUG1; +SET citus.enable_repartition_joins TO ON; +INSERT INTO nullkey_c1_t1 SELECT distributed_table_c1_t2.a, distributed_table_c1_t2.b FROM distributed_table_c1_t2 JOIN nullkey_c1_t1 USING (a); +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: Collecting INSERT ... SELECT results on coordinator +RESET citus.enable_repartition_joins; +SET client_min_messages TO DEBUG2; +-- use a non-colocated single-shard table +INSERT INTO nullkey_c2_t1 SELECT q.* FROM (SELECT reference_table.* FROM reference_table LEFT JOIN nullkey_c1_t1 USING (a)) q JOIN nullkey_c1_t2 USING (a); +DEBUG: cannot perform a lateral outer join when a distributed subquery references a reference table +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- use a materialized view +INSERT INTO nullkey_c1_t1 SELECT * FROM matview; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO nullkey_c1_t1 SELECT reference_table.a, reference_table.b FROM reference_table JOIN matview ON (reference_table.a = matview.a); +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO nullkey_c1_t1 SELECT q.* FROM (SELECT reference_table.* FROM reference_table JOIN nullkey_c1_t1 USING (a)) q JOIN matview USING (a); +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "matview" to a subquery +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM insert_select_single_shard_table.matview WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT q.a, q.b FROM ((SELECT reference_table.a, reference_table.b FROM (insert_select_single_shard_table.reference_table JOIN insert_select_single_shard_table.nullkey_c1_t1 USING (a))) q JOIN (SELECT matview_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) matview_1) matview USING (a)) +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- use append / range distributed tables +INSERT INTO nullkey_c1_t1 SELECT * FROM range_table; +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO nullkey_c1_t1 SELECT * FROM append_table; +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT +DEBUG: Router planner does not support append-partitioned tables. +DEBUG: Collecting INSERT ... SELECT results on coordinator +SELECT avg(a), avg(b) FROM nullkey_c1_t1 ORDER BY 1, 2; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + avg | avg +--------------------------------------------------------------------- + 5.6971153846153846 | 8.4903846153846154 +(1 row) + +SELECT avg(a), avg(b) FROM nullkey_c2_t1 ORDER BY 1, 2; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + avg | avg +--------------------------------------------------------------------- + 3.9864864864864865 | 3.9864864864864865 +(1 row) + +TRUNCATE nullkey_c1_t1, nullkey_c2_t1; +INSERT INTO nullkey_c1_t1 SELECT i, i FROM generate_series(1, 8) i; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO nullkey_c2_t1 SELECT i, i FROM generate_series(2, 7) i; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- Test inserting into a local table by selecting from a combination of +-- different table types, together with or without single-shard tables. +INSERT INTO postgres_local_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN reference_table USING (a); +DEBUG: Creating router plan +INSERT INTO postgres_local_table SELECT * FROM nullkey_c1_t1 ORDER BY 1,2 OFFSET 3 LIMIT 2; +DEBUG: Creating router plan +WITH cte_1 AS ( + DELETE FROM nullkey_c1_t1 WHERE a >= 1 and a <= 4 RETURNING * +) +INSERT INTO postgres_local_table SELECT cte_1.* FROM cte_1 LEFT JOIN nullkey_c1_t2 USING (a) WHERE nullkey_c1_t2.a IS NULL; +DEBUG: Creating router plan +INSERT INTO postgres_local_table SELECT * FROM nullkey_c1_t1 EXCEPT SELECT * FROM postgres_local_table; +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: generating subplan XXX_1 for subquery SELECT a, b FROM insert_select_single_shard_table.postgres_local_table +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_2 for subquery SELECT a, b FROM insert_select_single_shard_table.nullkey_c1_t1 +DEBUG: Creating router plan +DEBUG: generating subplan XXX_3 for subquery SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) EXCEPT SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT a, b FROM (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) citus_insert_select_subquery +DEBUG: Creating router plan +SELECT avg(a), avg(b) FROM postgres_local_table ORDER BY 1, 2; + avg | avg +--------------------------------------------------------------------- + 5.0000000000000000 | 5.0000000000000000 +(1 row) + +TRUNCATE postgres_local_table; +INSERT INTO postgres_local_table SELECT i, i FROM generate_series(5, 10) i; +-- Try slightly more complex queries. +SET client_min_messages TO DEBUG1; +WITH cte_1 AS ( + SELECT nullkey_c1_t1.a, reference_table.b FROM nullkey_c1_t1 JOIN reference_table USING (a) +), +cte_2 AS ( + SELECT reference_table.a, postgres_local_table.b FROM postgres_local_table LEFT JOIN reference_table USING (b) +) +INSERT INTO distributed_table_c1_t1 +SELECT cte_1.* FROM cte_1 JOIN cte_2 USING (a) JOIN distributed_table_c1_t2 USING (a) ORDER BY 1,2; +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: CTE cte_2 is going to be inlined via distributed planning +DEBUG: Wrapping relation "postgres_local_table" to a subquery +DEBUG: generating subplan XXX_1 for subquery SELECT b FROM insert_select_single_shard_table.postgres_local_table WHERE true +DEBUG: generating subplan XXX_2 for subquery SELECT nullkey_c1_t1.a, reference_table.b FROM (insert_select_single_shard_table.nullkey_c1_t1 JOIN insert_select_single_shard_table.reference_table USING (a)) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT a, b FROM (SELECT cte_1.a, cte_1.b FROM (((SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) cte_1 JOIN (SELECT reference_table.a, postgres_local_table.b FROM ((SELECT NULL::integer AS a, postgres_local_table_1.b FROM (SELECT intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(b integer)) postgres_local_table_1) postgres_local_table LEFT JOIN insert_select_single_shard_table.reference_table USING (b))) cte_2 USING (a)) JOIN insert_select_single_shard_table.distributed_table_c1_t2 USING (a)) ORDER BY cte_1.a, cte_1.b) citus_insert_select_subquery +DEBUG: performing repartitioned INSERT ... SELECT +SET client_min_messages TO DEBUG2; +WITH cte_1 AS ( + SELECT nullkey_c1_t1.a, reference_table.b FROM nullkey_c1_t1 JOIN reference_table USING (a) +), +cte_2 AS ( + SELECT * FROM nullkey_c1_t2 WHERE EXISTS ( + SELECT 1 FROM reference_table WHERE reference_table.a = nullkey_c1_t2.a + ) + ORDER BY 1,2 OFFSET 1 LIMIT 4 +) +INSERT INTO distributed_table_c1_t1 +SELECT * FROM cte_1 UNION SELECT * FROM cte_2 EXCEPT SELECT * FROM reference_table; +DEBUG: cannot push down this subquery +DETAIL: CTEs in subqueries are currently unsupported +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: CTE cte_2 is going to be inlined via distributed planning +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO distributed_table_c1_t1 (a, b) +SELECT t1.a, t2.b +FROM nullkey_c1_t1 t1 +JOIN ( + SELECT b FROM nullkey_c1_t2 ORDER BY b DESC LIMIT 1 +) t2 +ON t1.b < t2.b; +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO distributed_table_c1_t1 (a, b) +WITH cte AS ( + SELECT a, b, + (SELECT a FROM nullkey_c1_t2 WHERE b = t.b) AS d1, + (SELECT a FROM reference_table WHERE b = t.b) AS d2 + FROM nullkey_c1_t1 t +) +SELECT d1, COALESCE(d2, a) FROM cte WHERE d1 IS NOT NULL AND d2 IS NOT NULL; +DEBUG: CTE cte is going to be inlined via distributed planning +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: Subquery contains an expression that is not a simple column reference in the same position as the target table's partition column. +HINT: Ensure the target table's partition column has a corresponding simple column reference to a distributed table's partition column in the subquery. +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO citus_local_table (a, b) +SELECT t1.a, t2.b +FROM nullkey_c1_t1 t1 +CROSS JOIN ( + SELECT b FROM nullkey_c2_t1 ORDER BY b LIMIT 1 +) t2; +DEBUG: distributed INSERT ... SELECT cannot insert into a local table that is added to metadata +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT b FROM insert_select_single_shard_table.nullkey_c2_t1 ORDER BY b LIMIT 1 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT t1.a, t2.b FROM (insert_select_single_shard_table.nullkey_c1_t1 t1 CROSS JOIN (SELECT intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(b integer)) t2) +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO distributed_table_c1_t1 (a, b) +SELECT t1.a, t2.b +FROM reference_table t1 +LEFT JOIN ( + SELECT b, ROW_NUMBER() OVER (ORDER BY b DESC) AS rn + FROM nullkey_c1_t1 +) t2 ON t1.b = t2.b +WHERE t2.rn > 0; +DEBUG: cannot push down this subquery +DETAIL: Window functions without PARTITION BY on distribution column is currently unsupported +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO nullkey_c1_t1 (a, b) +SELECT t1.a, t2.b +FROM nullkey_c1_t1 t1 +JOIN ( + SELECT rn, b + FROM ( + SELECT b, ROW_NUMBER() OVER (ORDER BY b DESC) AS rn + FROM distributed_table_c2_t1 + ) q +) t2 ON t1.b = t2.b +WHERE t2.rn > 2; +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for subquery SELECT b, row_number() OVER (ORDER BY b DESC) AS rn FROM insert_select_single_shard_table.distributed_table_c2_t1 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT t1.a, t2.b FROM (insert_select_single_shard_table.nullkey_c1_t1 t1 JOIN (SELECT q.rn, q.b FROM (SELECT intermediate_result.b, intermediate_result.rn FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(b integer, rn bigint)) q) t2 ON ((t1.b OPERATOR(pg_catalog.=) t2.b))) WHERE (t2.rn OPERATOR(pg_catalog.>) 2) +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO distributed_table_c1_t1 (a, b) +SELECT t1.a, t2.b +FROM nullkey_c1_t1 t1 +JOIN ( + SELECT sum_val, b + FROM ( + SELECT b, SUM(a) OVER (PARTITION BY b) AS sum_val + FROM nullkey_c1_t1 + ) q +) t2 ON t1.b = t2.b +WHERE t2.sum_val > 2; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: The target table's partition column should correspond to a partition column in the subquery. +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- Temporaryly reduce the verbosity to avoid noise +-- in the output of the next query. +SET client_min_messages TO DEBUG1; +INSERT INTO nullkey_c1_t1 SELECT DISTINCT ON (a) a, b FROM nullkey_c1_t2; +SET client_min_messages TO DEBUG2; +-- Similarly, we could push down the following query as well. see +-- https://github.com/citusdata/citus/pull/6831. +INSERT INTO nullkey_c1_t1 SELECT b, SUM(a) OVER (ORDER BY b) AS sum_val FROM nullkey_c1_t1; +DEBUG: cannot push down this subquery +DETAIL: Window functions without PARTITION BY on distribution column is currently unsupported +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO nullkey_c2_t1 +SELECT t2.a, t2.b +FROM nullkey_c1_t1 AS t2 +JOIN reference_table AS t3 ON (t2.a = t3.a) +WHERE NOT EXISTS ( + SELECT 1 FROM nullkey_c1_t2 AS t1 WHERE t1.b = t3.b +); +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO distributed_table_c1_t1 +SELECT t1.a, t1.b +FROM nullkey_c1_t1 AS t1 +WHERE t1.a NOT IN ( + SELECT DISTINCT t2.a FROM distributed_table_c1_t2 AS t2 +); +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for subquery SELECT DISTINCT a FROM insert_select_single_shard_table.distributed_table_c1_t2 t2 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT a, b FROM insert_select_single_shard_table.nullkey_c1_t1 t1 WHERE (NOT (a OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)))) +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO distributed_table_c1_t1 +SELECT t1.a, t1.b +FROM reference_table AS t1 +JOIN ( + SELECT t2.a FROM ( + SELECT a FROM nullkey_c1_t1 + UNION + SELECT a FROM nullkey_c1_t2 + ) AS t2 +) AS t3 ON t1.a = t3.a; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: The target table's partition column should correspond to a partition column in the subquery. +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- Temporaryly reduce the verbosity to avoid noise +-- in the output of the next query. +SET client_min_messages TO DEBUG1; +INSERT INTO nullkey_c1_t1 +SELECT t1.a, t1.b +FROM reference_table AS t1 +WHERE t1.a IN ( + SELECT t2.a FROM ( + SELECT t3.a FROM ( + SELECT a FROM distributed_table_c1_t1 WHERE b > 4 + ) AS t3 + JOIN ( + SELECT a FROM distributed_table_c1_t2 WHERE b < 7 + ) AS t4 ON t3.a = t4.a + ) AS t2 +); +DEBUG: correlated subqueries are not supported when the FROM clause contains a reference table +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM (SELECT t3.a FROM ((SELECT distributed_table_c1_t1.a FROM insert_select_single_shard_table.distributed_table_c1_t1 WHERE (distributed_table_c1_t1.b OPERATOR(pg_catalog.>) 4)) t3 JOIN (SELECT distributed_table_c1_t2.a FROM insert_select_single_shard_table.distributed_table_c1_t2 WHERE (distributed_table_c1_t2.b OPERATOR(pg_catalog.<) 7)) t4 ON ((t3.a OPERATOR(pg_catalog.=) t4.a)))) t2 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT a, b FROM insert_select_single_shard_table.reference_table t1 WHERE (a OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer))) +DEBUG: Collecting INSERT ... SELECT results on coordinator +SET client_min_messages TO DEBUG2; +-- test upsert with plain INSERT query +CREATE TABLE upsert_test_1 +( + unique_col int UNIQUE, + other_col int, + third_col int +); +DEBUG: CREATE TABLE / UNIQUE will create implicit index "upsert_test_1_unique_col_key" for table "upsert_test_1" +SELECT create_distributed_table('upsert_test_1', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE upsert_test_2(key int primary key, value text); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "upsert_test_2_pkey" for table "upsert_test_2" +SELECT create_distributed_table('upsert_test_2', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO upsert_test_2 AS upsert_test_2_alias (key, value) VALUES (1, '5') ON CONFLICT(key) + DO UPDATE SET value = (upsert_test_2_alias.value::int * 2)::text; +DEBUG: Creating router plan +INSERT INTO upsert_test_2 (key, value) VALUES (1, '5') ON CONFLICT(key) + DO UPDATE SET value = (upsert_test_2.value::int * 3)::text; +DEBUG: Creating router plan +INSERT INTO upsert_test_1 (unique_col, other_col) VALUES (1, 1) ON CONFLICT (unique_col) + DO UPDATE SET other_col = (SELECT count(*) from upsert_test_1); +DEBUG: subqueries are not supported within INSERT queries +HINT: Try rewriting your queries with 'INSERT INTO ... SELECT' syntax. +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT count(*) AS count FROM insert_select_single_shard_table.upsert_test_1 +DEBUG: Plan XXX query after replacing subqueries and CTEs: INSERT INTO insert_select_single_shard_table.upsert_test_1 (unique_col, other_col) VALUES (1, 1) ON CONFLICT(unique_col) DO UPDATE SET other_col = (SELECT intermediate_result.count FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(count bigint)) +DEBUG: subqueries are not supported within INSERT queries +HINT: Try rewriting your queries with 'INSERT INTO ... SELECT' syntax. +ERROR: subqueries are not supported within INSERT queries +HINT: Try rewriting your queries with 'INSERT INTO ... SELECT' syntax. +INSERT INTO upsert_test_1 (unique_col, other_col) VALUES (1, 1) ON CONFLICT (unique_col) + DO UPDATE SET other_col = random()::int; +DEBUG: functions used in the DO UPDATE SET clause of INSERTs on distributed tables must be marked IMMUTABLE +ERROR: functions used in the DO UPDATE SET clause of INSERTs on distributed tables must be marked IMMUTABLE +INSERT INTO upsert_test_1 (unique_col, other_col) VALUES (1, 1) ON CONFLICT (unique_col) + DO UPDATE SET other_col = 5 WHERE upsert_test_1.other_col = random()::int; +DEBUG: functions used in the WHERE clause of the ON CONFLICT clause of INSERTs on distributed tables must be marked IMMUTABLE +ERROR: functions used in the WHERE clause of the ON CONFLICT clause of INSERTs on distributed tables must be marked IMMUTABLE +INSERT INTO upsert_test_1 VALUES (3, 5, 7); +DEBUG: Creating router plan +INSERT INTO upsert_test_1 (unique_col, other_col) VALUES (1, 1) ON CONFLICT (unique_col) WHERE unique_col = random()::int + DO UPDATE SET other_col = 5; +DEBUG: functions used in the WHERE clause of the ON CONFLICT clause of INSERTs on distributed tables must be marked IMMUTABLE +ERROR: functions used in the WHERE clause of the ON CONFLICT clause of INSERTs on distributed tables must be marked IMMUTABLE +CREATE TABLE upsert_test_3 (key_1 int, key_2 bigserial, value text DEFAULT 'default_value', PRIMARY KEY (key_1, key_2)); +DEBUG: CREATE TABLE will create implicit sequence "upsert_test_3_key_2_seq" for serial column "upsert_test_3.key_2" +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "upsert_test_3_pkey" for table "upsert_test_3" +SELECT create_distributed_table('upsert_test_3', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO upsert_test_3 VALUES (1, DEFAULT, '1') RETURNING *; +DEBUG: Creating router plan + key_1 | key_2 | value +--------------------------------------------------------------------- + 1 | 1 | 1 +(1 row) + +INSERT INTO upsert_test_3 VALUES (5, DEFAULT, DEFAULT) RETURNING *; +DEBUG: Creating router plan + key_1 | key_2 | value +--------------------------------------------------------------------- + 5 | 2 | default_value +(1 row) + +SET client_min_messages TO DEBUG1; +INSERT INTO upsert_test_3 SELECT 7, other_col, 'harcoded_text_value' FROM upsert_test_1 RETURNING *; + key_1 | key_2 | value +--------------------------------------------------------------------- + 7 | 5 | harcoded_text_value +(1 row) + +SET client_min_messages TO DEBUG2; +-- test upsert with INSERT .. SELECT queries +SET client_min_messages TO DEBUG1; +INSERT INTO upsert_test_1 (unique_col, other_col) SELECT unique_col, other_col FROM upsert_test_1 ON CONFLICT (unique_col) + DO UPDATE SET other_col = upsert_test_1.other_col + 1; +-- Fails due to https://github.com/citusdata/citus/issues/6826. +INSERT INTO upsert_test_1 (unique_col, other_col) SELECT unique_col, other_col FROM upsert_test_1 ON CONFLICT (unique_col) + DO UPDATE SET other_col = (SELECT count(*) from upsert_test_1); +ERROR: cannot execute a distributed query from a query on a shard +DETAIL: Executing a distributed query in a function call that may be pushed to a remote node can lead to incorrect results. +HINT: Avoid nesting of distributed queries or use alter user current_user set citus.allow_nested_distributed_execution to on to allow it with possible incorrectness. +CONTEXT: while executing command on localhost:xxxxx +SET client_min_messages TO DEBUG2; +INSERT INTO upsert_test_1 (unique_col, other_col) SELECT unique_col, other_col FROM upsert_test_1 ON CONFLICT (unique_col) + DO UPDATE SET other_col = random()::int; +ERROR: functions used in the DO UPDATE SET clause of INSERTs on distributed tables must be marked IMMUTABLE +INSERT INTO upsert_test_1 (unique_col, other_col) SELECT unique_col, other_col FROM upsert_test_1 ON CONFLICT (unique_col) + DO UPDATE SET other_col = 5 WHERE upsert_test_1.other_col = random()::int; +ERROR: functions used in the WHERE clause of the ON CONFLICT clause of INSERTs on distributed tables must be marked IMMUTABLE +SELECT reload_tables(); + reload_tables +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE nullkey_c1_t1 ADD PRIMARY KEY (a); +DEBUG: ALTER TABLE / ADD PRIMARY KEY will create implicit index "nullkey_c1_t1_pkey" for table "nullkey_c1_t1" +DEBUG: verifying table "nullkey_c1_t1" +ALTER TABLE distributed_table_c1_t1 ADD PRIMARY KEY (a,b); +DEBUG: ALTER TABLE / ADD PRIMARY KEY will create implicit index "distributed_table_c1_t1_pkey" for table "distributed_table_c1_t1" +DEBUG: verifying table "distributed_table_c1_t1" +INSERT INTO nullkey_c1_t1 AS t1 (a, b) SELECT t3.a, t3.b FROM nullkey_c1_t2 t2 JOIN reference_table t3 ON (t2.a = t3.a) ON CONFLICT (a) + DO UPDATE SET a = t1.a + 10; +DEBUG: distributed statement: INSERT INTO insert_select_single_shard_table.nullkey_c1_t1_1820000 AS t1 (a, b) SELECT t3.a, t3.b FROM (insert_select_single_shard_table.nullkey_c1_t2_1820001 t2 JOIN insert_select_single_shard_table.reference_table_1820003 t3 ON ((t2.a OPERATOR(pg_catalog.=) t3.a))) ON CONFLICT(a) DO UPDATE SET a = (t1.a OPERATOR(pg_catalog.+) 10) +SET client_min_messages TO DEBUG1; +INSERT INTO distributed_table_c1_t1 AS t1 (a, b) SELECT t3.a, t3.b FROM nullkey_c1_t2 t2 JOIN reference_table t3 ON (t2.a = t3.a) ON CONFLICT (a, b) + DO UPDATE SET b = t1.b + 10; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: The target table's partition column should correspond to a partition column in the subquery. +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO nullkey_c1_t1 AS t1 (a, b) SELECT t3.a, t3.b FROM distributed_table_c1_t1 t2 JOIN reference_table t3 ON (t2.a = t3.a) ON CONFLICT (a) + DO UPDATE SET a = t1.a + 10; +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- This also fails due to https://github.com/citusdata/citus/issues/6826. +INSERT INTO nullkey_c1_t1 AS t1 (a, b) SELECT t3.a, t3.b FROM distributed_table_c1_t1 t2 JOIN reference_table t3 ON (t2.a = t3.a) WHERE t2.a = 3 ON CONFLICT (a) + DO UPDATE SET a = (SELECT max(b)+1 FROM distributed_table_c1_t1 WHERE a = 3); +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: Collecting INSERT ... SELECT results on coordinator +ERROR: cannot execute a distributed query from a query on a shard +DETAIL: Executing a distributed query in a function call that may be pushed to a remote node can lead to incorrect results. +HINT: Avoid nesting of distributed queries or use alter user current_user set citus.allow_nested_distributed_execution to on to allow it with possible incorrectness. +CONTEXT: while executing command on localhost:xxxxx +SET client_min_messages TO DEBUG2; +SELECT avg(a), avg(b) FROM distributed_table_c1_t1; +DEBUG: Router planner cannot handle multi-shard select queries + avg | avg +--------------------------------------------------------------------- + 5.0000000000000000 | 9.2857142857142857 +(1 row) + +SELECT avg(a), avg(b) FROM nullkey_c1_t1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + avg | avg +--------------------------------------------------------------------- + 7.5000000000000000 | 4.1666666666666667 +(1 row) + +SELECT avg(a), avg(b) FROM nullkey_c1_t2; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + avg | avg +--------------------------------------------------------------------- + 4.5000000000000000 | 4.5000000000000000 +(1 row) + +SELECT * FROM upsert_test_1 ORDER BY unique_col; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + unique_col | other_col | third_col +--------------------------------------------------------------------- + 3 | 6 | 7 +(1 row) + +SELECT * FROM upsert_test_2 ORDER BY key; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + key | value +--------------------------------------------------------------------- + 1 | 15 +(1 row) + +SELECT * FROM upsert_test_3 ORDER BY key_1, key_2; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + key_1 | key_2 | value +--------------------------------------------------------------------- + 1 | 1 | 1 + 5 | 2 | default_value + 7 | 5 | harcoded_text_value +(3 rows) + +SET client_min_messages TO WARNING; +DROP SCHEMA insert_select_single_shard_table CASCADE; diff --git a/src/test/regress/expected/intermediate_result_pruning.out b/src/test/regress/expected/intermediate_result_pruning.out index f6cf8c1e1..5262ebc79 100644 --- a/src/test/regress/expected/intermediate_result_pruning.out +++ b/src/test/regress/expected/intermediate_result_pruning.out @@ -99,6 +99,7 @@ FROM DEBUG: generating subplan XXX_1 for CTE some_values_1: SELECT key, random() AS random FROM intermediate_result_pruning.table_1 WHERE (value OPERATOR(pg_catalog.=) ANY (ARRAY['3'::text, '4'::text])) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT intermediate_result.key, intermediate_result.random FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, random double precision)) some_values_1 JOIN intermediate_result_pruning.ref_table USING (key)) DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx count --------------------------------------------------------------------- @@ -386,6 +387,7 @@ DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx DEBUG: Subplan XXX_2 will be sent to localhost:xxxxx DEBUG: Subplan XXX_2 will be sent to localhost:xxxxx DEBUG: Subplan XXX_3 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_3 will be sent to localhost:xxxxx DEBUG: Subplan XXX_3 will be sent to localhost:xxxxx key | key | value --------------------------------------------------------------------- @@ -762,7 +764,8 @@ ROLLBACK; -- We use offset 1 to make sure the result needs to be pulled to the coordinator, offset 0 would be optimized away INSERT INTO table_1 SELECT * FROM table_2 OFFSET 1; -DEBUG: OFFSET clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Offset clause is currently unsupported when a subquery references a column from another query DEBUG: Collecting INSERT ... SELECT results on coordinator -- INSERT .. SELECT via coordinator which has intermediate result, -- and can be pruned to a single worker because the final query is on @@ -793,7 +796,7 @@ INSERT INTO table_1 SELECT * FROM cte_1 UNION SELECT * FROM cte_2); -DEBUG: Set operations are not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: generating subplan XXX_1 for CTE cte_1: SELECT table_1.key FROM intermediate_result_pruning.table_1 WHERE (table_1.key OPERATOR(pg_catalog.=) 1) INTERSECT SELECT table_1.key FROM intermediate_result_pruning.table_1 WHERE (table_1.key OPERATOR(pg_catalog.=) 2) DEBUG: generating subplan XXX_1 for subquery SELECT key FROM intermediate_result_pruning.table_1 WHERE (key OPERATOR(pg_catalog.=) 1) DEBUG: generating subplan XXX_2 for subquery SELECT key FROM intermediate_result_pruning.table_1 WHERE (key OPERATOR(pg_catalog.=) 2) @@ -830,7 +833,7 @@ INSERT INTO table_1 ) foo where table_2.key != 1 AND foo.key = table_2.value::int; -DEBUG: Set operations are not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: generating subplan XXX_1 for CTE cte_1: SELECT table_1.key FROM intermediate_result_pruning.table_1 WHERE (table_1.key OPERATOR(pg_catalog.=) 1) INTERSECT SELECT table_1.key FROM intermediate_result_pruning.table_1 WHERE (table_1.key OPERATOR(pg_catalog.=) 2) DEBUG: generating subplan XXX_1 for subquery SELECT key FROM intermediate_result_pruning.table_1 WHERE (key OPERATOR(pg_catalog.=) 1) DEBUG: generating subplan XXX_2 for subquery SELECT key FROM intermediate_result_pruning.table_1 WHERE (key OPERATOR(pg_catalog.=) 2) @@ -1054,7 +1057,8 @@ inserts AS MATERIALIZED ( ) SELECT count(*) FROM inserts; DEBUG: generating subplan XXX_1 for CTE stats: SELECT count(key) AS m FROM intermediate_result_pruning.table_3 DEBUG: generating subplan XXX_2 for CTE inserts: INSERT INTO intermediate_result_pruning.table_2 (key, value) SELECT table_1.key, count(*) AS count FROM intermediate_result_pruning.table_1 WHERE (table_1.key OPERATOR(pg_catalog.>) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) GROUP BY table_1.key HAVING (count(*) OPERATOR(pg_catalog.<) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) LIMIT 1 RETURNING table_2.key, table_2.value -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 1 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) inserts DEBUG: Subplan XXX_1 will be written to local file diff --git a/src/test/regress/expected/intermediate_result_pruning_0.out b/src/test/regress/expected/intermediate_result_pruning_0.out index 4ae6b8e16..ae1247545 100644 --- a/src/test/regress/expected/intermediate_result_pruning_0.out +++ b/src/test/regress/expected/intermediate_result_pruning_0.out @@ -99,6 +99,7 @@ FROM DEBUG: generating subplan XXX_1 for CTE some_values_1: SELECT key, random() AS random FROM intermediate_result_pruning.table_1 WHERE (value OPERATOR(pg_catalog.=) ANY (ARRAY['3'::text, '4'::text])) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT intermediate_result.key, intermediate_result.random FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, random double precision)) some_values_1 JOIN intermediate_result_pruning.ref_table USING (key)) DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx count --------------------------------------------------------------------- @@ -386,6 +387,7 @@ DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx DEBUG: Subplan XXX_2 will be sent to localhost:xxxxx DEBUG: Subplan XXX_2 will be sent to localhost:xxxxx DEBUG: Subplan XXX_3 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_3 will be sent to localhost:xxxxx DEBUG: Subplan XXX_3 will be sent to localhost:xxxxx key | key | value --------------------------------------------------------------------- @@ -762,7 +764,8 @@ ROLLBACK; -- We use offset 1 to make sure the result needs to be pulled to the coordinator, offset 0 would be optimized away INSERT INTO table_1 SELECT * FROM table_2 OFFSET 1; -DEBUG: OFFSET clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Offset clause is currently unsupported when a subquery references a column from another query DEBUG: Collecting INSERT ... SELECT results on coordinator -- INSERT .. SELECT via coordinator which has intermediate result, -- and can be pruned to a single worker because the final query is on @@ -793,7 +796,7 @@ INSERT INTO table_1 SELECT * FROM cte_1 UNION SELECT * FROM cte_2); -DEBUG: Set operations are not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: generating subplan XXX_1 for CTE cte_1: SELECT table_1.key FROM intermediate_result_pruning.table_1 WHERE (table_1.key OPERATOR(pg_catalog.=) 1) INTERSECT SELECT table_1.key FROM intermediate_result_pruning.table_1 WHERE (table_1.key OPERATOR(pg_catalog.=) 2) DEBUG: generating subplan XXX_1 for subquery SELECT key FROM intermediate_result_pruning.table_1 WHERE (key OPERATOR(pg_catalog.=) 1) DEBUG: generating subplan XXX_2 for subquery SELECT key FROM intermediate_result_pruning.table_1 WHERE (key OPERATOR(pg_catalog.=) 2) @@ -830,7 +833,7 @@ INSERT INTO table_1 ) foo where table_2.key != 1 AND foo.key = table_2.value::int; -DEBUG: Set operations are not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: generating subplan XXX_1 for CTE cte_1: SELECT table_1.key FROM intermediate_result_pruning.table_1 WHERE (table_1.key OPERATOR(pg_catalog.=) 1) INTERSECT SELECT table_1.key FROM intermediate_result_pruning.table_1 WHERE (table_1.key OPERATOR(pg_catalog.=) 2) DEBUG: generating subplan XXX_1 for subquery SELECT key FROM intermediate_result_pruning.table_1 WHERE (key OPERATOR(pg_catalog.=) 1) DEBUG: generating subplan XXX_2 for subquery SELECT key FROM intermediate_result_pruning.table_1 WHERE (key OPERATOR(pg_catalog.=) 2) @@ -1054,7 +1057,8 @@ inserts AS MATERIALIZED ( ) SELECT count(*) FROM inserts; DEBUG: generating subplan XXX_1 for CTE stats: SELECT count(key) AS m FROM intermediate_result_pruning.table_3 DEBUG: generating subplan XXX_2 for CTE inserts: INSERT INTO intermediate_result_pruning.table_2 (key, value) SELECT key, count(*) AS count FROM intermediate_result_pruning.table_1 WHERE (key OPERATOR(pg_catalog.>) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) GROUP BY key HAVING (count(*) OPERATOR(pg_catalog.<) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) LIMIT 1 RETURNING table_2.key, table_2.value -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 1 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) inserts DEBUG: Subplan XXX_1 will be written to local file diff --git a/src/test/regress/expected/intermediate_results.out b/src/test/regress/expected/intermediate_results.out index bf13ce21c..8b2e200f7 100644 --- a/src/test/regress/expected/intermediate_results.out +++ b/src/test/regress/expected/intermediate_results.out @@ -672,3 +672,5 @@ COMMIT; SET client_min_messages TO ERROR; DROP SCHEMA other_schema CASCADE; DROP SCHEMA intermediate_results CASCADE; +DROP OWNED BY some_other_user; +DROP USER some_other_user; diff --git a/src/test/regress/expected/isolation_citus_schema_distribute_undistribute.out b/src/test/regress/expected/isolation_citus_schema_distribute_undistribute.out new file mode 100644 index 000000000..88737f262 --- /dev/null +++ b/src/test/regress/expected/isolation_citus_schema_distribute_undistribute.out @@ -0,0 +1,1164 @@ +Parsed test spec with 2 sessions + +starting permutation: s1-begin s1-schema-distribute s2-drop-schema s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-drop-schema: + DROP SCHEMA tenant1 CASCADE; + +step s1-commit: + COMMIT; + +step s2-drop-schema: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid|partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +(0 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-schema-distribute s1-begin s1-schema-undistribute s2-drop-schema s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-drop-schema: + DROP SCHEMA tenant1 CASCADE; + +step s1-commit: + COMMIT; + +step s2-drop-schema: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid|partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +(0 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-begin s1-schema-distribute s2-rename-schema s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-rename-schema: + ALTER SCHEMA tenant1 RENAME TO tenant2; + +step s1-commit: + COMMIT; + +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant2.table1|n | |t |s |f +tenant2.table2|n | |t |s |f +tenant2.table3|n | |t |s |f +(3 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-schema-distribute s1-begin s1-schema-undistribute s2-rename-schema s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-rename-schema: + ALTER SCHEMA tenant1 RENAME TO tenant2; + +step s1-commit: + COMMIT; + +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant2.table3|n | | |s |t +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-begin s1-schema-distribute s2-add-table s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-add-table: + SET citus.shard_replication_factor TO 1; + CREATE TABLE tenant1.table4(id int PRIMARY KEY, name text, col bigint); + +step s1-commit: + COMMIT; + +step s2-add-table: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +tenant1.table3|n | |t |s |f +tenant1.table4|n | |t |s |f +(4 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-schema-distribute s1-begin s1-schema-undistribute s2-add-table s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-add-table: + SET citus.shard_replication_factor TO 1; + CREATE TABLE tenant1.table4(id int PRIMARY KEY, name text, col bigint); + +step s1-commit: + COMMIT; + +step s2-add-table: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table3|n | | |s |t +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-begin s1-schema-distribute s2-drop-table s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-drop-table: + DROP TABLE tenant1.table3; + +step s1-commit: + COMMIT; + +step s2-drop-table: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +(2 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-schema-distribute s1-begin s1-schema-undistribute s2-drop-table s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-drop-table: + DROP TABLE tenant1.table3; + +step s1-commit: + COMMIT; + +step s2-drop-table: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid|partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +(0 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-begin s1-schema-distribute s2-alter-col-type s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-alter-col-type: + ALTER TABLE tenant1.table3 ALTER COLUMN col1 TYPE text; + +step s1-commit: + COMMIT; + +step s2-alter-col-type: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +tenant1.table3|n | |t |s |f +(3 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-schema-distribute s1-begin s1-schema-undistribute s2-alter-col-type s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-alter-col-type: + ALTER TABLE tenant1.table3 ALTER COLUMN col1 TYPE text; + +step s1-commit: + COMMIT; + +step s2-alter-col-type: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table3|n | | |s |t +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-begin s1-schema-distribute s2-add-foreign-key s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-add-foreign-key: + ALTER TABLE tenant1.table3 ADD CONSTRAINT table3_fk1 FOREIGN KEY (id) REFERENCES tenant1.table2 (id); + +step s1-commit: + COMMIT; + +step s2-add-foreign-key: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +tenant1.table3|n | |t |s |f +(3 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-schema-distribute s1-begin s1-schema-undistribute s2-add-foreign-key s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-add-foreign-key: + ALTER TABLE tenant1.table3 ADD CONSTRAINT table3_fk1 FOREIGN KEY (id) REFERENCES tenant1.table2 (id); + +step s1-commit: + COMMIT; + +step s2-add-foreign-key: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table3|n | | |s |t +tenant1.table2|n | | |s |t +(2 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-begin s1-schema-distribute s2-drop-foreign-key s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-drop-foreign-key: + ALTER TABLE tenant1.table3 DROP CONSTRAINT table3_col_fkey; + +step s1-commit: + COMMIT; + +step s2-drop-foreign-key: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +tenant1.table3|n | |t |s |f +(3 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-schema-distribute s1-begin s1-schema-undistribute s2-drop-foreign-key s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-drop-foreign-key: + ALTER TABLE tenant1.table3 DROP CONSTRAINT table3_col_fkey; + +step s1-commit: + COMMIT; + +step s2-drop-foreign-key: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid|partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +(0 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-begin s1-schema-distribute s2-create-unique-index s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-create-unique-index: + CREATE UNIQUE INDEX idx_2 ON tenant1.table3 (col); + +step s1-commit: + COMMIT; + +step s2-create-unique-index: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +tenant1.table3|n | |t |s |f +(3 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-schema-distribute s1-begin s1-schema-undistribute s2-create-unique-index s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-create-unique-index: + CREATE UNIQUE INDEX idx_2 ON tenant1.table3 (col); + +step s1-commit: + COMMIT; + +step s2-create-unique-index: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table3|n | | |s |t +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-begin s1-schema-distribute s2-create-unique-index-concurrently s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-create-unique-index-concurrently: + CREATE UNIQUE INDEX CONCURRENTLY idx_3 ON tenant1.table3 (col); + +step s1-commit: + COMMIT; + +step s2-create-unique-index-concurrently: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +tenant1.table3|n | |t |s |f +(3 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-schema-distribute s1-begin s1-schema-undistribute s2-create-unique-index-concurrently s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-create-unique-index-concurrently: + CREATE UNIQUE INDEX CONCURRENTLY idx_3 ON tenant1.table3 (col); + +step s1-commit: + COMMIT; + +step s2-create-unique-index-concurrently: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table3|n | | |s |t +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s2-create-unique-index s1-begin s1-schema-distribute s2-reindex-unique-concurrently s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s2-create-unique-index: + CREATE UNIQUE INDEX idx_2 ON tenant1.table3 (col); + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-reindex-unique-concurrently: + REINDEX INDEX CONCURRENTLY tenant1.idx_2; + +step s1-commit: + COMMIT; + +step s2-reindex-unique-concurrently: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +tenant1.table3|n | |t |s |f +(3 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s2-create-unique-index s1-schema-distribute s1-begin s1-schema-undistribute s2-reindex-unique-concurrently s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s2-create-unique-index: + CREATE UNIQUE INDEX idx_2 ON tenant1.table3 (col); + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-reindex-unique-concurrently: + REINDEX INDEX CONCURRENTLY tenant1.idx_2; + +step s1-commit: + COMMIT; + +step s2-reindex-unique-concurrently: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table3|n | | |s |t +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-begin s1-schema-distribute s2-insert s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-insert: + INSERT INTO public.ref SELECT i FROM generate_series(11, 20) i; + INSERT INTO tenant1.table3 SELECT i, 'asd', i*1000 FROM generate_series(11, 20) i; + +step s1-commit: + COMMIT; + +step s2-insert: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +tenant1.table3|n | |t |s |f +(3 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-schema-distribute s1-begin s1-schema-undistribute s2-insert s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-insert: + INSERT INTO public.ref SELECT i FROM generate_series(11, 20) i; + INSERT INTO tenant1.table3 SELECT i, 'asd', i*1000 FROM generate_series(11, 20) i; + +step s1-commit: + COMMIT; + +step s2-insert: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table3|n | | |s |t +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s2-insert s1-begin s1-schema-distribute s2-update s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s2-insert: + INSERT INTO public.ref SELECT i FROM generate_series(11, 20) i; + INSERT INTO tenant1.table3 SELECT i, 'asd', i*1000 FROM generate_series(11, 20) i; + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-update: + UPDATE tenant1.table3 SET col = 11 WHERE col = 11; + +step s1-commit: + COMMIT; + +step s2-update: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +tenant1.table3|n | |t |s |f +(3 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s2-insert s1-schema-distribute s1-begin s1-schema-undistribute s2-update s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s2-insert: + INSERT INTO public.ref SELECT i FROM generate_series(11, 20) i; + INSERT INTO tenant1.table3 SELECT i, 'asd', i*1000 FROM generate_series(11, 20) i; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-update: + UPDATE tenant1.table3 SET col = 11 WHERE col = 11; + +step s1-commit: + COMMIT; + +step s2-update: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table3|n | | |s |t +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s2-insert s1-begin s1-schema-distribute s2-delete s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s2-insert: + INSERT INTO public.ref SELECT i FROM generate_series(11, 20) i; + INSERT INTO tenant1.table3 SELECT i, 'asd', i*1000 FROM generate_series(11, 20) i; + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-delete: + DELETE FROM tenant1.table3; + +step s1-commit: + COMMIT; + +step s2-delete: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +tenant1.table3|n | |t |s |f +(3 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s2-insert s1-schema-distribute s1-begin s1-schema-undistribute s2-delete s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s2-insert: + INSERT INTO public.ref SELECT i FROM generate_series(11, 20) i; + INSERT INTO tenant1.table3 SELECT i, 'asd', i*1000 FROM generate_series(11, 20) i; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + SET citus.shard_replication_factor TO 1; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-delete: + DELETE FROM tenant1.table3; + +step s1-commit: + COMMIT; + +step s2-delete: <... completed> +step s1-verify-distributed-schema: + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table3|n | | |s |t +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + diff --git a/src/test/regress/expected/isolation_ensure_dependency_activate_node.out b/src/test/regress/expected/isolation_ensure_dependency_activate_node.out index 980cf3a82..77ff2db9c 100644 --- a/src/test/regress/expected/isolation_ensure_dependency_activate_node.out +++ b/src/test/regress/expected/isolation_ensure_dependency_activate_node.out @@ -29,9 +29,10 @@ step s1-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{public},{}) -(3 rows) +(4 rows) count --------------------------------------------------------------------- @@ -116,10 +117,11 @@ step s2-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{public},{}) (table,"{public,t1}",{}) -(4 rows) +(5 rows) count --------------------------------------------------------------------- @@ -200,9 +202,10 @@ step s1-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{public},{}) -(3 rows) +(4 rows) count --------------------------------------------------------------------- @@ -293,10 +296,11 @@ step s2-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{public},{}) (table,"{public,t1}",{}) -(4 rows) +(5 rows) count --------------------------------------------------------------------- @@ -377,9 +381,10 @@ step s1-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{public},{}) -(3 rows) +(4 rows) count --------------------------------------------------------------------- @@ -470,10 +475,11 @@ step s2-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{public},{}) (table,"{public,t1}",{}) -(4 rows) +(5 rows) count --------------------------------------------------------------------- @@ -554,9 +560,10 @@ step s1-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{public},{}) -(3 rows) +(4 rows) count --------------------------------------------------------------------- @@ -642,11 +649,12 @@ step s2-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{myschema},{}) (schema,{public},{}) (table,"{myschema,t1}",{}) -(5 rows) +(6 rows) count --------------------------------------------------------------------- @@ -727,9 +735,10 @@ step s1-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{public},{}) -(3 rows) +(4 rows) count --------------------------------------------------------------------- @@ -821,11 +830,12 @@ step s2-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{myschema},{}) (schema,{public},{}) (table,"{myschema,t1}",{}) -(5 rows) +(6 rows) count --------------------------------------------------------------------- @@ -906,9 +916,10 @@ step s1-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{public},{}) -(3 rows) +(4 rows) count --------------------------------------------------------------------- @@ -1000,11 +1011,12 @@ step s2-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{myschema},{}) (schema,{public},{}) (table,"{myschema,t1}",{}) -(5 rows) +(6 rows) count --------------------------------------------------------------------- @@ -1085,9 +1097,10 @@ step s1-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{public},{}) -(3 rows) +(4 rows) count --------------------------------------------------------------------- @@ -1179,11 +1192,12 @@ step s2-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{myschema},{}) (schema,{public},{}) (table,"{myschema,t1}",{}) -(5 rows) +(6 rows) count --------------------------------------------------------------------- @@ -1264,9 +1278,10 @@ step s1-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{public},{}) -(3 rows) +(4 rows) count --------------------------------------------------------------------- @@ -1381,6 +1396,7 @@ step s2-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{col_schema},{}) (schema,{myschema},{}) @@ -1388,7 +1404,7 @@ pg_identify_object_as_address (table,"{col_schema,col_tbl}",{}) (table,"{myschema,t1}",{}) (table,"{myschema,t2}",{}) -(8 rows) +(9 rows) count --------------------------------------------------------------------- @@ -1469,9 +1485,10 @@ step s1-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{public},{}) -(3 rows) +(4 rows) count --------------------------------------------------------------------- @@ -1585,13 +1602,14 @@ step s2-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{myschema},{}) (schema,{myschema2},{}) (schema,{public},{}) (table,"{myschema,t1}",{}) (table,"{myschema2,t2}",{}) -(7 rows) +(8 rows) count --------------------------------------------------------------------- @@ -1672,9 +1690,10 @@ step s1-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{public},{}) -(3 rows) +(4 rows) count --------------------------------------------------------------------- @@ -1751,10 +1770,11 @@ step s2-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{public},{}) (type,{public.tt1},{}) -(4 rows) +(5 rows) count --------------------------------------------------------------------- @@ -1835,9 +1855,10 @@ step s1-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{public},{}) -(3 rows) +(4 rows) count --------------------------------------------------------------------- @@ -1913,10 +1934,11 @@ step s2-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{public},{}) (type,{public.tt1},{}) -(4 rows) +(5 rows) count --------------------------------------------------------------------- @@ -1997,9 +2019,10 @@ step s1-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{public},{}) -(3 rows) +(4 rows) count --------------------------------------------------------------------- @@ -2094,12 +2117,13 @@ step s2-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{myschema},{}) (schema,{public},{}) (table,"{myschema,t1}",{}) (type,{myschema.tt1},{}) -(6 rows) +(7 rows) count --------------------------------------------------------------------- @@ -2180,9 +2204,10 @@ step s1-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{public},{}) -(3 rows) +(4 rows) count --------------------------------------------------------------------- @@ -2290,11 +2315,12 @@ pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) (function,"{public,add}","{integer,integer}") +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{col_schema},{}) (schema,{public},{}) (table,"{col_schema,col_tbl}",{}) -(6 rows) +(7 rows) count --------------------------------------------------------------------- @@ -2375,9 +2401,10 @@ step s1-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{public},{}) -(3 rows) +(4 rows) count --------------------------------------------------------------------- @@ -2492,11 +2519,12 @@ pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) (function,"{public,add}","{integer,integer}") +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{col_schema},{}) (schema,{public},{}) (table,"{col_schema,col_tbl}",{}) -(6 rows) +(7 rows) count --------------------------------------------------------------------- @@ -2577,9 +2605,10 @@ step s1-print-distributed-objects: pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{public},{}) -(3 rows) +(4 rows) count --------------------------------------------------------------------- @@ -2695,12 +2724,13 @@ pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) (function,"{myschema,add}","{integer,integer}") +(function,"{public,wait_until_metadata_sync}",{integer}) (role,{postgres},{}) (schema,{col_schema},{}) (schema,{myschema},{}) (schema,{public},{}) (table,"{col_schema,col_tbl}",{}) -(7 rows) +(8 rows) count --------------------------------------------------------------------- diff --git a/src/test/regress/expected/isolation_extension_commands.out b/src/test/regress/expected/isolation_extension_commands.out index 028ec21f0..f1d6b64bb 100644 --- a/src/test/regress/expected/isolation_extension_commands.out +++ b/src/test/regress/expected/isolation_extension_commands.out @@ -20,16 +20,23 @@ step s1-commit: step s2-create-extension-version-11: <... completed> step s1-print: - select count(*) from pg_catalog.pg_dist_object ; + select pg_identify_object_as_address(classid, objid, objsubid)::text as obj_repr from pg_dist_object order by obj_repr; select extname, extversion, nspname from pg_extension, pg_namespace where pg_namespace.oid=pg_extension.extnamespace and extname='seg'; SELECT run_command_on_workers($$select extname from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select extversion from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select nspname from pg_extension, pg_namespace where extname='seg' and pg_extension.extnamespace=pg_namespace.oid$$); -count +obj_repr --------------------------------------------------------------------- - 7 -(1 row) +(database,{regression},{}) +(extension,{seg},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) +(role,{postgres},{}) +(schema,{public},{}) +(schema,{schema1},{}) +(schema,{schema2},{}) +(schema,{schema3},{}) +(8 rows) extname|extversion|nspname --------------------------------------------------------------------- @@ -81,16 +88,23 @@ step s1-commit: step s2-alter-extension-update-to-version-12: <... completed> step s1-print: - select count(*) from pg_catalog.pg_dist_object ; + select pg_identify_object_as_address(classid, objid, objsubid)::text as obj_repr from pg_dist_object order by obj_repr; select extname, extversion, nspname from pg_extension, pg_namespace where pg_namespace.oid=pg_extension.extnamespace and extname='seg'; SELECT run_command_on_workers($$select extname from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select extversion from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select nspname from pg_extension, pg_namespace where extname='seg' and pg_extension.extnamespace=pg_namespace.oid$$); -count +obj_repr --------------------------------------------------------------------- - 7 -(1 row) +(database,{regression},{}) +(extension,{seg},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) +(role,{postgres},{}) +(schema,{public},{}) +(schema,{schema1},{}) +(schema,{schema2},{}) +(schema,{schema3},{}) +(8 rows) extname|extversion|nspname --------------------------------------------------------------------- @@ -150,16 +164,22 @@ step s1-commit: step s2-drop-extension: <... completed> step s1-print: - select count(*) from pg_catalog.pg_dist_object ; + select pg_identify_object_as_address(classid, objid, objsubid)::text as obj_repr from pg_dist_object order by obj_repr; select extname, extversion, nspname from pg_extension, pg_namespace where pg_namespace.oid=pg_extension.extnamespace and extname='seg'; SELECT run_command_on_workers($$select extname from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select extversion from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select nspname from pg_extension, pg_namespace where extname='seg' and pg_extension.extnamespace=pg_namespace.oid$$); -count +obj_repr --------------------------------------------------------------------- - 6 -(1 row) +(database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) +(role,{postgres},{}) +(schema,{public},{}) +(schema,{schema1},{}) +(schema,{schema2},{}) +(schema,{schema3},{}) +(7 rows) extname|extversion|nspname --------------------------------------------------------------------- @@ -206,16 +226,23 @@ step s1-commit: step s2-create-extension-with-schema1: <... completed> step s1-print: - select count(*) from pg_catalog.pg_dist_object ; + select pg_identify_object_as_address(classid, objid, objsubid)::text as obj_repr from pg_dist_object order by obj_repr; select extname, extversion, nspname from pg_extension, pg_namespace where pg_namespace.oid=pg_extension.extnamespace and extname='seg'; SELECT run_command_on_workers($$select extname from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select extversion from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select nspname from pg_extension, pg_namespace where extname='seg' and pg_extension.extnamespace=pg_namespace.oid$$); -count +obj_repr --------------------------------------------------------------------- - 7 -(1 row) +(database,{regression},{}) +(extension,{seg},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) +(role,{postgres},{}) +(schema,{public},{}) +(schema,{schema1},{}) +(schema,{schema2},{}) +(schema,{schema3},{}) +(8 rows) extname|extversion|nspname --------------------------------------------------------------------- @@ -267,16 +294,22 @@ step s1-commit: step s2-drop-extension: <... completed> step s1-print: - select count(*) from pg_catalog.pg_dist_object ; + select pg_identify_object_as_address(classid, objid, objsubid)::text as obj_repr from pg_dist_object order by obj_repr; select extname, extversion, nspname from pg_extension, pg_namespace where pg_namespace.oid=pg_extension.extnamespace and extname='seg'; SELECT run_command_on_workers($$select extname from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select extversion from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select nspname from pg_extension, pg_namespace where extname='seg' and pg_extension.extnamespace=pg_namespace.oid$$); -count +obj_repr --------------------------------------------------------------------- - 6 -(1 row) +(database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) +(role,{postgres},{}) +(schema,{public},{}) +(schema,{schema1},{}) +(schema,{schema2},{}) +(schema,{schema3},{}) +(7 rows) extname|extversion|nspname --------------------------------------------------------------------- @@ -338,16 +371,23 @@ step s1-commit: step s2-alter-extension-set-schema3: <... completed> step s1-print: - select count(*) from pg_catalog.pg_dist_object ; + select pg_identify_object_as_address(classid, objid, objsubid)::text as obj_repr from pg_dist_object order by obj_repr; select extname, extversion, nspname from pg_extension, pg_namespace where pg_namespace.oid=pg_extension.extnamespace and extname='seg'; SELECT run_command_on_workers($$select extname from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select extversion from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select nspname from pg_extension, pg_namespace where extname='seg' and pg_extension.extnamespace=pg_namespace.oid$$); -count +obj_repr --------------------------------------------------------------------- - 7 -(1 row) +(database,{regression},{}) +(extension,{seg},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) +(role,{postgres},{}) +(schema,{public},{}) +(schema,{schema1},{}) +(schema,{schema2},{}) +(schema,{schema3},{}) +(8 rows) extname|extversion|nspname --------------------------------------------------------------------- @@ -406,16 +446,23 @@ step s1-commit: step s2-create-extension-with-schema1: <... completed> step s1-print: - select count(*) from pg_catalog.pg_dist_object ; + select pg_identify_object_as_address(classid, objid, objsubid)::text as obj_repr from pg_dist_object order by obj_repr; select extname, extversion, nspname from pg_extension, pg_namespace where pg_namespace.oid=pg_extension.extnamespace and extname='seg'; SELECT run_command_on_workers($$select extname from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select extversion from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select nspname from pg_extension, pg_namespace where extname='seg' and pg_extension.extnamespace=pg_namespace.oid$$); -count +obj_repr --------------------------------------------------------------------- - 7 -(1 row) +(database,{regression},{}) +(extension,{seg},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) +(role,{postgres},{}) +(schema,{public},{}) +(schema,{schema1},{}) +(schema,{schema2},{}) +(schema,{schema3},{}) +(8 rows) extname|extversion|nspname --------------------------------------------------------------------- @@ -482,16 +529,23 @@ step s1-add-node-1: <... completed> (1 row) step s1-print: - select count(*) from pg_catalog.pg_dist_object ; + select pg_identify_object_as_address(classid, objid, objsubid)::text as obj_repr from pg_dist_object order by obj_repr; select extname, extversion, nspname from pg_extension, pg_namespace where pg_namespace.oid=pg_extension.extnamespace and extname='seg'; SELECT run_command_on_workers($$select extname from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select extversion from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select nspname from pg_extension, pg_namespace where extname='seg' and pg_extension.extnamespace=pg_namespace.oid$$); -count +obj_repr --------------------------------------------------------------------- - 7 -(1 row) +(database,{regression},{}) +(extension,{seg},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) +(role,{postgres},{}) +(schema,{public},{}) +(schema,{schema1},{}) +(schema,{schema2},{}) +(schema,{schema3},{}) +(8 rows) extname|extversion|nspname --------------------------------------------------------------------- @@ -565,16 +619,23 @@ step s1-add-node-1: <... completed> (1 row) step s1-print: - select count(*) from pg_catalog.pg_dist_object ; + select pg_identify_object_as_address(classid, objid, objsubid)::text as obj_repr from pg_dist_object order by obj_repr; select extname, extversion, nspname from pg_extension, pg_namespace where pg_namespace.oid=pg_extension.extnamespace and extname='seg'; SELECT run_command_on_workers($$select extname from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select extversion from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select nspname from pg_extension, pg_namespace where extname='seg' and pg_extension.extnamespace=pg_namespace.oid$$); -count +obj_repr --------------------------------------------------------------------- - 7 -(1 row) +(database,{regression},{}) +(extension,{seg},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) +(role,{postgres},{}) +(schema,{public},{}) +(schema,{schema1},{}) +(schema,{schema2},{}) +(schema,{schema3},{}) +(8 rows) extname|extversion|nspname --------------------------------------------------------------------- @@ -634,16 +695,22 @@ step s1-remove-node-1: <... completed> (1 row) step s1-print: - select count(*) from pg_catalog.pg_dist_object ; + select pg_identify_object_as_address(classid, objid, objsubid)::text as obj_repr from pg_dist_object order by obj_repr; select extname, extversion, nspname from pg_extension, pg_namespace where pg_namespace.oid=pg_extension.extnamespace and extname='seg'; SELECT run_command_on_workers($$select extname from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select extversion from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select nspname from pg_extension, pg_namespace where extname='seg' and pg_extension.extnamespace=pg_namespace.oid$$); -count +obj_repr --------------------------------------------------------------------- - 6 -(1 row) +(database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) +(role,{postgres},{}) +(schema,{public},{}) +(schema,{schema1},{}) +(schema,{schema2},{}) +(schema,{schema3},{}) +(7 rows) extname|extversion|nspname --------------------------------------------------------------------- @@ -690,16 +757,23 @@ step s1-add-node-1: <... completed> (1 row) step s1-print: - select count(*) from pg_catalog.pg_dist_object ; + select pg_identify_object_as_address(classid, objid, objsubid)::text as obj_repr from pg_dist_object order by obj_repr; select extname, extversion, nspname from pg_extension, pg_namespace where pg_namespace.oid=pg_extension.extnamespace and extname='seg'; SELECT run_command_on_workers($$select extname from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select extversion from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select nspname from pg_extension, pg_namespace where extname='seg' and pg_extension.extnamespace=pg_namespace.oid$$); -count +obj_repr --------------------------------------------------------------------- - 7 -(1 row) +(database,{regression},{}) +(extension,{seg},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) +(role,{postgres},{}) +(schema,{public},{}) +(schema,{schema1},{}) +(schema,{schema2},{}) +(schema,{schema3},{}) +(8 rows) extname|extversion|nspname --------------------------------------------------------------------- @@ -765,16 +839,23 @@ step s1-remove-node-1: <... completed> (1 row) step s1-print: - select count(*) from pg_catalog.pg_dist_object ; + select pg_identify_object_as_address(classid, objid, objsubid)::text as obj_repr from pg_dist_object order by obj_repr; select extname, extversion, nspname from pg_extension, pg_namespace where pg_namespace.oid=pg_extension.extnamespace and extname='seg'; SELECT run_command_on_workers($$select extname from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select extversion from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select nspname from pg_extension, pg_namespace where extname='seg' and pg_extension.extnamespace=pg_namespace.oid$$); -count +obj_repr --------------------------------------------------------------------- - 7 -(1 row) +(database,{regression},{}) +(extension,{seg},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) +(role,{postgres},{}) +(schema,{public},{}) +(schema,{schema1},{}) +(schema,{schema2},{}) +(schema,{schema3},{}) +(8 rows) extname|extversion|nspname --------------------------------------------------------------------- @@ -833,16 +914,23 @@ step s1-remove-node-1: <... completed> (1 row) step s1-print: - select count(*) from pg_catalog.pg_dist_object ; + select pg_identify_object_as_address(classid, objid, objsubid)::text as obj_repr from pg_dist_object order by obj_repr; select extname, extversion, nspname from pg_extension, pg_namespace where pg_namespace.oid=pg_extension.extnamespace and extname='seg'; SELECT run_command_on_workers($$select extname from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select extversion from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select nspname from pg_extension, pg_namespace where extname='seg' and pg_extension.extnamespace=pg_namespace.oid$$); -count +obj_repr --------------------------------------------------------------------- - 7 -(1 row) +(database,{regression},{}) +(extension,{seg},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) +(role,{postgres},{}) +(schema,{public},{}) +(schema,{schema1},{}) +(schema,{schema2},{}) +(schema,{schema3},{}) +(8 rows) extname|extversion|nspname --------------------------------------------------------------------- @@ -870,7 +958,7 @@ master_remove_node (1 row) -starting permutation: s2-drop-extension s2-add-node-1 s2-create-extension-version-11 s2-remove-node-1 s2-begin s2-drop-extension s1-add-node-1 s2-commit s1-print +starting permutation: s2-drop-extension s2-add-node-1 s2-create-extension-version-11 s2-remove-node-1 s2-begin s2-drop-extension s1-add-node-1 s2-commit s1-print s1-cleanup-node-1 step s2-drop-extension: drop extension seg; @@ -912,16 +1000,22 @@ step s1-add-node-1: <... completed> (1 row) step s1-print: - select count(*) from pg_catalog.pg_dist_object ; + select pg_identify_object_as_address(classid, objid, objsubid)::text as obj_repr from pg_dist_object order by obj_repr; select extname, extversion, nspname from pg_extension, pg_namespace where pg_namespace.oid=pg_extension.extnamespace and extname='seg'; SELECT run_command_on_workers($$select extname from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select extversion from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select nspname from pg_extension, pg_namespace where extname='seg' and pg_extension.extnamespace=pg_namespace.oid$$); -count +obj_repr --------------------------------------------------------------------- - 6 -(1 row) +(database,{regression},{}) +(function,"{public,wait_until_metadata_sync}",{integer}) +(role,{postgres},{}) +(schema,{public},{}) +(schema,{schema1},{}) +(schema,{schema2},{}) +(schema,{schema3},{}) +(7 rows) extname|extversion|nspname --------------------------------------------------------------------- @@ -945,6 +1039,15 @@ run_command_on_workers (localhost,57638,t,"") (2 rows) +step s1-cleanup-node-1: + SELECT run_command_on_workers($$drop extension if exists seg$$); + +run_command_on_workers +--------------------------------------------------------------------- +(localhost,57637,t,"DROP EXTENSION") +(localhost,57638,t,"DROP EXTENSION") +(2 rows) + master_remove_node --------------------------------------------------------------------- diff --git a/src/test/regress/expected/isolation_master_update_node_1.out b/src/test/regress/expected/isolation_master_update_node_1.out deleted file mode 100644 index 194299c4d..000000000 --- a/src/test/regress/expected/isolation_master_update_node_1.out +++ /dev/null @@ -1,66 +0,0 @@ -Parsed test spec with 2 sessions - -starting permutation: s1-begin s1-insert s2-begin s2-update-node-1 s1-abort s2-abort -create_distributed_table ---------------------------------------------------------------------- - -(1 row) - -step s1-begin: BEGIN; -step s1-insert: INSERT INTO t1 SELECT generate_series(1, 100); -step s2-begin: BEGIN; -step s2-update-node-1: - -- update a specific node by address - SELECT master_update_node(nodeid, 'localhost', nodeport + 10) - FROM pg_dist_node - WHERE nodename = 'localhost' - AND nodeport = 57637; - -step s1-abort: ABORT; -step s2-update-node-1: <... completed> -master_update_node ---------------------------------------------------------------------- - -(1 row) - -step s2-abort: ABORT; -master_remove_node ---------------------------------------------------------------------- - - -(2 rows) - - -starting permutation: s1-begin s1-insert s2-begin s2-update-node-1-force s2-abort s1-abort -create_distributed_table ---------------------------------------------------------------------- - -(1 row) - -step s1-begin: BEGIN; -step s1-insert: INSERT INTO t1 SELECT generate_series(1, 100); -step s2-begin: BEGIN; -step s2-update-node-1-force: - -- update a specific node by address (force) - SELECT master_update_node(nodeid, 'localhost', nodeport + 10, force => true, lock_cooldown => 100) - FROM pg_dist_node - WHERE nodename = 'localhost' - AND nodeport = 57637; - -step s2-update-node-1-force: <... completed> -master_update_node ---------------------------------------------------------------------- - -(1 row) - -step s2-abort: ABORT; -step s1-abort: ABORT; -FATAL: terminating connection due to administrator command -server closed the connection unexpectedly - -master_remove_node ---------------------------------------------------------------------- - - -(2 rows) - diff --git a/src/test/regress/expected/isolation_schema_based_sharding.out b/src/test/regress/expected/isolation_schema_based_sharding.out new file mode 100644 index 000000000..f089babf5 --- /dev/null +++ b/src/test/regress/expected/isolation_schema_based_sharding.out @@ -0,0 +1,37 @@ +Parsed test spec with 2 sessions + +starting permutation: s1-begin s2-begin s1-tenant-1-create-table-1 s2-tenant-1-create-table-2 s1-commit s2-tenant-1-verify-colocation s2-commit +step s1-begin: BEGIN; SET citus.shard_replication_factor TO 1; +step s2-begin: BEGIN; SET citus.shard_replication_factor TO 1; +step s1-tenant-1-create-table-1: CREATE TABLE tenant_1.tbl_1 (a int); +step s2-tenant-1-create-table-2: CREATE TABLE tenant_1.tbl_2 (a int); +step s1-commit: COMMIT; +step s2-tenant-1-verify-colocation: SELECT COUNT(DISTINCT(colocationid))=1 FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant_1.%'; +?column? +--------------------------------------------------------------------- +t +(1 row) + +step s2-commit: COMMIT; + +starting permutation: s1-begin s2-begin s1-tenant-4-create-table-1 s2-tenant-4-create-table-2 s1-commit s2-tenant-4-verify-colocation s2-commit +step s1-begin: BEGIN; SET citus.shard_replication_factor TO 1; +step s2-begin: BEGIN; SET citus.shard_replication_factor TO 1; +step s1-tenant-4-create-table-1: CREATE TABLE tenant_4.tbl_1 (a int); +step s2-tenant-4-create-table-2: CREATE TABLE tenant_4.tbl_2 (a int); +step s1-commit: COMMIT; +step s2-tenant-4-verify-colocation: SELECT COUNT(DISTINCT(colocationid))=1 FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant_4.%'; +?column? +--------------------------------------------------------------------- +t +(1 row) + +step s2-commit: COMMIT; + +starting permutation: s1-begin s2-begin s1-tenant-2-create-table-1 s2-tenant-3-create-table-1 s1-commit s2-commit +step s1-begin: BEGIN; SET citus.shard_replication_factor TO 1; +step s2-begin: BEGIN; SET citus.shard_replication_factor TO 1; +step s1-tenant-2-create-table-1: CREATE TABLE tenant_2.tbl_1 (a int); +step s2-tenant-3-create-table-1: CREATE TABLE tenant_3.tbl_1 (a int); +step s1-commit: COMMIT; +step s2-commit: COMMIT; diff --git a/src/test/regress/expected/isolation_select_vs_all.out b/src/test/regress/expected/isolation_select_vs_all.out index 0485dba78..21be42915 100644 --- a/src/test/regress/expected/isolation_select_vs_all.out +++ b/src/test/regress/expected/isolation_select_vs_all.out @@ -341,29 +341,6 @@ count (1 row) -starting permutation: s1-initialize s1-begin s1-router-select s2-insert-select s1-commit s1-select-count -master_create_empty_shard ---------------------------------------------------------------------- - 6780300 -(1 row) - -step s1-initialize: COPY select_append FROM PROGRAM 'echo 0, a, 0 && echo 1, b, 1 && echo 2, c, 2 && echo 3, d, 3 && echo 4, e, 4' WITH (format 'csv', append_to_shard xxxxx); -step s1-begin: BEGIN; -step s1-router-select: SELECT * FROM select_append WHERE id = 1; -id|data|int_data ---------------------------------------------------------------------- - 1| b | 1 -(1 row) - -step s2-insert-select: INSERT INTO select_append SELECT * FROM select_append; -step s1-commit: COMMIT; -step s1-select-count: SELECT COUNT(*) FROM select_append; -count ---------------------------------------------------------------------- - 10 -(1 row) - - starting permutation: s1-initialize s1-begin s1-router-select s2-update s1-commit s1-select-count master_create_empty_shard --------------------------------------------------------------------- @@ -770,29 +747,6 @@ count (1 row) -starting permutation: s1-initialize s1-begin s1-insert-select s2-router-select s1-commit s1-select-count -master_create_empty_shard ---------------------------------------------------------------------- - 6780300 -(1 row) - -step s1-initialize: COPY select_append FROM PROGRAM 'echo 0, a, 0 && echo 1, b, 1 && echo 2, c, 2 && echo 3, d, 3 && echo 4, e, 4' WITH (format 'csv', append_to_shard xxxxx); -step s1-begin: BEGIN; -step s1-insert-select: INSERT INTO select_append SELECT * FROM select_append; -step s2-router-select: SELECT * FROM select_append WHERE id = 1; -id|data|int_data ---------------------------------------------------------------------- - 1| b | 1 -(1 row) - -step s1-commit: COMMIT; -step s1-select-count: SELECT COUNT(*) FROM select_append; -count ---------------------------------------------------------------------- - 10 -(1 row) - - starting permutation: s1-initialize s1-begin s1-update s2-router-select s1-commit s1-select-count master_create_empty_shard --------------------------------------------------------------------- @@ -1162,33 +1116,6 @@ count (1 row) -starting permutation: s1-initialize s1-begin s1-real-time-select s2-insert-select s1-commit s1-select-count -master_create_empty_shard ---------------------------------------------------------------------- - 6780300 -(1 row) - -step s1-initialize: COPY select_append FROM PROGRAM 'echo 0, a, 0 && echo 1, b, 1 && echo 2, c, 2 && echo 3, d, 3 && echo 4, e, 4' WITH (format 'csv', append_to_shard xxxxx); -step s1-begin: BEGIN; -step s1-real-time-select: SELECT * FROM select_append ORDER BY 1, 2; -id|data|int_data ---------------------------------------------------------------------- - 0| a | 0 - 1| b | 1 - 2| c | 2 - 3| d | 3 - 4| e | 4 -(5 rows) - -step s2-insert-select: INSERT INTO select_append SELECT * FROM select_append; -step s1-commit: COMMIT; -step s1-select-count: SELECT COUNT(*) FROM select_append; -count ---------------------------------------------------------------------- - 10 -(1 row) - - starting permutation: s1-initialize s1-begin s1-real-time-select s2-update s1-commit s1-select-count master_create_empty_shard --------------------------------------------------------------------- @@ -1621,33 +1548,6 @@ count (1 row) -starting permutation: s1-initialize s1-begin s1-insert-select s2-real-time-select s1-commit s1-select-count -master_create_empty_shard ---------------------------------------------------------------------- - 6780300 -(1 row) - -step s1-initialize: COPY select_append FROM PROGRAM 'echo 0, a, 0 && echo 1, b, 1 && echo 2, c, 2 && echo 3, d, 3 && echo 4, e, 4' WITH (format 'csv', append_to_shard xxxxx); -step s1-begin: BEGIN; -step s1-insert-select: INSERT INTO select_append SELECT * FROM select_append; -step s2-real-time-select: SELECT * FROM select_append ORDER BY 1, 2; -id|data|int_data ---------------------------------------------------------------------- - 0| a | 0 - 1| b | 1 - 2| c | 2 - 3| d | 3 - 4| e | 4 -(5 rows) - -step s1-commit: COMMIT; -step s1-select-count: SELECT COUNT(*) FROM select_append; -count ---------------------------------------------------------------------- - 10 -(1 row) - - starting permutation: s1-initialize s1-begin s1-update s2-real-time-select s1-commit s1-select-count master_create_empty_shard --------------------------------------------------------------------- @@ -2034,36 +1934,6 @@ count (1 row) -starting permutation: s1-initialize s1-begin s1-adaptive-select s2-insert-select s1-commit s1-select-count -master_create_empty_shard ---------------------------------------------------------------------- - 6780300 -(1 row) - -step s1-initialize: COPY select_append FROM PROGRAM 'echo 0, a, 0 && echo 1, b, 1 && echo 2, c, 2 && echo 3, d, 3 && echo 4, e, 4' WITH (format 'csv', append_to_shard xxxxx); -step s1-begin: BEGIN; -step s1-adaptive-select: - SET citus.enable_repartition_joins TO ON; - SELECT * FROM select_append AS t1 JOIN select_append AS t2 ON t1.id = t2.int_data ORDER BY 1, 2, 3, 4; - -id|data|int_data|id|data|int_data ---------------------------------------------------------------------- - 0| a | 0| 0| a | 0 - 1| b | 1| 1| b | 1 - 2| c | 2| 2| c | 2 - 3| d | 3| 3| d | 3 - 4| e | 4| 4| e | 4 -(5 rows) - -step s2-insert-select: INSERT INTO select_append SELECT * FROM select_append; -step s1-commit: COMMIT; -step s1-select-count: SELECT COUNT(*) FROM select_append; -count ---------------------------------------------------------------------- - 10 -(1 row) - - starting permutation: s1-initialize s1-begin s1-adaptive-select s2-update s1-commit s1-select-count master_create_empty_shard --------------------------------------------------------------------- @@ -2538,36 +2408,6 @@ count (1 row) -starting permutation: s1-initialize s1-begin s1-insert-select s2-adaptive-select s1-commit s1-select-count -master_create_empty_shard ---------------------------------------------------------------------- - 6780300 -(1 row) - -step s1-initialize: COPY select_append FROM PROGRAM 'echo 0, a, 0 && echo 1, b, 1 && echo 2, c, 2 && echo 3, d, 3 && echo 4, e, 4' WITH (format 'csv', append_to_shard xxxxx); -step s1-begin: BEGIN; -step s1-insert-select: INSERT INTO select_append SELECT * FROM select_append; -step s2-adaptive-select: - SET citus.enable_repartition_joins TO ON; - SELECT * FROM select_append AS t1 JOIN select_append AS t2 ON t1.id = t2.int_data ORDER BY 1, 2, 3, 4; - -id|data|int_data|id|data|int_data ---------------------------------------------------------------------- - 0| a | 0| 0| a | 0 - 1| b | 1| 1| b | 1 - 2| c | 2| 2| c | 2 - 3| d | 3| 3| d | 3 - 4| e | 4| 4| e | 4 -(5 rows) - -step s1-commit: COMMIT; -step s1-select-count: SELECT COUNT(*) FROM select_append; -count ---------------------------------------------------------------------- - 10 -(1 row) - - starting permutation: s1-initialize s1-begin s1-update s2-adaptive-select s1-commit s1-select-count master_create_empty_shard --------------------------------------------------------------------- diff --git a/src/test/regress/expected/issue_6758.out b/src/test/regress/expected/issue_6758.out new file mode 100644 index 000000000..34f20593f --- /dev/null +++ b/src/test/regress/expected/issue_6758.out @@ -0,0 +1,69 @@ +CREATE SCHEMA issue_6758; +SET search_path to 'issue_6758'; +CREATE TABLE dist0(id int); +SELECT create_distributed_table('dist0','id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE dist1(id int); +SELECT create_distributed_table('dist1','id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- added to verify we fixed the issue https://github.com/citusdata/citus/issues/6758 +-- generated by Citus query generator tool +SELECT + avg(avgsub.id) +FROM + ( + SELECT + table_0.id + FROM + ( + SELECT + table_1.id + FROM + ( + SELECT + table_2.id + FROM + ( + SELECT + table_3.id + FROM + ( + VALUES + (838) + ) AS table_3(id) FULL + JOIN dist0 AS table_4 USING (id) + WHERE + table_4.id = 3 + ) AS table_2 + WHERE + table_2.id = 2 + ORDER BY + id + LIMIT + 77 + ) AS table_1 + LEFT JOIN dist0 AS table_5 USING (id) + ORDER BY + id + LIMIT + 44 + ) AS table_0 FULL + JOIN dist1 AS table_6 USING (id) + ) AS avgsub; + avg +--------------------------------------------------------------------- + +(1 row) + +DROP SCHEMA issue_6758 CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table dist0 +drop cascades to table dist1 diff --git a/src/test/regress/expected/join_pushdown.out b/src/test/regress/expected/join_pushdown.out index c71478d30..004c007ef 100644 --- a/src/test/regress/expected/join_pushdown.out +++ b/src/test/regress/expected/join_pushdown.out @@ -152,7 +152,7 @@ ORDER BY 1; -- Full outer join with different distribution column types, should error out SELECT * FROM test_table_1 full join test_table_2 using(id); ERROR: cannot push down this subquery -DETAIL: Shards of relations in subquery need to have 1-to-1 shard partitioning +DETAIL: test_table_1 and test_table_2 are not colocated -- Test when the non-distributed column has the value of NULL INSERT INTO test_table_1 VALUES(7, NULL); INSERT INTO test_table_2 VALUES(7, NULL); @@ -463,10 +463,11 @@ SELECT * FROM abcd first join abcd second USING(b) join abcd third on first.b=th END; DROP SCHEMA join_schema CASCADE; -NOTICE: drop cascades to 6 other objects +NOTICE: drop cascades to 7 other objects DETAIL: drop cascades to table abcd drop cascades to table distributed_table drop cascades to table reference_table +drop cascades to table reference_table_9000004 drop cascades to table test_table_1 drop cascades to table test_table_2 drop cascades to view abcd_view diff --git a/src/test/regress/expected/limit_intermediate_size.out b/src/test/regress/expected/limit_intermediate_size.out index 662ce0e05..e6fd0e798 100644 --- a/src/test/regress/expected/limit_intermediate_size.out +++ b/src/test/regress/expected/limit_intermediate_size.out @@ -16,7 +16,7 @@ SELECT cte.user_id, cte.value_2 FROM cte,cte2 ORDER BY 1,2 LIMIT 10; ERROR: the intermediate result size exceeds citus.max_intermediate_result_size (currently 2 kB) DETAIL: Citus restricts the size of intermediate results of complex subqueries and CTEs to avoid accidentally pulling large result sets into once place. HINT: To run the current query, set citus.max_intermediate_result_size to a higher value or -1 to disable. -SET citus.max_intermediate_result_size TO 9; +SET citus.max_intermediate_result_size TO 17; WITH cte AS MATERIALIZED ( SELECT diff --git a/src/test/regress/expected/local_shard_execution.out b/src/test/regress/expected/local_shard_execution.out index f77af42da..e70dc1102 100644 --- a/src/test/regress/expected/local_shard_execution.out +++ b/src/test/regress/expected/local_shard_execution.out @@ -1200,7 +1200,7 @@ NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shar EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1209,7 +1209,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1218,7 +1218,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1227,7 +1227,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1236,7 +1236,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1245,7 +1245,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1254,7 +1254,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1263,7 +1263,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 diff --git a/src/test/regress/expected/local_shard_execution_0.out b/src/test/regress/expected/local_shard_execution_0.out index 5350728aa..c7f002cad 100644 --- a/src/test/regress/expected/local_shard_execution_0.out +++ b/src/test/regress/expected/local_shard_execution_0.out @@ -1200,7 +1200,7 @@ NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shar EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1209,7 +1209,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1218,7 +1218,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1227,7 +1227,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1236,7 +1236,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1245,7 +1245,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1254,7 +1254,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1263,7 +1263,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 diff --git a/src/test/regress/expected/local_shard_execution_replicated.out b/src/test/regress/expected/local_shard_execution_replicated.out index 7d36a5559..07da961c2 100644 --- a/src/test/regress/expected/local_shard_execution_replicated.out +++ b/src/test/regress/expected/local_shard_execution_replicated.out @@ -1187,7 +1187,7 @@ NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shar EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1196,7 +1196,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1205,7 +1205,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1214,7 +1214,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1223,7 +1223,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1232,7 +1232,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1241,7 +1241,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1250,7 +1250,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 diff --git a/src/test/regress/expected/local_shard_execution_replicated_0.out b/src/test/regress/expected/local_shard_execution_replicated_0.out index 759d842fd..c913bf628 100644 --- a/src/test/regress/expected/local_shard_execution_replicated_0.out +++ b/src/test/regress/expected/local_shard_execution_replicated_0.out @@ -1187,7 +1187,7 @@ NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shar EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1196,7 +1196,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1205,7 +1205,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1214,7 +1214,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1223,7 +1223,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1232,7 +1232,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1241,7 +1241,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 @@ -1250,7 +1250,7 @@ NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FR EXECUTE local_prepare_no_param_subquery; NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint NOTICE: executing the command locally: SELECT worker_column_1 AS value FROM (SELECT distributed_table.value AS worker_column_1 FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE ((distributed_table.key OPERATOR(pg_catalog.=) ANY (ARRAY[1, 6, 500, 701])) AND (((SELECT 2))::double precision OPERATOR(pg_catalog.>) random()))) worker_subquery ORDER BY worker_column_1 LIMIT '2'::bigint -NOTICE: executing the command locally: SELECT DISTINCT btrim(value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t +NOTICE: executing the command locally: SELECT DISTINCT TRIM(BOTH FROM value) AS btrim FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) t btrim --------------------------------------------------------------------- 12 diff --git a/src/test/regress/expected/local_table_join.out b/src/test/regress/expected/local_table_join.out index 96b570ac3..7da341207 100644 --- a/src/test/regress/expected/local_table_join.out +++ b/src/test/regress/expected/local_table_join.out @@ -86,7 +86,13 @@ CREATE FOREIGN TABLE foreign_table ( CREATE MATERIALIZED VIEW mv1 AS SELECT * FROM postgres_table; CREATE MATERIALIZED VIEW mv2 AS SELECT * FROM distributed_table; SET client_min_messages TO DEBUG1; --- the user doesn't allow local / distributed table joinn +-- the user doesn't allow local / distributed table join +SELECT master_remove_node('localhost', :master_port); -- https://github.com/citusdata/citus/issues/6958 + master_remove_node +--------------------------------------------------------------------- + +(1 row) + SET citus.local_table_join_policy TO 'never'; SELECT count(*) FROM postgres_table JOIN distributed_table USING(key); ERROR: direct joins between distributed and local tables are not supported @@ -94,6 +100,12 @@ HINT: Use CTE's or subqueries to select from local tables and use them in joins SELECT count(*) FROM postgres_table JOIN reference_table USING(key); ERROR: direct joins between distributed and local tables are not supported HINT: Use CTE's or subqueries to select from local tables and use them in joins +SELECT citus_set_coordinator_host('localhost'); -- https://github.com/citusdata/citus/issues/6958 + citus_set_coordinator_host +--------------------------------------------------------------------- + +(1 row) + -- the user prefers local table recursively planned SET citus.local_table_join_policy TO 'prefer-local'; SELECT count(*) FROM postgres_table JOIN distributed_table USING(key); @@ -1586,6 +1598,12 @@ DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT 1 AS res FROM (1 row) ROLLBACK; +SELECT master_remove_node('localhost', :master_port); -- https://github.com/citusdata/citus/issues/6958 + master_remove_node +--------------------------------------------------------------------- + +(1 row) + BEGIN; SELECT create_reference_table('table1'); NOTICE: Copying data from local table... @@ -1632,7 +1650,13 @@ DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT 1 AS res FROM (1 row) ROLLBACK; +SELECT citus_set_coordinator_host('localhost'); -- https://github.com/citusdata/citus/issues/6958 + citus_set_coordinator_host +--------------------------------------------------------------------- + +(1 row) + RESET client_min_messages; \set VERBOSITY terse DROP SCHEMA local_table_join CASCADE; -NOTICE: drop cascades to 22 other objects +NOTICE: drop cascades to 23 other objects diff --git a/src/test/regress/expected/logical_replication.out b/src/test/regress/expected/logical_replication.out index 79108dd11..8a3e96da9 100644 --- a/src/test/regress/expected/logical_replication.out +++ b/src/test/regress/expected/logical_replication.out @@ -14,13 +14,6 @@ SELECT create_distributed_table('dist', 'id'); (1 row) INSERT INTO dist SELECT generate_series(1, 100); -SELECT 1 from citus_add_node('localhost', :master_port, groupId := 0); -NOTICE: localhost:xxxxx is the coordinator and already contains metadata, skipping syncing the metadata - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - -- Create a publiction and subscription (including replication slot) manually. -- This allows us to test the cleanup logic at the start of the shard move. \c - - - :worker_1_port @@ -97,12 +90,6 @@ select citus_move_shard_placement(6830002, 'localhost', :worker_1_port, 'localho (1 row) -SELECT citus_remove_node('localhost', :master_port); - citus_remove_node ---------------------------------------------------------------------- - -(1 row) - -- the subscription is still there, as there is no cleanup record for it -- we have created it manually SELECT count(*) from pg_subscription; diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out index 412667037..3cb69936c 100644 --- a/src/test/regress/expected/merge.out +++ b/src/test/regress/expected/merge.out @@ -20,13 +20,14 @@ SET citus.next_shard_id TO 4000000; SET citus.explain_all_tasks TO true; SET citus.shard_replication_factor TO 1; SET citus.max_adaptive_executor_pool_size TO 1; +SET client_min_messages = warning; SELECT 1 FROM master_add_node('localhost', :master_port, groupid => 0); -NOTICE: localhost:xxxxx is the coordinator and already contains metadata, skipping syncing the metadata ?column? --------------------------------------------------------------------- 1 (1 row) +RESET client_min_messages; CREATE TABLE source ( order_id INT, @@ -206,7 +207,7 @@ HINT: To remove the local data, run: SELECT truncate_local_data_after_distribut (1 row) -SELECT create_distributed_table('source', 'customer_id'); +SELECT create_distributed_table('source', 'customer_id', colocate_with=>'target'); NOTICE: Copying data from local table... NOTICE: copying the data has completed DETAIL: The local data in the table is no longer visible, but is still on disk. @@ -236,7 +237,7 @@ MERGE INTO target t last_order_id = s.order_id WHEN NOT MATCHED THEN DO NOTHING; -NOTICE: issuing MERGE INTO merge_schema.target_xxxxxxx t USING merge_schema.source_xxxxxxx s ON ((t.customer_id OPERATOR(pg_catalog.=) s.customer_id) AND (t.customer_id OPERATOR(pg_catalog.=) 30002)) WHEN MATCHED AND ((t.order_center COLLATE "default") OPERATOR(pg_catalog.=) 'XX'::text) THEN DELETE WHEN MATCHED THEN UPDATE SET last_order_id = s.order_id, order_count = (t.order_count OPERATOR(pg_catalog.+) 1) WHEN NOT MATCHED THEN DO NOTHING +NOTICE: issuing MERGE INTO merge_schema.target_xxxxxxx t USING merge_schema.source_xxxxxxx s ON ((t.customer_id OPERATOR(pg_catalog.=) s.customer_id) AND (t.customer_id OPERATOR(pg_catalog.=) 30002)) WHEN MATCHED AND ((t.order_center)::text OPERATOR(pg_catalog.=) 'XX'::text) THEN DELETE WHEN MATCHED THEN UPDATE SET last_order_id = s.order_id, order_count = (t.order_count OPERATOR(pg_catalog.+) 1) WHEN NOT MATCHED THEN DO NOTHING DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx SET citus.log_remote_commands to false; SELECT * from target t WHERE t.customer_id = 30002; @@ -440,7 +441,7 @@ HINT: To remove the local data, run: SELECT truncate_local_data_after_distribut (1 row) -SELECT create_distributed_table('s1', 'id'); +SELECT create_distributed_table('s1', 'id', colocate_with=>'t1'); NOTICE: Copying data from local table... NOTICE: copying the data has completed DETAIL: The local data in the table is no longer visible, but is still on disk. @@ -650,7 +651,7 @@ HINT: To remove the local data, run: SELECT truncate_local_data_after_distribut (1 row) -SELECT create_distributed_table('s2', 'id'); +SELECT create_distributed_table('s2', 'id', colocate_with => 't2'); NOTICE: Copying data from local table... NOTICE: copying the data has completed DETAIL: The local data in the table is no longer visible, but is still on disk. @@ -1073,7 +1074,7 @@ UPDATE SET value = vl_source.value, id = vl_target.id + 1 WHEN NOT MATCHED THEN INSERT VALUES(vl_source.ID, vl_source.value); DEBUG: Creating MERGE router plan -DEBUG: +DEBUG: RESET client_min_messages; SELECT * INTO vl_local FROM vl_target ORDER BY 1 ; -- Should be equal @@ -1331,7 +1332,7 @@ MERGE INTO ft_target WHEN NOT MATCHED THEN INSERT (id, user_val) VALUES (foreign_table.id, foreign_table.user_val); DEBUG: Creating MERGE router plan -DEBUG: +DEBUG: RESET client_min_messages; SELECT * FROM ft_target; id | user_val @@ -1404,7 +1405,7 @@ HINT: To remove the local data, run: SELECT truncate_local_data_after_distribut (1 row) -SELECT create_distributed_table('source_cj1', 'sid1'); +SELECT create_distributed_table('source_cj1', 'sid1', colocate_with => 'target_cj'); NOTICE: Copying data from local table... NOTICE: copying the data has completed DETAIL: The local data in the table is no longer visible, but is still on disk. @@ -1414,7 +1415,7 @@ HINT: To remove the local data, run: SELECT truncate_local_data_after_distribut (1 row) -SELECT create_distributed_table('source_cj2', 'sid2'); +SELECT create_distributed_table('source_cj2', 'sid2', colocate_with => 'target_cj'); NOTICE: Copying data from local table... NOTICE: copying the data has completed DETAIL: The local data in the table is no longer visible, but is still on disk. @@ -1425,19 +1426,13 @@ HINT: To remove the local data, run: SELECT truncate_local_data_after_distribut (1 row) BEGIN; -SET citus.log_remote_commands to true; MERGE INTO target_cj t -USING source_cj1 s1 INNER JOIN source_cj2 s2 ON sid1 = sid2 +USING (SELECT * FROM source_cj1 s1 INNER JOIN source_cj2 s2 ON sid1 = sid2) s ON t.tid = sid1 AND t.tid = 2 WHEN MATCHED THEN UPDATE SET src = src2 WHEN NOT MATCHED THEN DO NOTHING; -NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.target_cj_xxxxxxx t USING (merge_schema.source_cj1_xxxxxxx s1 JOIN merge_schema.source_cj2_xxxxxxx s2 ON ((s1.sid1 OPERATOR(pg_catalog.=) s2.sid2))) ON ((t.tid OPERATOR(pg_catalog.=) s1.sid1) AND (t.tid OPERATOR(pg_catalog.=) 2)) WHEN MATCHED THEN UPDATE SET src = s2.src2 WHEN NOT MATCHED THEN DO NOTHING -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -SET citus.log_remote_commands to false; SELECT * FROM target_cj ORDER BY 1; tid | src | val --------------------------------------------------------------------- @@ -1451,8 +1446,8 @@ ROLLBACK; BEGIN; -- try accessing columns from either side of the source join MERGE INTO target_cj t -USING source_cj1 s2 - INNER JOIN source_cj2 s1 ON sid1 = sid2 AND val1 = 10 +USING (SELECT * FROM source_cj1 s2 + INNER JOIN source_cj2 s1 ON sid1 = sid2 AND val1 = 10) s ON t.tid = sid1 AND t.tid = 2 WHEN MATCHED THEN UPDATE SET src = src1, val = val2 @@ -1520,7 +1515,7 @@ SELECT * FROM target_cj ORDER BY 1; ROLLBACK; -- Test PREPARE -PREPARE foo(int) AS +PREPARE merge_prepare(int) AS MERGE INTO target_cj target USING (SELECT * FROM source_cj1) sub ON target.tid = sub.sid1 AND target.tid = $1 @@ -1538,11 +1533,11 @@ SELECT * FROM target_cj ORDER BY 1; (4 rows) BEGIN; -EXECUTE foo(2); -EXECUTE foo(2); -EXECUTE foo(2); -EXECUTE foo(2); -EXECUTE foo(2); +EXECUTE merge_prepare(2); +EXECUTE merge_prepare(2); +EXECUTE merge_prepare(2); +EXECUTE merge_prepare(2); +EXECUTE merge_prepare(2); SELECT * FROM target_cj ORDER BY 1; tid | src | val --------------------------------------------------------------------- @@ -1556,12 +1551,7 @@ ROLLBACK; BEGIN; SET citus.log_remote_commands to true; SET client_min_messages TO DEBUG1; -EXECUTE foo(2); -DEBUG: -DEBUG: -DEBUG: -DEBUG: -DEBUG: Creating MERGE router plan +EXECUTE merge_prepare(2); DEBUG: DEBUG: Creating MERGE router plan NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); @@ -1569,7 +1559,7 @@ DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing MERGE INTO merge_schema.target_cj_xxxxxxx target USING (SELECT source_cj1.sid1, source_cj1.src1, source_cj1.val1 FROM merge_schema.source_cj1_xxxxxxx source_cj1) sub ON ((target.tid OPERATOR(pg_catalog.=) sub.sid1) AND (target.tid OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = sub.val1 WHEN NOT MATCHED THEN DO NOTHING DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx RESET client_min_messages; -EXECUTE foo(2); +EXECUTE merge_prepare(2); NOTICE: issuing MERGE INTO merge_schema.target_cj_xxxxxxx target USING (SELECT source_cj1.sid1, source_cj1.src1, source_cj1.val1 FROM merge_schema.source_cj1_xxxxxxx source_cj1) sub ON ((target.tid OPERATOR(pg_catalog.=) sub.sid1) AND (target.tid OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = sub.val1 WHEN NOT MATCHED THEN DO NOTHING DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx SET citus.log_remote_commands to false; @@ -1609,7 +1599,7 @@ HINT: To remove the local data, run: SELECT truncate_local_data_after_distribut (1 row) -SELECT create_distributed_table('citus_source', 'id'); +SELECT create_distributed_table('citus_source', 'id', colocate_with => 'citus_target'); NOTICE: Copying data from local table... NOTICE: copying the data has completed DETAIL: The local data in the table is no longer visible, but is still on disk. @@ -1667,13 +1657,13 @@ NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_ DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON (t.id OPERATOR(pg_catalog.=) s.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by Merge'::text) COLLATE "default") WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON (t.id OPERATOR(pg_catalog.=) s.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by Merge'::text) WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON (t.id OPERATOR(pg_catalog.=) s.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by Merge'::text) COLLATE "default") WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON (t.id OPERATOR(pg_catalog.=) s.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by Merge'::text) WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON (t.id OPERATOR(pg_catalog.=) s.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by Merge'::text) COLLATE "default") WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON (t.id OPERATOR(pg_catalog.=) s.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by Merge'::text) WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON (t.id OPERATOR(pg_catalog.=) s.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by Merge'::text) COLLATE "default") WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON (t.id OPERATOR(pg_catalog.=) s.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by Merge'::text) WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx SET citus.log_remote_commands to false; SELECT compare_tables(); @@ -1710,13 +1700,13 @@ NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_ DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (s.id OPERATOR(pg_catalog.<) 100)) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by Merge'::text) COLLATE "default") WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (s.id OPERATOR(pg_catalog.<) 100)) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by Merge'::text) WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (s.id OPERATOR(pg_catalog.<) 100)) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by Merge'::text) COLLATE "default") WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (s.id OPERATOR(pg_catalog.<) 100)) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by Merge'::text) WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (s.id OPERATOR(pg_catalog.<) 100)) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by Merge'::text) COLLATE "default") WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (s.id OPERATOR(pg_catalog.<) 100)) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by Merge'::text) WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (s.id OPERATOR(pg_catalog.<) 100)) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by Merge'::text) COLLATE "default") WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (s.id OPERATOR(pg_catalog.<) 100)) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by Merge'::text) WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx SET citus.log_remote_commands to false; SELECT compare_tables(); @@ -1753,13 +1743,13 @@ NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_ DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (t.id OPERATOR(pg_catalog.<) 100)) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by Merge'::text) COLLATE "default") WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (t.id OPERATOR(pg_catalog.<) 100)) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by Merge'::text) WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (t.id OPERATOR(pg_catalog.<) 100)) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by Merge'::text) COLLATE "default") WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (t.id OPERATOR(pg_catalog.<) 100)) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by Merge'::text) WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (t.id OPERATOR(pg_catalog.<) 100)) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by Merge'::text) COLLATE "default") WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (t.id OPERATOR(pg_catalog.<) 100)) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by Merge'::text) WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (t.id OPERATOR(pg_catalog.<) 100)) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by Merge'::text) COLLATE "default") WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (t.id OPERATOR(pg_catalog.<) 100)) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 400) THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by Merge'::text) WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx SET citus.log_remote_commands to false; SELECT compare_tables(); @@ -1836,13 +1826,13 @@ NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_ DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (s.id OPERATOR(pg_catalog.=) 250)) WHEN MATCHED THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by Merge'::text) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (s.id OPERATOR(pg_catalog.=) 250)) WHEN MATCHED THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by Merge'::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (s.id OPERATOR(pg_catalog.=) 250)) WHEN MATCHED THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by Merge'::text) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (s.id OPERATOR(pg_catalog.=) 250)) WHEN MATCHED THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by Merge'::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (s.id OPERATOR(pg_catalog.=) 250)) WHEN MATCHED THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by Merge'::text) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (s.id OPERATOR(pg_catalog.=) 250)) WHEN MATCHED THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by Merge'::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (s.id OPERATOR(pg_catalog.=) 250)) WHEN MATCHED THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by Merge'::text) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (s.id OPERATOR(pg_catalog.=) 250)) WHEN MATCHED THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by Merge'::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx SET citus.log_remote_commands to false; SELECT compare_tables(); @@ -1871,7 +1861,7 @@ WHEN NOT MATCHED THEN DO NOTHING; NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (s.id OPERATOR(pg_catalog.=) 250)) WHEN MATCHED THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by Merge'::text) COLLATE "default") WHEN NOT MATCHED THEN DO NOTHING +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING merge_schema.citus_source_xxxxxxx s ON ((t.id OPERATOR(pg_catalog.=) s.id) AND (s.id OPERATOR(pg_catalog.=) 250)) WHEN MATCHED THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by Merge'::text) WHEN NOT MATCHED THEN DO NOTHING DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx SET citus.log_remote_commands to false; SELECT compare_tables(); @@ -1917,13 +1907,13 @@ NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_ DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing WITH cte AS (SELECT citus_source_view.id, citus_source_view.val FROM (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source WHERE (citus_source.id OPERATOR(pg_catalog.<) 400)) citus_source_view) MERGE INTO merge_schema.citus_target_xxxxxxx t USING cte ON (cte.id OPERATOR(pg_catalog.=) t.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 350) THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by CTE'::text) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (cte.id, cte.val) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.<) 350) THEN DELETE +NOTICE: issuing WITH cte AS (SELECT citus_source_view.id, citus_source_view.val FROM (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source WHERE (citus_source.id OPERATOR(pg_catalog.<) 400)) citus_source_view) MERGE INTO merge_schema.citus_target_xxxxxxx t USING cte ON (cte.id OPERATOR(pg_catalog.=) t.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 350) THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by CTE'::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (cte.id, cte.val) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.<) 350) THEN DELETE DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing WITH cte AS (SELECT citus_source_view.id, citus_source_view.val FROM (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source WHERE (citus_source.id OPERATOR(pg_catalog.<) 400)) citus_source_view) MERGE INTO merge_schema.citus_target_xxxxxxx t USING cte ON (cte.id OPERATOR(pg_catalog.=) t.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 350) THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by CTE'::text) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (cte.id, cte.val) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.<) 350) THEN DELETE +NOTICE: issuing WITH cte AS (SELECT citus_source_view.id, citus_source_view.val FROM (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source WHERE (citus_source.id OPERATOR(pg_catalog.<) 400)) citus_source_view) MERGE INTO merge_schema.citus_target_xxxxxxx t USING cte ON (cte.id OPERATOR(pg_catalog.=) t.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 350) THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by CTE'::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (cte.id, cte.val) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.<) 350) THEN DELETE DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing WITH cte AS (SELECT citus_source_view.id, citus_source_view.val FROM (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source WHERE (citus_source.id OPERATOR(pg_catalog.<) 400)) citus_source_view) MERGE INTO merge_schema.citus_target_xxxxxxx t USING cte ON (cte.id OPERATOR(pg_catalog.=) t.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 350) THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by CTE'::text) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (cte.id, cte.val) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.<) 350) THEN DELETE +NOTICE: issuing WITH cte AS (SELECT citus_source_view.id, citus_source_view.val FROM (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source WHERE (citus_source.id OPERATOR(pg_catalog.<) 400)) citus_source_view) MERGE INTO merge_schema.citus_target_xxxxxxx t USING cte ON (cte.id OPERATOR(pg_catalog.=) t.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 350) THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by CTE'::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (cte.id, cte.val) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.<) 350) THEN DELETE DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing WITH cte AS (SELECT citus_source_view.id, citus_source_view.val FROM (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source WHERE (citus_source.id OPERATOR(pg_catalog.<) 400)) citus_source_view) MERGE INTO merge_schema.citus_target_xxxxxxx t USING cte ON (cte.id OPERATOR(pg_catalog.=) t.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 350) THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by CTE'::text) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (cte.id, cte.val) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.<) 350) THEN DELETE +NOTICE: issuing WITH cte AS (SELECT citus_source_view.id, citus_source_view.val FROM (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source WHERE (citus_source.id OPERATOR(pg_catalog.<) 400)) citus_source_view) MERGE INTO merge_schema.citus_target_xxxxxxx t USING cte ON (cte.id OPERATOR(pg_catalog.=) t.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 350) THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by CTE'::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (cte.id, cte.val) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.<) 350) THEN DELETE DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx SET citus.log_remote_commands to false; SELECT compare_tables(); @@ -1958,13 +1948,13 @@ NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_ DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) subq ON (subq.id OPERATOR(pg_catalog.=) t.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 350) THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by subquery'::text) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (subq.id, subq.val) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.<) 350) THEN DELETE +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) subq ON (subq.id OPERATOR(pg_catalog.=) t.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 350) THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by subquery'::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (subq.id, subq.val) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.<) 350) THEN DELETE DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) subq ON (subq.id OPERATOR(pg_catalog.=) t.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 350) THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by subquery'::text) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (subq.id, subq.val) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.<) 350) THEN DELETE +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) subq ON (subq.id OPERATOR(pg_catalog.=) t.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 350) THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by subquery'::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (subq.id, subq.val) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.<) 350) THEN DELETE DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) subq ON (subq.id OPERATOR(pg_catalog.=) t.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 350) THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by subquery'::text) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (subq.id, subq.val) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.<) 350) THEN DELETE +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) subq ON (subq.id OPERATOR(pg_catalog.=) t.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 350) THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by subquery'::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (subq.id, subq.val) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.<) 350) THEN DELETE DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) subq ON (subq.id OPERATOR(pg_catalog.=) t.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 350) THEN UPDATE SET val = (((t.val COLLATE "default") OPERATOR(pg_catalog.||) 'Updated by subquery'::text) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (subq.id, subq.val) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.<) 350) THEN DELETE +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx t USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) subq ON (subq.id OPERATOR(pg_catalog.=) t.id) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.>) 350) THEN UPDATE SET val = ((t.val)::text OPERATOR(pg_catalog.||) 'Updated by subquery'::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (subq.id, subq.val) WHEN MATCHED AND (t.id OPERATOR(pg_catalog.<) 350) THEN DELETE DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx SET citus.log_remote_commands to false; SELECT compare_tables(); @@ -2042,13 +2032,13 @@ SELECT count(*) FROM citus_target; -- before merge SET citus.log_remote_commands to true; EXECUTE citus_prep(500); -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx SELECT * FROM citus_target WHERE id = 500; -- non-cached NOTICE: issuing SELECT id, val FROM merge_schema.citus_target_xxxxxxx citus_target WHERE (id OPERATOR(pg_catalog.=) 500) @@ -2059,49 +2049,49 @@ DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx (1 row) EXECUTE citus_prep(500); -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx EXECUTE citus_prep(500); -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx EXECUTE citus_prep(500); -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx EXECUTE citus_prep(500); -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx EXECUTE citus_prep(500); -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = (('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val COLLATE "default")) COLLATE "default") WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) +NOTICE: issuing MERGE INTO merge_schema.citus_target_xxxxxxx citus_target USING (SELECT citus_source.id, citus_source.val FROM merge_schema.citus_source_xxxxxxx citus_source) sub ON ((citus_target.id OPERATOR(pg_catalog.=) sub.id) AND (citus_target.id OPERATOR(pg_catalog.=) $1)) WHEN MATCHED THEN UPDATE SET val = ('Updated by prepare using '::text OPERATOR(pg_catalog.||) (sub.val)::text) WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx SET citus.log_remote_commands to false; SELECT * FROM citus_target WHERE id = 500; -- cached @@ -2416,36 +2406,785 @@ SELECT * FROM target_set ORDER BY 1, 2; 2 | (2 rows) +-- +-- Reference as a source +-- +CREATE TABLE reftarget_local(t1 int, t2 int); +CREATE TABLE refsource_ref(s1 int, s2 int); +INSERT INTO reftarget_local VALUES(1, 0); +INSERT INTO reftarget_local VALUES(3, 100); +INSERT INTO refsource_ref VALUES(1, 1); +INSERT INTO refsource_ref VALUES(2, 2); +INSERT INTO refsource_ref VALUES(3, 3); +MERGE INTO reftarget_local +USING (SELECT * FROM refsource_ref UNION SELECT * FROM refsource_ref) AS foo ON reftarget_local.t1 = foo.s1 +WHEN MATCHED AND reftarget_local.t2 = 100 THEN + DELETE +WHEN MATCHED THEN + UPDATE SET t2 = t2 + 100 +WHEN NOT MATCHED THEN + INSERT VALUES(foo.s1); +DROP TABLE IF EXISTS pg_result; +SELECT * INTO pg_result FROM reftarget_local ORDER BY 1, 2; +-- Make source table as reference (target is Postgres) +TRUNCATE reftarget_local; +TRUNCATE refsource_ref; +INSERT INTO reftarget_local VALUES(1, 0); +INSERT INTO reftarget_local VALUES(3, 100); +INSERT INTO refsource_ref VALUES(1, 1); +INSERT INTO refsource_ref VALUES(2, 2); +INSERT INTO refsource_ref VALUES(3, 3); +SELECT create_reference_table('refsource_ref'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_schema.refsource_ref$$) + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO reftarget_local +USING (SELECT * FROM refsource_ref UNION SELECT * FROM refsource_ref) AS foo ON reftarget_local.t1 = foo.s1 +WHEN MATCHED AND reftarget_local.t2 = 100 THEN + DELETE +WHEN MATCHED THEN + UPDATE SET t2 = t2 + 100 +WHEN NOT MATCHED THEN + INSERT VALUES(foo.s1); +SELECT * INTO pg_ref FROM reftarget_local ORDER BY 1, 2; +-- Should be equal +SELECT c.*, p.* +FROM pg_ref c, pg_result p +WHERE c.t1 = p.t1 +ORDER BY 1,2; + t1 | t2 | t1 | t2 +--------------------------------------------------------------------- + 1 | 100 | 1 | 100 + 2 | | 2 | +(2 rows) + +-- Must return zero rows +SELECT count(*) +FROM pg_result FULL OUTER JOIN pg_ref ON pg_result.t1 = pg_ref.t1 +WHERE pg_result.t1 IS NULL OR pg_ref.t1 IS NULL; + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- Now make both Citus tables, reference as source, local as target +TRUNCATE reftarget_local; +TRUNCATE refsource_ref; +INSERT INTO reftarget_local VALUES(1, 0); +INSERT INTO reftarget_local VALUES(3, 100); +INSERT INTO refsource_ref VALUES(1, 1); +INSERT INTO refsource_ref VALUES(2, 2); +INSERT INTO refsource_ref VALUES(3, 3); +SELECT citus_add_local_table_to_metadata('reftarget_local'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO reftarget_local +USING (SELECT * FROM refsource_ref UNION SELECT * FROM refsource_ref) AS foo ON reftarget_local.t1 = foo.s1 +WHEN MATCHED AND reftarget_local.t2 = 100 THEN + DELETE +WHEN MATCHED THEN + UPDATE SET t2 = t2 + 100 +WHEN NOT MATCHED THEN + INSERT VALUES(foo.s1); +SELECT * INTO local_ref FROM reftarget_local ORDER BY 1, 2; +-- Should be equal +SELECT c.*, p.* +FROM local_ref c, pg_result p +WHERE c.t1 = p.t1 +ORDER BY 1,2; + t1 | t2 | t1 | t2 +--------------------------------------------------------------------- + 1 | 100 | 1 | 100 + 2 | | 2 | +(2 rows) + +-- Must return zero rows +SELECT count(*) +FROM pg_result FULL OUTER JOIN local_ref ON pg_result.t1 = local_ref.t1 +WHERE pg_result.t1 IS NULL OR local_ref.t1 IS NULL; + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- Now make target as distributed, keep reference as source +TRUNCATE reftarget_local; +TRUNCATE refsource_ref; +INSERT INTO reftarget_local VALUES(1, 0); +INSERT INTO reftarget_local VALUES(3, 100); +INSERT INTO refsource_ref VALUES(1, 1); +INSERT INTO refsource_ref VALUES(2, 2); +INSERT INTO refsource_ref VALUES(3, 3); +SELECT create_distributed_table('reftarget_local', 't1'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_schema.reftarget_local$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO reftarget_local +USING (SELECT * FROM refsource_ref UNION SELECT * FROM refsource_ref) AS foo ON reftarget_local.t1 = foo.s1 +WHEN MATCHED AND reftarget_local.t2 = 100 THEN + DELETE +WHEN MATCHED THEN + UPDATE SET t2 = t2 + 100 +WHEN NOT MATCHED THEN + INSERT VALUES(foo.s1); +SELECT * INTO dist_reftarget FROM reftarget_local ORDER BY 1, 2; +-- Should be equal +SELECT c.*, p.* +FROM dist_reftarget c, pg_result p +WHERE c.t1 = p.t1 +ORDER BY 1,2; + t1 | t2 | t1 | t2 +--------------------------------------------------------------------- + 1 | 100 | 1 | 100 + 2 | | 2 | +(2 rows) + +-- Must return zero rows +SELECT count(*) +FROM pg_result FULL OUTER JOIN dist_reftarget ON pg_result.t1 = dist_reftarget.t1 +WHERE pg_result.t1 IS NULL OR dist_reftarget.t1 IS NULL; + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- +-- Distributed (target), Reference(source) +-- +CREATE TABLE demo_distributed(id1 int, val1 int); +CREATE TABLE demo_source_table(id2 int, val2 int); +CREATE FUNCTION setup_demo_data() RETURNS VOID AS $$ +INSERT INTO demo_distributed VALUES(1, 100); +INSERT INTO demo_distributed VALUES(7, 100); +INSERT INTO demo_distributed VALUES(15, 100); +INSERT INTO demo_distributed VALUES(100, 0); +INSERT INTO demo_distributed VALUES(300, 100); +INSERT INTO demo_distributed VALUES(400, 0); + +INSERT INTO demo_source_table VALUES(1, 77); +INSERT INTO demo_source_table VALUES(15, 77); +INSERT INTO demo_source_table VALUES(75, 77); +INSERT INTO demo_source_table VALUES(100, 77); +INSERT INTO demo_source_table VALUES(300, 77); +INSERT INTO demo_source_table VALUES(400, 77); +INSERT INTO demo_source_table VALUES(500, 77); +$$ +LANGUAGE SQL; +CREATE FUNCTION merge_demo_data() RETURNS VOID AS $$ +MERGE INTO demo_distributed t +USING demo_source_table s ON s.id2 = t.id1 +WHEN MATCHED AND t.val1= 0 THEN + DELETE +WHEN MATCHED THEN + UPDATE SET val1 = val1 + s.val2 +WHEN NOT MATCHED THEN + INSERT VALUES(s.id2, s.val2); +$$ +LANGUAGE SQL; +SELECT setup_demo_data(); + setup_demo_data +--------------------------------------------------------------------- + +(1 row) + +SELECT merge_demo_data(); + merge_demo_data +--------------------------------------------------------------------- + +(1 row) + +SELECT * INTO pg_demo_result FROM demo_distributed ORDER BY 1, 2; +TRUNCATE demo_distributed; +TRUNCATE demo_source_table; +SELECT create_distributed_table('demo_distributed', 'id1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_reference_table('demo_source_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +SELECT setup_demo_data(); + setup_demo_data +--------------------------------------------------------------------- + +(1 row) + +SELECT merge_demo_data(); + merge_demo_data +--------------------------------------------------------------------- + +(1 row) + +SELECT * INTO dist_demo_result FROM demo_distributed ORDER BY 1, 2; +-- Should be equal +SELECT c.*, p.* +FROM dist_demo_result c, pg_demo_result p +WHERE c.id1 = p.id1 +ORDER BY 1,2; + id1 | val1 | id1 | val1 +--------------------------------------------------------------------- + 1 | 177 | 1 | 177 + 7 | 100 | 7 | 100 + 15 | 177 | 15 | 177 + 75 | 77 | 75 | 77 + 300 | 177 | 300 | 177 + 500 | 77 | 500 | 77 +(6 rows) + +-- Must return zero rows +SELECT count(*) +FROM pg_demo_result p FULL OUTER JOIN dist_demo_result d ON p.id1 = d.id1 +WHERE p.id1 IS NULL OR d.id1 IS NULL; + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- Now convert source as distributed, but non-colocated with target +DROP TABLE pg_demo_result, dist_demo_result; +SELECT undistribute_table('demo_distributed'); +NOTICE: creating a new table for merge_schema.demo_distributed +NOTICE: moving the data of merge_schema.demo_distributed +NOTICE: dropping the old merge_schema.demo_distributed +NOTICE: renaming the new table to merge_schema.demo_distributed + undistribute_table +--------------------------------------------------------------------- + +(1 row) + +SELECT undistribute_table('demo_source_table'); +NOTICE: creating a new table for merge_schema.demo_source_table +NOTICE: moving the data of merge_schema.demo_source_table +NOTICE: dropping the old merge_schema.demo_source_table +NOTICE: renaming the new table to merge_schema.demo_source_table + undistribute_table +--------------------------------------------------------------------- + +(1 row) + +CREATE OR REPLACE FUNCTION merge_demo_data() RETURNS VOID AS $$ +MERGE INTO demo_distributed t +USING (SELECT id2,val2 FROM demo_source_table UNION SELECT val2,id2 FROM demo_source_table) AS s +ON t.id1 = s.id2 +WHEN MATCHED THEN + UPDATE SET val1 = val1 + 1; +$$ +LANGUAGE SQL; +TRUNCATE demo_distributed; +TRUNCATE demo_source_table; +SELECT setup_demo_data(); + setup_demo_data +--------------------------------------------------------------------- + +(1 row) + +SELECT merge_demo_data(); + merge_demo_data +--------------------------------------------------------------------- + +(1 row) + +SELECT * INTO pg_demo_result FROM demo_distributed ORDER BY 1, 2; +SELECT create_distributed_table('demo_distributed', 'id1'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_schema.demo_distributed$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('demo_source_table', 'id2', colocate_with=>'none'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_schema.demo_source_table$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +TRUNCATE demo_distributed; +TRUNCATE demo_source_table; +SELECT setup_demo_data(); + setup_demo_data +--------------------------------------------------------------------- + +(1 row) + +SELECT merge_demo_data(); + merge_demo_data +--------------------------------------------------------------------- + +(1 row) + +SELECT * INTO dist_demo_result FROM demo_distributed ORDER BY 1, 2; +-- Should be equal +SELECT c.*, p.* +FROM dist_demo_result c, pg_demo_result p +WHERE c.id1 = p.id1 +ORDER BY 1,2; + id1 | val1 | id1 | val1 +--------------------------------------------------------------------- + 1 | 101 | 1 | 101 + 7 | 100 | 7 | 100 + 15 | 101 | 15 | 101 + 100 | 1 | 100 | 1 + 300 | 101 | 300 | 101 + 400 | 1 | 400 | 1 +(6 rows) + +-- Must return zero rows +SELECT count(*) +FROM pg_demo_result p FULL OUTER JOIN dist_demo_result d ON p.id1 = d.id1 +WHERE p.id1 IS NULL OR d.id1 IS NULL; + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- Test with LIMIT +CREATE OR REPLACE FUNCTION merge_demo_data() RETURNS VOID AS $$ +MERGE INTO demo_distributed t +USING (SELECT 999 as s3, demo_source_table.* FROM (SELECT * FROM demo_source_table ORDER BY 1 LIMIT 3) as foo LEFT JOIN demo_source_table USING(id2)) AS s +ON t.id1 = s.id2 +WHEN MATCHED THEN + UPDATE SET val1 = s3 +WHEN NOT MATCHED THEN + INSERT VALUES(id2, s3); +$$ +LANGUAGE SQL; +DROP TABLE pg_demo_result, dist_demo_result; +SELECT undistribute_table('demo_distributed'); +NOTICE: creating a new table for merge_schema.demo_distributed +NOTICE: moving the data of merge_schema.demo_distributed +NOTICE: dropping the old merge_schema.demo_distributed +NOTICE: renaming the new table to merge_schema.demo_distributed + undistribute_table +--------------------------------------------------------------------- + +(1 row) + +SELECT undistribute_table('demo_source_table'); +NOTICE: creating a new table for merge_schema.demo_source_table +NOTICE: moving the data of merge_schema.demo_source_table +NOTICE: dropping the old merge_schema.demo_source_table +NOTICE: renaming the new table to merge_schema.demo_source_table + undistribute_table +--------------------------------------------------------------------- + +(1 row) + +TRUNCATE demo_distributed; +TRUNCATE demo_source_table; +SELECT setup_demo_data(); + setup_demo_data +--------------------------------------------------------------------- + +(1 row) + +SELECT merge_demo_data(); + merge_demo_data +--------------------------------------------------------------------- + +(1 row) + +SELECT * INTO pg_demo_result FROM demo_distributed ORDER BY 1, 2; +SELECT create_distributed_table('demo_distributed', 'id1'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_schema.demo_distributed$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('demo_source_table', 'id2', colocate_with=>'none'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_schema.demo_source_table$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +TRUNCATE demo_distributed; +TRUNCATE demo_source_table; +SELECT setup_demo_data(); + setup_demo_data +--------------------------------------------------------------------- + +(1 row) + +SELECT merge_demo_data(); + merge_demo_data +--------------------------------------------------------------------- + +(1 row) + +SELECT * INTO dist_demo_result FROM demo_distributed ORDER BY 1, 2; +-- Should be equal +SELECT c.*, p.* +FROM dist_demo_result c, pg_demo_result p +WHERE c.id1 = p.id1 +ORDER BY 1,2; + id1 | val1 | id1 | val1 +--------------------------------------------------------------------- + 1 | 999 | 1 | 999 + 7 | 100 | 7 | 100 + 15 | 999 | 15 | 999 + 75 | 999 | 75 | 999 + 100 | 0 | 100 | 0 + 300 | 100 | 300 | 100 + 400 | 0 | 400 | 0 +(7 rows) + +-- Must return zero rows +SELECT count(*) +FROM pg_demo_result p FULL OUTER JOIN dist_demo_result d ON p.id1 = d.id1 +WHERE p.id1 IS NULL OR d.id1 IS NULL; + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- Test explain with repartition +SET citus.explain_all_tasks TO false; +EXPLAIN (COSTS OFF) +MERGE INTO demo_distributed t +USING (SELECT 999 as s3, demo_source_table.* FROM (SELECT * FROM demo_source_table ORDER BY 1 LIMIT 3) as foo LEFT JOIN demo_source_table USING(id2)) AS s +ON t.id1 = s.id2 +WHEN MATCHED THEN + UPDATE SET val1 = s3 +WHEN NOT MATCHED THEN + INSERT VALUES(id2, s3); + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus MERGE INTO ...) + MERGE INTO demo_distributed method: pull to coordinator + -> Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Limit + -> Sort + Sort Key: remote_scan.id2 + -> Custom Scan (Citus Adaptive) + Task Count: 4 + Tasks Shown: One of 4 + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Limit + -> Sort + Sort Key: id2 + -> Seq Scan on demo_source_table_4000135 demo_source_table + -> Distributed Subplan XXX_2 + -> Custom Scan (Citus Adaptive) + Task Count: 4 + Tasks Shown: One of 4 + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Seq Scan on demo_source_table_4000135 demo_source_table + Task Count: 1 + Tasks Shown: All + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Merge Left Join + Merge Cond: (intermediate_result.id2 = intermediate_result_1.id2) + -> Sort + Sort Key: intermediate_result.id2 + -> Function Scan on read_intermediate_result intermediate_result + -> Sort + Sort Key: intermediate_result_1.id2 + -> Function Scan on read_intermediate_result intermediate_result_1 +(35 rows) + +-- Test multiple join conditions on distribution column +MERGE INTO demo_distributed t +USING (SELECT id2+1 as key, id2+3 as key2 FROM demo_source_table) s +ON t.id1 = s.key2 ANd t.id1 = s.key +WHEN NOT MATCHED THEN + INSERT VALUES(s.key2, 333); +MERGE INTO demo_distributed t +USING (SELECT id2+1 as key, id2+2 as key2 FROM demo_source_table) s +ON t.id1 = s.key2 AND t.id1 = s.key +WHEN NOT MATCHED THEN + DO NOTHING; +MERGE INTO demo_distributed t +USING (SELECT id2+1 as key, id2+3 as key2 FROM demo_source_table) s +ON t.val1 = s.key2 AND t.id1 = s.key AND t.id1 = s.key2 +WHEN NOT MATCHED THEN + INSERT VALUES(s.key2, 444); +-- Test aggregate functions in source-query +SELECT COUNT(*) FROM demo_distributed where val1 = 150; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT COUNT(*) FROM demo_distributed where id1 = 2; + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- One row with Key=7 updated in demo_distributed to 150 +MERGE INTO demo_distributed t +USING (SELECT count(DISTINCT id2)::int4 as key FROM demo_source_table GROUP BY val2) s +ON t.id1 = s.key +WHEN NOT MATCHED THEN INSERT VALUES(s.key, 1) +WHEN MATCHED THEN UPDATE SET val1 = 150; +-- Seven rows with Key=2 inserted in demo_distributed +MERGE INTO demo_distributed t +USING (SELECT (count(DISTINCT val2) + 1)::int4 as key FROM demo_source_table GROUP BY id2) s +ON t.id1 = s.key +WHEN NOT MATCHED THEN INSERT VALUES(s.key, 1) +WHEN MATCHED THEN UPDATE SET val1 = 150; +SELECT COUNT(*) FROM demo_distributed where val1 = 150; + count +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT COUNT(*) FROM demo_distributed where id1 = 2; + count +--------------------------------------------------------------------- + 7 +(1 row) + +-- +-- Test FALSE filters +-- +CREATE TABLE source_filter(order_id INT, customer_id INT, order_center VARCHAR, order_time timestamp); +CREATE TABLE target_filter(customer_id INT, last_order_id INT, order_center VARCHAR, order_count INT, last_order timestamp); +SELECT create_distributed_table('source_filter', 'customer_id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('target_filter', 'customer_id', colocate_with => 'source_filter'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE FUNCTION load_filter() RETURNS VOID AS $$ + +TRUNCATE target_filter; +TRUNCATE source_filter; + +INSERT INTO target_filter VALUES(100, 11, 'trg', -1, '2022-01-01 00:00:00'); -- Match UPDATE +INSERT INTO target_filter VALUES(200, 11, 'trg', -1, '2022-01-01 00:00:00'); -- Match DELETE + +INSERT INTO source_filter VALUES(12, 100, 'src', '2022-01-01 00:00:00'); +INSERT INTO source_filter VALUES(12, 200, 'src', '2022-01-01 00:00:00'); +INSERT INTO source_filter VALUES(12, 300, 'src', '2022-01-01 00:00:00'); + +$$ +LANGUAGE SQL; +--WHEN MATCH and FALSE +SELECT load_filter(); + load_filter +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO target_filter t +USING source_filter s +ON s.customer_id = t.customer_id +WHEN MATCHED AND t.customer_id = 100 AND (FALSE) THEN + UPDATE SET order_count = 999 +WHEN MATCHED AND t.customer_id = 200 THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.customer_id, s.order_id, s.order_center, 1, s.order_time); +SELECT * FROM target_filter ORDER BY 1, 2; + customer_id | last_order_id | order_center | order_count | last_order +--------------------------------------------------------------------- + 100 | 11 | trg | -1 | Sat Jan 01 00:00:00 2022 + 300 | 12 | src | 1 | Sat Jan 01 00:00:00 2022 +(2 rows) + +--WHEN NOT MATCH and 1=0 +SELECT load_filter(); + load_filter +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO target_filter t +USING source_filter s +ON s.customer_id = t.customer_id +WHEN MATCHED AND t.customer_id = 100 THEN + UPDATE SET order_count = 999 +WHEN MATCHED AND t.customer_id = 200 THEN + DELETE +WHEN NOT MATCHED AND (1=0) THEN + INSERT VALUES(s.customer_id, s.order_id, s.order_center, 1, s.order_time); +SELECT * FROM target_filter ORDER BY 1, 2; + customer_id | last_order_id | order_center | order_count | last_order +--------------------------------------------------------------------- + 100 | 11 | trg | 999 | Sat Jan 01 00:00:00 2022 +(1 row) + +--ON t.key = s.key AND 1 < 0 +SELECT load_filter(); + load_filter +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO target_filter t +USING source_filter s +ON s.customer_id = t.customer_id AND 1 < 0 +WHEN MATCHED AND t.customer_id = 100 THEN + UPDATE SET order_count = 999 +WHEN MATCHED AND t.customer_id = 200 THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.customer_id, s.order_id, s.order_center, 1, s.order_time); +ERROR: The required join operation is missing between the target's distribution column and any expression originating from the source. The issue may arise from either a non-equi-join or a mismatch in the datatypes of the columns being joined. +DETAIL: Without a equi-join condition on the target's distribution column, the source rows cannot be efficiently redistributed, and the NOT-MATCHED condition cannot be evaluated unambiguously. This can result in incorrect or unexpected results when attempting to merge tables in a distributed setting +SELECT * FROM target_filter ORDER BY 1, 2; + customer_id | last_order_id | order_center | order_count | last_order +--------------------------------------------------------------------- + 100 | 11 | trg | -1 | Sat Jan 01 00:00:00 2022 + 200 | 11 | trg | -1 | Sat Jan 01 00:00:00 2022 +(2 rows) + +--(SELECT * FROM source_filter WHERE false) as source_filter +SELECT load_filter(); + load_filter +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO target_filter t +USING (SELECT * FROM source_filter WHERE false) s +ON s.customer_id = t.customer_id +WHEN MATCHED AND t.customer_id = 100 THEN + UPDATE SET order_count = 999 +WHEN MATCHED AND t.customer_id = 200 THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.customer_id, s.order_id, s.order_center, 1, s.order_time); +SELECT * FROM target_filter ORDER BY 1, 2; + customer_id | last_order_id | order_center | order_count | last_order +--------------------------------------------------------------------- + 100 | 11 | trg | -1 | Sat Jan 01 00:00:00 2022 + 200 | 11 | trg | -1 | Sat Jan 01 00:00:00 2022 +(2 rows) + +-- Bug 6785 +CREATE TABLE source_6785( id integer, z int, d jsonb); +CREATE TABLE target_6785( id integer, z int, d jsonb); +SELECT create_distributed_table('target_6785','id'), create_distributed_table('source_6785', 'id'); + create_distributed_table | create_distributed_table +--------------------------------------------------------------------- + | +(1 row) + +INSERT INTO source_6785 SELECT i,i FROM generate_series(0,5)i; +SET client_min_messages TO DEBUG1; +MERGE INTO target_6785 sda +USING (SELECT * FROM source_6785 WHERE id = 1) sdn +ON sda.id = sdn.id AND sda.id = 2 +WHEN NOT matched THEN + INSERT (id, z) VALUES (sdn.id, 5); +DEBUG: Target relation has a filter of the form: false (AND ..), which results in empty shards, but we still need to evaluate NOT-MATCHED clause, try repartitioning +DEBUG: Routing query is not possible with no shards for target +DEBUG: Creating MERGE repartition plan +DEBUG: Using column - index:0 from the source list to redistribute +DEBUG: Collect source query results on coordinator +DEBUG: Create a MERGE task list that needs to be routed +DEBUG: +DEBUG: +DEBUG: +DEBUG: +DEBUG: Execute MERGE task list +RESET client_min_messages; +SELECT * FROM target_6785 ORDER BY 1; + id | z | d +--------------------------------------------------------------------- + 1 | 5 | +(1 row) + -- -- Error and Unsupported scenarios -- +-- Test explain analyze with repartition +EXPLAIN ANALYZE +MERGE INTO demo_distributed t +USING (SELECT 999 as s3, demo_source_table.* FROM (SELECT * FROM demo_source_table ORDER BY 1 LIMIT 3) as foo LEFT JOIN demo_source_table USING(id2)) AS s +ON t.id1 = s.id2 +WHEN MATCHED THEN + UPDATE SET val1 = s3 +WHEN NOT MATCHED THEN + INSERT VALUES(id2, s3); +ERROR: EXPLAIN ANALYZE is currently not supported for MERGE INTO ... commands with repartitioning +-- Source without a table +MERGE INTO target_cj t +USING (VALUES (1, 1), (2, 1), (3, 3)) as s (sid, val) +ON t.tid = s.sid AND t.tid = 2 +WHEN MATCHED THEN + UPDATE SET val = s.val +WHEN NOT MATCHED THEN + DO NOTHING; +ERROR: To MERGE into a distributed table, source must be Citus table(s) +-- Incomplete source +MERGE INTO target_cj t +USING (source_cj1 s1 INNER JOIN source_cj2 s2 ON sid1 = val2) s +ON t.tid = s.sid1 AND t.tid = 2 +WHEN MATCHED THEN + UPDATE SET src = src2 +WHEN NOT MATCHED THEN + DO NOTHING; +ERROR: Source is not an explicit query +HINT: Source query is a Join expression, try converting into a query as SELECT * FROM (..Join..) +-- Reference as a target and local as source +MERGE INTO refsource_ref +USING (SELECT * FROM reftarget_local UNION SELECT * FROM reftarget_local) AS foo ON refsource_ref.s1 = foo.t1 +WHEN MATCHED THEN + UPDATE SET s2 = s2 + 100 +WHEN NOT MATCHED THEN + INSERT VALUES(foo.t1); +ERROR: Reference table as target is not allowed in MERGE command MERGE INTO target_set USING source_set AS foo ON target_set.t1 = foo.s1 WHEN MATCHED THEN UPDATE SET ctid = '(0,100)'; ERROR: cannot assign to system column "ctid" -MERGE INTO target_set -USING (SELECT s1,s2 FROM source_set UNION SELECT s2,s1 FROM source_set) AS foo ON target_set.t1 = foo.s1 -WHEN MATCHED THEN - UPDATE SET t2 = t2 + 1; -ERROR: cannot pushdown the subquery since not all subqueries in the UNION have the partition column in the same position -DETAIL: Each leaf query of the UNION should return the partition column in the same position and all joins must be on the partition column -MERGE INTO target_set -USING (SELECT 2 as s3, source_set.* FROM (SELECT * FROM source_set LIMIT 1) as foo LEFT JOIN source_set USING( s1)) AS foo -ON target_set.t1 = foo.s1 -WHEN MATCHED THEN UPDATE SET t2 = t2 + 1 -WHEN NOT MATCHED THEN INSERT VALUES(s1, s3); -ERROR: cannot push down this subquery -DETAIL: Limit clause is currently unsupported when a subquery references a column from another query -- modifying CTE not supported EXPLAIN -WITH cte_1 AS (DELETE FROM target_json) +WITH cte_1 AS (DELETE FROM target_json RETURNING *) MERGE INTO target_json sda -USING source_json sdn +USING cte_1 sdn ON sda.id = sdn.id WHEN NOT matched THEN INSERT (id, z) VALUES (sdn.id, 5); -ERROR: MERGE command is only supported when all distributed tables are co-located and joined on their distribution columns +ERROR: CTEs with modifying actions are not yet supported in MERGE -- Grouping sets not supported MERGE INTO citus_target t USING (SELECT count(*), id FROM citus_source GROUP BY GROUPING SETS (id, val)) subq @@ -2456,8 +3195,8 @@ WHEN NOT MATCHED THEN INSERT VALUES (subq.id, 99) WHEN MATCHED AND t.id < 350 THEN DELETE; -ERROR: cannot push down this subquery -DETAIL: could not run distributed query with GROUPING SETS, CUBE, or ROLLUP +ERROR: could not run distributed query with GROUPING SETS, CUBE, or ROLLUP +HINT: Consider using an equality filter on the distributed table's partition column. WITH subq AS ( SELECT count(*), id FROM citus_source GROUP BY GROUPING SETS (id, val) @@ -2471,8 +3210,8 @@ WHEN NOT MATCHED THEN INSERT VALUES (subq.id, 99) WHEN MATCHED AND t.id < 350 THEN DELETE; -ERROR: cannot push down this subquery -DETAIL: could not run distributed query with GROUPING SETS, CUBE, or ROLLUP +ERROR: could not run distributed query with GROUPING SETS, CUBE, or ROLLUP +HINT: Consider using an equality filter on the distributed table's partition column. -- try inserting unmatched distribution column value MERGE INTO citus_target t USING citus_source s @@ -2480,18 +3219,22 @@ ON t.id = s.id WHEN NOT MATCHED THEN INSERT DEFAULT VALUES; ERROR: cannot perform MERGE INSERT with DEFAULTS +DETAIL: Inserting arbitrary values that don't correspond to the joined column values can lead to unpredictable outcomes where rows are incorrectly distributed among different shards MERGE INTO citus_target t USING citus_source s ON t.id = s.id WHEN NOT MATCHED THEN INSERT VALUES(10000); -ERROR: MERGE INSERT must refer a source column for distribution column +ERROR: MERGE INSERT is using unsupported expression type for distribution column +DETAIL: Inserting arbitrary values that don't correspond to the joined column values can lead to unpredictable outcomes where rows are incorrectly distributed among different shards MERGE INTO citus_target t USING citus_source s ON t.id = s.id WHEN NOT MATCHED THEN INSERT (id) VALUES(1000); -ERROR: MERGE INSERT must refer a source column for distribution column +ERROR: MERGE INSERT is using unsupported expression type for distribution column +DETAIL: Inserting arbitrary values that don't correspond to the joined column values can lead to unpredictable outcomes where rows are incorrectly distributed among different shards +-- Colocated merge MERGE INTO t1 t USING s1 s ON t.id = s.id @@ -2504,6 +3247,13 @@ ON t.id = s.id WHEN NOT MATCHED THEN INSERT (val) VALUES(s.val); ERROR: MERGE INSERT must have distribution column as value +-- Non-colocated merge +MERGE INTO t1 t +USING s1 s +ON t.id = s.val +WHEN NOT MATCHED THEN + INSERT (id) VALUES(s.id); +ERROR: MERGE INSERT must use the source's joining column for target's distribution column -- try updating the distribution key column BEGIN; MERGE INTO target_cj t @@ -2573,7 +3323,7 @@ MERGE INTO t1 UPDATE SET val = t1.val + 1 WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s1.id, s1.val); -ERROR: MERGE command is not supported with combination of distributed/local tables yet +ERROR: MERGE involving repartition of rows is supported only if the target is distributed -- Now both s1 and t1 are distributed tables SELECT undistribute_table('t1'); NOTICE: creating a new table for merge_schema.t1 @@ -2639,17 +3389,7 @@ WHEN MATCHED AND (merge_when_and_write()) THEN UPDATE SET val = t1.val + s1.val; ERROR: non-IMMUTABLE functions are not yet supported in MERGE sql with distributed tables ROLLBACK; --- Joining on partition columns with sub-query -MERGE INTO t1 - USING (SELECT * FROM s1) sub ON (sub.val = t1.id) -- sub.val is not a distribution column - WHEN MATCHED AND sub.val = 0 THEN - DELETE - WHEN MATCHED THEN - UPDATE SET val = t1.val + 1 - WHEN NOT MATCHED THEN - INSERT (id, val) VALUES (sub.id, sub.val); -ERROR: MERGE command is only supported when all distributed tables are co-located and joined on their distribution columns --- Joining on partition columns with CTE +-- Joining on non-partition columns with CTE source, but INSERT incorrect column WITH s1_res AS ( SELECT * FROM s1 ) @@ -2661,7 +3401,7 @@ MERGE INTO t1 UPDATE SET val = t1.val + 1 WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s1_res.id, s1_res.val); -ERROR: MERGE command is only supported when all distributed tables are co-located and joined on their distribution columns +ERROR: MERGE INSERT must use the source's joining column for target's distribution column -- Constant Join condition WITH s1_res AS ( SELECT * FROM s1 @@ -2674,8 +3414,9 @@ MERGE INTO t1 UPDATE SET val = t1.val + 1 WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s1_res.id, s1_res.val); -ERROR: MERGE command is only supported when all distributed tables are co-located and joined on their distribution columns --- With a single WHEN clause, which causes a non-left join +ERROR: The required join operation is missing between the target's distribution column and any expression originating from the source. The issue may arise from either a non-equi-join or a mismatch in the datatypes of the columns being joined. +DETAIL: Without a equi-join condition on the target's distribution column, the source rows cannot be efficiently redistributed, and the NOT-MATCHED condition cannot be evaluated unambiguously. This can result in incorrect or unexpected results when attempting to merge tables in a distributed setting +-- Join condition without target distribution column WITH s1_res AS ( SELECT * FROM s1 ) @@ -2683,7 +3424,8 @@ WITH s1_res AS ( WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s1_res.id, s1_res.val); -ERROR: MERGE command is only supported when all distributed tables are co-located and joined on their distribution columns +ERROR: The required join operation is missing between the target's distribution column and any expression originating from the source. The issue may arise from either a non-equi-join or a mismatch in the datatypes of the columns being joined. +DETAIL: Without a equi-join condition on the target's distribution column, the source rows cannot be efficiently redistributed, and the NOT-MATCHED condition cannot be evaluated unambiguously. This can result in incorrect or unexpected results when attempting to merge tables in a distributed setting -- -- Reference tables -- @@ -2735,7 +3477,7 @@ MERGE INTO t1 UPDATE SET val = t1.val + 1 WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s1.id, s1.val); -ERROR: MERGE command is not supported on reference tables yet +ERROR: Reference table as target is not allowed in MERGE command -- -- Postgres + Citus-Distributed table -- @@ -2777,7 +3519,7 @@ MERGE INTO t1 UPDATE SET val = t1.val + 1 WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s1.id, s1.val); -ERROR: MERGE command is not supported with combination of distributed/local tables yet +ERROR: MERGE INTO an distributed table from Postgres table is not yet supported MERGE INTO t1 USING (SELECT * FROM s1) sub ON (sub.id = t1.id) WHEN MATCHED AND sub.val = 0 THEN @@ -2786,7 +3528,7 @@ MERGE INTO t1 UPDATE SET val = t1.val + 1 WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val); -ERROR: MERGE command is not supported with combination of distributed/local tables yet +ERROR: MERGE INTO an distributed table from Postgres table is not yet supported CREATE TABLE pg(val int); SELECT create_distributed_table('s1', 'id'); NOTICE: Copying data from local table... @@ -2807,7 +3549,7 @@ MERGE INTO t1 UPDATE SET val = t1.val + 1 WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val); -ERROR: MERGE command is not supported with combination of distributed/local tables yet +ERROR: MERGE INTO an distributed table from Postgres table is not yet supported -- Mix Postgres table in CTE WITH pg_res AS ( SELECT * FROM pg @@ -2820,7 +3562,7 @@ MERGE INTO t1 UPDATE SET val = t1.val + 1 WHEN NOT MATCHED THEN INSERT (id, val) VALUES (sub.id, sub.val); -ERROR: MERGE command is not supported with combination of distributed/local tables yet +ERROR: MERGE INTO an distributed table from Postgres table is not yet supported -- Match more than one source row should fail same as Postgres behavior SELECT undistribute_table('t1'); NOTICE: creating a new table for merge_schema.t1 @@ -2875,7 +3617,7 @@ WHEN NOT MATCHED THEN INSERT VALUES(mv_source.id, mv_source.val); ERROR: cannot execute MERGE on relation "mv_source" DETAIL: This operation is not supported for materialized views. --- Distributed tables *must* be colocated +-- Do not allow constant values into the distribution column CREATE TABLE dist_target(id int, val varchar); SELECT create_distributed_table('dist_target', 'id'); create_distributed_table @@ -2890,31 +3632,6 @@ SELECT create_distributed_table('dist_source', 'id', colocate_with => 'none'); (1 row) -MERGE INTO dist_target -USING dist_source -ON dist_target.id = dist_source.id -WHEN MATCHED THEN -UPDATE SET val = dist_source.val -WHEN NOT MATCHED THEN -INSERT VALUES(dist_source.id, dist_source.val); -ERROR: For MERGE command, all the distributed tables must be colocated --- Distributed tables *must* be joined on distribution column -CREATE TABLE dist_colocated(id int, val int); -SELECT create_distributed_table('dist_colocated', 'id', colocate_with => 'dist_target'); - create_distributed_table ---------------------------------------------------------------------- - -(1 row) - -MERGE INTO dist_target -USING dist_colocated -ON dist_target.id = dist_colocated.val -- val is not the distribution column -WHEN MATCHED THEN -UPDATE SET val = dist_colocated.val -WHEN NOT MATCHED THEN -INSERT VALUES(dist_colocated.id, dist_colocated.val); -ERROR: MERGE command is only supported when all distributed tables are co-located and joined on their distribution columns --- Both the source and target must be distributed MERGE INTO dist_target USING (SELECT 100 id) AS source ON dist_target.id = source.id AND dist_target.val = 'const' @@ -2922,7 +3639,7 @@ WHEN MATCHED THEN UPDATE SET val = 'source' WHEN NOT MATCHED THEN INSERT VALUES(source.id, 'source'); -ERROR: For MERGE command, both the source and target must be distributed +ERROR: To MERGE into a distributed table, source must be Citus table(s) -- Non-hash distributed tables (append/range). CREATE VIEW show_tables AS SELECT logicalrelid, partmethod @@ -2961,8 +3678,7 @@ WHEN MATCHED THEN UPDATE SET val = dist_source.val WHEN NOT MATCHED THEN INSERT VALUES(dist_source.id, dist_source.val); -ERROR: For MERGE command, all the distributed tables must be colocated, for append/range distribution, colocation is not supported -HINT: Consider using hash distribution instead +ERROR: For MERGE command, append/range distribution table is not supported yet SELECT undistribute_table('dist_source'); NOTICE: creating a new table for merge_schema.dist_source NOTICE: moving the data of merge_schema.dist_source @@ -2995,8 +3711,7 @@ WHEN MATCHED THEN UPDATE SET val = dist_source.val WHEN NOT MATCHED THEN INSERT VALUES(dist_source.id, dist_source.val); -ERROR: For MERGE command, all the distributed tables must be colocated, for append/range distribution, colocation is not supported -HINT: Consider using hash distribution instead +ERROR: For MERGE command, append/range distribution table is not supported yet -- Both are append tables SELECT undistribute_table('dist_target'); NOTICE: creating a new table for merge_schema.dist_target @@ -3048,8 +3763,7 @@ WHEN MATCHED THEN UPDATE SET val = dist_source.val WHEN NOT MATCHED THEN INSERT VALUES(dist_source.id, dist_source.val); -ERROR: For MERGE command, all the distributed tables must be colocated, for append/range distribution, colocation is not supported -HINT: Consider using hash distribution instead +ERROR: For MERGE command, append/range distribution table is not supported yet -- Both are range tables SELECT undistribute_table('dist_target'); NOTICE: creating a new table for merge_schema.dist_target @@ -3101,8 +3815,328 @@ WHEN MATCHED THEN UPDATE SET val = dist_source.val WHEN NOT MATCHED THEN INSERT VALUES(dist_source.id, dist_source.val); -ERROR: For MERGE command, all the distributed tables must be colocated, for append/range distribution, colocation is not supported -HINT: Consider using hash distribution instead +ERROR: For MERGE command, append/range distribution table is not supported yet +-- test merge with single-shard tables +CREATE SCHEMA query_single_shard_table; +SET search_path TO query_single_shard_table; +CREATE TABLE nullkey_c1_t1(a int, b int); +CREATE TABLE nullkey_c1_t2(a int, b int); +SELECT create_distributed_table('nullkey_c1_t1', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('nullkey_c1_t2', null, colocate_with=>'nullkey_c1_t1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE nullkey_c2_t1(a int, b int); +CREATE TABLE nullkey_c2_t2(a int, b int); +SELECT create_distributed_table('nullkey_c2_t1', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('nullkey_c2_t2', null, colocate_with=>'nullkey_c2_t1', distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE reference_table(a int, b int); +CREATE TABLE distributed_table(a int, b int); +CREATE TABLE citus_local_table(a int, b int); +SELECT create_reference_table('reference_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('distributed_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_add_local_table_to_metadata('citus_local_table'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +SET client_min_messages TO DEBUG2; +INSERT INTO reference_table SELECT i, i FROM generate_series(0, 5) i; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO distributed_table SELECT i, i FROM generate_series(3, 8) i; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO citus_local_table SELECT i, i FROM generate_series(0, 10) i; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +CREATE TABLE postgres_local_table(a int, b int); +INSERT INTO postgres_local_table SELECT i, i FROM generate_series(5, 10) i; +-- with a colocated table +MERGE INTO nullkey_c1_t1 USING nullkey_c1_t2 ON (nullkey_c1_t1.a = nullkey_c1_t2.a) +WHEN MATCHED THEN UPDATE SET b = nullkey_c1_t2.b; +DEBUG: +DEBUG: Creating MERGE router plan +MERGE INTO nullkey_c1_t1 USING nullkey_c1_t2 ON (nullkey_c1_t1.a = nullkey_c1_t2.a) +WHEN MATCHED THEN DELETE; +DEBUG: +DEBUG: Creating MERGE router plan +MERGE INTO nullkey_c1_t1 USING nullkey_c1_t2 ON (nullkey_c1_t1.a = nullkey_c1_t2.a) +WHEN MATCHED THEN UPDATE SET b = nullkey_c1_t2.b +WHEN NOT MATCHED THEN INSERT VALUES (nullkey_c1_t2.a, nullkey_c1_t2.b); +DEBUG: +DEBUG: Creating MERGE router plan +MERGE INTO nullkey_c1_t1 USING nullkey_c1_t2 ON (nullkey_c1_t1.a = nullkey_c1_t2.a) +WHEN MATCHED THEN DELETE +WHEN NOT MATCHED THEN INSERT VALUES (nullkey_c1_t2.a, nullkey_c1_t2.b); +DEBUG: +DEBUG: Creating MERGE router plan +-- with non-colocated single-shard table +MERGE INTO nullkey_c1_t1 USING nullkey_c2_t1 ON (nullkey_c1_t1.a = nullkey_c2_t1.a) +WHEN MATCHED THEN UPDATE SET b = nullkey_c2_t1.b; +DEBUG: Distributed tables are not co-located, try repartitioning +DEBUG: For MERGE command, all the distributed tables must be colocated +DEBUG: Creating MERGE repartition plan +ERROR: MERGE operation on non-colocated distributed table(s) without a shard key is not yet supported +MERGE INTO nullkey_c1_t1 USING nullkey_c2_t1 ON (nullkey_c1_t1.a = nullkey_c2_t1.a) +WHEN MATCHED THEN UPDATE SET b = nullkey_c2_t1.b +WHEN NOT MATCHED THEN INSERT VALUES (nullkey_c2_t1.a, nullkey_c2_t1.b); +DEBUG: Distributed tables are not co-located, try repartitioning +DEBUG: For MERGE command, all the distributed tables must be colocated +DEBUG: Creating MERGE repartition plan +ERROR: MERGE operation on non-colocated distributed table(s) without a shard key is not yet supported +-- with a distributed table +MERGE INTO nullkey_c1_t1 USING distributed_table ON (nullkey_c1_t1.a = distributed_table.a) +WHEN MATCHED THEN UPDATE SET b = distributed_table.b +WHEN NOT MATCHED THEN INSERT VALUES (distributed_table.a, distributed_table.b); +DEBUG: Distributed tables are not co-located, try repartitioning +DEBUG: For MERGE command, all the distributed tables must be colocated +DEBUG: Creating MERGE repartition plan +ERROR: MERGE operation on non-colocated distributed table(s) without a shard key is not yet supported +MERGE INTO distributed_table USING nullkey_c1_t1 ON (nullkey_c1_t1.a = distributed_table.a) +WHEN MATCHED THEN DELETE +WHEN NOT MATCHED THEN INSERT VALUES (nullkey_c1_t1.a, nullkey_c1_t1.b); +DEBUG: Distributed tables are not co-located, try repartitioning +DEBUG: For MERGE command, all the distributed tables must be colocated +DEBUG: Creating MERGE repartition plan +DEBUG: Using column - index:0 from the source list to redistribute +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Collect source query results on coordinator +DEBUG: Create a MERGE task list that needs to be routed +DEBUG: +DEBUG: distributed statement: MERGE INTO query_single_shard_table.distributed_table_4000189 citus_table_alias USING (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('merge_into_XXX_4000189'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) nullkey_c1_t1 ON (nullkey_c1_t1.a OPERATOR(pg_catalog.=) citus_table_alias.a) WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (a, b) VALUES (nullkey_c1_t1.a, nullkey_c1_t1.b) +DEBUG: +DEBUG: distributed statement: MERGE INTO query_single_shard_table.distributed_table_4000190 citus_table_alias USING (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('merge_into_XXX_4000190'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) nullkey_c1_t1 ON (nullkey_c1_t1.a OPERATOR(pg_catalog.=) citus_table_alias.a) WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (a, b) VALUES (nullkey_c1_t1.a, nullkey_c1_t1.b) +DEBUG: +DEBUG: distributed statement: MERGE INTO query_single_shard_table.distributed_table_4000191 citus_table_alias USING (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('merge_into_XXX_4000191'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) nullkey_c1_t1 ON (nullkey_c1_t1.a OPERATOR(pg_catalog.=) citus_table_alias.a) WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (a, b) VALUES (nullkey_c1_t1.a, nullkey_c1_t1.b) +DEBUG: +DEBUG: distributed statement: MERGE INTO query_single_shard_table.distributed_table_4000192 citus_table_alias USING (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('merge_into_XXX_4000192'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) nullkey_c1_t1 ON (nullkey_c1_t1.a OPERATOR(pg_catalog.=) citus_table_alias.a) WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (a, b) VALUES (nullkey_c1_t1.a, nullkey_c1_t1.b) +-- with a reference table +MERGE INTO nullkey_c1_t1 USING reference_table ON (nullkey_c1_t1.a = reference_table.a) +WHEN MATCHED THEN UPDATE SET b = reference_table.b; +DEBUG: A mix of distributed and reference table, try repartitioning +DEBUG: A mix of distributed and reference table, routable query is not possible +DEBUG: Creating MERGE repartition plan +ERROR: MERGE operation on non-colocated distributed table(s) without a shard key is not yet supported +MERGE INTO reference_table USING nullkey_c1_t1 ON (nullkey_c1_t1.a = reference_table.a) +WHEN MATCHED THEN UPDATE SET b = nullkey_c1_t1.b +WHEN NOT MATCHED THEN INSERT VALUES (nullkey_c1_t1.a, nullkey_c1_t1.b); +ERROR: Reference table as target is not allowed in MERGE command +-- with a citus local table +MERGE INTO nullkey_c1_t1 USING citus_local_table ON (nullkey_c1_t1.a = citus_local_table.a) +WHEN MATCHED THEN UPDATE SET b = citus_local_table.b; +DEBUG: A mix of distributed and local table, try repartitioning +DEBUG: A mix of distributed and citus-local table, routable query is not possible +DEBUG: Creating MERGE repartition plan +ERROR: MERGE operation on non-colocated distributed table(s) without a shard key is not yet supported +MERGE INTO citus_local_table USING nullkey_c1_t1 ON (nullkey_c1_t1.a = citus_local_table.a) +WHEN MATCHED THEN DELETE; +DEBUG: A mix of distributed and local table, try repartitioning +DEBUG: A mix of distributed and citus-local table, routable query is not possible +DEBUG: Creating MERGE repartition plan +ERROR: MERGE involving repartition of rows is supported only if the target is distributed +-- with a postgres table +MERGE INTO nullkey_c1_t1 USING postgres_local_table ON (nullkey_c1_t1.a = postgres_local_table.a) +WHEN MATCHED THEN UPDATE SET b = postgres_local_table.b; +DEBUG: There is only one distributed table, merge is not pushable, try repartitioning +DEBUG: Creating MERGE repartition plan +ERROR: MERGE INTO an distributed table from Postgres table is not yet supported +MERGE INTO postgres_local_table USING nullkey_c1_t1 ON (nullkey_c1_t1.a = postgres_local_table.a) +WHEN MATCHED THEN UPDATE SET b = nullkey_c1_t1.b +WHEN NOT MATCHED THEN INSERT VALUES (nullkey_c1_t1.a, nullkey_c1_t1.b); +DEBUG: There is only one distributed table, merge is not pushable, try repartitioning +DEBUG: Creating MERGE repartition plan +ERROR: MERGE involving repartition of rows is supported only if the target is distributed +-- using ctes +WITH cte AS ( + SELECT * FROM nullkey_c1_t1 +) +MERGE INTO nullkey_c1_t1 USING cte ON (nullkey_c1_t1.a = cte.a) +WHEN MATCHED THEN UPDATE SET b = cte.b; +DEBUG: +DEBUG: Creating MERGE router plan +WITH cte AS ( + SELECT * FROM distributed_table +) +MERGE INTO nullkey_c1_t1 USING cte ON (nullkey_c1_t1.a = cte.a) +WHEN MATCHED THEN UPDATE SET b = cte.b; +DEBUG: Distributed tables are not co-located, try repartitioning +DEBUG: For MERGE command, all the distributed tables must be colocated +DEBUG: Creating MERGE repartition plan +ERROR: MERGE operation on non-colocated distributed table(s) without a shard key is not yet supported +WITH cte AS materialized ( + SELECT * FROM distributed_table +) +MERGE INTO nullkey_c1_t1 USING cte ON (nullkey_c1_t1.a = cte.a) +WHEN MATCHED THEN UPDATE SET b = cte.b; +DEBUG: Distributed tables are not co-located, try repartitioning +DEBUG: For MERGE command, all the distributed tables must be colocated +DEBUG: Creating MERGE repartition plan +ERROR: MERGE operation on non-colocated distributed table(s) without a shard key is not yet supported +SET client_min_messages TO WARNING; +DROP SCHEMA query_single_shard_table CASCADE; +SET search_path TO merge_schema; +-- Test Columnar table +CREATE TABLE target_columnar(cid int, name text) USING columnar; +SELECT create_distributed_table('target_columnar', 'cid'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO target_columnar t +USING demo_source_table s +ON t.cid = s.id2 +WHEN MATCHED THEN + UPDATE SET name = 'Columnar table updated by MERGE' +WHEN NOT MATCHED THEN + DO NOTHING; +ERROR: Columnar table as target is not allowed in MERGE command +MERGE INTO demo_distributed t +USING generate_series(0,100) as source(key) +ON (source.key + 1 = t.id1) + WHEN MATCHED THEN UPDATE SET val1 = 15; +ERROR: Currently, Citus only supports table, subquery, and CTEs as valid sources for the MERGE operation +-- This should fail in planning stage itself +EXPLAIN MERGE INTO demo_distributed t +USING demo_source_table s +ON (s.id2 + 1 = t.id1) + WHEN MATCHED THEN UPDATE SET val1 = 15; +ERROR: The required join operation is missing between the target's distribution column and any expression originating from the source. The issue may arise from either a non-equi-join or a mismatch in the datatypes of the columns being joined. +DETAIL: Without a equi-join condition on the target's distribution column, the source rows cannot be efficiently redistributed, and the NOT-MATCHED condition cannot be evaluated unambiguously. This can result in incorrect or unexpected results when attempting to merge tables in a distributed setting +-- Sub-queries and CTEs are not allowed in actions and ON clause +CREATE TABLE target_1 (a int, b int, c int); +SELECT create_distributed_table('target_1', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE source_2 (a int, b int, c int); +SELECT create_distributed_table('source_2', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO target_1 VALUES(1, 2, 3); +INSERT INTO target_1 VALUES(4, 5, 6); +INSERT INTO target_1 VALUES(11, 12, 13); +INSERT INTO source_2 VALUES(1, 2, 3); +WITH cte_1 as (SELECT max(a) as max_a, max(b) as b FROM source_2) +MERGE INTO target_1 +USING cte_1 +ON (target_1.a = cte_1.b) +WHEN NOT MATCHED AND (SELECT max_a > 10 FROM cte_1) THEN + INSERT VALUES (cte_1.b, 100); +ERROR: Sub-queries and CTEs are not allowed in actions for MERGE with repartitioning +HINT: Consider making the source and target colocated and joined on the distribution column to make it a routable query +WITH cte_1 as (SELECT a, b FROM source_2) +MERGE INTO target_1 +USING cte_1 +ON (target_1.a = cte_1.b) +WHEN NOT MATCHED AND (SELECT a > 10 FROM cte_1) THEN + INSERT VALUES (cte_1.b, 100); +ERROR: Sub-queries and CTEs are not allowed in actions for MERGE with repartitioning +HINT: Consider making the source and target colocated and joined on the distribution column to make it a routable query +MERGE INTO target_1 +USING source_2 +ON (target_1.a = source_2.b) +WHEN NOT MATCHED AND (SELECT max_a > 10 FROM (SELECT max(a) as max_a, max(b) as b FROM target_1) as foo) THEN + INSERT VALUES (source_2.b, 100); +ERROR: Sub-queries and CTEs are not allowed in actions for MERGE with repartitioning +HINT: Consider making the source and target colocated and joined on the distribution column to make it a routable query +-- or same with CTEs +WITH cte_1 as (SELECT max(a) as max_a, max(b) as b FROM target_1) +MERGE INTO target_1 +USING source_2 +ON (target_1.a = source_2.b) +WHEN NOT MATCHED AND (SELECT max_a > 10 FROM (SELECT max(a) as max_a, max(b) as b FROM target_1) as foo) THEN + INSERT VALUES (source_2.b, 100); +ERROR: Sub-queries and CTEs are not allowed in actions for MERGE with repartitioning +HINT: Consider making the source and target colocated and joined on the distribution column to make it a routable query +WITH cte_1 as (SELECT a, b FROM target_1), cte_2 as (select b,a from target_1) +MERGE INTO target_1 +USING (SELECT * FROM source_2) as subq +ON (target_1.a = subq.b) +WHEN NOT MATCHED AND (SELECT a > 10 FROM cte_2) THEN + INSERT VALUES (subq.b, 100); +ERROR: Sub-queries and CTEs are not allowed in actions for MERGE with repartitioning +HINT: Consider making the source and target colocated and joined on the distribution column to make it a routable query +MERGE INTO source_2 +USING target_1 +ON (target_1.a = source_2.a) +WHEN MATCHED THEN + UPDATE SET b = (SELECT max(a) FROM source_2); +ERROR: Sub-queries and CTEs are not allowed in actions for MERGE with repartitioning +HINT: Consider making the source and target colocated and joined on the distribution column to make it a routable query +MERGE INTO source_2 +USING target_1 +ON (target_1.a = source_2.a) +WHEN NOT MATCHED THEN + INSERT VALUES (target_1.a,(select max(a) from target_1)); +ERROR: Sub-queries and CTEs are not allowed in actions for MERGE with repartitioning +HINT: Consider making the source and target colocated and joined on the distribution column to make it a routable query +MERGE INTO target_1 +USING source_2 +ON (target_1.a = source_2.b) +WHEN NOT MATCHED AND (SELECT max(c) > 10 FROM source_2) THEN + INSERT VALUES (source_2.b, 100); +ERROR: Sub-queries and CTEs are not allowed in actions for MERGE with repartitioning +HINT: Consider making the source and target colocated and joined on the distribution column to make it a routable query +-- Test in ON clause +MERGE INTO target_1 t2 +USING (SELECT * FROM source_2) AS t1 +ON (t1.a = t2.a AND (SELECT 1=1 FROM target_1)) +WHEN MATCHED THEN + DELETE; +ERROR: Sub-queries and CTEs are not allowed in ON clause for MERGE with repartitioning +HINT: Consider making the source and target colocated and joined on the distribution column to make it a routable query +MERGE INTO target_1 t2 +USING (SELECT * FROM source_2) AS t1 +ON (t1.a = t2.a AND (SELECT max(a) > 55 FROM target_1)) +WHEN MATCHED THEN + DELETE; +ERROR: Sub-queries and CTEs are not allowed in ON clause for MERGE with repartitioning +HINT: Consider making the source and target colocated and joined on the distribution column to make it a routable query +WITH cte_1 as (SELECT a, b FROM target_1), cte_2 as (select b,a from target_1) +MERGE INTO target_1 t2 +USING (SELECT * FROM cte_1) AS t1 +ON (t1.a = t2.a AND (SELECT max(a) > 55 FROM cte_2)) +WHEN MATCHED THEN + DELETE; +ERROR: Sub-queries and CTEs are not allowed in ON clause for MERGE with repartitioning +HINT: Consider making the source and target colocated and joined on the distribution column to make it a routable query +RESET client_min_messages; DROP SERVER foreign_server CASCADE; NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to user mapping for postgres on server foreign_server @@ -3113,9 +4147,8 @@ CONTEXT: SQL statement "SELECT citus_drop_all_shards(v_obj.objid, v_obj.schema_ PL/pgSQL function citus_drop_trigger() line XX at PERFORM DROP FUNCTION merge_when_and_write(); DROP SCHEMA merge_schema CASCADE; -NOTICE: drop cascades to 84 other objects +NOTICE: drop cascades to 103 other objects DETAIL: drop cascades to function insert_data() -drop cascades to table pg_result drop cascades to table local_local drop cascades to table target drop cascades to table source @@ -3188,19 +4221,31 @@ drop cascades to table source_serial drop cascades to table target_serial drop cascades to table target_set drop cascades to table source_set +drop cascades to table refsource_ref +drop cascades to table pg_result +drop cascades to table refsource_ref_4000112 +drop cascades to table pg_ref +drop cascades to table local_ref +drop cascades to table reftarget_local +drop cascades to table dist_reftarget +drop cascades to function setup_demo_data() +drop cascades to function merge_demo_data() +drop cascades to table demo_distributed +drop cascades to table demo_source_table +drop cascades to table pg_demo_result +drop cascades to table dist_demo_result +drop cascades to table source_filter +drop cascades to table target_filter +drop cascades to function load_filter() +drop cascades to table source_6785 +drop cascades to table target_6785 drop cascades to function add_s(integer,integer) drop cascades to table pg -drop cascades to table t1_4000131 -drop cascades to table s1_4000132 +drop cascades to table t1_4000174 +drop cascades to table s1_4000175 drop cascades to table t1 drop cascades to table s1 -drop cascades to table dist_colocated drop cascades to table dist_target drop cascades to table dist_source drop cascades to view show_tables -SELECT 1 FROM master_remove_node('localhost', :master_port); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - +and 3 other objects (see server log for list) diff --git a/src/test/regress/expected/merge_arbitrary.out b/src/test/regress/expected/merge_arbitrary.out index 345ac1410..b55306b44 100644 --- a/src/test/regress/expected/merge_arbitrary.out +++ b/src/test/regress/expected/merge_arbitrary.out @@ -148,3 +148,51 @@ SELECT * FROM t1 order by id; (5 rows) ROLLBACK; +-- Test prepared statements with repartition +PREPARE merge_repartition_pg(int,int,int,int) as + MERGE INTO pg_target target + USING (SELECT id+1+$1 as key, val FROM (SELECT * FROM pg_source UNION SELECT * FROM pg_source WHERE id = $2) as foo) as source + ON (source.key = target.id AND $3 < 10000) + WHEN MATCHED THEN UPDATE SET val = (source.key::int+$4) + WHEN NOT MATCHED THEN INSERT VALUES (source.key, source.val); +PREPARE merge_repartition_citus(int,int,int,int) as + MERGE INTO citus_target target + USING (SELECT id+1+$1 as key, val FROM (SELECT * FROM citus_source UNION SELECT * FROM citus_source WHERE id = $2) as foo) as source + ON (source.key = target.id AND $3 < 10000) + WHEN MATCHED THEN UPDATE SET val = (source.key::int+$4) + WHEN NOT MATCHED THEN INSERT VALUES (source.key, source.val); +EXECUTE merge_repartition_pg(1,1,1,1); +EXECUTE merge_repartition_citus(1,1,1,1); +SET client_min_messages = NOTICE; +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +NOTICE: The average of pg_target.val is equal to citus_target.val + compare_data +--------------------------------------------------------------------- + +(1 row) + +RESET client_min_messages; +EXECUTE merge_repartition_pg(1,100,1,1); +EXECUTE merge_repartition_citus(1,100,1,1); +EXECUTE merge_repartition_pg(2,200,1,1); +EXECUTE merge_repartition_citus(2,200,1,1); +EXECUTE merge_repartition_pg(3,300,1,1); +EXECUTE merge_repartition_citus(3,300,1,1); +EXECUTE merge_repartition_pg(4,400,1,1); +EXECUTE merge_repartition_citus(4,400,1,1); +EXECUTE merge_repartition_pg(5,500,1,1); +EXECUTE merge_repartition_citus(5,500,1,1); +-- Sixth time +EXECUTE merge_repartition_pg(6,600,1,6); +EXECUTE merge_repartition_citus(6,600,1,6); +SET client_min_messages = NOTICE; +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +NOTICE: The average of pg_target.val is equal to citus_target.val + compare_data +--------------------------------------------------------------------- + +(1 row) + +RESET client_min_messages; diff --git a/src/test/regress/expected/merge_arbitrary_create.out b/src/test/regress/expected/merge_arbitrary_create.out index 9b2444f17..aff9ecd97 100644 --- a/src/test/regress/expected/merge_arbitrary_create.out +++ b/src/test/regress/expected/merge_arbitrary_create.out @@ -70,3 +70,77 @@ SELECT citus_add_local_table_to_metadata('s1'); (1 row) +-- Test prepared statements with repartition +CREATE TABLE pg_target(id int, val int); +CREATE TABLE pg_source(id int, val int, const int); +CREATE TABLE citus_target(id int, val int); +CREATE TABLE citus_source(id int, val int, const int); +SELECT citus_add_local_table_to_metadata('pg_target'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_add_local_table_to_metadata('pg_source'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +-- +-- Load same set of data to both Postgres and Citus tables +-- +CREATE OR REPLACE FUNCTION setup_data() RETURNS VOID AS $$ + INSERT INTO pg_source SELECT i, i+1, 1 FROM generate_series(1, 10000) i; + INSERT INTO pg_target SELECT i, 1 FROM generate_series(5001, 10000) i; + INSERT INTO citus_source SELECT i, i+1, 1 FROM generate_series(1, 10000) i; + INSERT INTO citus_target SELECT i, 1 FROM generate_series(5001, 10000) i; +$$ +LANGUAGE SQL; +-- +-- Compares the final target tables, merge-modified data, of both Postgres and Citus tables +-- +CREATE OR REPLACE FUNCTION check_data(table1_name text, column1_name text, table2_name text, column2_name text) +RETURNS VOID AS $$ +DECLARE + table1_avg numeric; + table2_avg numeric; +BEGIN + EXECUTE format('SELECT COALESCE(AVG(%I), 0) FROM %I', column1_name, table1_name) INTO table1_avg; + EXECUTE format('SELECT COALESCE(AVG(%I), 0) FROM %I', column2_name, table2_name) INTO table2_avg; + + IF table1_avg > table2_avg THEN + RAISE EXCEPTION 'The average of %.% is greater than %.%', table1_name, column1_name, table2_name, column2_name; + ELSIF table1_avg < table2_avg THEN + RAISE EXCEPTION 'The average of %.% is less than %.%', table1_name, column1_name, table2_name, column2_name; + ELSE + RAISE NOTICE 'The average of %.% is equal to %.%', table1_name, column1_name, table2_name, column2_name; + END IF; +END; +$$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION compare_data() RETURNS VOID AS $$ + SELECT check_data('pg_target', 'id', 'citus_target', 'id'); + SELECT check_data('pg_target', 'val', 'citus_target', 'val'); +$$ +LANGUAGE SQL; +-- +-- Target and source are distributed, and non-colocated +-- +SELECT setup_data(); + setup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_target', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + diff --git a/src/test/regress/expected/merge_partition_tables.out b/src/test/regress/expected/merge_partition_tables.out new file mode 100644 index 000000000..5ac375817 --- /dev/null +++ b/src/test/regress/expected/merge_partition_tables.out @@ -0,0 +1,230 @@ +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +\else +\q +\endif +-- We create two sets of source and target tables, one set in Postgres and +-- the other in Citus distributed. We run the _exact_ MERGE SQL on both sets +-- and compare the final results of the target tables in Postgres and Citus. +-- The results should match. This process is repeated for various combinations +-- of MERGE SQL. +DROP SCHEMA IF EXISTS merge_partition_tables CASCADE; +NOTICE: schema "merge_partition_tables" does not exist, skipping +CREATE SCHEMA merge_partition_tables; +SET search_path TO merge_partition_tables; +SET citus.shard_count TO 4; +SET citus.next_shard_id TO 7000000; +SET citus.explain_all_tasks TO true; +SET citus.shard_replication_factor TO 1; +SET citus.max_adaptive_executor_pool_size TO 1; +SET client_min_messages = warning; +SELECT 1 FROM master_add_node('localhost', :master_port, groupid => 0); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +RESET client_min_messages; +CREATE TABLE pg_target(id int, val int) PARTITION BY RANGE(id); +CREATE TABLE pg_source(id int, val int, const int) PARTITION BY RANGE(val); +CREATE TABLE citus_target(id int, val int) PARTITION BY RANGE(id); +CREATE TABLE citus_source(id int, val int, const int) PARTITION BY RANGE(val); +SELECT citus_add_local_table_to_metadata('citus_target'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_add_local_table_to_metadata('citus_source'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE part1 PARTITION OF pg_target FOR VALUES FROM (1) TO (2500) WITH (autovacuum_enabled=off); +CREATE TABLE part2 PARTITION OF pg_target FOR VALUES FROM (2501) TO (5000) WITH (autovacuum_enabled=off); +CREATE TABLE part3 PARTITION OF pg_target FOR VALUES FROM (5001) TO (7500) WITH (autovacuum_enabled=off); +CREATE TABLE part4 PARTITION OF pg_target DEFAULT WITH (autovacuum_enabled=off); +CREATE TABLE part5 PARTITION OF citus_target FOR VALUES FROM (1) TO (2500) WITH (autovacuum_enabled=off); +CREATE TABLE part6 PARTITION OF citus_target FOR VALUES FROM (2501) TO (5000) WITH (autovacuum_enabled=off); +CREATE TABLE part7 PARTITION OF citus_target FOR VALUES FROM (5001) TO (7500) WITH (autovacuum_enabled=off); +CREATE TABLE part8 PARTITION OF citus_target DEFAULT WITH (autovacuum_enabled=off); +CREATE TABLE part9 PARTITION OF pg_source FOR VALUES FROM (1) TO (2500) WITH (autovacuum_enabled=off); +CREATE TABLE part10 PARTITION OF pg_source FOR VALUES FROM (2501) TO (5000) WITH (autovacuum_enabled=off); +CREATE TABLE part11 PARTITION OF pg_source FOR VALUES FROM (5001) TO (7500) WITH (autovacuum_enabled=off); +CREATE TABLE part12 PARTITION OF pg_source DEFAULT WITH (autovacuum_enabled=off); +CREATE TABLE part13 PARTITION OF citus_source FOR VALUES FROM (1) TO (2500) WITH (autovacuum_enabled=off); +CREATE TABLE part14 PARTITION OF citus_source FOR VALUES FROM (2501) TO (5000) WITH (autovacuum_enabled=off); +CREATE TABLE part15 PARTITION OF citus_source FOR VALUES FROM (5001) TO (7500) WITH (autovacuum_enabled=off); +CREATE TABLE part16 PARTITION OF citus_source DEFAULT WITH (autovacuum_enabled=off); +CREATE OR REPLACE FUNCTION cleanup_data() RETURNS VOID SET search_path TO merge_partition_tables AS $$ + TRUNCATE pg_target; + TRUNCATE pg_source; + TRUNCATE citus_target; + TRUNCATE citus_source; + SELECT undistribute_table('citus_target'); + SELECT undistribute_table('citus_source'); +$$ +LANGUAGE SQL; +-- +-- Load same set of data to both Postgres and Citus tables +-- +CREATE OR REPLACE FUNCTION setup_data() RETURNS VOID SET search_path TO merge_partition_tables AS $$ + INSERT INTO pg_source SELECT i, i+1, 1 FROM generate_series(1, 10000) i; + INSERT INTO pg_target SELECT i, 1 FROM generate_series(5001, 10000) i; + INSERT INTO citus_source SELECT i, i+1, 1 FROM generate_series(1, 10000) i; + INSERT INTO citus_target SELECT i, 1 FROM generate_series(5001, 10000) i; +$$ +LANGUAGE SQL; +-- +-- Compares the final target tables, merge-modified data, of both Postgres and Citus tables +-- +CREATE OR REPLACE FUNCTION check_data(table1_name text, column1_name text, table2_name text, column2_name text) +RETURNS VOID SET search_path TO merge_partition_tables AS $$ +DECLARE + table1_avg numeric; + table2_avg numeric; +BEGIN + EXECUTE format('SELECT COALESCE(AVG(%I), 0) FROM %I', column1_name, table1_name) INTO table1_avg; + EXECUTE format('SELECT COALESCE(AVG(%I), 0) FROM %I', column2_name, table2_name) INTO table2_avg; + + IF table1_avg > table2_avg THEN + RAISE EXCEPTION 'The average of %.% is greater than %.%', table1_name, column1_name, table2_name, column2_name; + ELSIF table1_avg < table2_avg THEN + RAISE EXCEPTION 'The average of %.% is less than %.%', table1_name, column1_name, table2_name, column2_name; + ELSE + RAISE NOTICE 'The average of %.% is equal to %.%', table1_name, column1_name, table2_name, column2_name; + END IF; +END; +$$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION compare_data() RETURNS VOID SET search_path TO merge_partition_tables AS $$ + SELECT check_data('pg_target', 'id', 'citus_target', 'id'); + SELECT check_data('pg_target', 'val', 'citus_target', 'val'); +$$ +LANGUAGE SQL; +-- Test colocated partition tables +SET client_min_messages = ERROR; +SELECT cleanup_data(); + cleanup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT setup_data(); + setup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_target', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'citus_target'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +RESET client_min_messages; +MERGE INTO pg_target t +USING pg_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +MERGE INTO citus_target t +USING citus_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 1 +NOTICE: The average of pg_target.val is equal to citus_target.val +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 2 + compare_data +--------------------------------------------------------------------- + +(1 row) + +-- Test non-colocated partition tables +SET client_min_messages = ERROR; +SELECT cleanup_data(); + cleanup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT setup_data(); + setup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_target', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +RESET client_min_messages; +MERGE INTO pg_target t +USING pg_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +MERGE INTO citus_target t +USING citus_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 1 +NOTICE: The average of pg_target.val is equal to citus_target.val +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 2 + compare_data +--------------------------------------------------------------------- + +(1 row) + +DROP SCHEMA merge_partition_tables CASCADE; +NOTICE: drop cascades to 8 other objects +DETAIL: drop cascades to table pg_target +drop cascades to table pg_source +drop cascades to function cleanup_data() +drop cascades to function setup_data() +drop cascades to function check_data(text,text,text,text) +drop cascades to function compare_data() +drop cascades to table citus_target +drop cascades to table citus_source diff --git a/src/test/regress/expected/merge_partition_tables_0.out b/src/test/regress/expected/merge_partition_tables_0.out new file mode 100644 index 000000000..a7e3fbf20 --- /dev/null +++ b/src/test/regress/expected/merge_partition_tables_0.out @@ -0,0 +1,6 @@ +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +\else +\q diff --git a/src/test/regress/expected/merge_repartition1.out b/src/test/regress/expected/merge_repartition1.out new file mode 100644 index 000000000..279358e30 --- /dev/null +++ b/src/test/regress/expected/merge_repartition1.out @@ -0,0 +1,1348 @@ +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +\else +\q +\endif +-- We create two sets of source and target tables, one set in Postgres and +-- the other in Citus distributed. We run the _exact_ MERGE SQL on both sets +-- and compare the final results of the target tables in Postgres and Citus. +-- The results should match. This process is repeated for various combinations +-- of MERGE SQL. +DROP SCHEMA IF EXISTS merge_repartition1_schema CASCADE; +NOTICE: schema "merge_repartition1_schema" does not exist, skipping +CREATE SCHEMA merge_repartition1_schema; +SET search_path TO merge_repartition1_schema; +SET citus.shard_count TO 4; +SET citus.next_shard_id TO 5000000; +SET citus.explain_all_tasks TO true; +SET citus.shard_replication_factor TO 1; +SET citus.max_adaptive_executor_pool_size TO 1; +SET client_min_messages = warning; +SELECT 1 FROM master_add_node('localhost', :master_port, groupid => 0); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +RESET client_min_messages; +CREATE TABLE pg_target(id int, val int); +CREATE TABLE pg_source(id int, val int, const int); +CREATE TABLE citus_target(id int, val int); +CREATE TABLE citus_source(id int, val int, const int); +SELECT citus_add_local_table_to_metadata('citus_target'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_add_local_table_to_metadata('citus_source'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +CREATE OR REPLACE FUNCTION cleanup_data() RETURNS VOID SET search_path TO merge_repartition1_schema AS $$ + TRUNCATE pg_target; + TRUNCATE pg_source; + TRUNCATE citus_target; + TRUNCATE citus_source; + SELECT undistribute_table('citus_target'); + SELECT undistribute_table('citus_source'); +$$ +LANGUAGE SQL; +-- +-- Load same set of data to both Postgres and Citus tables +-- +CREATE OR REPLACE FUNCTION setup_data() RETURNS VOID SET search_path TO merge_repartition1_schema AS $$ + INSERT INTO pg_source SELECT i, i+1, 1 FROM generate_series(1, 10000) i; + INSERT INTO pg_target SELECT i, 1 FROM generate_series(5001, 10000) i; + INSERT INTO citus_source SELECT i, i+1, 1 FROM generate_series(1, 10000) i; + INSERT INTO citus_target SELECT i, 1 FROM generate_series(5001, 10000) i; +$$ +LANGUAGE SQL; +-- +-- Compares the final target tables, merge-modified data, of both Postgres and Citus tables +-- +CREATE OR REPLACE FUNCTION check_data(table1_name text, column1_name text, table2_name text, column2_name text) +RETURNS VOID SET search_path TO merge_repartition1_schema AS $$ +DECLARE + table1_avg numeric; + table2_avg numeric; +BEGIN + EXECUTE format('SELECT COALESCE(AVG(%I), 0) FROM %I', column1_name, table1_name) INTO table1_avg; + EXECUTE format('SELECT COALESCE(AVG(%I), 0) FROM %I', column2_name, table2_name) INTO table2_avg; + + IF table1_avg > table2_avg THEN + RAISE EXCEPTION 'The average of %.% is greater than %.%', table1_name, column1_name, table2_name, column2_name; + ELSIF table1_avg < table2_avg THEN + RAISE EXCEPTION 'The average of %.% is less than %.%', table1_name, column1_name, table2_name, column2_name; + ELSE + RAISE NOTICE 'The average of %.% is equal to %.%', table1_name, column1_name, table2_name, column2_name; + END IF; +END; +$$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION compare_data() RETURNS VOID SET search_path TO merge_repartition1_schema AS $$ + SELECT check_data('pg_target', 'id', 'citus_target', 'id'); + SELECT check_data('pg_target', 'val', 'citus_target', 'val'); +$$ +LANGUAGE SQL; +-- +-- Target and source are distributed, and non-colocated +-- +SELECT cleanup_data(); +NOTICE: creating a new table for merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: moving the data of merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: dropping the old merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: renaming the new table to merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: creating a new table for merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: moving the data of merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: dropping the old merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: renaming the new table to merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 + cleanup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT setup_data(); + setup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_target', 'id'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_target$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'none'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_source$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO pg_target t +USING pg_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +MERGE INTO citus_target t +USING citus_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 1 +NOTICE: The average of pg_target.val is equal to citus_target.val +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 2 + compare_data +--------------------------------------------------------------------- + +(1 row) + +-- +-- Target and source are distributed, and colocated but not joined on distribution column +-- +SELECT cleanup_data(); +NOTICE: creating a new table for merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: moving the data of merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: dropping the old merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: renaming the new table to merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: creating a new table for merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: moving the data of merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: dropping the old merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: renaming the new table to merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 + cleanup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT setup_data(); + setup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_target', 'id'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_target$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'citus_target'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_source$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO pg_target t +USING (SELECT * FROM pg_source) subq +ON (subq.val = t.id) +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = subq.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(subq.val, subq.id); +MERGE INTO citus_target t +USING (SELECT * FROM citus_source) subq +ON (subq.val = t.id) +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = subq.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(subq.val, subq.id); +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 1 +NOTICE: The average of pg_target.val is equal to citus_target.val +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 2 + compare_data +--------------------------------------------------------------------- + +(1 row) + +-- +-- Target and source are distributed, colocated, joined on distribution column +-- but with nondistribution values +-- +SELECT cleanup_data(); +NOTICE: creating a new table for merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: moving the data of merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: dropping the old merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: renaming the new table to merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: creating a new table for merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: moving the data of merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: dropping the old merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: renaming the new table to merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 + cleanup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT setup_data(); + setup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_target', 'id'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_target$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'citus_target'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_source$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO pg_target t +USING (SELECT id,const FROM pg_source UNION SELECT const,id FROM pg_source ) AS s +ON t.id = s.id +WHEN MATCHED THEN + UPDATE SET val = s.const + 1 +WHEN NOT MATCHED THEN + INSERT VALUES(id, const); +MERGE INTO citus_target t +USING (SELECT id,const FROM citus_source UNION SELECT const,id FROM citus_source) AS s +ON t.id = s.id +WHEN MATCHED THEN + UPDATE SET val = s.const + 1 +WHEN NOT MATCHED THEN + INSERT VALUES(id, const); +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 1 +NOTICE: The average of pg_target.val is equal to citus_target.val +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 2 + compare_data +--------------------------------------------------------------------- + +(1 row) + +-- +-- Repartition with a predicate on target_table_name rows in ON clause +-- +SELECT cleanup_data(); +NOTICE: creating a new table for merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: moving the data of merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: dropping the old merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: renaming the new table to merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: creating a new table for merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: moving the data of merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: dropping the old merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: renaming the new table to merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 + cleanup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT setup_data(); + setup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_target', 'id'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_target$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'none'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_source$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO pg_target t +USING (SELECT * FROM pg_source WHERE id < 9500) s +ON t.id = s.id AND t.id < 9000 +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +MERGE INTO citus_target t +USING (SELECT * FROM citus_source WHERE id < 9500) s +ON t.id = s.id AND t.id < 9000 +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 1 +NOTICE: The average of pg_target.val is equal to citus_target.val +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 2 + compare_data +--------------------------------------------------------------------- + +(1 row) + +-- +-- Test CTE and non-colocated tables +-- +SELECT cleanup_data(); +NOTICE: creating a new table for merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: moving the data of merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: dropping the old merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: renaming the new table to merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: creating a new table for merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: moving the data of merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: dropping the old merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: renaming the new table to merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 + cleanup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT setup_data(); + setup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_target', 'id'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_target$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'none'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_source$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +WITH cte AS ( + SELECT * FROM pg_source +) +MERGE INTO pg_target t +USING cte s +ON s.id = t.id +WHEN MATCHED AND t.id > 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES (s.id, s.val); +WITH cte AS ( + SELECT * FROM citus_source +) +MERGE INTO citus_target t +USING cte s +ON s.id = t.id +WHEN MATCHED AND t.id > 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES (s.id, s.val); +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 1 +NOTICE: The average of pg_target.val is equal to citus_target.val +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 2 + compare_data +--------------------------------------------------------------------- + +(1 row) + +-- +-- Test nested CTEs +-- +SELECT cleanup_data(); +NOTICE: creating a new table for merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: moving the data of merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: dropping the old merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: renaming the new table to merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: creating a new table for merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: moving the data of merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: dropping the old merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: renaming the new table to merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 + cleanup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT setup_data(); + setup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_target', 'id'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_target$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'none'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_source$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +WITH cte1 AS ( + SELECT * FROM pg_source ORDER BY 1 LIMIT 9000 +), +cte2 AS( + SELECT * FROM cte1 +), +cte3 AS( + SELECT * FROM cte2 +) +MERGE INTO pg_target t +USING cte3 s +ON (s.id=t.id) +WHEN MATCHED AND t.id > 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES (s.id, s.val); +WITH cte1 AS ( + SELECT * FROM citus_source ORDER BY 1 LIMIT 9000 +), +cte2 AS( + SELECT * FROM cte1 +), +cte3 AS( + SELECT * FROM cte2 +) +MERGE INTO citus_target t +USING cte3 s +ON (s.id=t.id) +WHEN MATCHED AND t.id > 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES (s.id, s.val); +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 1 +NOTICE: The average of pg_target.val is equal to citus_target.val +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 2 + compare_data +--------------------------------------------------------------------- + +(1 row) + +-- +-- Target and source are distributed and colocated +-- +SELECT cleanup_data(); +NOTICE: creating a new table for merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: moving the data of merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: dropping the old merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: renaming the new table to merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: creating a new table for merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: moving the data of merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: dropping the old merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: renaming the new table to merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 + cleanup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT setup_data(); + setup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_target', 'id'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_target$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_source', 'id', colocate_with => 'citus_target'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_source$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO pg_target t +USING (SELECT 999 as newval, pg_source.* FROM (SELECT * FROM pg_source ORDER BY 1 LIMIT 6000) as src LEFT JOIN pg_source USING(id)) AS s +ON t.id = s.id +WHEN MATCHED AND t.id <= 5500 THEN + UPDATE SET val = newval +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(id, newval); +MERGE INTO citus_target t +USING (SELECT 999 as newval, citus_source.* FROM (SELECT * FROM citus_source ORDER BY 1 LIMIT 6000) as src LEFT JOIN citus_source USING(id)) AS s +ON t.id = s.id +WHEN MATCHED AND t.id <= 5500 THEN + UPDATE SET val = newval +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(id, newval); +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 1 +NOTICE: The average of pg_target.val is equal to citus_target.val +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 2 + compare_data +--------------------------------------------------------------------- + +(1 row) + +-- +-- Target is distributed and source is reference +-- +SELECT cleanup_data(); +NOTICE: creating a new table for merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: moving the data of merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: dropping the old merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: renaming the new table to merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: creating a new table for merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: moving the data of merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: dropping the old merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: renaming the new table to merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 + cleanup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT setup_data(); + setup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_target', 'id'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_target$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_reference_table('citus_source'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_source$$) + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO pg_target t +USING pg_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +MERGE INTO citus_target t +USING citus_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 1 +NOTICE: The average of pg_target.val is equal to citus_target.val +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 2 + compare_data +--------------------------------------------------------------------- + +(1 row) + +-- +-- Target is distributed and reference as source in a sub-query +-- +SELECT cleanup_data(); +NOTICE: creating a new table for merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: moving the data of merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: dropping the old merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: renaming the new table to merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: creating a new table for merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: moving the data of merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: dropping the old merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: renaming the new table to merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 + cleanup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT setup_data(); + setup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_target', 'id'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_target$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_reference_table('citus_source'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_source$$) + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO pg_target t +USING (SELECT * FROM pg_source UNION SELECT * FROM pg_source) AS s ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + t.val +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +MERGE INTO citus_target t +USING (SELECT * FROM citus_source UNION SELECT * FROM citus_source) AS s ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + t.val +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 1 +NOTICE: The average of pg_target.val is equal to citus_target.val +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 2 + compare_data +--------------------------------------------------------------------- + +(1 row) + +-- +-- Target is distributed and citus-local as source +-- +SELECT cleanup_data(); +NOTICE: creating a new table for merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: moving the data of merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: dropping the old merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: renaming the new table to merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: creating a new table for merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: moving the data of merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: dropping the old merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: renaming the new table to merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 + cleanup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT setup_data(); + setup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_target', 'id'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_target$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_add_local_table_to_metadata('citus_source'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO pg_target t +USING pg_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +MERGE INTO citus_target t +USING citus_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 1 +NOTICE: The average of pg_target.val is equal to citus_target.val +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 2 + compare_data +--------------------------------------------------------------------- + +(1 row) + +-- +-- Target and source distributed and non-colocated. The source query requires evaluation +-- at the coordinator +-- +SELECT cleanup_data(); +NOTICE: creating a new table for merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: moving the data of merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: dropping the old merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: renaming the new table to merge_repartition1_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: creating a new table for merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: moving the data of merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: dropping the old merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: renaming the new table to merge_repartition1_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 + cleanup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT setup_data(); + setup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_target', 'id'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_target$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'none'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_source$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO pg_target t +USING (SELECT 100 AS insval, MAX(const) AS updval, val, MAX(id) AS sid + FROM pg_source + GROUP BY val ORDER BY sid LIMIT 6000) AS s +ON t.id = s.sid +WHEN MATCHED AND t.id <= 5500 THEN + UPDATE SET val = updval + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(sid, insval); +MERGE INTO citus_target t +USING (SELECT 100 AS insval, MAX(const) AS updval, val, MAX(id) AS sid + FROM citus_source + GROUP BY val ORDER BY sid LIMIT 6000) AS s +ON t.id = s.sid +WHEN MATCHED AND t.id <= 5500 THEN + UPDATE SET val = updval + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(sid, insval); +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 1 +NOTICE: The average of pg_target.val is equal to citus_target.val +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 2 + compare_data +--------------------------------------------------------------------- + +(1 row) + +-- Test source-query that requires repartitioning on top of MERGE repartitioning +SET client_min_messages TO WARNING; +SELECT cleanup_data(); + cleanup_data +--------------------------------------------------------------------- + +(1 row) + +RESET client_min_messages; +SELECT setup_data(); + setup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_target', 'id'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_target$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'none'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_source$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO pg_target t +USING (SELECT s1.val FROM pg_source s1 JOIN pg_source s2 USING (val)) AS s +ON t.id = s.val +WHEN MATCHED THEN + UPDATE SET val = t.val + 1; +SET citus.enable_repartition_joins TO true; +MERGE INTO citus_target t +USING (SELECT s1.val FROM citus_source s1 JOIN citus_source s2 USING (val)) AS s +ON t.id = s.val +WHEN MATCHED THEN + UPDATE SET val = t.val + 1; +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 1 +NOTICE: The average of pg_target.val is equal to citus_target.val +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 2 + compare_data +--------------------------------------------------------------------- + +(1 row) + +-- +-- Test columnar as source table +-- +SET client_min_messages TO WARNING; +SELECT cleanup_data(); + cleanup_data +--------------------------------------------------------------------- + +(1 row) + +RESET client_min_messages; +SELECT setup_data(); + setup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_target', 'id'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_target$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'none'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_source$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT alter_table_set_access_method('citus_source', 'columnar'); +NOTICE: creating a new table for merge_repartition1_schema.citus_source +NOTICE: moving the data of merge_repartition1_schema.citus_source +NOTICE: dropping the old merge_repartition1_schema.citus_source +NOTICE: renaming the new table to merge_repartition1_schema.citus_source + alter_table_set_access_method +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO pg_target t +USING pg_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +MERGE INTO citus_target t +USING citus_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 1 +NOTICE: The average of pg_target.val is equal to citus_target.val +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 2 + compare_data +--------------------------------------------------------------------- + +(1 row) + +SELECT alter_table_set_access_method('citus_source', 'heap'); +NOTICE: creating a new table for merge_repartition1_schema.citus_source +NOTICE: moving the data of merge_repartition1_schema.citus_source +NOTICE: dropping the old merge_repartition1_schema.citus_source +NOTICE: renaming the new table to merge_repartition1_schema.citus_source + alter_table_set_access_method +--------------------------------------------------------------------- + +(1 row) + +-- Test CTE/Subquery in merge-actions (works only for router query) +SET client_min_messages TO WARNING; +SELECT cleanup_data(); + cleanup_data +--------------------------------------------------------------------- + +(1 row) + +RESET client_min_messages; +SELECT setup_data(); + setup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_target', 'id'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_target$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'citus_target'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_source$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO pg_target +USING pg_source +ON (pg_target.id = pg_source.id) +WHEN MATCHED AND (SELECT max_a > 5001 FROM (SELECT max(id) as max_a, max(val) as b FROM pg_target WHERE id = pg_source.id) AS foo) THEN + DELETE +WHEN NOT MATCHED AND (SELECT max_a < 5001 FROM (SELECT max(id) as max_a, max(val) as b FROM pg_target WHERE id = pg_source.id) AS foo) THEN + INSERT VALUES (pg_source.id, 100); +MERGE INTO citus_target +USING citus_source +ON (citus_target.id = citus_source.id) +WHEN MATCHED AND (SELECT max_a > 5001 FROM (SELECT max(id) as max_a, max(val) as b FROM citus_target WHERE id = citus_source.id) AS foo) THEN + DELETE +WHEN NOT MATCHED AND (SELECT max_a < 5001 FROM (SELECT max(id) as max_a, max(val) as b FROM citus_target WHERE id = citus_source.id) AS foo) THEN + INSERT VALUES (citus_source.id, 100); +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 1 +NOTICE: The average of pg_target.val is equal to citus_target.val +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 2 + compare_data +--------------------------------------------------------------------- + +(1 row) + +-- +-- Test target with false clause +-- +SET client_min_messages TO WARNING; +SELECT cleanup_data(); + cleanup_data +--------------------------------------------------------------------- + +(1 row) + +RESET client_min_messages; +SELECT setup_data(); + setup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_target', 'id'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_target$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_source', 'id', colocate_with => 'citus_target'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_source$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO pg_target t +USING (SELECT * FROM pg_source WHERE id > 2500) AS s +ON t.id = s.id AND t.id < 2500 +WHEN MATCHED AND t.id <= 5500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +MERGE INTO citus_target t +USING (SELECT * FROM citus_source WHERE id > 2500) AS s +ON t.id = s.id AND t.id < 2500 +WHEN MATCHED AND t.id <= 5500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 1 +NOTICE: The average of pg_target.val is equal to citus_target.val +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 2 + compare_data +--------------------------------------------------------------------- + +(1 row) + +SET client_min_messages TO WARNING; +SELECT cleanup_data(); + cleanup_data +--------------------------------------------------------------------- + +(1 row) + +RESET client_min_messages; +SELECT setup_data(); + setup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_target', 'id'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_target$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_source', 'id', colocate_with => 'citus_target'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition1_schema.citus_source$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO pg_target t +USING (SELECT * FROM pg_source WHERE id = 2500) AS s +ON t.id = s.id AND t.id = 5000 +WHEN MATCHED AND t.id <= 5500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +MERGE INTO citus_target t +USING (SELECT * FROM citus_source WHERE id = 2500) AS s +ON t.id = s.id AND t.id = 5000 +WHEN MATCHED AND t.id <= 5500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 1 +NOTICE: The average of pg_target.val is equal to citus_target.val +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 2 + compare_data +--------------------------------------------------------------------- + +(1 row) + +DROP SCHEMA merge_repartition1_schema CASCADE; +NOTICE: drop cascades to 8 other objects +DETAIL: drop cascades to table pg_target +drop cascades to table pg_source +drop cascades to function cleanup_data() +drop cascades to function setup_data() +drop cascades to function check_data(text,text,text,text) +drop cascades to function compare_data() +drop cascades to table citus_target +drop cascades to table citus_source diff --git a/src/test/regress/expected/merge_repartition1_0.out b/src/test/regress/expected/merge_repartition1_0.out new file mode 100644 index 000000000..a7e3fbf20 --- /dev/null +++ b/src/test/regress/expected/merge_repartition1_0.out @@ -0,0 +1,6 @@ +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +\else +\q diff --git a/src/test/regress/expected/merge_repartition2.out b/src/test/regress/expected/merge_repartition2.out new file mode 100644 index 000000000..898b7c77a --- /dev/null +++ b/src/test/regress/expected/merge_repartition2.out @@ -0,0 +1,212 @@ +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +\else +\q +\endif +-- We create two sets of source and target tables, one set in Postgres and +-- the other in Citus distributed. We run the _exact_ MERGE SQL on both sets +-- and compare the final results of the target tables in Postgres and Citus. +-- The results should match. This process is repeated for various combinations +-- of MERGE SQL. +DROP SCHEMA IF EXISTS merge_repartition2_schema CASCADE; +NOTICE: schema "merge_repartition2_schema" does not exist, skipping +CREATE SCHEMA merge_repartition2_schema; +SET search_path TO merge_repartition2_schema; +SET citus.shard_count TO 4; +SET citus.next_shard_id TO 6000000; +SET citus.explain_all_tasks TO true; +SET citus.shard_replication_factor TO 1; +SET citus.max_adaptive_executor_pool_size TO 1; +SET client_min_messages = warning; +SELECT 1 FROM master_add_node('localhost', :master_port, groupid => 0); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +RESET client_min_messages; +CREATE TABLE pg_target(id int, val int); +CREATE TABLE pg_source(id int, val int, const int); +CREATE TABLE citus_target(id int, val int); +CREATE TABLE citus_source(id int, val int, const int); +SELECT citus_add_local_table_to_metadata('citus_target'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_add_local_table_to_metadata('citus_source'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +CREATE OR REPLACE FUNCTION cleanup_data() RETURNS VOID SET search_path TO merge_repartition2_schema AS $$ + TRUNCATE pg_target; + TRUNCATE pg_source; + TRUNCATE citus_target; + TRUNCATE citus_source; + SELECT undistribute_table('citus_target'); + SELECT undistribute_table('citus_source'); +$$ +LANGUAGE SQL; +-- +-- Load same set of data to both Postgres and Citus tables +-- +CREATE OR REPLACE FUNCTION setup_data() RETURNS VOID SET search_path TO merge_repartition2_schema AS $$ + INSERT INTO pg_source SELECT i, i+1, 1 FROM generate_series(1, 100000) i; + INSERT INTO pg_target SELECT i, 1 FROM generate_series(50001, 100000) i; + INSERT INTO citus_source SELECT i, i+1, 1 FROM generate_series(1, 100000) i; + INSERT INTO citus_target SELECT i, 1 FROM generate_series(50001, 100000) i; +$$ +LANGUAGE SQL; +-- +-- Compares the final target tables, merge-modified data, of both Postgres and Citus tables +-- +CREATE OR REPLACE FUNCTION check_data(table1_name text, column1_name text, table2_name text, column2_name text) +RETURNS VOID SET search_path TO merge_repartition2_schema AS $$ +DECLARE + table1_avg numeric; + table2_avg numeric; +BEGIN + EXECUTE format('SELECT COALESCE(AVG(%I), 0) FROM %I', column1_name, table1_name) INTO table1_avg; + EXECUTE format('SELECT COALESCE(AVG(%I), 0) FROM %I', column2_name, table2_name) INTO table2_avg; + + IF table1_avg > table2_avg THEN + RAISE EXCEPTION 'The average of %.% is greater than %.%', table1_name, column1_name, table2_name, column2_name; + ELSIF table1_avg < table2_avg THEN + RAISE EXCEPTION 'The average of %.% is less than %.%', table1_name, column1_name, table2_name, column2_name; + ELSE + RAISE NOTICE 'The average of %.% is equal to %.%', table1_name, column1_name, table2_name, column2_name; + END IF; +END; +$$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION compare_data() RETURNS VOID SET search_path TO merge_repartition2_schema AS $$ + SELECT check_data('pg_target', 'id', 'citus_target', 'id'); + SELECT check_data('pg_target', 'val', 'citus_target', 'val'); +$$ +LANGUAGE SQL; +-- Test nested cte +SELECT cleanup_data(); +NOTICE: creating a new table for merge_repartition2_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: moving the data of merge_repartition2_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: dropping the old merge_repartition2_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: renaming the new table to merge_repartition2_schema.citus_target +CONTEXT: SQL function "cleanup_data" statement 5 +NOTICE: creating a new table for merge_repartition2_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: moving the data of merge_repartition2_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: dropping the old merge_repartition2_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 +NOTICE: renaming the new table to merge_repartition2_schema.citus_source +CONTEXT: SQL function "cleanup_data" statement 6 + cleanup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT setup_data(); + setup_data +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_target', 'id'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition2_schema.citus_target$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'none'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$merge_repartition2_schema.citus_source$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +WITH cte_top AS(WITH cte_1 AS (WITH cte_2 AS (SELECT id, val FROM pg_source) SELECT * FROM cte_2) SELECT * FROM cte_1) +MERGE INTO pg_target t +USING (SELECT const, val, id FROM pg_source WHERE id IN (SELECT id FROM cte_top)) as s +ON (s.id = t.id) +WHEN MATCHED AND t.id <= 75000 THEN + UPDATE SET val = (s.val::int8+1) +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES (s.id, s.val); +WITH cte_top AS(WITH cte_1 AS (WITH cte_2 AS (SELECT id, val FROM citus_source) SELECT * FROM cte_2) SELECT * FROM cte_1) +MERGE INTO citus_target t +USING (SELECT const, val, id FROM citus_source WHERE id IN (SELECT id FROM cte_top)) as s +ON (s.id = t.id) +WHEN MATCHED AND t.id <= 75000 THEN + UPDATE SET val = (s.val::int8+1) +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES (s.id, s.val); +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 1 +NOTICE: The average of pg_target.val is equal to citus_target.val +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 2 + compare_data +--------------------------------------------------------------------- + +(1 row) + +-- Test aggregate function in source query +MERGE INTO pg_target t +USING (SELECT count(id+1)::text as value, val as key FROM pg_source group by key) s +ON t.id = s.key +WHEN MATCHED AND t.id <= 75000 THEN + UPDATE SET val = (s.value::int8+1) +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.key, value::int4+10); +MERGE INTO citus_target t +USING (SELECT count(id+1)::text as value, val as key FROM citus_source group by key) s +ON t.id = s.key +WHEN MATCHED AND t.id <= 75000 THEN + UPDATE SET val = (s.value::int8+1) +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.key, value::int4+10); +SELECT compare_data(); +NOTICE: The average of pg_target.id is equal to citus_target.id +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 1 +NOTICE: The average of pg_target.val is equal to citus_target.val +CONTEXT: PL/pgSQL function check_data(text,text,text,text) line XX at RAISE +SQL function "compare_data" statement 2 + compare_data +--------------------------------------------------------------------- + +(1 row) + +DROP SCHEMA merge_repartition2_schema CASCADE; +NOTICE: drop cascades to 8 other objects +DETAIL: drop cascades to table pg_target +drop cascades to table pg_source +drop cascades to function cleanup_data() +drop cascades to function setup_data() +drop cascades to function check_data(text,text,text,text) +drop cascades to function compare_data() +drop cascades to table citus_target +drop cascades to table citus_source diff --git a/src/test/regress/expected/merge_repartition2_0.out b/src/test/regress/expected/merge_repartition2_0.out new file mode 100644 index 000000000..a7e3fbf20 --- /dev/null +++ b/src/test/regress/expected/merge_repartition2_0.out @@ -0,0 +1,6 @@ +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +\else +\q diff --git a/src/test/regress/expected/metadata_sync_helpers.out b/src/test/regress/expected/metadata_sync_helpers.out index f745b0fe2..62268b32f 100644 --- a/src/test/regress/expected/metadata_sync_helpers.out +++ b/src/test/regress/expected/metadata_sync_helpers.out @@ -1317,7 +1317,7 @@ BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED; UPDATE pg_dist_partition SET partmethod = 'a' WHERE logicalrelid = 'test_2'::regclass; SELECT citus_internal_update_relation_colocation('test_2'::regclass, 251); -ERROR: Updating colocation ids are only allowed for hash distributed tables: a +ERROR: Updating colocation ids are only allowed for hash and single shard distributed tables: a ROLLBACK; -- colocated hash distributed table should have the same dist key columns CREATE TABLE test_5(int_col int, text_col text); diff --git a/src/test/regress/expected/minimal_cluster_management.out b/src/test/regress/expected/minimal_cluster_management.out index af3ac84f3..d05e83ed5 100644 --- a/src/test/regress/expected/minimal_cluster_management.out +++ b/src/test/regress/expected/minimal_cluster_management.out @@ -20,6 +20,13 @@ SELECT 1 FROM master_add_node('localhost', :worker_1_port); 1 (1 row) +-- make sure coordinator is always in metadata. +SELECT citus_set_coordinator_host('localhost'); + citus_set_coordinator_host +--------------------------------------------------------------------- + +(1 row) + -- Create the same colocation groups as multi_cluster_management.sql SET citus.shard_count TO 16; SET citus.shard_replication_factor TO 1; diff --git a/src/test/regress/expected/multi_alter_table_add_constraints_without_name.out b/src/test/regress/expected/multi_alter_table_add_constraints_without_name.out index 54224c924..0b048946c 100644 --- a/src/test/regress/expected/multi_alter_table_add_constraints_without_name.out +++ b/src/test/regress/expected/multi_alter_table_add_constraints_without_name.out @@ -862,14 +862,6 @@ DROP TABLE AT_AddConstNoName.dist_partitioned_table; -- Test with Citus Local Tables -- Test "ADD PRIMARY KEY" \c - - :master_host :master_port -SET client_min_messages to ERROR; -SELECT 1 FROM master_add_node('localhost', :master_port, groupId => 0); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - -RESET client_min_messages; CREATE TABLE AT_AddConstNoName.citus_local_table(id int, other_column int); SELECT citus_add_local_table_to_metadata('AT_AddConstNoName.citus_local_table'); citus_add_local_table_to_metadata @@ -1175,12 +1167,6 @@ SELECT con.conname (0 rows) \c - - :master_host :master_port -SELECT 1 FROM master_remove_node('localhost', :master_port); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - -- Test with unusual table and column names CREATE TABLE AT_AddConstNoName."2nd table" ( "2nd id" INTEGER, "3rd id" INTEGER); SELECT create_distributed_table('AT_AddConstNoName."2nd table"','2nd id'); @@ -1315,7 +1301,7 @@ NOTICE: drop cascades to 7 other objects DETAIL: drop cascades to table at_addconstnoname.tbl drop cascades to table at_addconstnoname.products_ref_2 drop cascades to table at_addconstnoname.products_ref_3 -drop cascades to table at_addconstnoname.verylonglonglonglonglonglonglonglonglonglonglonglonglonglonglon drop cascades to table at_addconstnoname.products_ref_3_5410009 +drop cascades to table at_addconstnoname.verylonglonglonglonglonglonglonglonglonglonglonglonglonglonglon drop cascades to table at_addconstnoname.citus_local_partitioned_table drop cascades to table at_addconstnoname."2nd table" diff --git a/src/test/regress/expected/multi_alter_table_add_foreign_key_without_name.out b/src/test/regress/expected/multi_alter_table_add_foreign_key_without_name.out index c27e6a425..9af806685 100644 --- a/src/test/regress/expected/multi_alter_table_add_foreign_key_without_name.out +++ b/src/test/regress/expected/multi_alter_table_add_foreign_key_without_name.out @@ -120,7 +120,7 @@ ERROR: cannot create foreign key constraint since relations are not colocated o DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table DROP TABLE referencing_table; DROP TABLE referenced_table; --- test foreign constraint creation is not supported when one of the tables is not a citus table +-- test foreign constraint creation is supported when coordinator is in metadata CREATE TABLE referenced_local_table(id int PRIMARY KEY, other_column int); CREATE TABLE reference_table(id int, referencing_column int); SELECT create_reference_table('reference_table'); @@ -130,11 +130,12 @@ SELECT create_reference_table('reference_table'); (1 row) ALTER TABLE reference_table ADD FOREIGN KEY (referencing_column) REFERENCES referenced_local_table(id); -ERROR: referenced table "referenced_local_table" must be a distributed table or a reference table -DETAIL: To enforce foreign keys, the referencing and referenced rows need to be stored on the same node. -HINT: You could use SELECT create_reference_table('referenced_local_table') to replicate the referenced table to all nodes or consider dropping the foreign key DROP TABLE referenced_local_table; -DROP TABLE reference_table; +ERROR: cannot drop table referenced_local_table because other objects depend on it +DETAIL: constraint reference_table_referencing_column_fkey on table reference_table depends on table referenced_local_table +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP TABLE reference_table CASCADE; +NOTICE: removing table at_add_fk.referenced_local_table from metadata as it is not connected to any reference tables via foreign keys -- test foreign constraint with correct conditions CREATE TABLE referenced_table(id int PRIMARY KEY, test_column int); CREATE TABLE referencing_table(id int, ref_id int); @@ -155,7 +156,7 @@ SELECT con.conname FROM pg_catalog.pg_constraint con INNER JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid INNER JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace - WHERE rel.relname = 'referencing_table'; + WHERE rel.relname = 'referencing_table' ORDER BY con.conname ASC; conname --------------------------------------------------------------------- referencing_table_ref_id_fkey @@ -166,12 +167,12 @@ SELECT con.conname FROM pg_catalog.pg_constraint con INNER JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid INNER JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace - WHERE rel.relname LIKE 'referencing_table%'; + WHERE rel.relname LIKE 'referencing_table%' ORDER BY con.conname ASC; conname --------------------------------------------------------------------- referencing_table_ref_id_fkey - referencing_table_ref_id_fkey_1770033 - referencing_table_ref_id_fkey_1770035 + referencing_table_ref_id_fkey_1770034 + referencing_table_ref_id_fkey_1770036 (3 rows) \c - - :master_host :master_port @@ -183,7 +184,7 @@ SELECT con.conname FROM pg_catalog.pg_constraint con INNER JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid INNER JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace - WHERE rel.relname = 'referencing_table'; + WHERE rel.relname = 'referencing_table' ORDER BY con.conname ASC; conname --------------------------------------------------------------------- referencing_table_ref_id_fkey @@ -194,12 +195,12 @@ SELECT con.conname FROM pg_catalog.pg_constraint con INNER JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid INNER JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace - WHERE rel.relname LIKE 'referencing_table%'; + WHERE rel.relname LIKE 'referencing_table%' ORDER BY con.conname ASC; conname --------------------------------------------------------------------- referencing_table_ref_id_fkey - referencing_table_ref_id_fkey_1770033 - referencing_table_ref_id_fkey_1770035 + referencing_table_ref_id_fkey_1770034 + referencing_table_ref_id_fkey_1770036 (3 rows) \c - - :master_host :master_port @@ -244,8 +245,8 @@ SELECT con.conname, con.confupdtype, con.confdeltype, con.confmatchtype conname | confupdtype | confdeltype | confmatchtype --------------------------------------------------------------------- referencing_table_ref_id_fkey | a | c | s - referencing_table_ref_id_fkey_1770041 | a | c | s - referencing_table_ref_id_fkey_1770043 | a | c | s + referencing_table_ref_id_fkey_1770042 | a | c | s + referencing_table_ref_id_fkey_1770044 | a | c | s (3 rows) \c - - :master_host :master_port @@ -272,8 +273,8 @@ SELECT con.conname, con.convalidated conname | convalidated --------------------------------------------------------------------- referencing_table_ref_id_fkey | f - referencing_table_ref_id_fkey_1770041 | f - referencing_table_ref_id_fkey_1770043 | f + referencing_table_ref_id_fkey_1770042 | f + referencing_table_ref_id_fkey_1770044 | f (3 rows) \c - - :master_host :master_port @@ -300,8 +301,8 @@ SELECT con.conname, con.confupdtype, con.confdeltype, con.confmatchtype conname | confupdtype | confdeltype | confmatchtype --------------------------------------------------------------------- referencing_table_ref_id_fkey | a | a | s - referencing_table_ref_id_fkey_1770041 | a | a | s - referencing_table_ref_id_fkey_1770043 | a | a | s + referencing_table_ref_id_fkey_1770042 | a | a | s + referencing_table_ref_id_fkey_1770044 | a | a | s (3 rows) \c - - :master_host :master_port @@ -328,8 +329,8 @@ SELECT con.conname, con.confupdtype, con.confdeltype, con.confmatchtype conname | confupdtype | confdeltype | confmatchtype --------------------------------------------------------------------- referencing_table_ref_id_fkey | a | r | s - referencing_table_ref_id_fkey_1770041 | a | r | s - referencing_table_ref_id_fkey_1770043 | a | r | s + referencing_table_ref_id_fkey_1770042 | a | r | s + referencing_table_ref_id_fkey_1770044 | a | r | s (3 rows) \c - - :master_host :master_port @@ -356,8 +357,8 @@ SELECT con.conname, con.confupdtype, con.confdeltype, con.confmatchtype conname | confupdtype | confdeltype | confmatchtype --------------------------------------------------------------------- referencing_table_ref_id_id_fkey | a | a | s - referencing_table_ref_id_id_fkey_1770041 | a | a | s - referencing_table_ref_id_id_fkey_1770043 | a | a | s + referencing_table_ref_id_id_fkey_1770042 | a | a | s + referencing_table_ref_id_id_fkey_1770044 | a | a | s (3 rows) \c - - :master_host :master_port @@ -384,8 +385,8 @@ SELECT con.conname, con.confupdtype, con.confdeltype, con.confmatchtype conname | confupdtype | confdeltype | confmatchtype --------------------------------------------------------------------- referencing_table_ref_id_id_fkey | r | a | s - referencing_table_ref_id_id_fkey_1770041 | r | a | s - referencing_table_ref_id_id_fkey_1770043 | r | a | s + referencing_table_ref_id_id_fkey_1770042 | r | a | s + referencing_table_ref_id_id_fkey_1770044 | r | a | s (3 rows) \c - - :master_host :master_port @@ -412,8 +413,8 @@ SELECT con.conname, con.confupdtype, con.confdeltype, con.confmatchtype conname | confupdtype | confdeltype | confmatchtype --------------------------------------------------------------------- referencing_table_ref_id_id_fkey | a | a | s - referencing_table_ref_id_id_fkey_1770041 | a | a | s - referencing_table_ref_id_id_fkey_1770043 | a | a | s + referencing_table_ref_id_id_fkey_1770042 | a | a | s + referencing_table_ref_id_id_fkey_1770044 | a | a | s (3 rows) \c - - :master_host :master_port @@ -440,8 +441,8 @@ SELECT con.conname, con.confupdtype, con.confdeltype, con.confmatchtype conname | confupdtype | confdeltype | confmatchtype --------------------------------------------------------------------- referencing_table_ref_id_id_fkey | a | a | f - referencing_table_ref_id_id_fkey_1770041 | a | a | f - referencing_table_ref_id_id_fkey_1770043 | a | a | f + referencing_table_ref_id_id_fkey_1770042 | a | a | f + referencing_table_ref_id_id_fkey_1770044 | a | a | f (3 rows) \c - - :master_host :master_port @@ -524,13 +525,6 @@ BEGIN; DROP TABLE dist_table CASCADE; DROP TABLE reference_table CASCADE; -- test ADD FOREIGN KEY from citus local to reference table -SELECT 1 FROM master_add_node('localhost', :master_port, groupId => 0); -NOTICE: localhost:xxxxx is the coordinator and already contains metadata, skipping syncing the metadata - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - CREATE TABLE citus_local_table(l1 int); SELECT citus_add_local_table_to_metadata('citus_local_table'); citus_add_local_table_to_metadata @@ -557,17 +551,30 @@ ALTER TABLE citus_local_table ADD FOREIGN KEY(l1) REFERENCES reference_table(r1) ALTER TABLE citus_local_table ADD FOREIGN KEY(l1) REFERENCES reference_table(r1) ON DELETE NO ACTION; ALTER TABLE citus_local_table ADD FOREIGN KEY(l1) REFERENCES reference_table(r1) ON DELETE RESTRICT; DROP TABLE citus_local_table CASCADE; -SELECT 1 FROM master_remove_node('localhost', :master_port); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - RESET SEARCH_PATH; RESET client_min_messages; DROP SCHEMA at_add_fk CASCADE; -NOTICE: drop cascades to 4 other objects -DETAIL: drop cascades to table at_add_fk.referenced_table +NOTICE: drop cascades to 5 other objects +DETAIL: drop cascades to table at_add_fk.referenced_local_table +drop cascades to table at_add_fk.referenced_table drop cascades to table at_add_fk.referencing_table drop cascades to table at_add_fk.reference_table -drop cascades to table at_add_fk.reference_table_1770051 +drop cascades to table at_add_fk.reference_table_1770052 +-- test ADD FOREIGN KEY when REFERENCED table is in another schema. +CREATE SCHEMA schema_1; +CREATE TABLE schema_1.referenced_table(a int PRIMARY KEY, b int); +SELECT create_reference_table('schema_1.referenced_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +CREATE SCHEMA schema_2; +CREATE TABLE schema_2.referencing_table (a int PRIMARY KEY, b int, c text); +ALTER TABLE schema_2.referencing_table ADD FOREIGN KEY (b) REFERENCES schema_1.referenced_table(a); +DROP SCHEMA schema_1, schema_2 CASCADE; +NOTICE: drop cascades to 4 other objects +DETAIL: drop cascades to table schema_2.referencing_table_1770054 +drop cascades to table schema_2.referencing_table +drop cascades to table schema_1.referenced_table +drop cascades to table schema_1.referenced_table_1770053 diff --git a/src/test/regress/expected/multi_alter_table_row_level_security.out b/src/test/regress/expected/multi_alter_table_row_level_security.out index 962b037fc..d82f76a25 100644 --- a/src/test/regress/expected/multi_alter_table_row_level_security.out +++ b/src/test/regress/expected/multi_alter_table_row_level_security.out @@ -538,6 +538,7 @@ CREATE POLICY fp_s ON information FOR SELECT -- this attempt for distribution fails because the table has a disallowed expression SELECT create_distributed_table('information', 'group_id'); ERROR: cannot create policy +DETAIL: Subqueries are not supported in policies on distributed tables -- DROP the expression so we can distribute the table DROP POLICY fp_s ON information; SELECT create_distributed_table('information', 'group_id'); @@ -549,7 +550,7 @@ SELECT create_distributed_table('information', 'group_id'); -- Try and create the expression on a distributed table, this should also fail CREATE POLICY fp_s ON information FOR SELECT USING (group_id <= (SELECT group_id FROM users WHERE user_name = current_user)); -ERROR: cannot create policy +ERROR: unexpected non-SELECT command in SubLink -- Clean up test DROP TABLE information, groups, users; SET citus.next_shard_id TO 1810000; diff --git a/src/test/regress/expected/multi_citus_tools.out b/src/test/regress/expected/multi_citus_tools.out index 792839d87..eef7a98ca 100644 --- a/src/test/regress/expected/multi_citus_tools.out +++ b/src/test/regress/expected/multi_citus_tools.out @@ -547,7 +547,8 @@ WHERE isactive = 't' AND noderole='primary'; --------------------------------------------------------------------- t t -(2 rows) + t +(3 rows) CREATE TABLE distributed(id int, data text); SELECT create_distributed_table('distributed', 'id'); @@ -632,11 +633,16 @@ SELECT citus_check_connection_to_node('localhost', :worker_2_port); SELECT * FROM citus_check_cluster_node_health() ORDER BY 1,2,3,4; from_nodename | from_nodeport | to_nodename | to_nodeport | result --------------------------------------------------------------------- + localhost | 57636 | localhost | 57636 | t + localhost | 57636 | localhost | 57637 | t + localhost | 57636 | localhost | 57638 | t + localhost | 57637 | localhost | 57636 | t localhost | 57637 | localhost | 57637 | t localhost | 57637 | localhost | 57638 | t + localhost | 57638 | localhost | 57636 | t localhost | 57638 | localhost | 57637 | t localhost | 57638 | localhost | 57638 | t -(4 rows) +(9 rows) -- test cluster connectivity when we have broken nodes SET client_min_messages TO ERROR; @@ -648,23 +654,32 @@ INSERT INTO pg_dist_node VALUES SELECT * FROM citus_check_cluster_node_health() ORDER BY 5,1,2,3,4; from_nodename | from_nodeport | to_nodename | to_nodeport | result --------------------------------------------------------------------- + localhost | 57636 | localhost | 123456789 | f + localhost | 57636 | www.citusdata.com | 5432 | f localhost | 57637 | localhost | 123456789 | f localhost | 57637 | www.citusdata.com | 5432 | f localhost | 57638 | localhost | 123456789 | f localhost | 57638 | www.citusdata.com | 5432 | f + localhost | 57636 | localhost | 57636 | t + localhost | 57636 | localhost | 57637 | t + localhost | 57636 | localhost | 57638 | t + localhost | 57637 | localhost | 57636 | t localhost | 57637 | localhost | 57637 | t localhost | 57637 | localhost | 57638 | t + localhost | 57638 | localhost | 57636 | t localhost | 57638 | localhost | 57637 | t localhost | 57638 | localhost | 57638 | t + localhost | 123456789 | localhost | 57636 | localhost | 123456789 | localhost | 57637 | localhost | 123456789 | localhost | 57638 | localhost | 123456789 | localhost | 123456789 | localhost | 123456789 | www.citusdata.com | 5432 | + www.citusdata.com | 5432 | localhost | 57636 | www.citusdata.com | 5432 | localhost | 57637 | www.citusdata.com | 5432 | localhost | 57638 | www.citusdata.com | 5432 | localhost | 123456789 | www.citusdata.com | 5432 | www.citusdata.com | 5432 | -(16 rows) +(25 rows) ROLLBACK; RESET citus.node_connection_timeout; diff --git a/src/test/regress/expected/multi_cluster_management.out b/src/test/regress/expected/multi_cluster_management.out index 08ac5a807..e58b02937 100644 --- a/src/test/regress/expected/multi_cluster_management.out +++ b/src/test/regress/expected/multi_cluster_management.out @@ -25,6 +25,13 @@ SELECT citus_is_coordinator(); t (1 row) +-- make sure coordinator is always in metadata. +SELECT citus_set_coordinator_host('localhost'); + citus_set_coordinator_host +--------------------------------------------------------------------- + +(1 row) + -- workers are not coordinator SELECT result FROM run_command_on_workers('SELECT citus_is_coordinator()'); result @@ -144,9 +151,9 @@ SELECT citus_disable_node('localhost', :worker_1_port, synchronous:=true); (1 row) SELECT run_command_on_workers($$SELECT array_agg(isactive ORDER BY nodeport) FROM pg_dist_node WHERE hasmetadata and noderole='primary'::noderole AND nodecluster='default'$$); - run_command_on_workers + run_command_on_workers --------------------------------------------------------------------- - (localhost,57638,t,"{f,t}") + (localhost,57638,t,"{t,f,t}") (1 row) SELECT 1 FROM citus_activate_node('localhost', :worker_1_port); @@ -163,9 +170,9 @@ SELECT citus_disable_node('localhost', :worker_2_port, synchronous:=true); (1 row) SELECT run_command_on_workers($$SELECT array_agg(isactive ORDER BY nodeport) FROM pg_dist_node WHERE hasmetadata and noderole='primary'::noderole AND nodecluster='default'$$); - run_command_on_workers + run_command_on_workers --------------------------------------------------------------------- - (localhost,57637,t,"{t,f}") + (localhost,57637,t,"{t,t,f}") (1 row) SELECT 1 FROM citus_activate_node('localhost', :worker_2_port); @@ -248,7 +255,7 @@ ERROR: node at "localhost.noexist:2345" does not exist SELECT master_activate_node('localhost', :worker_2_port); master_activate_node --------------------------------------------------------------------- - 3 + 4 (1 row) DROP TABLE test_reference_table, cluster_management_test; @@ -358,9 +365,10 @@ SELECT master_update_node(nodeid, 'localhost', :worker_2_port + 3) FROM pg_dist_ SELECT nodename, nodeport, noderole FROM pg_dist_node ORDER BY nodeport; nodename | nodeport | noderole --------------------------------------------------------------------- + localhost | 57636 | primary localhost | 57637 | primary localhost | 57640 | secondary -(2 rows) +(3 rows) ABORT; \c - postgres - :master_port @@ -377,7 +385,7 @@ SELECT master_get_active_worker_nodes(); SELECT * FROM master_add_node('localhost', :worker_2_port); master_add_node --------------------------------------------------------------------- - 6 + 7 (1 row) ALTER SEQUENCE pg_dist_node_nodeid_seq RESTART WITH 7; @@ -579,7 +587,7 @@ SELECT SELECT count(1) FROM pg_dist_node; count --------------------------------------------------------------------- - 0 + 1 (1 row) -- check that adding two nodes in the same transaction works @@ -594,9 +602,10 @@ SELECT SELECT * FROM pg_dist_node ORDER BY nodeid; nodeid | groupid | nodename | nodeport | noderack | hasmetadata | isactive | noderole | nodecluster | metadatasynced | shouldhaveshards --------------------------------------------------------------------- + 3 | 0 | localhost | 57636 | default | t | t | primary | default | t | f 11 | 9 | localhost | 57637 | default | t | t | primary | default | t | t 12 | 10 | localhost | 57638 | default | t | t | primary | default | t | t -(2 rows) +(3 rows) -- check that mixed add/remove node commands work fine inside transaction BEGIN; @@ -669,7 +678,14 @@ SELECT master_remove_node(nodename, nodeport) FROM pg_dist_node; --------------------------------------------------------------------- -(2 rows) + +(3 rows) + +SELECT citus_set_coordinator_host('localhost'); + citus_set_coordinator_host +--------------------------------------------------------------------- + +(1 row) SELECT 1 FROM master_add_node('localhost', :worker_1_port); ?column? @@ -781,13 +797,13 @@ SELECT 1 FROM master_add_inactive_node('localhost', 9996, groupid => :worker_2_g SELECT master_add_inactive_node('localhost', 9999, groupid => :worker_2_group, nodecluster => 'olap', noderole => 'secondary'); master_add_inactive_node --------------------------------------------------------------------- - 22 + 23 (1 row) SELECT master_activate_node('localhost', 9999); master_activate_node --------------------------------------------------------------------- - 22 + 23 (1 row) SELECT citus_disable_node('localhost', 9999); @@ -821,17 +837,17 @@ CONTEXT: PL/pgSQL function citus_internal.pg_dist_node_trigger_func() line XX a INSERT INTO pg_dist_node (nodename, nodeport, groupid, noderole, nodecluster) VALUES ('localhost', 5000, 1000, 'primary', 'olap'); ERROR: new row for relation "pg_dist_node" violates check constraint "primaries_are_only_allowed_in_the_default_cluster" -DETAIL: Failing row contains (24, 1000, localhost, 5000, default, f, t, primary, olap, f, t). +DETAIL: Failing row contains (25, 1000, localhost, 5000, default, f, t, primary, olap, f, t). UPDATE pg_dist_node SET nodecluster = 'olap' WHERE nodeport = :worker_1_port; ERROR: new row for relation "pg_dist_node" violates check constraint "primaries_are_only_allowed_in_the_default_cluster" -DETAIL: Failing row contains (16, 14, localhost, 57637, default, f, t, primary, olap, f, t). +DETAIL: Failing row contains (17, 14, localhost, 57637, default, f, t, primary, olap, f, t). -- check that you /can/ add a secondary node to a non-default cluster SELECT groupid AS worker_2_group FROM pg_dist_node WHERE nodeport = :worker_2_port \gset SELECT master_add_node('localhost', 8888, groupid => :worker_1_group, noderole => 'secondary', nodecluster=> 'olap'); master_add_node --------------------------------------------------------------------- - 25 + 26 (1 row) -- check that super-long cluster names are truncated @@ -844,13 +860,13 @@ SELECT master_add_node('localhost', 8887, groupid => :worker_1_group, noderole = ); master_add_node --------------------------------------------------------------------- - 26 + 27 (1 row) SELECT * FROM pg_dist_node WHERE nodeport=8887; nodeid | groupid | nodename | nodeport | noderack | hasmetadata | isactive | noderole | nodecluster | metadatasynced | shouldhaveshards --------------------------------------------------------------------- - 26 | 14 | localhost | 8887 | default | f | t | secondary | thisisasixtyfourcharacterstringrepeatedfourtimestomake256chars. | f | t + 27 | 14 | localhost | 8887 | default | f | t | secondary | thisisasixtyfourcharacterstringrepeatedfourtimestomake256chars. | f | t (1 row) -- don't remove the secondary and unavailable nodes, check that no commands are sent to @@ -859,13 +875,13 @@ SELECT * FROM pg_dist_node WHERE nodeport=8887; SELECT master_add_secondary_node('localhost', 9995, 'localhost', :worker_1_port); master_add_secondary_node --------------------------------------------------------------------- - 27 + 28 (1 row) SELECT master_add_secondary_node('localhost', 9994, primaryname => 'localhost', primaryport => :worker_2_port); master_add_secondary_node --------------------------------------------------------------------- - 28 + 29 (1 row) SELECT master_add_secondary_node('localhost', 9993, 'localhost', 2000); @@ -873,7 +889,7 @@ ERROR: node at "localhost:xxxxx" does not exist SELECT master_add_secondary_node('localhost', 9992, 'localhost', :worker_1_port, nodecluster => 'second-cluster'); master_add_secondary_node --------------------------------------------------------------------- - 29 + 30 (1 row) SELECT nodeid AS worker_1_node FROM pg_dist_node WHERE nodeport=9992 \gset @@ -931,7 +947,7 @@ SELECT master_update_node(:worker_1_node, 'somehost', 9000); SELECT * FROM pg_dist_node WHERE nodeid = :worker_1_node; nodeid | groupid | nodename | nodeport | noderack | hasmetadata | isactive | noderole | nodecluster | metadatasynced | shouldhaveshards --------------------------------------------------------------------- - 16 | 14 | somehost | 9000 | default | f | t | primary | default | f | t + 17 | 14 | somehost | 9000 | default | f | t | primary | default | f | t (1 row) -- cleanup @@ -944,7 +960,7 @@ SELECT master_update_node(:worker_1_node, 'localhost', :worker_1_port); SELECT * FROM pg_dist_node WHERE nodeid = :worker_1_node; nodeid | groupid | nodename | nodeport | noderack | hasmetadata | isactive | noderole | nodecluster | metadatasynced | shouldhaveshards --------------------------------------------------------------------- - 16 | 14 | localhost | 57637 | default | f | t | primary | default | f | t + 17 | 14 | localhost | 57637 | default | f | t | primary | default | f | t (1 row) SET client_min_messages TO ERROR; @@ -953,7 +969,8 @@ SELECT start_metadata_sync_to_node(nodename, nodeport) FROM pg_dist_node WHERE i --------------------------------------------------------------------- -(2 rows) + +(3 rows) RESET client_min_messages; SET citus.shard_replication_factor TO 1; @@ -1034,9 +1051,10 @@ FROM pg_dist_shard JOIN pg_dist_shard_placement USING (shardid) WHERE logicalrelid = 'test_ref'::regclass GROUP BY nodeport ORDER BY nodeport; nodeport | count --------------------------------------------------------------------- + 57636 | 1 57637 | 1 57638 | 1 -(2 rows) +(3 rows) -- cleanup for next test DROP TABLE test_dist, test_ref, test_dist_colocated, test_dist_non_colocated, test_dist_colocated_with_non_colocated; @@ -1078,9 +1096,10 @@ FROM pg_dist_shard JOIN pg_dist_shard_placement USING (shardid) WHERE logicalrelid = 'test_ref'::regclass GROUP BY nodeport ORDER BY nodeport; nodeport | count --------------------------------------------------------------------- + 57636 | 1 57637 | 1 57638 | 1 -(2 rows) +(3 rows) SELECT * from master_set_node_property('localhost', :worker_2_port, 'shouldhaveshards', true); master_set_node_property @@ -1104,9 +1123,10 @@ FROM pg_dist_shard JOIN pg_dist_shard_placement USING (shardid) WHERE logicalrelid = 'test_ref'::regclass GROUP BY nodeport ORDER BY nodeport; nodeport | count --------------------------------------------------------------------- + 57636 | 1 57637 | 1 57638 | 1 -(2 rows) +(3 rows) SELECT create_distributed_table('test_dist_colocated', 'x'); create_distributed_table diff --git a/src/test/regress/expected/multi_colocation_utils.out b/src/test/regress/expected/multi_colocation_utils.out index 93596a1b4..9ef2896ff 100644 --- a/src/test/regress/expected/multi_colocation_utils.out +++ b/src/test/regress/expected/multi_colocation_utils.out @@ -612,10 +612,10 @@ CREATE TABLE table_postgresql( id int ); CREATE TABLE table_failing ( id int ); SELECT create_distributed_table('table_failing', 'id', colocate_with => 'table_append'); ERROR: cannot distribute relation -DETAIL: Currently, colocate_with option is only supported for hash distributed tables. +DETAIL: Currently, colocate_with option is not supported with append / range distributed tables and local tables added to metadata. SELECT create_distributed_table('table_failing', 'id', 'append', 'table1_groupE'); ERROR: cannot distribute relation -DETAIL: Currently, colocate_with option is only supported for hash distributed tables. +DETAIL: Currently, colocate_with option is not supported for append / range distributed tables. SELECT create_distributed_table('table_failing', 'id', colocate_with => 'table_postgresql'); ERROR: relation table_postgresql is not distributed SELECT create_distributed_table('table_failing', 'id', colocate_with => 'no_table'); @@ -825,11 +825,13 @@ ORDER BY table1_group_default | 1300070 | t | 57638 | -715827883 | 715827881 table1_group_default | 1300071 | t | 57637 | 715827882 | 2147483647 table1_group_default | 1300071 | t | 57638 | 715827882 | 2147483647 + table1_groupf | 1300080 | t | 57636 | | table1_groupf | 1300080 | t | 57637 | | table1_groupf | 1300080 | t | 57638 | | + table2_groupf | 1300081 | t | 57636 | | table2_groupf | 1300081 | t | 57637 | | table2_groupf | 1300081 | t | 57638 | | -(92 rows) +(94 rows) -- reset colocation ids to test update_distributed_table_colocation ALTER SEQUENCE pg_catalog.pg_dist_colocationid_seq RESTART 1; @@ -868,9 +870,9 @@ ERROR: cannot colocate tables table1_groupd and table1_groupb DETAIL: Shard counts don't match for table1_groupd and table1_groupb. SELECT update_distributed_table_colocation('table1_groupB', colocate_with => 'table1_groupE'); ERROR: cannot colocate tables table1_groupe and table1_groupb -DETAIL: Shard 1300050 of table1_groupe and shard xxxxx of table1_groupb have different number of shard placements. +DETAIL: Shard xxxxx of table1_groupe and shard xxxxx of table1_groupb have different number of shard placements. SELECT update_distributed_table_colocation('table1_groupB', colocate_with => 'table1_groupF'); -ERROR: relation table1_groupf should be a hash distributed table +ERROR: relation table1_groupf should be a hash or single shard distributed table SELECT update_distributed_table_colocation('table1_groupB', colocate_with => 'table1_groupD'); ERROR: cannot colocate tables table1_groupd and table1_groupb DETAIL: Shard counts don't match for table1_groupd and table1_groupb. @@ -1369,9 +1371,9 @@ SELECT tables_colocated('d2', 'none'); -- make sure reference and local tables return an error. SELECT update_distributed_table_colocation('ref', colocate_with => 'none'); -ERROR: relation ref should be a hash distributed table +ERROR: relation ref should be a hash or single shard distributed table SELECT update_distributed_table_colocation('local_table', colocate_with => 'none'); -ERROR: relation local_table should be a hash distributed table +ERROR: relation local_table should be a hash or single shard distributed table -- make sure that different types cannot be colocated SELECT update_distributed_table_colocation('different_d1', colocate_with => 'd1'); ERROR: cannot colocate tables d1 and different_d1 @@ -1381,13 +1383,13 @@ ERROR: cannot colocate tables different_d1 and d1 DETAIL: Distribution column types don't match for different_d1 and d1. -- make sure that append distributed tables cannot be colocated SELECT update_distributed_table_colocation('append_table', colocate_with => 'd1'); -ERROR: relation append_table should be a hash distributed table +ERROR: relation append_table should be a hash or single shard distributed table SELECT update_distributed_table_colocation('d1', colocate_with => 'append_table'); -ERROR: relation append_table should be a hash distributed table +ERROR: relation append_table should be a hash or single shard distributed table SELECT update_distributed_table_colocation('range_table', colocate_with => 'd1'); -ERROR: relation range_table should be a hash distributed table +ERROR: relation range_table should be a hash or single shard distributed table SELECT update_distributed_table_colocation('d1', colocate_with => 'range_table'); -ERROR: relation range_table should be a hash distributed table +ERROR: relation range_table should be a hash or single shard distributed table -- drop tables to clean test space DROP TABLE table1_groupb; DROP TABLE table2_groupb; diff --git a/src/test/regress/expected/multi_copy.out b/src/test/regress/expected/multi_copy.out index 2b302965c..abd58eb1d 100644 --- a/src/test/regress/expected/multi_copy.out +++ b/src/test/regress/expected/multi_copy.out @@ -756,9 +756,10 @@ SELECT shardid, shardstate, nodename, nodeport WHERE logicalrelid = 'numbers_reference'::regclass order by placementid; shardid | shardstate | nodename | nodeport --------------------------------------------------------------------- + 560165 | 1 | localhost | 57636 560165 | 1 | localhost | 57637 560165 | 1 | localhost | 57638 -(2 rows) +(3 rows) -- try to insert into numbers_hash_other. copy should fail and rollback -- since it can not insert into either copies of a shard. shards are expected to diff --git a/src/test/regress/expected/multi_create_table_superuser.out b/src/test/regress/expected/multi_create_table_superuser.out index e15b6359e..1f756d688 100644 --- a/src/test/regress/expected/multi_create_table_superuser.out +++ b/src/test/regress/expected/multi_create_table_superuser.out @@ -642,13 +642,15 @@ DROP TABLE tt1; DROP TABLE tt2; DROP TABLE alter_replica_table; DROP SCHEMA sc CASCADE; -NOTICE: drop cascades to 2 other objects +NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to table sc.ref +drop cascades to table sc.ref_360102 drop cascades to table sc.hash DROP SCHEMA sc2 CASCADE; -NOTICE: drop cascades to 2 other objects +NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to table sc2.hash drop cascades to table sc2.ref +drop cascades to table sc2.ref_360111 DROP SCHEMA sc3 CASCADE; NOTICE: drop cascades to table sc3.alter_replica_table DROP SCHEMA sc4 CASCADE; diff --git a/src/test/regress/expected/multi_drop_extension.out b/src/test/regress/expected/multi_drop_extension.out index dfc34dea9..909ad2f87 100644 --- a/src/test/regress/expected/multi_drop_extension.out +++ b/src/test/regress/expected/multi_drop_extension.out @@ -21,12 +21,6 @@ BEGIN; SET client_min_messages TO ERROR; SET search_path TO public; CREATE EXTENSION citus; - SELECT 1 FROM master_add_node('localhost', :master_port, groupId => 0); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - create table l1 (a int unique); SELECT create_reference_table('l1'); create_reference_table @@ -43,8 +37,13 @@ BEGIN; (1 row) alter table other_schema.l3 add constraint fkey foreign key (a) references l1(a); - -- show that works fine - drop schema public cascade; + -- Commented out because it fails due to the issue documented in + -- https://github.com/citusdata/citus/issues/6901. + -- + -- This wasn't the case before https://github.com/citusdata/citus/pull/6900. + -- This is because, we were not marking the schemas as distributed when there + -- are no worker nodes in the cluster before that PR. + -- drop schema public cascade; ROLLBACK; CREATE EXTENSION citus; CREATE SCHEMA test_schema; @@ -131,6 +130,12 @@ ROLLBACK TO SAVEPOINT s3; ROLLBACK; CREATE EXTENSION citus; -- re-add the nodes to the cluster +SELECT citus_set_coordinator_host('localhost'); + citus_set_coordinator_host +--------------------------------------------------------------------- + +(1 row) + SELECT 1 FROM master_add_node('localhost', :worker_1_port); ?column? --------------------------------------------------------------------- diff --git a/src/test/regress/expected/multi_explain.out b/src/test/regress/expected/multi_explain.out index eee7a6236..b3e47474f 100644 --- a/src/test/regress/expected/multi_explain.out +++ b/src/test/regress/expected/multi_explain.out @@ -8,7 +8,7 @@ SET citus.enable_repartition_joins to ON; -- Ensure tuple data in explain analyze output is the same on all PG versions SET citus.enable_binary_protocol = TRUE; -- Function that parses explain output as JSON -CREATE FUNCTION explain_json(query text) +CREATE OR REPLACE FUNCTION explain_json(query text) RETURNS jsonb AS $BODY$ DECLARE @@ -18,7 +18,7 @@ BEGIN RETURN result; END; $BODY$ LANGUAGE plpgsql; -CREATE FUNCTION explain_analyze_json(query text) +CREATE OR REPLACE FUNCTION explain_analyze_json(query text) RETURNS jsonb AS $BODY$ DECLARE @@ -29,7 +29,7 @@ BEGIN END; $BODY$ LANGUAGE plpgsql; -- Function that parses explain output as XML -CREATE FUNCTION explain_xml(query text) +CREATE OR REPLACE FUNCTION explain_xml(query text) RETURNS xml AS $BODY$ DECLARE @@ -40,7 +40,7 @@ BEGIN END; $BODY$ LANGUAGE plpgsql; -- Function that parses explain output as XML -CREATE FUNCTION explain_analyze_xml(query text) +CREATE OR REPLACE FUNCTION explain_analyze_xml(query text) RETURNS xml AS $BODY$ DECLARE @@ -98,19 +98,24 @@ EXPLAIN (COSTS FALSE, FORMAT JSON) "Plan": { "Node Type": "Sort", "Parallel Aware": false, + "Async Capable": false, "Sort Key": ["(COALESCE((pg_catalog.sum(remote_scan.count_quantity))::bigint, '0'::bigint))", "remote_scan.l_quantity"], "Plans": [ { "Node Type": "Aggregate", "Strategy": "Hashed", "Partial Mode": "Simple", + "Parent Relationship": "Outer", "Parallel Aware": false, + "Async Capable": false, "Group Key": ["remote_scan.l_quantity"], "Plans": [ { "Node Type": "Custom Scan", + "Parent Relationship": "Outer", "Custom Plan Provider": "Citus Adaptive", "Parallel Aware": false, + "Async Capable": false, "Distributed Query": { "Job": { "Task Count": 2, @@ -126,11 +131,14 @@ EXPLAIN (COSTS FALSE, FORMAT JSON) "Strategy": "Hashed", "Partial Mode": "Simple", "Parallel Aware": false, + "Async Capable": false, "Group Key": ["l_quantity"], "Plans": [ { "Node Type": "Seq Scan", + "Parent Relationship": "Outer", "Parallel Aware": false, + "Async Capable": false, "Relation Name": "lineitem_360000", "Alias": "lineitem" } @@ -172,6 +180,7 @@ EXPLAIN (COSTS FALSE, FORMAT XML) Sort false + false (COALESCE((pg_catalog.sum(remote_scan.count_quantity))::bigint, '0'::bigint)) remote_scan.l_quantity @@ -181,15 +190,19 @@ EXPLAIN (COSTS FALSE, FORMAT XML) Aggregate Hashed Simple + Outer false + false remote_scan.l_quantity Custom Scan + Outer Citus Adaptive false + false 2 @@ -205,13 +218,16 @@ EXPLAIN (COSTS FALSE, FORMAT XML) Hashed Simple false + false l_quantity Seq Scan + Outer false + false lineitem_360000 lineitem @@ -250,6 +266,7 @@ EXPLAIN (COSTS FALSE, FORMAT YAML) - Plan: Node Type: "Sort" Parallel Aware: false + Async Capable: false Sort Key: - "(COALESCE((pg_catalog.sum(remote_scan.count_quantity))::bigint, '0'::bigint))" - "remote_scan.l_quantity" @@ -257,13 +274,17 @@ EXPLAIN (COSTS FALSE, FORMAT YAML) - Node Type: "Aggregate" Strategy: "Hashed" Partial Mode: "Simple" + Parent Relationship: "Outer" Parallel Aware: false + Async Capable: false Group Key: - "remote_scan.l_quantity" Plans: - Node Type: "Custom Scan" + Parent Relationship: "Outer" Custom Plan Provider: "Citus Adaptive" Parallel Aware: false + Async Capable: false Distributed Query: Job: Task Count: 2 @@ -276,11 +297,14 @@ EXPLAIN (COSTS FALSE, FORMAT YAML) Strategy: "Hashed" Partial Mode: "Simple" Parallel Aware: false + Async Capable: false Group Key: - "l_quantity" Plans: - Node Type: "Seq Scan" + Parent Relationship: "Outer" Parallel Aware: false + Async Capable: false Relation Name: "lineitem_360000" Alias: "lineitem" @@ -331,16 +355,16 @@ BEGIN; SET LOCAL citus.enable_repartition_joins TO true; EXPLAIN (COSTS off, ANALYZE on, TIMING off, SUMMARY off) SELECT count(*) FROM t1, t2 WHERE t1.a=t2.b; Aggregate (actual rows=1 loops=1) - -> Custom Scan (Citus Adaptive) (actual rows=4 loops=1) - Task Count: 4 - Tuple data received from nodes: 32 bytes + -> Custom Scan (Citus Adaptive) (actual rows=6 loops=1) + Task Count: 6 + Tuple data received from nodes: 48 bytes Tasks Shown: None, not supported for re-partition queries -> MapMergeJob Map Task Count: 3 - Merge Task Count: 4 + Merge Task Count: 6 -> MapMergeJob Map Task Count: 3 - Merge Task Count: 4 + Merge Task Count: 6 -- Confirm repartiton join in distributed subplan works EXPLAIN (COSTS off, ANALYZE on, TIMING off, SUMMARY off) WITH repartition AS (SELECT count(*) FROM t1, t2 WHERE t1.a=t2.b) @@ -350,16 +374,16 @@ Custom Scan (Citus Adaptive) (actual rows=1 loops=1) Intermediate Data Size: 14 bytes Result destination: Write locally -> Aggregate (actual rows=1 loops=1) - -> Custom Scan (Citus Adaptive) (actual rows=4 loops=1) - Task Count: 4 - Tuple data received from nodes: 32 bytes + -> Custom Scan (Citus Adaptive) (actual rows=6 loops=1) + Task Count: 6 + Tuple data received from nodes: 48 bytes Tasks Shown: None, not supported for re-partition queries -> MapMergeJob Map Task Count: 3 - Merge Task Count: 4 + Merge Task Count: 6 -> MapMergeJob Map Task Count: 3 - Merge Task Count: 4 + Merge Task Count: 6 Task Count: 1 Tuple data received from nodes: 8 bytes Tasks Shown: All @@ -1108,20 +1132,20 @@ EXPLAIN (COSTS FALSE) AND l_suppkey = s_suppkey; Aggregate -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 Tasks Shown: None, not supported for re-partition queries -> MapMergeJob - Map Task Count: 4 - Merge Task Count: 4 + Map Task Count: 6 + Merge Task Count: 6 -> MapMergeJob Map Task Count: 2 - Merge Task Count: 4 + Merge Task Count: 6 -> MapMergeJob Map Task Count: 1 - Merge Task Count: 4 + Merge Task Count: 6 -> MapMergeJob Map Task Count: 1 - Merge Task Count: 4 + Merge Task Count: 6 EXPLAIN (COSTS FALSE, FORMAT JSON) SELECT count(*) FROM lineitem, orders, customer_append, supplier_single_shard @@ -1135,33 +1159,36 @@ EXPLAIN (COSTS FALSE, FORMAT JSON) "Strategy": "Plain", "Partial Mode": "Simple", "Parallel Aware": false, + "Async Capable": false, "Plans": [ { "Node Type": "Custom Scan", + "Parent Relationship": "Outer", "Custom Plan Provider": "Citus Adaptive", "Parallel Aware": false, + "Async Capable": false, "Distributed Query": { "Job": { - "Task Count": 4, + "Task Count": 6, "Tasks Shown": "None, not supported for re-partition queries", "Dependent Jobs": [ { - "Map Task Count": 4, - "Merge Task Count": 4, + "Map Task Count": 6, + "Merge Task Count": 6, "Dependent Jobs": [ { "Map Task Count": 2, - "Merge Task Count": 4 + "Merge Task Count": 6 }, { "Map Task Count": 1, - "Merge Task Count": 4 + "Merge Task Count": 6 } ] }, { "Map Task Count": 1, - "Merge Task Count": 4 + "Merge Task Count": 6 } ] } @@ -1191,33 +1218,36 @@ EXPLAIN (COSTS FALSE, FORMAT XML) Plain Simple false + false Custom Scan + Outer Citus Adaptive false + false - 4 + 6 None, not supported for re-partition queries - 4 - 4 + 6 + 6 2 - 4 + 6 1 - 4 + 6 1 - 4 + 6 @@ -1258,24 +1288,28 @@ EXPLAIN (COSTS FALSE, FORMAT YAML) Strategy: "Plain" Partial Mode: "Simple" Parallel Aware: false + Async Capable: false Plans: - Node Type: "Custom Scan" + Parent Relationship: "Outer" Custom Plan Provider: "Citus Adaptive" Parallel Aware: false + Async Capable: false Distributed Query: Job: - Task Count: 4 + Task Count: 6 Tasks Shown: "None, not supported for re-partition queries" Dependent Jobs: - Map Task Count: 2 - Merge Task Count: 4 + Merge Task Count: 6 - Map Task Count: 1 - Merge Task Count: 4 + Merge Task Count: 6 -- ensure local plans display correctly CREATE TABLE lineitem_clone (LIKE lineitem); EXPLAIN (COSTS FALSE) SELECT avg(l_linenumber) FROM lineitem_clone; Aggregate -> Seq Scan on lineitem_clone +DROP TABLE lineitem_clone; -- ensure distributed plans don't break EXPLAIN (COSTS FALSE) SELECT avg(l_linenumber) FROM lineitem; Aggregate @@ -1683,6 +1717,7 @@ SELECT explain_analyze_output FROM worker_last_saved_explain_analyze(); "Plan": { + "Node Type": "Result", + "Parallel Aware": false,+ + "Async Capable": false, + "Actual Rows": 1, + "Actual Loops": 1 + }, + @@ -1706,6 +1741,7 @@ SELECT explain_analyze_output FROM worker_last_saved_explain_analyze(); + Result + false + + false + 1 + 1 + + @@ -1727,6 +1763,7 @@ SELECT explain_analyze_output FROM worker_last_saved_explain_analyze(); - Plan: + Node Type: "Result" + Parallel Aware: false+ + Async Capable: false + Actual Rows: 1 + Actual Loops: 1 + Triggers: @@ -2114,6 +2151,7 @@ EXPLAIN (COSTS off, ANALYZE on, TIMING off, SUMMARY off, FORMAT JSON) INSERT INT "Node Type": "Custom Scan", "Custom Plan Provider": "Citus Adaptive", "Parallel Aware": false, + "Async Capable": false, "Actual Rows": 0, "Actual Loops": 1, "Distributed Query": { @@ -2130,6 +2168,7 @@ EXPLAIN (COSTS off, ANALYZE on, TIMING off, SUMMARY off, FORMAT JSON) INSERT INT "Node Type": "ModifyTable", "Operation": "Insert", "Parallel Aware": false, + "Async Capable": false, "Relation Name": "explain_pk_570013", "Alias": "citus_table_alias", "Actual Rows": 0, @@ -2137,7 +2176,9 @@ EXPLAIN (COSTS off, ANALYZE on, TIMING off, SUMMARY off, FORMAT JSON) INSERT INT "Plans": [ { "Node Type": "Result", + "Parent Relationship": "Outer", "Parallel Aware": false, + "Async Capable": false, "Actual Rows": 1, "Actual Loops": 1 } @@ -2166,6 +2207,7 @@ EXPLAIN (COSTS off, ANALYZE on, TIMING off, SUMMARY off, FORMAT JSON) SELECT * F "Node Type": "Custom Scan", "Custom Plan Provider": "Citus Adaptive", "Parallel Aware": false, + "Async Capable": false, "Actual Rows": 0, "Actual Loops": 1, "Distributed Query": { @@ -2183,6 +2225,7 @@ EXPLAIN (COSTS off, ANALYZE on, TIMING off, SUMMARY off, FORMAT JSON) SELECT * F "Plan": { "Node Type": "Seq Scan", "Parallel Aware": false, + "Async Capable": false, "Relation Name": "explain_pk_570013", "Alias": "explain_pk", "Actual Rows": 0, @@ -2211,6 +2254,7 @@ EXPLAIN (COSTS off, ANALYZE on, TIMING off, SUMMARY off, FORMAT XML) INSERT INTO Custom Scan Citus Adaptive false + false 0 1 @@ -2227,6 +2271,7 @@ EXPLAIN (COSTS off, ANALYZE on, TIMING off, SUMMARY off, FORMAT XML) INSERT INTO ModifyTable Insert false + false explain_pk_570013 citus_table_alias 0 @@ -2234,7 +2279,9 @@ EXPLAIN (COSTS off, ANALYZE on, TIMING off, SUMMARY off, FORMAT XML) INSERT INTO Result + Outer false + false 1 1 @@ -2262,6 +2309,7 @@ EXPLAIN (COSTS off, ANALYZE on, TIMING off, SUMMARY off, FORMAT XML) SELECT * FR Custom Scan Citus Adaptive false + false 0 1 @@ -2279,6 +2327,7 @@ EXPLAIN (COSTS off, ANALYZE on, TIMING off, SUMMARY off, FORMAT XML) SELECT * FR Seq Scan false + false explain_pk_570013 explain_pk 0 @@ -2316,7 +2365,7 @@ SELECT count(distinct a) from r NATURAL JOIN ref_table; Custom Scan (Citus Adaptive) (actual rows=1 loops=1) -> Distributed Subplan XXX_1 Intermediate Data Size: 220 bytes - Result destination: Send to 2 nodes + Result destination: Send to 3 nodes -> Custom Scan (Citus Adaptive) (actual rows=10 loops=1) Task Count: 4 Tuple data received from nodes: 120 bytes @@ -2884,6 +2933,7 @@ Custom Scan (Citus Adaptive) (actual rows=0 loops=1) Filter: ((id IS NOT NULL) AND (name = ANY (ARRAY[$1, $2]))) Rows Removed by Filter: 1 deallocate distributed_insert_select; +DROP TABLE simple; -- prepared cte BEGIN; PREPARE cte_query AS @@ -2976,6 +3026,7 @@ Custom Scan (Citus Adaptive) (actual rows=0 loops=1) Node: host=localhost port=xxxxx dbname=regression -> Seq Scan on users_table_2_570028 users_table_2 (actual rows=0 loops=1) Filter: ((value_1)::text = '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000X'::text) +DROP TABLE users_table_2; -- sorted explain analyze output CREATE TABLE explain_analyze_execution_time (a int); INSERT INTO explain_analyze_execution_time VALUES (2); @@ -3140,5 +3191,17 @@ Custom Scan (Citus Adaptive) (actual rows=0 loops=1) -> Update on tbl_570036 tbl (actual rows=0 loops=1) -> Seq Scan on tbl_570036 tbl (actual rows=0 loops=1) Filter: (a = 1) +-- check when auto explain + analyze is enabled, we do not allow local execution. +CREATE SCHEMA test_auto_explain; +SET search_path TO 'test_auto_explain'; +CREATE TABLE test_ref_table (key int PRIMARY KEY); +SELECT create_reference_table('test_ref_table'); + +LOAD 'auto_explain'; +SET auto_explain.log_min_duration = 0; +set auto_explain.log_analyze to true; +-- the following should not be locally executed since explain analyze is on +select * from test_ref_table; +DROP SCHEMA test_auto_explain CASCADE; SET client_min_messages TO ERROR; DROP SCHEMA multi_explain CASCADE; diff --git a/src/test/regress/expected/multi_extension.out b/src/test/regress/expected/multi_extension.out index 0dd52e1ca..ca0c0514a 100644 --- a/src/test/regress/expected/multi_extension.out +++ b/src/test/regress/expected/multi_extension.out @@ -88,10 +88,11 @@ WHERE pgd.refclassid = 'pg_extension'::regclass AND pge.extname = 'citus' AND pgio.schema NOT IN ('pg_catalog', 'citus', 'citus_internal', 'test', 'columnar', 'columnar_internal') ORDER BY 1, 2; - type | identity + type | identity --------------------------------------------------------------------- + view | public.citus_schemas view | public.citus_tables -(1 row) +(2 rows) -- DROP EXTENSION pre-created by the regression suite DROP EXTENSION citus; @@ -1332,6 +1333,22 @@ SELECT * FROM multi_extension.print_extension_changes(); -- Test downgrade to 11.3-1 from 12.0-1 ALTER EXTENSION citus UPDATE TO '12.0-1'; +CREATE TABLE null_shard_key (x int, y int); +SET citus.shard_replication_factor TO 1; +SELECT create_distributed_table('null_shard_key', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- Show that we cannot downgrade to 11.3-1 becuase the cluster has a +-- distributed table with single-shard. +ALTER EXTENSION citus UPDATE TO '11.3-1'; +ERROR: cannot downgrade Citus because there are distributed tables without a shard key. +DETAIL: To downgrade Citus to an older version, you should first convert those tables to Postgres tables by executing SELECT undistribute_table("%s"). +HINT: You can find the distributed tables without a shard key in the cluster by using the following query: "SELECT * FROM citus_tables WHERE distribution_column = '' AND colocation_id > 0". +CONTEXT: PL/pgSQL function inline_code_block line XX at RAISE +DROP TABLE null_shard_key; ALTER EXTENSION citus UPDATE TO '11.3-1'; -- Should be empty result since upgrade+downgrade should be a no-op SELECT * FROM multi_extension.print_extension_changes(); @@ -1342,9 +1359,17 @@ SELECT * FROM multi_extension.print_extension_changes(); -- Snapshot of state at 12.0-1 ALTER EXTENSION citus UPDATE TO '12.0-1'; SELECT * FROM multi_extension.print_extension_changes(); - previous_object | current_object + previous_object | current_object --------------------------------------------------------------------- -(0 rows) + | function citus_internal_add_tenant_schema(oid,integer) void + | function citus_internal_delete_tenant_schema(oid) void + | function citus_internal_unregister_tenant_schema_globally(oid,text) void + | function citus_schema_distribute(regnamespace) void + | function citus_schema_undistribute(regnamespace) void + | function citus_stat_tenants_local_internal(boolean) SETOF record + | table pg_dist_schema + | view public.citus_schemas +(8 rows) DROP TABLE multi_extension.prev_objects, multi_extension.extension_diff; -- show running version @@ -1364,10 +1389,11 @@ WHERE pgd.refclassid = 'pg_extension'::regclass AND pge.extname = 'citus' AND pgio.schema NOT IN ('pg_catalog', 'citus', 'citus_internal', 'test', 'columnar', 'columnar_internal') ORDER BY 1, 2; - type | identity + type | identity --------------------------------------------------------------------- + view | public.citus_schemas view | public.citus_tables -(1 row) +(2 rows) -- see incompatible version errors out RESET citus.enable_version_checks; @@ -1475,10 +1501,11 @@ ALTER EXTENSION citus UPDATE; -- if cache is invalidated succesfull, this \d should work without any problem \d List of relations - Schema | Name | Type | Owner + Schema | Name | Type | Owner --------------------------------------------------------------------- - public | citus_tables | view | postgres -(1 row) + public | citus_schemas | view | postgres + public | citus_tables | view | postgres +(2 rows) \c - - - :master_port -- test https://github.com/citusdata/citus/issues/3409 @@ -1735,6 +1762,80 @@ HINT: To remove the local data, run: SELECT truncate_local_data_after_distribut DROP TABLE test; TRUNCATE pg_dist_node; +-- confirm that we can create a single-shard table on an empty node +CREATE TABLE test (x int, y int); +INSERT INTO test VALUES (1,2); +SET citus.shard_replication_factor TO 1; +SELECT create_distributed_table('test', null, colocate_with=>'none', distribution_type=>null); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$public.test$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- and make sure that we can't remove the coordinator due to "test" +SELECT citus_remove_node('localhost', :master_port); +ERROR: cannot remove or disable the node localhost:xxxxx because because it contains the only shard placement for shard xxxxx +DETAIL: One of the table(s) that prevents the operation complete successfully is public.test +HINT: To proceed, either drop the tables or use undistribute_table() function to convert them to local tables +DROP TABLE test; +-- and now we should be able to remove the coordinator +SELECT citus_remove_node('localhost', :master_port); + citus_remove_node +--------------------------------------------------------------------- + +(1 row) + +-- confirm that we can create a tenant schema / table on an empty node +SET citus.enable_schema_based_sharding TO ON; +CREATE SCHEMA tenant_schema; +CREATE TABLE tenant_schema.test(x int, y int); +SELECT colocationid = ( + SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_schema.test'::regclass +) +FROM pg_dist_schema +WHERE schemaid::regnamespace::text = 'tenant_schema'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- and make sure that we can't remove the coordinator due to "test" +SELECT citus_remove_node('localhost', :master_port); +ERROR: cannot remove or disable the node localhost:xxxxx because because it contains the only shard placement for shard xxxxx +DETAIL: One of the table(s) that prevents the operation complete successfully is tenant_schema.test +HINT: To proceed, either drop the tables or use undistribute_table() function to convert them to local tables +BEGIN; + SET LOCAL client_min_messages TO WARNING; + DROP SCHEMA tenant_schema CASCADE; +COMMIT; +-- and now we should be able to remove the coordinator +SELECT citus_remove_node('localhost', :master_port); + citus_remove_node +--------------------------------------------------------------------- + +(1 row) + +CREATE SCHEMA tenant_schema; +-- Make sure that we can sync metadata for empty tenant schemas +-- when adding the first node to the cluster. +SELECT 1 FROM citus_add_node('localhost', :worker_1_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +DROP SCHEMA tenant_schema; +SELECT citus_remove_node('localhost', :worker_1_port); + citus_remove_node +--------------------------------------------------------------------- + +(1 row) + +RESET citus.enable_schema_based_sharding; -- confirm that we can create a reference table on an empty node CREATE TABLE test (x int, y int); INSERT INTO test VALUES (1,2); @@ -1760,6 +1861,19 @@ SELECT citus_add_local_table_to_metadata('test'); (1 row) DROP TABLE test; +-- Verify that we don't consider the schemas created by extensions as tenant schemas. +-- Easiest way of verifying this is to drop and re-create columnar extension. +DROP EXTENSION citus_columnar; +SET citus.enable_schema_based_sharding TO ON; +CREATE EXTENSION citus_columnar; +SELECT COUNT(*)=0 FROM pg_dist_schema +WHERE schemaid IN ('columnar'::regnamespace, 'columnar_internal'::regnamespace); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +RESET citus.enable_schema_based_sharding; DROP EXTENSION citus; CREATE EXTENSION citus; DROP TABLE version_mismatch_table; diff --git a/src/test/regress/expected/multi_fix_partition_shard_index_names.out b/src/test/regress/expected/multi_fix_partition_shard_index_names.out index fea9c67a5..e6795317c 100644 --- a/src/test/regress/expected/multi_fix_partition_shard_index_names.out +++ b/src/test/regress/expected/multi_fix_partition_shard_index_names.out @@ -495,14 +495,6 @@ SET search_path TO fix_idx_names, public; DROP TABLE dist_partitioned_table; SET citus.next_shard_id TO 910040; -- test with citus local table -SET client_min_messages TO WARNING; -SELECT 1 FROM citus_add_node('localhost', :master_port, groupid=>0); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - -RESET client_min_messages; CREATE TABLE date_partitioned_citus_local_table( measureid integer, eventdate date, @@ -650,7 +642,7 @@ NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_ DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing SET citus.enable_ddl_propagation TO 'off' DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE EXTENSION IF NOT EXISTS citus_columnar WITH SCHEMA pg_catalog VERSION "11.2-1"; +NOTICE: issuing CREATE EXTENSION IF NOT EXISTS citus_columnar WITH SCHEMA pg_catalog VERSION "x.y-z"; DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing COMMIT DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx @@ -658,7 +650,7 @@ NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_ DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing SET citus.enable_ddl_propagation TO 'off' DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE EXTENSION IF NOT EXISTS citus_columnar WITH SCHEMA pg_catalog VERSION "11.2-1"; +NOTICE: issuing CREATE EXTENSION IF NOT EXISTS citus_columnar WITH SCHEMA pg_catalog VERSION "x.y-z"; DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing COMMIT DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx @@ -750,9 +742,3 @@ ALTER TABLE parent_table DROP CONSTRAINT pkey_cst CASCADE; ALTER TABLE parent_table DROP CONSTRAINT unique_cst CASCADE; SET client_min_messages TO WARNING; DROP SCHEMA fix_idx_names CASCADE; -SELECT citus_remove_node('localhost', :master_port); - citus_remove_node ---------------------------------------------------------------------- - -(1 row) - diff --git a/src/test/regress/expected/multi_foreign_key.out b/src/test/regress/expected/multi_foreign_key.out index 7efa9d61c..832be2740 100644 --- a/src/test/regress/expected/multi_foreign_key.out +++ b/src/test/regress/expected/multi_foreign_key.out @@ -856,11 +856,16 @@ SELECT create_reference_table('reference_table_second'); CREATE TABLE referenced_local_table(id int PRIMARY KEY, other_column int); DROP TABLE reference_table CASCADE; NOTICE: drop cascades to constraint reference_table_second_referencing_column_fkey on table reference_table_second +NOTICE: drop cascades to constraint reference_table_second_referencing_column_fkey_1350654 on table public.reference_table_second_1350654 +CONTEXT: SQL statement "SELECT citus_drop_all_shards(v_obj.objid, v_obj.schema_name, v_obj.object_name, drop_shards_metadata_only := false)" +PL/pgSQL function citus_drop_trigger() line XX at PERFORM CREATE TABLE reference_table(id int, referencing_column int REFERENCES referenced_local_table(id)); SELECT create_reference_table('reference_table'); -ERROR: referenced table "referenced_local_table" must be a distributed table or a reference table -DETAIL: To enforce foreign keys, the referencing and referenced rows need to be stored on the same node. -HINT: You could use SELECT create_reference_table('referenced_local_table') to replicate the referenced table to all nodes or consider dropping the foreign key + create_reference_table +--------------------------------------------------------------------- + +(1 row) + -- test foreign key creation on CREATE TABLE on self referencing reference table CREATE TABLE self_referencing_reference_table( id int, @@ -877,6 +882,7 @@ SELECT create_reference_table('self_referencing_reference_table'); -- test foreign key creation on ALTER TABLE from reference table DROP TABLE reference_table; +NOTICE: removing table public.referenced_local_table from metadata as it is not connected to any reference tables via foreign keys CREATE TABLE reference_table(id int PRIMARY KEY, referencing_column int); SELECT create_reference_table('reference_table'); create_reference_table @@ -911,6 +917,9 @@ DROP TABLE reference_table CASCADE; NOTICE: drop cascades to 2 other objects DETAIL: drop cascades to constraint fk on table references_to_reference_table drop cascades to constraint fk on table reference_table_second +NOTICE: drop cascades to constraint fk_1350663 on table public.reference_table_second_1350663 +CONTEXT: SQL statement "SELECT citus_drop_all_shards(v_obj.objid, v_obj.schema_name, v_obj.object_name, drop_shards_metadata_only := false)" +PL/pgSQL function citus_drop_trigger() line XX at PERFORM CREATE TABLE reference_table(id int PRIMARY KEY, referencing_column int); SELECT create_reference_table('reference_table'); create_reference_table @@ -919,9 +928,6 @@ SELECT create_reference_table('reference_table'); (1 row) ALTER TABLE reference_table ADD CONSTRAINT fk FOREIGN KEY(referencing_column) REFERENCES referenced_local_table(id); -ERROR: referenced table "referenced_local_table" must be a distributed table or a reference table -DETAIL: To enforce foreign keys, the referencing and referenced rows need to be stored on the same node. -HINT: You could use SELECT create_reference_table('referenced_local_table') to replicate the referenced table to all nodes or consider dropping the foreign key -- test foreign key creation on ALTER TABLE on self referencing reference table DROP TABLE self_referencing_reference_table; CREATE TABLE self_referencing_reference_table( @@ -1187,12 +1193,7 @@ CREATE TABLE set_on_default_test_referencing( REFERENCES set_on_default_test_referenced(col_1, col_3) ON UPDATE SET DEFAULT ); --- from distributed / reference to reference, fkey exists before calling the UDFs -SELECT create_distributed_table('set_on_default_test_referencing', 'col_1'); ERROR: cannot create foreign key constraint since Citus does not support ON DELETE / UPDATE SET DEFAULT actions on the columns that default to sequences -SELECT create_reference_table('set_on_default_test_referencing'); -ERROR: cannot create foreign key constraint since Citus does not support ON DELETE / UPDATE SET DEFAULT actions on the columns that default to sequences -DROP TABLE set_on_default_test_referencing; CREATE TABLE set_on_default_test_referencing( col_1 serial, col_2 int, col_3 int, col_4 int ); @@ -1276,3 +1277,6 @@ ERROR: cannot create foreign key constraint since Citus does not support ON DEL -- we no longer need those tables DROP TABLE referenced_by_reference_table, references_to_reference_table, reference_table, reference_table_second, referenced_local_table, self_referencing_reference_table, dropfkeytest2, set_on_default_test_referenced, set_on_default_test_referencing; +NOTICE: drop cascades to constraint fk_1350664 on table public.reference_table_1350664 +CONTEXT: SQL statement "SELECT citus_drop_all_shards(v_obj.objid, v_obj.schema_name, v_obj.object_name, drop_shards_metadata_only := false)" +PL/pgSQL function citus_drop_trigger() line XX at PERFORM diff --git a/src/test/regress/expected/multi_function_in_join.out b/src/test/regress/expected/multi_function_in_join.out index 7d62e286b..f77860945 100644 --- a/src/test/regress/expected/multi_function_in_join.out +++ b/src/test/regress/expected/multi_function_in_join.out @@ -57,7 +57,6 @@ $$ LANGUAGE plpgsql; DEBUG: switching to sequential query execution mode DETAIL: A command for a distributed function is run. To make sure subsequent commands see the function correctly we need to make sure to use only one connection for all future commands SELECT * FROM table1 JOIN increment(2) val ON (id = val) ORDER BY id ASC; -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT val FROM functions_in_joins.increment(2) val(val) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT table1.id, table1.data, val.val FROM (functions_in_joins.table1 JOIN (SELECT intermediate_result.val FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(val integer)) val ON ((table1.id OPERATOR(pg_catalog.=) val.val))) ORDER BY table1.id id | data | val @@ -96,7 +95,6 @@ LANGUAGE SQL; DEBUG: switching to sequential query execution mode DETAIL: A command for a distributed function is run. To make sure subsequent commands see the function correctly we need to make sure to use only one connection for all future commands SELECT * FROM table1 JOIN get_set_of_records() AS t2(x int, y int) ON (id = x) ORDER BY id ASC; -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT x, y FROM functions_in_joins.get_set_of_records() t2(x integer, y integer) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT table1.id, table1.data, t2.x, t2.y FROM (functions_in_joins.table1 JOIN (SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer)) t2 ON ((table1.id OPERATOR(pg_catalog.=) t2.x))) ORDER BY table1.id id | data | x | y @@ -114,7 +112,6 @@ LANGUAGE SQL; DEBUG: switching to sequential query execution mode DETAIL: A command for a distributed function is run. To make sure subsequent commands see the function correctly we need to make sure to use only one connection for all future commands SELECT f.* FROM table1 t JOIN dup(32) f ON (f1 = id); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT f1, f2 FROM functions_in_joins.dup(32) f(f1, f2) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT f.f1, f.f2 FROM (functions_in_joins.table1 t JOIN (SELECT intermediate_result.f1, intermediate_result.f2 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(f1 integer, f2 text)) f ON ((f.f1 OPERATOR(pg_catalog.=) t.id))) f1 | f2 @@ -128,7 +125,6 @@ CREATE OR REPLACE FUNCTION the_minimum_id() DEBUG: switching to sequential query execution mode DETAIL: A command for a distributed function is run. To make sure subsequent commands see the function correctly we need to make sure to use only one connection for all future commands SELECT * FROM table1 JOIN the_minimum_id() min_id ON (id = min_id); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT min_id FROM functions_in_joins.the_minimum_id() min_id(min_id) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT table1.id, table1.data, min_id.min_id FROM (functions_in_joins.table1 JOIN (SELECT intermediate_result.min_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(min_id integer)) min_id ON ((table1.id OPERATOR(pg_catalog.=) min_id.min_id))) id | data | min_id @@ -192,7 +188,6 @@ $$ language plpgsql; DEBUG: switching to sequential query execution mode DETAIL: A command for a distributed function is run. To make sure subsequent commands see the function correctly we need to make sure to use only one connection for all future commands SELECT * FROM table1 JOIN max_and_min() m ON (m.maximum = data OR m.minimum = data) ORDER BY 1,2,3,4; -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT minimum, maximum FROM functions_in_joins.max_and_min() m(minimum, maximum) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT table1.id, table1.data, m.minimum, m.maximum FROM (functions_in_joins.table1 JOIN (SELECT intermediate_result.minimum, intermediate_result.maximum FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(minimum integer, maximum integer)) m ON (((m.maximum OPERATOR(pg_catalog.=) table1.data) OR (m.minimum OPERATOR(pg_catalog.=) table1.data)))) ORDER BY table1.id, table1.data, m.minimum, m.maximum id | data | minimum | maximum diff --git a/src/test/regress/expected/multi_insert_select.out b/src/test/regress/expected/multi_insert_select.out index ed836d398..ac339a620 100644 --- a/src/test/regress/expected/multi_insert_select.out +++ b/src/test/regress/expected/multi_insert_select.out @@ -620,7 +620,8 @@ INSERT INTO agg_events (value_1_agg, user_id) DISTINCT ON (value_1) value_1, user_id FROM raw_events_first; -DEBUG: DISTINCT ON (non-partition column) clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Distinct on columns without partition column is currently unsupported DEBUG: Router planner cannot handle multi-shard select queries DEBUG: Collecting INSERT ... SELECT results on coordinator SELECT user_id, value_1_agg FROM agg_events ORDER BY 1,2; @@ -686,7 +687,7 @@ DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT user_id, v1_a DEBUG: Creating router plan DEBUG: Collecting INSERT ... SELECT results on coordinator ROLLBACK; --- We don't support CTEs that are referenced in the target list +-- We do support CTEs that are referenced in the target list INSERT INTO agg_events WITH sub_cte AS (SELECT 1) SELECT @@ -694,15 +695,11 @@ INSERT INTO agg_events FROM raw_events_first; DEBUG: CTE sub_cte is going to be inlined via distributed planning -DEBUG: Subqueries without relations are not allowed in distributed INSERT ... SELECT queries -DEBUG: Router planner cannot handle multi-shard select queries -DEBUG: performing repartitioned INSERT ... SELECT -DEBUG: partitioning SELECT query by column index 0 with name 'user_id' -DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT intermediate_result.user_id, intermediate_result.value_1_agg FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300000_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1_agg integer) -DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT intermediate_result.user_id, intermediate_result.value_1_agg FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300001_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1_agg integer) -DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT intermediate_result.user_id, intermediate_result.value_1_agg FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300002_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1_agg integer) -DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT intermediate_result.user_id, intermediate_result.value_1_agg FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300003_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1_agg integer) --- We support set operations via the coordinator +DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT raw_events_first.user_id, (SELECT sub_cte."?column?" FROM (SELECT 1) sub_cte("?column?")) FROM multi_insert_select.raw_events_first_13300000 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT raw_events_first.user_id, (SELECT sub_cte."?column?" FROM (SELECT 1) sub_cte("?column?")) FROM multi_insert_select.raw_events_first_13300001 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT raw_events_first.user_id, (SELECT sub_cte."?column?" FROM (SELECT 1) sub_cte("?column?")) FROM multi_insert_select.raw_events_first_13300002 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT raw_events_first.user_id, (SELECT sub_cte."?column?" FROM (SELECT 1) sub_cte("?column?")) FROM multi_insert_select.raw_events_first_13300003 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) +-- We support set operations BEGIN; INSERT INTO raw_events_first(user_id) @@ -711,14 +708,10 @@ SELECT FROM ((SELECT user_id FROM raw_events_first) UNION (SELECT user_id FROM raw_events_second)) as foo; -DEBUG: Set operations are not allowed in distributed INSERT ... SELECT queries -DEBUG: Router planner cannot handle multi-shard select queries -DEBUG: performing repartitioned INSERT ... SELECT -DEBUG: partitioning SELECT query by column index 0 with name 'user_id' -DEBUG: distributed statement: INSERT INTO multi_insert_select.raw_events_first_13300000 AS citus_table_alias (user_id) SELECT intermediate_result.user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300004_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) -DEBUG: distributed statement: INSERT INTO multi_insert_select.raw_events_first_13300001 AS citus_table_alias (user_id) SELECT intermediate_result.user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300005_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) -DEBUG: distributed statement: INSERT INTO multi_insert_select.raw_events_first_13300002 AS citus_table_alias (user_id) SELECT intermediate_result.user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300006_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) -DEBUG: distributed statement: INSERT INTO multi_insert_select.raw_events_first_13300003 AS citus_table_alias (user_id) SELECT intermediate_result.user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300007_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) +DEBUG: distributed statement: INSERT INTO multi_insert_select.raw_events_first_13300000 AS citus_table_alias (user_id) SELECT foo.user_id FROM (SELECT raw_events_first.user_id FROM multi_insert_select.raw_events_first_13300000 raw_events_first UNION SELECT raw_events_second.user_id FROM multi_insert_select.raw_events_second_13300004 raw_events_second) foo WHERE (foo.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO multi_insert_select.raw_events_first_13300001 AS citus_table_alias (user_id) SELECT foo.user_id FROM (SELECT raw_events_first.user_id FROM multi_insert_select.raw_events_first_13300001 raw_events_first UNION SELECT raw_events_second.user_id FROM multi_insert_select.raw_events_second_13300005 raw_events_second) foo WHERE (foo.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO multi_insert_select.raw_events_first_13300002 AS citus_table_alias (user_id) SELECT foo.user_id FROM (SELECT raw_events_first.user_id FROM multi_insert_select.raw_events_first_13300002 raw_events_first UNION SELECT raw_events_second.user_id FROM multi_insert_select.raw_events_second_13300006 raw_events_second) foo WHERE (foo.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO multi_insert_select.raw_events_first_13300003 AS citus_table_alias (user_id) SELECT foo.user_id FROM (SELECT raw_events_first.user_id FROM multi_insert_select.raw_events_first_13300003 raw_events_first UNION SELECT raw_events_second.user_id FROM multi_insert_select.raw_events_second_13300007 raw_events_second) foo WHERE (foo.user_id IS NOT NULL) ROLLBACK; -- We do support set operations through recursive planning BEGIN; @@ -727,7 +720,7 @@ INSERT INTO raw_events_first(user_id) (SELECT user_id FROM raw_events_first) INTERSECT (SELECT user_id FROM raw_events_first); -DEBUG: Set operations are not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: Router planner cannot handle multi-shard select queries DEBUG: Router planner cannot handle multi-shard select queries DEBUG: generating subplan XXX_1 for subquery SELECT user_id FROM multi_insert_select.raw_events_first @@ -747,7 +740,7 @@ SELECT FROM ((SELECT user_id FROM raw_events_first WHERE user_id = 15) EXCEPT (SELECT user_id FROM raw_events_second where user_id = 17)) as foo; -DEBUG: Set operations are not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: Creating router plan DEBUG: Collecting INSERT ... SELECT results on coordinator -- some supported LEFT joins @@ -765,10 +758,16 @@ DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_133000 raw_events_second.user_id FROM reference_table LEFT JOIN raw_events_second ON reference_table.user_id = raw_events_second.user_id; -DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300008 AS citus_table_alias (user_id) SELECT raw_events_second.user_id FROM (multi_insert_select.reference_table_13300012 reference_table LEFT JOIN multi_insert_select.raw_events_second_13300004 raw_events_second ON ((reference_table.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE (raw_events_second.user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300009 AS citus_table_alias (user_id) SELECT raw_events_second.user_id FROM (multi_insert_select.reference_table_13300012 reference_table LEFT JOIN multi_insert_select.raw_events_second_13300005 raw_events_second ON ((reference_table.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE (raw_events_second.user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300010 AS citus_table_alias (user_id) SELECT raw_events_second.user_id FROM (multi_insert_select.reference_table_13300012 reference_table LEFT JOIN multi_insert_select.raw_events_second_13300006 raw_events_second ON ((reference_table.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE (raw_events_second.user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300011 AS citus_table_alias (user_id) SELECT raw_events_second.user_id FROM (multi_insert_select.reference_table_13300012 reference_table LEFT JOIN multi_insert_select.raw_events_second_13300007 raw_events_second ON ((reference_table.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE (raw_events_second.user_id IS NOT NULL) +DEBUG: cannot perform a lateral outer join when a distributed subquery references a reference table +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: recursively planning right side of the left join since the outer side is a recurring rel +DEBUG: recursively planning distributed relation "raw_events_second" since it is part of a distributed join node that is outer joined with a recurring rel +DEBUG: Wrapping relation "raw_events_second" to a subquery +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for subquery SELECT user_id FROM multi_insert_select.raw_events_second WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT raw_events_second.user_id FROM (multi_insert_select.reference_table LEFT JOIN (SELECT raw_events_second_1.user_id, NULL::timestamp without time zone AS "time", NULL::integer AS value_1, NULL::integer AS value_2, NULL::double precision AS value_3, NULL::bigint AS value_4 FROM (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) raw_events_second_1) raw_events_second ON ((reference_table.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator INSERT INTO agg_events (user_id) SELECT raw_events_first.user_id @@ -885,7 +884,7 @@ $Q$); Custom Scan (Citus INSERT ... SELECT) INSERT/SELECT method: pull to coordinator -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 (4 rows) -- EXPLAIN ANALYZE is not supported for INSERT ... SELECT via coordinator @@ -922,7 +921,7 @@ $Q$); Custom Scan (Citus INSERT ... SELECT) INSERT/SELECT method: pull to coordinator -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 (4 rows) -- make things a bit more complicate with IN clauses @@ -941,7 +940,7 @@ $Q$); Custom Scan (Citus INSERT ... SELECT) INSERT/SELECT method: pull to coordinator -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 (4 rows) -- implicit join on non partition column should also not be pushed down, @@ -960,7 +959,7 @@ $Q$); Custom Scan (Citus INSERT ... SELECT) INSERT/SELECT method: pull to coordinator -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 (4 rows) RESET client_min_messages; @@ -982,7 +981,7 @@ $Q$); Custom Scan (Citus INSERT ... SELECT) INSERT/SELECT method: pull to coordinator -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 (4 rows) -- foo is not joined on the partition key so the query is not @@ -1047,7 +1046,7 @@ $Q$); Custom Scan (Citus INSERT ... SELECT) INSERT/SELECT method: pull to coordinator -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 (4 rows) -- foo is not joined on the partition key so the query is not @@ -1126,7 +1125,8 @@ FROM (SELECT SUM(raw_events_second.value_4) AS v4, raw_events_second WHERE raw_events_first.user_id = raw_events_second.user_id GROUP BY raw_events_second.value_3) AS foo; -DEBUG: Group by list without distribution column is not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Group by list without partition column is currently unsupported when a subquery references a column from another query DEBUG: Router planner cannot handle multi-shard select queries DEBUG: Router planner cannot handle multi-shard select queries DEBUG: join prunable for intervals [-2147483648,-1073741825] and [-1073741824,-1] @@ -1281,7 +1281,7 @@ FROM (SELECT SUM(raw_events_second.value_4) AS v4, GROUP BY raw_events_second.value_1 HAVING SUM(raw_events_second.value_4) > 10) AS foo2 ) as f2 ON (f.id = f2.id); -DEBUG: Group by list without distribution column is not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: Router planner cannot handle multi-shard select queries DEBUG: Router planner cannot handle multi-shard select queries DEBUG: join prunable for intervals [-2147483648,-1073741825] and [-1073741824,-1] @@ -1327,7 +1327,7 @@ FROM (SELECT SUM(raw_events_second.value_4) AS v4, GROUP BY raw_events_second.value_1 HAVING SUM(raw_events_second.value_4) > 10) AS foo2 ) as f2 ON (f.id = f2.id); -DEBUG: Group by list without distribution column is not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: Router planner cannot handle multi-shard select queries DEBUG: Router planner cannot handle multi-shard select queries DEBUG: join prunable for intervals [-2147483648,-1073741825] and [-1073741824,-1] @@ -1437,7 +1437,7 @@ $Q$); Group Key: remote_scan.id Filter: (pg_catalog.sum(remote_scan.worker_column_4) > '10'::numeric) -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 (11 rows) -- cannot push down since foo doesn't have en equi join @@ -1514,7 +1514,7 @@ $Q$); -> HashAggregate Group Key: remote_scan.user_id -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 (6 rows) -- join among reference_ids and averages is not on the partition key @@ -1576,7 +1576,7 @@ $Q$); Custom Scan (Citus INSERT ... SELECT) INSERT/SELECT method: pull to coordinator -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 (4 rows) -- Selected value in the WHERE is not partition key, so we cannot use distributed @@ -3242,5 +3242,236 @@ returning text_col_1; string (1 row) +CREATE TABLE dist_table_3( +dist_col bigint, +int_col integer +); +SELECT create_distributed_table('dist_table_3', 'dist_col'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- dist_table_2 and dist_table_3 are non-colocated source tables. Repartitioning is also not possible due to +-- different types for distribution columns. Citus would not be able to handle this complex insert select. +INSERT INTO dist_table_1 SELECT dist_table_2.dist_col FROM dist_table_2 JOIN dist_table_3 USING(dist_col); +ERROR: complex joins are only supported when all distributed tables are joined on their distribution columns with equal operator +CREATE TABLE dist_table_4( +dist_col integer, +int_col integer +); +SELECT create_distributed_table('dist_table_4', 'dist_col'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- Even if target table distribution column is colocated with dist_table_2's distributed column, source tables dist_table_2 and dist_table_4 +-- are non-colocated. Hence, SELECT part of the query should be pulled to coordinator. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_1 SELECT dist_table_2.dist_col FROM dist_table_2 JOIN dist_table_4 ON dist_table_2.dist_col = dist_table_4.int_col; +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + Task Count: 6 +(4 rows) + +-- For INSERT SELECT, when a lateral query references an outer query, push-down is possible even if limit clause exists in the lateral query. +-- It is because subquery with limit does not need to be merged at coordinator as it is a lateral query. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_1 SELECT d1.dist_col FROM dist_table_1 d1 LEFT JOIN LATERAL (SELECT * FROM dist_table_2 d2 WHERE d1.dist_col = d2.dist_col LIMIT 3) dummy USING(dist_col); +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + Task Count: 4 +(2 rows) + +-- For INSERT SELECT, when push-down is NOT possible when limit clause exists in a subquery at SELECT part of INSERT SELECT. +-- It is because the subquery with limit needs to be merged at coordinator. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_1 SELECT d1.dist_col FROM dist_table_1 d1 LEFT JOIN (SELECT * FROM dist_table_2 LIMIT 3) dummy USING(dist_col); +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: repartition + -> Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Limit + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(7 rows) + +CREATE TABLE dist_table_5(id int, id2 int); +SELECT create_distributed_table('dist_table_5','id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE dist_table_6(id int, id2 int); +SELECT create_distributed_table('dist_table_6','id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- verify that insert select with union can be pushed down since UNION clause has FROM clause at top level query. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5(id) SELECT id FROM (SELECT id FROM dist_table_5 UNION SELECT id FROM dist_table_6) dummy; +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + Task Count: 4 +(2 rows) + +-- verify that insert select with sublink can be pushed down when tables are colocated. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT id, (SELECT id FROM dist_table_5 WHERE dist_table_5.id = dist_table_6.id) FROM dist_table_6; +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + Task Count: 4 +(2 rows) + +CREATE TABLE ref_table_1(id int); +SELECT create_reference_table('ref_table_1'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +-- verify that insert select with sublink cannot be pushed down when from clause does not contain any distributed relation. +INSERT INTO dist_table_5 SELECT id, (SELECT id FROM dist_table_5 WHERE dist_table_5.id = ref_table_1.id) FROM ref_table_1; +ERROR: correlated subqueries are not supported when the FROM clause contains a reference table +-- verify that insert select cannot be pushed down when we have recurring range table in from clause. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT id, (SELECT id FROM ref_table_1 WHERE id = 1) FROM ref_table_1; +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + Task Count: 1 +(4 rows) + +-- verify that insert select cannot be pushed down when we have reference table in outside of outer join. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT a.id FROM dist_table_5 a LEFT JOIN ref_table_1 b ON (true) RIGHT JOIN ref_table_1 c ON (true); +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(6 rows) + +-- verify that insert select cannot be pushed down when it has a recurring outer join in a subquery. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT id FROM ref_table_1 LEFT JOIN dist_table_5 USING(id); +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(6 rows) + +CREATE TABLE loc_table_1(id int); +-- verify that insert select cannot be pushed down when it contains join between local and distributed tables. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT id FROM dist_table_5 JOIN loc_table_1 USING(id); +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: repartition + -> Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Seq Scan on loc_table_1 + Task Count: 4 +(6 rows) + +CREATE VIEW view_1 AS + SELECT id FROM dist_table_6; +CREATE MATERIALIZED VIEW view_2 AS + SELECT id FROM dist_table_6; +-- verify that insert select cannot be pushed down when it contains view. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT * FROM view_1; +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + Task Count: 4 +(2 rows) + +-- verify that insert select cannot be pushed down when it contains materialized view. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT * FROM view_2; +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Seq Scan on view_2 +(3 rows) + +CREATE TABLE append_table(id integer, data text, int_data int); +SELECT create_distributed_table('append_table', 'id', 'append'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT master_create_empty_shard('append_table'); + master_create_empty_shard +--------------------------------------------------------------------- + 13300096 +(1 row) + +-- verify that insert select push down for append tables are not supported. +INSERT INTO append_table SELECT * FROM append_table; +ERROR: INSERT ... SELECT into an append-distributed table is not supported +-- verify that CTEs at top level of INSERT SELECT, that can normally be inlined, would not be inlined by INSERT SELECT pushdown planner +-- and handled by pull to coordinator. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) WITH cte_1 AS (SELECT id FROM dist_table_5 WHERE id = 5) + INSERT INTO dist_table_5 + SELECT id FROM dist_table_5 JOIN cte_1 USING(id); +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + Task Count: 1 +(4 rows) + +-- verify that CTEs at top level of SELECT part, would be inlined by Postgres and pushed down by INSERT SELECT planner. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 + WITH cte_1 AS (SELECT id FROM dist_table_5 WHERE id = 5) + SELECT id FROM dist_table_5 JOIN cte_1 USING(id); +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + Task Count: 1 +(2 rows) + SET client_min_messages TO ERROR; DROP SCHEMA multi_insert_select CASCADE; diff --git a/src/test/regress/expected/multi_insert_select_0.out b/src/test/regress/expected/multi_insert_select_0.out index efc845e88..a4988bceb 100644 --- a/src/test/regress/expected/multi_insert_select_0.out +++ b/src/test/regress/expected/multi_insert_select_0.out @@ -620,7 +620,8 @@ INSERT INTO agg_events (value_1_agg, user_id) DISTINCT ON (value_1) value_1, user_id FROM raw_events_first; -DEBUG: DISTINCT ON (non-partition column) clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Distinct on columns without partition column is currently unsupported DEBUG: Router planner cannot handle multi-shard select queries DEBUG: Collecting INSERT ... SELECT results on coordinator SELECT user_id, value_1_agg FROM agg_events ORDER BY 1,2; @@ -686,7 +687,7 @@ DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT user_id, v1_a DEBUG: Creating router plan DEBUG: Collecting INSERT ... SELECT results on coordinator ROLLBACK; --- We don't support CTEs that are referenced in the target list +-- We do support CTEs that are referenced in the target list INSERT INTO agg_events WITH sub_cte AS (SELECT 1) SELECT @@ -694,15 +695,11 @@ INSERT INTO agg_events FROM raw_events_first; DEBUG: CTE sub_cte is going to be inlined via distributed planning -DEBUG: Subqueries without relations are not allowed in distributed INSERT ... SELECT queries -DEBUG: Router planner cannot handle multi-shard select queries -DEBUG: performing repartitioned INSERT ... SELECT -DEBUG: partitioning SELECT query by column index 0 with name 'user_id' -DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, value_1_agg FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300000_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1_agg integer) -DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, value_1_agg FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300001_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1_agg integer) -DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, value_1_agg FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300002_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1_agg integer) -DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, value_1_agg FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300003_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1_agg integer) --- We support set operations via the coordinator +DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, (SELECT sub_cte."?column?" FROM (SELECT 1) sub_cte("?column?")) FROM multi_insert_select.raw_events_first_13300000 raw_events_first WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, (SELECT sub_cte."?column?" FROM (SELECT 1) sub_cte("?column?")) FROM multi_insert_select.raw_events_first_13300001 raw_events_first WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, (SELECT sub_cte."?column?" FROM (SELECT 1) sub_cte("?column?")) FROM multi_insert_select.raw_events_first_13300002 raw_events_first WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, (SELECT sub_cte."?column?" FROM (SELECT 1) sub_cte("?column?")) FROM multi_insert_select.raw_events_first_13300003 raw_events_first WHERE (user_id IS NOT NULL) +-- We support set operations BEGIN; INSERT INTO raw_events_first(user_id) @@ -711,14 +708,10 @@ SELECT FROM ((SELECT user_id FROM raw_events_first) UNION (SELECT user_id FROM raw_events_second)) as foo; -DEBUG: Set operations are not allowed in distributed INSERT ... SELECT queries -DEBUG: Router planner cannot handle multi-shard select queries -DEBUG: performing repartitioned INSERT ... SELECT -DEBUG: partitioning SELECT query by column index 0 with name 'user_id' -DEBUG: distributed statement: INSERT INTO multi_insert_select.raw_events_first_13300000 AS citus_table_alias (user_id) SELECT user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300004_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) -DEBUG: distributed statement: INSERT INTO multi_insert_select.raw_events_first_13300001 AS citus_table_alias (user_id) SELECT user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300005_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) -DEBUG: distributed statement: INSERT INTO multi_insert_select.raw_events_first_13300002 AS citus_table_alias (user_id) SELECT user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300006_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) -DEBUG: distributed statement: INSERT INTO multi_insert_select.raw_events_first_13300003 AS citus_table_alias (user_id) SELECT user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300007_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) +DEBUG: distributed statement: INSERT INTO multi_insert_select.raw_events_first_13300000 AS citus_table_alias (user_id) SELECT user_id FROM (SELECT raw_events_first.user_id FROM multi_insert_select.raw_events_first_13300000 raw_events_first UNION SELECT raw_events_second.user_id FROM multi_insert_select.raw_events_second_13300004 raw_events_second) foo WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO multi_insert_select.raw_events_first_13300001 AS citus_table_alias (user_id) SELECT user_id FROM (SELECT raw_events_first.user_id FROM multi_insert_select.raw_events_first_13300001 raw_events_first UNION SELECT raw_events_second.user_id FROM multi_insert_select.raw_events_second_13300005 raw_events_second) foo WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO multi_insert_select.raw_events_first_13300002 AS citus_table_alias (user_id) SELECT user_id FROM (SELECT raw_events_first.user_id FROM multi_insert_select.raw_events_first_13300002 raw_events_first UNION SELECT raw_events_second.user_id FROM multi_insert_select.raw_events_second_13300006 raw_events_second) foo WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO multi_insert_select.raw_events_first_13300003 AS citus_table_alias (user_id) SELECT user_id FROM (SELECT raw_events_first.user_id FROM multi_insert_select.raw_events_first_13300003 raw_events_first UNION SELECT raw_events_second.user_id FROM multi_insert_select.raw_events_second_13300007 raw_events_second) foo WHERE (user_id IS NOT NULL) ROLLBACK; -- We do support set operations through recursive planning BEGIN; @@ -727,7 +720,7 @@ INSERT INTO raw_events_first(user_id) (SELECT user_id FROM raw_events_first) INTERSECT (SELECT user_id FROM raw_events_first); -DEBUG: Set operations are not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: Router planner cannot handle multi-shard select queries DEBUG: Router planner cannot handle multi-shard select queries DEBUG: generating subplan XXX_1 for subquery SELECT user_id FROM multi_insert_select.raw_events_first @@ -747,7 +740,7 @@ SELECT FROM ((SELECT user_id FROM raw_events_first WHERE user_id = 15) EXCEPT (SELECT user_id FROM raw_events_second where user_id = 17)) as foo; -DEBUG: Set operations are not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: Creating router plan DEBUG: Collecting INSERT ... SELECT results on coordinator -- some supported LEFT joins @@ -765,10 +758,16 @@ DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_133000 raw_events_second.user_id FROM reference_table LEFT JOIN raw_events_second ON reference_table.user_id = raw_events_second.user_id; -DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300008 AS citus_table_alias (user_id) SELECT raw_events_second.user_id FROM (multi_insert_select.reference_table_13300012 reference_table LEFT JOIN multi_insert_select.raw_events_second_13300004 raw_events_second ON ((reference_table.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE (raw_events_second.user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300009 AS citus_table_alias (user_id) SELECT raw_events_second.user_id FROM (multi_insert_select.reference_table_13300012 reference_table LEFT JOIN multi_insert_select.raw_events_second_13300005 raw_events_second ON ((reference_table.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE (raw_events_second.user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300010 AS citus_table_alias (user_id) SELECT raw_events_second.user_id FROM (multi_insert_select.reference_table_13300012 reference_table LEFT JOIN multi_insert_select.raw_events_second_13300006 raw_events_second ON ((reference_table.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE (raw_events_second.user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO multi_insert_select.agg_events_13300011 AS citus_table_alias (user_id) SELECT raw_events_second.user_id FROM (multi_insert_select.reference_table_13300012 reference_table LEFT JOIN multi_insert_select.raw_events_second_13300007 raw_events_second ON ((reference_table.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE (raw_events_second.user_id IS NOT NULL) +DEBUG: cannot perform a lateral outer join when a distributed subquery references a reference table +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: recursively planning right side of the left join since the outer side is a recurring rel +DEBUG: recursively planning distributed relation "raw_events_second" since it is part of a distributed join node that is outer joined with a recurring rel +DEBUG: Wrapping relation "raw_events_second" to a subquery +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for subquery SELECT user_id FROM multi_insert_select.raw_events_second WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT raw_events_second.user_id FROM (multi_insert_select.reference_table LEFT JOIN (SELECT raw_events_second_1.user_id, NULL::timestamp without time zone AS "time", NULL::integer AS value_1, NULL::integer AS value_2, NULL::double precision AS value_3, NULL::bigint AS value_4 FROM (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) raw_events_second_1) raw_events_second ON ((reference_table.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator INSERT INTO agg_events (user_id) SELECT raw_events_first.user_id @@ -885,7 +884,7 @@ $Q$); Custom Scan (Citus INSERT ... SELECT) INSERT/SELECT method: pull to coordinator -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 (4 rows) -- EXPLAIN ANALYZE is not supported for INSERT ... SELECT via coordinator @@ -922,7 +921,7 @@ $Q$); Custom Scan (Citus INSERT ... SELECT) INSERT/SELECT method: pull to coordinator -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 (4 rows) -- make things a bit more complicate with IN clauses @@ -941,7 +940,7 @@ $Q$); Custom Scan (Citus INSERT ... SELECT) INSERT/SELECT method: pull to coordinator -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 (4 rows) -- implicit join on non partition column should also not be pushed down, @@ -960,7 +959,7 @@ $Q$); Custom Scan (Citus INSERT ... SELECT) INSERT/SELECT method: pull to coordinator -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 (4 rows) RESET client_min_messages; @@ -982,7 +981,7 @@ $Q$); Custom Scan (Citus INSERT ... SELECT) INSERT/SELECT method: pull to coordinator -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 (4 rows) -- foo is not joined on the partition key so the query is not @@ -1047,7 +1046,7 @@ $Q$); Custom Scan (Citus INSERT ... SELECT) INSERT/SELECT method: pull to coordinator -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 (4 rows) -- foo is not joined on the partition key so the query is not @@ -1126,7 +1125,8 @@ FROM (SELECT SUM(raw_events_second.value_4) AS v4, raw_events_second WHERE raw_events_first.user_id = raw_events_second.user_id GROUP BY raw_events_second.value_3) AS foo; -DEBUG: Group by list without distribution column is not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Group by list without partition column is currently unsupported when a subquery references a column from another query DEBUG: Router planner cannot handle multi-shard select queries DEBUG: Router planner cannot handle multi-shard select queries DEBUG: join prunable for intervals [-2147483648,-1073741825] and [-1073741824,-1] @@ -1281,7 +1281,7 @@ FROM (SELECT SUM(raw_events_second.value_4) AS v4, GROUP BY raw_events_second.value_1 HAVING SUM(raw_events_second.value_4) > 10) AS foo2 ) as f2 ON (f.id = f2.id); -DEBUG: Group by list without distribution column is not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: Router planner cannot handle multi-shard select queries DEBUG: Router planner cannot handle multi-shard select queries DEBUG: join prunable for intervals [-2147483648,-1073741825] and [-1073741824,-1] @@ -1327,7 +1327,7 @@ FROM (SELECT SUM(raw_events_second.value_4) AS v4, GROUP BY raw_events_second.value_1 HAVING SUM(raw_events_second.value_4) > 10) AS foo2 ) as f2 ON (f.id = f2.id); -DEBUG: Group by list without distribution column is not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: Router planner cannot handle multi-shard select queries DEBUG: Router planner cannot handle multi-shard select queries DEBUG: join prunable for intervals [-2147483648,-1073741825] and [-1073741824,-1] @@ -1437,7 +1437,7 @@ $Q$); Group Key: remote_scan.id Filter: (pg_catalog.sum(remote_scan.worker_column_4) > '10'::numeric) -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 (11 rows) -- cannot push down since foo doesn't have en equi join @@ -1514,7 +1514,7 @@ $Q$); -> HashAggregate Group Key: remote_scan.user_id -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 (6 rows) -- join among reference_ids and averages is not on the partition key @@ -1576,7 +1576,7 @@ $Q$); Custom Scan (Citus INSERT ... SELECT) INSERT/SELECT method: pull to coordinator -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 (4 rows) -- Selected value in the WHERE is not partition key, so we cannot use distributed @@ -3242,5 +3242,236 @@ returning text_col_1; string (1 row) +CREATE TABLE dist_table_3( +dist_col bigint, +int_col integer +); +SELECT create_distributed_table('dist_table_3', 'dist_col'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- dist_table_2 and dist_table_3 are non-colocated source tables. Repartitioning is also not possible due to +-- different types for distribution columns. Citus would not be able to handle this complex insert select. +INSERT INTO dist_table_1 SELECT dist_table_2.dist_col FROM dist_table_2 JOIN dist_table_3 USING(dist_col); +ERROR: complex joins are only supported when all distributed tables are joined on their distribution columns with equal operator +CREATE TABLE dist_table_4( +dist_col integer, +int_col integer +); +SELECT create_distributed_table('dist_table_4', 'dist_col'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- Even if target table distribution column is colocated with dist_table_2's distributed column, source tables dist_table_2 and dist_table_4 +-- are non-colocated. Hence, SELECT part of the query should be pulled to coordinator. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_1 SELECT dist_table_2.dist_col FROM dist_table_2 JOIN dist_table_4 ON dist_table_2.dist_col = dist_table_4.int_col; +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + Task Count: 6 +(4 rows) + +-- For INSERT SELECT, when a lateral query references an outer query, push-down is possible even if limit clause exists in the lateral query. +-- It is because subquery with limit does not need to be merged at coordinator as it is a lateral query. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_1 SELECT d1.dist_col FROM dist_table_1 d1 LEFT JOIN LATERAL (SELECT * FROM dist_table_2 d2 WHERE d1.dist_col = d2.dist_col LIMIT 3) dummy USING(dist_col); +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + Task Count: 4 +(2 rows) + +-- For INSERT SELECT, when push-down is NOT possible when limit clause exists in a subquery at SELECT part of INSERT SELECT. +-- It is because the subquery with limit needs to be merged at coordinator. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_1 SELECT d1.dist_col FROM dist_table_1 d1 LEFT JOIN (SELECT * FROM dist_table_2 LIMIT 3) dummy USING(dist_col); +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: repartition + -> Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Limit + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(7 rows) + +CREATE TABLE dist_table_5(id int, id2 int); +SELECT create_distributed_table('dist_table_5','id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE dist_table_6(id int, id2 int); +SELECT create_distributed_table('dist_table_6','id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- verify that insert select with union can be pushed down since UNION clause has FROM clause at top level query. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5(id) SELECT id FROM (SELECT id FROM dist_table_5 UNION SELECT id FROM dist_table_6) dummy; +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + Task Count: 4 +(2 rows) + +-- verify that insert select with sublink can be pushed down when tables are colocated. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT id, (SELECT id FROM dist_table_5 WHERE dist_table_5.id = dist_table_6.id) FROM dist_table_6; +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + Task Count: 4 +(2 rows) + +CREATE TABLE ref_table_1(id int); +SELECT create_reference_table('ref_table_1'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +-- verify that insert select with sublink cannot be pushed down when from clause does not contain any distributed relation. +INSERT INTO dist_table_5 SELECT id, (SELECT id FROM dist_table_5 WHERE dist_table_5.id = ref_table_1.id) FROM ref_table_1; +ERROR: correlated subqueries are not supported when the FROM clause contains a reference table +-- verify that insert select cannot be pushed down when we have recurring range table in from clause. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT id, (SELECT id FROM ref_table_1 WHERE id = 1) FROM ref_table_1; +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + Task Count: 1 +(4 rows) + +-- verify that insert select cannot be pushed down when we have reference table in outside of outer join. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT a.id FROM dist_table_5 a LEFT JOIN ref_table_1 b ON (true) RIGHT JOIN ref_table_1 c ON (true); +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(6 rows) + +-- verify that insert select cannot be pushed down when it has a recurring outer join in a subquery. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT id FROM ref_table_1 LEFT JOIN dist_table_5 USING(id); +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(6 rows) + +CREATE TABLE loc_table_1(id int); +-- verify that insert select cannot be pushed down when it contains join between local and distributed tables. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT id FROM dist_table_5 JOIN loc_table_1 USING(id); +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: repartition + -> Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Seq Scan on loc_table_1 + Task Count: 4 +(6 rows) + +CREATE VIEW view_1 AS + SELECT id FROM dist_table_6; +CREATE MATERIALIZED VIEW view_2 AS + SELECT id FROM dist_table_6; +-- verify that insert select cannot be pushed down when it contains view. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT * FROM view_1; +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + Task Count: 4 +(2 rows) + +-- verify that insert select cannot be pushed down when it contains materialized view. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT * FROM view_2; +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Seq Scan on view_2 +(3 rows) + +CREATE TABLE append_table(id integer, data text, int_data int); +SELECT create_distributed_table('append_table', 'id', 'append'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT master_create_empty_shard('append_table'); + master_create_empty_shard +--------------------------------------------------------------------- + 13300096 +(1 row) + +-- verify that insert select push down for append tables are not supported. +INSERT INTO append_table SELECT * FROM append_table; +ERROR: INSERT ... SELECT into an append-distributed table is not supported +-- verify that CTEs at top level of INSERT SELECT, that can normally be inlined, would not be inlined by INSERT SELECT pushdown planner +-- and handled by pull to coordinator. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) WITH cte_1 AS (SELECT id FROM dist_table_5 WHERE id = 5) + INSERT INTO dist_table_5 + SELECT id FROM dist_table_5 JOIN cte_1 USING(id); +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + Task Count: 1 +(4 rows) + +-- verify that CTEs at top level of SELECT part, would be inlined by Postgres and pushed down by INSERT SELECT planner. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 + WITH cte_1 AS (SELECT id FROM dist_table_5 WHERE id = 5) + SELECT id FROM dist_table_5 JOIN cte_1 USING(id); +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + Task Count: 1 +(2 rows) + SET client_min_messages TO ERROR; DROP SCHEMA multi_insert_select CASCADE; diff --git a/src/test/regress/expected/multi_insert_select_conflict.out b/src/test/regress/expected/multi_insert_select_conflict.out index df7bdc9b9..5c06719d3 100644 --- a/src/test/regress/expected/multi_insert_select_conflict.out +++ b/src/test/regress/expected/multi_insert_select_conflict.out @@ -106,7 +106,8 @@ FROM ( LIMIT 5 ) as foo ON CONFLICT DO NOTHING; -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 5 DEBUG: generating subplan XXX_1 for subquery SELECT col_1, col_2, col_3 FROM on_conflict.source_table_1 LIMIT 5 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT col_1, col_2 FROM (SELECT intermediate_result.col_1, intermediate_result.col_2, intermediate_result.col_3 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(col_1 integer, col_2 integer, col_3 integer)) foo @@ -127,7 +128,8 @@ WITH inserted_table AS ( ON CONFLICT(col_1) DO UPDATE SET col_2 = EXCLUDED.col_2 RETURNING * ) SELECT * FROM inserted_table ORDER BY 1; DEBUG: generating subplan XXX_1 for CTE inserted_table: INSERT INTO on_conflict.target_table (col_1, col_2) SELECT foo.col_1, foo.col_2 FROM (SELECT source_table_1.col_1, source_table_1.col_2, source_table_1.col_3 FROM on_conflict.source_table_1 LIMIT 5) foo ON CONFLICT(col_1) DO UPDATE SET col_2 = excluded.col_2 RETURNING target_table.col_1, target_table.col_2 -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 5 DEBUG: generating subplan XXX_1 for subquery SELECT col_1, col_2, col_3 FROM on_conflict.source_table_1 LIMIT 5 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT col_1, col_2 FROM (SELECT intermediate_result.col_1, intermediate_result.col_2, intermediate_result.col_3 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(col_1 integer, col_2 integer, col_3 integer)) foo @@ -163,7 +165,8 @@ WITH inserted_table AS ( ON CONFLICT(col_1) DO UPDATE SET col_2 = 0 RETURNING * ) SELECT * FROM inserted_table ORDER BY 1; DEBUG: generating subplan XXX_1 for CTE inserted_table: INSERT INTO on_conflict.target_table (col_1, col_2) SELECT foo.col_1, foo.col_2 FROM ((SELECT source_table_1.col_1, source_table_1.col_2, source_table_1.col_3 FROM on_conflict.source_table_1 LIMIT 5) UNION (SELECT source_table_2.col_1, source_table_2.col_2, source_table_2.col_3 FROM on_conflict.source_table_2 LIMIT 5)) foo ON CONFLICT(col_1) DO UPDATE SET col_2 = 0 RETURNING target_table.col_1, target_table.col_2 -DEBUG: Set operations are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 5 DEBUG: generating subplan XXX_1 for subquery SELECT col_1, col_2, col_3 FROM on_conflict.source_table_1 LIMIT 5 DEBUG: push down of limit count: 5 @@ -498,7 +501,8 @@ FROM ( LIMIT 5) ) as foo ON CONFLICT(col_1) DO UPDATE SET col_2 = 0; -DEBUG: Set operations are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 5 DEBUG: generating subplan XXX_1 for subquery SELECT col_1, col_2, col_3 FROM on_conflict.source_table_1 LIMIT 5 DEBUG: push down of limit count: 5 @@ -560,7 +564,8 @@ DEBUG: Collecting INSERT ... SELECT results on coordinator -- intermediate result file SET citus.max_adaptive_executor_pool_size TO 1; INSERT INTO target_table SELECT * FROM target_table LIMIT 10000 ON CONFLICT(col_1) DO UPDATE SET col_2 = EXCLUDED.col_2 + 1; -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 10000 DEBUG: Collecting INSERT ... SELECT results on coordinator SELECT DISTINCT col_2 FROM target_table; @@ -572,7 +577,8 @@ SELECT DISTINCT col_2 FROM target_table; WITH cte_1 AS (INSERT INTO target_table SELECT * FROM target_table LIMIT 10000 ON CONFLICT(col_1) DO UPDATE SET col_2 = EXCLUDED.col_2 + 1 RETURNING *) SELECT DISTINCT col_2 FROM cte_1; DEBUG: generating subplan XXX_1 for CTE cte_1: INSERT INTO on_conflict.target_table (col_1, col_2) SELECT target_table_1.col_1, target_table_1.col_2 FROM on_conflict.target_table target_table_1 LIMIT 10000 ON CONFLICT(col_1) DO UPDATE SET col_2 = (excluded.col_2 OPERATOR(pg_catalog.+) 1) RETURNING target_table.col_1, target_table.col_2 -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 10000 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT DISTINCT col_2 FROM (SELECT intermediate_result.col_1, intermediate_result.col_2 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(col_1 integer, col_2 integer)) cte_1 DEBUG: Collecting INSERT ... SELECT results on coordinator @@ -583,8 +589,9 @@ DEBUG: Collecting INSERT ... SELECT results on coordinator RESET client_min_messages; DROP SCHEMA on_conflict CASCADE; -NOTICE: drop cascades to 7 other objects +NOTICE: drop cascades to 8 other objects DETAIL: drop cascades to table test_ref_table +drop cascades to table test_ref_table_1900012 drop cascades to table source_table_3 drop cascades to table source_table_4 drop cascades to table target_table_2 diff --git a/src/test/regress/expected/multi_insert_select_conflict_0.out b/src/test/regress/expected/multi_insert_select_conflict_0.out index b8f926d30..4c2add1d7 100644 --- a/src/test/regress/expected/multi_insert_select_conflict_0.out +++ b/src/test/regress/expected/multi_insert_select_conflict_0.out @@ -106,7 +106,8 @@ FROM ( LIMIT 5 ) as foo ON CONFLICT DO NOTHING; -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 5 DEBUG: generating subplan XXX_1 for subquery SELECT col_1, col_2, col_3 FROM on_conflict.source_table_1 LIMIT 5 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT col_1, col_2 FROM (SELECT intermediate_result.col_1, intermediate_result.col_2, intermediate_result.col_3 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(col_1 integer, col_2 integer, col_3 integer)) foo @@ -127,7 +128,8 @@ WITH inserted_table AS ( ON CONFLICT(col_1) DO UPDATE SET col_2 = EXCLUDED.col_2 RETURNING * ) SELECT * FROM inserted_table ORDER BY 1; DEBUG: generating subplan XXX_1 for CTE inserted_table: INSERT INTO on_conflict.target_table (col_1, col_2) SELECT col_1, col_2 FROM (SELECT source_table_1.col_1, source_table_1.col_2, source_table_1.col_3 FROM on_conflict.source_table_1 LIMIT 5) foo ON CONFLICT(col_1) DO UPDATE SET col_2 = excluded.col_2 RETURNING target_table.col_1, target_table.col_2 -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 5 DEBUG: generating subplan XXX_1 for subquery SELECT col_1, col_2, col_3 FROM on_conflict.source_table_1 LIMIT 5 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT col_1, col_2 FROM (SELECT intermediate_result.col_1, intermediate_result.col_2, intermediate_result.col_3 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(col_1 integer, col_2 integer, col_3 integer)) foo @@ -163,7 +165,8 @@ WITH inserted_table AS ( ON CONFLICT(col_1) DO UPDATE SET col_2 = 0 RETURNING * ) SELECT * FROM inserted_table ORDER BY 1; DEBUG: generating subplan XXX_1 for CTE inserted_table: INSERT INTO on_conflict.target_table (col_1, col_2) SELECT col_1, col_2 FROM ((SELECT source_table_1.col_1, source_table_1.col_2, source_table_1.col_3 FROM on_conflict.source_table_1 LIMIT 5) UNION (SELECT source_table_2.col_1, source_table_2.col_2, source_table_2.col_3 FROM on_conflict.source_table_2 LIMIT 5)) foo ON CONFLICT(col_1) DO UPDATE SET col_2 = 0 RETURNING target_table.col_1, target_table.col_2 -DEBUG: Set operations are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 5 DEBUG: generating subplan XXX_1 for subquery SELECT col_1, col_2, col_3 FROM on_conflict.source_table_1 LIMIT 5 DEBUG: push down of limit count: 5 @@ -498,7 +501,8 @@ FROM ( LIMIT 5) ) as foo ON CONFLICT(col_1) DO UPDATE SET col_2 = 0; -DEBUG: Set operations are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 5 DEBUG: generating subplan XXX_1 for subquery SELECT col_1, col_2, col_3 FROM on_conflict.source_table_1 LIMIT 5 DEBUG: push down of limit count: 5 @@ -560,7 +564,8 @@ DEBUG: Collecting INSERT ... SELECT results on coordinator -- intermediate result file SET citus.max_adaptive_executor_pool_size TO 1; INSERT INTO target_table SELECT * FROM target_table LIMIT 10000 ON CONFLICT(col_1) DO UPDATE SET col_2 = EXCLUDED.col_2 + 1; -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 10000 DEBUG: Collecting INSERT ... SELECT results on coordinator SELECT DISTINCT col_2 FROM target_table; @@ -572,7 +577,8 @@ SELECT DISTINCT col_2 FROM target_table; WITH cte_1 AS (INSERT INTO target_table SELECT * FROM target_table LIMIT 10000 ON CONFLICT(col_1) DO UPDATE SET col_2 = EXCLUDED.col_2 + 1 RETURNING *) SELECT DISTINCT col_2 FROM cte_1; DEBUG: generating subplan XXX_1 for CTE cte_1: INSERT INTO on_conflict.target_table (col_1, col_2) SELECT col_1, col_2 FROM on_conflict.target_table LIMIT 10000 ON CONFLICT(col_1) DO UPDATE SET col_2 = (excluded.col_2 OPERATOR(pg_catalog.+) 1) RETURNING target_table.col_1, target_table.col_2 -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 10000 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT DISTINCT col_2 FROM (SELECT intermediate_result.col_1, intermediate_result.col_2 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(col_1 integer, col_2 integer)) cte_1 DEBUG: Collecting INSERT ... SELECT results on coordinator @@ -583,8 +589,9 @@ DEBUG: Collecting INSERT ... SELECT results on coordinator RESET client_min_messages; DROP SCHEMA on_conflict CASCADE; -NOTICE: drop cascades to 7 other objects +NOTICE: drop cascades to 8 other objects DETAIL: drop cascades to table test_ref_table +drop cascades to table test_ref_table_1900012 drop cascades to table source_table_3 drop cascades to table source_table_4 drop cascades to table target_table_2 diff --git a/src/test/regress/expected/multi_insert_select_non_pushable_queries.out b/src/test/regress/expected/multi_insert_select_non_pushable_queries.out index fc3f62385..c4a537277 100644 --- a/src/test/regress/expected/multi_insert_select_non_pushable_queries.out +++ b/src/test/regress/expected/multi_insert_select_non_pushable_queries.out @@ -3,6 +3,8 @@ -- Vanilla funnel query --------------------------------------------------------------------- --------------------------------------------------------------------- +CREATE SCHEMA multi_insert_select_non_pushable_queries; +SET search_path = multi_insert_select_non_pushable_queries,public; -- not pushable since the JOIN is not an equi join INSERT INTO agg_results_third (user_id, value_1_agg) SELECT user_id, array_length(events_table, 1) @@ -147,7 +149,7 @@ FROM ( GROUP BY t1.user_id, hasdone_event ) t GROUP BY user_id, hasdone_event RETURNING user_id, value_1_agg, value_2_agg; -DEBUG: Set operations are not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: generating subplan XXX_1 for subquery SELECT u.user_id, 'step=>1'::text AS event, e."time" FROM public.users_table u, public.events_table e WHERE ((u.user_id OPERATOR(pg_catalog.=) e.user_id) AND (u.user_id OPERATOR(pg_catalog.>=) 10) AND (u.user_id OPERATOR(pg_catalog.<=) 25) AND (e.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[100, 101, 102]))) DEBUG: generating subplan XXX_2 for subquery SELECT u.user_id, 'step=>2'::text AS event, e."time" FROM public.users_table u, public.events_table e WHERE ((u.user_id OPERATOR(pg_catalog.=) e.user_id) AND (u.user_id OPERATOR(pg_catalog.>=) 10) AND (u.user_id OPERATOR(pg_catalog.<=) 25) AND (e.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[103, 104, 105]))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT intermediate_result.user_id, intermediate_result.event, intermediate_result."time" FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, event text, "time" timestamp without time zone) UNION SELECT intermediate_result.user_id, intermediate_result.event, intermediate_result."time" FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, event text, "time" timestamp without time zone) @@ -305,7 +307,7 @@ GROUP BY ORDER BY count_pay RETURNING user_id, value_1_agg, value_2_agg; -DEBUG: Set operations are not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: generating subplan XXX_1 for subquery SELECT users_table.user_id, 'action=>1'::text AS event, events_table."time" FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (users_table.user_id OPERATOR(pg_catalog.>=) 10) AND (users_table.user_id OPERATOR(pg_catalog.<=) 70) AND (events_table.event_type OPERATOR(pg_catalog.>) 10) AND (events_table.event_type OPERATOR(pg_catalog.<) 12)) DEBUG: generating subplan XXX_2 for subquery SELECT users_table.user_id, 'action=>2'::text AS event, events_table."time" FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (users_table.user_id OPERATOR(pg_catalog.>=) 10) AND (users_table.user_id OPERATOR(pg_catalog.<=) 70) AND (events_table.event_type OPERATOR(pg_catalog.>) 12) AND (events_table.event_type OPERATOR(pg_catalog.<) 14)) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT intermediate_result.user_id, intermediate_result.event, intermediate_result."time" FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, event text, "time" timestamp without time zone) UNION SELECT intermediate_result.user_id, intermediate_result.event, intermediate_result."time" FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, event text, "time" timestamp without time zone) @@ -808,3 +810,36 @@ FROM ( GROUP BY user_id ) AS shard_union ORDER BY user_lastseen DESC; +CREATE TABLE dist_table_1(id int); +SELECT create_distributed_table('dist_table_1','id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE dist_table_2(id int, id2 int); +SELECT create_distributed_table('dist_table_2','id2'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- verify that insert select with union can be pulled to coordinator. We cannot push down the query +-- since UNION clause has no FROM clause at top level query. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_1(id) SELECT id FROM dist_table_1 UNION SELECT id FROM dist_table_2; +$$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(6 rows) + +DROP SCHEMA multi_insert_select_non_pushable_queries CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table dist_table_1 +drop cascades to table dist_table_2 diff --git a/src/test/regress/expected/multi_insert_select_window.out b/src/test/regress/expected/multi_insert_select_window.out index 0cf605990..b87299d7e 100644 --- a/src/test/regress/expected/multi_insert_select_window.out +++ b/src/test/regress/expected/multi_insert_select_window.out @@ -816,4 +816,5 @@ FROM ( user_id ) ) AS ftop; +TRUNCATE agg_results_window; DROP VIEW view_with_window_func; diff --git a/src/test/regress/expected/multi_join_pruning.out b/src/test/regress/expected/multi_join_pruning.out index efc6954ad..27fdc3980 100644 --- a/src/test/regress/expected/multi_join_pruning.out +++ b/src/test/regress/expected/multi_join_pruning.out @@ -140,3 +140,7 @@ DEBUG: join prunable for intervals [BA1000U2AMO4ZGX,BZZXSP27F21T6] and [AA1000U explain statements for distributed queries are not enabled (3 rows) +SET client_min_messages TO WARNING; +DROP TABLE varchar_partitioned_table; +DROP TABLE array_partitioned_table; +DROP TABLE composite_partitioned_table; diff --git a/src/test/regress/expected/multi_level_recursive_queries.out b/src/test/regress/expected/multi_level_recursive_queries.out index b2bf0a49c..e0a4d44a9 100644 --- a/src/test/regress/expected/multi_level_recursive_queries.out +++ b/src/test/regress/expected/multi_level_recursive_queries.out @@ -298,10 +298,5 @@ DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT avg(table_5.i DEBUG: generating subplan XXX_1 for subquery SELECT table_6.id FROM ((SELECT intermediate_result.id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id integer)) table_6 JOIN multi_recursive.dist0 table_8 USING (id)) WHERE (table_8.id OPERATOR(pg_catalog.<) 0) ORDER BY table_6.id DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT avg(table_5.id) AS avg FROM ((SELECT intermediate_result.id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id integer)) table_5 JOIN multi_recursive.dist0 table_9 USING (id)) ERROR: recursive complex joins are only supported when all distributed tables are co-located and joined on their distribution columns -RESET client_min_messages; +SET client_min_messages TO WARNING; DROP SCHEMA multi_recursive CASCADE; -NOTICE: drop cascades to 4 other objects -DETAIL: drop cascades to table tbl_dist1 -drop cascades to table tbl_ref1 -drop cascades to table dist0 -drop cascades to table dist1 diff --git a/src/test/regress/expected/multi_metadata_sync.out b/src/test/regress/expected/multi_metadata_sync.out index 8c74045be..e85253031 100644 --- a/src/test/regress/expected/multi_metadata_sync.out +++ b/src/test/regress/expected/multi_metadata_sync.out @@ -1,7 +1,7 @@ -- -- MULTI_METADATA_SYNC -- --- this test has different output for PG13/14 compared to PG15 +-- this test has different output for PG14 compared to PG15 -- In PG15, public schema is owned by pg_database_owner role -- Relevant PG commit: b073c3ccd06e4cb845e121387a43faa8c68a7b62 SHOW server_version \gset @@ -69,13 +69,14 @@ ALTER ROLE CURRENT_USER WITH PASSWORD 'dummypassword'; -- Show that, with no MX tables, activate node snapshot contains only the delete commands, -- pg_dist_node entries, pg_dist_object entries and roles. SELECT unnest(activate_node_snapshot()) order by 1; - unnest + unnest --------------------------------------------------------------------- ALTER DATABASE regression OWNER TO postgres; CALL pg_catalog.worker_drop_all_shell_tables(true) CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION pg_database_owner DELETE FROM pg_catalog.pg_dist_colocation DELETE FROM pg_catalog.pg_dist_object + DELETE FROM pg_catalog.pg_dist_schema DELETE FROM pg_dist_node DELETE FROM pg_dist_partition DELETE FROM pg_dist_placement @@ -84,7 +85,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; GRANT CREATE ON SCHEMA public TO pg_database_owner; GRANT USAGE ON SCHEMA public TO PUBLIC; GRANT USAGE ON SCHEMA public TO pg_database_owner; - INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(2, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) + INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 0, 'localhost', 57636, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', FALSE),(2, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(3, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) RESET ROLE RESET ROLE SELECT alter_role_if_exists('postgres', 'ALTER ROLE postgres SET lc_messages = ''C''') @@ -97,13 +98,13 @@ SELECT unnest(activate_node_snapshot()) order by 1; SET citus.enable_ddl_propagation TO 'on' SET citus.enable_ddl_propagation TO 'on' UPDATE pg_dist_local_group SET groupid = 1 - UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 1 + UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 2 WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('schema', ARRAY['public']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; -(32 rows) +(33 rows) -- Create a test table with constraints and SERIAL and default from user defined sequence CREATE SEQUENCE user_defined_seq; @@ -117,10 +118,19 @@ SELECT create_distributed_table('mx_test_table', 'col_1'); (1 row) reset citus.shard_count; -reset citus.shard_replication_factor; -- Set the replication model of the test table to streaming replication so that it is -- considered as an MX table UPDATE pg_dist_partition SET repmodel='s' WHERE logicalrelid='mx_test_table'::regclass; +-- add a single shard table and verify the creation commands are included in the activate node snapshot +CREATE TABLE single_shard_tbl(a int); +SELECT create_distributed_table('single_shard_tbl', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO single_shard_tbl VALUES (1); +reset citus.shard_replication_factor; -- Show that the created MX table is and its sequences are included in the activate node snapshot SELECT unnest(activate_node_snapshot()) order by 1; unnest @@ -130,31 +140,39 @@ SELECT unnest(activate_node_snapshot()) order by 1; ALTER SEQUENCE public.user_defined_seq OWNER TO postgres ALTER TABLE public.mx_test_table ADD CONSTRAINT mx_test_table_col_1_key UNIQUE (col_1) ALTER TABLE public.mx_test_table OWNER TO postgres + ALTER TABLE public.single_shard_tbl OWNER TO postgres CALL pg_catalog.worker_drop_all_shell_tables(true) CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION pg_database_owner CREATE TABLE public.mx_test_table (col_1 integer, col_2 text NOT NULL, col_3 bigint DEFAULT nextval('public.mx_test_table_col_3_seq'::regclass) NOT NULL, col_4 bigint DEFAULT nextval('public.user_defined_seq'::regclass)) USING heap + CREATE TABLE public.single_shard_tbl (a integer) USING heap DELETE FROM pg_catalog.pg_dist_colocation DELETE FROM pg_catalog.pg_dist_object + DELETE FROM pg_catalog.pg_dist_schema DELETE FROM pg_dist_node DELETE FROM pg_dist_partition DELETE FROM pg_dist_placement DELETE FROM pg_dist_shard DROP TABLE IF EXISTS public.mx_test_table CASCADE + DROP TABLE IF EXISTS public.single_shard_tbl CASCADE GRANT CREATE ON SCHEMA public TO PUBLIC; GRANT CREATE ON SCHEMA public TO pg_database_owner; GRANT USAGE ON SCHEMA public TO PUBLIC; GRANT USAGE ON SCHEMA public TO pg_database_owner; - INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(2, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) + INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 0, 'localhost', 57636, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', FALSE),(2, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(3, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) RESET ROLE RESET ROLE SELECT alter_role_if_exists('postgres', 'ALTER ROLE postgres SET lc_messages = ''C''') SELECT citus_internal_add_partition_metadata ('public.mx_test_table'::regclass, 'h', 'col_1', 2, 's') + SELECT citus_internal_add_partition_metadata ('public.single_shard_tbl'::regclass, 'n', NULL, 3, 's') + SELECT pg_catalog.worker_drop_sequence_dependency('public.mx_test_table'); + SELECT pg_catalog.worker_drop_sequence_dependency('public.single_shard_tbl'); SELECT pg_catalog.worker_drop_sequence_dependency(logicalrelid::regclass::text) FROM pg_dist_partition SELECT pg_catalog.worker_record_sequence_dependency('public.mx_test_table_col_3_seq'::regclass,'public.mx_test_table'::regclass,'col_3') SELECT worker_apply_sequence_command ('CREATE SEQUENCE IF NOT EXISTS public.mx_test_table_col_3_seq AS bigint INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1 NO CYCLE','bigint') SELECT worker_apply_sequence_command ('CREATE SEQUENCE IF NOT EXISTS public.user_defined_seq AS bigint INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1 NO CYCLE','bigint') SELECT worker_create_or_alter_role('postgres', 'CREATE ROLE postgres SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN REPLICATION BYPASSRLS CONNECTION LIMIT 0 PASSWORD ''md5c53670dddfc3bb4b5675c7872bc2249a'' VALID UNTIL ''2052-05-05 00:00:00-07''', 'ALTER ROLE postgres SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN REPLICATION BYPASSRLS CONNECTION LIMIT 0 PASSWORD ''md5c53670dddfc3bb4b5675c7872bc2249a'' VALID UNTIL ''2052-05-05 00:00:00-07''') SELECT worker_create_truncate_trigger('public.mx_test_table') + SELECT worker_create_truncate_trigger('public.single_shard_tbl') SET ROLE pg_database_owner SET ROLE pg_database_owner SET citus.enable_ddl_propagation TO 'off' @@ -162,20 +180,26 @@ SELECT unnest(activate_node_snapshot()) order by 1; SET citus.enable_ddl_propagation TO 'on' SET citus.enable_ddl_propagation TO 'on' UPDATE pg_dist_local_group SET groupid = 1 - UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 1 + UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 2 WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (2, 8, 1, 'integer'::regtype, NULL, NULL)) SELECT pg_catalog.citus_internal_add_colocation_metadata(colocationid, shardcount, replicationfactor, distributioncolumntype, coalesce(c.oid, 0)) FROM colocation_group_data d LEFT JOIN pg_collation c ON (d.distributioncolumncollationname = c.collname AND d.distributioncolumncollationschema::regnamespace = c.collnamespace) + WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (3, 1, 1, 0, NULL, NULL)) SELECT pg_catalog.citus_internal_add_colocation_metadata(colocationid, shardcount, replicationfactor, distributioncolumntype, coalesce(c.oid, 0)) FROM colocation_group_data d LEFT JOIN pg_collation c ON (d.distributioncolumncollationname = c.collname AND d.distributioncolumncollationschema::regnamespace = c.collnamespace) WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('schema', ARRAY['public']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('sequence', ARRAY['public', 'mx_test_table_col_3_seq']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('sequence', ARRAY['public', 'user_defined_seq']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('table', ARRAY['public', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; + WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('table', ARRAY['public', 'single_shard_tbl']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310000, 0, 1, 100000), (1310001, 0, 2, 100001), (1310002, 0, 1, 100002), (1310003, 0, 2, 100003), (1310004, 0, 1, 100004), (1310005, 0, 2, 100005), (1310006, 0, 1, 100006), (1310007, 0, 2, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; + WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310008, 0, 2, 100008)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('public.mx_test_table'::regclass, 1310000, 't'::"char", '-2147483648', '-1610612737'), ('public.mx_test_table'::regclass, 1310001, 't'::"char", '-1610612736', '-1073741825'), ('public.mx_test_table'::regclass, 1310002, 't'::"char", '-1073741824', '-536870913'), ('public.mx_test_table'::regclass, 1310003, 't'::"char", '-536870912', '-1'), ('public.mx_test_table'::regclass, 1310004, 't'::"char", '0', '536870911'), ('public.mx_test_table'::regclass, 1310005, 't'::"char", '536870912', '1073741823'), ('public.mx_test_table'::regclass, 1310006, 't'::"char", '1073741824', '1610612735'), ('public.mx_test_table'::regclass, 1310007, 't'::"char", '1610612736', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; -(49 rows) + WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('public.single_shard_tbl'::regclass, 1310008, 't'::"char", NULL, NULL)) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; +(61 rows) +-- Drop single shard table +DROP TABLE single_shard_tbl; -- Show that CREATE INDEX commands are included in the activate node snapshot CREATE INDEX mx_index ON mx_test_table(col_2); SELECT unnest(activate_node_snapshot()) order by 1; @@ -192,6 +216,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; CREATE TABLE public.mx_test_table (col_1 integer, col_2 text NOT NULL, col_3 bigint DEFAULT nextval('public.mx_test_table_col_3_seq'::regclass) NOT NULL, col_4 bigint DEFAULT nextval('public.user_defined_seq'::regclass)) USING heap DELETE FROM pg_catalog.pg_dist_colocation DELETE FROM pg_catalog.pg_dist_object + DELETE FROM pg_catalog.pg_dist_schema DELETE FROM pg_dist_node DELETE FROM pg_dist_partition DELETE FROM pg_dist_placement @@ -201,11 +226,12 @@ SELECT unnest(activate_node_snapshot()) order by 1; GRANT CREATE ON SCHEMA public TO pg_database_owner; GRANT USAGE ON SCHEMA public TO PUBLIC; GRANT USAGE ON SCHEMA public TO pg_database_owner; - INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(2, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) + INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 0, 'localhost', 57636, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', FALSE),(2, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(3, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) RESET ROLE RESET ROLE SELECT alter_role_if_exists('postgres', 'ALTER ROLE postgres SET lc_messages = ''C''') SELECT citus_internal_add_partition_metadata ('public.mx_test_table'::regclass, 'h', 'col_1', 2, 's') + SELECT pg_catalog.worker_drop_sequence_dependency('public.mx_test_table'); SELECT pg_catalog.worker_drop_sequence_dependency(logicalrelid::regclass::text) FROM pg_dist_partition SELECT pg_catalog.worker_record_sequence_dependency('public.mx_test_table_col_3_seq'::regclass,'public.mx_test_table'::regclass,'col_3') SELECT worker_apply_sequence_command ('CREATE SEQUENCE IF NOT EXISTS public.mx_test_table_col_3_seq AS bigint INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1 NO CYCLE','bigint') @@ -219,9 +245,9 @@ SELECT unnest(activate_node_snapshot()) order by 1; SET citus.enable_ddl_propagation TO 'on' SET citus.enable_ddl_propagation TO 'on' UPDATE pg_dist_local_group SET groupid = 1 - UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 1 + UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 2 WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (2, 8, 1, 'integer'::regtype, NULL, NULL)) SELECT pg_catalog.citus_internal_add_colocation_metadata(colocationid, shardcount, replicationfactor, distributioncolumntype, coalesce(c.oid, 0)) FROM colocation_group_data d LEFT JOIN pg_collation c ON (d.distributioncolumncollationname = c.collname AND d.distributioncolumncollationschema::regnamespace = c.collnamespace) WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; @@ -231,7 +257,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('table', ARRAY['public', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310000, 0, 1, 100000), (1310001, 0, 2, 100001), (1310002, 0, 1, 100002), (1310003, 0, 2, 100003), (1310004, 0, 1, 100004), (1310005, 0, 2, 100005), (1310006, 0, 1, 100006), (1310007, 0, 2, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('public.mx_test_table'::regclass, 1310000, 't'::"char", '-2147483648', '-1610612737'), ('public.mx_test_table'::regclass, 1310001, 't'::"char", '-1610612736', '-1073741825'), ('public.mx_test_table'::regclass, 1310002, 't'::"char", '-1073741824', '-536870913'), ('public.mx_test_table'::regclass, 1310003, 't'::"char", '-536870912', '-1'), ('public.mx_test_table'::regclass, 1310004, 't'::"char", '0', '536870911'), ('public.mx_test_table'::regclass, 1310005, 't'::"char", '536870912', '1073741823'), ('public.mx_test_table'::regclass, 1310006, 't'::"char", '1073741824', '1610612735'), ('public.mx_test_table'::regclass, 1310007, 't'::"char", '1610612736', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; -(50 rows) +(52 rows) -- Show that schema changes are included in the activate node snapshot CREATE SCHEMA mx_testing_schema; @@ -251,6 +277,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; CREATE TABLE mx_testing_schema.mx_test_table (col_1 integer, col_2 text NOT NULL, col_3 bigint DEFAULT nextval('mx_testing_schema.mx_test_table_col_3_seq'::regclass) NOT NULL, col_4 bigint DEFAULT nextval('public.user_defined_seq'::regclass)) USING heap DELETE FROM pg_catalog.pg_dist_colocation DELETE FROM pg_catalog.pg_dist_object + DELETE FROM pg_catalog.pg_dist_schema DELETE FROM pg_dist_node DELETE FROM pg_dist_partition DELETE FROM pg_dist_placement @@ -260,11 +287,12 @@ SELECT unnest(activate_node_snapshot()) order by 1; GRANT CREATE ON SCHEMA public TO pg_database_owner; GRANT USAGE ON SCHEMA public TO PUBLIC; GRANT USAGE ON SCHEMA public TO pg_database_owner; - INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(2, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) + INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 0, 'localhost', 57636, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', FALSE),(2, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(3, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) RESET ROLE RESET ROLE SELECT alter_role_if_exists('postgres', 'ALTER ROLE postgres SET lc_messages = ''C''') SELECT citus_internal_add_partition_metadata ('mx_testing_schema.mx_test_table'::regclass, 'h', 'col_1', 2, 's') + SELECT pg_catalog.worker_drop_sequence_dependency('mx_testing_schema.mx_test_table'); SELECT pg_catalog.worker_drop_sequence_dependency(logicalrelid::regclass::text) FROM pg_dist_partition SELECT pg_catalog.worker_record_sequence_dependency('mx_testing_schema.mx_test_table_col_3_seq'::regclass,'mx_testing_schema.mx_test_table'::regclass,'col_3') SELECT worker_apply_sequence_command ('CREATE SEQUENCE IF NOT EXISTS mx_testing_schema.mx_test_table_col_3_seq AS bigint INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1 NO CYCLE','bigint') @@ -278,9 +306,9 @@ SELECT unnest(activate_node_snapshot()) order by 1; SET citus.enable_ddl_propagation TO 'on' SET citus.enable_ddl_propagation TO 'on' UPDATE pg_dist_local_group SET groupid = 1 - UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 1 + UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 2 WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (2, 8, 1, 'integer'::regtype, NULL, NULL)) SELECT pg_catalog.citus_internal_add_colocation_metadata(colocationid, shardcount, replicationfactor, distributioncolumntype, coalesce(c.oid, 0)) FROM colocation_group_data d LEFT JOIN pg_collation c ON (d.distributioncolumncollationname = c.collname AND d.distributioncolumncollationschema::regnamespace = c.collnamespace) WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; @@ -291,7 +319,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('table', ARRAY['mx_testing_schema', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310000, 0, 1, 100000), (1310001, 0, 2, 100001), (1310002, 0, 1, 100002), (1310003, 0, 2, 100003), (1310004, 0, 1, 100004), (1310005, 0, 2, 100005), (1310006, 0, 1, 100006), (1310007, 0, 2, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('mx_testing_schema.mx_test_table'::regclass, 1310000, 't'::"char", '-2147483648', '-1610612737'), ('mx_testing_schema.mx_test_table'::regclass, 1310001, 't'::"char", '-1610612736', '-1073741825'), ('mx_testing_schema.mx_test_table'::regclass, 1310002, 't'::"char", '-1073741824', '-536870913'), ('mx_testing_schema.mx_test_table'::regclass, 1310003, 't'::"char", '-536870912', '-1'), ('mx_testing_schema.mx_test_table'::regclass, 1310004, 't'::"char", '0', '536870911'), ('mx_testing_schema.mx_test_table'::regclass, 1310005, 't'::"char", '536870912', '1073741823'), ('mx_testing_schema.mx_test_table'::regclass, 1310006, 't'::"char", '1073741824', '1610612735'), ('mx_testing_schema.mx_test_table'::regclass, 1310007, 't'::"char", '1610612736', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; -(52 rows) +(54 rows) -- Show that append distributed tables are not included in the activate node snapshot CREATE TABLE non_mx_test_table (col_1 int, col_2 text); @@ -317,6 +345,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; CREATE TABLE mx_testing_schema.mx_test_table (col_1 integer, col_2 text NOT NULL, col_3 bigint DEFAULT nextval('mx_testing_schema.mx_test_table_col_3_seq'::regclass) NOT NULL, col_4 bigint DEFAULT nextval('public.user_defined_seq'::regclass)) USING heap DELETE FROM pg_catalog.pg_dist_colocation DELETE FROM pg_catalog.pg_dist_object + DELETE FROM pg_catalog.pg_dist_schema DELETE FROM pg_dist_node DELETE FROM pg_dist_partition DELETE FROM pg_dist_placement @@ -326,11 +355,12 @@ SELECT unnest(activate_node_snapshot()) order by 1; GRANT CREATE ON SCHEMA public TO pg_database_owner; GRANT USAGE ON SCHEMA public TO PUBLIC; GRANT USAGE ON SCHEMA public TO pg_database_owner; - INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(2, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) + INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 0, 'localhost', 57636, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', FALSE),(2, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(3, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) RESET ROLE RESET ROLE SELECT alter_role_if_exists('postgres', 'ALTER ROLE postgres SET lc_messages = ''C''') SELECT citus_internal_add_partition_metadata ('mx_testing_schema.mx_test_table'::regclass, 'h', 'col_1', 2, 's') + SELECT pg_catalog.worker_drop_sequence_dependency('mx_testing_schema.mx_test_table'); SELECT pg_catalog.worker_drop_sequence_dependency(logicalrelid::regclass::text) FROM pg_dist_partition SELECT pg_catalog.worker_record_sequence_dependency('mx_testing_schema.mx_test_table_col_3_seq'::regclass,'mx_testing_schema.mx_test_table'::regclass,'col_3') SELECT worker_apply_sequence_command ('CREATE SEQUENCE IF NOT EXISTS mx_testing_schema.mx_test_table_col_3_seq AS bigint INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1 NO CYCLE','bigint') @@ -344,9 +374,9 @@ SELECT unnest(activate_node_snapshot()) order by 1; SET citus.enable_ddl_propagation TO 'on' SET citus.enable_ddl_propagation TO 'on' UPDATE pg_dist_local_group SET groupid = 1 - UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 1 + UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 2 WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (2, 8, 1, 'integer'::regtype, NULL, NULL)) SELECT pg_catalog.citus_internal_add_colocation_metadata(colocationid, shardcount, replicationfactor, distributioncolumntype, coalesce(c.oid, 0)) FROM colocation_group_data d LEFT JOIN pg_collation c ON (d.distributioncolumncollationname = c.collname AND d.distributioncolumncollationschema::regnamespace = c.collnamespace) WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; @@ -357,7 +387,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('table', ARRAY['mx_testing_schema', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310000, 0, 1, 100000), (1310001, 0, 2, 100001), (1310002, 0, 1, 100002), (1310003, 0, 2, 100003), (1310004, 0, 1, 100004), (1310005, 0, 2, 100005), (1310006, 0, 1, 100006), (1310007, 0, 2, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('mx_testing_schema.mx_test_table'::regclass, 1310000, 't'::"char", '-2147483648', '-1610612737'), ('mx_testing_schema.mx_test_table'::regclass, 1310001, 't'::"char", '-1610612736', '-1073741825'), ('mx_testing_schema.mx_test_table'::regclass, 1310002, 't'::"char", '-1073741824', '-536870913'), ('mx_testing_schema.mx_test_table'::regclass, 1310003, 't'::"char", '-536870912', '-1'), ('mx_testing_schema.mx_test_table'::regclass, 1310004, 't'::"char", '0', '536870911'), ('mx_testing_schema.mx_test_table'::regclass, 1310005, 't'::"char", '536870912', '1073741823'), ('mx_testing_schema.mx_test_table'::regclass, 1310006, 't'::"char", '1073741824', '1610612735'), ('mx_testing_schema.mx_test_table'::regclass, 1310007, 't'::"char", '1610612736', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; -(52 rows) +(54 rows) -- Show that range distributed tables are not included in the activate node snapshot UPDATE pg_dist_partition SET partmethod='r' WHERE logicalrelid='non_mx_test_table'::regclass; @@ -376,6 +406,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; CREATE TABLE mx_testing_schema.mx_test_table (col_1 integer, col_2 text NOT NULL, col_3 bigint DEFAULT nextval('mx_testing_schema.mx_test_table_col_3_seq'::regclass) NOT NULL, col_4 bigint DEFAULT nextval('public.user_defined_seq'::regclass)) USING heap DELETE FROM pg_catalog.pg_dist_colocation DELETE FROM pg_catalog.pg_dist_object + DELETE FROM pg_catalog.pg_dist_schema DELETE FROM pg_dist_node DELETE FROM pg_dist_partition DELETE FROM pg_dist_placement @@ -385,11 +416,12 @@ SELECT unnest(activate_node_snapshot()) order by 1; GRANT CREATE ON SCHEMA public TO pg_database_owner; GRANT USAGE ON SCHEMA public TO PUBLIC; GRANT USAGE ON SCHEMA public TO pg_database_owner; - INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(2, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) + INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 0, 'localhost', 57636, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', FALSE),(2, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(3, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) RESET ROLE RESET ROLE SELECT alter_role_if_exists('postgres', 'ALTER ROLE postgres SET lc_messages = ''C''') SELECT citus_internal_add_partition_metadata ('mx_testing_schema.mx_test_table'::regclass, 'h', 'col_1', 2, 's') + SELECT pg_catalog.worker_drop_sequence_dependency('mx_testing_schema.mx_test_table'); SELECT pg_catalog.worker_drop_sequence_dependency(logicalrelid::regclass::text) FROM pg_dist_partition SELECT pg_catalog.worker_record_sequence_dependency('mx_testing_schema.mx_test_table_col_3_seq'::regclass,'mx_testing_schema.mx_test_table'::regclass,'col_3') SELECT worker_apply_sequence_command ('CREATE SEQUENCE IF NOT EXISTS mx_testing_schema.mx_test_table_col_3_seq AS bigint INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1 NO CYCLE','bigint') @@ -403,9 +435,9 @@ SELECT unnest(activate_node_snapshot()) order by 1; SET citus.enable_ddl_propagation TO 'on' SET citus.enable_ddl_propagation TO 'on' UPDATE pg_dist_local_group SET groupid = 1 - UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 1 + UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 2 WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (2, 8, 1, 'integer'::regtype, NULL, NULL)) SELECT pg_catalog.citus_internal_add_colocation_metadata(colocationid, shardcount, replicationfactor, distributioncolumntype, coalesce(c.oid, 0)) FROM colocation_group_data d LEFT JOIN pg_collation c ON (d.distributioncolumncollationname = c.collname AND d.distributioncolumncollationschema::regnamespace = c.collnamespace) WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; @@ -416,14 +448,14 @@ SELECT unnest(activate_node_snapshot()) order by 1; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('table', ARRAY['mx_testing_schema', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310000, 0, 1, 100000), (1310001, 0, 2, 100001), (1310002, 0, 1, 100002), (1310003, 0, 2, 100003), (1310004, 0, 1, 100004), (1310005, 0, 2, 100005), (1310006, 0, 1, 100006), (1310007, 0, 2, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('mx_testing_schema.mx_test_table'::regclass, 1310000, 't'::"char", '-2147483648', '-1610612737'), ('mx_testing_schema.mx_test_table'::regclass, 1310001, 't'::"char", '-1610612736', '-1073741825'), ('mx_testing_schema.mx_test_table'::regclass, 1310002, 't'::"char", '-1073741824', '-536870913'), ('mx_testing_schema.mx_test_table'::regclass, 1310003, 't'::"char", '-536870912', '-1'), ('mx_testing_schema.mx_test_table'::regclass, 1310004, 't'::"char", '0', '536870911'), ('mx_testing_schema.mx_test_table'::regclass, 1310005, 't'::"char", '536870912', '1073741823'), ('mx_testing_schema.mx_test_table'::regclass, 1310006, 't'::"char", '1073741824', '1610612735'), ('mx_testing_schema.mx_test_table'::regclass, 1310007, 't'::"char", '1610612736', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; -(52 rows) +(54 rows) -- Test start_metadata_sync_to_node and citus_activate_node UDFs --- Ensure that hasmetadata=false for all nodes +-- Ensure that hasmetadata=false for all nodes except for the coordinator node SELECT count(*) FROM pg_dist_node WHERE hasmetadata=true; count --------------------------------------------------------------------- - 0 + 1 (1 row) -- Show that metadata can not be synced on secondary node @@ -431,7 +463,7 @@ SELECT groupid AS worker_1_group FROM pg_dist_node WHERE nodeport = :worker_1_po SELECT master_add_node('localhost', 8888, groupid => :worker_1_group, noderole => 'secondary'); master_add_node --------------------------------------------------------------------- - 4 + 5 (1 row) SELECT start_metadata_sync_to_node('localhost', 8888); @@ -463,7 +495,7 @@ SELECT hasmetadata FROM pg_dist_node WHERE nodeport = 8888; SELECT master_add_secondary_node('localhost', 8889, 'localhost', :worker_1_port, nodecluster => 'second-cluster'); master_add_secondary_node --------------------------------------------------------------------- - 5 + 6 (1 row) \c - - - :master_port @@ -477,7 +509,7 @@ SELECT 1 FROM citus_activate_node('localhost', :worker_1_port); SELECT nodeid, hasmetadata FROM pg_dist_node WHERE nodename='localhost' AND nodeport=:worker_1_port; nodeid | hasmetadata --------------------------------------------------------------------- - 1 | t + 2 | t (1 row) -- Check that the metadata has been copied to the worker @@ -491,11 +523,12 @@ SELECT * FROM pg_dist_local_group; SELECT * FROM pg_dist_node ORDER BY nodeid; nodeid | groupid | nodename | nodeport | noderack | hasmetadata | isactive | noderole | nodecluster | metadatasynced | shouldhaveshards --------------------------------------------------------------------- - 1 | 1 | localhost | 57637 | default | t | t | primary | default | t | t - 2 | 2 | localhost | 57638 | default | f | t | primary | default | f | t - 4 | 1 | localhost | 8888 | default | f | t | secondary | default | f | t - 5 | 1 | localhost | 8889 | default | f | t | secondary | second-cluster | f | t -(4 rows) + 1 | 0 | localhost | 57636 | default | t | t | primary | default | t | f + 2 | 1 | localhost | 57637 | default | t | t | primary | default | t | t + 3 | 2 | localhost | 57638 | default | f | t | primary | default | f | t + 5 | 1 | localhost | 8888 | default | f | t | secondary | default | f | t + 6 | 1 | localhost | 8889 | default | f | t | secondary | second-cluster | f | t +(5 rows) SELECT * FROM pg_dist_partition WHERE logicalrelid::text LIKE 'mx_testing_schema%' ORDER BY logicalrelid::text; logicalrelid | partmethod | partkey | colocationid | repmodel | autoconverted @@ -629,11 +662,12 @@ SELECT * FROM pg_dist_local_group; SELECT * FROM pg_dist_node ORDER BY nodeid; nodeid | groupid | nodename | nodeport | noderack | hasmetadata | isactive | noderole | nodecluster | metadatasynced | shouldhaveshards --------------------------------------------------------------------- - 1 | 1 | localhost | 57637 | default | t | t | primary | default | t | t - 2 | 2 | localhost | 57638 | default | f | t | primary | default | f | t - 4 | 1 | localhost | 8888 | default | f | t | secondary | default | f | t - 5 | 1 | localhost | 8889 | default | f | t | secondary | second-cluster | f | t -(4 rows) + 1 | 0 | localhost | 57636 | default | t | t | primary | default | t | f + 2 | 1 | localhost | 57637 | default | t | t | primary | default | t | t + 3 | 2 | localhost | 57638 | default | f | t | primary | default | f | t + 5 | 1 | localhost | 8888 | default | f | t | secondary | default | f | t + 6 | 1 | localhost | 8889 | default | f | t | secondary | second-cluster | f | t +(5 rows) SELECT * FROM pg_dist_partition WHERE logicalrelid::text LIKE 'mx_testing_schema%' ORDER BY logicalrelid::text; logicalrelid | partmethod | partkey | colocationid | repmodel | autoconverted @@ -728,6 +762,14 @@ SELECT create_distributed_table('mx_query_test', 'a'); (1 row) +CREATE TABLE single_shard_tbl(a int); +SELECT create_distributed_table('single_shard_tbl', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO single_shard_tbl VALUES (1); SELECT repmodel FROM pg_dist_partition WHERE logicalrelid='mx_query_test'::regclass; repmodel --------------------------------------------------------------------- @@ -752,6 +794,13 @@ SELECT * FROM mx_query_test ORDER BY a; INSERT INTO mx_query_test VALUES (6, 'six', 36); UPDATE mx_query_test SET c = 25 WHERE a = 5; +SELECT * FROM single_shard_tbl ORDER BY a; + a +--------------------------------------------------------------------- + 1 +(1 row) + +INSERT INTO single_shard_tbl VALUES (2); \c - - - :master_port SELECT * FROM mx_query_test ORDER BY a; a | b | c @@ -764,8 +813,16 @@ SELECT * FROM mx_query_test ORDER BY a; 6 | six | 36 (6 rows) +SELECT * FROM single_shard_tbl ORDER BY a; + a +--------------------------------------------------------------------- + 1 + 2 +(2 rows) + \c - - - :master_port DROP TABLE mx_query_test; +DROP TABLE single_shard_tbl; -- Check that stop_metadata_sync_to_node function sets hasmetadata of the node to false \c - - - :master_port SELECT start_metadata_sync_to_node('localhost', :worker_1_port); @@ -891,16 +948,16 @@ ORDER BY logicalrelid::text, shardid; logicalrelid | shardid | nodename | nodeport --------------------------------------------------------------------- - mx_test_schema_1.mx_table_1 | 1310020 | localhost | 57637 - mx_test_schema_1.mx_table_1 | 1310021 | localhost | 57638 mx_test_schema_1.mx_table_1 | 1310022 | localhost | 57637 mx_test_schema_1.mx_table_1 | 1310023 | localhost | 57638 mx_test_schema_1.mx_table_1 | 1310024 | localhost | 57637 - mx_test_schema_2.mx_table_2 | 1310025 | localhost | 57637 - mx_test_schema_2.mx_table_2 | 1310026 | localhost | 57638 + mx_test_schema_1.mx_table_1 | 1310025 | localhost | 57638 + mx_test_schema_1.mx_table_1 | 1310026 | localhost | 57637 mx_test_schema_2.mx_table_2 | 1310027 | localhost | 57637 mx_test_schema_2.mx_table_2 | 1310028 | localhost | 57638 mx_test_schema_2.mx_table_2 | 1310029 | localhost | 57637 + mx_test_schema_2.mx_table_2 | 1310030 | localhost | 57638 + mx_test_schema_2.mx_table_2 | 1310031 | localhost | 57637 (10 rows) -- Check that metadata of MX tables exist on the metadata worker @@ -942,16 +999,16 @@ ORDER BY logicalrelid::text, shardid; logicalrelid | shardid | nodename | nodeport --------------------------------------------------------------------- - mx_test_schema_1.mx_table_1 | 1310020 | localhost | 57637 - mx_test_schema_1.mx_table_1 | 1310021 | localhost | 57638 mx_test_schema_1.mx_table_1 | 1310022 | localhost | 57637 mx_test_schema_1.mx_table_1 | 1310023 | localhost | 57638 mx_test_schema_1.mx_table_1 | 1310024 | localhost | 57637 - mx_test_schema_2.mx_table_2 | 1310025 | localhost | 57637 - mx_test_schema_2.mx_table_2 | 1310026 | localhost | 57638 + mx_test_schema_1.mx_table_1 | 1310025 | localhost | 57638 + mx_test_schema_1.mx_table_1 | 1310026 | localhost | 57637 mx_test_schema_2.mx_table_2 | 1310027 | localhost | 57637 mx_test_schema_2.mx_table_2 | 1310028 | localhost | 57638 mx_test_schema_2.mx_table_2 | 1310029 | localhost | 57637 + mx_test_schema_2.mx_table_2 | 1310030 | localhost | 57638 + mx_test_schema_2.mx_table_2 | 1310031 | localhost | 57637 (10 rows) -- Check that metadata of MX tables don't exist on the non-metadata worker @@ -1454,7 +1511,7 @@ SELECT create_distributed_table('mx_table', 'a'); SELECT master_add_node('localhost', :worker_2_port); master_add_node --------------------------------------------------------------------- - 6 + 7 (1 row) \c - mx_user - :worker_1_port @@ -1565,9 +1622,10 @@ ORDER BY nodeport; logicalrelid | partmethod | repmodel | shardid | placementid | nodename | nodeport --------------------------------------------------------------------- - mx_ref | n | t | 1310072 | 100072 | localhost | 57637 - mx_ref | n | t | 1310072 | 100073 | localhost | 57638 -(2 rows) + mx_ref | n | t | 1310074 | 100074 | localhost | 57636 + mx_ref | n | t | 1310074 | 100075 | localhost | 57637 + mx_ref | n | t | 1310074 | 100076 | localhost | 57638 +(3 rows) SELECT shardid AS ref_table_shardid FROM pg_dist_shard WHERE logicalrelid='mx_ref'::regclass \gset -- make sure we have the pg_dist_colocation record on the worker @@ -1642,8 +1700,8 @@ DELETE FROM pg_dist_placement WHERE groupid = :old_worker_2_group; SELECT master_remove_node('localhost', :worker_2_port); WARNING: could not find any shard placements for shardId 1310001 -WARNING: could not find any shard placements for shardId 1310021 -WARNING: could not find any shard placements for shardId 1310026 +WARNING: could not find any shard placements for shardId 1310023 +WARNING: could not find any shard placements for shardId 1310028 master_remove_node --------------------------------------------------------------------- @@ -1661,8 +1719,9 @@ FROM pg_dist_shard NATURAL JOIN pg_dist_shard_placement WHERE logicalrelid='mx_ref'::regclass; shardid | nodename | nodeport --------------------------------------------------------------------- - 1310073 | localhost | 57637 -(1 row) + 1310075 | localhost | 57636 + 1310075 | localhost | 57637 +(2 rows) \c - - - :worker_1_port SELECT shardid, nodename, nodeport @@ -1670,15 +1729,16 @@ FROM pg_dist_shard NATURAL JOIN pg_dist_shard_placement WHERE logicalrelid='mx_ref'::regclass; shardid | nodename | nodeport --------------------------------------------------------------------- - 1310073 | localhost | 57637 -(1 row) + 1310075 | localhost | 57636 + 1310075 | localhost | 57637 +(2 rows) \c - - - :master_port SET client_min_messages TO ERROR; SELECT master_add_node('localhost', :worker_2_port); master_add_node --------------------------------------------------------------------- - 7 + 8 (1 row) RESET client_min_messages; @@ -1688,8 +1748,9 @@ WHERE logicalrelid='mx_ref'::regclass ORDER BY shardid, nodeport; shardid | nodename | nodeport --------------------------------------------------------------------- - 1310073 | localhost | 57637 -(1 row) + 1310075 | localhost | 57636 + 1310075 | localhost | 57637 +(2 rows) \c - - - :worker_1_port SELECT shardid, nodename, nodeport @@ -1698,8 +1759,9 @@ WHERE logicalrelid='mx_ref'::regclass ORDER BY shardid, nodeport; shardid | nodename | nodeport --------------------------------------------------------------------- - 1310073 | localhost | 57637 -(1 row) + 1310075 | localhost | 57636 + 1310075 | localhost | 57637 +(2 rows) -- Get the metadata back into a consistent state \c - - - :master_port @@ -1807,10 +1869,6 @@ HINT: If the node is up, wait until metadata gets synced to it and try again. ALTER TABLE dist_table_1 ADD COLUMN b int; ERROR: localhost:xxxxx is a metadata node, but is out of sync HINT: If the node is up, wait until metadata gets synced to it and try again. -SELECT master_add_node('localhost', :master_port, groupid => 0); -NOTICE: localhost:xxxxx is the coordinator and already contains metadata, skipping syncing the metadata -ERROR: localhost:xxxxx is a metadata node, but is out of sync -HINT: If the node is up, wait until metadata gets synced to it and try again. SELECT citus_disable_node_and_wait('localhost', :worker_1_port); ERROR: disabling the first worker node in the metadata is not allowed DETAIL: Citus uses the first worker node in the metadata for certain internal operations when replicated tables are modified. Synchronous mode ensures that all nodes have the same view of the first worker node, which is used for certain locking operations. @@ -1863,7 +1921,7 @@ SELECT wait_until_metadata_sync(60000); SELECT master_add_node('localhost', :worker_2_port); master_add_node --------------------------------------------------------------------- - 7 + 8 (1 row) CREATE SEQUENCE mx_test_sequence_0; @@ -1919,6 +1977,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; CREATE TABLE public.test_table (id integer DEFAULT worker_nextval('public.mx_test_sequence_0'::regclass), id2 integer DEFAULT worker_nextval('public.mx_test_sequence_1'::regclass)) USING heap DELETE FROM pg_catalog.pg_dist_colocation DELETE FROM pg_catalog.pg_dist_object + DELETE FROM pg_catalog.pg_dist_schema DELETE FROM pg_dist_node DELETE FROM pg_dist_partition DELETE FROM pg_dist_placement @@ -1933,16 +1992,22 @@ SELECT unnest(activate_node_snapshot()) order by 1; GRANT CREATE ON SCHEMA public TO pg_database_owner; GRANT USAGE ON SCHEMA public TO PUBLIC; GRANT USAGE ON SCHEMA public TO pg_database_owner; - INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (4, 1, 'localhost', 8888, 'default', FALSE, FALSE, TRUE, 'secondary'::noderole, 'default', TRUE),(5, 1, 'localhost', 8889, 'default', FALSE, FALSE, TRUE, 'secondary'::noderole, 'second-cluster', TRUE),(1, 1, 'localhost', 57637, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', TRUE),(7, 5, 'localhost', 57638, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', TRUE) + INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (5, 1, 'localhost', 8888, 'default', FALSE, FALSE, TRUE, 'secondary'::noderole, 'default', TRUE),(6, 1, 'localhost', 8889, 'default', FALSE, FALSE, TRUE, 'secondary'::noderole, 'second-cluster', TRUE),(1, 0, 'localhost', 57636, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', FALSE),(2, 1, 'localhost', 57637, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', TRUE),(8, 5, 'localhost', 57638, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', TRUE) RESET ROLE RESET ROLE SELECT alter_role_if_exists('postgres', 'ALTER ROLE postgres SET lc_messages = ''C''') - SELECT citus_internal_add_partition_metadata ('mx_test_schema_1.mx_table_1'::regclass, 'h', 'col1', 5, 's') - SELECT citus_internal_add_partition_metadata ('mx_test_schema_2.mx_table_2'::regclass, 'h', 'col1', 5, 's') + SELECT citus_internal_add_partition_metadata ('mx_test_schema_1.mx_table_1'::regclass, 'h', 'col1', 7, 's') + SELECT citus_internal_add_partition_metadata ('mx_test_schema_2.mx_table_2'::regclass, 'h', 'col1', 7, 's') SELECT citus_internal_add_partition_metadata ('mx_testing_schema.mx_test_table'::regclass, 'h', 'col_1', 2, 's') SELECT citus_internal_add_partition_metadata ('public.dist_table_1'::regclass, 'h', 'a', 10010, 's') SELECT citus_internal_add_partition_metadata ('public.mx_ref'::regclass, 'n', NULL, 10009, 't') SELECT citus_internal_add_partition_metadata ('public.test_table'::regclass, 'h', 'id', 10010, 's') + SELECT pg_catalog.worker_drop_sequence_dependency('mx_test_schema_1.mx_table_1'); + SELECT pg_catalog.worker_drop_sequence_dependency('mx_test_schema_2.mx_table_2'); + SELECT pg_catalog.worker_drop_sequence_dependency('mx_testing_schema.mx_test_table'); + SELECT pg_catalog.worker_drop_sequence_dependency('public.dist_table_1'); + SELECT pg_catalog.worker_drop_sequence_dependency('public.mx_ref'); + SELECT pg_catalog.worker_drop_sequence_dependency('public.test_table'); SELECT pg_catalog.worker_drop_sequence_dependency(logicalrelid::regclass::text) FROM pg_dist_partition SELECT pg_catalog.worker_record_sequence_dependency('mx_testing_schema.mx_test_table_col_3_seq'::regclass,'mx_testing_schema.mx_test_table'::regclass,'col_3') SELECT worker_apply_sequence_command ('CREATE SEQUENCE IF NOT EXISTS mx_testing_schema.mx_test_table_col_3_seq AS bigint INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1 NO CYCLE','bigint') @@ -1963,9 +2028,9 @@ SELECT unnest(activate_node_snapshot()) order by 1; SET citus.enable_ddl_propagation TO 'on' SET citus.enable_ddl_propagation TO 'on' UPDATE pg_dist_local_group SET groupid = 1 - UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 1 + UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 2 WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (10009, 1, -1, 0, NULL, NULL)) SELECT pg_catalog.citus_internal_add_colocation_metadata(colocationid, shardcount, replicationfactor, distributioncolumntype, coalesce(c.oid, 0)) FROM colocation_group_data d LEFT JOIN pg_collation c ON (d.distributioncolumncollationname = c.collname AND d.distributioncolumncollationschema::regnamespace = c.collnamespace) WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (10010, 4, 1, 'integer'::regtype, NULL, NULL)) SELECT pg_catalog.citus_internal_add_colocation_metadata(colocationid, shardcount, replicationfactor, distributioncolumntype, coalesce(c.oid, 0)) FROM colocation_group_data d LEFT JOIN pg_collation c ON (d.distributioncolumncollationname = c.collname AND d.distributioncolumncollationschema::regnamespace = c.collnamespace) WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; @@ -1986,18 +2051,18 @@ SELECT unnest(activate_node_snapshot()) order by 1; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('table', ARRAY['public', 'mx_ref']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('table', ARRAY['public', 'test_table']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310000, 0, 1, 100000), (1310001, 0, 5, 100001), (1310002, 0, 1, 100002), (1310003, 0, 5, 100003), (1310004, 0, 1, 100004), (1310005, 0, 5, 100005), (1310006, 0, 1, 100006), (1310007, 0, 5, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; - WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310020, 0, 1, 100020), (1310021, 0, 5, 100021), (1310022, 0, 1, 100022), (1310023, 0, 5, 100023), (1310024, 0, 1, 100024)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; - WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310025, 0, 1, 100025), (1310026, 0, 5, 100026), (1310027, 0, 1, 100027), (1310028, 0, 5, 100028), (1310029, 0, 1, 100029)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; - WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310073, 0, 1, 100074), (1310073, 0, 5, 100075)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; - WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310074, 0, 1, 100076), (1310075, 0, 5, 100077), (1310076, 0, 1, 100078), (1310077, 0, 5, 100079)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; - WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310083, 0, 1, 100086), (1310084, 0, 5, 100087), (1310085, 0, 1, 100088), (1310086, 0, 5, 100089)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; - WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('mx_test_schema_1.mx_table_1'::regclass, 1310020, 't'::"char", '-2147483648', '-1288490190'), ('mx_test_schema_1.mx_table_1'::regclass, 1310021, 't'::"char", '-1288490189', '-429496731'), ('mx_test_schema_1.mx_table_1'::regclass, 1310022, 't'::"char", '-429496730', '429496728'), ('mx_test_schema_1.mx_table_1'::regclass, 1310023, 't'::"char", '429496729', '1288490187'), ('mx_test_schema_1.mx_table_1'::regclass, 1310024, 't'::"char", '1288490188', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; - WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('mx_test_schema_2.mx_table_2'::regclass, 1310025, 't'::"char", '-2147483648', '-1288490190'), ('mx_test_schema_2.mx_table_2'::regclass, 1310026, 't'::"char", '-1288490189', '-429496731'), ('mx_test_schema_2.mx_table_2'::regclass, 1310027, 't'::"char", '-429496730', '429496728'), ('mx_test_schema_2.mx_table_2'::regclass, 1310028, 't'::"char", '429496729', '1288490187'), ('mx_test_schema_2.mx_table_2'::regclass, 1310029, 't'::"char", '1288490188', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; + WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310022, 0, 1, 100022), (1310023, 0, 5, 100023), (1310024, 0, 1, 100024), (1310025, 0, 5, 100025), (1310026, 0, 1, 100026)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; + WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310027, 0, 1, 100027), (1310028, 0, 5, 100028), (1310029, 0, 1, 100029), (1310030, 0, 5, 100030), (1310031, 0, 1, 100031)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; + WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310075, 0, 0, 100077), (1310075, 0, 1, 100078), (1310075, 0, 5, 100079)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; + WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310076, 0, 1, 100080), (1310077, 0, 5, 100081), (1310078, 0, 1, 100082), (1310079, 0, 5, 100083)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; + WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310085, 0, 1, 100091), (1310086, 0, 5, 100092), (1310087, 0, 1, 100093), (1310088, 0, 5, 100094)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; + WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('mx_test_schema_1.mx_table_1'::regclass, 1310022, 't'::"char", '-2147483648', '-1288490190'), ('mx_test_schema_1.mx_table_1'::regclass, 1310023, 't'::"char", '-1288490189', '-429496731'), ('mx_test_schema_1.mx_table_1'::regclass, 1310024, 't'::"char", '-429496730', '429496728'), ('mx_test_schema_1.mx_table_1'::regclass, 1310025, 't'::"char", '429496729', '1288490187'), ('mx_test_schema_1.mx_table_1'::regclass, 1310026, 't'::"char", '1288490188', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; + WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('mx_test_schema_2.mx_table_2'::regclass, 1310027, 't'::"char", '-2147483648', '-1288490190'), ('mx_test_schema_2.mx_table_2'::regclass, 1310028, 't'::"char", '-1288490189', '-429496731'), ('mx_test_schema_2.mx_table_2'::regclass, 1310029, 't'::"char", '-429496730', '429496728'), ('mx_test_schema_2.mx_table_2'::regclass, 1310030, 't'::"char", '429496729', '1288490187'), ('mx_test_schema_2.mx_table_2'::regclass, 1310031, 't'::"char", '1288490188', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('mx_testing_schema.mx_test_table'::regclass, 1310000, 't'::"char", '-2147483648', '-1610612737'), ('mx_testing_schema.mx_test_table'::regclass, 1310001, 't'::"char", '-1610612736', '-1073741825'), ('mx_testing_schema.mx_test_table'::regclass, 1310002, 't'::"char", '-1073741824', '-536870913'), ('mx_testing_schema.mx_test_table'::regclass, 1310003, 't'::"char", '-536870912', '-1'), ('mx_testing_schema.mx_test_table'::regclass, 1310004, 't'::"char", '0', '536870911'), ('mx_testing_schema.mx_test_table'::regclass, 1310005, 't'::"char", '536870912', '1073741823'), ('mx_testing_schema.mx_test_table'::regclass, 1310006, 't'::"char", '1073741824', '1610612735'), ('mx_testing_schema.mx_test_table'::regclass, 1310007, 't'::"char", '1610612736', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; - WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('public.dist_table_1'::regclass, 1310074, 't'::"char", '-2147483648', '-1073741825'), ('public.dist_table_1'::regclass, 1310075, 't'::"char", '-1073741824', '-1'), ('public.dist_table_1'::regclass, 1310076, 't'::"char", '0', '1073741823'), ('public.dist_table_1'::regclass, 1310077, 't'::"char", '1073741824', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; - WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('public.mx_ref'::regclass, 1310073, 't'::"char", NULL, NULL)) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; - WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('public.test_table'::regclass, 1310083, 't'::"char", '-2147483648', '-1073741825'), ('public.test_table'::regclass, 1310084, 't'::"char", '-1073741824', '-1'), ('public.test_table'::regclass, 1310085, 't'::"char", '0', '1073741823'), ('public.test_table'::regclass, 1310086, 't'::"char", '1073741824', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; -(111 rows) + WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('public.dist_table_1'::regclass, 1310076, 't'::"char", '-2147483648', '-1073741825'), ('public.dist_table_1'::regclass, 1310077, 't'::"char", '-1073741824', '-1'), ('public.dist_table_1'::regclass, 1310078, 't'::"char", '0', '1073741823'), ('public.dist_table_1'::regclass, 1310079, 't'::"char", '1073741824', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; + WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('public.mx_ref'::regclass, 1310075, 't'::"char", NULL, NULL)) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; + WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('public.test_table'::regclass, 1310085, 't'::"char", '-2147483648', '-1073741825'), ('public.test_table'::regclass, 1310086, 't'::"char", '-1073741824', '-1'), ('public.test_table'::regclass, 1310087, 't'::"char", '0', '1073741823'), ('public.test_table'::regclass, 1310088, 't'::"char", '1073741824', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; +(118 rows) -- shouldn't work since test_table is MX ALTER TABLE test_table ADD COLUMN id3 bigserial; diff --git a/src/test/regress/expected/multi_metadata_sync_0.out b/src/test/regress/expected/multi_metadata_sync_0.out index 5fb08bbc3..6e1ba6525 100644 --- a/src/test/regress/expected/multi_metadata_sync_0.out +++ b/src/test/regress/expected/multi_metadata_sync_0.out @@ -1,7 +1,7 @@ -- -- MULTI_METADATA_SYNC -- --- this test has different output for PG13/14 compared to PG15 +-- this test has different output for PG14 compared to PG15 -- In PG15, public schema is owned by pg_database_owner role -- Relevant PG commit: b073c3ccd06e4cb845e121387a43faa8c68a7b62 SHOW server_version \gset @@ -69,13 +69,14 @@ ALTER ROLE CURRENT_USER WITH PASSWORD 'dummypassword'; -- Show that, with no MX tables, activate node snapshot contains only the delete commands, -- pg_dist_node entries, pg_dist_object entries and roles. SELECT unnest(activate_node_snapshot()) order by 1; - unnest + unnest --------------------------------------------------------------------- ALTER DATABASE regression OWNER TO postgres; CALL pg_catalog.worker_drop_all_shell_tables(true) CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION postgres DELETE FROM pg_catalog.pg_dist_colocation DELETE FROM pg_catalog.pg_dist_object + DELETE FROM pg_catalog.pg_dist_schema DELETE FROM pg_dist_node DELETE FROM pg_dist_partition DELETE FROM pg_dist_placement @@ -84,7 +85,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; GRANT CREATE ON SCHEMA public TO postgres; GRANT USAGE ON SCHEMA public TO PUBLIC; GRANT USAGE ON SCHEMA public TO postgres; - INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(2, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) + INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 0, 'localhost', 57636, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', FALSE),(2, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(3, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) RESET ROLE RESET ROLE SELECT alter_role_if_exists('postgres', 'ALTER ROLE postgres SET lc_messages = ''C''') @@ -97,13 +98,13 @@ SELECT unnest(activate_node_snapshot()) order by 1; SET citus.enable_ddl_propagation TO 'on' SET citus.enable_ddl_propagation TO 'on' UPDATE pg_dist_local_group SET groupid = 1 - UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 1 + UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 2 WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('schema', ARRAY['public']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; -(32 rows) +(33 rows) -- Create a test table with constraints and SERIAL and default from user defined sequence CREATE SEQUENCE user_defined_seq; @@ -117,10 +118,19 @@ SELECT create_distributed_table('mx_test_table', 'col_1'); (1 row) reset citus.shard_count; -reset citus.shard_replication_factor; -- Set the replication model of the test table to streaming replication so that it is -- considered as an MX table UPDATE pg_dist_partition SET repmodel='s' WHERE logicalrelid='mx_test_table'::regclass; +-- add a single shard table and verify the creation commands are included in the activate node snapshot +CREATE TABLE single_shard_tbl(a int); +SELECT create_distributed_table('single_shard_tbl', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO single_shard_tbl VALUES (1); +reset citus.shard_replication_factor; -- Show that the created MX table is and its sequences are included in the activate node snapshot SELECT unnest(activate_node_snapshot()) order by 1; unnest @@ -130,31 +140,39 @@ SELECT unnest(activate_node_snapshot()) order by 1; ALTER SEQUENCE public.user_defined_seq OWNER TO postgres ALTER TABLE public.mx_test_table ADD CONSTRAINT mx_test_table_col_1_key UNIQUE (col_1) ALTER TABLE public.mx_test_table OWNER TO postgres + ALTER TABLE public.single_shard_tbl OWNER TO postgres CALL pg_catalog.worker_drop_all_shell_tables(true) CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION postgres CREATE TABLE public.mx_test_table (col_1 integer, col_2 text NOT NULL, col_3 bigint DEFAULT nextval('public.mx_test_table_col_3_seq'::regclass) NOT NULL, col_4 bigint DEFAULT nextval('public.user_defined_seq'::regclass)) USING heap + CREATE TABLE public.single_shard_tbl (a integer) USING heap DELETE FROM pg_catalog.pg_dist_colocation DELETE FROM pg_catalog.pg_dist_object + DELETE FROM pg_catalog.pg_dist_schema DELETE FROM pg_dist_node DELETE FROM pg_dist_partition DELETE FROM pg_dist_placement DELETE FROM pg_dist_shard DROP TABLE IF EXISTS public.mx_test_table CASCADE + DROP TABLE IF EXISTS public.single_shard_tbl CASCADE GRANT CREATE ON SCHEMA public TO PUBLIC; GRANT CREATE ON SCHEMA public TO postgres; GRANT USAGE ON SCHEMA public TO PUBLIC; GRANT USAGE ON SCHEMA public TO postgres; - INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(2, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) + INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 0, 'localhost', 57636, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', FALSE),(2, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(3, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) RESET ROLE RESET ROLE SELECT alter_role_if_exists('postgres', 'ALTER ROLE postgres SET lc_messages = ''C''') SELECT citus_internal_add_partition_metadata ('public.mx_test_table'::regclass, 'h', 'col_1', 2, 's') + SELECT citus_internal_add_partition_metadata ('public.single_shard_tbl'::regclass, 'n', NULL, 3, 's') + SELECT pg_catalog.worker_drop_sequence_dependency('public.mx_test_table'); + SELECT pg_catalog.worker_drop_sequence_dependency('public.single_shard_tbl'); SELECT pg_catalog.worker_drop_sequence_dependency(logicalrelid::regclass::text) FROM pg_dist_partition SELECT pg_catalog.worker_record_sequence_dependency('public.mx_test_table_col_3_seq'::regclass,'public.mx_test_table'::regclass,'col_3') SELECT worker_apply_sequence_command ('CREATE SEQUENCE IF NOT EXISTS public.mx_test_table_col_3_seq AS bigint INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1 NO CYCLE','bigint') SELECT worker_apply_sequence_command ('CREATE SEQUENCE IF NOT EXISTS public.user_defined_seq AS bigint INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1 NO CYCLE','bigint') SELECT worker_create_or_alter_role('postgres', 'CREATE ROLE postgres SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN REPLICATION BYPASSRLS CONNECTION LIMIT 0 PASSWORD ''md5c53670dddfc3bb4b5675c7872bc2249a'' VALID UNTIL ''2052-05-05 00:00:00-07''', 'ALTER ROLE postgres SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN REPLICATION BYPASSRLS CONNECTION LIMIT 0 PASSWORD ''md5c53670dddfc3bb4b5675c7872bc2249a'' VALID UNTIL ''2052-05-05 00:00:00-07''') SELECT worker_create_truncate_trigger('public.mx_test_table') + SELECT worker_create_truncate_trigger('public.single_shard_tbl') SET ROLE postgres SET ROLE postgres SET citus.enable_ddl_propagation TO 'off' @@ -162,20 +180,26 @@ SELECT unnest(activate_node_snapshot()) order by 1; SET citus.enable_ddl_propagation TO 'on' SET citus.enable_ddl_propagation TO 'on' UPDATE pg_dist_local_group SET groupid = 1 - UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 1 + UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 2 WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (2, 8, 1, 'integer'::regtype, NULL, NULL)) SELECT pg_catalog.citus_internal_add_colocation_metadata(colocationid, shardcount, replicationfactor, distributioncolumntype, coalesce(c.oid, 0)) FROM colocation_group_data d LEFT JOIN pg_collation c ON (d.distributioncolumncollationname = c.collname AND d.distributioncolumncollationschema::regnamespace = c.collnamespace) + WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (3, 1, 1, 0, NULL, NULL)) SELECT pg_catalog.citus_internal_add_colocation_metadata(colocationid, shardcount, replicationfactor, distributioncolumntype, coalesce(c.oid, 0)) FROM colocation_group_data d LEFT JOIN pg_collation c ON (d.distributioncolumncollationname = c.collname AND d.distributioncolumncollationschema::regnamespace = c.collnamespace) WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('schema', ARRAY['public']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('sequence', ARRAY['public', 'mx_test_table_col_3_seq']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('sequence', ARRAY['public', 'user_defined_seq']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('table', ARRAY['public', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; + WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('table', ARRAY['public', 'single_shard_tbl']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310000, 0, 1, 100000), (1310001, 0, 2, 100001), (1310002, 0, 1, 100002), (1310003, 0, 2, 100003), (1310004, 0, 1, 100004), (1310005, 0, 2, 100005), (1310006, 0, 1, 100006), (1310007, 0, 2, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; + WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310008, 0, 2, 100008)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('public.mx_test_table'::regclass, 1310000, 't'::"char", '-2147483648', '-1610612737'), ('public.mx_test_table'::regclass, 1310001, 't'::"char", '-1610612736', '-1073741825'), ('public.mx_test_table'::regclass, 1310002, 't'::"char", '-1073741824', '-536870913'), ('public.mx_test_table'::regclass, 1310003, 't'::"char", '-536870912', '-1'), ('public.mx_test_table'::regclass, 1310004, 't'::"char", '0', '536870911'), ('public.mx_test_table'::regclass, 1310005, 't'::"char", '536870912', '1073741823'), ('public.mx_test_table'::regclass, 1310006, 't'::"char", '1073741824', '1610612735'), ('public.mx_test_table'::regclass, 1310007, 't'::"char", '1610612736', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; -(49 rows) + WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('public.single_shard_tbl'::regclass, 1310008, 't'::"char", NULL, NULL)) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; +(61 rows) +-- Drop single shard table +DROP TABLE single_shard_tbl; -- Show that CREATE INDEX commands are included in the activate node snapshot CREATE INDEX mx_index ON mx_test_table(col_2); SELECT unnest(activate_node_snapshot()) order by 1; @@ -192,6 +216,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; CREATE TABLE public.mx_test_table (col_1 integer, col_2 text NOT NULL, col_3 bigint DEFAULT nextval('public.mx_test_table_col_3_seq'::regclass) NOT NULL, col_4 bigint DEFAULT nextval('public.user_defined_seq'::regclass)) USING heap DELETE FROM pg_catalog.pg_dist_colocation DELETE FROM pg_catalog.pg_dist_object + DELETE FROM pg_catalog.pg_dist_schema DELETE FROM pg_dist_node DELETE FROM pg_dist_partition DELETE FROM pg_dist_placement @@ -201,11 +226,12 @@ SELECT unnest(activate_node_snapshot()) order by 1; GRANT CREATE ON SCHEMA public TO postgres; GRANT USAGE ON SCHEMA public TO PUBLIC; GRANT USAGE ON SCHEMA public TO postgres; - INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(2, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) + INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 0, 'localhost', 57636, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', FALSE),(2, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(3, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) RESET ROLE RESET ROLE SELECT alter_role_if_exists('postgres', 'ALTER ROLE postgres SET lc_messages = ''C''') SELECT citus_internal_add_partition_metadata ('public.mx_test_table'::regclass, 'h', 'col_1', 2, 's') + SELECT pg_catalog.worker_drop_sequence_dependency('public.mx_test_table'); SELECT pg_catalog.worker_drop_sequence_dependency(logicalrelid::regclass::text) FROM pg_dist_partition SELECT pg_catalog.worker_record_sequence_dependency('public.mx_test_table_col_3_seq'::regclass,'public.mx_test_table'::regclass,'col_3') SELECT worker_apply_sequence_command ('CREATE SEQUENCE IF NOT EXISTS public.mx_test_table_col_3_seq AS bigint INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1 NO CYCLE','bigint') @@ -219,9 +245,9 @@ SELECT unnest(activate_node_snapshot()) order by 1; SET citus.enable_ddl_propagation TO 'on' SET citus.enable_ddl_propagation TO 'on' UPDATE pg_dist_local_group SET groupid = 1 - UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 1 + UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 2 WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (2, 8, 1, 'integer'::regtype, NULL, NULL)) SELECT pg_catalog.citus_internal_add_colocation_metadata(colocationid, shardcount, replicationfactor, distributioncolumntype, coalesce(c.oid, 0)) FROM colocation_group_data d LEFT JOIN pg_collation c ON (d.distributioncolumncollationname = c.collname AND d.distributioncolumncollationschema::regnamespace = c.collnamespace) WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; @@ -231,7 +257,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('table', ARRAY['public', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310000, 0, 1, 100000), (1310001, 0, 2, 100001), (1310002, 0, 1, 100002), (1310003, 0, 2, 100003), (1310004, 0, 1, 100004), (1310005, 0, 2, 100005), (1310006, 0, 1, 100006), (1310007, 0, 2, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('public.mx_test_table'::regclass, 1310000, 't'::"char", '-2147483648', '-1610612737'), ('public.mx_test_table'::regclass, 1310001, 't'::"char", '-1610612736', '-1073741825'), ('public.mx_test_table'::regclass, 1310002, 't'::"char", '-1073741824', '-536870913'), ('public.mx_test_table'::regclass, 1310003, 't'::"char", '-536870912', '-1'), ('public.mx_test_table'::regclass, 1310004, 't'::"char", '0', '536870911'), ('public.mx_test_table'::regclass, 1310005, 't'::"char", '536870912', '1073741823'), ('public.mx_test_table'::regclass, 1310006, 't'::"char", '1073741824', '1610612735'), ('public.mx_test_table'::regclass, 1310007, 't'::"char", '1610612736', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; -(50 rows) +(52 rows) -- Show that schema changes are included in the activate node snapshot CREATE SCHEMA mx_testing_schema; @@ -251,6 +277,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; CREATE TABLE mx_testing_schema.mx_test_table (col_1 integer, col_2 text NOT NULL, col_3 bigint DEFAULT nextval('mx_testing_schema.mx_test_table_col_3_seq'::regclass) NOT NULL, col_4 bigint DEFAULT nextval('public.user_defined_seq'::regclass)) USING heap DELETE FROM pg_catalog.pg_dist_colocation DELETE FROM pg_catalog.pg_dist_object + DELETE FROM pg_catalog.pg_dist_schema DELETE FROM pg_dist_node DELETE FROM pg_dist_partition DELETE FROM pg_dist_placement @@ -260,11 +287,12 @@ SELECT unnest(activate_node_snapshot()) order by 1; GRANT CREATE ON SCHEMA public TO postgres; GRANT USAGE ON SCHEMA public TO PUBLIC; GRANT USAGE ON SCHEMA public TO postgres; - INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(2, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) + INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 0, 'localhost', 57636, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', FALSE),(2, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(3, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) RESET ROLE RESET ROLE SELECT alter_role_if_exists('postgres', 'ALTER ROLE postgres SET lc_messages = ''C''') SELECT citus_internal_add_partition_metadata ('mx_testing_schema.mx_test_table'::regclass, 'h', 'col_1', 2, 's') + SELECT pg_catalog.worker_drop_sequence_dependency('mx_testing_schema.mx_test_table'); SELECT pg_catalog.worker_drop_sequence_dependency(logicalrelid::regclass::text) FROM pg_dist_partition SELECT pg_catalog.worker_record_sequence_dependency('mx_testing_schema.mx_test_table_col_3_seq'::regclass,'mx_testing_schema.mx_test_table'::regclass,'col_3') SELECT worker_apply_sequence_command ('CREATE SEQUENCE IF NOT EXISTS mx_testing_schema.mx_test_table_col_3_seq AS bigint INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1 NO CYCLE','bigint') @@ -278,9 +306,9 @@ SELECT unnest(activate_node_snapshot()) order by 1; SET citus.enable_ddl_propagation TO 'on' SET citus.enable_ddl_propagation TO 'on' UPDATE pg_dist_local_group SET groupid = 1 - UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 1 + UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 2 WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (2, 8, 1, 'integer'::regtype, NULL, NULL)) SELECT pg_catalog.citus_internal_add_colocation_metadata(colocationid, shardcount, replicationfactor, distributioncolumntype, coalesce(c.oid, 0)) FROM colocation_group_data d LEFT JOIN pg_collation c ON (d.distributioncolumncollationname = c.collname AND d.distributioncolumncollationschema::regnamespace = c.collnamespace) WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; @@ -291,7 +319,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('table', ARRAY['mx_testing_schema', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310000, 0, 1, 100000), (1310001, 0, 2, 100001), (1310002, 0, 1, 100002), (1310003, 0, 2, 100003), (1310004, 0, 1, 100004), (1310005, 0, 2, 100005), (1310006, 0, 1, 100006), (1310007, 0, 2, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('mx_testing_schema.mx_test_table'::regclass, 1310000, 't'::"char", '-2147483648', '-1610612737'), ('mx_testing_schema.mx_test_table'::regclass, 1310001, 't'::"char", '-1610612736', '-1073741825'), ('mx_testing_schema.mx_test_table'::regclass, 1310002, 't'::"char", '-1073741824', '-536870913'), ('mx_testing_schema.mx_test_table'::regclass, 1310003, 't'::"char", '-536870912', '-1'), ('mx_testing_schema.mx_test_table'::regclass, 1310004, 't'::"char", '0', '536870911'), ('mx_testing_schema.mx_test_table'::regclass, 1310005, 't'::"char", '536870912', '1073741823'), ('mx_testing_schema.mx_test_table'::regclass, 1310006, 't'::"char", '1073741824', '1610612735'), ('mx_testing_schema.mx_test_table'::regclass, 1310007, 't'::"char", '1610612736', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; -(52 rows) +(54 rows) -- Show that append distributed tables are not included in the activate node snapshot CREATE TABLE non_mx_test_table (col_1 int, col_2 text); @@ -317,6 +345,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; CREATE TABLE mx_testing_schema.mx_test_table (col_1 integer, col_2 text NOT NULL, col_3 bigint DEFAULT nextval('mx_testing_schema.mx_test_table_col_3_seq'::regclass) NOT NULL, col_4 bigint DEFAULT nextval('public.user_defined_seq'::regclass)) USING heap DELETE FROM pg_catalog.pg_dist_colocation DELETE FROM pg_catalog.pg_dist_object + DELETE FROM pg_catalog.pg_dist_schema DELETE FROM pg_dist_node DELETE FROM pg_dist_partition DELETE FROM pg_dist_placement @@ -326,11 +355,12 @@ SELECT unnest(activate_node_snapshot()) order by 1; GRANT CREATE ON SCHEMA public TO postgres; GRANT USAGE ON SCHEMA public TO PUBLIC; GRANT USAGE ON SCHEMA public TO postgres; - INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(2, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) + INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 0, 'localhost', 57636, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', FALSE),(2, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(3, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) RESET ROLE RESET ROLE SELECT alter_role_if_exists('postgres', 'ALTER ROLE postgres SET lc_messages = ''C''') SELECT citus_internal_add_partition_metadata ('mx_testing_schema.mx_test_table'::regclass, 'h', 'col_1', 2, 's') + SELECT pg_catalog.worker_drop_sequence_dependency('mx_testing_schema.mx_test_table'); SELECT pg_catalog.worker_drop_sequence_dependency(logicalrelid::regclass::text) FROM pg_dist_partition SELECT pg_catalog.worker_record_sequence_dependency('mx_testing_schema.mx_test_table_col_3_seq'::regclass,'mx_testing_schema.mx_test_table'::regclass,'col_3') SELECT worker_apply_sequence_command ('CREATE SEQUENCE IF NOT EXISTS mx_testing_schema.mx_test_table_col_3_seq AS bigint INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1 NO CYCLE','bigint') @@ -344,9 +374,9 @@ SELECT unnest(activate_node_snapshot()) order by 1; SET citus.enable_ddl_propagation TO 'on' SET citus.enable_ddl_propagation TO 'on' UPDATE pg_dist_local_group SET groupid = 1 - UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 1 + UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 2 WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (2, 8, 1, 'integer'::regtype, NULL, NULL)) SELECT pg_catalog.citus_internal_add_colocation_metadata(colocationid, shardcount, replicationfactor, distributioncolumntype, coalesce(c.oid, 0)) FROM colocation_group_data d LEFT JOIN pg_collation c ON (d.distributioncolumncollationname = c.collname AND d.distributioncolumncollationschema::regnamespace = c.collnamespace) WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; @@ -357,7 +387,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('table', ARRAY['mx_testing_schema', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310000, 0, 1, 100000), (1310001, 0, 2, 100001), (1310002, 0, 1, 100002), (1310003, 0, 2, 100003), (1310004, 0, 1, 100004), (1310005, 0, 2, 100005), (1310006, 0, 1, 100006), (1310007, 0, 2, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('mx_testing_schema.mx_test_table'::regclass, 1310000, 't'::"char", '-2147483648', '-1610612737'), ('mx_testing_schema.mx_test_table'::regclass, 1310001, 't'::"char", '-1610612736', '-1073741825'), ('mx_testing_schema.mx_test_table'::regclass, 1310002, 't'::"char", '-1073741824', '-536870913'), ('mx_testing_schema.mx_test_table'::regclass, 1310003, 't'::"char", '-536870912', '-1'), ('mx_testing_schema.mx_test_table'::regclass, 1310004, 't'::"char", '0', '536870911'), ('mx_testing_schema.mx_test_table'::regclass, 1310005, 't'::"char", '536870912', '1073741823'), ('mx_testing_schema.mx_test_table'::regclass, 1310006, 't'::"char", '1073741824', '1610612735'), ('mx_testing_schema.mx_test_table'::regclass, 1310007, 't'::"char", '1610612736', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; -(52 rows) +(54 rows) -- Show that range distributed tables are not included in the activate node snapshot UPDATE pg_dist_partition SET partmethod='r' WHERE logicalrelid='non_mx_test_table'::regclass; @@ -376,6 +406,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; CREATE TABLE mx_testing_schema.mx_test_table (col_1 integer, col_2 text NOT NULL, col_3 bigint DEFAULT nextval('mx_testing_schema.mx_test_table_col_3_seq'::regclass) NOT NULL, col_4 bigint DEFAULT nextval('public.user_defined_seq'::regclass)) USING heap DELETE FROM pg_catalog.pg_dist_colocation DELETE FROM pg_catalog.pg_dist_object + DELETE FROM pg_catalog.pg_dist_schema DELETE FROM pg_dist_node DELETE FROM pg_dist_partition DELETE FROM pg_dist_placement @@ -385,11 +416,12 @@ SELECT unnest(activate_node_snapshot()) order by 1; GRANT CREATE ON SCHEMA public TO postgres; GRANT USAGE ON SCHEMA public TO PUBLIC; GRANT USAGE ON SCHEMA public TO postgres; - INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(2, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) + INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (1, 0, 'localhost', 57636, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', FALSE),(2, 1, 'localhost', 57637, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE),(3, 2, 'localhost', 57638, 'default', FALSE, FALSE, TRUE, 'primary'::noderole, 'default', TRUE) RESET ROLE RESET ROLE SELECT alter_role_if_exists('postgres', 'ALTER ROLE postgres SET lc_messages = ''C''') SELECT citus_internal_add_partition_metadata ('mx_testing_schema.mx_test_table'::regclass, 'h', 'col_1', 2, 's') + SELECT pg_catalog.worker_drop_sequence_dependency('mx_testing_schema.mx_test_table'); SELECT pg_catalog.worker_drop_sequence_dependency(logicalrelid::regclass::text) FROM pg_dist_partition SELECT pg_catalog.worker_record_sequence_dependency('mx_testing_schema.mx_test_table_col_3_seq'::regclass,'mx_testing_schema.mx_test_table'::regclass,'col_3') SELECT worker_apply_sequence_command ('CREATE SEQUENCE IF NOT EXISTS mx_testing_schema.mx_test_table_col_3_seq AS bigint INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1 NO CYCLE','bigint') @@ -403,9 +435,9 @@ SELECT unnest(activate_node_snapshot()) order by 1; SET citus.enable_ddl_propagation TO 'on' SET citus.enable_ddl_propagation TO 'on' UPDATE pg_dist_local_group SET groupid = 1 - UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 1 + UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 2 WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (2, 8, 1, 'integer'::regtype, NULL, NULL)) SELECT pg_catalog.citus_internal_add_colocation_metadata(colocationid, shardcount, replicationfactor, distributioncolumntype, coalesce(c.oid, 0)) FROM colocation_group_data d LEFT JOIN pg_collation c ON (d.distributioncolumncollationname = c.collname AND d.distributioncolumncollationschema::regnamespace = c.collnamespace) WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; @@ -416,14 +448,14 @@ SELECT unnest(activate_node_snapshot()) order by 1; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('table', ARRAY['mx_testing_schema', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310000, 0, 1, 100000), (1310001, 0, 2, 100001), (1310002, 0, 1, 100002), (1310003, 0, 2, 100003), (1310004, 0, 1, 100004), (1310005, 0, 2, 100005), (1310006, 0, 1, 100006), (1310007, 0, 2, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('mx_testing_schema.mx_test_table'::regclass, 1310000, 't'::"char", '-2147483648', '-1610612737'), ('mx_testing_schema.mx_test_table'::regclass, 1310001, 't'::"char", '-1610612736', '-1073741825'), ('mx_testing_schema.mx_test_table'::regclass, 1310002, 't'::"char", '-1073741824', '-536870913'), ('mx_testing_schema.mx_test_table'::regclass, 1310003, 't'::"char", '-536870912', '-1'), ('mx_testing_schema.mx_test_table'::regclass, 1310004, 't'::"char", '0', '536870911'), ('mx_testing_schema.mx_test_table'::regclass, 1310005, 't'::"char", '536870912', '1073741823'), ('mx_testing_schema.mx_test_table'::regclass, 1310006, 't'::"char", '1073741824', '1610612735'), ('mx_testing_schema.mx_test_table'::regclass, 1310007, 't'::"char", '1610612736', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; -(52 rows) +(54 rows) -- Test start_metadata_sync_to_node and citus_activate_node UDFs --- Ensure that hasmetadata=false for all nodes +-- Ensure that hasmetadata=false for all nodes except for the coordinator node SELECT count(*) FROM pg_dist_node WHERE hasmetadata=true; count --------------------------------------------------------------------- - 0 + 1 (1 row) -- Show that metadata can not be synced on secondary node @@ -431,7 +463,7 @@ SELECT groupid AS worker_1_group FROM pg_dist_node WHERE nodeport = :worker_1_po SELECT master_add_node('localhost', 8888, groupid => :worker_1_group, noderole => 'secondary'); master_add_node --------------------------------------------------------------------- - 4 + 5 (1 row) SELECT start_metadata_sync_to_node('localhost', 8888); @@ -463,7 +495,7 @@ SELECT hasmetadata FROM pg_dist_node WHERE nodeport = 8888; SELECT master_add_secondary_node('localhost', 8889, 'localhost', :worker_1_port, nodecluster => 'second-cluster'); master_add_secondary_node --------------------------------------------------------------------- - 5 + 6 (1 row) \c - - - :master_port @@ -477,7 +509,7 @@ SELECT 1 FROM citus_activate_node('localhost', :worker_1_port); SELECT nodeid, hasmetadata FROM pg_dist_node WHERE nodename='localhost' AND nodeport=:worker_1_port; nodeid | hasmetadata --------------------------------------------------------------------- - 1 | t + 2 | t (1 row) -- Check that the metadata has been copied to the worker @@ -491,11 +523,12 @@ SELECT * FROM pg_dist_local_group; SELECT * FROM pg_dist_node ORDER BY nodeid; nodeid | groupid | nodename | nodeport | noderack | hasmetadata | isactive | noderole | nodecluster | metadatasynced | shouldhaveshards --------------------------------------------------------------------- - 1 | 1 | localhost | 57637 | default | t | t | primary | default | t | t - 2 | 2 | localhost | 57638 | default | f | t | primary | default | f | t - 4 | 1 | localhost | 8888 | default | f | t | secondary | default | f | t - 5 | 1 | localhost | 8889 | default | f | t | secondary | second-cluster | f | t -(4 rows) + 1 | 0 | localhost | 57636 | default | t | t | primary | default | t | f + 2 | 1 | localhost | 57637 | default | t | t | primary | default | t | t + 3 | 2 | localhost | 57638 | default | f | t | primary | default | f | t + 5 | 1 | localhost | 8888 | default | f | t | secondary | default | f | t + 6 | 1 | localhost | 8889 | default | f | t | secondary | second-cluster | f | t +(5 rows) SELECT * FROM pg_dist_partition WHERE logicalrelid::text LIKE 'mx_testing_schema%' ORDER BY logicalrelid::text; logicalrelid | partmethod | partkey | colocationid | repmodel | autoconverted @@ -629,11 +662,12 @@ SELECT * FROM pg_dist_local_group; SELECT * FROM pg_dist_node ORDER BY nodeid; nodeid | groupid | nodename | nodeport | noderack | hasmetadata | isactive | noderole | nodecluster | metadatasynced | shouldhaveshards --------------------------------------------------------------------- - 1 | 1 | localhost | 57637 | default | t | t | primary | default | t | t - 2 | 2 | localhost | 57638 | default | f | t | primary | default | f | t - 4 | 1 | localhost | 8888 | default | f | t | secondary | default | f | t - 5 | 1 | localhost | 8889 | default | f | t | secondary | second-cluster | f | t -(4 rows) + 1 | 0 | localhost | 57636 | default | t | t | primary | default | t | f + 2 | 1 | localhost | 57637 | default | t | t | primary | default | t | t + 3 | 2 | localhost | 57638 | default | f | t | primary | default | f | t + 5 | 1 | localhost | 8888 | default | f | t | secondary | default | f | t + 6 | 1 | localhost | 8889 | default | f | t | secondary | second-cluster | f | t +(5 rows) SELECT * FROM pg_dist_partition WHERE logicalrelid::text LIKE 'mx_testing_schema%' ORDER BY logicalrelid::text; logicalrelid | partmethod | partkey | colocationid | repmodel | autoconverted @@ -728,6 +762,14 @@ SELECT create_distributed_table('mx_query_test', 'a'); (1 row) +CREATE TABLE single_shard_tbl(a int); +SELECT create_distributed_table('single_shard_tbl', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO single_shard_tbl VALUES (1); SELECT repmodel FROM pg_dist_partition WHERE logicalrelid='mx_query_test'::regclass; repmodel --------------------------------------------------------------------- @@ -752,6 +794,13 @@ SELECT * FROM mx_query_test ORDER BY a; INSERT INTO mx_query_test VALUES (6, 'six', 36); UPDATE mx_query_test SET c = 25 WHERE a = 5; +SELECT * FROM single_shard_tbl ORDER BY a; + a +--------------------------------------------------------------------- + 1 +(1 row) + +INSERT INTO single_shard_tbl VALUES (2); \c - - - :master_port SELECT * FROM mx_query_test ORDER BY a; a | b | c @@ -764,8 +813,16 @@ SELECT * FROM mx_query_test ORDER BY a; 6 | six | 36 (6 rows) +SELECT * FROM single_shard_tbl ORDER BY a; + a +--------------------------------------------------------------------- + 1 + 2 +(2 rows) + \c - - - :master_port DROP TABLE mx_query_test; +DROP TABLE single_shard_tbl; -- Check that stop_metadata_sync_to_node function sets hasmetadata of the node to false \c - - - :master_port SELECT start_metadata_sync_to_node('localhost', :worker_1_port); @@ -891,16 +948,16 @@ ORDER BY logicalrelid::text, shardid; logicalrelid | shardid | nodename | nodeport --------------------------------------------------------------------- - mx_test_schema_1.mx_table_1 | 1310020 | localhost | 57637 - mx_test_schema_1.mx_table_1 | 1310021 | localhost | 57638 mx_test_schema_1.mx_table_1 | 1310022 | localhost | 57637 mx_test_schema_1.mx_table_1 | 1310023 | localhost | 57638 mx_test_schema_1.mx_table_1 | 1310024 | localhost | 57637 - mx_test_schema_2.mx_table_2 | 1310025 | localhost | 57637 - mx_test_schema_2.mx_table_2 | 1310026 | localhost | 57638 + mx_test_schema_1.mx_table_1 | 1310025 | localhost | 57638 + mx_test_schema_1.mx_table_1 | 1310026 | localhost | 57637 mx_test_schema_2.mx_table_2 | 1310027 | localhost | 57637 mx_test_schema_2.mx_table_2 | 1310028 | localhost | 57638 mx_test_schema_2.mx_table_2 | 1310029 | localhost | 57637 + mx_test_schema_2.mx_table_2 | 1310030 | localhost | 57638 + mx_test_schema_2.mx_table_2 | 1310031 | localhost | 57637 (10 rows) -- Check that metadata of MX tables exist on the metadata worker @@ -942,16 +999,16 @@ ORDER BY logicalrelid::text, shardid; logicalrelid | shardid | nodename | nodeport --------------------------------------------------------------------- - mx_test_schema_1.mx_table_1 | 1310020 | localhost | 57637 - mx_test_schema_1.mx_table_1 | 1310021 | localhost | 57638 mx_test_schema_1.mx_table_1 | 1310022 | localhost | 57637 mx_test_schema_1.mx_table_1 | 1310023 | localhost | 57638 mx_test_schema_1.mx_table_1 | 1310024 | localhost | 57637 - mx_test_schema_2.mx_table_2 | 1310025 | localhost | 57637 - mx_test_schema_2.mx_table_2 | 1310026 | localhost | 57638 + mx_test_schema_1.mx_table_1 | 1310025 | localhost | 57638 + mx_test_schema_1.mx_table_1 | 1310026 | localhost | 57637 mx_test_schema_2.mx_table_2 | 1310027 | localhost | 57637 mx_test_schema_2.mx_table_2 | 1310028 | localhost | 57638 mx_test_schema_2.mx_table_2 | 1310029 | localhost | 57637 + mx_test_schema_2.mx_table_2 | 1310030 | localhost | 57638 + mx_test_schema_2.mx_table_2 | 1310031 | localhost | 57637 (10 rows) -- Check that metadata of MX tables don't exist on the non-metadata worker @@ -1454,7 +1511,7 @@ SELECT create_distributed_table('mx_table', 'a'); SELECT master_add_node('localhost', :worker_2_port); master_add_node --------------------------------------------------------------------- - 6 + 7 (1 row) \c - mx_user - :worker_1_port @@ -1565,9 +1622,10 @@ ORDER BY nodeport; logicalrelid | partmethod | repmodel | shardid | placementid | nodename | nodeport --------------------------------------------------------------------- - mx_ref | n | t | 1310072 | 100072 | localhost | 57637 - mx_ref | n | t | 1310072 | 100073 | localhost | 57638 -(2 rows) + mx_ref | n | t | 1310074 | 100074 | localhost | 57636 + mx_ref | n | t | 1310074 | 100075 | localhost | 57637 + mx_ref | n | t | 1310074 | 100076 | localhost | 57638 +(3 rows) SELECT shardid AS ref_table_shardid FROM pg_dist_shard WHERE logicalrelid='mx_ref'::regclass \gset -- make sure we have the pg_dist_colocation record on the worker @@ -1642,8 +1700,8 @@ DELETE FROM pg_dist_placement WHERE groupid = :old_worker_2_group; SELECT master_remove_node('localhost', :worker_2_port); WARNING: could not find any shard placements for shardId 1310001 -WARNING: could not find any shard placements for shardId 1310021 -WARNING: could not find any shard placements for shardId 1310026 +WARNING: could not find any shard placements for shardId 1310023 +WARNING: could not find any shard placements for shardId 1310028 master_remove_node --------------------------------------------------------------------- @@ -1661,8 +1719,9 @@ FROM pg_dist_shard NATURAL JOIN pg_dist_shard_placement WHERE logicalrelid='mx_ref'::regclass; shardid | nodename | nodeport --------------------------------------------------------------------- - 1310073 | localhost | 57637 -(1 row) + 1310075 | localhost | 57636 + 1310075 | localhost | 57637 +(2 rows) \c - - - :worker_1_port SELECT shardid, nodename, nodeport @@ -1670,15 +1729,16 @@ FROM pg_dist_shard NATURAL JOIN pg_dist_shard_placement WHERE logicalrelid='mx_ref'::regclass; shardid | nodename | nodeport --------------------------------------------------------------------- - 1310073 | localhost | 57637 -(1 row) + 1310075 | localhost | 57636 + 1310075 | localhost | 57637 +(2 rows) \c - - - :master_port SET client_min_messages TO ERROR; SELECT master_add_node('localhost', :worker_2_port); master_add_node --------------------------------------------------------------------- - 7 + 8 (1 row) RESET client_min_messages; @@ -1688,8 +1748,9 @@ WHERE logicalrelid='mx_ref'::regclass ORDER BY shardid, nodeport; shardid | nodename | nodeport --------------------------------------------------------------------- - 1310073 | localhost | 57637 -(1 row) + 1310075 | localhost | 57636 + 1310075 | localhost | 57637 +(2 rows) \c - - - :worker_1_port SELECT shardid, nodename, nodeport @@ -1698,8 +1759,9 @@ WHERE logicalrelid='mx_ref'::regclass ORDER BY shardid, nodeport; shardid | nodename | nodeport --------------------------------------------------------------------- - 1310073 | localhost | 57637 -(1 row) + 1310075 | localhost | 57636 + 1310075 | localhost | 57637 +(2 rows) -- Get the metadata back into a consistent state \c - - - :master_port @@ -1807,10 +1869,6 @@ HINT: If the node is up, wait until metadata gets synced to it and try again. ALTER TABLE dist_table_1 ADD COLUMN b int; ERROR: localhost:xxxxx is a metadata node, but is out of sync HINT: If the node is up, wait until metadata gets synced to it and try again. -SELECT master_add_node('localhost', :master_port, groupid => 0); -NOTICE: localhost:xxxxx is the coordinator and already contains metadata, skipping syncing the metadata -ERROR: localhost:xxxxx is a metadata node, but is out of sync -HINT: If the node is up, wait until metadata gets synced to it and try again. SELECT citus_disable_node_and_wait('localhost', :worker_1_port); ERROR: disabling the first worker node in the metadata is not allowed DETAIL: Citus uses the first worker node in the metadata for certain internal operations when replicated tables are modified. Synchronous mode ensures that all nodes have the same view of the first worker node, which is used for certain locking operations. @@ -1863,7 +1921,7 @@ SELECT wait_until_metadata_sync(60000); SELECT master_add_node('localhost', :worker_2_port); master_add_node --------------------------------------------------------------------- - 7 + 8 (1 row) CREATE SEQUENCE mx_test_sequence_0; @@ -1919,6 +1977,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; CREATE TABLE public.test_table (id integer DEFAULT worker_nextval('public.mx_test_sequence_0'::regclass), id2 integer DEFAULT worker_nextval('public.mx_test_sequence_1'::regclass)) USING heap DELETE FROM pg_catalog.pg_dist_colocation DELETE FROM pg_catalog.pg_dist_object + DELETE FROM pg_catalog.pg_dist_schema DELETE FROM pg_dist_node DELETE FROM pg_dist_partition DELETE FROM pg_dist_placement @@ -1933,16 +1992,22 @@ SELECT unnest(activate_node_snapshot()) order by 1; GRANT CREATE ON SCHEMA public TO postgres; GRANT USAGE ON SCHEMA public TO PUBLIC; GRANT USAGE ON SCHEMA public TO postgres; - INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (4, 1, 'localhost', 8888, 'default', FALSE, FALSE, TRUE, 'secondary'::noderole, 'default', TRUE),(5, 1, 'localhost', 8889, 'default', FALSE, FALSE, TRUE, 'secondary'::noderole, 'second-cluster', TRUE),(1, 1, 'localhost', 57637, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', TRUE),(7, 5, 'localhost', 57638, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', TRUE) + INSERT INTO pg_dist_node (nodeid, groupid, nodename, nodeport, noderack, hasmetadata, metadatasynced, isactive, noderole, nodecluster, shouldhaveshards) VALUES (5, 1, 'localhost', 8888, 'default', FALSE, FALSE, TRUE, 'secondary'::noderole, 'default', TRUE),(6, 1, 'localhost', 8889, 'default', FALSE, FALSE, TRUE, 'secondary'::noderole, 'second-cluster', TRUE),(1, 0, 'localhost', 57636, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', FALSE),(2, 1, 'localhost', 57637, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', TRUE),(8, 5, 'localhost', 57638, 'default', TRUE, TRUE, TRUE, 'primary'::noderole, 'default', TRUE) RESET ROLE RESET ROLE SELECT alter_role_if_exists('postgres', 'ALTER ROLE postgres SET lc_messages = ''C''') - SELECT citus_internal_add_partition_metadata ('mx_test_schema_1.mx_table_1'::regclass, 'h', 'col1', 5, 's') - SELECT citus_internal_add_partition_metadata ('mx_test_schema_2.mx_table_2'::regclass, 'h', 'col1', 5, 's') + SELECT citus_internal_add_partition_metadata ('mx_test_schema_1.mx_table_1'::regclass, 'h', 'col1', 7, 's') + SELECT citus_internal_add_partition_metadata ('mx_test_schema_2.mx_table_2'::regclass, 'h', 'col1', 7, 's') SELECT citus_internal_add_partition_metadata ('mx_testing_schema.mx_test_table'::regclass, 'h', 'col_1', 2, 's') SELECT citus_internal_add_partition_metadata ('public.dist_table_1'::regclass, 'h', 'a', 10010, 's') SELECT citus_internal_add_partition_metadata ('public.mx_ref'::regclass, 'n', NULL, 10009, 't') SELECT citus_internal_add_partition_metadata ('public.test_table'::regclass, 'h', 'id', 10010, 's') + SELECT pg_catalog.worker_drop_sequence_dependency('mx_test_schema_1.mx_table_1'); + SELECT pg_catalog.worker_drop_sequence_dependency('mx_test_schema_2.mx_table_2'); + SELECT pg_catalog.worker_drop_sequence_dependency('mx_testing_schema.mx_test_table'); + SELECT pg_catalog.worker_drop_sequence_dependency('public.dist_table_1'); + SELECT pg_catalog.worker_drop_sequence_dependency('public.mx_ref'); + SELECT pg_catalog.worker_drop_sequence_dependency('public.test_table'); SELECT pg_catalog.worker_drop_sequence_dependency(logicalrelid::regclass::text) FROM pg_dist_partition SELECT pg_catalog.worker_record_sequence_dependency('mx_testing_schema.mx_test_table_col_3_seq'::regclass,'mx_testing_schema.mx_test_table'::regclass,'col_3') SELECT worker_apply_sequence_command ('CREATE SEQUENCE IF NOT EXISTS mx_testing_schema.mx_test_table_col_3_seq AS bigint INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1 NO CYCLE','bigint') @@ -1963,9 +2028,9 @@ SELECT unnest(activate_node_snapshot()) order by 1; SET citus.enable_ddl_propagation TO 'on' SET citus.enable_ddl_propagation TO 'on' UPDATE pg_dist_local_group SET groupid = 1 - UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 1 - UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 1 + UPDATE pg_dist_node SET hasmetadata = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET isactive = TRUE WHERE nodeid = 2 + UPDATE pg_dist_node SET metadatasynced = TRUE WHERE nodeid = 2 WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (10009, 1, -1, 0, NULL, NULL)) SELECT pg_catalog.citus_internal_add_colocation_metadata(colocationid, shardcount, replicationfactor, distributioncolumntype, coalesce(c.oid, 0)) FROM colocation_group_data d LEFT JOIN pg_collation c ON (d.distributioncolumncollationname = c.collname AND d.distributioncolumncollationschema::regnamespace = c.collnamespace) WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (10010, 4, 1, 'integer'::regtype, NULL, NULL)) SELECT pg_catalog.citus_internal_add_colocation_metadata(colocationid, shardcount, replicationfactor, distributioncolumntype, coalesce(c.oid, 0)) FROM colocation_group_data d LEFT JOIN pg_collation c ON (d.distributioncolumncollationname = c.collname AND d.distributioncolumncollationschema::regnamespace = c.collnamespace) WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; @@ -1986,18 +2051,18 @@ SELECT unnest(activate_node_snapshot()) order by 1; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('table', ARRAY['public', 'mx_ref']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('table', ARRAY['public', 'test_table']::text[], ARRAY[]::text[], -1, 0, false)) SELECT citus_internal_add_object_metadata(typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) FROM distributed_object_data; WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310000, 0, 1, 100000), (1310001, 0, 5, 100001), (1310002, 0, 1, 100002), (1310003, 0, 5, 100003), (1310004, 0, 1, 100004), (1310005, 0, 5, 100005), (1310006, 0, 1, 100006), (1310007, 0, 5, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; - WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310020, 0, 1, 100020), (1310021, 0, 5, 100021), (1310022, 0, 1, 100022), (1310023, 0, 5, 100023), (1310024, 0, 1, 100024)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; - WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310025, 0, 1, 100025), (1310026, 0, 5, 100026), (1310027, 0, 1, 100027), (1310028, 0, 5, 100028), (1310029, 0, 1, 100029)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; - WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310073, 0, 1, 100074), (1310073, 0, 5, 100075)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; - WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310074, 0, 1, 100076), (1310075, 0, 5, 100077), (1310076, 0, 1, 100078), (1310077, 0, 5, 100079)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; - WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310083, 0, 1, 100086), (1310084, 0, 5, 100087), (1310085, 0, 1, 100088), (1310086, 0, 5, 100089)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; - WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('mx_test_schema_1.mx_table_1'::regclass, 1310020, 't'::"char", '-2147483648', '-1288490190'), ('mx_test_schema_1.mx_table_1'::regclass, 1310021, 't'::"char", '-1288490189', '-429496731'), ('mx_test_schema_1.mx_table_1'::regclass, 1310022, 't'::"char", '-429496730', '429496728'), ('mx_test_schema_1.mx_table_1'::regclass, 1310023, 't'::"char", '429496729', '1288490187'), ('mx_test_schema_1.mx_table_1'::regclass, 1310024, 't'::"char", '1288490188', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; - WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('mx_test_schema_2.mx_table_2'::regclass, 1310025, 't'::"char", '-2147483648', '-1288490190'), ('mx_test_schema_2.mx_table_2'::regclass, 1310026, 't'::"char", '-1288490189', '-429496731'), ('mx_test_schema_2.mx_table_2'::regclass, 1310027, 't'::"char", '-429496730', '429496728'), ('mx_test_schema_2.mx_table_2'::regclass, 1310028, 't'::"char", '429496729', '1288490187'), ('mx_test_schema_2.mx_table_2'::regclass, 1310029, 't'::"char", '1288490188', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; + WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310022, 0, 1, 100022), (1310023, 0, 5, 100023), (1310024, 0, 1, 100024), (1310025, 0, 5, 100025), (1310026, 0, 1, 100026)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; + WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310027, 0, 1, 100027), (1310028, 0, 5, 100028), (1310029, 0, 1, 100029), (1310030, 0, 5, 100030), (1310031, 0, 1, 100031)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; + WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310075, 0, 0, 100077), (1310075, 0, 1, 100078), (1310075, 0, 5, 100079)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; + WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310076, 0, 1, 100080), (1310077, 0, 5, 100081), (1310078, 0, 1, 100082), (1310079, 0, 5, 100083)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; + WITH placement_data(shardid, shardlength, groupid, placementid) AS (VALUES (1310085, 0, 1, 100091), (1310086, 0, 5, 100092), (1310087, 0, 1, 100093), (1310088, 0, 5, 100094)) SELECT citus_internal_add_placement_metadata(shardid, shardlength, groupid, placementid) FROM placement_data; + WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('mx_test_schema_1.mx_table_1'::regclass, 1310022, 't'::"char", '-2147483648', '-1288490190'), ('mx_test_schema_1.mx_table_1'::regclass, 1310023, 't'::"char", '-1288490189', '-429496731'), ('mx_test_schema_1.mx_table_1'::regclass, 1310024, 't'::"char", '-429496730', '429496728'), ('mx_test_schema_1.mx_table_1'::regclass, 1310025, 't'::"char", '429496729', '1288490187'), ('mx_test_schema_1.mx_table_1'::regclass, 1310026, 't'::"char", '1288490188', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; + WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('mx_test_schema_2.mx_table_2'::regclass, 1310027, 't'::"char", '-2147483648', '-1288490190'), ('mx_test_schema_2.mx_table_2'::regclass, 1310028, 't'::"char", '-1288490189', '-429496731'), ('mx_test_schema_2.mx_table_2'::regclass, 1310029, 't'::"char", '-429496730', '429496728'), ('mx_test_schema_2.mx_table_2'::regclass, 1310030, 't'::"char", '429496729', '1288490187'), ('mx_test_schema_2.mx_table_2'::regclass, 1310031, 't'::"char", '1288490188', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('mx_testing_schema.mx_test_table'::regclass, 1310000, 't'::"char", '-2147483648', '-1610612737'), ('mx_testing_schema.mx_test_table'::regclass, 1310001, 't'::"char", '-1610612736', '-1073741825'), ('mx_testing_schema.mx_test_table'::regclass, 1310002, 't'::"char", '-1073741824', '-536870913'), ('mx_testing_schema.mx_test_table'::regclass, 1310003, 't'::"char", '-536870912', '-1'), ('mx_testing_schema.mx_test_table'::regclass, 1310004, 't'::"char", '0', '536870911'), ('mx_testing_schema.mx_test_table'::regclass, 1310005, 't'::"char", '536870912', '1073741823'), ('mx_testing_schema.mx_test_table'::regclass, 1310006, 't'::"char", '1073741824', '1610612735'), ('mx_testing_schema.mx_test_table'::regclass, 1310007, 't'::"char", '1610612736', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; - WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('public.dist_table_1'::regclass, 1310074, 't'::"char", '-2147483648', '-1073741825'), ('public.dist_table_1'::regclass, 1310075, 't'::"char", '-1073741824', '-1'), ('public.dist_table_1'::regclass, 1310076, 't'::"char", '0', '1073741823'), ('public.dist_table_1'::regclass, 1310077, 't'::"char", '1073741824', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; - WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('public.mx_ref'::regclass, 1310073, 't'::"char", NULL, NULL)) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; - WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('public.test_table'::regclass, 1310083, 't'::"char", '-2147483648', '-1073741825'), ('public.test_table'::regclass, 1310084, 't'::"char", '-1073741824', '-1'), ('public.test_table'::regclass, 1310085, 't'::"char", '0', '1073741823'), ('public.test_table'::regclass, 1310086, 't'::"char", '1073741824', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; -(111 rows) + WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('public.dist_table_1'::regclass, 1310076, 't'::"char", '-2147483648', '-1073741825'), ('public.dist_table_1'::regclass, 1310077, 't'::"char", '-1073741824', '-1'), ('public.dist_table_1'::regclass, 1310078, 't'::"char", '0', '1073741823'), ('public.dist_table_1'::regclass, 1310079, 't'::"char", '1073741824', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; + WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('public.mx_ref'::regclass, 1310075, 't'::"char", NULL, NULL)) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; + WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('public.test_table'::regclass, 1310085, 't'::"char", '-2147483648', '-1073741825'), ('public.test_table'::regclass, 1310086, 't'::"char", '-1073741824', '-1'), ('public.test_table'::regclass, 1310087, 't'::"char", '0', '1073741823'), ('public.test_table'::regclass, 1310088, 't'::"char", '1073741824', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; +(118 rows) -- shouldn't work since test_table is MX ALTER TABLE test_table ADD COLUMN id3 bigserial; diff --git a/src/test/regress/expected/multi_modifying_xacts.out b/src/test/regress/expected/multi_modifying_xacts.out index 99cdc9ce4..5eba6e21d 100644 --- a/src/test/regress/expected/multi_modifying_xacts.out +++ b/src/test/regress/expected/multi_modifying_xacts.out @@ -914,7 +914,7 @@ GROUP BY s.logicalrelid, sp.shardstate ORDER BY s.logicalrelid, sp.shardstate; logicalrelid | shardstate | count --------------------------------------------------------------------- - reference_modifying_xacts | 1 | 2 + reference_modifying_xacts | 1 | 3 (1 row) -- for the time-being drop the constraint @@ -1021,7 +1021,7 @@ GROUP BY s.logicalrelid, sp.shardstate ORDER BY s.logicalrelid, sp.shardstate; logicalrelid | shardstate | count --------------------------------------------------------------------- - reference_modifying_xacts | 1 | 2 + reference_modifying_xacts | 1 | 3 hash_modifying_xacts | 1 | 4 (2 rows) @@ -1070,7 +1070,7 @@ GROUP BY s.logicalrelid, sp.shardstate ORDER BY s.logicalrelid, sp.shardstate; logicalrelid | shardstate | count --------------------------------------------------------------------- - reference_modifying_xacts | 1 | 2 + reference_modifying_xacts | 1 | 3 hash_modifying_xacts | 1 | 4 (2 rows) @@ -1235,7 +1235,7 @@ GROUP BY s.logicalrelid, sp.shardstate ORDER BY s.logicalrelid, sp.shardstate; logicalrelid | shardstate | count --------------------------------------------------------------------- - reference_failure_test | 1 | 2 + reference_failure_test | 1 | 3 (1 row) -- any failure rollbacks the transaction diff --git a/src/test/regress/expected/multi_multiuser_auth.out b/src/test/regress/expected/multi_multiuser_auth.out index 4b7c6fcc7..8dd9b8ba7 100644 --- a/src/test/regress/expected/multi_multiuser_auth.out +++ b/src/test/regress/expected/multi_multiuser_auth.out @@ -15,7 +15,7 @@ SELECT nodeid AS worker_1_id FROM pg_dist_node WHERE nodename = 'localhost' AND nodeport = :worker_1_port; worker_1_id --------------------------------------------------------------------- - 16 + 17 (1 row) \gset diff --git a/src/test/regress/expected/multi_multiuser_master_protocol.out b/src/test/regress/expected/multi_multiuser_master_protocol.out index 0ea53b339..a6bddb7f2 100644 --- a/src/test/regress/expected/multi_multiuser_master_protocol.out +++ b/src/test/regress/expected/multi_multiuser_master_protocol.out @@ -355,9 +355,10 @@ SELECT * FROM run_command_on_placements('multiuser_schema.reference_table', $$ s ORDER BY nodename, nodeport, shardid; nodename | nodeport | shardid | success | result --------------------------------------------------------------------- + localhost | 57636 | 109094 | t | t localhost | 57637 | 109094 | t | t localhost | 57638 | 109094 | t | t -(2 rows) +(3 rows) -- create another table in the schema, verify select is not granted CREATE TABLE multiuser_schema.another_table(a int, b int); @@ -483,9 +484,10 @@ ORDER BY nodename, nodeport, shardid; (6 rows) DROP SCHEMA multiuser_schema CASCADE; -NOTICE: drop cascades to 3 other objects +NOTICE: drop cascades to 4 other objects DETAIL: drop cascades to table multiuser_schema.hash_table drop cascades to table multiuser_schema.reference_table +drop cascades to table multiuser_schema.reference_table_109094 drop cascades to table multiuser_schema.another_table DROP SCHEMA multiuser_second_schema CASCADE; NOTICE: drop cascades to table multiuser_second_schema.hash_table diff --git a/src/test/regress/expected/multi_mx_explain.out b/src/test/regress/expected/multi_mx_explain.out index 1c585a027..beb374d23 100644 --- a/src/test/regress/expected/multi_mx_explain.out +++ b/src/test/regress/expected/multi_mx_explain.out @@ -85,19 +85,24 @@ EXPLAIN (COSTS FALSE, FORMAT JSON) "Plan": { "Node Type": "Sort", "Parallel Aware": false, + "Async Capable": false, "Sort Key": ["(COALESCE((pg_catalog.sum(remote_scan.count_quantity))::bigint, '0'::bigint))", "remote_scan.l_quantity"], "Plans": [ { "Node Type": "Aggregate", "Strategy": "Hashed", "Partial Mode": "Simple", + "Parent Relationship": "Outer", "Parallel Aware": false, + "Async Capable": false, "Group Key": ["remote_scan.l_quantity"], "Plans": [ { "Node Type": "Custom Scan", + "Parent Relationship": "Outer", "Custom Plan Provider": "Citus Adaptive", "Parallel Aware": false, + "Async Capable": false, "Distributed Query": { "Job": { "Task Count": 16, @@ -113,11 +118,14 @@ EXPLAIN (COSTS FALSE, FORMAT JSON) "Strategy": "Hashed", "Partial Mode": "Simple", "Parallel Aware": false, + "Async Capable": false, "Group Key": ["l_quantity"], "Plans": [ { "Node Type": "Seq Scan", + "Parent Relationship": "Outer", "Parallel Aware": false, + "Async Capable": false, "Relation Name": "lineitem_mx_1220052", "Alias": "lineitem_mx" } @@ -153,6 +161,7 @@ EXPLAIN (COSTS FALSE, FORMAT XML) Sort false + false (COALESCE((pg_catalog.sum(remote_scan.count_quantity))::bigint, '0'::bigint)) remote_scan.l_quantity @@ -162,15 +171,19 @@ EXPLAIN (COSTS FALSE, FORMAT XML) Aggregate Hashed Simple + Outer false + false remote_scan.l_quantity Custom Scan + Outer Citus Adaptive false + false 16 @@ -186,13 +199,16 @@ EXPLAIN (COSTS FALSE, FORMAT XML) Hashed Simple false + false l_quantity Seq Scan + Outer false + false lineitem_mx_1220052 lineitem_mx @@ -224,6 +240,7 @@ EXPLAIN (COSTS FALSE, FORMAT YAML) - Plan: Node Type: "Sort" Parallel Aware: false + Async Capable: false Sort Key: - "(COALESCE((pg_catalog.sum(remote_scan.count_quantity))::bigint, '0'::bigint))" - "remote_scan.l_quantity" @@ -231,13 +248,17 @@ EXPLAIN (COSTS FALSE, FORMAT YAML) - Node Type: "Aggregate" Strategy: "Hashed" Partial Mode: "Simple" + Parent Relationship: "Outer" Parallel Aware: false + Async Capable: false Group Key: - "remote_scan.l_quantity" Plans: - Node Type: "Custom Scan" + Parent Relationship: "Outer" Custom Plan Provider: "Citus Adaptive" Parallel Aware: false + Async Capable: false Distributed Query: Job: Task Count: 16 @@ -250,11 +271,14 @@ EXPLAIN (COSTS FALSE, FORMAT YAML) Strategy: "Hashed" Partial Mode: "Simple" Parallel Aware: false + Async Capable: false Group Key: - "l_quantity" Plans: - Node Type: "Seq Scan" + Parent Relationship: "Outer" Parallel Aware: false + Async Capable: false Relation Name: "lineitem_mx_1220052" Alias: "lineitem_mx" @@ -528,11 +552,14 @@ EXPLAIN (COSTS FALSE, FORMAT JSON) "Strategy": "Plain", "Partial Mode": "Simple", "Parallel Aware": false, + "Async Capable": false, "Plans": [ { "Node Type": "Custom Scan", + "Parent Relationship": "Outer", "Custom Plan Provider": "Citus Adaptive", "Parallel Aware": false, + "Async Capable": false, "Distributed Query": { "Job": { "Task Count": 16, @@ -548,34 +575,45 @@ EXPLAIN (COSTS FALSE, FORMAT JSON) "Strategy": "Plain", "Partial Mode": "Simple", "Parallel Aware": false, + "Async Capable": false, "Plans": [ { "Node Type": "Hash Join", + "Parent Relationship": "Outer", "Parallel Aware": false, + "Async Capable": false, "Join Type": "Inner", "Inner Unique": false, "Hash Cond": "(lineitem_mx.l_orderkey = orders_mx.o_orderkey)", "Plans": [ { "Node Type": "Hash Join", + "Parent Relationship": "Outer", "Parallel Aware": false, + "Async Capable": false, "Join Type": "Inner", "Inner Unique": false, "Hash Cond": "(supplier_mx.s_suppkey = lineitem_mx.l_suppkey)", "Plans": [ { "Node Type": "Seq Scan", + "Parent Relationship": "Outer", "Parallel Aware": false, + "Async Capable": false, "Relation Name": "supplier_mx_1220087", "Alias": "supplier_mx" }, { "Node Type": "Hash", + "Parent Relationship": "Inner", "Parallel Aware": false, + "Async Capable": false, "Plans": [ { "Node Type": "Seq Scan", + "Parent Relationship": "Outer", "Parallel Aware": false, + "Async Capable": false, "Relation Name": "lineitem_mx_1220052", "Alias": "lineitem_mx" } @@ -585,28 +623,38 @@ EXPLAIN (COSTS FALSE, FORMAT JSON) }, { "Node Type": "Hash", + "Parent Relationship": "Inner", "Parallel Aware": false, + "Async Capable": false, "Plans": [ { "Node Type": "Hash Join", + "Parent Relationship": "Outer", "Parallel Aware": false, + "Async Capable": false, "Join Type": "Inner", "Inner Unique": false, "Hash Cond": "(customer_mx.c_custkey = orders_mx.o_custkey)", "Plans": [ { "Node Type": "Seq Scan", + "Parent Relationship": "Outer", "Parallel Aware": false, + "Async Capable": false, "Relation Name": "customer_mx_1220084", "Alias": "customer_mx" }, { "Node Type": "Hash", + "Parent Relationship": "Inner", "Parallel Aware": false, + "Async Capable": false, "Plans": [ { "Node Type": "Seq Scan", + "Parent Relationship": "Outer", "Parallel Aware": false, + "Async Capable": false, "Relation Name": "orders_mx_1220068", "Alias": "orders_mx" } @@ -653,11 +701,14 @@ EXPLAIN (COSTS FALSE, FORMAT XML) Plain Simple false + false Custom Scan + Outer Citus Adaptive false + false 16 @@ -673,34 +724,45 @@ EXPLAIN (COSTS FALSE, FORMAT XML) Plain Simple false + false Hash Join + Outer false + false Inner false (lineitem_mx.l_orderkey = orders_mx.o_orderkey) Hash Join + Outer false + false Inner false (supplier_mx.s_suppkey = lineitem_mx.l_suppkey) Seq Scan + Outer false + false supplier_mx_1220087 supplier_mx Hash + Inner false + false Seq Scan + Outer false + false lineitem_mx_1220052 lineitem_mx @@ -710,28 +772,38 @@ EXPLAIN (COSTS FALSE, FORMAT XML) Hash + Inner false + false Hash Join + Outer false + false Inner false (customer_mx.c_custkey = orders_mx.o_custkey) Seq Scan + Outer false + false customer_mx_1220084 customer_mx Hash + Inner false + false Seq Scan + Outer false + false orders_mx_1220068 orders_mx @@ -775,10 +847,13 @@ EXPLAIN (COSTS FALSE, FORMAT YAML) Strategy: "Plain" Partial Mode: "Simple" Parallel Aware: false + Async Capable: false Plans: - Node Type: "Custom Scan" + Parent Relationship: "Outer" Custom Plan Provider: "Citus Adaptive" Parallel Aware: false + Async Capable: false Distributed Query: Job: Task Count: 16 @@ -791,48 +866,69 @@ EXPLAIN (COSTS FALSE, FORMAT YAML) Strategy: "Plain" Partial Mode: "Simple" Parallel Aware: false + Async Capable: false Plans: - Node Type: "Hash Join" + Parent Relationship: "Outer" Parallel Aware: false + Async Capable: false Join Type: "Inner" Inner Unique: false Hash Cond: "(lineitem_mx.l_orderkey = orders_mx.o_orderkey)" Plans: - Node Type: "Hash Join" + Parent Relationship: "Outer" Parallel Aware: false + Async Capable: false Join Type: "Inner" Inner Unique: false Hash Cond: "(supplier_mx.s_suppkey = lineitem_mx.l_suppkey)" Plans: - Node Type: "Seq Scan" + Parent Relationship: "Outer" Parallel Aware: false + Async Capable: false Relation Name: "supplier_mx_1220087" Alias: "supplier_mx" - Node Type: "Hash" + Parent Relationship: "Inner" Parallel Aware: false + Async Capable: false Plans: - Node Type: "Seq Scan" + Parent Relationship: "Outer" Parallel Aware: false + Async Capable: false Relation Name: "lineitem_mx_1220052" Alias: "lineitem_mx" - Node Type: "Hash" + Parent Relationship: "Inner" Parallel Aware: false + Async Capable: false Plans: - Node Type: "Hash Join" + Parent Relationship: "Outer" Parallel Aware: false + Async Capable: false Join Type: "Inner" Inner Unique: false Hash Cond: "(customer_mx.c_custkey = orders_mx.o_custkey)" Plans: - Node Type: "Seq Scan" + Parent Relationship: "Outer" Parallel Aware: false + Async Capable: false Relation Name: "customer_mx_1220084" Alias: "customer_mx" - Node Type: "Hash" + Parent Relationship: "Inner" Parallel Aware: false + Async Capable: false Plans: - Node Type: "Seq Scan" + Parent Relationship: "Outer" Parallel Aware: false + Async Capable: false Relation Name: "orders_mx_1220068" Alias: "orders_mx" diff --git a/src/test/regress/expected/multi_mx_function_call_delegation.out b/src/test/regress/expected/multi_mx_function_call_delegation.out index ffb35b08e..9f6aecfa2 100644 --- a/src/test/regress/expected/multi_mx_function_call_delegation.out +++ b/src/test/regress/expected/multi_mx_function_call_delegation.out @@ -40,6 +40,14 @@ select create_distributed_table('mx_call_dist_table_bigint', 'id'); (1 row) insert into mx_call_dist_table_bigint values (1,1),(1,2),(2,2),(3,3),(3,4); +create table mx_call_dist_table_single_shard(id int, val int); +select create_distributed_table('mx_call_dist_table_single_shard', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +insert into mx_call_dist_table_single_shard values (2,7),(1,8),(2,8),(1,8),(2,8); create table mx_call_dist_table_ref(id int, val int); select create_reference_table('mx_call_dist_table_ref'); create_reference_table @@ -170,7 +178,6 @@ NOTICE: procedure multi_mx_function_call_delegation.squares is already distribu -- colocated with any distributed tables. SET client_min_messages TO DEBUG1; select mx_call_func(2, 0); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT sum((t1.val OPERATOR(pg_catalog.+) t2.val)) AS sum FROM (multi_mx_function_call_delegation.mx_call_dist_table_1 t1 JOIN multi_mx_function_call_delegation.mx_call_dist_table_2 t2 ON ((t1.id OPERATOR(pg_catalog.=) t2.id))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT ((3 OPERATOR(pg_catalog.+) (SELECT intermediate_result.sum FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(sum bigint))))::integer mx_call_func @@ -179,14 +186,12 @@ DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT ((3 OPERATOR( (1 row) select multi_mx_function_call_delegation.mx_call_func_bigint(4, 2); -DEBUG: function does not have co-located tables mx_call_func_bigint --------------------------------------------------------------------- 8 (1 row) select mx_call_func_custom_types('S', 'A'); -DEBUG: function does not have co-located tables mx_call_func_custom_types --------------------------------------------------------------------- (F,S) @@ -286,7 +291,6 @@ RESET citus.enable_binary_protocol; -- We don't allow distributing calls inside transactions begin; select mx_call_func(2, 0); -DEBUG: not pushing down function calls in a multi-statement transaction DEBUG: generating subplan XXX_1 for subquery SELECT sum((t1.val OPERATOR(pg_catalog.+) t2.val)) AS sum FROM (multi_mx_function_call_delegation.mx_call_dist_table_1 t1 JOIN multi_mx_function_call_delegation.mx_call_dist_table_2 t2 ON ((t1.id OPERATOR(pg_catalog.=) t2.id))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT ((3 OPERATOR(pg_catalog.+) (SELECT intermediate_result.sum FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(sum bigint))))::integer mx_call_func @@ -301,7 +305,6 @@ SET client_min_messages TO NOTICE; drop table mx_call_dist_table_enum; SET client_min_messages TO DEBUG1; select mx_call_func_custom_types('S', 'A'); -DEBUG: function does not have co-located tables mx_call_func_custom_types --------------------------------------------------------------------- (F,S) @@ -353,6 +356,20 @@ DEBUG: pushing down the function call 28 (1 row) +-- We support colocating with single shard tables +select colocate_proc_with_table('mx_call_func', 'mx_call_dist_table_single_shard'::regclass, 1); + colocate_proc_with_table +--------------------------------------------------------------------- + +(1 row) + +select mx_call_func(2, 0); +DEBUG: pushing down the function call + mx_call_func +--------------------------------------------------------------------- + 28 +(1 row) + -- We don't currently support colocating with replicated tables select colocate_proc_with_table('mx_call_func', 'mx_call_dist_table_replica'::regclass, 1); colocate_proc_with_table @@ -395,7 +412,6 @@ END;$$; DEBUG: switching to sequential query execution mode -- before distribution ... select mx_call_func_tbl(10); -DEBUG: function does not have co-located tables mx_call_func_tbl --------------------------------------------------------------------- (10,-1) @@ -685,7 +701,6 @@ ERROR: cannot execute a distributed query from a query on a shard select mx_call_func_copy(2) from mx_call_dist_table_1 where id = 3; ERROR: cannot execute a distributed query from a query on a shard DO $$ BEGIN perform mx_call_func_tbl(40); END; $$; -DEBUG: not pushing down function calls in a multi-statement transaction SELECT * FROM mx_call_dist_table_1 WHERE id >= 40 ORDER BY id, val; id | val --------------------------------------------------------------------- @@ -769,7 +784,6 @@ DEBUG: pushing down the function call -- not delegated in a transaction block BEGIN; SELECT mx_call_func(2, 0); -DEBUG: not pushing down function calls in a multi-statement transaction DEBUG: generating subplan XXX_1 for subquery SELECT sum((t1.val OPERATOR(pg_catalog.+) t2.val)) AS sum FROM (multi_mx_function_call_delegation.mx_call_dist_table_1 t1 JOIN multi_mx_function_call_delegation.mx_call_dist_table_2 t2 ON ((t1.id OPERATOR(pg_catalog.=) t2.id))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT ((2 OPERATOR(pg_catalog.+) (SELECT intermediate_result.sum FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(sum bigint))))::integer mx_call_func @@ -784,7 +798,6 @@ BEGIN PERFORM mx_call_func(2, 0); END; $$ LANGUAGE plpgsql; -DEBUG: not pushing down function calls in a multi-statement transaction DEBUG: generating subplan XXX_1 for subquery SELECT sum((t1.val OPERATOR(pg_catalog.+) t2.val)) AS sum FROM (multi_mx_function_call_delegation.mx_call_dist_table_1 t1 JOIN multi_mx_function_call_delegation.mx_call_dist_table_2 t2 ON ((t1.id OPERATOR(pg_catalog.=) t2.id))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT ((2 OPERATOR(pg_catalog.+) (SELECT intermediate_result.sum FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(sum bigint))))::integer -- forced calls are delegated in a transaction block @@ -812,4 +825,4 @@ SET search_path TO multi_mx_function_call_delegation, public; RESET client_min_messages; \set VERBOSITY terse DROP SCHEMA multi_mx_function_call_delegation CASCADE; -NOTICE: drop cascades to 16 other objects +NOTICE: drop cascades to 17 other objects diff --git a/src/test/regress/expected/multi_mx_function_call_delegation_0.out b/src/test/regress/expected/multi_mx_function_call_delegation_0.out index 6706ec6f8..b4c57920d 100644 --- a/src/test/regress/expected/multi_mx_function_call_delegation_0.out +++ b/src/test/regress/expected/multi_mx_function_call_delegation_0.out @@ -40,6 +40,14 @@ select create_distributed_table('mx_call_dist_table_bigint', 'id'); (1 row) insert into mx_call_dist_table_bigint values (1,1),(1,2),(2,2),(3,3),(3,4); +create table mx_call_dist_table_single_shard(id int, val int); +select create_distributed_table('mx_call_dist_table_single_shard', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +insert into mx_call_dist_table_single_shard values (2,7),(1,8),(2,8),(1,8),(2,8); create table mx_call_dist_table_ref(id int, val int); select create_reference_table('mx_call_dist_table_ref'); create_reference_table @@ -170,7 +178,6 @@ NOTICE: procedure multi_mx_function_call_delegation.squares is already distribu -- colocated with any distributed tables. SET client_min_messages TO DEBUG1; select mx_call_func(2, 0); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT sum((t1.val OPERATOR(pg_catalog.+) t2.val)) AS sum FROM (multi_mx_function_call_delegation.mx_call_dist_table_1 t1 JOIN multi_mx_function_call_delegation.mx_call_dist_table_2 t2 ON ((t1.id OPERATOR(pg_catalog.=) t2.id))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT (3 OPERATOR(pg_catalog.+) (SELECT intermediate_result.sum FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(sum bigint))) mx_call_func @@ -179,14 +186,12 @@ DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT (3 OPERATOR(p (1 row) select multi_mx_function_call_delegation.mx_call_func_bigint(4, 2); -DEBUG: function does not have co-located tables mx_call_func_bigint --------------------------------------------------------------------- 8 (1 row) select mx_call_func_custom_types('S', 'A'); -DEBUG: function does not have co-located tables mx_call_func_custom_types --------------------------------------------------------------------- (F,S) @@ -286,7 +291,6 @@ RESET citus.enable_binary_protocol; -- We don't allow distributing calls inside transactions begin; select mx_call_func(2, 0); -DEBUG: not pushing down function calls in a multi-statement transaction DEBUG: generating subplan XXX_1 for subquery SELECT sum((t1.val OPERATOR(pg_catalog.+) t2.val)) AS sum FROM (multi_mx_function_call_delegation.mx_call_dist_table_1 t1 JOIN multi_mx_function_call_delegation.mx_call_dist_table_2 t2 ON ((t1.id OPERATOR(pg_catalog.=) t2.id))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT (3 OPERATOR(pg_catalog.+) (SELECT intermediate_result.sum FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(sum bigint))) mx_call_func @@ -301,7 +305,6 @@ SET client_min_messages TO NOTICE; drop table mx_call_dist_table_enum; SET client_min_messages TO DEBUG1; select mx_call_func_custom_types('S', 'A'); -DEBUG: function does not have co-located tables mx_call_func_custom_types --------------------------------------------------------------------- (F,S) @@ -353,6 +356,20 @@ DEBUG: pushing down the function call 28 (1 row) +-- We support colocating with single shard tables +select colocate_proc_with_table('mx_call_func', 'mx_call_dist_table_single_shard'::regclass, 1); + colocate_proc_with_table +--------------------------------------------------------------------- + +(1 row) + +select mx_call_func(2, 0); +DEBUG: pushing down the function call + mx_call_func +--------------------------------------------------------------------- + 28 +(1 row) + -- We don't currently support colocating with replicated tables select colocate_proc_with_table('mx_call_func', 'mx_call_dist_table_replica'::regclass, 1); colocate_proc_with_table @@ -395,7 +412,6 @@ END;$$; DEBUG: switching to sequential query execution mode -- before distribution ... select mx_call_func_tbl(10); -DEBUG: function does not have co-located tables mx_call_func_tbl --------------------------------------------------------------------- (10,-1) @@ -685,7 +701,6 @@ ERROR: cannot execute a distributed query from a query on a shard select mx_call_func_copy(2) from mx_call_dist_table_1 where id = 3; ERROR: cannot execute a distributed query from a query on a shard DO $$ BEGIN perform mx_call_func_tbl(40); END; $$; -DEBUG: not pushing down function calls in a multi-statement transaction SELECT * FROM mx_call_dist_table_1 WHERE id >= 40 ORDER BY id, val; id | val --------------------------------------------------------------------- @@ -769,7 +784,6 @@ DEBUG: pushing down the function call -- not delegated in a transaction block BEGIN; SELECT mx_call_func(2, 0); -DEBUG: not pushing down function calls in a multi-statement transaction DEBUG: generating subplan XXX_1 for subquery SELECT sum((t1.val OPERATOR(pg_catalog.+) t2.val)) AS sum FROM (multi_mx_function_call_delegation.mx_call_dist_table_1 t1 JOIN multi_mx_function_call_delegation.mx_call_dist_table_2 t2 ON ((t1.id OPERATOR(pg_catalog.=) t2.id))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT (2 OPERATOR(pg_catalog.+) (SELECT intermediate_result.sum FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(sum bigint))) mx_call_func @@ -784,7 +798,6 @@ BEGIN PERFORM mx_call_func(2, 0); END; $$ LANGUAGE plpgsql; -DEBUG: not pushing down function calls in a multi-statement transaction DEBUG: generating subplan XXX_1 for subquery SELECT sum((t1.val OPERATOR(pg_catalog.+) t2.val)) AS sum FROM (multi_mx_function_call_delegation.mx_call_dist_table_1 t1 JOIN multi_mx_function_call_delegation.mx_call_dist_table_2 t2 ON ((t1.id OPERATOR(pg_catalog.=) t2.id))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT (2 OPERATOR(pg_catalog.+) (SELECT intermediate_result.sum FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(sum bigint))) -- forced calls are delegated in a transaction block @@ -812,4 +825,4 @@ SET search_path TO multi_mx_function_call_delegation, public; RESET client_min_messages; \set VERBOSITY terse DROP SCHEMA multi_mx_function_call_delegation CASCADE; -NOTICE: drop cascades to 16 other objects +NOTICE: drop cascades to 17 other objects diff --git a/src/test/regress/expected/multi_name_resolution.out b/src/test/regress/expected/multi_name_resolution.out index 890c336bf..5c59a10e6 100644 --- a/src/test/regress/expected/multi_name_resolution.out +++ b/src/test/regress/expected/multi_name_resolution.out @@ -36,6 +36,7 @@ WHERE bar.id_deep = join_alias.id_deep; (0 rows) DROP SCHEMA multi_name_resolution CASCADE; -NOTICE: drop cascades to 2 other objects +NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to table namenest1 drop cascades to table namenest2 +drop cascades to table namenest2_2250000000010 diff --git a/src/test/regress/expected/multi_null_minmax_value_pruning.out b/src/test/regress/expected/multi_null_minmax_value_pruning.out index a531b065f..5b0ad79f4 100644 --- a/src/test/regress/expected/multi_null_minmax_value_pruning.out +++ b/src/test/regress/expected/multi_null_minmax_value_pruning.out @@ -104,15 +104,33 @@ LOG: join order: [ "lineitem" ][ dual partition join "orders" ] DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 3 DEBUG: pruning merge fetch taskId 2 @@ -129,18 +147,26 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 12 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 18 QUERY PLAN --------------------------------------------------------------------- Aggregate -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 Tasks Shown: None, not supported for re-partition queries -> MapMergeJob Map Task Count: 2 - Merge Task Count: 4 + Merge Task Count: 6 -> MapMergeJob Map Task Count: 2 - Merge Task Count: 4 + Merge Task Count: 6 (10 rows) -- Next, set the maximum value for another shard to null. Then check that we @@ -169,15 +195,33 @@ LOG: join order: [ "lineitem" ][ dual partition join "orders" ] DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 3 DEBUG: pruning merge fetch taskId 2 @@ -194,18 +238,26 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 12 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 18 QUERY PLAN --------------------------------------------------------------------- Aggregate -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 Tasks Shown: None, not supported for re-partition queries -> MapMergeJob Map Task Count: 2 - Merge Task Count: 4 + Merge Task Count: 6 -> MapMergeJob Map Task Count: 2 - Merge Task Count: 4 + Merge Task Count: 6 (10 rows) -- Last, set the minimum value to 0 and check that we don't treat it as null. We @@ -232,15 +284,33 @@ LOG: join order: [ "lineitem" ][ dual partition join "orders" ] DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 3 DEBUG: pruning merge fetch taskId 2 @@ -257,18 +327,26 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 12 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 18 QUERY PLAN --------------------------------------------------------------------- Aggregate -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 Tasks Shown: None, not supported for re-partition queries -> MapMergeJob Map Task Count: 2 - Merge Task Count: 4 + Merge Task Count: 6 -> MapMergeJob Map Task Count: 2 - Merge Task Count: 4 + Merge Task Count: 6 (10 rows) RESET client_min_messages; diff --git a/src/test/regress/expected/multi_partitioning.out b/src/test/regress/expected/multi_partitioning.out index db79e075a..64a4fd4dc 100644 --- a/src/test/regress/expected/multi_partitioning.out +++ b/src/test/regress/expected/multi_partitioning.out @@ -1952,6 +1952,8 @@ DEBUG: switching to sequential query execution mode DETAIL: Table "" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed tables due to foreign keys. Any parallel modification to those hash distributed tables in the same transaction can only be executed in sequential query execution mode CONTEXT: SQL statement "SELECT citus_drop_all_shards(v_obj.objid, v_obj.schema_name, v_obj.object_name, drop_shards_metadata_only := false)" PL/pgSQL function citus_drop_trigger() line XX at PERFORM +CONTEXT: SQL statement "SELECT citus_drop_all_shards(v_obj.objid, v_obj.schema_name, v_obj.object_name, drop_shards_metadata_only := false)" +PL/pgSQL function citus_drop_trigger() line XX at PERFORM DEBUG: drop cascades to 2 other objects DETAIL: drop cascades to constraint partitioning_reference_fkey_1660302 on table partitioning_schema.partitioning_test_1660302 drop cascades to constraint partitioning_reference_fkey_1660304 on table partitioning_schema.partitioning_test_1660304 @@ -2040,7 +2042,7 @@ SELECT citus_shard_cost_by_disk_size(shardid) FROM pg_dist_shard WHERE logicalre DEBUG: skipping child tables for relation named: events.Energy Added citus_shard_cost_by_disk_size --------------------------------------------------------------------- - 16384 + 1.04874e+08 (1 row) RESET client_min_messages; @@ -3772,13 +3774,6 @@ BEGIN; ROLLBACK; DROP TABLE pi_table; -- 6) test with citus local table -select 1 from citus_add_node('localhost', :master_port, groupid=>0); -NOTICE: localhost:xxxxx is the coordinator and already contains metadata, skipping syncing the metadata - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - CREATE TABLE date_partitioned_citus_local_table( measureid integer, eventdate date, @@ -4064,6 +4059,47 @@ BEGIN; ROLLBACK; set client_min_messages to notice; +-- 7) test with bigint partition column +CREATE FUNCTION nanos_to_timestamptz(nanos bigint) RETURNS timestamptz LANGUAGE plpgsql AS +$$ +DECLARE + value timestamptz; +BEGIN + select to_timestamp(nanos * 1.0 / 1000000000) into value; + return value; +END; +$$; +CREATE CAST (bigint AS timestamptz) WITH FUNCTION nanos_to_timestamptz(bigint); +CREATE FUNCTION timestamptz_to_nanos(ts timestamptz) RETURNS bigint LANGUAGE plpgsql AS +$$ +DECLARE + value bigint; +BEGIN + select extract(epoch from ts) * 1000000000 into value; + return value; +END; +$$; +CREATE CAST (timestamptz AS bigint) WITH FUNCTION timestamptz_to_nanos(timestamptz); +CREATE TABLE bigint_partitioned_table (timestamp bigint, description text) partition by range (timestamp); +BEGIN; + SELECT create_time_partitions('bigint_partitioned_table', INTERVAL '1 month', '2023-05-01', '2023-01-1'); + create_time_partitions +--------------------------------------------------------------------- + t +(1 row) + + SELECT * FROM time_partitions WHERE parent_table = 'bigint_partitioned_table'::regclass ORDER BY 3; + parent_table | partition_column | partition | from_value | to_value | access_method +--------------------------------------------------------------------- + bigint_partitioned_table | timestamp | bigint_partitioned_table_p2023_01 | 1672560000000000000 | 1675238400000000000 | heap + bigint_partitioned_table | timestamp | bigint_partitioned_table_p2023_02 | 1675238400000000000 | 1677657600000000000 | heap + bigint_partitioned_table | timestamp | bigint_partitioned_table_p2023_03 | 1677657600000000000 | 1680332400000000000 | heap + bigint_partitioned_table | timestamp | bigint_partitioned_table_p2023_04 | 1680332400000000000 | 1682924400000000000 | heap +(4 rows) + +ROLLBACK; +DROP CAST (bigint AS timestamptz); +DROP CAST (timestamptz AS bigint); -- c) test drop_old_time_partitions -- 1) test with date partitioned table CREATE TABLE date_partitioned_table_to_exp (event_date date, event int) partition by range (event_date); @@ -4214,12 +4250,6 @@ DROP TABLE date_partitioned_table_to_exp; DROP TABLE date_partitioned_citus_local_table CASCADE; DROP TABLE date_partitioned_citus_local_table_2; set client_min_messages to notice; -SELECT citus_remove_node('localhost', :master_port); - citus_remove_node ---------------------------------------------------------------------- - -(1 row) - -- d) invalid tables for helper UDFs CREATE TABLE multiple_partition_column_table( event_id bigserial, @@ -4370,10 +4400,13 @@ SELECT a, b FROM stxdinp GROUP BY 1, 2; (10 rows) DROP SCHEMA partitioning_schema CASCADE; -NOTICE: drop cascades to 5 other objects +NOTICE: drop cascades to 8 other objects DETAIL: drop cascades to table "schema-test" drop cascades to table another_distributed_table drop cascades to table distributed_parent_table +drop cascades to function nanos_to_timestamptz(bigint) +drop cascades to function timestamptz_to_nanos(timestamp with time zone) +drop cascades to table bigint_partitioned_table drop cascades to table part_table_with_very_long_name drop cascades to table stxdinp RESET search_path; diff --git a/src/test/regress/expected/multi_poolinfo_usage.out b/src/test/regress/expected/multi_poolinfo_usage.out index b428409ff..ee98f0df7 100644 --- a/src/test/regress/expected/multi_poolinfo_usage.out +++ b/src/test/regress/expected/multi_poolinfo_usage.out @@ -9,7 +9,7 @@ SET citus.next_shard_id TO 20000000; SELECT nodeid AS worker_1_id FROM pg_dist_node WHERE nodename = 'localhost' AND nodeport = :worker_1_port; worker_1_id --------------------------------------------------------------------- - 16 + 17 (1 row) \gset diff --git a/src/test/regress/expected/multi_read_from_secondaries.out b/src/test/regress/expected/multi_read_from_secondaries.out index 5c69458e4..9652b6520 100644 --- a/src/test/regress/expected/multi_read_from_secondaries.out +++ b/src/test/regress/expected/multi_read_from_secondaries.out @@ -27,9 +27,10 @@ INSERT INTO source_table (a, b) VALUES (10, 10); SELECT nodeid, groupid, nodename, nodeport, noderack, isactive, noderole, nodecluster FROM pg_dist_node ORDER BY 1, 2; nodeid | groupid | nodename | nodeport | noderack | isactive | noderole | nodecluster --------------------------------------------------------------------- - 1 | 1 | localhost | 57637 | default | t | primary | default - 2 | 2 | localhost | 57638 | default | t | primary | default -(2 rows) + 1 | 0 | localhost | 57636 | default | t | primary | default + 2 | 1 | localhost | 57637 | default | t | primary | default + 3 | 2 | localhost | 57638 | default | t | primary | default +(3 rows) UPDATE pg_dist_node SET noderole = 'secondary'; \c "dbname=regression options='-c\ citus.use_secondary_nodes=always'" diff --git a/src/test/regress/expected/multi_real_time_transaction.out b/src/test/regress/expected/multi_real_time_transaction.out index 633d00dab..f348430e1 100644 --- a/src/test/regress/expected/multi_real_time_transaction.out +++ b/src/test/regress/expected/multi_real_time_transaction.out @@ -668,8 +668,9 @@ SELECT id, pg_advisory_xact_lock(16) FROM test_table ORDER BY id; END; DROP SCHEMA multi_real_time_transaction CASCADE; -NOTICE: drop cascades to 4 other objects +NOTICE: drop cascades to 5 other objects DETAIL: drop cascades to table test_table drop cascades to table co_test_table drop cascades to table ref_test_table +drop cascades to table ref_test_table_1610008 drop cascades to function insert_row_test(name) diff --git a/src/test/regress/expected/multi_reference_table.out b/src/test/regress/expected/multi_reference_table.out index fd7c9bb50..75a9c3b64 100644 --- a/src/test/regress/expected/multi_reference_table.out +++ b/src/test/regress/expected/multi_reference_table.out @@ -1203,9 +1203,9 @@ RESET client_min_messages; -- some tests for mark_tables_colocated -- should error out SELECT update_distributed_table_colocation('colocated_table_test_2', colocate_with => 'reference_table_test'); -ERROR: relation reference_table_test should be a hash distributed table +ERROR: relation reference_table_test should be a hash or single shard distributed table SELECT update_distributed_table_colocation('reference_table_test', colocate_with => 'reference_table_test_fifth'); -ERROR: relation reference_table_test_fifth should be a hash distributed table +ERROR: relation reference_table_test_fifth should be a hash or single shard distributed table -- ensure that reference tables on -- different queries works as expected CREATE SCHEMA reference_schema; diff --git a/src/test/regress/expected/multi_remove_node_reference_table.out b/src/test/regress/expected/multi_remove_node_reference_table.out index 98fd168b5..44233266a 100644 --- a/src/test/regress/expected/multi_remove_node_reference_table.out +++ b/src/test/regress/expected/multi_remove_node_reference_table.out @@ -218,10 +218,24 @@ WHERE colocationid IN 1 | -1 | 0 (1 row) +-- test that we cannot remove a node if it has the only placement for a shard +SELECT master_remove_node('localhost', :master_port); + master_remove_node +--------------------------------------------------------------------- + +(1 row) + SELECT master_remove_node('localhost', :worker_1_port); ERROR: cannot remove or disable the node localhost:xxxxx because because it contains the only shard placement for shard xxxxx DETAIL: One of the table(s) that prevents the operation complete successfully is public.remove_node_reference_table HINT: To proceed, either drop the tables or use undistribute_table() function to convert them to local tables +-- restore the coordinator +SELECT citus_set_coordinator_host('localhost'); + citus_set_coordinator_host +--------------------------------------------------------------------- + +(1 row) + \c - - - :worker_1_port SELECT COUNT(*) FROM pg_dist_node WHERE nodeport = :worker_2_port; count @@ -972,12 +986,6 @@ ORDER BY shardid ASC; (0 rows) \c - - - :master_port -SELECT 1 FROM citus_set_coordinator_host('localhost', :master_port); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - SELECT citus_disable_node('localhost', :worker_2_port); citus_disable_node --------------------------------------------------------------------- @@ -1004,12 +1012,6 @@ SELECT hasmetadata, metadatasynced FROM pg_dist_node WHERE nodeport = :master_po t | t (1 row) -SELECT 1 FROM citus_remove_node('localhost', :master_port); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - SELECT shardid, shardstate, shardlength, nodename, nodeport FROM diff --git a/src/test/regress/expected/multi_repartition_join_planning.out b/src/test/regress/expected/multi_repartition_join_planning.out index 13f569a4e..237fe906b 100644 --- a/src/test/regress/expected/multi_repartition_join_planning.out +++ b/src/test/regress/expected/multi_repartition_join_planning.out @@ -7,6 +7,7 @@ SET citus.next_shard_id TO 690000; SET citus.enable_unique_job_ids TO off; SET citus.enable_repartition_joins to ON; +SET citus.shard_replication_factor to 1; create schema repartition_join; DROP TABLE IF EXISTS repartition_join.order_line; NOTICE: table "order_line" does not exist, skipping @@ -69,15 +70,33 @@ DEBUG: join prunable for intervals [0,2147483647] and [-2147483648,-1] DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 3 DEBUG: pruning merge fetch taskId 2 @@ -94,34 +113,68 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 12 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 18 DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 -DETAIL: Creating dependency on merge taskId 13 +DETAIL: Creating dependency on merge taskId 19 DEBUG: pruning merge fetch taskId 2 DETAIL: Creating dependency on merge taskId 4 DEBUG: pruning merge fetch taskId 4 -DETAIL: Creating dependency on merge taskId 18 +DETAIL: Creating dependency on merge taskId 26 DEBUG: pruning merge fetch taskId 5 DETAIL: Creating dependency on merge taskId 8 DEBUG: pruning merge fetch taskId 7 -DETAIL: Creating dependency on merge taskId 23 +DETAIL: Creating dependency on merge taskId 33 DEBUG: pruning merge fetch taskId 8 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 10 -DETAIL: Creating dependency on merge taskId 28 +DETAIL: Creating dependency on merge taskId 40 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 16 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 47 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 54 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 24 l_partkey | o_orderkey | count --------------------------------------------------------------------- 18 | 12005 | 1 @@ -170,15 +223,33 @@ DEBUG: Router planner cannot handle multi-shard select queries DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 3 DEBUG: pruning merge fetch taskId 2 @@ -195,6 +266,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 12 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 18 l_partkey | o_orderkey | count --------------------------------------------------------------------- (0 rows) @@ -214,15 +293,33 @@ DEBUG: Router planner cannot handle multi-shard select queries DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 3 DEBUG: pruning merge fetch taskId 2 @@ -239,6 +336,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 12 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 18 o_orderkey | o_shippriority | count --------------------------------------------------------------------- (0 rows) @@ -260,15 +365,33 @@ DEBUG: Router planner cannot handle multi-shard select queries DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 3 DEBUG: pruning merge fetch taskId 2 @@ -285,6 +408,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 12 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 18 o_orderkey | o_shippriority | count --------------------------------------------------------------------- (0 rows) @@ -304,15 +435,33 @@ DEBUG: Router planner cannot handle multi-shard select queries DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 3 DEBUG: pruning merge fetch taskId 2 @@ -329,6 +478,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 12 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 18 o_orderkey | any_value --------------------------------------------------------------------- (0 rows) @@ -346,15 +503,33 @@ DEBUG: Router planner cannot handle multi-shard select queries DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 5 DEBUG: pruning merge fetch taskId 2 @@ -371,6 +546,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 20 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 25 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 25 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 30 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 30 s_i_id --------------------------------------------------------------------- (0 rows) diff --git a/src/test/regress/expected/multi_repartition_join_pruning.out b/src/test/regress/expected/multi_repartition_join_pruning.out index 8c0a26800..b5c571f2a 100644 --- a/src/test/regress/expected/multi_repartition_join_pruning.out +++ b/src/test/regress/expected/multi_repartition_join_pruning.out @@ -17,15 +17,33 @@ DEBUG: Router planner does not support append-partitioned tables. DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 3 DEBUG: pruning merge fetch taskId 2 @@ -42,18 +60,26 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 16 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 24 QUERY PLAN --------------------------------------------------------------------- Aggregate -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 Tasks Shown: None, not supported for re-partition queries -> MapMergeJob Map Task Count: 2 - Merge Task Count: 4 + Merge Task Count: 6 -> MapMergeJob Map Task Count: 3 - Merge Task Count: 4 + Merge Task Count: 6 (10 rows) SELECT @@ -66,15 +92,33 @@ DEBUG: Router planner does not support append-partitioned tables. DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 3 DEBUG: pruning merge fetch taskId 2 @@ -91,6 +135,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 16 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 24 count --------------------------------------------------------------------- 2985 @@ -110,15 +162,33 @@ DEBUG: Router planner does not support append-partitioned tables. DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 3 DEBUG: pruning merge fetch taskId 2 @@ -135,18 +205,26 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 16 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 24 QUERY PLAN --------------------------------------------------------------------- Aggregate -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 Tasks Shown: None, not supported for re-partition queries -> MapMergeJob Map Task Count: 2 - Merge Task Count: 4 + Merge Task Count: 6 -> MapMergeJob Map Task Count: 3 - Merge Task Count: 4 + Merge Task Count: 6 (10 rows) SELECT @@ -160,15 +238,33 @@ DEBUG: Router planner does not support append-partitioned tables. DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 3 DEBUG: pruning merge fetch taskId 2 @@ -185,6 +281,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 16 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 24 count --------------------------------------------------------------------- 0 @@ -204,15 +308,33 @@ DEBUG: Router planner does not support append-partitioned tables. DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 3 DEBUG: pruning merge fetch taskId 2 @@ -229,18 +351,26 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 16 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 24 QUERY PLAN --------------------------------------------------------------------- Aggregate -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 Tasks Shown: None, not supported for re-partition queries -> MapMergeJob Map Task Count: 2 - Merge Task Count: 4 + Merge Task Count: 6 -> MapMergeJob Map Task Count: 3 - Merge Task Count: 4 + Merge Task Count: 6 (10 rows) SELECT @@ -254,15 +384,33 @@ DEBUG: Router planner does not support append-partitioned tables. DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 3 DEBUG: pruning merge fetch taskId 2 @@ -279,6 +427,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 16 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 24 count --------------------------------------------------------------------- 0 @@ -298,15 +454,33 @@ DEBUG: Router planner does not support append-partitioned tables. DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 3 DEBUG: pruning merge fetch taskId 2 @@ -323,18 +497,26 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 16 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 24 QUERY PLAN --------------------------------------------------------------------- Aggregate -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 Tasks Shown: None, not supported for re-partition queries -> MapMergeJob Map Task Count: 2 - Merge Task Count: 4 + Merge Task Count: 6 -> MapMergeJob Map Task Count: 3 - Merge Task Count: 4 + Merge Task Count: 6 (10 rows) SELECT @@ -347,15 +529,33 @@ DEBUG: Router planner does not support append-partitioned tables. DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 3 DEBUG: pruning merge fetch taskId 2 @@ -372,6 +572,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 16 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 24 count --------------------------------------------------------------------- 125 @@ -391,15 +599,33 @@ DEBUG: Router planner does not support append-partitioned tables. DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 3 DEBUG: pruning merge fetch taskId 2 @@ -416,18 +642,26 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 16 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 24 QUERY PLAN --------------------------------------------------------------------- Aggregate -> Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 Tasks Shown: None, not supported for re-partition queries -> MapMergeJob Map Task Count: 2 - Merge Task Count: 4 + Merge Task Count: 6 -> MapMergeJob Map Task Count: 3 - Merge Task Count: 4 + Merge Task Count: 6 (10 rows) SELECT @@ -441,15 +675,33 @@ DEBUG: Router planner does not support append-partitioned tables. DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 3 DEBUG: pruning merge fetch taskId 2 @@ -466,6 +718,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 16 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 24 count --------------------------------------------------------------------- 0 diff --git a/src/test/regress/expected/multi_repartition_join_task_assignment.out b/src/test/regress/expected/multi_repartition_join_task_assignment.out index 3fbe9121b..713a68e6b 100644 --- a/src/test/regress/expected/multi_repartition_join_task_assignment.out +++ b/src/test/regress/expected/multi_repartition_join_task_assignment.out @@ -30,15 +30,33 @@ DEBUG: assigned task to node localhost:xxxxx DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 3 DEBUG: pruning merge fetch taskId 2 @@ -55,6 +73,16 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 16 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 24 +DEBUG: assigned task to node localhost:xxxxx +DEBUG: assigned task to node localhost:xxxxx DEBUG: assigned task to node localhost:xxxxx DEBUG: assigned task to node localhost:xxxxx DEBUG: assigned task to node localhost:xxxxx @@ -88,15 +116,33 @@ DEBUG: assigned task to node localhost:xxxxx DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 4 DEBUG: pruning merge fetch taskId 2 @@ -113,6 +159,16 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 16 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 12 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 24 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: assigned task to node localhost:xxxxx +DEBUG: assigned task to node localhost:xxxxx DEBUG: assigned task to node localhost:xxxxx DEBUG: assigned task to node localhost:xxxxx DEBUG: assigned task to node localhost:xxxxx @@ -143,15 +199,33 @@ DEBUG: assigned task to node localhost:xxxxx DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 3 DEBUG: pruning merge fetch taskId 2 @@ -168,6 +242,16 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 12 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 16 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 18 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 24 +DEBUG: assigned task to node localhost:xxxxx +DEBUG: assigned task to node localhost:xxxxx DEBUG: assigned task to node localhost:xxxxx DEBUG: assigned task to node localhost:xxxxx DEBUG: assigned task to node localhost:xxxxx diff --git a/src/test/regress/expected/multi_repartition_udt.out b/src/test/regress/expected/multi_repartition_udt.out index 437e188ee..35d3bd80f 100644 --- a/src/test/regress/expected/multi_repartition_udt.out +++ b/src/test/regress/expected/multi_repartition_udt.out @@ -155,14 +155,14 @@ LOG: join order: [ "repartition_udt" ][ dual partition join "repartition_udt_ot QUERY PLAN --------------------------------------------------------------------- Custom Scan (Citus Adaptive) - Task Count: 4 + Task Count: 6 Tasks Shown: None, not supported for re-partition queries -> MapMergeJob Map Task Count: 3 - Merge Task Count: 4 + Merge Task Count: 6 -> MapMergeJob Map Task Count: 5 - Merge Task Count: 4 + Merge Task Count: 6 (9 rows) SELECT * FROM repartition_udt JOIN repartition_udt_other diff --git a/src/test/regress/expected/multi_replicate_reference_table.out b/src/test/regress/expected/multi_replicate_reference_table.out index e16726171..3d8d8a787 100644 --- a/src/test/regress/expected/multi_replicate_reference_table.out +++ b/src/test/regress/expected/multi_replicate_reference_table.out @@ -284,7 +284,7 @@ DROP TABLE replicate_reference_table_rollback; SELECT count(*) FROM pg_dist_node; count --------------------------------------------------------------------- - 1 + 2 (1 row) -- test whether we can create distributed objects on a single worker node @@ -376,12 +376,6 @@ SELECT citus_add_node('localhost', :worker_2_port); (1 row) -- required for create_distributed_table_concurrently -SELECT 1 FROM citus_set_coordinator_host('localhost', :master_port); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - SET citus.shard_replication_factor TO 1; CREATE TABLE distributed_table_cdtc(column1 int primary key); SELECT create_distributed_table_concurrently('distributed_table_cdtc', 'column1'); @@ -391,12 +385,6 @@ SELECT create_distributed_table_concurrently('distributed_table_cdtc', 'column1' (1 row) RESET citus.shard_replication_factor; -SELECT citus_remove_node('localhost', :master_port); - citus_remove_node ---------------------------------------------------------------------- - -(1 row) - SELECT shardid, shardstate, shardlength, nodename, nodeport FROM @@ -712,12 +700,22 @@ SELECT master_remove_node('localhost', :worker_2_port); CREATE TABLE ref_table_1(id int primary key, v int); CREATE TABLE ref_table_2(id int primary key, v int references ref_table_1(id)); CREATE TABLE ref_table_3(id int primary key, v int references ref_table_2(id)); -SELECT create_reference_table('ref_table_1'), - create_reference_table('ref_table_2'), - create_reference_table('ref_table_3'); - create_reference_table | create_reference_table | create_reference_table +SELECT create_reference_table('ref_table_1'); + create_reference_table --------------------------------------------------------------------- - | | + +(1 row) + +SELECT create_reference_table('ref_table_2'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_reference_table('ref_table_3'); + create_reference_table +--------------------------------------------------------------------- + (1 row) -- status before master_add_node @@ -795,7 +793,7 @@ WHERE ORDER BY 1,4,5; shardid | shardstate | shardlength | nodename | nodeport --------------------------------------------------------------------- - 1370019 | 1 | 0 | localhost | 57637 + 1370021 | 1 | 0 | localhost | 57637 (1 row) -- we should see the two shard placements after activation @@ -820,7 +818,7 @@ WHERE ORDER BY 1,4,5; shardid | shardstate | shardlength | nodename | nodeport --------------------------------------------------------------------- - 1370019 | 1 | 0 | localhost | 57637 + 1370021 | 1 | 0 | localhost | 57637 (1 row) SELECT 1 FROM master_remove_node('localhost', :worker_2_port); @@ -850,7 +848,7 @@ HINT: Add the target node via SELECT citus_add_node('localhost', 57638); SELECT citus_add_secondary_node('localhost', :worker_2_port, 'localhost', :worker_1_port); citus_add_secondary_node --------------------------------------------------------------------- - 1370014 + 1370013 (1 row) SELECT citus_copy_shard_placement( @@ -1139,8 +1137,10 @@ select 1 FROM master_add_node('localhost', :worker_2_port); BEGIN; DROP TABLE test; CREATE TABLE test (x int, y int references ref(a)); -SELECT create_distributed_table('test','x'); ERROR: canceling the transaction since it was involved in a distributed deadlock +DETAIL: When adding a foreign key from a local table to a reference table, Citus applies a conversion to all the local tables in the foreign key graph +SELECT create_distributed_table('test','x'); +ERROR: current transaction is aborted, commands ignored until end of transaction block END; -- verify the split fails if we still need to replicate reference tables SELECT citus_remove_node('localhost', :worker_2_port); @@ -1158,7 +1158,7 @@ SELECT create_distributed_table('test','x'); SELECT citus_add_node('localhost', :worker_2_port); citus_add_node --------------------------------------------------------------------- - 1370022 + 1370020 (1 row) SELECT @@ -1194,7 +1194,7 @@ errors_received := 0; RAISE '(%/1) failed to add node', errors_received; END; $$; -ERROR: (1/1) failed to add node +ERROR: (0/1) failed to add node -- drop unnecassary tables DROP TABLE initially_not_replicated_reference_table; -- reload pg_dist_shard_placement table diff --git a/src/test/regress/expected/multi_router_planner.out b/src/test/regress/expected/multi_router_planner.out index 1553309d2..edfc728db 100644 --- a/src/test/regress/expected/multi_router_planner.out +++ b/src/test/regress/expected/multi_router_planner.out @@ -794,15 +794,33 @@ DEBUG: push down of limit count: 3 DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 2 DEBUG: pruning merge fetch taskId 2 @@ -819,6 +837,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 8 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 8 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 10 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 10 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 12 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 12 ERROR: the query contains a join that requires repartitioning HINT: Set citus.enable_repartition_joins to on to enable repartitioning RESET citus.enable_non_colocated_router_query_pushdown; @@ -830,7 +856,7 @@ SELECT a.author_id as first_author, b.word_count as second_word_count FROM articles_hash a, single_shard b WHERE a.author_id = 2 and a.author_id = b.author_id LIMIT 3; -DEBUG: found no worker with all shard placements +DEBUG: router planner does not support queries that reference non-colocated distributed tables DEBUG: generating subplan XXX_1 for CTE single_shard: SELECT id, author_id, title, word_count FROM multi_router_planner.articles_single_shard_hash DEBUG: Creating router plan DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT a.author_id AS first_author, b.word_count AS second_word_count FROM multi_router_planner.articles_hash a, (SELECT intermediate_result.id, intermediate_result.author_id, intermediate_result.title, intermediate_result.word_count FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, author_id bigint, title character varying(20), word_count integer)) b WHERE ((a.author_id OPERATOR(pg_catalog.=) 2) AND (a.author_id OPERATOR(pg_catalog.=) b.author_id)) LIMIT 3 @@ -1513,21 +1539,67 @@ DEBUG: Creating router plan SELECT a.author_id as first_author, b.word_count as second_word_count FROM articles_hash a, articles_single_shard_hash b WHERE a.author_id = 10 and a.author_id = b.author_id and int4eq(1, 1); -DEBUG: Creating router plan -DEBUG: query has a single distribution column value: 10 - first_author | second_word_count ---------------------------------------------------------------------- - 10 | 19519 - 10 | 19519 - 10 | 19519 - 10 | 19519 - 10 | 19519 -(5 rows) - +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: join prunable for task partitionId 0 and 1 +DEBUG: join prunable for task partitionId 0 and 2 +DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 +DEBUG: join prunable for task partitionId 1 and 0 +DEBUG: join prunable for task partitionId 1 and 2 +DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 +DEBUG: join prunable for task partitionId 2 and 0 +DEBUG: join prunable for task partitionId 2 and 1 +DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 +DEBUG: join prunable for task partitionId 3 and 0 +DEBUG: join prunable for task partitionId 3 and 1 +DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 +DEBUG: pruning merge fetch taskId 1 +DETAIL: Creating dependency on merge taskId 2 +DEBUG: pruning merge fetch taskId 2 +DETAIL: Creating dependency on merge taskId 2 +DEBUG: pruning merge fetch taskId 4 +DETAIL: Creating dependency on merge taskId 4 +DEBUG: pruning merge fetch taskId 5 +DETAIL: Creating dependency on merge taskId 4 +DEBUG: pruning merge fetch taskId 7 +DETAIL: Creating dependency on merge taskId 6 +DEBUG: pruning merge fetch taskId 8 +DETAIL: Creating dependency on merge taskId 6 +DEBUG: pruning merge fetch taskId 10 +DETAIL: Creating dependency on merge taskId 8 +DEBUG: pruning merge fetch taskId 11 +DETAIL: Creating dependency on merge taskId 8 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 10 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 10 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 12 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 12 +ERROR: the query contains a join that requires repartitioning +HINT: Set citus.enable_repartition_joins to on to enable repartitioning SELECT a.author_id as first_author, b.word_count as second_word_count FROM articles_hash a, articles_single_shard_hash b WHERE a.author_id = 10 and a.author_id = b.author_id and int4eq(1, 2); -DEBUG: Creating router plan +DEBUG: router planner does not support queries that reference non-colocated distributed tables first_author | second_word_count --------------------------------------------------------------------- (0 rows) @@ -1565,7 +1637,7 @@ SELECT a.author_id as first_author, b.word_count as second_word_count FROM articles_hash a, articles_single_shard_hash b WHERE a.author_id = 10 and a.author_id = b.author_id and date_ne_timestamp('1954-04-11', '1954-04-11'::timestamp); -DEBUG: Creating router plan +DEBUG: router planner does not support queries that reference non-colocated distributed tables first_author | second_word_count --------------------------------------------------------------------- (0 rows) @@ -1632,12 +1704,6 @@ DETAIL: A command for a distributed function is run. To make sure subsequent co SELECT 1 FROM authors_reference r JOIN ( SELECT s.datid FROM number1() s LEFT JOIN pg_database d ON s.datid = d.oid ) num_db ON (r.id = num_db.datid) LIMIT 1; -DEBUG: found no worker with all shard placements -DEBUG: function does not have co-located tables -DEBUG: generating subplan XXX_1 for subquery SELECT datid FROM multi_router_planner.number1() s(datid) -DEBUG: Creating router plan -DEBUG: generating subplan XXX_2 for subquery SELECT s.datid FROM ((SELECT intermediate_result.datid FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(datid integer)) s LEFT JOIN pg_database d ON (((s.datid)::oid OPERATOR(pg_catalog.=) d.oid))) -DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT 1 FROM (multi_router_planner.authors_reference r JOIN (SELECT intermediate_result.datid FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(datid integer)) num_db ON ((r.id OPERATOR(pg_catalog.=) num_db.datid))) LIMIT 1 DEBUG: Creating router plan ?column? --------------------------------------------------------------------- @@ -1647,12 +1713,6 @@ DEBUG: Creating router plan CREATE VIEW num_db AS SELECT s.datid FROM number1() s LEFT JOIN pg_database d ON s.datid = d.oid; SELECT 1 FROM authors_reference r JOIN num_db ON (r.id = num_db.datid) LIMIT 1; -DEBUG: found no worker with all shard placements -DEBUG: function does not have co-located tables -DEBUG: generating subplan XXX_1 for subquery SELECT datid FROM multi_router_planner.number1() s(datid) -DEBUG: Creating router plan -DEBUG: generating subplan XXX_2 for subquery SELECT s.datid FROM ((SELECT intermediate_result.datid FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(datid integer)) s LEFT JOIN pg_database d ON (((s.datid)::oid OPERATOR(pg_catalog.=) d.oid))) -DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT 1 FROM (multi_router_planner.authors_reference r JOIN (SELECT intermediate_result.datid FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(datid integer)) num_db ON ((r.id OPERATOR(pg_catalog.=) num_db.datid))) LIMIT 1 DEBUG: Creating router plan ?column? --------------------------------------------------------------------- @@ -1661,9 +1721,6 @@ DEBUG: Creating router plan -- with a CTE in a view WITH cte AS MATERIALIZED (SELECT * FROM num_db) SELECT 1 FROM authors_reference r JOIN cte ON (r.id = cte.datid) LIMIT 1; -DEBUG: found no worker with all shard placements -DEBUG: generating subplan XXX_1 for CTE cte: SELECT datid FROM (SELECT s.datid FROM (multi_router_planner.number1() s(datid) LEFT JOIN pg_database d ON (((s.datid)::oid OPERATOR(pg_catalog.=) d.oid)))) num_db -DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT 1 FROM (multi_router_planner.authors_reference r JOIN (SELECT intermediate_result.datid FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(datid integer)) cte ON ((r.id OPERATOR(pg_catalog.=) cte.datid))) LIMIT 1 DEBUG: Creating router plan ?column? --------------------------------------------------------------------- @@ -1879,15 +1936,33 @@ DEBUG: Router planner cannot handle multi-shard select queries DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 2 DEBUG: pruning merge fetch taskId 2 @@ -1904,6 +1979,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 8 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 10 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 25 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 12 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 30 id | author_id | title | word_count | name | id --------------------------------------------------------------------- (0 rows) @@ -1917,15 +2000,33 @@ DEBUG: Router planner cannot handle multi-shard select queries DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 5 DEBUG: pruning merge fetch taskId 2 @@ -1942,6 +2043,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 20 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 25 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 25 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 30 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 30 id | author_id | title | word_count | name | id --------------------------------------------------------------------- (0 rows) @@ -1975,15 +2084,33 @@ DEBUG: router planner does not support queries that reference non-colocated dis DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 2 DEBUG: pruning merge fetch taskId 2 @@ -2000,6 +2127,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 8 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 10 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 25 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 12 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 30 id | author_id | title | word_count | name | id --------------------------------------------------------------------- (0 rows) @@ -2008,19 +2143,37 @@ RESET citus.enable_non_colocated_router_query_pushdown; -- not router plannable SELECT * FROM articles_hash ar join authors_range au on (ar.author_id = au.id) WHERE ar.author_id = 3; -DEBUG: found no worker with all shard placements +DEBUG: router planner does not support queries that reference non-colocated distributed tables DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 2 DEBUG: pruning merge fetch taskId 2 @@ -2037,6 +2190,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 8 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 10 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 25 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 12 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 30 id | author_id | title | word_count | name | id --------------------------------------------------------------------- (0 rows) @@ -2402,7 +2563,6 @@ $$ LANGUAGE plpgsql; DEBUG: switching to sequential query execution mode DETAIL: A command for a distributed function is run. To make sure subsequent commands see the function correctly we need to make sure to use only one connection for all future commands SELECT author_articles_max_id(); -DEBUG: function does not have co-located tables DEBUG: Creating router plan CONTEXT: SQL statement "SELECT MAX(id) FROM articles_hash ah WHERE author_id = 1" @@ -2430,7 +2590,6 @@ $$ LANGUAGE plpgsql; DEBUG: switching to sequential query execution mode DETAIL: A command for a distributed function is run. To make sure subsequent commands see the function correctly we need to make sure to use only one connection for all future commands SELECT * FROM author_articles_id_word_count() ORDER BY 1; -DEBUG: function does not have co-located tables DEBUG: Creating router plan CONTEXT: SQL statement "SELECT ah.id, ah.word_count FROM articles_hash ah diff --git a/src/test/regress/expected/multi_router_planner_fast_path.out b/src/test/regress/expected/multi_router_planner_fast_path.out index e95d7517a..474d4a107 100644 --- a/src/test/regress/expected/multi_router_planner_fast_path.out +++ b/src/test/regress/expected/multi_router_planner_fast_path.out @@ -1612,7 +1612,6 @@ DETAIL: A command for a distributed function is run. To make sure subsequent co -- since the query results verifies the correctness \set VERBOSITY terse SELECT author_articles_max_id(); -DEBUG: function does not have co-located tables DEBUG: Distributed planning for a fast-path router query DEBUG: Creating router plan DEBUG: query has a single distribution column value: 1 @@ -1622,35 +1621,30 @@ DEBUG: query has a single distribution column value: 1 (1 row) SELECT author_articles_max_id(); -DEBUG: function does not have co-located tables author_articles_max_id --------------------------------------------------------------------- 41 (1 row) SELECT author_articles_max_id(); -DEBUG: function does not have co-located tables author_articles_max_id --------------------------------------------------------------------- 41 (1 row) SELECT author_articles_max_id(); -DEBUG: function does not have co-located tables author_articles_max_id --------------------------------------------------------------------- 41 (1 row) SELECT author_articles_max_id(); -DEBUG: function does not have co-located tables author_articles_max_id --------------------------------------------------------------------- 41 (1 row) SELECT author_articles_max_id(); -DEBUG: function does not have co-located tables author_articles_max_id --------------------------------------------------------------------- 41 @@ -1669,7 +1663,6 @@ END; $$ LANGUAGE plpgsql; DEBUG: switching to sequential query execution mode SELECT author_articles_max_id(1); -DEBUG: function does not have co-located tables DEBUG: Deferred pruning for a fast-path router query DEBUG: Creating router plan author_articles_max_id @@ -1678,7 +1671,6 @@ DEBUG: Creating router plan (1 row) SELECT author_articles_max_id(1); -DEBUG: function does not have co-located tables DEBUG: Deferred pruning for a fast-path router query DEBUG: Creating router plan author_articles_max_id @@ -1687,7 +1679,6 @@ DEBUG: Creating router plan (1 row) SELECT author_articles_max_id(1); -DEBUG: function does not have co-located tables DEBUG: Deferred pruning for a fast-path router query DEBUG: Creating router plan author_articles_max_id @@ -1696,7 +1687,6 @@ DEBUG: Creating router plan (1 row) SELECT author_articles_max_id(1); -DEBUG: function does not have co-located tables DEBUG: Deferred pruning for a fast-path router query DEBUG: Creating router plan author_articles_max_id @@ -1705,7 +1695,6 @@ DEBUG: Creating router plan (1 row) SELECT author_articles_max_id(1); -DEBUG: function does not have co-located tables DEBUG: Deferred pruning for a fast-path router query DEBUG: Creating router plan author_articles_max_id @@ -1714,7 +1703,6 @@ DEBUG: Creating router plan (1 row) SELECT author_articles_max_id(1); -DEBUG: function does not have co-located tables DEBUG: Deferred pruning for a fast-path router query DEBUG: Creating router plan author_articles_max_id @@ -1735,7 +1723,6 @@ END; $$ LANGUAGE plpgsql; DEBUG: switching to sequential query execution mode SELECT * FROM author_articles_id_word_count(); -DEBUG: function does not have co-located tables DEBUG: Distributed planning for a fast-path router query DEBUG: Creating router plan DEBUG: query has a single distribution column value: 1 @@ -1749,7 +1736,6 @@ DEBUG: query has a single distribution column value: 1 (5 rows) SELECT * FROM author_articles_id_word_count(); -DEBUG: function does not have co-located tables id | word_count --------------------------------------------------------------------- 1 | 9572 @@ -1760,7 +1746,6 @@ DEBUG: function does not have co-located tables (5 rows) SELECT * FROM author_articles_id_word_count(); -DEBUG: function does not have co-located tables id | word_count --------------------------------------------------------------------- 1 | 9572 @@ -1771,7 +1756,6 @@ DEBUG: function does not have co-located tables (5 rows) SELECT * FROM author_articles_id_word_count(); -DEBUG: function does not have co-located tables id | word_count --------------------------------------------------------------------- 1 | 9572 @@ -1782,7 +1766,6 @@ DEBUG: function does not have co-located tables (5 rows) SELECT * FROM author_articles_id_word_count(); -DEBUG: function does not have co-located tables id | word_count --------------------------------------------------------------------- 1 | 9572 @@ -1793,7 +1776,6 @@ DEBUG: function does not have co-located tables (5 rows) SELECT * FROM author_articles_id_word_count(); -DEBUG: function does not have co-located tables id | word_count --------------------------------------------------------------------- 1 | 9572 @@ -1816,7 +1798,6 @@ END; $$ LANGUAGE plpgsql; DEBUG: switching to sequential query execution mode SELECT * FROM author_articles_id_word_count(1); -DEBUG: function does not have co-located tables DEBUG: Deferred pruning for a fast-path router query DEBUG: Creating router plan id | word_count @@ -1829,7 +1810,6 @@ DEBUG: Creating router plan (5 rows) SELECT * FROM author_articles_id_word_count(1); -DEBUG: function does not have co-located tables DEBUG: Deferred pruning for a fast-path router query DEBUG: Creating router plan id | word_count @@ -1842,7 +1822,6 @@ DEBUG: Creating router plan (5 rows) SELECT * FROM author_articles_id_word_count(1); -DEBUG: function does not have co-located tables DEBUG: Deferred pruning for a fast-path router query DEBUG: Creating router plan id | word_count @@ -1855,7 +1834,6 @@ DEBUG: Creating router plan (5 rows) SELECT * FROM author_articles_id_word_count(1); -DEBUG: function does not have co-located tables DEBUG: Deferred pruning for a fast-path router query DEBUG: Creating router plan id | word_count @@ -1868,7 +1846,6 @@ DEBUG: Creating router plan (5 rows) SELECT * FROM author_articles_id_word_count(1); -DEBUG: function does not have co-located tables DEBUG: Deferred pruning for a fast-path router query DEBUG: Creating router plan id | word_count @@ -1881,7 +1858,6 @@ DEBUG: Creating router plan (5 rows) SELECT * FROM author_articles_id_word_count(1); -DEBUG: function does not have co-located tables DEBUG: Deferred pruning for a fast-path router query DEBUG: Creating router plan id | word_count @@ -1900,32 +1876,38 @@ PREPARE insert_sel(int, int) AS INSERT INTO articles_hash SELECT * FROM articles_hash WHERE author_id = $2 AND word_count = $1 OFFSET 0; EXECUTE insert_sel(1,1); -DEBUG: OFFSET clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Offset clause is currently unsupported when a subquery references a column from another query DEBUG: Deferred pruning for a fast-path router query DEBUG: Creating router plan DEBUG: Collecting INSERT ... SELECT results on coordinator EXECUTE insert_sel(1,1); -DEBUG: OFFSET clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Offset clause is currently unsupported when a subquery references a column from another query DEBUG: Deferred pruning for a fast-path router query DEBUG: Creating router plan DEBUG: Collecting INSERT ... SELECT results on coordinator EXECUTE insert_sel(1,1); -DEBUG: OFFSET clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Offset clause is currently unsupported when a subquery references a column from another query DEBUG: Deferred pruning for a fast-path router query DEBUG: Creating router plan DEBUG: Collecting INSERT ... SELECT results on coordinator EXECUTE insert_sel(1,1); -DEBUG: OFFSET clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Offset clause is currently unsupported when a subquery references a column from another query DEBUG: Deferred pruning for a fast-path router query DEBUG: Creating router plan DEBUG: Collecting INSERT ... SELECT results on coordinator EXECUTE insert_sel(1,1); -DEBUG: OFFSET clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Offset clause is currently unsupported when a subquery references a column from another query DEBUG: Deferred pruning for a fast-path router query DEBUG: Creating router plan DEBUG: Collecting INSERT ... SELECT results on coordinator EXECUTE insert_sel(1,1); -DEBUG: OFFSET clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Offset clause is currently unsupported when a subquery references a column from another query DEBUG: Deferred pruning for a fast-path router query DEBUG: Creating router plan DEBUG: Collecting INSERT ... SELECT results on coordinator diff --git a/src/test/regress/expected/multi_schema_support.out b/src/test/regress/expected/multi_schema_support.out index b4003f258..f39f5f2b1 100644 --- a/src/test/regress/expected/multi_schema_support.out +++ b/src/test/regress/expected/multi_schema_support.out @@ -868,7 +868,7 @@ SELECT create_distributed_table('old_schema.table_set_schema', 'id'); CREATE SCHEMA new_schema; SELECT objid::oid::regnamespace as "Distributed Schemas" FROM pg_catalog.pg_dist_object - WHERE objid::oid::regnamespace IN ('old_schema', 'new_schema'); + WHERE objid::oid::regnamespace IN ('old_schema', 'new_schema') ORDER BY objid::oid::regnamespace; Distributed Schemas --------------------------------------------------------------------- old_schema @@ -890,7 +890,7 @@ SELECT table_schema AS "Shards' Schema" ALTER TABLE old_schema.table_set_schema SET SCHEMA new_schema; SELECT objid::oid::regnamespace as "Distributed Schemas" FROM pg_catalog.pg_dist_object - WHERE objid::oid::regnamespace IN ('old_schema', 'new_schema'); + WHERE objid::oid::regnamespace IN ('old_schema', 'new_schema') ORDER BY objid::oid::regnamespace; Distributed Schemas --------------------------------------------------------------------- old_schema @@ -994,7 +994,7 @@ SELECT create_distributed_table('table_set_schema', 'id'); CREATE SCHEMA new_schema; SELECT objid::oid::regnamespace as "Distributed Schemas" FROM pg_catalog.pg_dist_object - WHERE objid::oid::regnamespace IN ('old_schema', 'new_schema'); + WHERE objid::oid::regnamespace IN ('old_schema', 'new_schema') ORDER BY objid::oid::regnamespace; Distributed Schemas --------------------------------------------------------------------- old_schema @@ -1018,7 +1018,7 @@ SET search_path TO old_schema; ALTER TABLE table_set_schema SET SCHEMA new_schema; SELECT objid::oid::regnamespace as "Distributed Schemas" FROM pg_catalog.pg_dist_object - WHERE objid::oid::regnamespace IN ('old_schema', 'new_schema'); + WHERE objid::oid::regnamespace IN ('old_schema', 'new_schema') ORDER BY objid::oid::regnamespace; Distributed Schemas --------------------------------------------------------------------- old_schema @@ -1095,6 +1095,9 @@ ALTER TABLE IF EXISTS non_existent_table SET SCHEMA non_existent_schema; NOTICE: relation "non_existent_table" does not exist, skipping DROP SCHEMA existing_schema, another_existing_schema CASCADE; NOTICE: drop cascades to table existing_schema.table_set_schema +-- test DROP SCHEMA with nonexisting schemas +DROP SCHEMA ax, bx, cx, dx, ex, fx, gx, jx; +ERROR: schema "ax" does not exist -- test ALTER TABLE SET SCHEMA with interesting names CREATE SCHEMA "cItuS.T E E N'sSchema"; CREATE SCHEMA "citus-teen's scnd schm."; @@ -1153,9 +1156,13 @@ SELECT create_reference_table('schema_with_user.test_table'); SET citus.next_shard_id TO 1197000; -- we do not use run_command_on_coordinator_and_workers here because when there is CASCADE, it causes deadlock DROP OWNED BY "test-user" CASCADE; -NOTICE: drop cascades to table schema_with_user.test_table +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table schema_with_user.test_table +drop cascades to table schema_with_user.test_table_1190039 +NOTICE: schema "schema_with_user" does not exist, skipping +CONTEXT: SQL statement "SELECT citus_drop_all_shards(v_obj.objid, v_obj.schema_name, v_obj.object_name, drop_shards_metadata_only := false)" +PL/pgSQL function citus_drop_trigger() line XX at PERFORM DROP USER "test-user"; -DROP FUNCTION run_command_on_coordinator_and_workers(p_sql text); -- test run_command_on_* UDFs with schema CREATE SCHEMA run_test_schema; CREATE TABLE run_test_schema.test_table(id int); @@ -1328,6 +1335,31 @@ SELECT * FROM "Citus'Teen123"."TeeNTabLE.1!?!" ORDER BY id; 4 | 4 | (4 rows) +-- test alter owner propagation +CREATE ROLE test_non_super_user; +ALTER ROLE test_non_super_user NOSUPERUSER; +SELECT pg_get_userbyid(nspowner) AS schema_owner + FROM pg_namespace + WHERE nspname = 'bar'; + schema_owner +--------------------------------------------------------------------- + postgres +(1 row) + +ALTER SCHEMA bar OWNER TO test_non_super_user; +select result from run_command_on_workers ($$ + SELECT pg_get_userbyid(nspowner) AS schema_owner + FROM pg_namespace + WHERE nspname = 'bar' +$$); + result +--------------------------------------------------------------------- + test_non_super_user + test_non_super_user +(2 rows) + +ALTER SCHEMA bar OWNER TO postgres; +DROP ROLE test_non_super_user; -- test error INSERT INTO bar.test VALUES (3,3), (4,4), (5,5), (6,6), (7,7), (8,8), (9,9); BEGIN; @@ -1361,6 +1393,7 @@ SELECT pg_identify_object_as_address(classid, objid, objsubid) FROM pg_catalog.p (schema,{run_test_schema},{}) (1 row) +DROP TABLE public.nation_local; DROP SCHEMA run_test_schema, test_schema_support_join_1, test_schema_support_join_2, "Citus'Teen123", "CiTUS.TEEN2", bar, test_schema_support CASCADE; -- verify that the dropped schema is removed from worker's pg_dist_object SELECT pg_identify_object_as_address(classid, objid, objsubid) FROM pg_catalog.pg_dist_object diff --git a/src/test/regress/expected/multi_sequence_default.out b/src/test/regress/expected/multi_sequence_default.out index c22e8109b..1cf2e806d 100644 --- a/src/test/regress/expected/multi_sequence_default.out +++ b/src/test/regress/expected/multi_sequence_default.out @@ -9,13 +9,6 @@ SET citus.shard_replication_factor TO 1; CREATE SCHEMA sequence_default; SET search_path = sequence_default, public; -- test both distributed and citus local tables -SELECT 1 FROM citus_add_node('localhost', :master_port, groupId => 0); -NOTICE: localhost:xxxxx is the coordinator and already contains metadata, skipping syncing the metadata - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - -- Cannot add a column involving DEFAULT nextval('..') because the table is not empty CREATE SEQUENCE seq_0; CREATE SEQUENCE seq_0_local_table; @@ -675,8 +668,12 @@ ERROR: nextval(sequence) calls in worker nodes are not supported for column def -- nextval from worker should fail for int and smallint sequences SELECT nextval('seq_12'); ERROR: nextval: reached maximum value of sequence "seq_12" (32767) +DETAIL: nextval(sequence) calls in worker nodes are not supported for column defaults of type int or smallint +HINT: If the command was issued from a worker node, try issuing it from the coordinator node instead. SELECT nextval('seq_13'); ERROR: nextval: reached maximum value of sequence "seq_13" (2147483647) +DETAIL: nextval(sequence) calls in worker nodes are not supported for column defaults of type int or smallint +HINT: If the command was issued from a worker node, try issuing it from the coordinator node instead. -- nextval from worker should work for bigint sequences SELECT nextval('seq_14'); nextval @@ -745,8 +742,12 @@ SELECT nextval('seq_12'); -- nextval from worker should fail for int and smallint sequences SELECT nextval('seq_13'); ERROR: nextval: reached maximum value of sequence "seq_13" (2147483647) +DETAIL: nextval(sequence) calls in worker nodes are not supported for column defaults of type int or smallint +HINT: If the command was issued from a worker node, try issuing it from the coordinator node instead. SELECT nextval('seq_14'); ERROR: nextval: reached maximum value of sequence "seq_14" (32767) +DETAIL: nextval(sequence) calls in worker nodes are not supported for column defaults of type int or smallint +HINT: If the command was issued from a worker node, try issuing it from the coordinator node instead. \c - - - :master_port SET citus.shard_replication_factor TO 1; SET search_path = sequence_default, public; @@ -811,8 +812,12 @@ SELECT nextval('seq_12'); -- nextval from worker should fail for int and smallint sequences SELECT nextval('seq_13'); ERROR: nextval: reached maximum value of sequence "seq_13" (2147483647) +DETAIL: nextval(sequence) calls in worker nodes are not supported for column defaults of type int or smallint +HINT: If the command was issued from a worker node, try issuing it from the coordinator node instead. SELECT nextval('seq_14'); ERROR: nextval: reached maximum value of sequence "seq_14" (32767) +DETAIL: nextval(sequence) calls in worker nodes are not supported for column defaults of type int or smallint +HINT: If the command was issued from a worker node, try issuing it from the coordinator node instead. \c - - - :master_port -- Show that sequence and its dependency schema will be propagated if a distributed -- table with default column is added @@ -891,10 +896,4 @@ DROP TABLE test_seq_dist; DROP TABLE sequence_default.seq_test_7_par; SET client_min_messages TO error; -- suppress cascading objects dropping DROP SCHEMA sequence_default CASCADE; -SELECT master_remove_node('localhost', :master_port); - master_remove_node ---------------------------------------------------------------------- - -(1 row) - SET search_path TO public; diff --git a/src/test/regress/expected/multi_shard_update_delete.out b/src/test/regress/expected/multi_shard_update_delete.out index af8ddfb2d..a42f90475 100644 --- a/src/test/regress/expected/multi_shard_update_delete.out +++ b/src/test/regress/expected/multi_shard_update_delete.out @@ -725,7 +725,7 @@ SET value_2 = 5 FROM events_test_table_2 WHERE users_test_table.user_id = events_test_table_2.user_id; ERROR: cannot push down this subquery -DETAIL: Shards of relations in subquery need to have 1-to-1 shard partitioning +DETAIL: users_test_table and events_test_table_2 are not colocated -- Should error out due to multiple row return from subquery, but we can not get this information within -- subquery pushdown planner. This query will be sent to worker with recursive planner. \set VERBOSITY terse diff --git a/src/test/regress/expected/multi_simple_queries.out b/src/test/regress/expected/multi_simple_queries.out index 646c42599..d48f935c6 100644 --- a/src/test/regress/expected/multi_simple_queries.out +++ b/src/test/regress/expected/multi_simple_queries.out @@ -507,15 +507,33 @@ DEBUG: push down of limit count: 3 DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 2 DEBUG: pruning merge fetch taskId 2 @@ -532,6 +550,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 8 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 8 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 10 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 10 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 12 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 12 ERROR: the query contains a join that requires repartitioning HINT: Set citus.enable_repartition_joins to on to enable repartitioning -- but they can be executed via repartition join planner @@ -545,15 +571,33 @@ DEBUG: push down of limit count: 3 DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 2 DEBUG: pruning merge fetch taskId 2 @@ -570,6 +614,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 8 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 8 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 10 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 10 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 12 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 12 first_author | second_word_count --------------------------------------------------------------------- 10 | 19519 @@ -655,15 +707,33 @@ DEBUG: Router planner cannot handle multi-shard select queries DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 2 DEBUG: pruning merge fetch taskId 2 @@ -680,6 +750,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 8 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 12 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 10 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 15 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 12 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 18 ERROR: the query contains a join that requires repartitioning HINT: Set citus.enable_repartition_joins to on to enable repartitioning -- system columns from shard tables can be queried and retrieved diff --git a/src/test/regress/expected/multi_size_queries.out b/src/test/regress/expected/multi_size_queries.out index 97036b1db..2ff8d9c4b 100644 --- a/src/test/regress/expected/multi_size_queries.out +++ b/src/test/regress/expected/multi_size_queries.out @@ -75,7 +75,7 @@ SELECT citus_table_size('customer_copy_hash'), citus_table_size('supplier'); citus_table_size | citus_table_size | citus_table_size --------------------------------------------------------------------- - 548864 | 548864 | 442368 + 548864 | 548864 | 655360 (1 row) CREATE INDEX index_1 on customer_copy_hash(c_custkey); @@ -104,19 +104,19 @@ VACUUM (FULL) supplier; SELECT citus_table_size('supplier'); citus_table_size --------------------------------------------------------------------- - 376832 + 565248 (1 row) SELECT citus_relation_size('supplier'); citus_relation_size --------------------------------------------------------------------- - 376832 + 565248 (1 row) SELECT citus_total_relation_size('supplier'); citus_total_relation_size --------------------------------------------------------------------- - 376832 + 565248 (1 row) CREATE INDEX index_2 on supplier(s_suppkey); @@ -124,19 +124,19 @@ VACUUM (FULL) supplier; SELECT citus_table_size('supplier'); citus_table_size --------------------------------------------------------------------- - 376832 + 565248 (1 row) SELECT citus_relation_size('supplier'); citus_relation_size --------------------------------------------------------------------- - 376832 + 565248 (1 row) SELECT citus_total_relation_size('supplier'); citus_total_relation_size --------------------------------------------------------------------- - 458752 + 688128 (1 row) -- Test inside the transaction diff --git a/src/test/regress/expected/multi_table_ddl.out b/src/test/regress/expected/multi_table_ddl.out index 4a2f68162..2db4a7797 100644 --- a/src/test/regress/expected/multi_table_ddl.out +++ b/src/test/regress/expected/multi_table_ddl.out @@ -78,6 +78,12 @@ SELECT * FROM pg_dist_shard_placement; DROP EXTENSION citus; CREATE EXTENSION citus; -- re-add the nodes to the cluster +SELECT 1 FROM citus_set_coordinator_host('localhost'); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + SELECT 1 FROM master_add_node('localhost', :worker_1_port); ?column? --------------------------------------------------------------------- diff --git a/src/test/regress/expected/multi_tenant_isolation.out b/src/test/regress/expected/multi_tenant_isolation.out index b370ba6c6..5af7acac8 100644 --- a/src/test/regress/expected/multi_tenant_isolation.out +++ b/src/test/regress/expected/multi_tenant_isolation.out @@ -986,20 +986,25 @@ SELECT create_distributed_table('test_colocated_table_1', 'id', colocate_with => (1 row) -CREATE TABLE test_colocated_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_reference_table_fkey(id), FOREIGN KEY(id) REFERENCES test_colocated_table_1(id)); +CREATE TABLE test_colocated_table_2(id int PRIMARY KEY, value_1 int); SELECT create_distributed_table('test_colocated_table_2', 'id', colocate_with => 'test_colocated_table_1'); create_distributed_table --------------------------------------------------------------------- (1 row) -CREATE TABLE test_colocated_table_3(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_reference_table_fkey(id), FOREIGN KEY(id) REFERENCES test_colocated_table_1(id), FOREIGN KEY(id) REFERENCES test_colocated_table_2(id)); +ALTER TABLE test_colocated_table_2 ADD FOREIGN KEY(value_1) REFERENCES test_reference_table_fkey(id); +ALTER TABLE test_colocated_table_2 ADD FOREIGN KEY(id) REFERENCES test_colocated_table_1(id); +CREATE TABLE test_colocated_table_3(id int PRIMARY KEY, value_1 int); SELECT create_distributed_table('test_colocated_table_3', 'id', colocate_with => 'test_colocated_table_1'); create_distributed_table --------------------------------------------------------------------- (1 row) +ALTER TABLE test_colocated_table_3 ADD FOREIGN KEY(value_1) REFERENCES test_reference_table_fkey(id); +ALTER TABLE test_colocated_table_3 ADD FOREIGN KEY(id) REFERENCES test_colocated_table_1(id); +ALTER TABLE test_colocated_table_3 ADD FOREIGN KEY(id) REFERENCES test_colocated_table_2(id); INSERT INTO test_reference_table_fkey SELECT i FROM generate_series (0, 100) i; INSERT INTO test_colocated_table_1 SELECT i, i FROM generate_series (0, 100) i; INSERT INTO test_colocated_table_2 SELECT i, i FROM generate_series (0, 100) i; @@ -1159,7 +1164,7 @@ SELECT 1 FROM master_add_node('localhost', :master_port, groupId=>0); SELECT count(*) FROM pg_dist_shard NATURAL JOIN pg_dist_shard_placement WHERE logicalrelid = 'ref_table'::regclass; count --------------------------------------------------------------------- - 2 + 3 (1 row) \c - mx_isolation_role_ent - :master_port diff --git a/src/test/regress/expected/multi_tenant_isolation_nonblocking.out b/src/test/regress/expected/multi_tenant_isolation_nonblocking.out index dbd15b056..3ec16e6ee 100644 --- a/src/test/regress/expected/multi_tenant_isolation_nonblocking.out +++ b/src/test/regress/expected/multi_tenant_isolation_nonblocking.out @@ -1275,3 +1275,9 @@ SELECT count(*) FROM pg_catalog.pg_dist_partition WHERE colocationid > 0; TRUNCATE TABLE pg_catalog.pg_dist_colocation; ALTER SEQUENCE pg_catalog.pg_dist_colocationid_seq RESTART 100; ALTER SEQUENCE pg_catalog.pg_dist_placement_placementid_seq RESTART :last_placement_id; +SELECT citus_set_coordinator_host('localhost'); + citus_set_coordinator_host +--------------------------------------------------------------------- + +(1 row) + diff --git a/src/test/regress/expected/multi_transaction_recovery.out b/src/test/regress/expected/multi_transaction_recovery.out index 509a87acf..85144944d 100644 --- a/src/test/regress/expected/multi_transaction_recovery.out +++ b/src/test/regress/expected/multi_transaction_recovery.out @@ -1,16 +1,5 @@ -- Tests for prepared transaction recovery SET citus.next_shard_id TO 1220000; --- reference tables can have placements on the coordinator. Add it so --- verify we recover transactions which do DML on coordinator placements --- properly. -SET client_min_messages TO ERROR; -SELECT 1 FROM master_add_node('localhost', :master_port, groupid => 0); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - -RESET client_min_messages; -- enforce 1 connection per placement since -- the tests are prepared for that SET citus.force_max_query_parallelization TO ON; @@ -516,9 +505,3 @@ DROP TABLE test_recovery; DROP TABLE test_recovery_single; DROP TABLE test_2pcskip; DROP TABLE test_reference; -SELECT 1 FROM master_remove_node('localhost', :master_port); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - diff --git a/src/test/regress/expected/multi_transactional_drop_shards.out b/src/test/regress/expected/multi_transactional_drop_shards.out index dca2466d6..761275dff 100644 --- a/src/test/regress/expected/multi_transactional_drop_shards.out +++ b/src/test/regress/expected/multi_transactional_drop_shards.out @@ -374,9 +374,10 @@ ORDER BY shardid, nodename, nodeport; shardid | shardstate | nodename | nodeport --------------------------------------------------------------------- + 1410006 | 1 | localhost | 57636 1410006 | 1 | localhost | 57637 1410006 | 1 | localhost | 57638 -(2 rows) +(3 rows) -- verify table is not dropped \dt transactional_drop_reference @@ -516,7 +517,7 @@ SET citus.override_table_visibility TO false; (8 rows) \ds transactional_drop_serial_column2_seq - List of relations + List of relations Schema | Name | Type | Owner --------------------------------------------------------------------- public | transactional_drop_serial_column2_seq | sequence | postgres @@ -670,13 +671,6 @@ ORDER BY \c - - - :master_port SET client_min_messages TO WARNING; --- try using the coordinator as a worker and then dropping the table -SELECT 1 FROM master_add_node('localhost', :master_port, groupid := 0); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - CREATE TABLE citus_local (id serial, k int); SELECT create_distributed_table('citus_local', 'id'); create_distributed_table @@ -686,12 +680,6 @@ SELECT create_distributed_table('citus_local', 'id'); INSERT INTO citus_local (k) VALUES (2); DROP TABLE citus_local; -SELECT master_remove_node('localhost', :master_port); - master_remove_node ---------------------------------------------------------------------- - -(1 row) - -- clean the workspace DROP TABLE transactional_drop_shards, transactional_drop_reference; -- test DROP TABLE as a non-superuser in a transaction block diff --git a/src/test/regress/expected/multi_truncate.out b/src/test/regress/expected/multi_truncate.out index 9b47237ea..f08f0c3c7 100644 --- a/src/test/regress/expected/multi_truncate.out +++ b/src/test/regress/expected/multi_truncate.out @@ -432,8 +432,12 @@ CREATE TABLE dist(id int, ref_id int REFERENCES ref(id)); INSERT INTO dist SELECT x,x FROM generate_series(1,10000) x; -- test that we do not cascade truncates to local referencing tables SELECT truncate_local_data_after_distributing_table('ref'); -ERROR: cannot truncate a table referenced in a foreign key constraint by a local table -DETAIL: Table "dist" references "ref" +NOTICE: truncate cascades to table "dist" + truncate_local_data_after_distributing_table +--------------------------------------------------------------------- + +(1 row) + -- test that we allow distributing tables that have foreign keys to reference tables SELECT create_distributed_table('dist','id'); NOTICE: Copying data from local table... @@ -461,11 +465,12 @@ NOTICE: truncate cascades to table "dist" (1 row) SELECT * FROM table_sizes; - name | has_data + name | has_data --------------------------------------------------------------------- - dist | f - ref | f -(2 rows) + dist | f + ref | f + ref_1210032 | t +(3 rows) ROLLBACK; -- the following should truncate dist table only @@ -477,11 +482,12 @@ SELECT truncate_local_data_after_distributing_table('dist'); (1 row) SELECT * FROM table_sizes; - name | has_data + name | has_data --------------------------------------------------------------------- - dist | f - ref | t -(2 rows) + dist | f + ref | f + ref_1210032 | t +(3 rows) ROLLBACK; DROP TABLE ref, dist; diff --git a/src/test/regress/expected/multi_utilities.out b/src/test/regress/expected/multi_utilities.out index 93021a067..b82e54f16 100644 --- a/src/test/regress/expected/multi_utilities.out +++ b/src/test/regress/expected/multi_utilities.out @@ -370,6 +370,8 @@ NOTICE: issuing VACUUM multi_utilities.reference_vacuum_table_970000 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing VACUUM multi_utilities.reference_vacuum_table_970000 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +NOTICE: issuing VACUUM multi_utilities.reference_vacuum_table_970000 +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -- should propagate to all workers because table is distributed table VACUUM distributed_vacuum_table; NOTICE: issuing VACUUM multi_utilities.distributed_vacuum_table_970001 @@ -382,12 +384,16 @@ NOTICE: issuing VACUUM multi_utilities.reference_vacuum_table_970000 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing VACUUM multi_utilities.reference_vacuum_table_970000 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +NOTICE: issuing VACUUM multi_utilities.reference_vacuum_table_970000 +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -- only reference_vacuum_table should propagate VACUUM local_vacuum_table, reference_vacuum_table; NOTICE: issuing VACUUM multi_utilities.reference_vacuum_table_970000 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing VACUUM multi_utilities.reference_vacuum_table_970000 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +NOTICE: issuing VACUUM multi_utilities.reference_vacuum_table_970000 +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -- vacuum (disable_page_skipping) aggressively process pages of the relation, it does not respect visibility map VACUUM (DISABLE_PAGE_SKIPPING true) local_vacuum_table; VACUUM (DISABLE_PAGE_SKIPPING false) local_vacuum_table; @@ -440,6 +446,8 @@ NOTICE: issuing VACUUM (ANALYZE) multi_utilities.reference_vacuum_table_970000 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing VACUUM (ANALYZE) multi_utilities.reference_vacuum_table_970000 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +NOTICE: issuing VACUUM (ANALYZE) multi_utilities.reference_vacuum_table_970000 +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -- give enough time for stats to be updated.(updated per 500ms by default) select pg_sleep(1); pg_sleep @@ -499,6 +507,8 @@ NOTICE: issuing ANALYZE multi_utilities.reference_analyze_table_970002 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing ANALYZE multi_utilities.reference_analyze_table_970002 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +NOTICE: issuing ANALYZE multi_utilities.reference_analyze_table_970002 +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -- should propagate to all workers because table is distributed table ANALYZE distributed_analyze_table; NOTICE: issuing ANALYZE multi_utilities.distributed_analyze_table_970003 @@ -511,12 +521,16 @@ NOTICE: issuing ANALYZE multi_utilities.reference_analyze_table_970002 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing ANALYZE multi_utilities.reference_analyze_table_970002 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +NOTICE: issuing ANALYZE multi_utilities.reference_analyze_table_970002 +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -- only reference_analyze_table should propagate ANALYZE local_analyze_table, reference_analyze_table; NOTICE: issuing ANALYZE multi_utilities.reference_analyze_table_970002 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing ANALYZE multi_utilities.reference_analyze_table_970002 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +NOTICE: issuing ANALYZE multi_utilities.reference_analyze_table_970002 +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -- should not propagate because ddl propagation is disabled SET citus.enable_ddl_propagation TO OFF; ANALYZE distributed_analyze_table; diff --git a/src/test/regress/expected/multi_utility_statements.out b/src/test/regress/expected/multi_utility_statements.out index ad97dd267..ccfe3a333 100644 --- a/src/test/regress/expected/multi_utility_statements.out +++ b/src/test/regress/expected/multi_utility_statements.out @@ -254,6 +254,76 @@ FETCH FORWARD 3 FROM holdCursor; 1 | 19 (3 rows) +CLOSE holdCursor; +-- Test DECLARE CURSOR .. WITH HOLD inside transaction block +BEGIN; +DECLARE holdCursor CURSOR WITH HOLD FOR + SELECT * FROM cursor_me WHERE x = 1 ORDER BY y; +FETCH 3 FROM holdCursor; + x | y +--------------------------------------------------------------------- + 1 | 10 + 1 | 11 + 1 | 12 +(3 rows) + +FETCH BACKWARD 3 FROM holdCursor; + x | y +--------------------------------------------------------------------- + 1 | 11 + 1 | 10 +(2 rows) + +FETCH FORWARD 3 FROM holdCursor; + x | y +--------------------------------------------------------------------- + 1 | 10 + 1 | 11 + 1 | 12 +(3 rows) + +COMMIT; +FETCH 3 FROM holdCursor; + x | y +--------------------------------------------------------------------- + 1 | 13 + 1 | 14 + 1 | 15 +(3 rows) + +CLOSE holdCursor; +-- Test DECLARE NO SCROLL CURSOR .. WITH HOLD inside transaction block +BEGIN; +DECLARE holdCursor NO SCROLL CURSOR WITH HOLD FOR + SELECT * FROM cursor_me WHERE x = 1 ORDER BY y; +FETCH 3 FROM holdCursor; + x | y +--------------------------------------------------------------------- + 1 | 10 + 1 | 11 + 1 | 12 +(3 rows) + +FETCH FORWARD 3 FROM holdCursor; + x | y +--------------------------------------------------------------------- + 1 | 13 + 1 | 14 + 1 | 15 +(3 rows) + +COMMIT; +FETCH 3 FROM holdCursor; + x | y +--------------------------------------------------------------------- + 1 | 16 + 1 | 17 + 1 | 18 +(3 rows) + +FETCH BACKWARD 3 FROM holdCursor; +ERROR: cursor can only scan forward +HINT: Declare it with SCROLL option to enable backward scan. CLOSE holdCursor; -- Test DECLARE CURSOR .. WITH HOLD with parameter CREATE OR REPLACE FUNCTION declares_cursor(p int) diff --git a/src/test/regress/expected/mx_coordinator_shouldhaveshards.out b/src/test/regress/expected/mx_coordinator_shouldhaveshards.out index 438e1dcdd..547300460 100644 --- a/src/test/regress/expected/mx_coordinator_shouldhaveshards.out +++ b/src/test/regress/expected/mx_coordinator_shouldhaveshards.out @@ -89,7 +89,7 @@ key FROM a JOIN table_2 USING (key) GROUP BY key HAVING (max(table_2.value) >= (SELECT value FROM a)); -DEBUG: Group by list without distribution column is not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: generating subplan XXX_1 for CTE a: SELECT key, value FROM mx_coordinator_shouldhaveshards.table_1 ORDER BY key, value DESC LIMIT 1 DEBUG: push down of limit count: 1 DEBUG: generating subplan XXX_2 for subquery SELECT int4(count(*)) AS auto_coerced_by_citus_0, (a.key)::text AS auto_coerced_by_citus_1 FROM ((SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) a JOIN mx_coordinator_shouldhaveshards.table_2 USING (key)) GROUP BY a.key HAVING (max(table_2.value) OPERATOR(pg_catalog.>=) (SELECT a_1.value FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) a_1)) @@ -114,7 +114,8 @@ inserts AS ( ) SELECT count(*) FROM inserts; DEBUG: generating subplan XXX_1 for CTE stats: SELECT count(key) AS m FROM mx_coordinator_shouldhaveshards.table_1 DEBUG: generating subplan XXX_2 for CTE inserts: INSERT INTO mx_coordinator_shouldhaveshards.table_2 (key, value) SELECT table_1.key, count(*) AS count FROM mx_coordinator_shouldhaveshards.table_1 WHERE (table_1.key OPERATOR(pg_catalog.>=) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) GROUP BY table_1.key HAVING (count(*) OPERATOR(pg_catalog.<=) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) LIMIT 1 RETURNING table_2.key, table_2.value -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 1 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) inserts DEBUG: Subplan XXX_1 will be written to local file @@ -150,7 +151,7 @@ key FROM a JOIN table_2_rep USING (key) GROUP BY key HAVING (max(table_2_rep.value) >= (SELECT value FROM a)); -DEBUG: Group by list without distribution column is not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: generating subplan XXX_1 for CTE a: SELECT key, value FROM mx_coordinator_shouldhaveshards.table_1_rep ORDER BY key, value DESC LIMIT 1 DEBUG: push down of limit count: 1 DEBUG: generating subplan XXX_2 for subquery SELECT int4(count(*)) AS auto_coerced_by_citus_0, (a.key)::text AS auto_coerced_by_citus_1 FROM ((SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) a JOIN mx_coordinator_shouldhaveshards.table_2_rep USING (key)) GROUP BY a.key HAVING (max(table_2_rep.value) OPERATOR(pg_catalog.>=) (SELECT a_1.value FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) a_1)) @@ -175,7 +176,8 @@ inserts AS ( ) SELECT count(*) FROM inserts; DEBUG: generating subplan XXX_1 for CTE stats: SELECT count(key) AS m FROM mx_coordinator_shouldhaveshards.table_1_rep DEBUG: generating subplan XXX_2 for CTE inserts: INSERT INTO mx_coordinator_shouldhaveshards.table_2_rep (key, value) SELECT table_1_rep.key, count(*) AS count FROM mx_coordinator_shouldhaveshards.table_1_rep WHERE (table_1_rep.key OPERATOR(pg_catalog.>=) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) GROUP BY table_1_rep.key HAVING (count(*) OPERATOR(pg_catalog.<=) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) LIMIT 1 RETURNING table_2_rep.key, table_2_rep.value -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 1 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) inserts DEBUG: Subplan XXX_1 will be written to local file @@ -215,7 +217,7 @@ key FROM a JOIN table_2 USING (key) GROUP BY key HAVING (max(table_2.value) >= (SELECT value FROM a)); -DEBUG: Group by list without distribution column is not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: generating subplan XXX_1 for CTE a: SELECT key, value FROM mx_coordinator_shouldhaveshards.table_1 ORDER BY key, value DESC LIMIT 1 DEBUG: push down of limit count: 1 DEBUG: generating subplan XXX_2 for subquery SELECT int4(count(*)) AS auto_coerced_by_citus_0, (a.key)::text AS auto_coerced_by_citus_1 FROM ((SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) a JOIN mx_coordinator_shouldhaveshards.table_2 USING (key)) GROUP BY a.key HAVING (max(table_2.value) OPERATOR(pg_catalog.>=) (SELECT a_1.value FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) a_1)) @@ -240,7 +242,8 @@ inserts AS ( ) SELECT count(*) FROM inserts; DEBUG: generating subplan XXX_1 for CTE stats: SELECT count(key) AS m FROM mx_coordinator_shouldhaveshards.table_1 DEBUG: generating subplan XXX_2 for CTE inserts: INSERT INTO mx_coordinator_shouldhaveshards.table_2 (key, value) SELECT table_1.key, count(*) AS count FROM mx_coordinator_shouldhaveshards.table_1 WHERE (table_1.key OPERATOR(pg_catalog.>=) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) GROUP BY table_1.key HAVING (count(*) OPERATOR(pg_catalog.<=) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) LIMIT 1 RETURNING table_2.key, table_2.value -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 1 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) inserts DEBUG: Subplan XXX_1 will be written to local file @@ -276,7 +279,7 @@ key FROM a JOIN table_2_rep USING (key) GROUP BY key HAVING (max(table_2_rep.value) >= (SELECT value FROM a)); -DEBUG: Group by list without distribution column is not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: generating subplan XXX_1 for CTE a: SELECT key, value FROM mx_coordinator_shouldhaveshards.table_1_rep ORDER BY key, value DESC LIMIT 1 DEBUG: push down of limit count: 1 DEBUG: generating subplan XXX_2 for subquery SELECT int4(count(*)) AS auto_coerced_by_citus_0, (a.key)::text AS auto_coerced_by_citus_1 FROM ((SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) a JOIN mx_coordinator_shouldhaveshards.table_2_rep USING (key)) GROUP BY a.key HAVING (max(table_2_rep.value) OPERATOR(pg_catalog.>=) (SELECT a_1.value FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) a_1)) @@ -301,7 +304,8 @@ inserts AS ( ) SELECT count(*) FROM inserts; DEBUG: generating subplan XXX_1 for CTE stats: SELECT count(key) AS m FROM mx_coordinator_shouldhaveshards.table_1_rep DEBUG: generating subplan XXX_2 for CTE inserts: INSERT INTO mx_coordinator_shouldhaveshards.table_2_rep (key, value) SELECT table_1_rep.key, count(*) AS count FROM mx_coordinator_shouldhaveshards.table_1_rep WHERE (table_1_rep.key OPERATOR(pg_catalog.>=) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) GROUP BY table_1_rep.key HAVING (count(*) OPERATOR(pg_catalog.<=) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) LIMIT 1 RETURNING table_2_rep.key, table_2_rep.value -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 1 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) inserts DEBUG: Subplan XXX_1 will be written to local file diff --git a/src/test/regress/expected/mx_coordinator_shouldhaveshards_0.out b/src/test/regress/expected/mx_coordinator_shouldhaveshards_0.out index 398229fbb..15cd69068 100644 --- a/src/test/regress/expected/mx_coordinator_shouldhaveshards_0.out +++ b/src/test/regress/expected/mx_coordinator_shouldhaveshards_0.out @@ -89,7 +89,7 @@ key FROM a JOIN table_2 USING (key) GROUP BY key HAVING (max(table_2.value) >= (SELECT value FROM a)); -DEBUG: Group by list without distribution column is not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: generating subplan XXX_1 for CTE a: SELECT key, value FROM mx_coordinator_shouldhaveshards.table_1 ORDER BY key, value DESC LIMIT 1 DEBUG: push down of limit count: 1 DEBUG: generating subplan XXX_2 for subquery SELECT int4(count(*)) AS auto_coerced_by_citus_0, (a.key)::text AS auto_coerced_by_citus_1 FROM ((SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) a JOIN mx_coordinator_shouldhaveshards.table_2 USING (key)) GROUP BY a.key HAVING (max(table_2.value) OPERATOR(pg_catalog.>=) (SELECT a_1.value FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) a_1)) @@ -114,7 +114,8 @@ inserts AS ( ) SELECT count(*) FROM inserts; DEBUG: generating subplan XXX_1 for CTE stats: SELECT count(key) AS m FROM mx_coordinator_shouldhaveshards.table_1 DEBUG: generating subplan XXX_2 for CTE inserts: INSERT INTO mx_coordinator_shouldhaveshards.table_2 (key, value) SELECT key, count(*) AS count FROM mx_coordinator_shouldhaveshards.table_1 WHERE (key OPERATOR(pg_catalog.>=) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) GROUP BY key HAVING (count(*) OPERATOR(pg_catalog.<=) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) LIMIT 1 RETURNING table_2.key, table_2.value -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 1 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) inserts DEBUG: Subplan XXX_1 will be written to local file @@ -150,7 +151,7 @@ key FROM a JOIN table_2_rep USING (key) GROUP BY key HAVING (max(table_2_rep.value) >= (SELECT value FROM a)); -DEBUG: Group by list without distribution column is not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: generating subplan XXX_1 for CTE a: SELECT key, value FROM mx_coordinator_shouldhaveshards.table_1_rep ORDER BY key, value DESC LIMIT 1 DEBUG: push down of limit count: 1 DEBUG: generating subplan XXX_2 for subquery SELECT int4(count(*)) AS auto_coerced_by_citus_0, (a.key)::text AS auto_coerced_by_citus_1 FROM ((SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) a JOIN mx_coordinator_shouldhaveshards.table_2_rep USING (key)) GROUP BY a.key HAVING (max(table_2_rep.value) OPERATOR(pg_catalog.>=) (SELECT a_1.value FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) a_1)) @@ -175,7 +176,8 @@ inserts AS ( ) SELECT count(*) FROM inserts; DEBUG: generating subplan XXX_1 for CTE stats: SELECT count(key) AS m FROM mx_coordinator_shouldhaveshards.table_1_rep DEBUG: generating subplan XXX_2 for CTE inserts: INSERT INTO mx_coordinator_shouldhaveshards.table_2_rep (key, value) SELECT key, count(*) AS count FROM mx_coordinator_shouldhaveshards.table_1_rep WHERE (key OPERATOR(pg_catalog.>=) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) GROUP BY key HAVING (count(*) OPERATOR(pg_catalog.<=) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) LIMIT 1 RETURNING table_2_rep.key, table_2_rep.value -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 1 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) inserts DEBUG: Subplan XXX_1 will be written to local file @@ -215,7 +217,7 @@ key FROM a JOIN table_2 USING (key) GROUP BY key HAVING (max(table_2.value) >= (SELECT value FROM a)); -DEBUG: Group by list without distribution column is not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: generating subplan XXX_1 for CTE a: SELECT key, value FROM mx_coordinator_shouldhaveshards.table_1 ORDER BY key, value DESC LIMIT 1 DEBUG: push down of limit count: 1 DEBUG: generating subplan XXX_2 for subquery SELECT int4(count(*)) AS auto_coerced_by_citus_0, (a.key)::text AS auto_coerced_by_citus_1 FROM ((SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) a JOIN mx_coordinator_shouldhaveshards.table_2 USING (key)) GROUP BY a.key HAVING (max(table_2.value) OPERATOR(pg_catalog.>=) (SELECT a_1.value FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) a_1)) @@ -240,7 +242,8 @@ inserts AS ( ) SELECT count(*) FROM inserts; DEBUG: generating subplan XXX_1 for CTE stats: SELECT count(key) AS m FROM mx_coordinator_shouldhaveshards.table_1 DEBUG: generating subplan XXX_2 for CTE inserts: INSERT INTO mx_coordinator_shouldhaveshards.table_2 (key, value) SELECT key, count(*) AS count FROM mx_coordinator_shouldhaveshards.table_1 WHERE (key OPERATOR(pg_catalog.>=) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) GROUP BY key HAVING (count(*) OPERATOR(pg_catalog.<=) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) LIMIT 1 RETURNING table_2.key, table_2.value -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 1 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) inserts DEBUG: Subplan XXX_1 will be written to local file @@ -276,7 +279,7 @@ key FROM a JOIN table_2_rep USING (key) GROUP BY key HAVING (max(table_2_rep.value) >= (SELECT value FROM a)); -DEBUG: Group by list without distribution column is not allowed in distributed INSERT ... SELECT queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: generating subplan XXX_1 for CTE a: SELECT key, value FROM mx_coordinator_shouldhaveshards.table_1_rep ORDER BY key, value DESC LIMIT 1 DEBUG: push down of limit count: 1 DEBUG: generating subplan XXX_2 for subquery SELECT int4(count(*)) AS auto_coerced_by_citus_0, (a.key)::text AS auto_coerced_by_citus_1 FROM ((SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) a JOIN mx_coordinator_shouldhaveshards.table_2_rep USING (key)) GROUP BY a.key HAVING (max(table_2_rep.value) OPERATOR(pg_catalog.>=) (SELECT a_1.value FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) a_1)) @@ -301,7 +304,8 @@ inserts AS ( ) SELECT count(*) FROM inserts; DEBUG: generating subplan XXX_1 for CTE stats: SELECT count(key) AS m FROM mx_coordinator_shouldhaveshards.table_1_rep DEBUG: generating subplan XXX_2 for CTE inserts: INSERT INTO mx_coordinator_shouldhaveshards.table_2_rep (key, value) SELECT key, count(*) AS count FROM mx_coordinator_shouldhaveshards.table_1_rep WHERE (key OPERATOR(pg_catalog.>=) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) GROUP BY key HAVING (count(*) OPERATOR(pg_catalog.<=) (SELECT stats.m FROM (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m bigint)) stats)) LIMIT 1 RETURNING table_2_rep.key, table_2_rep.value -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: push down of limit count: 1 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) inserts DEBUG: Subplan XXX_1 will be written to local file diff --git a/src/test/regress/expected/non_colocated_leaf_subquery_joins.out b/src/test/regress/expected/non_colocated_leaf_subquery_joins.out index 976f3d438..7c032f31f 100644 --- a/src/test/regress/expected/non_colocated_leaf_subquery_joins.out +++ b/src/test/regress/expected/non_colocated_leaf_subquery_joins.out @@ -37,7 +37,6 @@ FROM (SELECT users_table.user_id FROM users_table, events_table WHERE users_table.user_id = events_table.user_id AND event_type IN (5,6,7,8)) as bar WHERE foo.user_id = bar.user_id;$$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT users_table.user_id, random() AS random FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.value_2) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4]))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.user_id, intermediate_result.random FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, random double precision)) foo, (SELECT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8])))) bar WHERE (foo.user_id OPERATOR(pg_catalog.=) bar.user_id) valid @@ -53,7 +52,6 @@ FROM (SELECT users_table.user_id FROM users_table, events_table WHERE users_table.user_id = events_table.value_2 AND event_type IN (5,6,7,8)) as bar WHERE foo.user_id = bar.user_id;$$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT users_table.user_id, random() AS random FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.value_2) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4]))) DEBUG: generating subplan XXX_2 for subquery SELECT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.value_2) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8]))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.user_id, intermediate_result.random FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, random double precision)) foo, (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) bar WHERE (foo.user_id OPERATOR(pg_catalog.=) bar.user_id) @@ -76,7 +74,6 @@ WHERE users_table, events_table WHERE users_table.user_id = events_table.value_2 AND event_type IN (5,6));$$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.value_2) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6]))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM public.users_table WHERE (value_1 OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer))) valid @@ -93,7 +90,6 @@ SELECT count(*) FROM q1, (SELECT users_table, events_table WHERE users_table.user_id = events_table.value_2 AND event_type IN (1,2,3,4)) as bar WHERE bar.user_id = q1.user_id ;$$); -DEBUG: function does not have co-located tables DEBUG: CTE q1 is going to be inlined via distributed planning DEBUG: generating subplan XXX_1 for subquery SELECT users_table.user_id, random() AS random FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.value_2) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4]))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT users_table.user_id FROM public.users_table) q1, (SELECT intermediate_result.user_id, intermediate_result.random FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, random double precision)) bar WHERE (bar.user_id OPERATOR(pg_catalog.=) q1.user_id) @@ -106,7 +102,6 @@ DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS c SELECT true AS valid FROM explain_json($$ (SELECT users_table.user_id FROM users_table, events_table WHERE users_table.user_id = events_table.value_2 AND event_type IN (1,2,3,4)) UNION (SELECT users_table.user_id FROM users_table, events_table WHERE users_table.user_id = events_table.user_id AND event_type IN (5,6,7,8));$$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.value_2) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4]))) DEBUG: generating subplan XXX_2 for subquery SELECT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8]))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer) UNION SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer) @@ -143,7 +138,6 @@ FROM ( ) q ORDER BY 2 DESC, 1; $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.value_2) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4]))) DEBUG: push down of limit count: 5 DEBUG: generating subplan XXX_2 for subquery SELECT user_id FROM public.users_table WHERE ((value_2 OPERATOR(pg_catalog.>=) 5) AND (EXISTS (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)))) LIMIT 5 @@ -165,7 +159,6 @@ FROM (SELECT users_table.user_id, value_1 FROM users_table, events_table WHERE users_table.user_id = events_table.user_id AND event_type IN (5,6,7,8)) as bar WHERE foo.user_id = bar.value_1;$$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8]))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT users_table.user_id, random() AS random FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4])))) foo, (SELECT intermediate_result.user_id, intermediate_result.value_1 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer)) bar WHERE (foo.user_id OPERATOR(pg_catalog.=) bar.value_1) valid diff --git a/src/test/regress/expected/non_colocated_subquery_joins.out b/src/test/regress/expected/non_colocated_subquery_joins.out index d03d4ecf3..1c1a7d935 100644 --- a/src/test/regress/expected/non_colocated_subquery_joins.out +++ b/src/test/regress/expected/non_colocated_subquery_joins.out @@ -44,7 +44,6 @@ SELECT true AS valid FROM explain_json_2($$ WHERE foo.value_2 = bar.value_2; $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT users_table.value_2 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8]))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT foo.value_2 FROM (SELECT users_table.value_2 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4])))) foo, (SELECT intermediate_result.value_2 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value_2 integer)) bar WHERE (foo.value_2 OPERATOR(pg_catalog.=) bar.value_2) valid @@ -65,7 +64,6 @@ SELECT true AS valid FROM explain_json_2($$ (SELECT event_type FROM events_table WHERE user_id < 100); $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT event_type FROM public.events_table WHERE (user_id OPERATOR(pg_catalog.<) 100) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM public.events_table WHERE (event_type OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.event_type FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(event_type integer))) valid @@ -85,7 +83,6 @@ SELECT true AS valid FROM explain_json_2($$ NOT IN (SELECT user_id FROM events_table WHERE event_type = 2); $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT user_id FROM public.events_table WHERE (event_type OPERATOR(pg_catalog.=) 2) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM public.events_table WHERE (NOT (user_id OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)))) valid @@ -107,7 +104,6 @@ SELECT true AS valid FROM explain_json_2($$ foo.event_type IN (SELECT event_type FROM events_table WHERE user_id < 3); $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT event_type FROM public.events_table WHERE (user_id OPERATOR(pg_catalog.<) 3) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT foo.user_id FROM (SELECT users_table.user_id, events_table.event_type FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4])))) foo, (SELECT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8])))) bar WHERE ((foo.user_id OPERATOR(pg_catalog.=) bar.user_id) AND (foo.event_type OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.event_type FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(event_type integer)))) valid @@ -128,7 +124,6 @@ SELECT true AS valid FROM explain_json_2($$ foo.user_id = bar.user_id AND foo.user_id IN (SELECT user_id FROM events_table WHERE user_id < 10); $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT (users_table.user_id OPERATOR(pg_catalog./) 2) AS user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8]))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT foo.user_id FROM (SELECT users_table.user_id, events_table.event_type FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4])))) foo, (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) bar WHERE ((foo.user_id OPERATOR(pg_catalog.=) bar.user_id) AND (foo.user_id OPERATOR(pg_catalog.=) ANY (SELECT events_table.user_id FROM public.events_table WHERE (events_table.user_id OPERATOR(pg_catalog.<) 10)))) valid @@ -149,7 +144,6 @@ SELECT true AS valid FROM explain_json_2($$ foo.user_id = bar.user_id AND foo.user_id NOT IN (SELECT user_id FROM events_table WHERE user_id < 10); $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT (users_table.user_id OPERATOR(pg_catalog./) 2) AS user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8]))) DEBUG: generating subplan XXX_2 for subquery SELECT user_id FROM public.events_table WHERE (user_id OPERATOR(pg_catalog.<) 10) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT foo.user_id FROM (SELECT users_table.user_id, events_table.event_type FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4])))) foo, (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) bar WHERE ((foo.user_id OPERATOR(pg_catalog.=) bar.user_id) AND (NOT (foo.user_id OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer))))) @@ -171,7 +165,6 @@ SELECT true AS valid FROM explain_json_2($$ foo.user_id = bar.user_id AND foo.event_type IN (SELECT event_type FROM events_table WHERE user_id < 4); $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT users_table.user_id, events_table.event_type FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.value_2) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4]))) DEBUG: generating subplan XXX_2 for subquery SELECT event_type FROM public.events_table WHERE (user_id OPERATOR(pg_catalog.<) 4) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT foo.user_id FROM (SELECT intermediate_result.user_id, intermediate_result.event_type FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, event_type integer)) foo, (SELECT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8])))) bar WHERE ((foo.user_id OPERATOR(pg_catalog.=) bar.user_id) AND (foo.event_type OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.event_type FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(event_type integer)))) @@ -196,7 +189,6 @@ SELECT true AS valid FROM explain_json_2($$ ) as foo_top, events_table WHERE events_table.user_id = foo_top.user_id; $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT users_table.user_id, events_table.event_type FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.value_2) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4]))) DEBUG: generating subplan XXX_2 for subquery SELECT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.event_type) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8]))) DEBUG: generating subplan XXX_3 for subquery SELECT event_type FROM public.events_table WHERE (user_id OPERATOR(pg_catalog.=) 5) @@ -231,7 +223,6 @@ SELECT true AS valid FROM explain_json_2($$ ) as foo_top; $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[17, 18, 19, 20]))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT user_id, random FROM (SELECT foo1.user_id, random() AS random FROM (SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4])))) foo1, (SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8])))) foo2, (SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[9, 10, 11, 12])))) foo3, (SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[13, 14, 15, 16])))) foo4, (SELECT intermediate_result.user_id, intermediate_result.value_1 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer)) foo5 WHERE ((foo1.user_id OPERATOR(pg_catalog.=) foo4.user_id) AND (foo1.user_id OPERATOR(pg_catalog.=) foo2.user_id) AND (foo1.user_id OPERATOR(pg_catalog.=) foo3.user_id) AND (foo1.user_id OPERATOR(pg_catalog.=) foo4.user_id) AND (foo1.user_id OPERATOR(pg_catalog.=) foo5.value_1))) foo_top valid @@ -263,7 +254,6 @@ SELECT true AS valid FROM explain_json_2($$ foo1.user_id = foo5.user_id ) as foo_top; $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.value_2) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[17, 18, 19, 20]))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT user_id, random FROM (SELECT foo1.user_id, random() AS random FROM (SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4])))) foo1, (SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8])))) foo2, (SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[9, 10, 11, 12])))) foo3, (SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[13, 14, 15, 16])))) foo4, (SELECT intermediate_result.user_id, intermediate_result.value_1 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer)) foo5 WHERE ((foo1.user_id OPERATOR(pg_catalog.=) foo4.user_id) AND (foo1.user_id OPERATOR(pg_catalog.=) foo2.user_id) AND (foo1.user_id OPERATOR(pg_catalog.=) foo3.user_id) AND (foo1.user_id OPERATOR(pg_catalog.=) foo4.user_id) AND (foo1.user_id OPERATOR(pg_catalog.=) foo5.user_id))) foo_top valid @@ -293,7 +283,6 @@ SELECT true AS valid FROM explain_json_2($$ foo1.user_id = foo5.value_1 ) as foo_top; $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.value_2) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8]))) DEBUG: generating subplan XXX_2 for subquery SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[17, 18, 19, 20]))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT user_id, random FROM (SELECT foo1.user_id, random() AS random FROM (SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4])))) foo1, (SELECT intermediate_result.user_id, intermediate_result.value_1 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer)) foo2, (SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[9, 10, 11, 12])))) foo3, (SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[13, 14, 15, 16])))) foo4, (SELECT intermediate_result.user_id, intermediate_result.value_1 FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer)) foo5 WHERE ((foo1.user_id OPERATOR(pg_catalog.=) foo4.user_id) AND (foo1.user_id OPERATOR(pg_catalog.=) foo2.user_id) AND (foo1.user_id OPERATOR(pg_catalog.=) foo3.user_id) AND (foo1.user_id OPERATOR(pg_catalog.=) foo4.user_id) AND (foo1.user_id OPERATOR(pg_catalog.=) foo5.value_1))) foo_top @@ -325,7 +314,6 @@ SELECT true AS valid FROM explain_json_2($$ foo2.user_id = foo5.value_1 ) as foo_top; $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.value_2) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8]))) DEBUG: generating subplan XXX_2 for subquery SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[17, 18, 19, 20]))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT user_id, random FROM (SELECT foo1.user_id, random() AS random FROM (SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4])))) foo1, (SELECT intermediate_result.user_id, intermediate_result.value_1 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer)) foo2, (SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[9, 10, 11, 12])))) foo3, (SELECT users_table.user_id, users_table.value_1 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[13, 14, 15, 16])))) foo4, (SELECT intermediate_result.user_id, intermediate_result.value_1 FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer)) foo5 WHERE ((foo1.user_id OPERATOR(pg_catalog.=) foo4.user_id) AND (foo1.user_id OPERATOR(pg_catalog.=) foo2.user_id) AND (foo1.user_id OPERATOR(pg_catalog.=) foo3.user_id) AND (foo1.user_id OPERATOR(pg_catalog.=) foo4.user_id) AND (foo2.user_id OPERATOR(pg_catalog.=) foo5.value_1))) foo_top @@ -359,7 +347,6 @@ SELECT true AS valid FROM explain_json_2($$ foo.user_id = bar.user_id) as bar_top ON (foo_top.user_id = bar_top.user_id); $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.value_2) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4]))) DEBUG: generating subplan XXX_2 for subquery SELECT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.value_2) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4]))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT foo.user_id FROM (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) foo, (SELECT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8])))) bar WHERE (foo.user_id OPERATOR(pg_catalog.=) bar.user_id)) foo_top JOIN (SELECT foo.user_id FROM (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) foo, (SELECT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8])))) bar WHERE (foo.user_id OPERATOR(pg_catalog.=) bar.user_id)) bar_top ON ((foo_top.user_id OPERATOR(pg_catalog.=) bar_top.user_id))) @@ -394,7 +381,6 @@ SELECT true AS valid FROM explain_json_2($$ ON (foo_top.value_2 = bar_top.user_id); $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT foo.user_id FROM (SELECT DISTINCT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[9, 10, 11, 12])))) foo, (SELECT DISTINCT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[13, 14, 15, 16])))) bar WHERE (foo.user_id OPERATOR(pg_catalog.=) bar.user_id) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT foo.user_id, foo.value_2 FROM (SELECT DISTINCT users_table.user_id, users_table.value_2 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4])))) foo, (SELECT DISTINCT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8])))) bar WHERE (foo.user_id OPERATOR(pg_catalog.=) bar.user_id)) foo_top JOIN (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) bar_top ON ((foo_top.value_2 OPERATOR(pg_catalog.=) bar_top.user_id))) valid @@ -426,7 +412,6 @@ SELECT true AS valid FROM explain_json_2($$ foo.user_id = bar.user_id) as bar_top ON (foo_top.value_2 = bar_top.user_id); $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT DISTINCT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.value_2) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[13, 14, 15, 16]))) DEBUG: generating subplan XXX_2 for subquery SELECT foo.user_id FROM (SELECT DISTINCT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[9, 10, 11, 12])))) foo, (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) bar WHERE (foo.user_id OPERATOR(pg_catalog.=) bar.user_id) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT foo.user_id, foo.value_2 FROM (SELECT DISTINCT users_table.user_id, users_table.value_2 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4])))) foo, (SELECT DISTINCT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8])))) bar WHERE (foo.user_id OPERATOR(pg_catalog.=) bar.user_id)) foo_top JOIN (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) bar_top ON ((foo_top.value_2 OPERATOR(pg_catalog.=) bar_top.user_id))) @@ -448,7 +433,6 @@ SELECT true AS valid FROM explain_json_2($$ WHERE foo.my_users = users_table.user_id) as mid_level_query ) as bar; $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT events_table.user_id AS my_users FROM public.events_table, public.users_table WHERE (events_table.event_type OPERATOR(pg_catalog.=) users_table.user_id) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT mid_level_query.user_id FROM (SELECT DISTINCT users_table.user_id FROM public.users_table, (SELECT intermediate_result.my_users FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(my_users integer)) foo WHERE (foo.my_users OPERATOR(pg_catalog.=) users_table.user_id)) mid_level_query) bar valid @@ -468,7 +452,6 @@ SELECT true AS valid FROM explain_json_2($$ WHERE foo.my_users = users_table.user_id) as mid_level_query ) as bar; $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT events_table.event_type AS my_users, random() AS random FROM public.events_table, public.users_table WHERE (events_table.user_id OPERATOR(pg_catalog.=) users_table.user_id) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT mid_level_query.user_id FROM (SELECT DISTINCT users_table.user_id FROM public.users_table, (SELECT intermediate_result.my_users, intermediate_result.random FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(my_users integer, random double precision)) foo WHERE (foo.my_users OPERATOR(pg_catalog.=) users_table.user_id)) mid_level_query) bar valid @@ -492,7 +475,6 @@ DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS c WHERE foo.my_users = users_table.user_id) as mid_level_query ) as bar; $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT events_table.user_id, random() AS random FROM public.users_table, public.events_table WHERE (users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT mid_level_query.user_id FROM (SELECT DISTINCT users_table.user_id FROM public.users_table, (SELECT events_table.user_id AS my_users FROM public.events_table, (SELECT intermediate_result.user_id, intermediate_result.random FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, random double precision)) selected_users WHERE (events_table.event_type OPERATOR(pg_catalog.=) selected_users.user_id)) foo WHERE (foo.my_users OPERATOR(pg_catalog.=) users_table.user_id)) mid_level_query) bar valid @@ -526,7 +508,6 @@ SELECT true AS valid FROM explain_json_2($$ ) as bar; $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT value_2 FROM public.events_table DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT mid_level_query.user_id FROM (SELECT DISTINCT users_table.user_id FROM public.users_table, (SELECT events_table.user_id AS my_users FROM public.events_table, (SELECT events_table_1.user_id FROM public.users_table users_table_1, public.events_table events_table_1 WHERE ((users_table_1.user_id OPERATOR(pg_catalog.=) events_table_1.user_id) AND (users_table_1.user_id OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.value_2 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value_2 integer))))) selected_users WHERE (events_table.user_id OPERATOR(pg_catalog.=) selected_users.user_id)) foo WHERE (foo.my_users OPERATOR(pg_catalog.=) users_table.user_id)) mid_level_query) bar valid @@ -548,7 +529,6 @@ WHERE users_table, events_table WHERE users_table.user_id = events_table.value_2 AND event_type IN (5,6));$$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.value_2) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6]))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM public.users_table WHERE (value_1 OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer))) valid @@ -565,7 +545,6 @@ SELECT count(*) FROM q1, (SELECT users_table, events_table WHERE users_table.user_id = events_table.value_2 AND event_type IN (1,2,3,4)) as bar WHERE bar.user_id = q1.user_id ;$$); -DEBUG: function does not have co-located tables DEBUG: CTE q1 is going to be inlined via distributed planning DEBUG: generating subplan XXX_1 for subquery SELECT users_table.user_id, random() AS random FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.value_2) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4]))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT users_table.user_id FROM public.users_table) q1, (SELECT intermediate_result.user_id, intermediate_result.random FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, random double precision)) bar WHERE (bar.user_id OPERATOR(pg_catalog.=) q1.user_id) @@ -583,7 +562,6 @@ SELECT true AS valid FROM explain_json_2($$ users_table, events_table WHERE users_table.user_id = events_table.user_id AND event_type IN (1,2,3,4)) as bar WHERE bar.user_id = q1.user_id ;$$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for CTE q1: SELECT user_id FROM public.users_table DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) q1, (SELECT users_table.user_id, random() AS random FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4])))) bar WHERE (bar.user_id OPERATOR(pg_catalog.=) q1.user_id) valid @@ -595,7 +573,6 @@ DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS c SELECT true AS valid FROM explain_json_2($$ (SELECT users_table.user_id FROM users_table, events_table WHERE users_table.user_id = events_table.value_2 AND event_type IN (1,2,3,4)) UNION (SELECT users_table.user_id FROM users_table, events_table WHERE users_table.user_id = events_table.user_id AND event_type IN (5,6,7,8));$$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.value_2) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4]))) DEBUG: generating subplan XXX_2 for subquery SELECT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8]))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer) UNION SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer) @@ -632,7 +609,6 @@ FROM ( ) q ORDER BY 2 DESC, 1; $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT users_table.user_id FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.value_2) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4]))) DEBUG: push down of limit count: 5 DEBUG: generating subplan XXX_2 for subquery SELECT user_id FROM public.users_table WHERE ((value_2 OPERATOR(pg_catalog.>=) 5) AND (EXISTS (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)))) LIMIT 5 @@ -654,7 +630,6 @@ SELECT true AS valid FROM explain_json_2($$ FROM (users_table u1 JOIN users_table u2 using(value_1)) a JOIN (SELECT value_1, random() FROM users_table) as u3 USING (value_1); $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT value_1, random() AS random FROM public.users_table DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((public.users_table u1 JOIN public.users_table u2 USING (value_1)) a(value_1, user_id, "time", value_2, value_3, value_4, user_id_1, time_1, value_2_1, value_3_1, value_4_1) JOIN (SELECT intermediate_result.value_1, intermediate_result.random FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value_1 integer, random double precision)) u3 USING (value_1)) ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns @@ -667,7 +642,6 @@ SELECT true AS valid FROM explain_json_2($$ FROM (SELECT * FROM users_table u1 JOIN users_table u2 using(value_1)) a JOIN (SELECT value_1, random() FROM users_table) as u3 USING (value_1); $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT u1.value_1, u1.user_id, u1."time", u1.value_2, u1.value_3, u1.value_4, u2.user_id, u2."time", u2.value_2, u2.value_3, u2.value_4 FROM (public.users_table u1 JOIN public.users_table u2 USING (value_1)) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT intermediate_result.value_1, intermediate_result.user_id, intermediate_result."time", intermediate_result.value_2, intermediate_result.value_3, intermediate_result.value_4, intermediate_result.user_id_1 AS user_id, intermediate_result.time_1 AS "time", intermediate_result.value_2_1 AS value_2, intermediate_result.value_3_1 AS value_3, intermediate_result.value_4_1 AS value_4 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value_1 integer, user_id integer, "time" timestamp without time zone, value_2 integer, value_3 double precision, value_4 bigint, user_id_1 integer, time_1 timestamp without time zone, value_2_1 integer, value_3_1 double precision, value_4_1 bigint)) a(value_1, user_id, "time", value_2, value_3, value_4, user_id_1, time_1, value_2_1, value_3_1, value_4_1) JOIN (SELECT users_table.value_1, random() AS random FROM public.users_table) u3 USING (value_1)) valid @@ -687,7 +661,6 @@ SELECT true AS valid FROM explain_json_2($$ events_table using (value_2); $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT value_2, random() AS random FROM public.users_table DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT intermediate_result.value_2, intermediate_result.random FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value_2 integer, random double precision)) u1 JOIN public.events_table USING (value_2)) valid @@ -706,7 +679,6 @@ SELECT true AS valid FROM explain_json_2($$ (SELECT value_2, random() FROM users_table) as u2 USING(value_2); $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT value_2, random() AS random FROM public.users_table DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT users_table.value_2, random() AS random FROM public.users_table) u1 LEFT JOIN (SELECT intermediate_result.value_2, intermediate_result.random FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value_2 integer, random double precision)) u2 USING (value_2)) valid @@ -725,7 +697,6 @@ SELECT true AS valid FROM explain_json_2($$ (SELECT value_2, random() FROM users_table) as u2 USING(value_2); $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT value_2, random() AS random FROM public.users_table DEBUG: recursively planning left side of the right join since the outer side is a recurring rel DEBUG: recursively planning the distributed subquery since it is part of a distributed join node that is outer joined with a recurring rel @@ -752,7 +723,6 @@ SELECT true AS valid FROM explain_json_2($$ (SELECT value_1 FROM users_table) as foo ON (a.user_id = foo.value_1) ); $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT user_id FROM public.users_table DEBUG: generating subplan XXX_2 for subquery SELECT user_id FROM public.users_table DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer) UNION SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer) @@ -777,7 +747,6 @@ SELECT true AS valid FROM explain_json_2($$ users_table as foo ON (a.user_id = foo.value_1) ); $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT user_id FROM public.users_table DEBUG: generating subplan XXX_2 for subquery SELECT user_id FROM public.users_table DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer) UNION SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer) @@ -811,7 +780,6 @@ SELECT true AS valid FROM explain_json_2($$ ON(foo.user_id = bar.value_1) ); $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT value_1 FROM public.users_table DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT foo.user_id, a.user_id, bar.value_1 FROM (((SELECT users_table.user_id FROM public.users_table) foo JOIN (SELECT users_table.user_id FROM public.users_table WHERE (users_table.user_id OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4])) UNION SELECT users_table.user_id FROM public.users_table WHERE (users_table.user_id OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8]))) a ON ((a.user_id OPERATOR(pg_catalog.=) foo.user_id))) JOIN (SELECT intermediate_result.value_1 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value_1 integer)) bar ON ((foo.user_id OPERATOR(pg_catalog.=) bar.value_1))) valid @@ -851,7 +819,6 @@ SELECT true AS valid FROM explain_json_2($$ WHERE non_colocated_subquery.value_2 != non_colocated_subquery_2.cnt $$); -DEBUG: function does not have co-located tables DEBUG: CTE non_colocated_subquery is going to be inlined via distributed planning DEBUG: CTE non_colocated_subquery_2 is going to be inlined via distributed planning DEBUG: generating subplan XXX_1 for subquery SELECT users_table.value_2 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8]))) @@ -876,7 +843,6 @@ SELECT true AS valid FROM explain_json_2($$ AND foo.value_2 = baz.value_2 $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT users_table_local.value_2 FROM non_colocated_subquery.users_table_local, non_colocated_subquery.events_table_local WHERE ((users_table_local.user_id OPERATOR(pg_catalog.=) events_table_local.user_id) AND (events_table_local.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[5, 6, 7, 8]))) DEBUG: generating subplan XXX_2 for subquery SELECT users_table.value_2 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[9, 10, 11, 12]))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT users_table.value_2 FROM public.users_table, public.events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) events_table.user_id) AND (events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4])))) foo, (SELECT intermediate_result.value_2 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value_2 integer)) bar, (SELECT intermediate_result.value_2 FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(value_2 integer)) baz WHERE ((foo.value_2 OPERATOR(pg_catalog.=) bar.value_2) AND (foo.value_2 OPERATOR(pg_catalog.=) baz.value_2)) @@ -912,7 +878,6 @@ SELECT true AS valid FROM explain_json_2($$ AND foo.user_id IN (SELECT users_table.user_id FROM users_table, events_table WHERE users_table.user_id = events_table.user_id AND event_type IN (1,2)) $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT value_1, value_2 FROM public.users_table DEBUG: generating subplan XXX_2 for subquery SELECT value_1 FROM public.users_table WHERE (value_2 OPERATOR(pg_catalog.<) 1) DEBUG: generating subplan XXX_3 for subquery SELECT value_2 FROM public.users_table WHERE (value_1 OPERATOR(pg_catalog.<) 2) @@ -935,7 +900,6 @@ SELECT true AS valid FROM explain_json_2($$ users_table_ref.user_id = foo.user_id AND foo.user_id = bar.value_2; $$); -DEBUG: function does not have co-located tables DEBUG: generating subplan XXX_1 for subquery SELECT user_id, value_2 FROM public.events_table DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM public.users_reference_table users_table_ref, (SELECT users_table.user_id FROM public.users_table) foo, (SELECT intermediate_result.user_id, intermediate_result.value_2 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_2 integer)) bar WHERE ((users_table_ref.user_id OPERATOR(pg_catalog.=) foo.user_id) AND (foo.user_id OPERATOR(pg_catalog.=) bar.value_2)) valid @@ -984,7 +948,6 @@ JOIN LATERAL WHERE user_id = users_table.user_id) AS bar LEFT JOIN users_table u2 ON u2.user_id = bar.value_2) AS foo ON TRUE $$); -DEBUG: function does not have co-located tables DEBUG: Router planner cannot handle multi-shard select queries DEBUG: skipping recursive planning for the subquery since it contains references to outer queries DEBUG: skipping recursive planning for the subquery since it contains references to outer queries diff --git a/src/test/regress/expected/pg12.out b/src/test/regress/expected/pg12.out index e8ebe3f3f..8999038ec 100644 --- a/src/test/regress/expected/pg12.out +++ b/src/test/regress/expected/pg12.out @@ -404,13 +404,6 @@ where val = 'asdf'; 3 (1 row) -SELECT 1 FROM citus_add_node('localhost', :master_port, groupId => 0); -NOTICE: localhost:xxxxx is the coordinator and already contains metadata, skipping syncing the metadata - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - BEGIN; CREATE TABLE generated_stored_col_test (x int, y int generated always as (x+1) stored); SELECT citus_add_local_table_to_metadata('generated_stored_col_test'); @@ -639,12 +632,6 @@ NOTICE: renaming the new table to test_pg12.generated_stored_ref (4 rows) ROLLBACK; -SELECT citus_remove_node('localhost', :master_port); - citus_remove_node ---------------------------------------------------------------------- - -(1 row) - CREATE TABLE superuser_columnar_table (a int) USING columnar; CREATE USER read_access; SET ROLE read_access; diff --git a/src/test/regress/expected/pg14.out b/src/test/regress/expected/pg14.out index ae5fe8bdc..8483a2891 100644 --- a/src/test/regress/expected/pg14.out +++ b/src/test/regress/expected/pg14.out @@ -1,10 +1,3 @@ -SHOW server_version \gset -SELECT substring(:'server_version', '\d+')::int >= 14 AS server_version_ge_14 -\gset -\if :server_version_ge_14 -\else -\q -\endif create schema pg14; set search_path to pg14; SET citus.shard_replication_factor TO 1; @@ -1332,12 +1325,6 @@ set client_min_messages to error; drop schema pg14 cascade; create schema pg14; set search_path to pg14; -select 1 from citus_add_node('localhost',:master_port,groupid=>0); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - -- test adding foreign table to metadata with the guc -- will test truncating foreign tables later CREATE TABLE foreign_table_test (id integer NOT NULL, data text, a bigserial); @@ -1505,9 +1492,3 @@ set client_min_messages to error; drop extension postgres_fdw cascade; drop schema pg14 cascade; reset client_min_messages; -select 1 from citus_remove_node('localhost',:master_port); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - diff --git a/src/test/regress/expected/pg14_0.out b/src/test/regress/expected/pg14_0.out deleted file mode 100644 index cff095489..000000000 --- a/src/test/regress/expected/pg14_0.out +++ /dev/null @@ -1,6 +0,0 @@ -SHOW server_version \gset -SELECT substring(:'server_version', '\d+')::int >= 14 AS server_version_ge_14 -\gset -\if :server_version_ge_14 -\else -\q diff --git a/src/test/regress/expected/pg15.out b/src/test/regress/expected/pg15.out index 7fc102dbb..667305225 100644 --- a/src/test/regress/expected/pg15.out +++ b/src/test/regress/expected/pg15.out @@ -218,6 +218,9 @@ BEGIN; NOTICE: drop cascades to 2 other objects DETAIL: drop cascades to column col_3 of table generated_stored_ref drop cascades to column col_5 of table generated_stored_ref +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to column col_3 of table generated_stored_ref_960016 +drop cascades to column col_5 of table generated_stored_ref_960016 ALTER TABLE generated_stored_ref DROP COLUMN col_4; -- show that undistribute_table works fine SELECT undistribute_table('generated_stored_ref'); @@ -269,15 +272,6 @@ CREATE TABLE tbl2 -- on local tables works fine MERGE INTO tbl1 USING tbl2 ON (true) WHEN MATCHED THEN DELETE; --- add coordinator node as a worker -SET client_min_messages to ERROR; -SELECT 1 FROM master_add_node('localhost', :master_port, groupId => 0); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - -RESET client_min_messages; -- one table is Citus local table, fails SELECT citus_add_local_table_to_metadata('tbl1'); citus_add_local_table_to_metadata @@ -306,7 +300,7 @@ SELECT citus_add_local_table_to_metadata('tbl2'); MERGE INTO tbl1 USING tbl2 ON (true) WHEN MATCHED THEN DELETE; --- one table is reference, the other local, not supported +-- source table is reference, the target is local, supported SELECT create_reference_table('tbl2'); create_reference_table --------------------------------------------------------------------- @@ -315,8 +309,7 @@ SELECT create_reference_table('tbl2'); MERGE INTO tbl1 USING tbl2 ON (true) WHEN MATCHED THEN DELETE; -ERROR: MERGE command is not supported on reference tables yet --- now, both are reference, still not supported +-- now, both are reference, not supported SELECT create_reference_table('tbl1'); create_reference_table --------------------------------------------------------------------- @@ -325,7 +318,7 @@ SELECT create_reference_table('tbl1'); MERGE INTO tbl1 USING tbl2 ON (true) WHEN MATCHED THEN DELETE; -ERROR: MERGE command is not supported on reference tables yet +ERROR: Reference table as target is not allowed in MERGE command -- now, both distributed, not works SELECT undistribute_table('tbl1'); NOTICE: creating a new table for pg15.tbl1 @@ -399,12 +392,6 @@ SET search_path TO pg15; SET client_min_messages to ERROR; DROP TABLE FKTABLE_local, PKTABLE_local; RESET client_min_messages; -SELECT 1 FROM citus_remove_node('localhost', :master_port); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - SELECT create_distributed_table('tbl1', 'x'); create_distributed_table --------------------------------------------------------------------- @@ -419,14 +406,16 @@ SELECT create_distributed_table('tbl2', 'x'); MERGE INTO tbl1 USING tbl2 ON (true) WHEN MATCHED THEN DELETE; -ERROR: MERGE command is only supported when all distributed tables are co-located and joined on their distribution columns --- also, not inside subqueries & ctes +ERROR: The required join operation is missing between the target's distribution column and any expression originating from the source. The issue may arise from either a non-equi-join or a mismatch in the datatypes of the columns being joined. +DETAIL: Without a equi-join condition on the target's distribution column, the source rows cannot be efficiently redistributed, and the NOT-MATCHED condition cannot be evaluated unambiguously. This can result in incorrect or unexpected results when attempting to merge tables in a distributed setting +-- also, inside subqueries & ctes WITH targq AS ( SELECT * FROM tbl2 ) MERGE INTO tbl1 USING targq ON (true) WHEN MATCHED THEN DELETE; -ERROR: MERGE command is only supported when all distributed tables are co-located and joined on their distribution columns +ERROR: The required join operation is missing between the target's distribution column and any expression originating from the source. The issue may arise from either a non-equi-join or a mismatch in the datatypes of the columns being joined. +DETAIL: Without a equi-join condition on the target's distribution column, the source rows cannot be efficiently redistributed, and the NOT-MATCHED condition cannot be evaluated unambiguously. This can result in incorrect or unexpected results when attempting to merge tables in a distributed setting WITH foo AS ( MERGE INTO tbl1 USING tbl2 ON (true) WHEN MATCHED THEN DELETE @@ -442,7 +431,8 @@ USING tbl2 ON (true) WHEN MATCHED THEN DO NOTHING; -ERROR: MERGE command is only supported when all distributed tables are co-located and joined on their distribution columns +ERROR: The required join operation is missing between the target's distribution column and any expression originating from the source. The issue may arise from either a non-equi-join or a mismatch in the datatypes of the columns being joined. +DETAIL: Without a equi-join condition on the target's distribution column, the source rows cannot be efficiently redistributed, and the NOT-MATCHED condition cannot be evaluated unambiguously. This can result in incorrect or unexpected results when attempting to merge tables in a distributed setting MERGE INTO tbl1 t USING tbl2 ON (true) @@ -881,8 +871,8 @@ SELECT create_reference_table('FKTABLE'); SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'fktable'::regclass::oid ORDER BY oid; pg_get_constraintdef --------------------------------------------------------------------- - FOREIGN KEY (tid, fk_id_del_set_null) REFERENCES pktable(tid, id) ON DELETE SET NULL (fk_id_del_set_null) FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES pktable(tid, id) ON DELETE SET DEFAULT (fk_id_del_set_default) + FOREIGN KEY (tid, fk_id_del_set_null) REFERENCES pktable(tid, id) ON DELETE SET NULL (fk_id_del_set_null) (2 rows) \c - - - :worker_1_port @@ -1275,6 +1265,7 @@ SELECT create_reference_table('set_on_default_test_referenced'); (1 row) +-- should error since col_3 defaults to a sequence CREATE TABLE set_on_default_test_referencing( col_1 int, col_2 int, col_3 serial, col_4 int, FOREIGN KEY(col_1, col_3) @@ -1282,10 +1273,7 @@ CREATE TABLE set_on_default_test_referencing( ON DELETE SET DEFAULT (col_1) ON UPDATE SET DEFAULT ); --- should error since col_3 defaults to a sequence -SELECT create_reference_table('set_on_default_test_referencing'); ERROR: cannot create foreign key constraint since Citus does not support ON DELETE / UPDATE SET DEFAULT actions on the columns that default to sequences -DROP TABLE set_on_default_test_referencing; CREATE TABLE set_on_default_test_referencing( col_1 int, col_2 int, col_3 serial, col_4 int, FOREIGN KEY(col_1, col_3) @@ -1448,12 +1436,6 @@ NOTICE: renaming the new table to pg15.foreign_table_test (1 row) -SELECT 1 FROM citus_remove_node('localhost', :master_port); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - DROP SERVER foreign_server CASCADE; NOTICE: drop cascades to 2 other objects -- PG15 now supports specifying oid on CREATE DATABASE diff --git a/src/test/regress/expected/pgmerge.out b/src/test/regress/expected/pgmerge.out index 6bdb7f771..895bf0680 100644 --- a/src/test/regress/expected/pgmerge.out +++ b/src/test/regress/expected/pgmerge.out @@ -15,13 +15,14 @@ SET search_path TO pgmerge_schema; SET citus.use_citus_managed_tables to true; \set SHOW_CONTEXT errors SET citus.next_shard_id TO 4001000; +SET client_min_messages = warning; SELECT 1 FROM master_add_node('localhost', :master_port, groupid => 0); -NOTICE: localhost:xxxxx is the coordinator and already contains metadata, skipping syncing the metadata ?column? --------------------------------------------------------------------- 1 (1 row) +RESET client_min_messages; CREATE USER regress_merge_privs; CREATE USER regress_merge_no_privs; DROP TABLE IF EXISTS target; @@ -2133,9 +2134,3 @@ drop cascades to table source2 drop cascades to function merge_trigfunc() DROP USER regress_merge_privs; DROP USER regress_merge_no_privs; -SELECT 1 FROM master_remove_node('localhost', :master_port); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - diff --git a/src/test/regress/expected/prepared_statements_4.out b/src/test/regress/expected/prepared_statements_4.out index 0dba296e8..4b81ec260 100644 --- a/src/test/regress/expected/prepared_statements_4.out +++ b/src/test/regress/expected/prepared_statements_4.out @@ -50,3 +50,307 @@ SELECT count(distinct ingest_time) FROM http_request WHERE site_id = 1; 8 (1 row) +-- Standard planner converted text and varchar casts to cstring in some cases +-- We make sure we convert it back to text when parsing the expression +INSERT INTO test VALUES ('2022-02-02', 0); +INSERT INTO test VALUES ('2022-01-01', 1); +INSERT INTO test VALUES ('2021-01-01', 2); +-- try different planners +PREPARE test_statement_regular(text) AS +SELECT user_id FROM test WHERE t >= $1::timestamp ORDER BY user_id; +EXECUTE test_statement_regular('2022-01-01'); + user_id +--------------------------------------------------------------------- + 0 + 1 +(2 rows) + +EXECUTE test_statement_regular('2022-01-01'); + user_id +--------------------------------------------------------------------- + 0 + 1 +(2 rows) + +EXECUTE test_statement_regular('2022-01-01'); + user_id +--------------------------------------------------------------------- + 0 + 1 +(2 rows) + +EXECUTE test_statement_regular('2022-01-01'); + user_id +--------------------------------------------------------------------- + 0 + 1 +(2 rows) + +EXECUTE test_statement_regular('2022-01-01'); + user_id +--------------------------------------------------------------------- + 0 + 1 +(2 rows) + +EXECUTE test_statement_regular('2022-01-01'); + user_id +--------------------------------------------------------------------- + 0 + 1 +(2 rows) + +EXECUTE test_statement_regular('2022-01-01'); + user_id +--------------------------------------------------------------------- + 0 + 1 +(2 rows) + +EXECUTE test_statement_regular('2022-01-01'); + user_id +--------------------------------------------------------------------- + 0 + 1 +(2 rows) + +PREPARE test_statement_router(int, text) AS +SELECT user_id FROM test WHERE user_id = $1 AND t >= $2::timestamp ORDER BY user_id; +EXECUTE test_statement_router(1, '2022-01-01'); + user_id +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_router(1, '2022-01-01'); + user_id +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_router(1, '2022-01-01'); + user_id +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_router(1, '2022-01-01'); + user_id +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_router(1, '2022-01-01'); + user_id +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_router(1, '2022-01-01'); + user_id +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_router(1, '2022-01-01'); + user_id +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_router(1, '2022-01-01'); + user_id +--------------------------------------------------------------------- + 1 +(1 row) + +PREPARE test_statement_repartition(int, text) AS +SELECT count(*) FROM test t1 JOIN test t2 USING (t) WHERE t1.user_id = $1 AND t >= $2::timestamp; +EXECUTE test_statement_repartition(1, '2022-01-01'); + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_repartition(1, '2022-01-01'); + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_repartition(1, '2022-01-01'); + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_repartition(1, '2022-01-01'); + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_repartition(1, '2022-01-01'); + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_repartition(1, '2022-01-01'); + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_repartition(1, '2022-01-01'); + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_repartition(1, '2022-01-01'); + count +--------------------------------------------------------------------- + 1 +(1 row) + +PREPARE test_statement_cte(text, text) AS +WITH cte_1 AS MATERIALIZED (SELECT user_id, t FROM test WHERE t >= $1::timestamp ORDER BY user_id) +SELECT user_id FROM cte_1 WHERE t <= $2::timestamp; +EXECUTE test_statement_cte('2022-01-01', '2022-01-01'); + user_id +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_cte('2022-01-01', '2022-01-01'); + user_id +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_cte('2022-01-01', '2022-01-01'); + user_id +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_cte('2022-01-01', '2022-01-01'); + user_id +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_cte('2022-01-01', '2022-01-01'); + user_id +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_cte('2022-01-01', '2022-01-01'); + user_id +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_cte('2022-01-01', '2022-01-01'); + user_id +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE test_statement_cte('2022-01-01', '2022-01-01'); + user_id +--------------------------------------------------------------------- + 1 +(1 row) + +PREPARE test_statement_insert(int, text) AS +INSERT INTO test VALUES ($2::timestamp, $1); +EXECUTE test_statement_insert(3, '2022-03-03'); +EXECUTE test_statement_insert(4, '2022-04-04'); +EXECUTE test_statement_insert(5, '2022-05-05'); +EXECUTE test_statement_insert(6, '2022-06-06'); +EXECUTE test_statement_insert(7, '2022-07-07'); +EXECUTE test_statement_insert(8, '2022-08-08'); +EXECUTE test_statement_insert(9, '2022-09-09'); +EXECUTE test_statement_insert(10, '2022-10-10'); +SELECT count(*) FROM test; + count +--------------------------------------------------------------------- + 11 +(1 row) + +EXECUTE test_statement_regular('2022-01-01'); + user_id +--------------------------------------------------------------------- + 0 + 1 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 +(10 rows) + +PREPARE test_statement_null(text) AS +SELECT user_id , $1::timestamp FROM test ORDER BY user_id LIMIT 2; +EXECUTE test_statement_null(NULL); + user_id | timestamp +--------------------------------------------------------------------- + 0 | + 1 | +(2 rows) + +EXECUTE test_statement_null(NULL); + user_id | timestamp +--------------------------------------------------------------------- + 0 | + 1 | +(2 rows) + +EXECUTE test_statement_null(NULL); + user_id | timestamp +--------------------------------------------------------------------- + 0 | + 1 | +(2 rows) + +EXECUTE test_statement_null(NULL); + user_id | timestamp +--------------------------------------------------------------------- + 0 | + 1 | +(2 rows) + +EXECUTE test_statement_null(NULL); + user_id | timestamp +--------------------------------------------------------------------- + 0 | + 1 | +(2 rows) + +EXECUTE test_statement_null(NULL); + user_id | timestamp +--------------------------------------------------------------------- + 0 | + 1 | +(2 rows) + +EXECUTE test_statement_null(NULL); + user_id | timestamp +--------------------------------------------------------------------- + 0 | + 1 | +(2 rows) + +EXECUTE test_statement_null(NULL); + user_id | timestamp +--------------------------------------------------------------------- + 0 | + 1 | +(2 rows) + diff --git a/src/test/regress/expected/prepared_statements_create_load.out b/src/test/regress/expected/prepared_statements_create_load.out index d1817f39f..5e3740abe 100644 --- a/src/test/regress/expected/prepared_statements_create_load.out +++ b/src/test/regress/expected/prepared_statements_create_load.out @@ -91,3 +91,15 @@ SELECT create_distributed_table('http_request', 'site_id'); (1 row) +-- Standard planner converted text and varchar casts to cstring in some cases +-- We make sure we convert it back to text when parsing the expression +-- https://github.com/citusdata/citus/issues/6061 +-- https://github.com/citusdata/citus/issues/5646 +-- https://github.com/citusdata/citus/issues/5033 +CREATE TABLE test(t timestamp, user_id int); +SELECT create_distributed_table('test', 'user_id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + diff --git a/src/test/regress/expected/propagate_foreign_servers.out b/src/test/regress/expected/propagate_foreign_servers.out index 551d1dde7..076973da3 100644 --- a/src/test/regress/expected/propagate_foreign_servers.out +++ b/src/test/regress/expected/propagate_foreign_servers.out @@ -30,13 +30,6 @@ CREATE FOREIGN TABLE foreign_table ( ) SERVER foreign_server_dependent_schema OPTIONS (schema_name 'test_dependent_schema', table_name 'foreign_table_test'); -SELECT 1 FROM citus_add_node('localhost', :master_port, groupId=>0); -NOTICE: localhost:xxxxx is the coordinator and already contains metadata, skipping syncing the metadata - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - -- verify that the aggregate is propagated to the new node SELECT run_command_on_workers($$select aggfnoid from pg_aggregate where aggfnoid::text like '%propagate_foreign_server.array_agg%';$$); run_command_on_workers diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out index a267cbe71..702d23f1f 100644 --- a/src/test/regress/expected/publication.out +++ b/src/test/regress/expected/publication.out @@ -2,13 +2,6 @@ CREATE SCHEMA publication; CREATE SCHEMA "publication-1"; SET search_path TO publication; SET citus.shard_replication_factor TO 1; --- for citus_add_local_table_to_metadata / create_distributed_table_concurrently -SELECT citus_set_coordinator_host('localhost', :master_port); - citus_set_coordinator_host ---------------------------------------------------------------------- - -(1 row) - CREATE OR REPLACE FUNCTION activate_node_snapshot() RETURNS text[] LANGUAGE C STRICT @@ -97,13 +90,18 @@ SELECT DISTINCT c FROM ( SELECT worker_create_or_replace_object('CREATE PUBLICATION pubtables_orig WITH (publish_via_partition_root = ''false'', publish = ''insert, truncate'')'); (1 row) --- distribute a table, creating a mixed publication +-- distribute a table and create a tenant schema, creating a mixed publication SELECT create_distributed_table('test','x', colocate_with := 'none'); create_distributed_table --------------------------------------------------------------------- (1 row) +SET citus.enable_schema_based_sharding TO ON; +CREATE SCHEMA citus_schema_1; +CREATE TABLE citus_schema_1.test (x int primary key, y int, "column-1" int, doc xml); +SET citus.enable_schema_based_sharding TO OFF; +ALTER PUBLICATION pubtables_orig ADD TABLE citus_schema_1.test; -- some generic operations ALTER PUBLICATION pubtables_orig RENAME TO pubtables; ALTER PUBLICATION pubtables SET (publish = 'insert, update, delete'); @@ -115,7 +113,11 @@ ERROR: relation "notexist" does not exist -- operations with a distributed table ALTER PUBLICATION pubtables DROP TABLE test; ALTER PUBLICATION pubtables ADD TABLE test; -ALTER PUBLICATION pubtables SET TABLE test, "test-pubs", "publication-1"."test-pubs"; +ALTER PUBLICATION pubtables SET TABLE test, "test-pubs", "publication-1"."test-pubs", citus_schema_1.test; +-- operations with a tenant schema table +ALTER PUBLICATION pubtables DROP TABLE citus_schema_1.test; +ALTER PUBLICATION pubtables ADD TABLE citus_schema_1.test; +ALTER PUBLICATION pubtables SET TABLE test, "test-pubs", "publication-1"."test-pubs", citus_schema_1.test; -- operations with a local table in a mixed publication ALTER PUBLICATION pubtables DROP TABLE "test-pubs"; ALTER PUBLICATION pubtables ADD TABLE "test-pubs"; @@ -131,9 +133,9 @@ SELECT DISTINCT c FROM ( FROM run_command_on_workers($$ SELECT array_agg(c) FROM (SELECT c FROM unnest(activate_node_snapshot()) c WHERE c LIKE '%CREATE PUBLICATION%' AND c LIKE '%pubtables%' ORDER BY 1) s$$) ORDER BY c) s; - c + c --------------------------------------------------------------------- - SELECT worker_create_or_replace_object('CREATE PUBLICATION pubtables FOR TABLE publication.test, TABLE publication."test-pubs" WITH (publish_via_partition_root = ''false'', publish = ''insert, update, delete'')'); + SELECT worker_create_or_replace_object('CREATE PUBLICATION pubtables FOR TABLE publication.test, TABLE citus_schema_1.test, TABLE publication."test-pubs" WITH (publish_via_partition_root = ''false'', publish = ''insert, update, delete'')'); (1 row) -- operations with a strangely named distributed table in a mixed publication @@ -141,7 +143,7 @@ ALTER PUBLICATION pubtables DROP TABLE "test-pubs"; ALTER PUBLICATION pubtables ADD TABLE "test-pubs"; -- create a publication with distributed and local tables DROP PUBLICATION pubtables; -CREATE PUBLICATION pubtables FOR TABLE test, "test-pubs", "publication-1"."test-pubs"; +CREATE PUBLICATION pubtables FOR TABLE test, "test-pubs", "publication-1"."test-pubs", citus_schema_1.test; -- change distributed tables SELECT alter_distributed_table('test', shard_count := 5, cascade_to_colocated := true); NOTICE: creating a new table for publication.test @@ -201,9 +203,9 @@ SELECT DISTINCT c FROM ( FROM run_command_on_workers($$ SELECT array_agg(c) FROM (SELECT c FROM unnest(activate_node_snapshot()) c WHERE c LIKE '%CREATE PUBLICATION%' AND c LIKE '%pubtables%' ORDER BY 1) s$$) ORDER BY c) s; - c + c --------------------------------------------------------------------- - SELECT worker_create_or_replace_object('CREATE PUBLICATION pubtables FOR TABLE publication.test, TABLE publication."test-pubs" WITH (publish_via_partition_root = ''false'', publish = ''insert, update, delete, truncate'')'); + SELECT worker_create_or_replace_object('CREATE PUBLICATION pubtables FOR TABLE citus_schema_1.test, TABLE publication.test, TABLE publication."test-pubs" WITH (publish_via_partition_root = ''false'', publish = ''insert, update, delete, truncate'')'); (1 row) -- partitioned table @@ -264,11 +266,11 @@ SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 SET client_min_messages TO ERROR; DROP SCHEMA publication CASCADE; DROP SCHEMA "publication-1" CASCADE; -SELECT citus_remove_node('localhost', :master_port); +DROP SCHEMA citus_schema_1 CASCADE; \q \endif -- recreate a mixed publication -CREATE PUBLICATION pubtables FOR TABLE test, "publication-1"."test-pubs"; +CREATE PUBLICATION pubtables FOR TABLE test, "publication-1"."test-pubs", citus_schema_1.test; -- operations on an existing distributed table ALTER PUBLICATION pubtables DROP TABLE test; ALTER PUBLICATION pubtables ADD TABLE test (y); @@ -285,6 +287,22 @@ SELECT DISTINCT c FROM ( SELECT worker_create_or_replace_object('CREATE PUBLICATION pubtables FOR TABLE publication.test WHERE (CASE test.x WHEN 5 THEN true ELSE false END) WITH (publish_via_partition_root = ''false'', publish = ''insert, update, delete, truncate'')'); (1 row) +-- operations on an existing tenant schema table +ALTER PUBLICATION pubtables ADD TABLE citus_schema_1.test (y); +ALTER PUBLICATION pubtables DROP TABLE citus_schema_1.test; +ALTER PUBLICATION pubtables SET TABLE citus_schema_1.test WHERE (doc IS DOCUMENT); +ALTER PUBLICATION pubtables SET TABLE citus_schema_1.test WHERE (xmlexists('//foo[text() = ''bar'']' PASSING BY VALUE doc)); +ALTER PUBLICATION pubtables SET TABLE citus_schema_1.test WHERE (CASE x WHEN 5 THEN true ELSE false END); +SELECT DISTINCT c FROM ( + SELECT unnest(result::text[]) c + FROM run_command_on_workers($$ + SELECT array_agg(c) FROM (SELECT c FROM unnest(activate_node_snapshot()) c WHERE c LIKE '%CREATE PUBLICATION%' AND c LIKE '%pubtables%' ORDER BY 1) s$$) + ORDER BY c) s; + c +--------------------------------------------------------------------- + SELECT worker_create_or_replace_object('CREATE PUBLICATION pubtables FOR TABLE citus_schema_1.test WHERE (CASE test.x WHEN 5 THEN true ELSE false END) WITH (publish_via_partition_root = ''false'', publish = ''insert, update, delete, truncate'')'); +(1 row) + ALTER PUBLICATION pubtables SET TABLE test ("column-1", x) WHERE (x > "column-1"), "publication-1"."test-pubs"; -- operations on a local table ALTER PUBLICATION pubtables DROP TABLE "publication-1"."test-pubs"; @@ -371,9 +389,4 @@ DROP PUBLICATION pubpartitioned; SET client_min_messages TO ERROR; DROP SCHEMA publication CASCADE; DROP SCHEMA "publication-1" CASCADE; -SELECT citus_remove_node('localhost', :master_port); - citus_remove_node ---------------------------------------------------------------------- - -(1 row) - +DROP SCHEMA citus_schema_1 CASCADE; diff --git a/src/test/regress/expected/publication_0.out b/src/test/regress/expected/publication_0.out index 617950a76..14fa94d17 100644 --- a/src/test/regress/expected/publication_0.out +++ b/src/test/regress/expected/publication_0.out @@ -2,13 +2,6 @@ CREATE SCHEMA publication; CREATE SCHEMA "publication-1"; SET search_path TO publication; SET citus.shard_replication_factor TO 1; --- for citus_add_local_table_to_metadata / create_distributed_table_concurrently -SELECT citus_set_coordinator_host('localhost', :master_port); - citus_set_coordinator_host ---------------------------------------------------------------------- - -(1 row) - CREATE OR REPLACE FUNCTION activate_node_snapshot() RETURNS text[] LANGUAGE C STRICT @@ -97,13 +90,18 @@ SELECT DISTINCT c FROM ( SELECT worker_create_or_replace_object('CREATE PUBLICATION pubtables_orig WITH (publish_via_partition_root = ''false'', publish = ''insert, truncate'')'); (1 row) --- distribute a table, creating a mixed publication +-- distribute a table and create a tenant schema, creating a mixed publication SELECT create_distributed_table('test','x', colocate_with := 'none'); create_distributed_table --------------------------------------------------------------------- (1 row) +SET citus.enable_schema_based_sharding TO ON; +CREATE SCHEMA citus_schema_1; +CREATE TABLE citus_schema_1.test (x int primary key, y int, "column-1" int, doc xml); +SET citus.enable_schema_based_sharding TO OFF; +ALTER PUBLICATION pubtables_orig ADD TABLE citus_schema_1.test; -- some generic operations ALTER PUBLICATION pubtables_orig RENAME TO pubtables; ALTER PUBLICATION pubtables SET (publish = 'insert, update, delete'); @@ -115,7 +113,11 @@ ERROR: relation "notexist" does not exist -- operations with a distributed table ALTER PUBLICATION pubtables DROP TABLE test; ALTER PUBLICATION pubtables ADD TABLE test; -ALTER PUBLICATION pubtables SET TABLE test, "test-pubs", "publication-1"."test-pubs"; +ALTER PUBLICATION pubtables SET TABLE test, "test-pubs", "publication-1"."test-pubs", citus_schema_1.test; +-- operations with a tenant schema table +ALTER PUBLICATION pubtables DROP TABLE citus_schema_1.test; +ALTER PUBLICATION pubtables ADD TABLE citus_schema_1.test; +ALTER PUBLICATION pubtables SET TABLE test, "test-pubs", "publication-1"."test-pubs", citus_schema_1.test; -- operations with a local table in a mixed publication ALTER PUBLICATION pubtables DROP TABLE "test-pubs"; ALTER PUBLICATION pubtables ADD TABLE "test-pubs"; @@ -131,9 +133,9 @@ SELECT DISTINCT c FROM ( FROM run_command_on_workers($$ SELECT array_agg(c) FROM (SELECT c FROM unnest(activate_node_snapshot()) c WHERE c LIKE '%CREATE PUBLICATION%' AND c LIKE '%pubtables%' ORDER BY 1) s$$) ORDER BY c) s; - c + c --------------------------------------------------------------------- - SELECT worker_create_or_replace_object('CREATE PUBLICATION pubtables FOR TABLE publication.test, publication."test-pubs" WITH (publish_via_partition_root = ''false'', publish = ''insert, update, delete'')'); + SELECT worker_create_or_replace_object('CREATE PUBLICATION pubtables FOR TABLE publication.test, citus_schema_1.test, publication."test-pubs" WITH (publish_via_partition_root = ''false'', publish = ''insert, update, delete'')'); (1 row) -- operations with a strangely named distributed table in a mixed publication @@ -141,7 +143,7 @@ ALTER PUBLICATION pubtables DROP TABLE "test-pubs"; ALTER PUBLICATION pubtables ADD TABLE "test-pubs"; -- create a publication with distributed and local tables DROP PUBLICATION pubtables; -CREATE PUBLICATION pubtables FOR TABLE test, "test-pubs", "publication-1"."test-pubs"; +CREATE PUBLICATION pubtables FOR TABLE test, "test-pubs", "publication-1"."test-pubs", citus_schema_1.test; -- change distributed tables SELECT alter_distributed_table('test', shard_count := 5, cascade_to_colocated := true); NOTICE: creating a new table for publication.test @@ -201,9 +203,9 @@ SELECT DISTINCT c FROM ( FROM run_command_on_workers($$ SELECT array_agg(c) FROM (SELECT c FROM unnest(activate_node_snapshot()) c WHERE c LIKE '%CREATE PUBLICATION%' AND c LIKE '%pubtables%' ORDER BY 1) s$$) ORDER BY c) s; - c + c --------------------------------------------------------------------- - SELECT worker_create_or_replace_object('CREATE PUBLICATION pubtables FOR TABLE publication.test, publication."test-pubs" WITH (publish_via_partition_root = ''false'', publish = ''insert, update, delete, truncate'')'); + SELECT worker_create_or_replace_object('CREATE PUBLICATION pubtables FOR TABLE citus_schema_1.test, publication.test, publication."test-pubs" WITH (publish_via_partition_root = ''false'', publish = ''insert, update, delete, truncate'')'); (1 row) -- partitioned table @@ -264,10 +266,5 @@ SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 SET client_min_messages TO ERROR; DROP SCHEMA publication CASCADE; DROP SCHEMA "publication-1" CASCADE; -SELECT citus_remove_node('localhost', :master_port); - citus_remove_node ---------------------------------------------------------------------- - -(1 row) - +DROP SCHEMA citus_schema_1 CASCADE; \q diff --git a/src/test/regress/expected/query_single_shard_table.out b/src/test/regress/expected/query_single_shard_table.out new file mode 100644 index 000000000..5716c570d --- /dev/null +++ b/src/test/regress/expected/query_single_shard_table.out @@ -0,0 +1,3370 @@ +CREATE SCHEMA query_single_shard_table; +SET search_path TO query_single_shard_table; +SET citus.next_shard_id TO 1620000; +SET citus.shard_count TO 32; +SET citus.shard_replication_factor TO 1; +SET client_min_messages TO NOTICE; +CREATE TABLE nullkey_c1_t1(a int, b int); +CREATE TABLE nullkey_c1_t2(a int, b int); +SELECT create_distributed_table('nullkey_c1_t1', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('nullkey_c1_t2', null, colocate_with=>'nullkey_c1_t1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO nullkey_c1_t1 SELECT i, i FROM generate_series(1, 8) i; +INSERT INTO nullkey_c1_t2 SELECT i, i FROM generate_series(2, 7) i; +CREATE TABLE nullkey_c2_t1(a int, b int); +CREATE TABLE nullkey_c2_t2(a int, b int); +SELECT create_distributed_table('nullkey_c2_t1', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('nullkey_c2_t2', null, colocate_with=>'nullkey_c2_t1', distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO nullkey_c2_t1 SELECT i, i FROM generate_series(2, 7) i; +INSERT INTO nullkey_c2_t2 SELECT i, i FROM generate_series(1, 8) i; +CREATE TABLE nullkey_c3_t1(a int, b int); +SELECT create_distributed_table('nullkey_c3_t1', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO nullkey_c3_t1 SELECT i, i FROM generate_series(1, 8) i; +RESET citus.shard_replication_factor; +CREATE TABLE reference_table(a int, b int); +SELECT create_reference_table('reference_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO reference_table SELECT i, i FROM generate_series(0, 5) i; +CREATE TABLE distributed_table(a int, b int); +SELECT create_distributed_table('distributed_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO distributed_table SELECT i, i FROM generate_series(3, 8) i; +CREATE TABLE citus_local_table(a int, b int); +SELECT citus_add_local_table_to_metadata('citus_local_table'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO citus_local_table SELECT i, i FROM generate_series(0, 10) i; +CREATE TABLE postgres_local_table(a int, b int); +INSERT INTO postgres_local_table SELECT i, i FROM generate_series(5, 10) i; +CREATE TABLE articles_hash ( + id bigint NOT NULL, + author_id bigint NOT NULL, + title varchar(20) NOT NULL, + word_count integer +); +INSERT INTO articles_hash VALUES ( 4, 4, 'altdorfer', 14551),( 5, 5, 'aruru', 11389), + (13, 3, 'aseyev', 2255),(15, 5, 'adversa', 3164), + (18, 8, 'assembly', 911),(19, 9, 'aubergiste', 4981), + (28, 8, 'aerophyte', 5454),(29, 9, 'amateur', 9524), + (42, 2, 'ausable', 15885),(43, 3, 'affixal', 12723), + (49, 9, 'anyone', 2681),(50, 10, 'anjanette', 19519); +SET citus.shard_replication_factor TO 1; +SELECT create_distributed_table('articles_hash', null, colocate_with=>'none'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$query_single_shard_table.articles_hash$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE raw_events_first (user_id int, time timestamp, value_1 int, value_2 int, value_3 float, value_4 bigint, UNIQUE(user_id, value_1)); +SELECT create_distributed_table('raw_events_first', null, colocate_with=>'none', distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE raw_events_second (user_id int, time timestamp, value_1 int, value_2 int, value_3 float, value_4 bigint, UNIQUE(user_id, value_1)); +SELECT create_distributed_table('raw_events_second', null, colocate_with=>'raw_events_first', distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE agg_events (user_id int, value_1_agg int, value_2_agg int, value_3_agg float, value_4_agg bigint, agg_time timestamp, UNIQUE(user_id, value_1_agg)); +SELECT create_distributed_table('agg_events', null, colocate_with=>'raw_events_first', distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE users_ref_table (user_id int); +SELECT create_reference_table('users_ref_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO raw_events_first VALUES (1, '1970-01-01', 10, 100, 1000.1, 10000), (3, '1971-01-01', 30, 300, 3000.1, 30000), + (5, '1972-01-01', 50, 500, 5000.1, 50000), (2, '1973-01-01', 20, 200, 2000.1, 20000), + (4, '1974-01-01', 40, 400, 4000.1, 40000), (6, '1975-01-01', 60, 600, 6000.1, 60000); +CREATE TABLE modify_fast_path(key int, value_1 int, value_2 text); +SELECT create_distributed_table('modify_fast_path', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE modify_fast_path_reference(key int, value_1 int, value_2 text); +SELECT create_reference_table('modify_fast_path_reference'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE bigserial_test (x int, y int, z bigserial); +SELECT create_distributed_table('bigserial_test', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +RESET citus.shard_replication_factor; +CREATE TABLE append_table (text_col text, a int); +SELECT create_distributed_table('append_table', 'a', 'append'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT master_create_empty_shard('append_table') AS shardid1 \gset +SELECT master_create_empty_shard('append_table') AS shardid2 \gset +SELECT master_create_empty_shard('append_table') AS shardid3 \gset +COPY append_table (text_col, a) FROM STDIN WITH (format 'csv', append_to_shard :shardid1); +COPY append_table (text_col, a) FROM STDIN WITH (format 'csv', append_to_shard :shardid2); +CREATE TABLE range_table(a int, b int); +SELECT create_distributed_table('range_table', 'a', 'range'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CALL public.create_range_partitioned_shards('range_table', '{"0","25"}','{"24","49"}'); +INSERT INTO range_table VALUES (0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 50); +\set users_table_data_file :abs_srcdir '/data/users_table.data' +\set events_table_data_file :abs_srcdir '/data/events_table.data' +SET citus.shard_replication_factor TO 1; +CREATE TABLE users_table (user_id int, time timestamp, value_1 int, value_2 int, value_3 float, value_4 bigint); +SELECT create_distributed_table('users_table', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +\set client_side_copy_command '\\copy users_table FROM ' :'users_table_data_file' ' WITH CSV;' +:client_side_copy_command +CREATE TABLE non_colocated_users_table (id int, value int); +SELECT create_distributed_table('non_colocated_users_table', null, colocate_with => 'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO non_colocated_users_table (id, value) VALUES(1, 2),(2, 3),(3,4); +CREATE TABLE colocated_events_table (user_id int, time timestamp, event_type int, value_2 int, value_3 float, value_4 bigint); +SELECT create_distributed_table('colocated_events_table', null, colocate_with=>'users_table'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +\set client_side_copy_command '\\copy colocated_events_table FROM ' :'events_table_data_file' ' WITH CSV;' +:client_side_copy_command +CREATE TABLE non_colocated_events_table (user_id int, time timestamp, event_type int, value_2 int, value_3 float, value_4 bigint); +SELECT create_distributed_table('non_colocated_events_table', null, colocate_with=>'non_colocated_users_table'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +\set client_side_copy_command '\\copy non_colocated_events_table FROM ' :'events_table_data_file' ' WITH CSV;' +:client_side_copy_command +CREATE TABLE users_table_local AS SELECT * FROM users_table; +CREATE TABLE colocated_users_table (id int, value int); +SELECT create_distributed_table('colocated_users_table', null, colocate_with => 'users_table'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO colocated_users_table (id, value) VALUES(1, 2),(2, 3),(3,4); +CREATE TABLE users_reference_table (like users_table including all); +SELECT create_reference_table('users_reference_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE events_reference_table (like colocated_events_table including all); +SELECT create_reference_table('events_reference_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +CREATE FUNCTION func() RETURNS TABLE (id int, value int) AS $$ + SELECT 1, 2 +$$ LANGUAGE SQL; +SET client_min_messages to DEBUG2; +-- simple insert +INSERT INTO nullkey_c1_t1 VALUES (1,2), (2,2), (3,4); +DEBUG: Creating router plan +INSERT INTO nullkey_c1_t2 VALUES (1,3), (3,4), (5,1), (6,2); +DEBUG: Creating router plan +INSERT INTO nullkey_c2_t1 VALUES (1,0), (2,5), (4,3), (5,2); +DEBUG: Creating router plan +INSERT INTO nullkey_c2_t2 VALUES (2,4), (3,2), (5,2), (7,4); +DEBUG: Creating router plan +-- simple select +SELECT * FROM nullkey_c1_t1 ORDER BY 1,2; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + a | b +--------------------------------------------------------------------- + 1 | 1 + 1 | 2 + 2 | 2 + 2 | 2 + 3 | 3 + 3 | 4 + 4 | 4 + 5 | 5 + 6 | 6 + 7 | 7 + 8 | 8 +(11 rows) + +-- for update / share +SELECT * FROM modify_fast_path WHERE key = 1 FOR UPDATE; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + key | value_1 | value_2 +--------------------------------------------------------------------- +(0 rows) + +SELECT * FROM modify_fast_path WHERE key = 1 FOR SHARE; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + key | value_1 | value_2 +--------------------------------------------------------------------- +(0 rows) + +SELECT * FROM modify_fast_path FOR UPDATE; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + key | value_1 | value_2 +--------------------------------------------------------------------- +(0 rows) + +SELECT * FROM modify_fast_path FOR SHARE; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + key | value_1 | value_2 +--------------------------------------------------------------------- +(0 rows) + +-- cartesian product with different table types +-- with other table types +SELECT COUNT(*) FROM distributed_table d1, nullkey_c1_t1; +DEBUG: router planner does not support queries that reference non-colocated distributed tables +ERROR: cannot perform distributed planning on this query +DETAIL: Cartesian products are currently unsupported +SELECT COUNT(*) FROM reference_table d1, nullkey_c1_t1; +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 66 +(1 row) + +SELECT COUNT(*) FROM citus_local_table d1, nullkey_c1_t1; +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "citus_local_table" "d1" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT NULL::integer AS "dummy-1" FROM query_single_shard_table.citus_local_table d1 WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT NULL::integer AS a, NULL::integer AS b FROM (SELECT intermediate_result."dummy-1" FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result("dummy-1" integer)) d1_1) d1, query_single_shard_table.nullkey_c1_t1 +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 121 +(1 row) + +SELECT COUNT(*) FROM postgres_local_table d1, nullkey_c1_t1; +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "postgres_local_table" "d1" to a subquery +DEBUG: generating subplan XXX_1 for subquery SELECT NULL::integer AS "dummy-1" FROM query_single_shard_table.postgres_local_table d1 WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT NULL::integer AS a, NULL::integer AS b FROM (SELECT intermediate_result."dummy-1" FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result("dummy-1" integer)) d1_1) d1, query_single_shard_table.nullkey_c1_t1 +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 66 +(1 row) + +-- with a colocated single-shard table +SELECT COUNT(*) FROM nullkey_c1_t1 d1, nullkey_c1_t2; +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 110 +(1 row) + +-- with a non-colocated single-shard table +SELECT COUNT(*) FROM nullkey_c1_t1 d1, nullkey_c2_t1; +DEBUG: router planner does not support queries that reference non-colocated distributed tables +ERROR: cannot perform distributed planning on this query +DETAIL: Cartesian products are currently unsupported +-- First, show that nullkey_c1_t1 and nullkey_c3_t1 are not colocated. +SELECT + (SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'query_single_shard_table.nullkey_c1_t1'::regclass) != + (SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'query_single_shard_table.nullkey_c3_t1'::regclass); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- Now verify that we can join them via router planner because it doesn't care +-- about whether two tables are colocated or not but physical location of shards +-- when citus.enable_non_colocated_router_query_pushdown is set to on. +SET citus.enable_non_colocated_router_query_pushdown TO ON; +SELECT COUNT(*) FROM nullkey_c1_t1 JOIN nullkey_c3_t1 USING(a); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 11 +(1 row) + +SET citus.enable_non_colocated_router_query_pushdown TO OFF; +SET citus.enable_repartition_joins TO ON; +SET client_min_messages TO DEBUG1; +SELECT COUNT(*) FROM nullkey_c1_t1 JOIN nullkey_c3_t1 USING(a); + count +--------------------------------------------------------------------- + 11 +(1 row) + +SET client_min_messages TO DEBUG2; +SET citus.enable_repartition_joins TO OFF; +RESET citus.enable_non_colocated_router_query_pushdown; +-- colocated join between single-shard tables +SELECT COUNT(*) FROM nullkey_c1_t1 JOIN nullkey_c1_t2 USING(a); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 14 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 LEFT JOIN nullkey_c1_t2 USING(a); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 15 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 FULL JOIN nullkey_c1_t2 USING(a); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 15 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +LEFT JOIN LATERAL ( + SELECT * FROM nullkey_c1_t2 t2 WHERE t2.b > t1.a +) q USING(a); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 11 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +JOIN LATERAL ( + SELECT * FROM nullkey_c1_t2 t2 WHERE t2.b > t1.a +) q USING(a); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 4 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE EXISTS ( + SELECT * FROM nullkey_c1_t2 t2 WHERE t2.b > t1.a +); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 9 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b IN ( + SELECT b+1 FROM nullkey_c1_t2 t2 WHERE t2.b = t1.a +); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 2 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b NOT IN ( + SELECT a FROM nullkey_c1_t2 t2 WHERE t2.b > t1.a +); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 7 +(1 row) + +-- non-colocated inner joins between single-shard tables +SET client_min_messages to DEBUG1; +SET citus.enable_repartition_joins TO ON; +SELECT * FROM nullkey_c1_t1 JOIN nullkey_c2_t1 USING(a) ORDER BY 1,2,3; + a | b | b +--------------------------------------------------------------------- + 1 | 1 | 0 + 1 | 2 | 0 + 2 | 2 | 2 + 2 | 2 | 2 + 2 | 2 | 5 + 2 | 2 | 5 + 3 | 3 | 3 + 3 | 4 | 3 + 4 | 4 | 3 + 4 | 4 | 4 + 5 | 5 | 2 + 5 | 5 | 5 + 6 | 6 | 6 + 7 | 7 | 7 +(14 rows) + +SELECT * FROM (SELECT * FROM nullkey_c1_t1) nullkey_c1_t1 JOIN nullkey_c2_t1 USING(a) ORDER BY 1,2,3; + a | b | b +--------------------------------------------------------------------- + 1 | 1 | 0 + 1 | 2 | 0 + 2 | 2 | 2 + 2 | 2 | 2 + 2 | 2 | 5 + 2 | 2 | 5 + 3 | 3 | 3 + 3 | 4 | 3 + 4 | 4 | 3 + 4 | 4 | 4 + 5 | 5 | 2 + 5 | 5 | 5 + 6 | 6 | 6 + 7 | 7 | 7 +(14 rows) + +SELECT * FROM nullkey_c2_t1 JOIN (SELECT * FROM nullkey_c1_t1) nullkey_c1_t1 USING(a) ORDER BY 1,2,3; + a | b | b +--------------------------------------------------------------------- + 1 | 0 | 1 + 1 | 0 | 2 + 2 | 2 | 2 + 2 | 2 | 2 + 2 | 5 | 2 + 2 | 5 | 2 + 3 | 3 | 3 + 3 | 3 | 4 + 4 | 3 | 4 + 4 | 4 | 4 + 5 | 2 | 5 + 5 | 5 | 5 + 6 | 6 | 6 + 7 | 7 | 7 +(14 rows) + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +JOIN LATERAL ( + SELECT * FROM nullkey_c2_t2 t2 WHERE t2.b > t1.a +) q USING(a); + count +--------------------------------------------------------------------- + 2 +(1 row) + +SET citus.enable_repartition_joins TO OFF; +SET client_min_messages to DEBUG2; +-- non-colocated outer joins between single-shard tables +SELECT * FROM nullkey_c1_t1 LEFT JOIN nullkey_c2_t2 USING(a) ORDER BY 1,2,3 LIMIT 4; +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: push down of limit count: 4 +ERROR: cannot push down this subquery +DETAIL: nullkey_c1_t1 and nullkey_c2_t2 are not colocated +SELECT * FROM nullkey_c1_t1 FULL JOIN nullkey_c2_t2 USING(a) ORDER BY 1,2,3 LIMIT 4; +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: push down of limit count: 4 +ERROR: cannot push down this subquery +DETAIL: nullkey_c1_t1 and nullkey_c2_t2 are not colocated +SELECT * FROM nullkey_c1_t1 t1 +LEFT JOIN LATERAL ( + SELECT * FROM nullkey_c2_t2 t2 WHERE t2.b > t1.a +) q USING(a) ORDER BY 1,2,3 OFFSET 3 LIMIT 4; +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: push down of limit count: 7 +ERROR: cannot push down this subquery +DETAIL: nullkey_c1_t1 and nullkey_c2_t2 are not colocated +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +LEFT JOIN LATERAL ( + SELECT * FROM nullkey_c2_t2 t2 WHERE t2.b > t1.a +) q USING(a); +DEBUG: router planner does not support queries that reference non-colocated distributed tables +ERROR: cannot push down this subquery +DETAIL: nullkey_c1_t1 and nullkey_c2_t2 are not colocated +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE EXISTS ( + SELECT * FROM nullkey_c2_t2 t2 WHERE t2.b > t1.a +); +DEBUG: router planner does not support queries that reference non-colocated distributed tables +ERROR: cannot push down this subquery +DETAIL: nullkey_c2_t2 and nullkey_c1_t1 are not colocated +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b IN ( + SELECT b+1 FROM nullkey_c2_t2 t2 WHERE t2.b = t1.a +); +DEBUG: found no worker with all shard placements +ERROR: cannot push down this subquery +DETAIL: nullkey_c2_t2 and nullkey_c1_t1 are not colocated +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b NOT IN ( + SELECT a FROM nullkey_c2_t2 t2 WHERE t2.b > t1.a +); +DEBUG: found no worker with all shard placements +ERROR: cannot push down this subquery +DETAIL: nullkey_c2_t2 and nullkey_c1_t1 are not colocated +-- join with a reference table +SELECT COUNT(*) FROM nullkey_c1_t1, reference_table WHERE nullkey_c1_t1.a = reference_table.a; +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 8 +(1 row) + +WITH cte_1 AS + (SELECT * FROM nullkey_c1_t1, reference_table WHERE nullkey_c1_t1.a = reference_table.a ORDER BY 1,2,3,4 FOR UPDATE) +SELECT COUNT(*) FROM cte_1; +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 8 +(1 row) + +-- join with postgres / citus local tables +SELECT * FROM nullkey_c1_t1 JOIN postgres_local_table USING(a) ORDER BY 1,2,3; +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "postgres_local_table" to a subquery +DEBUG: generating subplan XXX_1 for subquery SELECT a, b FROM query_single_shard_table.postgres_local_table WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT nullkey_c1_t1.a, nullkey_c1_t1.b, postgres_local_table.b FROM (query_single_shard_table.nullkey_c1_t1 JOIN (SELECT postgres_local_table_1.a, postgres_local_table_1.b FROM (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) postgres_local_table_1) postgres_local_table USING (a)) ORDER BY nullkey_c1_t1.a, nullkey_c1_t1.b, postgres_local_table.b +DEBUG: Creating router plan + a | b | b +--------------------------------------------------------------------- + 5 | 5 | 5 + 6 | 6 | 6 + 7 | 7 | 7 + 8 | 8 | 8 +(4 rows) + +SELECT * FROM nullkey_c1_t1 JOIN citus_local_table USING(a) ORDER BY 1,2,3; +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "citus_local_table" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT a, b FROM query_single_shard_table.citus_local_table WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT nullkey_c1_t1.a, nullkey_c1_t1.b, citus_local_table.b FROM (query_single_shard_table.nullkey_c1_t1 JOIN (SELECT citus_local_table_1.a, citus_local_table_1.b FROM (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) citus_local_table_1) citus_local_table USING (a)) ORDER BY nullkey_c1_t1.a, nullkey_c1_t1.b, citus_local_table.b +DEBUG: Creating router plan + a | b | b +--------------------------------------------------------------------- + 1 | 1 | 1 + 1 | 2 | 1 + 2 | 2 | 2 + 2 | 2 | 2 + 3 | 3 | 3 + 3 | 4 | 3 + 4 | 4 | 4 + 5 | 5 | 5 + 6 | 6 | 6 + 7 | 7 | 7 + 8 | 8 | 8 +(11 rows) + +SET citus.local_table_join_policy TO 'prefer-distributed'; +SELECT * FROM nullkey_c1_t1 JOIN citus_local_table USING(a) ORDER BY 1,2,3; +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "nullkey_c1_t1" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT a, b FROM query_single_shard_table.nullkey_c1_t1 WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT nullkey_c1_t1.a, nullkey_c1_t1.b, citus_local_table.b FROM ((SELECT nullkey_c1_t1_1.a, nullkey_c1_t1_1.b FROM (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) nullkey_c1_t1_1) nullkey_c1_t1 JOIN query_single_shard_table.citus_local_table USING (a)) ORDER BY nullkey_c1_t1.a, nullkey_c1_t1.b, citus_local_table.b +DEBUG: Creating router plan + a | b | b +--------------------------------------------------------------------- + 1 | 1 | 1 + 1 | 2 | 1 + 2 | 2 | 2 + 2 | 2 | 2 + 3 | 3 | 3 + 3 | 4 | 3 + 4 | 4 | 4 + 5 | 5 | 5 + 6 | 6 | 6 + 7 | 7 | 7 + 8 | 8 | 8 +(11 rows) + +RESET citus.local_table_join_policy; +-- join with a distributed table +SET citus.enable_repartition_joins TO ON; +SET client_min_messages TO DEBUG1; +SELECT * FROM distributed_table d1 JOIN nullkey_c1_t1 USING(a) ORDER BY 1,2,3; + a | b | b +--------------------------------------------------------------------- + 3 | 3 | 3 + 3 | 3 | 4 + 4 | 4 | 4 + 5 | 5 | 5 + 6 | 6 | 6 + 7 | 7 | 7 + 8 | 8 | 8 +(7 rows) + +SELECT * FROM (SELECT * FROM distributed_table) d1 JOIN nullkey_c1_t1 USING(a) ORDER BY 1,2,3; +DEBUG: generating subplan XXX_1 for subquery SELECT a, b FROM query_single_shard_table.distributed_table +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT d1.a, d1.b, nullkey_c1_t1.b FROM ((SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) d1 JOIN query_single_shard_table.nullkey_c1_t1 USING (a)) ORDER BY d1.a, d1.b, nullkey_c1_t1.b + a | b | b +--------------------------------------------------------------------- + 3 | 3 | 3 + 3 | 3 | 4 + 4 | 4 | 4 + 5 | 5 | 5 + 6 | 6 | 6 + 7 | 7 | 7 + 8 | 8 | 8 +(7 rows) + +SELECT * FROM nullkey_c1_t1 JOIN (SELECT * FROM distributed_table) d1 USING(a) ORDER BY 1,2,3; +DEBUG: generating subplan XXX_1 for subquery SELECT a, b FROM query_single_shard_table.distributed_table +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT nullkey_c1_t1.a, nullkey_c1_t1.b, d1.b FROM (query_single_shard_table.nullkey_c1_t1 JOIN (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) d1 USING (a)) ORDER BY nullkey_c1_t1.a, nullkey_c1_t1.b, d1.b + a | b | b +--------------------------------------------------------------------- + 3 | 3 | 3 + 3 | 4 | 3 + 4 | 4 | 4 + 5 | 5 | 5 + 6 | 6 | 6 + 7 | 7 | 7 + 8 | 8 | 8 +(7 rows) + +SELECT * FROM distributed_table d1 JOIN (SELECT * FROM nullkey_c1_t1) nullkey_c1_t1 USING(a) ORDER BY 1,2,3; +DEBUG: generating subplan XXX_1 for subquery SELECT a, b FROM query_single_shard_table.nullkey_c1_t1 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT d1.a, d1.b, nullkey_c1_t1.b FROM (query_single_shard_table.distributed_table d1 JOIN (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) nullkey_c1_t1 USING (a)) ORDER BY d1.a, d1.b, nullkey_c1_t1.b + a | b | b +--------------------------------------------------------------------- + 3 | 3 | 3 + 3 | 3 | 4 + 4 | 4 | 4 + 5 | 5 | 5 + 6 | 6 | 6 + 7 | 7 | 7 + 8 | 8 | 8 +(7 rows) + +SELECT * FROM (SELECT * FROM nullkey_c1_t1) nullkey_c1_t1 JOIN distributed_table d1 USING(a) ORDER BY 1,2,3; +DEBUG: generating subplan XXX_1 for subquery SELECT a, b FROM query_single_shard_table.nullkey_c1_t1 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT nullkey_c1_t1.a, nullkey_c1_t1.b, d1.b FROM ((SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) nullkey_c1_t1 JOIN query_single_shard_table.distributed_table d1 USING (a)) ORDER BY nullkey_c1_t1.a, nullkey_c1_t1.b, d1.b + a | b | b +--------------------------------------------------------------------- + 3 | 3 | 3 + 3 | 4 | 3 + 4 | 4 | 4 + 5 | 5 | 5 + 6 | 6 | 6 + 7 | 7 | 7 + 8 | 8 | 8 +(7 rows) + +-- test joins with non-colocated distributed tables, by using subqueries +SELECT * FROM nullkey_c1_t1 t1 JOIN (SELECT * FROM distributed_table) t2 USING (a) JOIN (SELECT * FROM nullkey_c1_t2) t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +DEBUG: generating subplan XXX_1 for subquery SELECT a, b FROM query_single_shard_table.distributed_table +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT t1.a, t1.b, t2.b, t3.b FROM ((query_single_shard_table.nullkey_c1_t1 t1 JOIN (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) t2 USING (a)) JOIN (SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM query_single_shard_table.nullkey_c1_t2) t3 USING (a)) ORDER BY t1.a, t1.b, t2.b, t3.b LIMIT 1 + a | b | b | b +--------------------------------------------------------------------- + 3 | 3 | 3 | 3 +(1 row) + +SELECT * FROM (SELECT * FROM nullkey_c1_t1) t1 JOIN nullkey_c2_t1 t2 USING (a) JOIN (SELECT * FROM nullkey_c1_t2) t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +DEBUG: push down of limit count: 1 + a | b | b | b +--------------------------------------------------------------------- + 1 | 1 | 0 | 3 +(1 row) + +SELECT * FROM distributed_table t1 JOIN (SELECT * FROM nullkey_c1_t1) t2 USING (a) JOIN (SELECT b as a FROM distributed_table) t3 USING (a) ORDER BY 1,2,3 LIMIT 1; +DEBUG: generating subplan XXX_1 for subquery SELECT a, b FROM query_single_shard_table.nullkey_c1_t1 +DEBUG: generating subplan XXX_2 for subquery SELECT b AS a FROM query_single_shard_table.distributed_table +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT t1.a, t1.b, t2.b FROM ((query_single_shard_table.distributed_table t1 JOIN (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) t2 USING (a)) JOIN (SELECT intermediate_result.a FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) t3 USING (a)) ORDER BY t1.a, t1.b, t2.b LIMIT 1 +DEBUG: push down of limit count: 1 + a | b | b +--------------------------------------------------------------------- + 3 | 3 | 3 +(1 row) + +SELECT * FROM (SELECT * FROM nullkey_c2_t1) t1 JOIN nullkey_c1_t1 t2 USING (a) JOIN (SELECT * FROM nullkey_c2_t1) t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +DEBUG: push down of limit count: 1 + a | b | b | b +--------------------------------------------------------------------- + 1 | 0 | 1 | 0 +(1 row) + +SELECT * FROM nullkey_c1_t1 t1 JOIN (SELECT * FROM distributed_table) t2 USING (a) JOIN (SELECT * FROM distributed_table) t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +DEBUG: push down of limit count: 1 + a | b | b | b +--------------------------------------------------------------------- + 3 | 3 | 3 | 3 +(1 row) + +SELECT * FROM (SELECT * FROM nullkey_c1_t1) t1 JOIN nullkey_c2_t1 t2 USING (a) JOIN (SELECT * FROM nullkey_c2_t1) t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +DEBUG: push down of limit count: 1 + a | b | b | b +--------------------------------------------------------------------- + 1 | 1 | 0 | 0 +(1 row) + +SELECT * FROM distributed_table t1 JOIN (SELECT * FROM nullkey_c1_t1) t2 USING (a) JOIN (SELECT * FROM nullkey_c1_t1) t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +DEBUG: generating subplan XXX_1 for subquery SELECT a, b FROM query_single_shard_table.nullkey_c1_t1 +DEBUG: generating subplan XXX_2 for subquery SELECT a, b FROM query_single_shard_table.nullkey_c1_t1 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT t1.a, t1.b, t2.b, t3.b FROM ((query_single_shard_table.distributed_table t1 JOIN (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) t2 USING (a)) JOIN (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) t3 USING (a)) ORDER BY t1.a, t1.b, t2.b, t3.b LIMIT 1 +DEBUG: push down of limit count: 1 + a | b | b | b +--------------------------------------------------------------------- + 3 | 3 | 3 | 3 +(1 row) + +SELECT * FROM (SELECT * FROM nullkey_c2_t1) t1 JOIN nullkey_c1_t1 t2 USING (a) JOIN (SELECT * FROM nullkey_c1_t1) t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +DEBUG: push down of limit count: 1 + a | b | b | b +--------------------------------------------------------------------- + 1 | 0 | 1 | 1 +(1 row) + +SELECT * FROM nullkey_c1_t1 t1 JOIN (SELECT * FROM nullkey_c1_t1) t2 USING (a) JOIN distributed_table t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +DEBUG: push down of limit count: 1 + a | b | b | b +--------------------------------------------------------------------- + 3 | 3 | 3 | 3 +(1 row) + +SELECT * FROM nullkey_c1_t1 t1 JOIN nullkey_c1_t1 t2 USING (a) JOIN nullkey_c2_t1 t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +DEBUG: push down of limit count: 1 + a | b | b | b +--------------------------------------------------------------------- + 1 | 1 | 1 | 0 +(1 row) + +SELECT * FROM (SELECT * FROM distributed_table) t1 JOIN distributed_table t2 USING (a) JOIN (SELECT * FROM nullkey_c1_t1) t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +DEBUG: push down of limit count: 1 + a | b | b | b +--------------------------------------------------------------------- + 3 | 3 | 3 | 3 +(1 row) + +SELECT * FROM (SELECT * FROM nullkey_c2_t1) t1 JOIN nullkey_c2_t1 t2 USING (a) JOIN (SELECT * FROM nullkey_c1_t1) t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +DEBUG: push down of limit count: 1 + a | b | b | b +--------------------------------------------------------------------- + 1 | 0 | 0 | 1 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +JOIN LATERAL ( + SELECT * FROM distributed_table t2 WHERE t2.b > t1.a +) q USING(a); + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +JOIN LATERAL ( + SELECT *, random() FROM distributed_table t2 WHERE t2.b > t1.a +) q USING(a); +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +SELECT COUNT(*) FROM distributed_table t1 +JOIN LATERAL ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +) q USING(a); + count +--------------------------------------------------------------------- + 1 +(1 row) + +SET client_min_messages TO DEBUG2; +SET citus.enable_repartition_joins TO OFF; +-- outer joins with different table types +SELECT COUNT(*) FROM nullkey_c1_t1 LEFT JOIN reference_table USING(a); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 11 +(1 row) + +SELECT COUNT(*) FROM reference_table LEFT JOIN nullkey_c1_t1 USING(a); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 9 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 LEFT JOIN citus_local_table USING(a); +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "citus_local_table" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM query_single_shard_table.citus_local_table WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (query_single_shard_table.nullkey_c1_t1 LEFT JOIN (SELECT citus_local_table_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) citus_local_table_1) citus_local_table USING (a)) +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 11 +(1 row) + +SELECT COUNT(*) FROM citus_local_table LEFT JOIN nullkey_c1_t1 USING(a); +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "citus_local_table" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM query_single_shard_table.citus_local_table WHERE true +DEBUG: recursively planning right side of the left join since the outer side is a recurring rel +DEBUG: recursively planning distributed relation "nullkey_c1_t1" since it is part of a distributed join node that is outer joined with a recurring rel +DEBUG: Wrapping relation "nullkey_c1_t1" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_2 for subquery SELECT a FROM query_single_shard_table.nullkey_c1_t1 WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT citus_local_table_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) citus_local_table_1) citus_local_table LEFT JOIN (SELECT nullkey_c1_t1_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) nullkey_c1_t1_1) nullkey_c1_t1 USING (a)) +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 14 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 LEFT JOIN postgres_local_table USING(a); +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "postgres_local_table" to a subquery +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM query_single_shard_table.postgres_local_table WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (query_single_shard_table.nullkey_c1_t1 LEFT JOIN (SELECT postgres_local_table_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) postgres_local_table_1) postgres_local_table USING (a)) +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 11 +(1 row) + +SELECT COUNT(*) FROM postgres_local_table LEFT JOIN nullkey_c1_t1 USING(a); +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "postgres_local_table" to a subquery +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM query_single_shard_table.postgres_local_table WHERE true +DEBUG: recursively planning right side of the left join since the outer side is a recurring rel +DEBUG: recursively planning distributed relation "nullkey_c1_t1" since it is part of a distributed join node that is outer joined with a recurring rel +DEBUG: Wrapping relation "nullkey_c1_t1" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_2 for subquery SELECT a FROM query_single_shard_table.nullkey_c1_t1 WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT postgres_local_table_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) postgres_local_table_1) postgres_local_table LEFT JOIN (SELECT nullkey_c1_t1_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) nullkey_c1_t1_1) nullkey_c1_t1 USING (a)) +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 FULL JOIN citus_local_table USING(a); +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "citus_local_table" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM query_single_shard_table.citus_local_table WHERE true +DEBUG: recursively planning left side of the full join since the other side is a recurring rel +DEBUG: recursively planning distributed relation "nullkey_c1_t1" since it is part of a distributed join node that is outer joined with a recurring rel +DEBUG: Wrapping relation "nullkey_c1_t1" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_2 for subquery SELECT a FROM query_single_shard_table.nullkey_c1_t1 WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT nullkey_c1_t1_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) nullkey_c1_t1_1) nullkey_c1_t1 FULL JOIN (SELECT citus_local_table_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) citus_local_table_1) citus_local_table USING (a)) +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 14 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 FULL JOIN postgres_local_table USING(a); +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "postgres_local_table" to a subquery +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM query_single_shard_table.postgres_local_table WHERE true +DEBUG: recursively planning left side of the full join since the other side is a recurring rel +DEBUG: recursively planning distributed relation "nullkey_c1_t1" since it is part of a distributed join node that is outer joined with a recurring rel +DEBUG: Wrapping relation "nullkey_c1_t1" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_2 for subquery SELECT a FROM query_single_shard_table.nullkey_c1_t1 WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT nullkey_c1_t1_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) nullkey_c1_t1_1) nullkey_c1_t1 FULL JOIN (SELECT postgres_local_table_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) postgres_local_table_1) postgres_local_table USING (a)) +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 13 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 FULL JOIN reference_table USING(a); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 12 +(1 row) + +SET citus.enable_repartition_joins TO ON; +SET client_min_messages TO DEBUG1; +SELECT COUNT(*) FROM nullkey_c1_t1 JOIN append_table USING(a); + count +--------------------------------------------------------------------- + 2 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 JOIN range_table USING(a); + count +--------------------------------------------------------------------- + 9 +(1 row) + +SET client_min_messages TO DEBUG2; +SET citus.enable_repartition_joins TO OFF; +SET citus.enable_non_colocated_router_query_pushdown TO ON; +SELECT COUNT(*) FROM nullkey_c1_t1 JOIN range_table USING(a) WHERE range_table.a = 20; +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 0 +(1 row) + +SET citus.enable_non_colocated_router_query_pushdown TO OFF; +SET citus.enable_repartition_joins TO ON; +SET client_min_messages TO DEBUG1; +SELECT COUNT(*) FROM nullkey_c1_t1 JOIN range_table USING(a) WHERE range_table.a = 20; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SET client_min_messages TO DEBUG2; +SET citus.enable_repartition_joins TO OFF; +RESET citus.enable_non_colocated_router_query_pushdown; +-- lateral / semi / anti joins with different table types +-- with a reference table +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +LEFT JOIN LATERAL ( + SELECT * FROM reference_table t2 WHERE t2.b > t1.a +) q USING(a); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 11 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE EXISTS ( + SELECT * FROM reference_table t2 WHERE t2.b > t1.a +); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 7 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE NOT EXISTS ( + SELECT * FROM reference_table t2 WHERE t2.b > t1.a +); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 4 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b IN ( + SELECT b+1 FROM reference_table t2 WHERE t2.b = t1.a +); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 2 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b NOT IN ( + SELECT a FROM reference_table t2 WHERE t2.b > t1.a +); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 9 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +JOIN LATERAL ( + SELECT * FROM reference_table t2 WHERE t2.b > t1.a +) q USING(a); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT COUNT(*) FROM reference_table t1 +LEFT JOIN LATERAL ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +) q USING(a); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT COUNT(*) FROM reference_table t1 +WHERE EXISTS ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT COUNT(*) FROM reference_table t1 +WHERE t1.b IN ( + SELECT b+1 FROM nullkey_c1_t1 t2 WHERE t2.b = t1.a +); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT COUNT(*) FROM reference_table t1 +WHERE t1.b NOT IN ( + SELECT a FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 4 +(1 row) + +SELECT COUNT(*) FROM reference_table t1 +JOIN LATERAL ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +) q USING(a); +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 2 +(1 row) + +-- with a distributed table +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +LEFT JOIN LATERAL ( + SELECT * FROM distributed_table t2 WHERE t2.b > t1.a +) q USING(a); +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE EXISTS ( + SELECT * FROM distributed_table t2 WHERE t2.b > t1.a +); +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE NOT EXISTS ( + SELECT * FROM distributed_table t2 WHERE t2.b > t1.a +); +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b IN ( + SELECT b+1 FROM distributed_table t2 WHERE t2.b = t1.a +); +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b NOT IN ( + SELECT a FROM distributed_table t2 WHERE t2.b > t1.a +); +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +SELECT COUNT(*) FROM distributed_table t1 +LEFT JOIN LATERAL ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +) q USING(a); +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +SELECT COUNT(*) FROM distributed_table t1 +WHERE EXISTS ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +); +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +SELECT COUNT(*) FROM distributed_table t1 +WHERE t1.b IN ( + SELECT b+1 FROM nullkey_c1_t1 t2 WHERE t2.b = t1.a +); +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +SELECT COUNT(*) FROM distributed_table t1 +WHERE t1.b NOT IN ( + SELECT a FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +); +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +-- with postgres / citus local tables +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +LEFT JOIN LATERAL ( + SELECT * FROM citus_local_table t2 WHERE t2.b > t1.a +) q USING(a); +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: direct joins between distributed and local tables are not supported +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE EXISTS ( + SELECT * FROM citus_local_table t2 WHERE t2.b > t1.a +); +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: direct joins between distributed and local tables are not supported +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE NOT EXISTS ( + SELECT * FROM citus_local_table t2 WHERE t2.b > t1.a +); +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: direct joins between distributed and local tables are not supported +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b IN ( + SELECT b+1 FROM citus_local_table t2 WHERE t2.b = t1.a +); +DEBUG: found no worker with all shard placements +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: direct joins between distributed and local tables are not supported +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b NOT IN ( + SELECT a FROM citus_local_table t2 WHERE t2.b > t1.a +); +DEBUG: found no worker with all shard placements +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: direct joins between distributed and local tables are not supported +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +JOIN LATERAL ( + SELECT * FROM citus_local_table t2 WHERE t2.b > t1.a +) q USING(a); +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: direct joins between distributed and local tables are not supported +-- The following and a few other tests in this file unnecessarily go through +-- recursive planning. This is because we recursive plan distributed tables +-- when they are referred in the inner side of an outer join, if the outer +-- side is a recurring rel. In future, we can optimize that such that we +-- can skip recursively planning the single-shard table because such a join +-- wouldn't result in returning recurring tuples. +-- +-- And specifically for the tests that contains a sublink (as below), things +-- get even more interesting. We try to recursively plan the single-shard +-- table but we cannot do so due to the sublink. However, the final query +-- can go through router planner and hence is supported. +SELECT COUNT(*) FROM citus_local_table t1 +LEFT JOIN LATERAL ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +) q USING(a); +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "citus_local_table" "t1" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM query_single_shard_table.citus_local_table t1 WHERE true +DEBUG: recursively planning right side of the left join since the outer side is a recurring rel +DEBUG: recursively planning the distributed subquery since it is part of a distributed join node that is outer joined with a recurring rel +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT t1_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) t1_1) t1 LEFT JOIN LATERAL (SELECT t2.a, t2.b FROM query_single_shard_table.nullkey_c1_t1 t2 WHERE (t2.b OPERATOR(pg_catalog.>) t1.a)) q USING (a)) +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 11 +(1 row) + +SELECT COUNT(*) FROM postgres_local_table t1 +LEFT JOIN LATERAL ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +) q USING(a); +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "postgres_local_table" "t1" to a subquery +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM query_single_shard_table.postgres_local_table t1 WHERE true +DEBUG: recursively planning right side of the left join since the outer side is a recurring rel +DEBUG: recursively planning the distributed subquery since it is part of a distributed join node that is outer joined with a recurring rel +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT t1_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) t1_1) t1 LEFT JOIN LATERAL (SELECT t2.a, t2.b FROM query_single_shard_table.nullkey_c1_t1 t2 WHERE (t2.b OPERATOR(pg_catalog.>) t1.a)) q USING (a)) +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT COUNT(*) FROM citus_local_table t1 +WHERE EXISTS ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +); +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: direct joins between distributed and local tables are not supported +SELECT COUNT(*) FROM citus_local_table t1 +WHERE t1.b IN ( + SELECT b+1 FROM nullkey_c1_t1 t2 WHERE t2.b = t1.a +); +DEBUG: found no worker with all shard placements +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: direct joins between distributed and local tables are not supported +SELECT COUNT(*) FROM citus_local_table t1 +WHERE t1.b NOT IN ( + SELECT a FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +); +DEBUG: found no worker with all shard placements +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: direct joins between distributed and local tables are not supported +SELECT COUNT(*) FROM citus_local_table t1 +JOIN LATERAL ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +) q USING(a); +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "citus_local_table" "t1" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM query_single_shard_table.citus_local_table t1 WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT t1_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) t1_1) t1 JOIN LATERAL (SELECT t2.a, t2.b FROM query_single_shard_table.nullkey_c1_t1 t2 WHERE (t2.b OPERATOR(pg_catalog.>) t1.a)) q USING (a)) +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 2 +(1 row) + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +LEFT JOIN LATERAL ( + SELECT * FROM postgres_local_table t2 WHERE t2.b > t1.a +) q USING(a); +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: direct joins between distributed and local tables are not supported +HINT: Use CTE's or subqueries to select from local tables and use them in joins +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE EXISTS ( + SELECT * FROM postgres_local_table t2 WHERE t2.b > t1.a +); +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: direct joins between distributed and local tables are not supported +HINT: Use CTE's or subqueries to select from local tables and use them in joins +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE NOT EXISTS ( + SELECT * FROM postgres_local_table t2 WHERE t2.b > t1.a +); +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: direct joins between distributed and local tables are not supported +HINT: Use CTE's or subqueries to select from local tables and use them in joins +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b IN ( + SELECT b+1 FROM postgres_local_table t2 WHERE t2.b = t1.a +); +DEBUG: found no worker with all shard placements +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: direct joins between distributed and local tables are not supported +HINT: Use CTE's or subqueries to select from local tables and use them in joins +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b NOT IN ( + SELECT a FROM postgres_local_table t2 WHERE t2.b > t1.a +); +DEBUG: found no worker with all shard placements +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: direct joins between distributed and local tables are not supported +HINT: Use CTE's or subqueries to select from local tables and use them in joins +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +JOIN LATERAL ( + SELECT * FROM postgres_local_table t2 WHERE t2.b > t1.a +) q USING(a); +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: direct joins between distributed and local tables are not supported +HINT: Use CTE's or subqueries to select from local tables and use them in joins +SELECT COUNT(*) FROM postgres_local_table t1 +WHERE EXISTS ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +); +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: direct joins between distributed and local tables are not supported +HINT: Use CTE's or subqueries to select from local tables and use them in joins +SELECT COUNT(*) FROM postgres_local_table t1 +WHERE t1.b IN ( + SELECT b+1 FROM nullkey_c1_t1 t2 WHERE t2.b = t1.a +); +DEBUG: found no worker with all shard placements +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: direct joins between distributed and local tables are not supported +HINT: Use CTE's or subqueries to select from local tables and use them in joins +SELECT COUNT(*) FROM postgres_local_table t1 +WHERE t1.b NOT IN ( + SELECT a FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +); +DEBUG: found no worker with all shard placements +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: direct joins between distributed and local tables are not supported +HINT: Use CTE's or subqueries to select from local tables and use them in joins +SELECT COUNT(*) FROM postgres_local_table t1 +JOIN LATERAL ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +) q USING(a); +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: Wrapping relation "postgres_local_table" "t1" to a subquery +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM query_single_shard_table.postgres_local_table t1 WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT t1_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) t1_1) t1 JOIN LATERAL (SELECT t2.a, t2.b FROM query_single_shard_table.nullkey_c1_t1 t2 WHERE (t2.b OPERATOR(pg_catalog.>) t1.a)) q USING (a)) +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- insert .. select +-- between two colocated single-shard tables +-- The target list of "distributed statement"s that we send to workers +-- differ(*) in Postgres versions < 15. For this reason, we temporarily +-- disable debug messages here and run the EXPLAIN'ed version of the +-- command. +-- +-- (*): < SELECT a, b > vs < SELECT table_name.a, table_name.b > +SET client_min_messages TO WARNING; +EXPLAIN (ANALYZE TRUE, TIMING FALSE, COSTS FALSE, SUMMARY FALSE, VERBOSE FALSE) +INSERT INTO nullkey_c1_t1 SELECT * FROM nullkey_c1_t2; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) (actual rows=0 loops=1) + Task Count: 1 + Tasks Shown: All + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Insert on nullkey_c1_t1_1620000 citus_table_alias (actual rows=0 loops=1) + -> Seq Scan on nullkey_c1_t2_1620001 nullkey_c1_t2 (actual rows=10 loops=1) +(7 rows) + +SET client_min_messages TO DEBUG2; +-- between two non-colocated single-shard tables +INSERT INTO nullkey_c1_t1 SELECT * FROM nullkey_c2_t1; +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- between a single-shard table and a table of different type +SET client_min_messages TO WARNING; +EXPLAIN (ANALYZE TRUE, TIMING FALSE, COSTS FALSE, SUMMARY FALSE, VERBOSE FALSE) +INSERT INTO nullkey_c1_t1 SELECT * FROM reference_table; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) (actual rows=0 loops=1) + Task Count: 1 + Tasks Shown: All + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Insert on nullkey_c1_t1_1620000 citus_table_alias (actual rows=0 loops=1) + -> Seq Scan on reference_table_1620005 reference_table (actual rows=6 loops=1) +(7 rows) + +SET client_min_messages TO DEBUG2; +INSERT INTO nullkey_c1_t1 SELECT * FROM distributed_table; +DEBUG: INSERT target relation and all source relations of the SELECT must be colocated in distributed INSERT ... SELECT +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO nullkey_c1_t1 SELECT * FROM citus_local_table; +DEBUG: distributed INSERT ... SELECT cannot select from a local relation when inserting into a distributed table +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO nullkey_c1_t1 SELECT * FROM postgres_local_table; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO reference_table SELECT * FROM nullkey_c1_t1; +DEBUG: only reference tables may be queried when targeting a reference table with distributed INSERT ... SELECT +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO distributed_table SELECT * FROM nullkey_c1_t1; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: The target table's partition column should correspond to a partition column in the subquery. +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO citus_local_table SELECT * FROM nullkey_c1_t1; +DEBUG: distributed INSERT ... SELECT cannot insert into a local table that is added to metadata +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO postgres_local_table SELECT * FROM nullkey_c1_t1; +DEBUG: Creating router plan +-- test subquery +SELECT count(*) FROM +( + SELECT * FROM (SELECT * FROM nullkey_c1_t2) as subquery_inner +) AS subquery_top; +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 10 +(1 row) + +-- test cte inlining +WITH cte_nullkey_c1_t1 AS (SELECT * FROM nullkey_c1_t1), + cte_postgres_local_table AS (SELECT * FROM postgres_local_table), + cte_distributed_table AS (SELECT * FROM distributed_table) +SELECT COUNT(*) FROM cte_distributed_table, cte_nullkey_c1_t1, cte_postgres_local_table +WHERE cte_nullkey_c1_t1.a > 3 AND cte_distributed_table.a < 5; +DEBUG: CTE cte_nullkey_c1_t1 is going to be inlined via distributed planning +DEBUG: CTE cte_postgres_local_table is going to be inlined via distributed planning +DEBUG: CTE cte_distributed_table is going to be inlined via distributed planning +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: generating subplan XXX_1 for subquery SELECT a, b FROM query_single_shard_table.postgres_local_table +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_2 for subquery SELECT a, b FROM query_single_shard_table.nullkey_c1_t1 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT distributed_table.a, distributed_table.b FROM query_single_shard_table.distributed_table) cte_distributed_table, (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) cte_nullkey_c1_t1, (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) cte_postgres_local_table WHERE ((cte_nullkey_c1_t1.a OPERATOR(pg_catalog.>) 3) AND (cte_distributed_table.a OPERATOR(pg_catalog.<) 5)) +DEBUG: Router planner cannot handle multi-shard select queries + count +--------------------------------------------------------------------- + 78144 +(1 row) + +-- test recursive ctes +WITH level_0 AS ( + WITH level_1 AS ( + WITH RECURSIVE level_2_recursive(x) AS ( + VALUES (1) + UNION ALL + SELECT a + 1 FROM nullkey_c1_t1 JOIN level_2_recursive ON (a = x) WHERE a < 2 + ) + SELECT * FROM level_2_recursive RIGHT JOIN reference_table ON (level_2_recursive.x = reference_table.a) + ) + SELECT * FROM level_1 +) +SELECT COUNT(*) FROM level_0; +DEBUG: CTE level_0 is going to be inlined via distributed planning +DEBUG: CTE level_1 is going to be inlined via distributed planning +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 106 +(1 row) + +WITH level_0 AS ( + WITH level_1 AS ( + WITH RECURSIVE level_2_recursive(x) AS ( + VALUES (1) + UNION ALL + SELECT a + 1 FROM nullkey_c1_t1 JOIN level_2_recursive ON (a = x) WHERE a < 100 + ) + SELECT * FROM level_2_recursive JOIN distributed_table ON (level_2_recursive.x = distributed_table.a) + ) + SELECT * FROM level_1 +) +SELECT COUNT(*) FROM level_0; +DEBUG: CTE level_0 is going to be inlined via distributed planning +DEBUG: CTE level_1 is going to be inlined via distributed planning +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: generating subplan XXX_1 for CTE level_0: WITH level_1 AS (WITH RECURSIVE level_2_recursive(x) AS (VALUES (1) UNION ALL SELECT (nullkey_c1_t1.a OPERATOR(pg_catalog.+) 1) FROM (query_single_shard_table.nullkey_c1_t1 JOIN level_2_recursive level_2_recursive_1 ON ((nullkey_c1_t1.a OPERATOR(pg_catalog.=) level_2_recursive_1.x))) WHERE (nullkey_c1_t1.a OPERATOR(pg_catalog.<) 100)) SELECT level_2_recursive.x, distributed_table.a, distributed_table.b FROM (level_2_recursive JOIN query_single_shard_table.distributed_table ON ((level_2_recursive.x OPERATOR(pg_catalog.=) distributed_table.a)))) SELECT x, a, b FROM level_1 +DEBUG: CTE level_1 is going to be inlined via distributed planning +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: generating subplan XXX_1 for CTE level_1: WITH RECURSIVE level_2_recursive(x) AS (VALUES (1) UNION ALL SELECT (nullkey_c1_t1.a OPERATOR(pg_catalog.+) 1) FROM (query_single_shard_table.nullkey_c1_t1 JOIN level_2_recursive level_2_recursive_1 ON ((nullkey_c1_t1.a OPERATOR(pg_catalog.=) level_2_recursive_1.x))) WHERE (nullkey_c1_t1.a OPERATOR(pg_catalog.<) 100)) SELECT level_2_recursive.x, distributed_table.a, distributed_table.b FROM (level_2_recursive JOIN query_single_shard_table.distributed_table ON ((level_2_recursive.x OPERATOR(pg_catalog.=) distributed_table.a))) +DEBUG: router planner does not support queries that reference non-colocated distributed tables +ERROR: recursive CTEs are not supported in distributed queries +-- grouping set +SELECT + id, substring(title, 2, 1) AS subtitle, count(*) + FROM articles_hash + WHERE author_id = 1 or author_id = 2 + GROUP BY GROUPING SETS ((id),(subtitle)) + ORDER BY id, subtitle; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + id | subtitle | count +--------------------------------------------------------------------- + 42 | | 1 + | u | 1 +(2 rows) + +-- subquery in SELECT clause +SELECT a.title AS name, (SELECT a2.id FROM articles_hash a2 WHERE a.id = a2.id LIMIT 1) + AS special_price FROM articles_hash a +ORDER BY 1,2; +DEBUG: Creating router plan + name | special_price +--------------------------------------------------------------------- + adversa | 15 + aerophyte | 28 + affixal | 43 + altdorfer | 4 + amateur | 29 + anjanette | 50 + anyone | 49 + aruru | 5 + aseyev | 13 + assembly | 18 + aubergiste | 19 + ausable | 42 +(12 rows) + +-- test having clause +SELECT COUNT(*), b FROM nullkey_c1_t1 GROUP BY 2 +HAVING (SELECT COUNT(*) FROM nullkey_c1_t2) > 0 +ORDER BY 1,2; +DEBUG: Creating router plan + count | b +--------------------------------------------------------------------- + 2 | 9 + 2 | 10 + 3 | 0 + 4 | 1 + 4 | 8 + 6 | 6 + 6 | 7 + 8 | 3 + 8 | 4 + 8 | 5 + 9 | 2 +(11 rows) + +SELECT COUNT(*), b FROM nullkey_c1_t1 GROUP BY 2 +HAVING (SELECT COUNT(*) FROM nullkey_c2_t1) > 0 +ORDER BY 1,2; +DEBUG: found no worker with all shard placements +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT count(*) AS count FROM query_single_shard_table.nullkey_c2_t1 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count, b FROM query_single_shard_table.nullkey_c1_t1 GROUP BY b HAVING ((SELECT intermediate_result.count FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(count bigint)) OPERATOR(pg_catalog.>) 0) ORDER BY (count(*)), b +DEBUG: Creating router plan + count | b +--------------------------------------------------------------------- + 2 | 9 + 2 | 10 + 3 | 0 + 4 | 1 + 4 | 8 + 6 | 6 + 6 | 7 + 8 | 3 + 8 | 4 + 8 | 5 + 9 | 2 +(11 rows) + +SELECT COUNT(*), b FROM nullkey_c1_t1 GROUP BY 2 +HAVING (SELECT COUNT(*) FROM distributed_table) > 0 +ORDER BY 1,2; +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for subquery SELECT count(*) AS count FROM query_single_shard_table.distributed_table +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count, b FROM query_single_shard_table.nullkey_c1_t1 GROUP BY b HAVING ((SELECT intermediate_result.count FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(count bigint)) OPERATOR(pg_catalog.>) 0) ORDER BY (count(*)), b +DEBUG: Creating router plan + count | b +--------------------------------------------------------------------- + 2 | 9 + 2 | 10 + 3 | 0 + 4 | 1 + 4 | 8 + 6 | 6 + 6 | 7 + 8 | 3 + 8 | 4 + 8 | 5 + 9 | 2 +(11 rows) + +SELECT COUNT(*), b FROM nullkey_c1_t1 t4 GROUP BY 2 +HAVING ( + SELECT COUNT(*) FROM nullkey_c1_t1 t1 JOIN (SELECT * FROM nullkey_c1_t2) t2 USING (a) JOIN (SELECT * FROM nullkey_c1_t2) t3 USING (a) + WHERE t2.b > t4.b +) > 5 +ORDER BY 1,2; +DEBUG: Creating router plan + count | b +--------------------------------------------------------------------- + 3 | 0 + 4 | 1 + 6 | 6 + 8 | 3 + 8 | 4 + 8 | 5 + 9 | 2 +(7 rows) + +SELECT COUNT(*), b FROM distributed_table t4 GROUP BY 2 +HAVING ( + SELECT COUNT(*) FROM nullkey_c1_t1 t1 JOIN (SELECT * FROM distributed_table) t2 USING (a) JOIN (SELECT * FROM nullkey_c1_t2) t3 USING (a) + WHERE t2.b > t4.b +) > 5 +ORDER BY 1,2; +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for subquery SELECT a, b FROM query_single_shard_table.distributed_table +ERROR: Subqueries in HAVING cannot refer to outer query +-- test prepared statements +-- prepare queries can be router plannable +PREPARE author_1_articles as + SELECT * + FROM articles_hash + WHERE author_id = 1; +EXECUTE author_1_articles; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + id | author_id | title | word_count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE author_1_articles; + id | author_id | title | word_count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE author_1_articles; + id | author_id | title | word_count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE author_1_articles; + id | author_id | title | word_count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE author_1_articles; + id | author_id | title | word_count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE author_1_articles; + id | author_id | title | word_count +--------------------------------------------------------------------- +(0 rows) + +-- parametric prepare queries can be router plannable +PREPARE author_articles(int) as + SELECT * + FROM articles_hash + WHERE author_id = $1; +EXECUTE author_articles(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + id | author_id | title | word_count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE author_articles(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + id | author_id | title | word_count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE author_articles(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + id | author_id | title | word_count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE author_articles(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + id | author_id | title | word_count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE author_articles(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + id | author_id | title | word_count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE author_articles(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + id | author_id | title | word_count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE author_articles(NULL); + id | author_id | title | word_count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE author_articles(NULL); + id | author_id | title | word_count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE author_articles(NULL); + id | author_id | title | word_count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE author_articles(NULL); + id | author_id | title | word_count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE author_articles(NULL); + id | author_id | title | word_count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE author_articles(NULL); + id | author_id | title | word_count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE author_articles(NULL); + id | author_id | title | word_count +--------------------------------------------------------------------- +(0 rows) + +PREPARE author_articles_update(int) AS + UPDATE articles_hash SET title = 'test' WHERE author_id = $1; +EXECUTE author_articles_update(NULL); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE author_articles_update(NULL); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE author_articles_update(NULL); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE author_articles_update(NULL); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE author_articles_update(NULL); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE author_articles_update(NULL); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE author_articles_update(NULL); +-- More tests with insert .. select. +-- +-- The target list of "distributed statement"s that we send to workers +-- might differ(*) in Postgres versions < 15 and they are reported when +-- "log level >= DEBUG2". For this reason, we set log level to DEBUG1 to +-- avoid reporting them. +-- +-- DEBUG1 still allows reporting the reason why given INSERT .. SELECT +-- query is not distributed / requires pull-to-coordinator. +SET client_min_messages TO DEBUG1; +INSERT INTO bigserial_test (x, y) SELECT x, y FROM bigserial_test; +DEBUG: volatile functions are not allowed in distributed INSERT ... SELECT queries +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO bigserial_test (x, y) SELECT a, a FROM reference_table; +DEBUG: volatile functions are not allowed in distributed INSERT ... SELECT queries +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO agg_events + (user_id) +SELECT f2.id FROM +(SELECT + id +FROM (SELECT users_ref_table.user_id AS id + FROM raw_events_first, + users_ref_table + WHERE raw_events_first.user_id = users_ref_table.user_id ) AS foo) as f +INNER JOIN +(SELECT v4, + v1, + id +FROM (SELECT SUM(raw_events_second.value_4) AS v4, + SUM(raw_events_first.value_1) AS v1, + raw_events_second.user_id AS id + FROM raw_events_first, + raw_events_second + WHERE raw_events_first.user_id = raw_events_second.user_id + GROUP BY raw_events_second.user_id + HAVING SUM(raw_events_second.value_4) > 1000) AS foo2 ) as f2 +ON (f.id = f2.id) +WHERE f.id IN (SELECT user_id + FROM raw_events_second); +-- upsert with returning +INSERT INTO agg_events AS ae + ( + user_id, + value_1_agg, + agg_time + ) +SELECT user_id, + value_1, + time +FROM raw_events_first +ON conflict (user_id, value_1_agg) +DO UPDATE + SET agg_time = EXCLUDED.agg_time + WHERE ae.agg_time < EXCLUDED.agg_time +RETURNING user_id, value_1_agg; + user_id | value_1_agg +--------------------------------------------------------------------- + 1 | 10 + 2 | 20 + 3 | 30 + 4 | 40 + 5 | 50 + 6 | 60 +(6 rows) + +-- using a left join +INSERT INTO agg_events (user_id) +SELECT + raw_events_first.user_id +FROM + raw_events_first LEFT JOIN raw_events_second ON raw_events_first.user_id = raw_events_second.user_id + WHERE raw_events_second.user_id = 10 OR raw_events_second.user_id = 11; +INSERT INTO agg_events (user_id) +SELECT + users_ref_table.user_id +FROM + users_ref_table LEFT JOIN raw_events_second ON users_ref_table.user_id = raw_events_second.user_id + WHERE raw_events_second.user_id = 10 OR raw_events_second.user_id = 11; +INSERT INTO agg_events (user_id) +SELECT COALESCE(raw_events_first.user_id, users_ref_table.user_id) +FROM raw_events_first + RIGHT JOIN (users_ref_table LEFT JOIN raw_events_second ON users_ref_table.user_id = raw_events_second.user_id) + ON raw_events_first.user_id = users_ref_table.user_id; +DEBUG: cannot perform a lateral outer join when a distributed subquery references a reference table +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- using a full join +INSERT INTO agg_events (user_id, value_1_agg) +SELECT t1.user_id AS col1, + t2.user_id AS col2 +FROM raw_events_first t1 + FULL JOIN raw_events_second t2 + ON t1.user_id = t2.user_id; +-- using semi join +INSERT INTO raw_events_second + (user_id) +SELECT user_id +FROM raw_events_first +WHERE user_id IN (SELECT raw_events_second.user_id + FROM raw_events_second, raw_events_first + WHERE raw_events_second.user_id = raw_events_first.user_id AND raw_events_first.user_id = 200); +-- using lateral join +INSERT INTO raw_events_second + (user_id) +SELECT user_id +FROM raw_events_first +WHERE NOT EXISTS (SELECT 1 + FROM raw_events_second + WHERE raw_events_second.user_id =raw_events_first.user_id); +INSERT INTO raw_events_second + (user_id) +SELECT user_id +FROM users_ref_table +WHERE NOT EXISTS (SELECT 1 + FROM raw_events_second + WHERE raw_events_second.user_id = users_ref_table.user_id); +DEBUG: correlated subqueries are not supported when the FROM clause contains a reference table +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- using inner join +INSERT INTO agg_events (user_id) +SELECT raw_events_first.user_id +FROM raw_events_first INNER JOIN raw_events_second ON raw_events_first.user_id = raw_events_second.value_1 +WHERE raw_events_first.value_1 IN (10, 11,12) OR raw_events_second.user_id IN (1,2,3,4); +INSERT INTO agg_events (user_id) +SELECT raw_events_first.user_id +FROM raw_events_first INNER JOIN users_ref_table ON raw_events_first.user_id = users_ref_table.user_id +WHERE raw_events_first.value_1 IN (10, 11,12) OR users_ref_table.user_id IN (1,2,3,4); +-- We could relax distributed insert .. select checks to allow pushing +-- down more clauses down to the worker nodes when inserting into a single +-- shard by selecting from a colocated one. We might want to do something +-- like https://github.com/citusdata/citus/pull/6772. +-- +-- e.g., insert into null_shard_key_1/citus_local/reference +-- select * from null_shard_key_1/citus_local/reference limit 1 +-- +-- Below "limit / offset clause" test and some others are examples of this. +-- limit / offset clause +INSERT INTO agg_events (user_id) SELECT raw_events_first.user_id FROM raw_events_first LIMIT 1; +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO agg_events (user_id) SELECT raw_events_first.user_id FROM raw_events_first OFFSET 1; +DEBUG: cannot push down this subquery +DETAIL: Offset clause is currently unsupported when a subquery references a column from another query +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO agg_events (user_id) SELECT users_ref_table.user_id FROM users_ref_table LIMIT 1; +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- using a materialized cte +WITH cte AS MATERIALIZED + (SELECT max(value_1)+1 as v1_agg, user_id FROM raw_events_first GROUP BY user_id) +INSERT INTO agg_events (value_1_agg, user_id) +SELECT v1_agg, user_id FROM cte; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO raw_events_second + WITH cte AS MATERIALIZED (SELECT * FROM raw_events_first) + SELECT user_id * 1000, time, value_1, value_2, value_3, value_4 FROM cte; +DEBUG: cannot push down this subquery +DETAIL: CTEs in subqueries are currently unsupported +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO raw_events_second (user_id) + WITH cte AS MATERIALIZED (SELECT * FROM users_ref_table) + SELECT user_id FROM cte; +DEBUG: cannot push down this subquery +DETAIL: CTEs in subqueries are currently unsupported +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- using a regular cte +WITH cte AS (SELECT * FROM raw_events_first) +INSERT INTO raw_events_second + SELECT user_id * 7000, time, value_1, value_2, value_3, value_4 FROM cte; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: CTE cte is going to be inlined via distributed planning +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO raw_events_second + WITH cte AS (SELECT * FROM raw_events_first) + SELECT * FROM cte; +DEBUG: CTE cte is going to be inlined via distributed planning +INSERT INTO agg_events + WITH sub_cte AS (SELECT 1) + SELECT + raw_events_first.user_id, (SELECT * FROM sub_cte) + FROM + raw_events_first; +DEBUG: CTE sub_cte is going to be inlined via distributed planning +-- we still support complex joins via INSERT's cte list .. +WITH cte AS ( + SELECT DISTINCT(reference_table.a) AS a, 1 AS b + FROM distributed_table RIGHT JOIN reference_table USING (a) +) +INSERT INTO raw_events_second (user_id, value_1) + SELECT (a+5)*-1, b FROM cte; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: CTE cte is going to be inlined via distributed planning +DEBUG: recursively planning left side of the right join since the outer side is a recurring rel +DEBUG: recursively planning distributed relation "distributed_table" since it is part of a distributed join node that is outer joined with a recurring rel +DEBUG: Wrapping relation "distributed_table" to a subquery +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM query_single_shard_table.distributed_table WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT "?column?" AS user_id, b AS value_1 FROM (SELECT ((cte.a OPERATOR(pg_catalog.+) 5) OPERATOR(pg_catalog.*) '-1'::integer), cte.b FROM (SELECT DISTINCT reference_table.a, 1 AS b FROM ((SELECT distributed_table_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) distributed_table_1) distributed_table RIGHT JOIN query_single_shard_table.reference_table USING (a))) cte) citus_insert_select_subquery("?column?", b) +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- .. and via SELECT's cte list too +INSERT INTO raw_events_second (user_id, value_1) +WITH cte AS ( + SELECT DISTINCT(reference_table.a) AS a, 1 AS b + FROM distributed_table RIGHT JOIN reference_table USING (a) +) + SELECT (a+5)*2, b FROM cte; +DEBUG: CTE cte is going to be inlined via distributed planning +DEBUG: cannot perform a lateral outer join when a distributed subquery references a reference table +DEBUG: recursively planning left side of the right join since the outer side is a recurring rel +DEBUG: recursively planning distributed relation "distributed_table" since it is part of a distributed join node that is outer joined with a recurring rel +DEBUG: Wrapping relation "distributed_table" to a subquery +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM query_single_shard_table.distributed_table WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT ((a OPERATOR(pg_catalog.+) 5) OPERATOR(pg_catalog.*) 2) AS user_id, b AS value_1 FROM (SELECT DISTINCT reference_table.a, 1 AS b FROM ((SELECT distributed_table_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) distributed_table_1) distributed_table RIGHT JOIN query_single_shard_table.reference_table USING (a))) cte +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- using set operations +INSERT INTO + raw_events_first(user_id) + (SELECT user_id FROM raw_events_first) INTERSECT + (SELECT user_id FROM raw_events_first); +DEBUG: cannot push down this subquery +DETAIL: Intersect and Except are currently unsupported +DEBUG: Collecting INSERT ... SELECT results on coordinator +INSERT INTO + raw_events_first(user_id) + (SELECT user_id FROM users_ref_table) INTERSECT + (SELECT user_id FROM raw_events_first); +DEBUG: cannot push down this subquery +DETAIL: Intersect and Except are currently unsupported +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- group by clause inside subquery +INSERT INTO agg_events + (user_id) +SELECT f2.id FROM +(SELECT + id +FROM (SELECT raw_events_second.user_id AS id + FROM raw_events_first, + raw_events_second + WHERE raw_events_first.user_id = raw_events_second.user_id ) AS foo) as f +INNER JOIN +(SELECT v4, + v1, + id +FROM (SELECT SUM(raw_events_second.value_4) AS v4, + SUM(raw_events_first.value_1) AS v1, + raw_events_second.user_id AS id + FROM raw_events_first, + raw_events_second + WHERE raw_events_first.user_id = raw_events_second.user_id + GROUP BY raw_events_second.user_id + HAVING SUM(raw_events_second.value_4) > 1000) AS foo2 ) as f2 +ON (f.id = f2.id) +WHERE f.id IN (SELECT user_id + FROM raw_events_second); +-- group by clause inside lateral subquery +INSERT INTO agg_events (user_id, value_4_agg) +SELECT + averages.user_id, avg(averages.value_4) +FROM + (SELECT + t1.user_id + FROM + raw_events_second t1 JOIN raw_events_second t2 on (t1.user_id = t2.user_id) + ) reference_ids + JOIN LATERAL + (SELECT + user_id, value_4 + FROM + raw_events_first) as averages ON averages.value_4 = reference_ids.user_id + GROUP BY averages.user_id; +-- using aggregates +INSERT INTO agg_events + (value_3_agg, + value_4_agg, + value_1_agg, + value_2_agg, + user_id) +SELECT SUM(value_3), + Count(value_4), + user_id, + SUM(value_1), + Avg(value_2) +FROM raw_events_first +GROUP BY user_id; +INSERT INTO agg_events (value_3_agg, value_1_agg) +SELECT AVG(user_id), SUM(user_id) +FROM users_ref_table +GROUP BY user_id; +-- using generate_series +INSERT INTO raw_events_first (user_id, value_1, value_2) +SELECT s, s, s FROM generate_series(1, 5) s; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +CREATE SEQUENCE insert_select_test_seq; +-- nextval() expression in select's targetlist +INSERT INTO raw_events_first (user_id, value_1, value_2) +SELECT s, nextval('insert_select_test_seq'), (random()*10)::int +FROM generate_series(100, 105) s; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- non-immutable function +INSERT INTO modify_fast_path (key, value_1) VALUES (2,1) RETURNING value_1, random() * key; +ERROR: non-IMMUTABLE functions are not allowed in the RETURNING clause +SET client_min_messages TO DEBUG2; +-- update / delete +UPDATE nullkey_c1_t1 SET a = 1 WHERE b = 5; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +UPDATE nullkey_c1_t1 SET a = 1 WHERE a = 5; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +UPDATE nullkey_c1_t1 SET a = random(); +DEBUG: functions used in UPDATE queries on distributed tables must not be VOLATILE +ERROR: functions used in UPDATE queries on distributed tables must not be VOLATILE +UPDATE nullkey_c1_t1 SET a = 1 WHERE a = random(); +DEBUG: functions used in the WHERE/ON/WHEN clause of modification queries on distributed tables must not be VOLATILE +ERROR: functions used in the WHERE/ON/WHEN clause of modification queries on distributed tables must not be VOLATILE +DELETE FROM nullkey_c1_t1 WHERE b = 5; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DELETE FROM nullkey_c1_t1 WHERE a = random(); +DEBUG: functions used in the WHERE/ON/WHEN clause of modification queries on distributed tables must not be VOLATILE +ERROR: functions used in the WHERE/ON/WHEN clause of modification queries on distributed tables must not be VOLATILE +-- simple update queries between different table types / colocated tables +UPDATE nullkey_c1_t1 SET b = 5 FROM nullkey_c1_t2 WHERE nullkey_c1_t1.b = nullkey_c1_t2.b; +DEBUG: Creating router plan +UPDATE nullkey_c1_t1 SET b = 5 FROM nullkey_c2_t1 WHERE nullkey_c1_t1.b = nullkey_c2_t1.b; +DEBUG: found no worker with all shard placements +ERROR: found no worker with all shard placements +UPDATE nullkey_c1_t1 SET b = 5 FROM reference_table WHERE nullkey_c1_t1.b = reference_table.b; +DEBUG: Creating router plan +UPDATE nullkey_c1_t1 SET b = 5 FROM distributed_table WHERE nullkey_c1_t1.b = distributed_table.b; +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +UPDATE nullkey_c1_t1 SET b = 5 FROM distributed_table WHERE nullkey_c1_t1.b = distributed_table.a; +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +UPDATE nullkey_c1_t1 SET b = 5 FROM citus_local_table WHERE nullkey_c1_t1.b = citus_local_table.b; +DEBUG: local table citus_local_table cannot be joined with these distributed tables +DEBUG: Wrapping relation "citus_local_table" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT b FROM query_single_shard_table.citus_local_table WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: UPDATE query_single_shard_table.nullkey_c1_t1 SET b = 5 FROM (SELECT NULL::integer AS a, citus_local_table_1.b FROM (SELECT intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(b integer)) citus_local_table_1) citus_local_table WHERE (nullkey_c1_t1.b OPERATOR(pg_catalog.=) citus_local_table.b) +DEBUG: Creating router plan +UPDATE nullkey_c1_t1 SET b = 5 FROM postgres_local_table WHERE nullkey_c1_t1.b = postgres_local_table.b; +DEBUG: relation postgres_local_table is not distributed +DEBUG: Wrapping relation "postgres_local_table" to a subquery +DEBUG: generating subplan XXX_1 for subquery SELECT b FROM query_single_shard_table.postgres_local_table WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: UPDATE query_single_shard_table.nullkey_c1_t1 SET b = 5 FROM (SELECT NULL::integer AS a, postgres_local_table_1.b FROM (SELECT intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(b integer)) postgres_local_table_1) postgres_local_table WHERE (nullkey_c1_t1.b OPERATOR(pg_catalog.=) postgres_local_table.b) +DEBUG: Creating router plan +UPDATE reference_table SET b = 5 FROM nullkey_c1_t1 WHERE nullkey_c1_t1.b = reference_table.b; +ERROR: cannot perform select on a distributed table and modify a reference table +UPDATE distributed_table SET b = 5 FROM nullkey_c1_t1 WHERE nullkey_c1_t1.b = distributed_table.b; +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +UPDATE distributed_table SET b = 5 FROM nullkey_c1_t1 WHERE nullkey_c1_t1.b = distributed_table.a; +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +UPDATE citus_local_table SET b = 5 FROM nullkey_c1_t1 WHERE nullkey_c1_t1.b = citus_local_table.b; +DEBUG: local table citus_local_table cannot be joined with these distributed tables +DEBUG: Wrapping relation "nullkey_c1_t1" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT b FROM query_single_shard_table.nullkey_c1_t1 WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: UPDATE query_single_shard_table.citus_local_table SET b = 5 FROM (SELECT NULL::integer AS a, nullkey_c1_t1_1.b FROM (SELECT intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(b integer)) nullkey_c1_t1_1) nullkey_c1_t1 WHERE (nullkey_c1_t1.b OPERATOR(pg_catalog.=) citus_local_table.b) +DEBUG: Creating router plan +UPDATE postgres_local_table SET b = 5 FROM nullkey_c1_t1 WHERE nullkey_c1_t1.b = postgres_local_table.b; +DEBUG: relation postgres_local_table is not distributed +DEBUG: Wrapping relation "nullkey_c1_t1" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT b FROM query_single_shard_table.nullkey_c1_t1 WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: UPDATE query_single_shard_table.postgres_local_table SET b = 5 FROM (SELECT NULL::integer AS a, nullkey_c1_t1_1.b FROM (SELECT intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(b integer)) nullkey_c1_t1_1) nullkey_c1_t1 WHERE (nullkey_c1_t1.b OPERATOR(pg_catalog.=) postgres_local_table.b) +DEBUG: Creating router plan +-- simple delete queries between different table types / colocated tables +DELETE FROM nullkey_c1_t1 USING nullkey_c1_t2 WHERE nullkey_c1_t1.b = nullkey_c1_t2.b; +DEBUG: Creating router plan +DELETE FROM nullkey_c1_t1 USING nullkey_c2_t1 WHERE nullkey_c1_t1.b = nullkey_c2_t1.b; +DEBUG: found no worker with all shard placements +ERROR: found no worker with all shard placements +DELETE FROM nullkey_c1_t1 USING reference_table WHERE nullkey_c1_t1.b = reference_table.b; +DEBUG: Creating router plan +DELETE FROM nullkey_c1_t1 USING distributed_table WHERE nullkey_c1_t1.b = distributed_table.b; +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DELETE FROM nullkey_c1_t1 USING distributed_table WHERE nullkey_c1_t1.b = distributed_table.a; +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DELETE FROM nullkey_c1_t1 USING citus_local_table WHERE nullkey_c1_t1.b = citus_local_table.b; +DEBUG: local table citus_local_table cannot be joined with these distributed tables +DEBUG: Wrapping relation "citus_local_table" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT b FROM query_single_shard_table.citus_local_table WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: DELETE FROM query_single_shard_table.nullkey_c1_t1 USING (SELECT NULL::integer AS a, citus_local_table_1.b FROM (SELECT intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(b integer)) citus_local_table_1) citus_local_table WHERE (nullkey_c1_t1.b OPERATOR(pg_catalog.=) citus_local_table.b) +DEBUG: Creating router plan +DELETE FROM nullkey_c1_t1 USING postgres_local_table WHERE nullkey_c1_t1.b = postgres_local_table.b; +DEBUG: relation postgres_local_table is not distributed +DEBUG: Wrapping relation "postgres_local_table" to a subquery +DEBUG: generating subplan XXX_1 for subquery SELECT b FROM query_single_shard_table.postgres_local_table WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: DELETE FROM query_single_shard_table.nullkey_c1_t1 USING (SELECT NULL::integer AS a, postgres_local_table_1.b FROM (SELECT intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(b integer)) postgres_local_table_1) postgres_local_table WHERE (nullkey_c1_t1.b OPERATOR(pg_catalog.=) postgres_local_table.b) +DEBUG: Creating router plan +DELETE FROM reference_table USING nullkey_c1_t1 WHERE nullkey_c1_t1.b = reference_table.b; +ERROR: cannot perform select on a distributed table and modify a reference table +DELETE FROM distributed_table USING nullkey_c1_t1 WHERE nullkey_c1_t1.b = distributed_table.b; +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DELETE FROM distributed_table USING nullkey_c1_t1 WHERE nullkey_c1_t1.b = distributed_table.a; +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DELETE FROM citus_local_table USING nullkey_c1_t1 WHERE nullkey_c1_t1.b = citus_local_table.b; +DEBUG: local table citus_local_table cannot be joined with these distributed tables +DEBUG: Wrapping relation "nullkey_c1_t1" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT b FROM query_single_shard_table.nullkey_c1_t1 WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: DELETE FROM query_single_shard_table.citus_local_table USING (SELECT NULL::integer AS a, nullkey_c1_t1_1.b FROM (SELECT intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(b integer)) nullkey_c1_t1_1) nullkey_c1_t1 WHERE (nullkey_c1_t1.b OPERATOR(pg_catalog.=) citus_local_table.b) +DEBUG: Creating router plan +DELETE FROM postgres_local_table USING nullkey_c1_t1 WHERE nullkey_c1_t1.b = postgres_local_table.b; +DEBUG: relation postgres_local_table is not distributed +DEBUG: Wrapping relation "nullkey_c1_t1" to a subquery +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT b FROM query_single_shard_table.nullkey_c1_t1 WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: DELETE FROM query_single_shard_table.postgres_local_table USING (SELECT NULL::integer AS a, nullkey_c1_t1_1.b FROM (SELECT intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(b integer)) nullkey_c1_t1_1) nullkey_c1_t1 WHERE (nullkey_c1_t1.b OPERATOR(pg_catalog.=) postgres_local_table.b) +DEBUG: Creating router plan +-- slightly more complex update queries +UPDATE nullkey_c1_t1 SET b = 5 WHERE nullkey_c1_t1.b IN (SELECT b FROM distributed_table); +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for subquery SELECT b FROM query_single_shard_table.distributed_table +DEBUG: Plan XXX query after replacing subqueries and CTEs: UPDATE query_single_shard_table.nullkey_c1_t1 SET b = 5 WHERE (b OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(b integer))) +DEBUG: Creating router plan +WITH cte AS materialized( + SELECT * FROM distributed_table +) +UPDATE nullkey_c1_t1 SET b = 5 FROM cte WHERE nullkey_c1_t1.b = cte.a; +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: generating subplan XXX_1 for CTE cte: SELECT a, b FROM query_single_shard_table.distributed_table +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Plan XXX query after replacing subqueries and CTEs: UPDATE query_single_shard_table.nullkey_c1_t1 SET b = 5 FROM (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) cte WHERE (nullkey_c1_t1.b OPERATOR(pg_catalog.=) cte.a) +DEBUG: Creating router plan +WITH cte AS ( + SELECT reference_table.a AS a, 1 AS b + FROM distributed_table RIGHT JOIN reference_table USING (a) +) +UPDATE nullkey_c1_t1 SET b = 5 WHERE nullkey_c1_t1.b IN (SELECT b FROM cte); +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: generating subplan XXX_1 for CTE cte: SELECT reference_table.a, 1 AS b FROM (query_single_shard_table.distributed_table RIGHT JOIN query_single_shard_table.reference_table USING (a)) +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: recursively planning left side of the right join since the outer side is a recurring rel +DEBUG: recursively planning distributed relation "distributed_table" since it is part of a distributed join node that is outer joined with a recurring rel +DEBUG: Wrapping relation "distributed_table" to a subquery +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM query_single_shard_table.distributed_table WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT reference_table.a, 1 AS b FROM ((SELECT distributed_table_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) distributed_table_1) distributed_table RIGHT JOIN query_single_shard_table.reference_table USING (a)) +DEBUG: Creating router plan +DEBUG: Plan XXX query after replacing subqueries and CTEs: UPDATE query_single_shard_table.nullkey_c1_t1 SET b = 5 WHERE (b OPERATOR(pg_catalog.=) ANY (SELECT cte.b FROM (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) cte)) +DEBUG: Creating router plan +UPDATE nullkey_c1_t1 SET b = 5 FROM reference_table WHERE EXISTS ( + SELECT 1 FROM reference_table LEFT JOIN nullkey_c1_t1 USING (a) WHERE nullkey_c1_t1.b IS NULL +); +DEBUG: Creating router plan +UPDATE nullkey_c1_t1 tx SET b = ( + SELECT nullkey_c1_t2.b FROM nullkey_c1_t2 JOIN nullkey_c1_t1 ON (nullkey_c1_t1.a != nullkey_c1_t2.a) WHERE nullkey_c1_t1.a = tx.a ORDER BY 1 LIMIT 1 +); +DEBUG: Creating router plan +UPDATE nullkey_c1_t1 tx SET b = t2.b FROM nullkey_c1_t1 t1 JOIN nullkey_c1_t2 t2 ON (t1.a = t2.a); +DEBUG: Creating router plan +WITH cte AS ( + SELECT * FROM nullkey_c1_t2 ORDER BY 1,2 LIMIT 10 +) +UPDATE nullkey_c1_t1 SET b = 5 WHERE nullkey_c1_t1.a IN (SELECT b FROM cte); +DEBUG: Creating router plan +UPDATE modify_fast_path SET value_1 = value_1 + 12 * value_1 WHERE key = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +UPDATE modify_fast_path SET value_1 = NULL WHERE value_1 = 15 AND (key = 1 OR value_2 = 'citus'); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +UPDATE modify_fast_path SET value_1 = 5 WHERE key = 2 RETURNING value_1 * 15, value_1::numeric * 16; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + ?column? | ?column? +--------------------------------------------------------------------- +(0 rows) + +UPDATE modify_fast_path + SET value_1 = 1 + FROM modify_fast_path_reference + WHERE + modify_fast_path.key = modify_fast_path_reference.key AND + modify_fast_path.key = 1 AND + modify_fast_path_reference.key = 1; +DEBUG: Creating router plan +PREPARE p1 (int, int, int) AS + UPDATE modify_fast_path SET value_1 = value_1 + $1 WHERE key = $2 AND value_1 = $3; +EXECUTE p1(1,1,1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE p1(2,2,2); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE p1(3,3,3); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE p1(4,4,4); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE p1(5,5,5); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE p1(6,6,6); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE p1(7,7,7); +PREPARE prepared_zero_shard_update(int) AS UPDATE modify_fast_path SET value_1 = 1 WHERE key = $1 AND false; +EXECUTE prepared_zero_shard_update(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE prepared_zero_shard_update(2); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE prepared_zero_shard_update(3); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE prepared_zero_shard_update(4); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE prepared_zero_shard_update(5); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE prepared_zero_shard_update(6); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE prepared_zero_shard_update(7); +-- slightly more complex delete queries +DELETE FROM nullkey_c1_t1 WHERE nullkey_c1_t1.b IN (SELECT b FROM distributed_table); +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for subquery SELECT b FROM query_single_shard_table.distributed_table +DEBUG: Plan XXX query after replacing subqueries and CTEs: DELETE FROM query_single_shard_table.nullkey_c1_t1 WHERE (b OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(b integer))) +DEBUG: Creating router plan +WITH cte AS materialized( + SELECT * FROM distributed_table +) +DELETE FROM nullkey_c1_t1 USING cte WHERE nullkey_c1_t1.b = cte.a; +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: generating subplan XXX_1 for CTE cte: SELECT a, b FROM query_single_shard_table.distributed_table +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Plan XXX query after replacing subqueries and CTEs: DELETE FROM query_single_shard_table.nullkey_c1_t1 USING (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) cte WHERE (nullkey_c1_t1.b OPERATOR(pg_catalog.=) cte.a) +DEBUG: Creating router plan +WITH cte AS ( + SELECT reference_table.a AS a, 1 AS b + FROM distributed_table RIGHT JOIN reference_table USING (a) +) +DELETE FROM nullkey_c1_t1 WHERE nullkey_c1_t1.b IN (SELECT b FROM cte); +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +DEBUG: generating subplan XXX_1 for CTE cte: SELECT reference_table.a, 1 AS b FROM (query_single_shard_table.distributed_table RIGHT JOIN query_single_shard_table.reference_table USING (a)) +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: recursively planning left side of the right join since the outer side is a recurring rel +DEBUG: recursively planning distributed relation "distributed_table" since it is part of a distributed join node that is outer joined with a recurring rel +DEBUG: Wrapping relation "distributed_table" to a subquery +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM query_single_shard_table.distributed_table WHERE true +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT reference_table.a, 1 AS b FROM ((SELECT distributed_table_1.a, NULL::integer AS b FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) distributed_table_1) distributed_table RIGHT JOIN query_single_shard_table.reference_table USING (a)) +DEBUG: Creating router plan +DEBUG: Plan XXX query after replacing subqueries and CTEs: DELETE FROM query_single_shard_table.nullkey_c1_t1 WHERE (b OPERATOR(pg_catalog.=) ANY (SELECT cte.b FROM (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) cte)) +DEBUG: Creating router plan +DELETE FROM nullkey_c1_t1 USING reference_table WHERE EXISTS ( + SELECT 1 FROM reference_table LEFT JOIN nullkey_c1_t1 USING (a) WHERE nullkey_c1_t1.b IS NULL +); +DEBUG: Creating router plan +DELETE FROM nullkey_c1_t1 tx USING nullkey_c1_t1 t1 JOIN nullkey_c1_t2 t2 ON (t1.a = t2.a); +DEBUG: Creating router plan +WITH cte AS ( + SELECT * FROM nullkey_c1_t2 ORDER BY 1,2 LIMIT 10 +) +DELETE FROM nullkey_c1_t1 WHERE nullkey_c1_t1.a IN (SELECT b FROM cte); +DEBUG: Creating router plan +DELETE FROM modify_fast_path WHERE value_1 = 15 AND (key = 1 OR value_2 = 'citus'); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DELETE FROM modify_fast_path WHERE key = 2 RETURNING value_1 * 15, value_1::numeric * 16; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + ?column? | ?column? +--------------------------------------------------------------------- +(0 rows) + +DELETE FROM modify_fast_path + USING modify_fast_path_reference + WHERE + modify_fast_path.key = modify_fast_path_reference.key AND + modify_fast_path.key = 1 AND + modify_fast_path_reference.key = 1; +DEBUG: Creating router plan +PREPARE p2 (int, int, int) AS + DELETE FROM modify_fast_path WHERE key = ($2)*$1 AND value_1 = $3; +EXECUTE p2(1,1,1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE p2(2,2,2); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE p2(3,3,3); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE p2(4,4,4); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE p2(5,5,5); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE p2(6,6,6); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE p2(7,7,7); +PREPARE prepared_zero_shard_delete(int) AS DELETE FROM modify_fast_path WHERE key = $1 AND false; +EXECUTE prepared_zero_shard_delete(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE prepared_zero_shard_delete(2); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE prepared_zero_shard_delete(3); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE prepared_zero_shard_delete(4); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE prepared_zero_shard_delete(5); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE prepared_zero_shard_delete(6); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +EXECUTE prepared_zero_shard_delete(7); +-- test modifying ctes +WITH cte AS ( + UPDATE modify_fast_path SET value_1 = value_1 + 1 WHERE key = 1 RETURNING * +) +SELECT * FROM cte; +DEBUG: Creating router plan + key | value_1 | value_2 +--------------------------------------------------------------------- +(0 rows) + +WITH cte AS ( + DELETE FROM modify_fast_path WHERE key = 1 RETURNING * +) +SELECT * FROM modify_fast_path; +DEBUG: Creating router plan + key | value_1 | value_2 +--------------------------------------------------------------------- +(0 rows) + +WITH cte AS ( + DELETE FROM modify_fast_path WHERE key = 1 RETURNING * +) +SELECT * FROM modify_fast_path_reference WHERE key IN (SELECT key FROM cte); +DEBUG: Creating router plan + key | value_1 | value_2 +--------------------------------------------------------------------- +(0 rows) + +WITH cte AS ( + DELETE FROM reference_table WHERE a = 1 RETURNING * +) +SELECT * FROM nullkey_c1_t1 WHERE a IN (SELECT a FROM cte); +DEBUG: cannot router plan modification of a non-distributed table +DEBUG: generating subplan XXX_1 for CTE cte: DELETE FROM query_single_shard_table.reference_table WHERE (a OPERATOR(pg_catalog.=) 1) RETURNING a, b +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT a, b FROM query_single_shard_table.nullkey_c1_t1 WHERE (a OPERATOR(pg_catalog.=) ANY (SELECT cte.a FROM (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) cte)) +DEBUG: Creating router plan + a | b +--------------------------------------------------------------------- +(0 rows) + +WITH cte AS ( + DELETE FROM nullkey_c1_t1 WHERE a = 1 RETURNING * +) +SELECT * FROM nullkey_c1_t2 WHERE a IN (SELECT a FROM cte); +DEBUG: Creating router plan + a | b +--------------------------------------------------------------------- +(0 rows) + +WITH cte AS ( + DELETE FROM nullkey_c1_t1 WHERE a = 1 RETURNING * +) +SELECT * FROM nullkey_c2_t1 WHERE a IN (SELECT a FROM cte); +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: generating subplan XXX_1 for CTE cte: DELETE FROM query_single_shard_table.nullkey_c1_t1 WHERE (a OPERATOR(pg_catalog.=) 1) RETURNING a, b +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT a, b FROM query_single_shard_table.nullkey_c2_t1 WHERE (a OPERATOR(pg_catalog.=) ANY (SELECT cte.a FROM (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) cte)) +DEBUG: Creating router plan + a | b +--------------------------------------------------------------------- +(0 rows) + +WITH cte AS ( + DELETE FROM nullkey_c1_t1 WHERE a = 1 RETURNING * +) +SELECT * FROM distributed_table WHERE a IN (SELECT a FROM cte); +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: generating subplan XXX_1 for CTE cte: DELETE FROM query_single_shard_table.nullkey_c1_t1 WHERE (a OPERATOR(pg_catalog.=) 1) RETURNING a, b +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT a, b FROM query_single_shard_table.distributed_table WHERE (a OPERATOR(pg_catalog.=) ANY (SELECT cte.a FROM (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) cte)) +DEBUG: Router planner cannot handle multi-shard select queries + a | b +--------------------------------------------------------------------- +(0 rows) + +-- Below two queries fail very late when +-- citus.enable_non_colocated_router_query_pushdown is set to on. +SET citus.enable_non_colocated_router_query_pushdown TO ON; +WITH cte AS ( + DELETE FROM distributed_table WHERE a = 1 RETURNING * +) +SELECT * FROM nullkey_c1_t1 WHERE a IN (SELECT a FROM cte); +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 1 +ERROR: relation "query_single_shard_table.nullkey_c1_t1_1620000" does not exist +CONTEXT: while executing command on localhost:xxxxx +WITH cte AS ( + DELETE FROM distributed_table WHERE a = 1 RETURNING * +) +SELECT * FROM nullkey_c1_t1 WHERE b IN (SELECT b FROM cte); +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 1 +ERROR: relation "query_single_shard_table.nullkey_c1_t1_1620000" does not exist +CONTEXT: while executing command on localhost:xxxxx +SET citus.enable_non_colocated_router_query_pushdown TO OFF; +WITH cte AS ( + DELETE FROM distributed_table WHERE a = 1 RETURNING * +) +SELECT * FROM nullkey_c1_t1 WHERE a IN (SELECT a FROM cte); +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: generating subplan XXX_1 for CTE cte: DELETE FROM query_single_shard_table.distributed_table WHERE (a OPERATOR(pg_catalog.=) 1) RETURNING a, b +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 1 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT a, b FROM query_single_shard_table.nullkey_c1_t1 WHERE (a OPERATOR(pg_catalog.=) ANY (SELECT cte.a FROM (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) cte)) +DEBUG: Creating router plan + a | b +--------------------------------------------------------------------- +(0 rows) + +WITH cte AS ( + DELETE FROM distributed_table WHERE a = 1 RETURNING * +) +SELECT * FROM nullkey_c1_t1 WHERE b IN (SELECT b FROM cte); +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: generating subplan XXX_1 for CTE cte: DELETE FROM query_single_shard_table.distributed_table WHERE (a OPERATOR(pg_catalog.=) 1) RETURNING a, b +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 1 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT a, b FROM query_single_shard_table.nullkey_c1_t1 WHERE (b OPERATOR(pg_catalog.=) ANY (SELECT cte.b FROM (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) cte)) +DEBUG: Creating router plan + a | b +--------------------------------------------------------------------- +(0 rows) + +RESET citus.enable_non_colocated_router_query_pushdown; +WITH cte AS ( + UPDATE modify_fast_path SET value_1 = value_1 + 1 WHERE key = 1 RETURNING * +) +UPDATE modify_fast_path SET value_1 = value_1 + 1 WHERE key = 1; +DEBUG: Creating router plan +WITH cte AS ( + DELETE FROM modify_fast_path WHERE key = 1 RETURNING * +) +DELETE FROM modify_fast_path WHERE key = 1; +DEBUG: Creating router plan +-- test window functions +SELECT + user_id, avg(avg(value_3)) OVER (PARTITION BY user_id, MIN(value_2)) +FROM + raw_events_first +GROUP BY + 1 +ORDER BY + 2 DESC NULLS LAST, 1 DESC; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan + user_id | avg +--------------------------------------------------------------------- + 6 | 6000.1 + 5 | 5000.1 + 4 | 4000.1 + 3 | 3000.1 + 2 | 2000.1 + 1 | 1000.1 + 105 | + 104 | + 103 | + 102 | + 101 | + 100 | +(12 rows) + +SELECT + user_id, max(value_1) OVER (PARTITION BY user_id, MIN(value_2)) +FROM ( + SELECT + DISTINCT us.user_id, us.value_2, us.value_1, random() as r1 + FROM + raw_events_first as us, raw_events_second + WHERE + us.user_id = raw_events_second.user_id + ORDER BY + user_id, value_2 + ) s +GROUP BY + 1, value_1 +ORDER BY + 2 DESC, 1; +DEBUG: Creating router plan + user_id | max +--------------------------------------------------------------------- + 1 | + 2 | + 3 | + 4 | + 5 | + 6 | + 6 | 60 + 5 | 50 + 4 | 40 + 3 | 30 + 2 | 20 + 1 | 10 + 5 | 5 + 4 | 4 + 3 | 3 + 2 | 2 + 1 | 1 +(17 rows) + +SELECT + DISTINCT ON (raw_events_second.user_id, rnk) raw_events_second.user_id, rank() OVER my_win AS rnk +FROM + raw_events_second, raw_events_first +WHERE + raw_events_first.user_id = raw_events_second.user_id +WINDOW + my_win AS (PARTITION BY raw_events_second.user_id, raw_events_first.value_1 ORDER BY raw_events_second.time DESC) +ORDER BY + rnk DESC, 1 DESC +LIMIT 10; +DEBUG: Creating router plan + user_id | rnk +--------------------------------------------------------------------- + 6 | 2 + 5 | 2 + 4 | 2 + 3 | 2 + 2 | 2 + 1 | 2 + 6 | 1 + 5 | 1 + 4 | 1 + 3 | 1 +(10 rows) + +-- more tests with ctes and subqueries +-- CTEs are recursively planned, and subquery foo is also recursively planned. +-- Then the final plan becomes a router plan. +WITH cte AS MATERIALIZED ( + WITH local_cte AS MATERIALIZED ( + SELECT * FROM users_table_local + ), + dist_cte AS MATERIALIZED ( + SELECT user_id FROM colocated_events_table + ) + SELECT dist_cte.user_id FROM local_cte JOIN dist_cte ON dist_cte.user_id=local_cte.user_id +) +SELECT count(*) +FROM cte, + ( + SELECT DISTINCT users_table.user_id + FROM users_table, colocated_events_table + WHERE users_table.user_id = colocated_events_table.user_id AND event_type IN (1,2,3,4) + ORDER BY 1 DESC LIMIT 5 + ) AS foo +WHERE foo.user_id = cte.user_id; +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: generating subplan XXX_1 for CTE cte: WITH local_cte AS MATERIALIZED (SELECT users_table_local.user_id, users_table_local."time", users_table_local.value_1, users_table_local.value_2, users_table_local.value_3, users_table_local.value_4 FROM query_single_shard_table.users_table_local), dist_cte AS MATERIALIZED (SELECT colocated_events_table.user_id FROM query_single_shard_table.colocated_events_table) SELECT dist_cte.user_id FROM (local_cte JOIN dist_cte ON ((dist_cte.user_id OPERATOR(pg_catalog.=) local_cte.user_id))) +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: generating subplan XXX_1 for CTE local_cte: SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM query_single_shard_table.users_table_local +DEBUG: generating subplan XXX_2 for CTE dist_cte: SELECT user_id FROM query_single_shard_table.colocated_events_table +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT dist_cte.user_id FROM ((SELECT intermediate_result.user_id, intermediate_result."time", intermediate_result.value_1, intermediate_result.value_2, intermediate_result.value_3, intermediate_result.value_4 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, "time" timestamp without time zone, value_1 integer, value_2 integer, value_3 double precision, value_4 bigint)) local_cte JOIN (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) dist_cte ON ((dist_cte.user_id OPERATOR(pg_catalog.=) local_cte.user_id))) +DEBUG: Creating router plan +DEBUG: Creating router plan +DEBUG: generating subplan XXX_2 for subquery SELECT DISTINCT users_table.user_id FROM query_single_shard_table.users_table, query_single_shard_table.colocated_events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) colocated_events_table.user_id) AND (colocated_events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4]))) ORDER BY users_table.user_id DESC LIMIT 5 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) cte, (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) foo WHERE (foo.user_id OPERATOR(pg_catalog.=) cte.user_id) +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 1644 +(1 row) + +-- CTEs are colocated, route entire query. +WITH cte1 AS ( + SELECT * FROM users_table WHERE user_id = 1 +), cte2 AS ( + SELECT * FROM colocated_events_table WHERE user_id = 1 +) +SELECT cte1.user_id, cte1.value_1, cte2.user_id, cte2.event_type +FROM cte1, cte2 +ORDER BY cte1.user_id, cte1.value_1, cte2.user_id, cte2.event_type +LIMIT 5; +DEBUG: CTE cte1 is going to be inlined via distributed planning +DEBUG: CTE cte2 is going to be inlined via distributed planning +DEBUG: Creating router plan + user_id | value_1 | user_id | event_type +--------------------------------------------------------------------- + 1 | 1 | 1 | 0 + 1 | 1 | 1 | 0 + 1 | 1 | 1 | 1 + 1 | 1 | 1 | 1 + 1 | 1 | 1 | 2 +(5 rows) + +-- CTEs aren't colocated, CTEs become intermediate results. +WITH cte1 AS MATERIALIZED ( + SELECT * FROM users_table WHERE user_id = 1 +), cte2 AS MATERIALIZED ( + SELECT * FROM non_colocated_events_table WHERE user_id = 6 +) +SELECT cte1.user_id, cte1.value_1, cte2.user_id, cte2.user_id +FROM cte1, cte2 +ORDER BY cte1.user_id, cte1.value_1, cte2.user_id, cte2.event_type +LIMIT 5; +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: generating subplan XXX_1 for CTE cte1: SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM query_single_shard_table.users_table WHERE (user_id OPERATOR(pg_catalog.=) 1) +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_2 for CTE cte2: SELECT user_id, "time", event_type, value_2, value_3, value_4 FROM query_single_shard_table.non_colocated_events_table WHERE (user_id OPERATOR(pg_catalog.=) 6) +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT cte1.user_id, cte1.value_1, cte2.user_id, cte2.user_id FROM (SELECT intermediate_result.user_id, intermediate_result."time", intermediate_result.value_1, intermediate_result.value_2, intermediate_result.value_3, intermediate_result.value_4 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, "time" timestamp without time zone, value_1 integer, value_2 integer, value_3 double precision, value_4 bigint)) cte1, (SELECT intermediate_result.user_id, intermediate_result."time", intermediate_result.event_type, intermediate_result.value_2, intermediate_result.value_3, intermediate_result.value_4 FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, "time" timestamp without time zone, event_type integer, value_2 integer, value_3 double precision, value_4 bigint)) cte2 ORDER BY cte1.user_id, cte1.value_1, cte2.user_id, cte2.event_type LIMIT 5 +DEBUG: Creating router plan + user_id | value_1 | user_id | user_id +--------------------------------------------------------------------- + 1 | 1 | 6 | 6 + 1 | 1 | 6 | 6 + 1 | 1 | 6 | 6 + 1 | 1 | 6 | 6 + 1 | 1 | 6 | 6 +(5 rows) + +-- users_table & colocated_users_table are colocated, route entire query. +WITH cte1 AS ( + SELECT * FROM users_table WHERE user_id = 1 +) +UPDATE colocated_users_table dt SET value = cte1.value_1 +FROM cte1 WHERE cte1.user_id = dt.id AND dt.id = 1; +DEBUG: Creating router plan +-- users_table & non_colocated_users_table are not colocated, cte is recursive planned. +WITH cte1 AS ( + SELECT * FROM users_table WHERE user_id = 1 +) +UPDATE non_colocated_users_table dt SET value = cte1.value_1 +FROM cte1 WHERE cte1.user_id = dt.id AND dt.id = 1; +DEBUG: found no worker with all shard placements +DEBUG: generating subplan XXX_1 for CTE cte1: SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM query_single_shard_table.users_table WHERE (user_id OPERATOR(pg_catalog.=) 1) +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan XXX query after replacing subqueries and CTEs: UPDATE query_single_shard_table.non_colocated_users_table dt SET value = cte1.value_1 FROM (SELECT intermediate_result.user_id, intermediate_result."time", intermediate_result.value_1, intermediate_result.value_2, intermediate_result.value_3, intermediate_result.value_4 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, "time" timestamp without time zone, value_1 integer, value_2 integer, value_3 double precision, value_4 bigint)) cte1 WHERE ((cte1.user_id OPERATOR(pg_catalog.=) dt.id) AND (dt.id OPERATOR(pg_catalog.=) 1)) +DEBUG: Creating router plan +-- All relations are not colocated, CTEs become intermediate results. +WITH cte1 AS MATERIALIZED ( + SELECT * FROM users_table WHERE user_id = 1 +), cte2 AS MATERIALIZED ( + SELECT * FROM non_colocated_events_table WHERE user_id = 6 +) +UPDATE non_colocated_users_table dt SET value = cte1.value_1 + cte2.event_type +FROM cte1, cte2 WHERE cte1.user_id = dt.id AND dt.id = 1; +DEBUG: found no worker with all shard placements +DEBUG: generating subplan XXX_1 for CTE cte1: SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM query_single_shard_table.users_table WHERE (user_id OPERATOR(pg_catalog.=) 1) +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_2 for CTE cte2: SELECT user_id, "time", event_type, value_2, value_3, value_4 FROM query_single_shard_table.non_colocated_events_table WHERE (user_id OPERATOR(pg_catalog.=) 6) +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan XXX query after replacing subqueries and CTEs: UPDATE query_single_shard_table.non_colocated_users_table dt SET value = (cte1.value_1 OPERATOR(pg_catalog.+) cte2.event_type) FROM (SELECT intermediate_result.user_id, intermediate_result."time", intermediate_result.value_1, intermediate_result.value_2, intermediate_result.value_3, intermediate_result.value_4 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, "time" timestamp without time zone, value_1 integer, value_2 integer, value_3 double precision, value_4 bigint)) cte1, (SELECT intermediate_result.user_id, intermediate_result."time", intermediate_result.event_type, intermediate_result.value_2, intermediate_result.value_3, intermediate_result.value_4 FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, "time" timestamp without time zone, event_type integer, value_2 integer, value_3 double precision, value_4 bigint)) cte2 WHERE ((cte1.user_id OPERATOR(pg_catalog.=) dt.id) AND (dt.id OPERATOR(pg_catalog.=) 1)) +DEBUG: Creating router plan +-- Volatile function calls should not be routed. +WITH cte1 AS MATERIALIZED (SELECT id, value FROM func()) +UPDATE colocated_users_table dt SET value = cte1.value +FROM cte1 WHERE dt.id = 1; +DEBUG: Router planner doesn't support VOLATILE functions in common table expressions. +DEBUG: generating subplan XXX_1 for CTE cte1: SELECT id, value FROM query_single_shard_table.func() func(id, value) +DEBUG: Plan XXX query after replacing subqueries and CTEs: UPDATE query_single_shard_table.colocated_users_table dt SET value = cte1.value FROM (SELECT intermediate_result.id, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id integer, value integer)) cte1 WHERE (dt.id OPERATOR(pg_catalog.=) 1) +DEBUG: Creating router plan +-- CTEs are recursively planned, and subquery foo is also recursively planned. +WITH cte AS MATERIALIZED ( + WITH local_cte AS MATERIALIZED ( + SELECT * FROM users_table_local + ), + dist_cte AS MATERIALIZED ( + SELECT user_id FROM colocated_events_table + ) + SELECT dist_cte.user_id FROM local_cte JOIN dist_cte ON dist_cte.user_id=local_cte.user_id +) +SELECT count(*) +FROM + cte, + ( + SELECT DISTINCT users_table.user_id + FROM users_table, colocated_events_table + WHERE users_table.user_id = colocated_events_table.user_id AND event_type IN (1,2,3,4) + ORDER BY 1 DESC LIMIT 5 + ) AS foo, colocated_events_table +WHERE foo.user_id = cte.user_id AND colocated_events_table.user_id = cte.user_id; +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: generating subplan XXX_1 for CTE cte: WITH local_cte AS MATERIALIZED (SELECT users_table_local.user_id, users_table_local."time", users_table_local.value_1, users_table_local.value_2, users_table_local.value_3, users_table_local.value_4 FROM query_single_shard_table.users_table_local), dist_cte AS MATERIALIZED (SELECT colocated_events_table.user_id FROM query_single_shard_table.colocated_events_table) SELECT dist_cte.user_id FROM (local_cte JOIN dist_cte ON ((dist_cte.user_id OPERATOR(pg_catalog.=) local_cte.user_id))) +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: generating subplan XXX_1 for CTE local_cte: SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM query_single_shard_table.users_table_local +DEBUG: generating subplan XXX_2 for CTE dist_cte: SELECT user_id FROM query_single_shard_table.colocated_events_table +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT dist_cte.user_id FROM ((SELECT intermediate_result.user_id, intermediate_result."time", intermediate_result.value_1, intermediate_result.value_2, intermediate_result.value_3, intermediate_result.value_4 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, "time" timestamp without time zone, value_1 integer, value_2 integer, value_3 double precision, value_4 bigint)) local_cte JOIN (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) dist_cte ON ((dist_cte.user_id OPERATOR(pg_catalog.=) local_cte.user_id))) +DEBUG: Creating router plan +DEBUG: Creating router plan +DEBUG: generating subplan XXX_2 for subquery SELECT DISTINCT users_table.user_id FROM query_single_shard_table.users_table, query_single_shard_table.colocated_events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) colocated_events_table.user_id) AND (colocated_events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4]))) ORDER BY users_table.user_id DESC LIMIT 5 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) cte, (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) foo, query_single_shard_table.colocated_events_table WHERE ((foo.user_id OPERATOR(pg_catalog.=) cte.user_id) AND (colocated_events_table.user_id OPERATOR(pg_catalog.=) cte.user_id)) +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 30608 +(1 row) + +-- CTEs are replaced and subquery in WHERE is also replaced. +WITH cte AS MATERIALIZED ( + WITH local_cte AS MATERIALIZED ( + SELECT * FROM users_table_local + ), + dist_cte AS MATERIALIZED ( + SELECT user_id FROM colocated_events_table + ) + SELECT dist_cte.user_id FROM local_cte JOIN dist_cte ON dist_cte.user_id=local_cte.user_id +) +SELECT DISTINCT cte.user_id +FROM users_table, cte +WHERE users_table.user_id = cte.user_id AND + users_table.user_id IN ( + SELECT DISTINCT value_2 FROM users_table WHERE value_1 >= 1 AND value_1 <= 20 ORDER BY 1 LIMIT 5 + ) +ORDER BY 1 DESC; +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: generating subplan XXX_1 for CTE cte: WITH local_cte AS MATERIALIZED (SELECT users_table_local.user_id, users_table_local."time", users_table_local.value_1, users_table_local.value_2, users_table_local.value_3, users_table_local.value_4 FROM query_single_shard_table.users_table_local), dist_cte AS MATERIALIZED (SELECT colocated_events_table.user_id FROM query_single_shard_table.colocated_events_table) SELECT dist_cte.user_id FROM (local_cte JOIN dist_cte ON ((dist_cte.user_id OPERATOR(pg_catalog.=) local_cte.user_id))) +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: generating subplan XXX_1 for CTE local_cte: SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM query_single_shard_table.users_table_local +DEBUG: generating subplan XXX_2 for CTE dist_cte: SELECT user_id FROM query_single_shard_table.colocated_events_table +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT dist_cte.user_id FROM ((SELECT intermediate_result.user_id, intermediate_result."time", intermediate_result.value_1, intermediate_result.value_2, intermediate_result.value_3, intermediate_result.value_4 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, "time" timestamp without time zone, value_1 integer, value_2 integer, value_3 double precision, value_4 bigint)) local_cte JOIN (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) dist_cte ON ((dist_cte.user_id OPERATOR(pg_catalog.=) local_cte.user_id))) +DEBUG: Creating router plan +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_2 for subquery SELECT DISTINCT value_2 FROM query_single_shard_table.users_table WHERE ((value_1 OPERATOR(pg_catalog.>=) 1) AND (value_1 OPERATOR(pg_catalog.<=) 20)) ORDER BY value_2 LIMIT 5 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT DISTINCT cte.user_id FROM query_single_shard_table.users_table, (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) cte WHERE ((users_table.user_id OPERATOR(pg_catalog.=) cte.user_id) AND (users_table.user_id OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.value_2 FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(value_2 integer)))) ORDER BY cte.user_id DESC +DEBUG: Creating router plan + user_id +--------------------------------------------------------------------- + 4 + 3 + 2 + 1 +(4 rows) + +-- Subquery in WHERE clause is planned recursively due to the recurring table +-- in FROM clause. +WITH cte AS MATERIALIZED ( + WITH local_cte AS MATERIALIZED ( + SELECT * FROM users_table_local + ), + dist_cte AS MATERIALIZED ( + SELECT user_id FROM colocated_events_table + ) + SELECT dist_cte.user_id FROM local_cte JOIN dist_cte ON dist_cte.user_id=local_cte.user_id +) +SELECT DISTINCT cte.user_id +FROM cte +WHERE cte.user_id IN (SELECT DISTINCT user_id FROM users_table WHERE value_1 >= 1 AND value_1 <= 20) +ORDER BY 1 DESC; +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: generating subplan XXX_1 for CTE cte: WITH local_cte AS MATERIALIZED (SELECT users_table_local.user_id, users_table_local."time", users_table_local.value_1, users_table_local.value_2, users_table_local.value_3, users_table_local.value_4 FROM query_single_shard_table.users_table_local), dist_cte AS MATERIALIZED (SELECT colocated_events_table.user_id FROM query_single_shard_table.colocated_events_table) SELECT dist_cte.user_id FROM (local_cte JOIN dist_cte ON ((dist_cte.user_id OPERATOR(pg_catalog.=) local_cte.user_id))) +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: generating subplan XXX_1 for CTE local_cte: SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM query_single_shard_table.users_table_local +DEBUG: generating subplan XXX_2 for CTE dist_cte: SELECT user_id FROM query_single_shard_table.colocated_events_table +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT dist_cte.user_id FROM ((SELECT intermediate_result.user_id, intermediate_result."time", intermediate_result.value_1, intermediate_result.value_2, intermediate_result.value_3, intermediate_result.value_4 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, "time" timestamp without time zone, value_1 integer, value_2 integer, value_3 double precision, value_4 bigint)) local_cte JOIN (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) dist_cte ON ((dist_cte.user_id OPERATOR(pg_catalog.=) local_cte.user_id))) +DEBUG: Creating router plan +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_2 for subquery SELECT DISTINCT user_id FROM query_single_shard_table.users_table WHERE ((value_1 OPERATOR(pg_catalog.>=) 1) AND (value_1 OPERATOR(pg_catalog.<=) 20)) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT DISTINCT user_id FROM (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) cte WHERE (user_id OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer))) ORDER BY user_id DESC +DEBUG: Creating router plan + user_id +--------------------------------------------------------------------- + 6 + 5 + 4 + 3 + 2 + 1 +(6 rows) + +-- CTEs inside a subquery and the final query becomes a router +-- query. +SELECT + user_id +FROM + ( + WITH cte AS MATERIALIZED ( + SELECT DISTINCT users_table.user_id + FROM users_table, colocated_events_table + WHERE users_table.user_id = colocated_events_table.user_id AND + event_type IN (1,2,3,4) + ) + SELECT * FROM cte ORDER BY 1 DESC + ) AS foo +ORDER BY 1 DESC; +DEBUG: Creating router plan + user_id +--------------------------------------------------------------------- + 6 + 5 + 4 + 3 + 2 + 1 +(6 rows) + +-- CTEs inside a deeper subquery and also the subquery that contains the CTE are +-- recursively planned. +SELECT DISTINCT bar.user_id +FROM + ( + WITH cte AS MATERIALIZED ( + SELECT DISTINCT users_table.user_id + FROM users_table, colocated_events_table + WHERE users_table.user_id = colocated_events_table.user_id AND event_type IN (1,2,3,4) + ) + SELECT * FROM cte ORDER BY 1 DESC + ) AS foo, + ( + SELECT users_table.user_id, some_events.event_type + FROM + users_table, + ( + WITH cte AS MATERIALIZED ( + SELECT event_type, users_table.user_id + FROM users_table, colocated_events_table + WHERE users_table.user_id = colocated_events_table.user_id AND value_1 IN (1,2) + ) SELECT * FROM cte ORDER BY 1 DESC + ) AS some_events + WHERE users_table.user_id = some_events.user_id AND event_type IN (1,2,3,4) + ORDER BY 2,1 LIMIT 2 + ) AS bar +WHERE foo.user_id = bar.user_id +ORDER BY 1 DESC LIMIT 5; +DEBUG: Creating router plan + user_id +--------------------------------------------------------------------- + 1 +(1 row) + +-- Recursively plan subqueries inside the CTEs that contains LIMIT and OFFSET. +WITH cte AS MATERIALIZED ( + WITH local_cte AS MATERIALIZED ( + SELECT * FROM users_table_local + ), + dist_cte AS MATERIALIZED ( + SELECT + user_id + FROM + colocated_events_table, + (SELECT DISTINCT value_2 FROM users_table OFFSET 0) as foo + WHERE + colocated_events_table.user_id = foo.value_2 AND + colocated_events_table.user_id IN (SELECT DISTINCT value_1 FROM users_table ORDER BY 1 LIMIT 3) + ) + SELECT dist_cte.user_id FROM local_cte JOIN dist_cte ON dist_cte.user_id=local_cte.user_id +) +SELECT count(*) +FROM + cte, + ( + SELECT DISTINCT users_table.user_id + FROM users_table, colocated_events_table + WHERE users_table.user_id = colocated_events_table.user_id AND event_type IN (1,2,3,4) + ORDER BY 1 DESC LIMIT 5 + ) AS foo +WHERE foo.user_id = cte.user_id; +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: generating subplan XXX_1 for CTE cte: WITH local_cte AS MATERIALIZED (SELECT users_table_local.user_id, users_table_local."time", users_table_local.value_1, users_table_local.value_2, users_table_local.value_3, users_table_local.value_4 FROM query_single_shard_table.users_table_local), dist_cte AS MATERIALIZED (SELECT colocated_events_table.user_id FROM query_single_shard_table.colocated_events_table, (SELECT DISTINCT users_table.value_2 FROM query_single_shard_table.users_table OFFSET 0) foo WHERE ((colocated_events_table.user_id OPERATOR(pg_catalog.=) foo.value_2) AND (colocated_events_table.user_id OPERATOR(pg_catalog.=) ANY (SELECT DISTINCT users_table.value_1 FROM query_single_shard_table.users_table ORDER BY users_table.value_1 LIMIT 3)))) SELECT dist_cte.user_id FROM (local_cte JOIN dist_cte ON ((dist_cte.user_id OPERATOR(pg_catalog.=) local_cte.user_id))) +DEBUG: Local tables cannot be used in distributed queries. +DEBUG: generating subplan XXX_1 for CTE local_cte: SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM query_single_shard_table.users_table_local +DEBUG: generating subplan XXX_2 for CTE dist_cte: SELECT colocated_events_table.user_id FROM query_single_shard_table.colocated_events_table, (SELECT DISTINCT users_table.value_2 FROM query_single_shard_table.users_table OFFSET 0) foo WHERE ((colocated_events_table.user_id OPERATOR(pg_catalog.=) foo.value_2) AND (colocated_events_table.user_id OPERATOR(pg_catalog.=) ANY (SELECT DISTINCT users_table.value_1 FROM query_single_shard_table.users_table ORDER BY users_table.value_1 LIMIT 3))) +DEBUG: Creating router plan +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT dist_cte.user_id FROM ((SELECT intermediate_result.user_id, intermediate_result."time", intermediate_result.value_1, intermediate_result.value_2, intermediate_result.value_3, intermediate_result.value_4 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, "time" timestamp without time zone, value_1 integer, value_2 integer, value_3 double precision, value_4 bigint)) local_cte JOIN (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) dist_cte ON ((dist_cte.user_id OPERATOR(pg_catalog.=) local_cte.user_id))) +DEBUG: Creating router plan +DEBUG: Creating router plan +DEBUG: generating subplan XXX_2 for subquery SELECT DISTINCT users_table.user_id FROM query_single_shard_table.users_table, query_single_shard_table.colocated_events_table WHERE ((users_table.user_id OPERATOR(pg_catalog.=) colocated_events_table.user_id) AND (colocated_events_table.event_type OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4]))) ORDER BY users_table.user_id DESC LIMIT 5 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) cte, (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) foo WHERE (foo.user_id OPERATOR(pg_catalog.=) cte.user_id) +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 432 +(1 row) + +-- more tests with sublinks and subqueries in targetlist +SELECT event_type, (SELECT e.value_2 FROM users_reference_table WHERE user_id = 1 AND value_1 = 1), (SELECT e.value_2) +FROM non_colocated_events_table e +ORDER BY 1,2 LIMIT 1; +DEBUG: Creating router plan + event_type | value_2 | value_2 +--------------------------------------------------------------------- + 0 | | 0 +(1 row) + +SELECT event_type, (SELECT time FROM users_table WHERE user_id = e.user_id ORDER BY time LIMIT 1) +FROM non_colocated_events_table e +ORDER BY 1,2 LIMIT 1; +DEBUG: found no worker with all shard placements +DEBUG: push down of limit count: 1 +ERROR: cannot push down this subquery +DETAIL: users_table and non_colocated_events_table are not colocated +SELECT event_type, (SELECT max(time) FROM users_table WHERE user_id = e.value_2) +FROM non_colocated_events_table e +ORDER BY 1,2 LIMIT 1; +DEBUG: found no worker with all shard placements +DEBUG: push down of limit count: 1 +ERROR: cannot push down this subquery +DETAIL: users_table and non_colocated_events_table are not colocated +SELECT event_type, (SELECT max(time) FROM users_table) +FROM non_colocated_events_table e +ORDER BY 1,2 LIMIT 1; +DEBUG: found no worker with all shard placements +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT max("time") AS max FROM query_single_shard_table.users_table +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT event_type, (SELECT intermediate_result.max FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(max timestamp without time zone)) AS max FROM query_single_shard_table.non_colocated_events_table e ORDER BY event_type, (SELECT intermediate_result.max FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(max timestamp without time zone)) LIMIT 1 +DEBUG: Creating router plan + event_type | max +--------------------------------------------------------------------- + 0 | Thu Nov 23 17:30:34.635085 2017 +(1 row) + +WITH cte_1 AS (SELECT max(time) FROM users_table) +SELECT event_type, (SELECT * FROM cte_1) +FROM non_colocated_events_table e +ORDER BY 1,2 LIMIT 1; +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT max("time") AS max FROM query_single_shard_table.users_table +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT event_type, (SELECT cte_1.max FROM (SELECT intermediate_result.max FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(max timestamp without time zone)) cte_1) AS max FROM query_single_shard_table.non_colocated_events_table e ORDER BY event_type, (SELECT cte_1.max FROM (SELECT intermediate_result.max FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(max timestamp without time zone)) cte_1) LIMIT 1 +DEBUG: Creating router plan + event_type | max +--------------------------------------------------------------------- + 0 | Thu Nov 23 17:30:34.635085 2017 +(1 row) + +WITH cte_1 AS (SELECT max(time) FROM users_table) +SELECT event_type, (SELECT * FROM cte_1 LIMIT 1) +FROM non_colocated_events_table e +ORDER BY 1,2 LIMIT 1; +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT max("time") AS max FROM query_single_shard_table.users_table +DEBUG: Creating router plan +DEBUG: generating subplan XXX_2 for subquery SELECT max FROM (SELECT intermediate_result.max FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(max timestamp without time zone)) cte_1 LIMIT 1 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT event_type, (SELECT intermediate_result.max FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(max timestamp without time zone)) AS max FROM query_single_shard_table.non_colocated_events_table e ORDER BY event_type, (SELECT intermediate_result.max FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(max timestamp without time zone)) LIMIT 1 +DEBUG: Creating router plan + event_type | max +--------------------------------------------------------------------- + 0 | Thu Nov 23 17:30:34.635085 2017 +(1 row) + +WITH cte_1 AS (SELECT max(time) m FROM users_table) +SELECT count(*), (SELECT * FROM cte_1 c1 join cte_1 c2 using (m)) +FROM non_colocated_events_table e +GROUP BY 2 +ORDER BY 1,2 LIMIT 1; +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: generating subplan XXX_1 for CTE cte_1: SELECT max("time") AS m FROM query_single_shard_table.users_table +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count, (SELECT c1.m FROM ((SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m timestamp without time zone)) c1 JOIN (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m timestamp without time zone)) c2 USING (m))) AS m FROM query_single_shard_table.non_colocated_events_table e GROUP BY (SELECT c1.m FROM ((SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m timestamp without time zone)) c1 JOIN (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m timestamp without time zone)) c2 USING (m))) ORDER BY (count(*)), (SELECT c1.m FROM ((SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m timestamp without time zone)) c1 JOIN (SELECT intermediate_result.m FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(m timestamp without time zone)) c2 USING (m))) LIMIT 1 +DEBUG: Creating router plan + count | m +--------------------------------------------------------------------- + 101 | Thu Nov 23 17:30:34.635085 2017 +(1 row) + +WITH cte_1 AS (SELECT min(user_id) u, max(time) m FROM users_table) +SELECT count(*), (SELECT max(time) FROM users_table WHERE user_id = cte_1.u GROUP BY user_id) +FROM cte_1 +GROUP BY 2 +ORDER BY 1,2 LIMIT 1; +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Creating router plan + count | max +--------------------------------------------------------------------- + 1 | Thu Nov 23 17:30:34.635085 2017 +(1 row) + +SELECT sum(e.user_id) + (SELECT max(value_3) FROM users_table WHERE user_id = e.user_id GROUP BY user_id) +FROM non_colocated_events_table e +GROUP BY e.user_id +ORDER BY 1 LIMIT 3; +DEBUG: found no worker with all shard placements +ERROR: cannot push down subquery on the target list +DETAIL: Subqueries in the SELECT part of the query can only be pushed down if they happen before aggregates and window functions +SELECT e.user_id, sum((SELECT any_value(value_3) FROM users_reference_table WHERE user_id = e.user_id GROUP BY user_id)) OVER (PARTITION BY e.user_id) +FROM non_colocated_events_table e +ORDER BY 1, 2 LIMIT 3; +DEBUG: Creating router plan + user_id | sum +--------------------------------------------------------------------- + 1 | + 1 | + 1 | +(3 rows) + +SELECT (SELECT (SELECT e.user_id + user_id) FROM users_table WHERE user_id = e.user_id GROUP BY user_id) +FROM non_colocated_events_table e +GROUP BY 1 +ORDER BY 1 LIMIT 3; +DEBUG: found no worker with all shard placements +DEBUG: push down of limit count: 3 +ERROR: cannot push down this subquery +DETAIL: users_table and non_colocated_events_table are not colocated +SELECT (SELECT (SELECT e.user_id + user_id) FROM users_reference_table WHERE user_id = e.user_id GROUP BY user_id) +FROM non_colocated_events_table e +GROUP BY 1 +ORDER BY 1 LIMIT 3; +DEBUG: Creating router plan + ?column? +--------------------------------------------------------------------- + +(1 row) + +WITH cte_1 AS (SELECT user_id FROM users_table ORDER BY 1 LIMIT 1) +SELECT (SELECT (SELECT e.user_id + user_id) FROM cte_1 WHERE user_id = e.user_id GROUP BY user_id) +FROM non_colocated_events_table e +GROUP BY 1 +ORDER BY 1 LIMIT 3; +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT user_id FROM query_single_shard_table.users_table ORDER BY user_id LIMIT 1 +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT (SELECT (SELECT (e.user_id OPERATOR(pg_catalog.+) cte_1.user_id)) FROM (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) cte_1 WHERE (cte_1.user_id OPERATOR(pg_catalog.=) e.user_id) GROUP BY cte_1.user_id) FROM query_single_shard_table.non_colocated_events_table e GROUP BY (SELECT (SELECT (e.user_id OPERATOR(pg_catalog.+) cte_1.user_id)) FROM (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) cte_1 WHERE (cte_1.user_id OPERATOR(pg_catalog.=) e.user_id) GROUP BY cte_1.user_id) ORDER BY (SELECT (SELECT (e.user_id OPERATOR(pg_catalog.+) cte_1.user_id)) FROM (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) cte_1 WHERE (cte_1.user_id OPERATOR(pg_catalog.=) e.user_id) GROUP BY cte_1.user_id) LIMIT 3 +DEBUG: Creating router plan + ?column? +--------------------------------------------------------------------- + 2 + +(2 rows) + +SELECT (SELECT (SELECT e.user_id + user_id) FROM (SELECT 1 AS user_id) s WHERE user_id = e.user_id GROUP BY user_id) +FROM non_colocated_events_table e +GROUP BY 1 +ORDER BY 1 LIMIT 3; +DEBUG: Creating router plan + ?column? +--------------------------------------------------------------------- + 2 + +(2 rows) + +CREATE TEMP VIEW view_1 AS (SELECT user_id, value_2 FROM users_table WHERE user_id = 1 AND value_1 = 1 ORDER BY 1,2); +WARNING: "view view_1" has dependency on unsupported object "schema pg_temp_xxx" +DETAIL: "view view_1" will be created only locally +SELECT (SELECT value_2 FROM view_1 WHERE user_id = e.user_id GROUP BY value_2) +FROM non_colocated_events_table e +GROUP BY 1 +ORDER BY 1 LIMIT 3; +DEBUG: found no worker with all shard placements +DEBUG: push down of limit count: 3 +ERROR: cannot push down this subquery +DETAIL: users_table and non_colocated_events_table are not colocated +SELECT + user_id, count(*) +FROM + non_colocated_events_table e1 +GROUP BY user_id + HAVING + count(*) > (SELECT count(*) FROM (SELECT + (SELECT sum(user_id) FROM users_table WHERE user_id = u1.user_id GROUP BY user_id) + FROM users_table u1 + GROUP BY user_id) as foo) ORDER BY 1 DESC; +DEBUG: found no worker with all shard placements +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT count(*) AS count FROM (SELECT (SELECT sum(users_table.user_id) AS sum FROM query_single_shard_table.users_table WHERE (users_table.user_id OPERATOR(pg_catalog.=) u1.user_id) GROUP BY users_table.user_id) AS sum FROM query_single_shard_table.users_table u1 GROUP BY u1.user_id) foo +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT user_id, count(*) AS count FROM query_single_shard_table.non_colocated_events_table e1 GROUP BY user_id HAVING (count(*) OPERATOR(pg_catalog.>) (SELECT intermediate_result.count FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(count bigint))) ORDER BY user_id DESC +DEBUG: Creating router plan + user_id | count +--------------------------------------------------------------------- + 6 | 10 + 5 | 14 + 4 | 17 + 3 | 21 + 2 | 24 + 1 | 15 +(6 rows) + +SELECT count(*) FROM (SELECT + (SELECT user_id FROM users_table WHERE user_id = u1.user_id FOR UPDATE) +FROM users_table u1 +GROUP BY user_id) as foo; +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 6 +(1 row) + +-- test single hash repartition join +SET citus.log_multi_join_order TO ON; +SET client_min_messages TO DEBUG1; +SET citus.enable_repartition_joins TO ON; +SET citus.enable_single_hash_repartition_joins TO ON; +SELECT count(*) FROM nullkey_c1_t1 JOIN distributed_table USING(a); +LOG: join order: [ "nullkey_c1_t1" ][ single hash partition join "distributed_table" ] + count +--------------------------------------------------------------------- + 0 +(1 row) + +select count(*) from nullkey_c1_t1 JOIN nullkey_c2_t2 USING(a); +LOG: join order: [ "nullkey_c1_t1" ][ dual partition join "nullkey_c2_t2" ] + count +--------------------------------------------------------------------- + 0 +(1 row) + +RESET citus.log_multi_join_order; +SET client_min_messages TO DEBUG2; +RESET citus.enable_repartition_joins; +RESET citus.enable_single_hash_repartition_joins; +SET client_min_messages TO DEBUG1; +SET citus.enable_repartition_joins TO ON; +SET citus.log_multi_join_order TO ON; +SELECT count(*), avg(avgsub.a) +FROM ( + SELECT table_0.a + FROM reference_table AS table_0 + INNER JOIN nullkey_c1_t1 AS table_1 USING (a) + INNER JOIN reference_table AS table_2 USING (a) + INNER JOIN nullkey_c2_t1 AS table_3 USING (a) + ORDER BY a LIMIT 7 +) AS avgsub; +LOG: join order: [ "nullkey_c1_t1" ][ reference join "reference_table" ][ reference join "reference_table" ][ dual partition join "nullkey_c2_t1" ] +DEBUG: push down of limit count: 7 +DEBUG: generating subplan XXX_1 for subquery SELECT table_0.a FROM (((query_single_shard_table.reference_table table_0 JOIN query_single_shard_table.nullkey_c1_t1 table_1 USING (a)) JOIN query_single_shard_table.reference_table table_2 USING (a)) JOIN query_single_shard_table.nullkey_c2_t1 table_3 USING (a)) ORDER BY table_0.a LIMIT 7 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count, avg(a) AS avg FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) avgsub + count | avg +--------------------------------------------------------------------- + 0 | +(1 row) + +SET citus.enable_single_hash_repartition_joins TO ON; +-- We prefer dual-hash repartition join over single-hash repartition join +-- even if citus.enable_single_hash_repartition_joins is set to ON. This +-- happens because single shard tables don't have a shard key. +SELECT count(*), avg(avgsub.a) +FROM ( + SELECT table_0.a + FROM reference_table AS table_0 + INNER JOIN nullkey_c1_t1 AS table_1 USING (a) + INNER JOIN reference_table AS table_2 USING (a) + INNER JOIN nullkey_c2_t1 AS table_3 USING (a) + ORDER BY a LIMIT 7 +) AS avgsub; +LOG: join order: [ "nullkey_c1_t1" ][ reference join "reference_table" ][ reference join "reference_table" ][ dual partition join "nullkey_c2_t1" ] +DEBUG: push down of limit count: 7 +DEBUG: generating subplan XXX_1 for subquery SELECT table_0.a FROM (((query_single_shard_table.reference_table table_0 JOIN query_single_shard_table.nullkey_c1_t1 table_1 USING (a)) JOIN query_single_shard_table.reference_table table_2 USING (a)) JOIN query_single_shard_table.nullkey_c2_t1 table_3 USING (a)) ORDER BY table_0.a LIMIT 7 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count, avg(a) AS avg FROM (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) avgsub + count | avg +--------------------------------------------------------------------- + 0 | +(1 row) + +RESET citus.enable_single_hash_repartition_joins; +SET client_min_messages TO DEBUG2; +RESET citus.enable_repartition_joins; +RESET citus.log_multi_join_order; +SELECT count(*), avg(avgsub.a) +FROM ( + SELECT table_0.a + FROM nullkey_c1_t1 AS table_0 + RIGHT JOIN ( + SELECT table_2.a FROM ( + SELECT table_3.a FROM nullkey_c2_t1 AS table_3 + ORDER BY a LIMIT 0 + ) AS table_2 + INNER JOIN nullkey_c2_t1 AS table_4 USING (a) + WHERE table_4.a < 8 + ) AS table_1 USING (a) +) AS avgsub; +DEBUG: router planner does not support queries that reference non-colocated distributed tables +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: generating subplan XXX_1 for subquery SELECT a FROM query_single_shard_table.nullkey_c2_t1 table_3 ORDER BY a LIMIT 0 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count, avg(a) AS avg FROM (SELECT table_0.a FROM (query_single_shard_table.nullkey_c1_t1 table_0 RIGHT JOIN (SELECT table_2.a FROM ((SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) table_2 JOIN query_single_shard_table.nullkey_c2_t1 table_4 USING (a)) WHERE (table_4.a OPERATOR(pg_catalog.<) 8)) table_1 USING (a))) avgsub +DEBUG: router planner does not support queries that reference non-colocated distributed tables +ERROR: cannot perform a lateral outer join when a distributed subquery references complex subqueries, CTEs or local tables +-- test nested exec +CREATE FUNCTION dist_query_single_shard(p_key int) +RETURNS bigint +LANGUAGE plpgsql AS $$ +DECLARE + result bigint; +BEGIN + SELECT count(*) INTO result FROM query_single_shard_table.nullkey_c1_t1 WHERE a = p_key; + RETURN result; +END; +$$; +DEBUG: switching to sequential query execution mode +DETAIL: A command for a distributed function is run. To make sure subsequent commands see the function correctly we need to make sure to use only one connection for all future commands +CREATE FUNCTION ref_query() +RETURNS bigint +LANGUAGE plpgsql AS $$ +DECLARE + result bigint; +BEGIN + SELECT count(*) INTO result FROM query_single_shard_table.reference_table; + RETURN result; +END; +$$; +DEBUG: switching to sequential query execution mode +DETAIL: A command for a distributed function is run. To make sure subsequent commands see the function correctly we need to make sure to use only one connection for all future commands +SELECT dist_query_single_shard(count(*)::int) FROM nullkey_c1_t1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +ERROR: cannot execute a distributed query from a query on a shard +DETAIL: Executing a distributed query in a function call that may be pushed to a remote node can lead to incorrect results. +HINT: Avoid nesting of distributed queries or use alter user current_user set citus.allow_nested_distributed_execution to on to allow it with possible incorrectness. +CONTEXT: SQL statement "SELECT count(*) FROM query_single_shard_table.nullkey_c1_t1 WHERE a = p_key" +PL/pgSQL function query_single_shard_table.dist_query_single_shard(integer) line XX at SQL statement +while executing command on localhost:xxxxx +SELECT ref_query()+count(*) FROM nullkey_c1_t1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +ERROR: cannot execute a distributed query from a query on a shard +DETAIL: Executing a distributed query in a function call that may be pushed to a remote node can lead to incorrect results. +HINT: Avoid nesting of distributed queries or use alter user current_user set citus.allow_nested_distributed_execution to on to allow it with possible incorrectness. +CONTEXT: SQL statement "SELECT count(*) FROM query_single_shard_table.reference_table" +PL/pgSQL function query_single_shard_table.ref_query() line XX at SQL statement +while executing command on localhost:xxxxx +SET client_min_messages TO ERROR; +DROP SCHEMA query_single_shard_table CASCADE; diff --git a/src/test/regress/expected/recurring_outer_join.out b/src/test/regress/expected/recurring_outer_join.out index 3cd7cc6dc..4ff353838 100644 --- a/src/test/regress/expected/recurring_outer_join.out +++ b/src/test/regress/expected/recurring_outer_join.out @@ -2,14 +2,6 @@ CREATE SCHEMA recurring_outer_join; SET search_path TO recurring_outer_join; SET citus.next_shard_id TO 1520000; SET citus.shard_count TO 32; --- idempotently add node to allow this test to run without add_coordinator -SET client_min_messages TO WARNING; -SELECT 1 FROM citus_add_node('localhost', :master_port, groupid => 0); - ?column? ---------------------------------------------------------------------- - 1 -(1 row) - SET client_min_messages TO DEBUG1; CREATE TABLE dist_1 (a int, b int); SELECT create_distributed_table('dist_1', 'a'); @@ -1969,8 +1961,7 @@ BEGIN; FROM ref_1 t1 LEFT JOIN dist_1 t2 ON (t1.a = t2.a); -DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match -DETAIL: The target table's partition column should correspond to a partition column in the subquery. +DEBUG: cannot perform a lateral outer join when a distributed subquery references a reference table DEBUG: recursively planning right side of the left join since the outer side is a recurring rel DEBUG: recursively planning distributed relation "dist_1" "t2" since it is part of a distributed join node that is outer joined with a recurring rel DEBUG: Wrapping relation "dist_1" "t2" to a subquery @@ -1986,9 +1977,7 @@ BEGIN; JOIN (ref_1 t2 LEFT JOIN dist_1 t3 USING(a)) t4 ON (t1.a = t4.a); -DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match -DETAIL: Subquery contains an operator in the same position as the target table's partition column. -HINT: Ensure the target table's partition column has a corresponding simple column reference to a distributed table's partition column in the subquery. +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: recursively planning right side of the left join since the outer side is a recurring rel DEBUG: recursively planning distributed relation "dist_1" "t3" since it is part of a distributed join node that is outer joined with a recurring rel DEBUG: Wrapping relation "dist_1" "t3" to a subquery @@ -2005,7 +1994,7 @@ BEGIN; JOIN (ref_1 t2 LEFT JOIN dist_1 t3 USING(a)) t4 ON (t1.a = t4.a); -DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: recursively planning right side of the left join since the outer side is a recurring rel DEBUG: recursively planning distributed relation "dist_1" "t3" since it is part of a distributed join node that is outer joined with a recurring rel DEBUG: Wrapping relation "dist_1" "t3" to a subquery @@ -2015,9 +2004,3 @@ DEBUG: performing repartitioned INSERT ... SELECT ROLLBACK; SET client_min_messages TO ERROR; DROP SCHEMA recurring_outer_join CASCADE; -SELECT master_remove_node('localhost', :master_port); - master_remove_node ---------------------------------------------------------------------- - -(1 row) - diff --git a/src/test/regress/expected/recursive_dml_with_different_planners_executors.out b/src/test/regress/expected/recursive_dml_with_different_planners_executors.out index 4532b3bb2..029d7b451 100644 --- a/src/test/regress/expected/recursive_dml_with_different_planners_executors.out +++ b/src/test/regress/expected/recursive_dml_with_different_planners_executors.out @@ -71,10 +71,6 @@ UPDATE distributed_table SET dept = foo.max_dept FROM ) as foo WHERE foo.max_dept >= dept and tenant_id = '8'; DEBUG: generating subplan XXX_1 for subquery SELECT max(dept) AS max_dept FROM (SELECT DISTINCT distributed_table_1.tenant_id, distributed_table_1.dept FROM recursive_dml_with_different_planner_executors.distributed_table distributed_table_1) distributed_table WHERE (tenant_id OPERATOR(pg_catalog.=) ANY (SELECT second_distributed_table.tenant_id FROM recursive_dml_with_different_planner_executors.second_distributed_table WHERE (second_distributed_table.dept OPERATOR(pg_catalog.=) ANY (ARRAY[1, 2, 3, 4])))) DEBUG: Plan XXX query after replacing subqueries and CTEs: UPDATE recursive_dml_with_different_planner_executors.distributed_table SET dept = foo.max_dept FROM (SELECT intermediate_result.max_dept FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(max_dept integer)) foo WHERE ((foo.max_dept OPERATOR(pg_catalog.>=) distributed_table.dept) AND (distributed_table.tenant_id OPERATOR(pg_catalog.=) '8'::text)) -RESET client_min_messages; +SET client_min_messages TO WARNING; DROP SCHEMA recursive_dml_with_different_planner_executors CASCADE; -NOTICE: drop cascades to 3 other objects -DETAIL: drop cascades to table distributed_table -drop cascades to table second_distributed_table -drop cascades to table reference_table SET search_path TO public; diff --git a/src/test/regress/expected/recursive_relation_planning_restriction_pushdown.out b/src/test/regress/expected/recursive_relation_planning_restriction_pushdown.out index 6a41c735a..26c4e09b4 100644 --- a/src/test/regress/expected/recursive_relation_planning_restriction_pushdown.out +++ b/src/test/regress/expected/recursive_relation_planning_restriction_pushdown.out @@ -491,18 +491,14 @@ SELECT MAX(x) FROM ( UNION ALL SELECT 1 as x FROM (SELECT 1 FROM tbl1, tbl2 WHERE b > 0) AS s1 WHERE false ) as res; -DEBUG: Wrapping relation "tbl2" to a subquery -DEBUG: generating subplan XXX_1 for subquery SELECT b FROM push_down_filters.tbl2 WHERE (b OPERATOR(pg_catalog.>) 0) -DEBUG: Wrapping relation "tbl2" to a subquery -DEBUG: generating subplan XXX_2 for subquery SELECT b FROM push_down_filters.tbl2 WHERE false -DEBUG: generating subplan XXX_3 for subquery SELECT 1 AS x FROM (SELECT 1 FROM push_down_filters.tbl1, (SELECT tbl2_1.b FROM (SELECT intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(b integer)) tbl2_1) tbl2 WHERE (tbl2.b OPERATOR(pg_catalog.>) 0)) s1("?column?") WHERE true UNION ALL SELECT 1 AS x FROM (SELECT 1 FROM push_down_filters.tbl1, (SELECT tbl2_1.b FROM (SELECT intermediate_result.b FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(b integer)) tbl2_1) tbl2 WHERE (tbl2.b OPERATOR(pg_catalog.>) 0)) s1("?column?") WHERE false -DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT max(x) AS max FROM (SELECT intermediate_result.x FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(x integer)) res max --------------------------------------------------------------------- 1 (1 row) DROP TABLE tbl1, tbl2; +CONTEXT: SQL statement "SELECT citus_drop_all_shards(v_obj.objid, v_obj.schema_name, v_obj.object_name, drop_shards_metadata_only := false)" +PL/pgSQL function citus_drop_trigger() line XX at PERFORM CREATE table tbl2(a int, b int, d int); CREATE table tbl1(a int, b int, c int); INSERT INTO tbl1 VALUES (1,1,1); @@ -563,12 +559,6 @@ SELECT 1 as x FROM (SELECT 1 FROM tbl1, tbl2 WHERE tbl2.b > 0) AS s1 WHERE true UNION ALL SELECT 1 as x FROM (SELECT 1 FROM tbl1, tbl2 WHERE tbl2.b > 0) AS s1 WHERE false ) as res; -DEBUG: Wrapping relation "tbl2" to a subquery -DEBUG: generating subplan XXX_1 for subquery SELECT b FROM push_down_filters.tbl2 WHERE (b OPERATOR(pg_catalog.>) 0) -DEBUG: Wrapping relation "tbl2" to a subquery -DEBUG: generating subplan XXX_2 for subquery SELECT b FROM push_down_filters.tbl2 WHERE false -DEBUG: generating subplan XXX_3 for subquery SELECT 1 AS x FROM (SELECT 1 FROM push_down_filters.tbl1, (SELECT NULL::integer AS a, tbl2_1.b, NULL::integer AS d FROM (SELECT intermediate_result.b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(b integer)) tbl2_1) tbl2 WHERE (tbl2.b OPERATOR(pg_catalog.>) 0)) s1("?column?") WHERE true UNION ALL SELECT 1 AS x FROM (SELECT 1 FROM push_down_filters.tbl1, (SELECT NULL::integer AS a, tbl2_1.b, NULL::integer AS d FROM (SELECT intermediate_result.b FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(b integer)) tbl2_1) tbl2 WHERE (tbl2.b OPERATOR(pg_catalog.>) 0)) s1("?column?") WHERE false -DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT max(x) AS max FROM (SELECT intermediate_result.x FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(x integer)) res max --------------------------------------------------------------------- 1 @@ -577,4 +567,4 @@ DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT max(x) AS max \set VERBOSITY terse RESET client_min_messages; DROP SCHEMA push_down_filters CASCADE; -NOTICE: drop cascades to 7 other objects +NOTICE: drop cascades to 8 other objects diff --git a/src/test/regress/expected/recursive_view_local_table.out b/src/test/regress/expected/recursive_view_local_table.out index b4ef802b4..dd11b103a 100644 --- a/src/test/regress/expected/recursive_view_local_table.out +++ b/src/test/regress/expected/recursive_view_local_table.out @@ -152,8 +152,11 @@ SELECT ref_table.* FROM ref_table JOIN (SELECT * FROM recursive_defined_non_recu (3 rows) SELECT ref_table.* FROM ref_table WHERE EXISTS (SELECT * FROM local_table l WHERE l.a = ref_table.a); -ERROR: direct joins between distributed and local tables are not supported -HINT: Use CTE's or subqueries to select from local tables and use them in joins + a | b +--------------------------------------------------------------------- + 1 | 1 +(1 row) + SELECT ref_table.* FROM ref_table WHERE EXISTS (SELECT * FROM local_table l WHERE l.a = ref_table.a) AND false; a | b --------------------------------------------------------------------- @@ -196,9 +199,5 @@ SELECT ref_table.* FROM ref_table WHERE EXISTS (SELECT * FROM recursive_defined_ --------------------------------------------------------------------- (0 rows) +SET client_min_messages TO WARNING; DROP SCHEMA postgres_local_table CASCADE; -NOTICE: drop cascades to 4 other objects -DETAIL: drop cascades to table local_table -drop cascades to view recursive_view -drop cascades to view recursive_defined_non_recursive_view -drop cascades to table ref_table diff --git a/src/test/regress/expected/relation_access_tracking.out b/src/test/regress/expected/relation_access_tracking.out index 0b8c1d05b..052c456e5 100644 --- a/src/test/regress/expected/relation_access_tracking.out +++ b/src/test/regress/expected/relation_access_tracking.out @@ -1020,20 +1020,6 @@ HINT: To remove the local data, run: SELECT truncate_local_data_after_distribut (1 row) COMMIT; +SET client_min_messages TO WARNING; SET search_path TO 'public'; DROP SCHEMA access_tracking CASCADE; -NOTICE: drop cascades to 14 other objects -DETAIL: drop cascades to function access_tracking.relation_select_access_mode(oid) -drop cascades to function access_tracking.relation_dml_access_mode(oid) -drop cascades to function access_tracking.relation_ddl_access_mode(oid) -drop cascades to function access_tracking.distributed_relation(text) -drop cascades to function access_tracking.relation_access_mode_to_text(text,integer) -drop cascades to view access_tracking.relation_accesses -drop cascades to table access_tracking.table_1 -drop cascades to table access_tracking.table_2 -drop cascades to table access_tracking.table_4 -drop cascades to table access_tracking.table_5 -drop cascades to table access_tracking.table_6 -drop cascades to table access_tracking.table_7 -drop cascades to table access_tracking.partitioning_test -drop cascades to table access_tracking.table_3 diff --git a/src/test/regress/expected/remove_coordinator.out b/src/test/regress/expected/remove_coordinator.out index e59a1f89e..0226a7cd0 100644 --- a/src/test/regress/expected/remove_coordinator.out +++ b/src/test/regress/expected/remove_coordinator.out @@ -5,3 +5,10 @@ SELECT master_remove_node('localhost', :master_port); (1 row) +-- restore coordinator for the rest of the tests +SELECT citus_set_coordinator_host('localhost', :master_port); + citus_set_coordinator_host +--------------------------------------------------------------------- + +(1 row) + diff --git a/src/test/regress/expected/remove_coordinator_from_metadata.out b/src/test/regress/expected/remove_coordinator_from_metadata.out new file mode 100644 index 000000000..5b062ed6a --- /dev/null +++ b/src/test/regress/expected/remove_coordinator_from_metadata.out @@ -0,0 +1,6 @@ +SELECT master_remove_node('localhost', :master_port); + master_remove_node +--------------------------------------------------------------------- + +(1 row) + diff --git a/src/test/regress/expected/replicated_table_disable_node.out b/src/test/regress/expected/replicated_table_disable_node.out index 60de41f08..be1ad92b3 100644 --- a/src/test/regress/expected/replicated_table_disable_node.out +++ b/src/test/regress/expected/replicated_table_disable_node.out @@ -38,7 +38,7 @@ SELECT count(*) FROM pg_dist_placement p JOIN pg_dist_node n USING(groupid) AND p.shardid IN (101500, 101501, 101502); count --------------------------------------------------------------------- - 3 + 4 (1 row) \c - - - :worker_1_port @@ -47,7 +47,7 @@ SELECT count(*) FROM pg_dist_placement p JOIN pg_dist_node n USING(groupid) AND p.shardid IN (101500, 101501, 101502); count --------------------------------------------------------------------- - 3 + 4 (1 row) SET search_path TO disable_node_with_replicated_tables; diff --git a/src/test/regress/expected/run_command_on_all_nodes.out b/src/test/regress/expected/run_command_on_all_nodes.out index 76c42ad23..e95989d84 100644 --- a/src/test/regress/expected/run_command_on_all_nodes.out +++ b/src/test/regress/expected/run_command_on_all_nodes.out @@ -1,5 +1,11 @@ CREATE SCHEMA run_command_on_all_nodes; SET search_path TO run_command_on_all_nodes; +SELECT master_remove_node('localhost', :master_port); + master_remove_node +--------------------------------------------------------------------- + +(1 row) + -- check coordinator isn't in metadata SELECT count(*) != 0 AS "Coordinator is in Metadata" FROM pg_dist_node @@ -205,3 +211,9 @@ DROP SCHEMA run_command_on_all_nodes CASCADE; NOTICE: drop cascades to 2 other objects DETAIL: drop cascades to table run_command_on_all_nodes.tbl drop cascades to table run_command_on_all_nodes.test +SELECT citus_set_coordinator_host('localhost'); + citus_set_coordinator_host +--------------------------------------------------------------------- + +(1 row) + diff --git a/src/test/regress/expected/schema_based_sharding.out b/src/test/regress/expected/schema_based_sharding.out new file mode 100644 index 000000000..d7b7b4710 --- /dev/null +++ b/src/test/regress/expected/schema_based_sharding.out @@ -0,0 +1,1703 @@ +CREATE SCHEMA regular_schema; +SET search_path TO regular_schema; +SET citus.next_shard_id TO 1920000; +SET citus.shard_count TO 32; +SET citus.shard_replication_factor TO 1; +SET client_min_messages TO WARNING; +SELECT 1 FROM citus_add_node('localhost', :master_port, groupid => 0); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SET client_min_messages TO NOTICE; +-- Verify that the UDFs used to sync tenant schema metadata to workers +-- fail on NULL input. +SELECT citus_internal_add_tenant_schema(NULL, 1); +ERROR: schema_id cannot be NULL +SELECT citus_internal_add_tenant_schema(1, NULL); +ERROR: colocation_id cannot be NULL +SELECT citus_internal_delete_tenant_schema(NULL); +ERROR: schema_id cannot be NULL +SELECT citus_internal_unregister_tenant_schema_globally(1, NULL); +ERROR: schema_name cannot be NULL +SELECT citus_internal_unregister_tenant_schema_globally(NULL, 'text'); +ERROR: schema_id cannot be NULL +-- Verify that citus_internal_unregister_tenant_schema_globally can only +-- be called on schemas that are dropped already. +SELECT citus_internal_unregister_tenant_schema_globally('regular_schema'::regnamespace, 'regular_schema'); +ERROR: schema is expected to be already dropped because this function is only expected to be called from Citus drop hook +SELECT 1 FROM citus_remove_node('localhost', :worker_2_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +CREATE TABLE regular_schema.test_table(a int, b text); +SELECT create_distributed_table('regular_schema.test_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SET citus.enable_schema_based_sharding TO ON; +-- show that regular_schema doesn't show up in pg_dist_schema +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'regular_schema'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- empty tenant +CREATE SCHEMA "tenant\'_1"; +-- non-empty tenant +CREATE SCHEMA "tenant\'_2"; +CREATE TABLE "tenant\'_2".test_table(a int, b text); +-- empty tenant +CREATE SCHEMA "tenant\'_3"; +CREATE TABLE "tenant\'_3".test_table(a int, b text); +DROP TABLE "tenant\'_3".test_table; +-- add a node after creating tenant schemas +SELECT 1 FROM citus_add_node('localhost', :worker_2_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +ALTER SCHEMA "tenant\'_1" RENAME TO tenant_1; +ALTER SCHEMA "tenant\'_2" RENAME TO tenant_2; +ALTER SCHEMA "tenant\'_3" RENAME TO tenant_3; +-- verify that create_distributed_table() and others fail when called on tenant tables +SELECT create_distributed_table('tenant_2.test_table', 'a'); +ERROR: table "test_table" is already distributed +SELECT create_reference_table('tenant_2.test_table'); +ERROR: table "test_table" is already distributed +SELECT citus_add_local_table_to_metadata('tenant_2.test_table'); +ERROR: table "test_table" is already distributed +-- verify we don't allow update_distributed_table_colocation for tenant tables +SELECT update_distributed_table_colocation('tenant_2.test_table', colocate_with => 'none'); +ERROR: tenant_2.test_table is not allowed for update_distributed_table_colocation because it belongs to a distributed schema +-- verify we also don't allow colocate_with a tenant table +SELECT update_distributed_table_colocation('regular_schema.test_table', colocate_with => 'tenant_2.test_table'); +ERROR: tenant_2.test_table is not allowed for colocate_with because it belongs to a distributed schema +-- verify we do not allow undistribute_table for tenant tables +CREATE TABLE tenant_2.undist_table(id int); +SELECT undistribute_table('tenant_2.undist_table'); +ERROR: tenant_2.undist_table is not allowed for undistribute_table because it belongs to a distributed schema +-- verify we don't allow alter_distributed_table for tenant tables +SELECT alter_distributed_table('tenant_2.test_table', colocate_with => 'none'); +ERROR: tenant_2.test_table is not allowed for alter_distributed_table because it belongs to a distributed schema +-- verify we also don't allow colocate_with a tenant table +SELECT alter_distributed_table('regular_schema.test_table', colocate_with => 'tenant_2.test_table'); +ERROR: tenant_2.test_table is not allowed for colocate_with because it belongs to a distributed schema +-- verify we can set tenant table's schema to regular schema +CREATE TABLE tenant_2.test_table2(id int); +ALTER TABLE tenant_2.test_table2 SET SCHEMA regular_schema; +NOTICE: undistributing table test_table2 in distributed schema tenant_2 before altering its schema +-- verify that regular_schema.test_table2 does not exist in pg_dist_partition +SELECT COUNT(*)=0 FROM pg_dist_partition +WHERE logicalrelid = 'regular_schema.test_table2'::regclass AND + partmethod = 'n' AND repmodel = 's' AND colocationid > 0; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- verify that tenant_2.test_table2 does not exist +SELECT * FROM tenant_2.test_table2; +ERROR: relation "tenant_2.test_table2" does not exist +-- verify we can set regular table's schema to distributed schema +CREATE TABLE regular_schema.test_table3(id int); +ALTER TABLE regular_schema.test_table3 SET SCHEMA tenant_2; +NOTICE: Moving test_table3 into distributed schema tenant_2 +-- verify that tenant_2.test_table3 is recorded in pg_dist_partition as a single-shard table. +SELECT COUNT(*)=1 FROM pg_dist_partition +WHERE logicalrelid = 'tenant_2.test_table3'::regclass AND + partmethod = 'n' AND repmodel = 's' AND colocationid > 0; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- verify that regular_schema.test_table3 does not exist +SELECT * FROM regular_schema.test_table3; +ERROR: relation "regular_schema.test_table3" does not exist +-- verify we can set tenant table's schema to another distributed schema +CREATE TABLE tenant_2.test_table4(id int); +ALTER TABLE tenant_2.test_table4 SET SCHEMA tenant_3; +NOTICE: undistributing table test_table4 in distributed schema tenant_2 before altering its schema +NOTICE: Moving test_table4 into distributed schema tenant_3 +-- verify that tenant_3.test_table4 is recorded in pg_dist_partition as a single-shard table. +SELECT COUNT(*)=1 FROM pg_dist_partition +WHERE logicalrelid = 'tenant_3.test_table4'::regclass AND + partmethod = 'n' AND repmodel = 's' AND colocationid > 0; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- verify that tenant_2.test_table4 does not exist +SELECT * FROM tenant_2.test_table4; +ERROR: relation "tenant_2.test_table4" does not exist +-- verify that we can put a local table in regular schema into distributed schema +CREATE TABLE regular_schema.pg_local_tbl(id int); +ALTER TABLE regular_schema.pg_local_tbl SET SCHEMA tenant_2; +NOTICE: Moving pg_local_tbl into distributed schema tenant_2 +-- verify that we can put a Citus local table in regular schema into distributed schema +CREATE TABLE regular_schema.citus_local_tbl(id int); +SELECT citus_add_local_table_to_metadata('regular_schema.citus_local_tbl'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE regular_schema.citus_local_tbl SET SCHEMA tenant_2; +NOTICE: Moving citus_local_tbl into distributed schema tenant_2 +-- verify that we do not allow a hash distributed table in regular schema into distributed schema +CREATE TABLE regular_schema.hash_dist_tbl(id int); +SELECT create_distributed_table('regular_schema.hash_dist_tbl', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE regular_schema.hash_dist_tbl SET SCHEMA tenant_2; +ERROR: distributed schema cannot have distributed tables +HINT: Undistribute distributed tables before 'ALTER TABLE SET SCHEMA'. +-- verify that we do not allow a reference table in regular schema into distributed schema +CREATE TABLE regular_schema.ref_tbl(id int PRIMARY KEY); +SELECT create_reference_table('regular_schema.ref_tbl'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE regular_schema.ref_tbl SET SCHEMA tenant_2; +ERROR: distributed schema cannot have distributed tables +HINT: Undistribute distributed tables before 'ALTER TABLE SET SCHEMA'. +-- verify that we can put a table in tenant schema into regular schema +CREATE TABLE tenant_2.tenant_tbl(id int); +ALTER TABLE tenant_2.tenant_tbl SET SCHEMA regular_schema; +NOTICE: undistributing table tenant_tbl in distributed schema tenant_2 before altering its schema +-- verify that we can put a table in tenant schema into another tenant schema +CREATE TABLE tenant_2.tenant_tbl2(id int); +ALTER TABLE tenant_2.tenant_tbl2 SET SCHEMA tenant_3; +NOTICE: undistributing table tenant_tbl2 in distributed schema tenant_2 before altering its schema +NOTICE: Moving tenant_tbl2 into distributed schema tenant_3 +-- verify that we do not allow a local table in regular schema into distributed schema if it has foreign key to a non-reference table in another schema +CREATE TABLE regular_schema.pg_local_tbl1(id int PRIMARY KEY); +CREATE TABLE regular_schema.pg_local_tbl2(id int REFERENCES regular_schema.pg_local_tbl1(id)); +ALTER TABLE regular_schema.pg_local_tbl2 SET SCHEMA tenant_2; +ERROR: foreign keys from distributed schemas can only point to the same distributed schema or reference tables in regular schemas +DETAIL: "tenant_2.pg_local_tbl2" references "regular_schema.pg_local_tbl1" via foreign key constraint "pg_local_tbl2_id_fkey" +-- verify that we allow a local table in regular schema into distributed schema if it has foreign key to a reference table in another schema +CREATE TABLE regular_schema.pg_local_tbl3(id int REFERENCES regular_schema.ref_tbl(id)); +ALTER TABLE regular_schema.pg_local_tbl3 SET SCHEMA tenant_2; +NOTICE: Moving pg_local_tbl3 into distributed schema tenant_2 +-- verify that we do not allow a table in tenant schema into regular schema if it has foreign key to/from another table in the same schema +CREATE TABLE tenant_2.tenant_tbl1(id int PRIMARY KEY); +CREATE TABLE tenant_2.tenant_tbl2(id int REFERENCES tenant_2.tenant_tbl1(id)); +ALTER TABLE tenant_2.tenant_tbl1 SET SCHEMA regular_schema; +ERROR: set schema is not allowed for table tenant_tbl1 in distributed schema tenant_2 +DETAIL: distributed schemas cannot have foreign keys from/to local tables or different schema +ALTER TABLE tenant_2.tenant_tbl2 SET SCHEMA regular_schema; +ERROR: set schema is not allowed for table tenant_tbl2 in distributed schema tenant_2 +DETAIL: distributed schemas cannot have foreign keys from/to local tables or different schema +-- verify that we do not allow a table in distributed schema into another distributed schema if it has foreign key to/from another table in the same schema +CREATE TABLE tenant_2.tenant_tbl3(id int PRIMARY KEY); +CREATE TABLE tenant_2.tenant_tbl4(id int REFERENCES tenant_2.tenant_tbl3(id)); +ALTER TABLE tenant_2.tenant_tbl3 SET SCHEMA tenant_3; +ERROR: set schema is not allowed for table tenant_tbl3 in distributed schema tenant_2 +DETAIL: distributed schemas cannot have foreign keys from/to local tables or different schema +ALTER TABLE tenant_2.tenant_tbl4 SET SCHEMA tenant_3; +ERROR: set schema is not allowed for table tenant_tbl4 in distributed schema tenant_2 +DETAIL: distributed schemas cannot have foreign keys from/to local tables or different schema +-- alter set non-existent schema +ALTER TABLE tenant_2.test_table SET SCHEMA ghost_schema; +ERROR: schema "ghost_schema" does not exist +ALTER TABLE IF EXISTS tenant_2.test_table SET SCHEMA ghost_schema; +ERROR: schema "ghost_schema" does not exist +-- alter set non-existent table +ALTER TABLE tenant_2.ghost_table SET SCHEMA ghost_schema; +ERROR: relation "tenant_2.ghost_table" does not exist +ALTER TABLE IF EXISTS tenant_2.ghost_table SET SCHEMA ghost_schema; +NOTICE: relation "ghost_table" does not exist, skipping +-- (on coordinator) verify that colocation id is set for empty tenants too +SELECT colocationid > 0 FROM pg_dist_schema +WHERE schemaid::regnamespace::text IN ('tenant_1', 'tenant_3'); + ?column? +--------------------------------------------------------------------- + t + t +(2 rows) + +-- (on workers) verify that colocation id is set for empty tenants too +SELECT result FROM run_command_on_workers($$ + SELECT array_agg(colocationid > 0) FROM pg_dist_schema + WHERE schemaid::regnamespace::text IN ('tenant_1', 'tenant_3'); +$$); + result +--------------------------------------------------------------------- + {t,t} + {t,t} +(2 rows) + +-- Verify that tenant_2.test_table is recorded in pg_dist_partition as a +-- single-shard table. +SELECT COUNT(*)=1 FROM pg_dist_partition +WHERE logicalrelid = 'tenant_2.test_table'::regclass AND + partmethod = 'n' AND repmodel = 's' AND colocationid > 0; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- (on coordinator) verify that colocation id is properly set for non-empty tenant schema +SELECT colocationid = ( + SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_2.test_table'::regclass +) +FROM pg_dist_schema +WHERE schemaid::regnamespace::text = 'tenant_2'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- (on workers) verify that colocation id is properly set for non-empty tenant schema +SELECT result FROM run_command_on_workers($$ + SELECT colocationid = ( + SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_2.test_table'::regclass + ) + FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_2'; +$$); + result +--------------------------------------------------------------------- + t + t +(2 rows) + +-- create a tenant table for tenant_1 after add_node +CREATE TABLE tenant_1.test_table(a int, b text); +-- (on coordinator) verify that colocation id is properly set for now-non-empty tenant schema +SELECT colocationid = ( + SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_1.test_table'::regclass +) +FROM pg_dist_schema +WHERE schemaid::regnamespace::text = 'tenant_1'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- (on workers) verify that colocation id is properly set for now-non-empty tenant schema +SELECT result FROM run_command_on_workers($$ + SELECT colocationid = ( + SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_1.test_table'::regclass + ) + FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_1'; +$$); + result +--------------------------------------------------------------------- + t + t +(2 rows) + +-- verify that tenant_1 and tenant_2 have different colocation ids +SELECT COUNT(DISTINCT(colocationid))=2 FROM pg_dist_schema +WHERE schemaid::regnamespace::text IN ('tenant_1', 'tenant_2'); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- verify that we don't allow creating tenant tables via CREATE SCHEMA command +CREATE SCHEMA schema_using_schema_elements CREATE TABLE test_table(a int, b text); +ERROR: cannot create distributed schema and table in a single statement +HINT: SET citus.enable_schema_based_sharding TO off, or create the schema and table in separate commands. +CREATE SCHEMA tenant_4; +CREATE TABLE tenant_4.tbl_1(a int, b text); +CREATE TABLE tenant_4.tbl_2(a int, b text); +-- verify that we don't allow creating a foreign table in a tenant schema, with a nice error message +CREATE FOREIGN TABLE tenant_4.foreign_table ( + id bigint not null, + full_name text not null default '' +) SERVER fake_fdw_server OPTIONS (encoding 'utf-8', compression 'true', table_name 'foreign_table'); +ERROR: cannot create a foreign table in a distributed schema +-- verify that we don't allow creating a foreign table in a tenant schema +CREATE TEMPORARY TABLE tenant_4.temp_table (a int, b text); +ERROR: cannot create temporary relation in non-temporary schema +CREATE TABLE tenant_4.partitioned_table(a int, b text, PRIMARY KEY (a)) PARTITION BY RANGE (a); +CREATE TABLE tenant_4.partitioned_table_child_1 PARTITION OF tenant_4.partitioned_table FOR VALUES FROM (1) TO (2); +CREATE TABLE tenant_4.another_partitioned_table(a int, b text, FOREIGN KEY (a) REFERENCES tenant_4.partitioned_table(a)) PARTITION BY RANGE (a); +CREATE TABLE tenant_4.another_partitioned_table_child PARTITION OF tenant_4.another_partitioned_table FOR VALUES FROM (1) TO (2); +-- verify that we allow creating partitioned tables in a tenant schema +SELECT COUNT(*)=1 FROM pg_dist_partition +WHERE logicalrelid = 'tenant_4.partitioned_table_child_1'::regclass AND + partmethod = 'n' AND repmodel = 's' AND colocationid = ( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'tenant_4.partitioned_table'::regclass); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT EXISTS( + SELECT 1 + FROM pg_inherits + WHERE inhrelid = 'tenant_4.partitioned_table_child_1'::regclass AND + inhparent = 'tenant_4.partitioned_table'::regclass +) AS is_partition; + is_partition +--------------------------------------------------------------------- + t +(1 row) + +SELECT COUNT(*)=1 FROM pg_dist_partition +WHERE logicalrelid = 'tenant_4.another_partitioned_table_child'::regclass AND + partmethod = 'n' AND repmodel = 's' AND colocationid = ( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'tenant_4.another_partitioned_table'::regclass); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT EXISTS( + SELECT 1 + FROM pg_inherits + WHERE inhrelid = 'tenant_4.another_partitioned_table_child'::regclass AND + inhparent = 'tenant_4.another_partitioned_table'::regclass +) AS is_partition; + is_partition +--------------------------------------------------------------------- + t +(1 row) + +-- verify the foreign key between parents +SELECT EXISTS( + SELECT 1 + FROM pg_constraint + WHERE conrelid = 'tenant_4.another_partitioned_table'::regclass AND + confrelid = 'tenant_4.partitioned_table'::regclass AND + contype = 'f' +) AS foreign_key_exists; + foreign_key_exists +--------------------------------------------------------------------- + t +(1 row) + +INSERT INTO tenant_4.another_partitioned_table VALUES (1, 'a'); +ERROR: insert or update on table "another_partitioned_table_child_1920090" violates foreign key constraint "another_partitioned_table_a_fkey_1920089" +DETAIL: Key (a)=(1) is not present in table "partitioned_table_1920087". +CONTEXT: while executing command on localhost:xxxxx +INSERT INTO tenant_4.partitioned_table VALUES (1, 'a'); +INSERT INTO tenant_4.another_partitioned_table VALUES (1, 'a'); +CREATE SCHEMA tenant_5; +CREATE TABLE tenant_5.tbl_1(a int, b text); +CREATE TABLE tenant_5.partitioned_table(a int, b text) PARTITION BY RANGE (a); +-- verify that we don't allow creating a partition table that is child of a partitioned table in a different tenant schema +CREATE TABLE tenant_4.partitioned_table_child_2 PARTITION OF tenant_5.partitioned_table FOR VALUES FROM (1) TO (2); +ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas +-- verify that we don't allow creating a local partition table that is child of a tenant partitioned table +CREATE TABLE regular_schema.local_child_table PARTITION OF tenant_5.partitioned_table FOR VALUES FROM (1) TO (2); +ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas +SET citus.use_citus_managed_tables TO ON; +CREATE TABLE regular_schema.local_child_table PARTITION OF tenant_5.partitioned_table FOR VALUES FROM (1) TO (2); +ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas +RESET citus.use_citus_managed_tables; +CREATE TABLE regular_schema.local_partitioned_table(a int, b text) PARTITION BY RANGE (a); +CREATE TABLE regular_schema.citus_local_partitioned_table(a int, b text) PARTITION BY RANGE (a); +SELECT citus_add_local_table_to_metadata('regular_schema.citus_local_partitioned_table'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE regular_schema.dist_partitioned_table(a int, b text) PARTITION BY RANGE (a); +SELECT create_distributed_table('regular_schema.dist_partitioned_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- verify that we don't allow creating a partition table that is child of a non-tenant partitioned table +CREATE TABLE tenant_4.partitioned_table_child_2 PARTITION OF regular_schema.local_partitioned_table FOR VALUES FROM (1) TO (2); +ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas +CREATE TABLE tenant_4.partitioned_table_child_2 PARTITION OF regular_schema.citus_local_partitioned_table FOR VALUES FROM (1) TO (2); +ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas +CREATE TABLE tenant_4.partitioned_table_child_2 PARTITION OF regular_schema.dist_partitioned_table FOR VALUES FROM (1) TO (2); +ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas +CREATE TABLE tenant_4.parent_attach_test(a int, b text) PARTITION BY RANGE (a); +CREATE TABLE tenant_4.child_attach_test(a int, b text); +CREATE TABLE tenant_5.parent_attach_test(a int, b text) PARTITION BY RANGE (a); +CREATE TABLE tenant_5.child_attach_test(a int, b text); +CREATE TABLE regular_schema.parent_attach_test_local(a int, b text) PARTITION BY RANGE (a); +CREATE TABLE regular_schema.parent_attach_test_citus_local(a int, b text) PARTITION BY RANGE (a); +SELECT citus_add_local_table_to_metadata('regular_schema.parent_attach_test_citus_local'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE regular_schema.parent_attach_test_dist(a int, b text) PARTITION BY RANGE (a); +SELECT create_distributed_table('regular_schema.parent_attach_test_dist', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE regular_schema.child_attach_test_local(a int, b text); +CREATE TABLE regular_schema.child_attach_test_citus_local(a int, b text); +SELECT citus_add_local_table_to_metadata('regular_schema.child_attach_test_citus_local'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE regular_schema.child_attach_test_dist(a int, b text); +SELECT create_distributed_table('regular_schema.child_attach_test_dist', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- verify that we don't allow attaching a tenant table into a tenant partitioned table, if they are not in the same schema +ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION tenant_5.child_attach_test FOR VALUES FROM (1) TO (2); +ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas +-- verify that we don't allow attaching a non-tenant table into a tenant partitioned table +ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION regular_schema.child_attach_test_local FOR VALUES FROM (1) TO (2); +ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas +ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION regular_schema.child_attach_test_citus_local FOR VALUES FROM (1) TO (2); +ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas +ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION regular_schema.child_attach_test_dist FOR VALUES FROM (1) TO (2); +ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas +-- verify that we don't allow attaching a tenant table into a non-tenant partitioned table +ALTER TABLE regular_schema.parent_attach_test_local ATTACH PARTITION tenant_4.child_attach_test FOR VALUES FROM (1) TO (2); +ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas +ALTER TABLE regular_schema.parent_attach_test_citus_local ATTACH PARTITION tenant_4.child_attach_test FOR VALUES FROM (1) TO (2); +ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas +ALTER TABLE regular_schema.parent_attach_test_dist ATTACH PARTITION tenant_4.child_attach_test FOR VALUES FROM (1) TO (2); +ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas +ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION tenant_4.child_attach_test FOR VALUES FROM (1) TO (2); +-- verify that we don't allow multi-level partitioning on tenant tables +CREATE TABLE tenant_4.multi_level_test(a int, b text) PARTITION BY RANGE (a); +ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION tenant_4.multi_level_test FOR VALUES FROM (1) TO (2); +ERROR: Citus doesn't support multi-level partitioned tables +DETAIL: Relation "multi_level_test" is partitioned table itself and it is also partition of relation "parent_attach_test". +-- verify that we allow attaching a tenant table into a tenant partitioned table, if they are in the same schema +SELECT COUNT(*)=1 FROM pg_dist_partition +WHERE logicalrelid = 'tenant_4.parent_attach_test'::regclass AND + partmethod = 'n' AND repmodel = 's' AND colocationid = ( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'tenant_4.child_attach_test'::regclass); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT EXISTS( + SELECT 1 + FROM pg_inherits + WHERE inhrelid = 'tenant_4.child_attach_test'::regclass AND + inhparent = 'tenant_4.parent_attach_test'::regclass +) AS is_partition; + is_partition +--------------------------------------------------------------------- + t +(1 row) + +-- errors out because shard replication factor > 1 +SET citus.shard_replication_factor TO 2; +CREATE TABLE tenant_4.tbl_3 AS SELECT 1 AS a, 'text' as b; +ERROR: could not create single shard table: citus.shard_replication_factor is greater than 1 +HINT: Consider setting citus.shard_replication_factor to 1 and try again +SET citus.shard_replication_factor TO 1; +-- verify that we allow creating tenant tables by using CREATE TABLE AS / SELECT INTO commands +CREATE TABLE tenant_4.tbl_3 AS SELECT 1 AS a, 'text' as b; +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$tenant_4.tbl_3$$) +CREATE TEMP TABLE IF NOT EXISTS tenant_4.tbl_4 AS SELECT 1 as a, 'text' as b; +ERROR: cannot create temporary relation in non-temporary schema +CREATE UNLOGGED TABLE IF NOT EXISTS tenant_4.tbl_4 AS SELECT 1 as a, 'text' as b WITH NO DATA; +-- the same command, no changes because of IF NOT EXISTS +CREATE UNLOGGED TABLE IF NOT EXISTS tenant_4.tbl_4 AS SELECT 1 as a, 'text' as b WITH NO DATA; +NOTICE: relation "tbl_4" already exists, skipping +SELECT 1 as a, 'text' as b INTO tenant_4.tbl_5; +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$tenant_4.tbl_5$$) +-- verify we can query the newly created tenant tables +SELECT * FROM tenant_4.tbl_3; + a | b +--------------------------------------------------------------------- + 1 | text +(1 row) + +SELECT COUNT(*) FROM tenant_4.tbl_5; + count +--------------------------------------------------------------------- + 1 +(1 row) + +CREATE TYPE employee_type AS (name text, salary numeric); +-- verify that we don't allow creating tenant tables by using CREATE TABLE OF commands +CREATE TABLE tenant_4.employees OF employee_type ( + PRIMARY KEY (name), + salary WITH OPTIONS DEFAULT 1000 +); +ERROR: cannot create tables in a distributed schema using CREATE TABLE OF syntax +-- verify that we act accordingly when if not exists is used +CREATE TABLE IF NOT EXISTS tenant_4.tbl_6(a int, b text); +CREATE TABLE IF NOT EXISTS tenant_4.tbl_6(a int, b text); +NOTICE: relation "tbl_6" already exists, skipping +SELECT logicalrelid, partmethod + FROM pg_dist_partition + WHERE logicalrelid::text LIKE 'tenant_4.tbl%' + ORDER BY logicalrelid; + logicalrelid | partmethod +--------------------------------------------------------------------- + tenant_4.tbl_1 | n + tenant_4.tbl_2 | n + tenant_4.tbl_3 | n + tenant_4.tbl_4 | n + tenant_4.tbl_5 | n + tenant_4.tbl_6 | n +(6 rows) + +CREATE TABLE regular_schema.local(a int, b text); +CREATE TABLE regular_schema.citus_local(a int, b text); +SELECT citus_add_local_table_to_metadata('regular_schema.citus_local'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE regular_schema.dist(a int, b text); +SELECT create_distributed_table('regular_schema.dist', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- verify that we can create a table LIKE another table +CREATE TABLE tenant_5.test_table_like_1(LIKE tenant_5.tbl_1); -- using a table from the same schema +CREATE TABLE tenant_5.test_table_like_2(LIKE tenant_4.tbl_1); -- using a table from another schema +CREATE TABLE tenant_5.test_table_like_3(LIKE regular_schema.local); -- using a local table +CREATE TABLE tenant_5.test_table_like_4(LIKE regular_schema.citus_local); -- using a citus local table +CREATE TABLE tenant_5.test_table_like_5(LIKE regular_schema.dist); -- using a distributed table +-- verify that all of them are converted to tenant tables +SELECT COUNT(*) = 5 +FROM pg_dist_partition +WHERE logicalrelid::text LIKE 'tenant_5.test_table_like_%' AND + partmethod = 'n' AND repmodel = 's' AND colocationid = ( + SELECT colocationid FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_5' + ); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +CREATE TABLE regular_schema.local_table_using_like(LIKE tenant_5.tbl_1); +-- verify that regular_schema.local_table_using_like is not a tenant table +SELECT COUNT(*) = 0 FROM pg_dist_partition +WHERE logicalrelid = 'regular_schema.local_table_using_like'::regclass; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- verify that INHERITS syntax is not supported when creating a tenant table +CREATE TABLE tenant_5.test_table_inherits_1(x int) INHERITS (tenant_5.tbl_1); -- using a table from the same schema +ERROR: tables in a distributed schema cannot inherit or be inherited +CREATE TABLE tenant_5.test_table_inherits_2(x int) INHERITS (tenant_4.tbl_1); -- using a table from another schema +ERROR: tables in a distributed schema cannot inherit or be inherited +CREATE TABLE tenant_5.test_table_inherits_3(x int) INHERITS (regular_schema.local); -- using a local table +ERROR: tables in a distributed schema cannot inherit or be inherited +CREATE TABLE tenant_5.test_table_inherits_4(x int) INHERITS (regular_schema.citus_local); -- using a citus local table +ERROR: tables in a distributed schema cannot inherit or be inherited +CREATE TABLE tenant_5.test_table_inherits_5(x int) INHERITS (regular_schema.dist); -- using a distributed table +ERROR: tables in a distributed schema cannot inherit or be inherited +-- verify that INHERITS syntax is not supported when creating a local table based on a tenant table +CREATE TABLE regular_schema.local_table_using_inherits(x int) INHERITS (tenant_5.tbl_1); +ERROR: tables in a distributed schema cannot inherit or be inherited +CREATE TABLE tenant_5.tbl_2(a int, b text); +CREATE SCHEMA "CiTuS.TeeN_108"; +ALTER SCHEMA "CiTuS.TeeN_108" RENAME TO citus_teen_proper; +SELECT schemaid AS citus_teen_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'citus_teen_proper' \gset +SELECT colocationid AS citus_teen_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'citus_teen_proper' \gset +SELECT result FROM run_command_on_workers($$ + SELECT schemaid INTO citus_teen_schemaid FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'citus_teen_proper' +$$); + result +--------------------------------------------------------------------- + SELECT 1 + SELECT 1 +(2 rows) + +-- (on coordinator) verify that colocation id is set for the tenant with a weird name too +SELECT :citus_teen_colocationid > 0; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- (on workers) verify that the same colocation id is used on workers too +SELECT format( + 'SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=1 FROM pg_dist_schema + WHERE schemaid::regnamespace::text = ''citus_teen_proper'' AND + colocationid = %s; + $$);', +:citus_teen_colocationid) AS verify_workers_query \gset +:verify_workers_query + result +--------------------------------------------------------------------- + t + t +(2 rows) + +ALTER SCHEMA citus_teen_proper RENAME TO "CiTuS.TeeN_108"; +SET citus.enable_schema_based_sharding TO OFF; +-- Show that the tables created in tenant schemas are considered to be +-- tenant tables even if the GUC was set to off when creating the table. +CREATE TABLE tenant_5.tbl_3(a int, b text); +SELECT COUNT(*)=1 FROM pg_dist_partition WHERE logicalrelid = 'tenant_5.tbl_3'::regclass; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SET citus.enable_schema_based_sharding TO ON; +-- Verify that tables that belong to tenant_4 and tenant_5 are stored on +-- different worker nodes due to order we followed when creating first tenant +-- tables in each of them. +SELECT COUNT(DISTINCT(nodename, nodeport))=2 FROM citus_shards +WHERE table_name IN ('tenant_4.tbl_1'::regclass, 'tenant_5.tbl_1'::regclass); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- show that all the tables in tenant_4 are colocated with each other. +SELECT COUNT(DISTINCT(colocationid))=1 FROM pg_dist_partition +WHERE logicalrelid::regclass::text LIKE 'tenant_4.%'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- verify the same for tenant_5 too +SELECT COUNT(DISTINCT(colocationid))=1 FROM pg_dist_partition +WHERE logicalrelid::regclass::text LIKE 'tenant_5.%'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT schemaid AS tenant_4_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_4' \gset +SELECT colocationid AS tenant_4_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_4' \gset +SELECT result FROM run_command_on_workers($$ + SELECT schemaid INTO tenant_4_schemaid FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_4' +$$); + result +--------------------------------------------------------------------- + SELECT 1 + SELECT 1 +(2 rows) + +SET client_min_messages TO WARNING; +-- Rename it to a name that contains a single quote to verify that we properly +-- escape its name when sending the command to delete the pg_dist_schema +-- entry on workers. +ALTER SCHEMA tenant_4 RENAME TO "tenant\'_4"; +DROP SCHEMA "tenant\'_4", "CiTuS.TeeN_108" CASCADE; +SET client_min_messages TO NOTICE; +-- (on coordinator) Verify that dropping a tenant schema deletes the associated +-- pg_dist_schema entry and pg_dist_colocation too. +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid = :tenant_4_schemaid; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = :tenant_4_colocationid; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid = :citus_teen_schemaid; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = :citus_teen_colocationid; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- (on workers) Verify that dropping a tenant schema deletes the associated +-- pg_dist_schema entry and pg_dist_colocation too. +SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_schema + WHERE schemaid = (SELECT schemaid FROM tenant_4_schemaid) +$$); + result +--------------------------------------------------------------------- + t + t +(2 rows) + +SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_schema + WHERE schemaid = (SELECT schemaid FROM citus_teen_schemaid) +$$); + result +--------------------------------------------------------------------- + t + t +(2 rows) + +SELECT format( + 'SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = %s; + $$);', +:tenant_4_colocationid) AS verify_workers_query \gset +:verify_workers_query + result +--------------------------------------------------------------------- + t + t +(2 rows) + +SELECT result FROM run_command_on_workers($$ + DROP TABLE tenant_4_schemaid +$$); + result +--------------------------------------------------------------------- + DROP TABLE + DROP TABLE +(2 rows) + +SELECT format( + 'SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = %s; + $$);', +:citus_teen_colocationid) AS verify_workers_query \gset +:verify_workers_query + result +--------------------------------------------------------------------- + t + t +(2 rows) + +SELECT result FROM run_command_on_workers($$ + DROP TABLE citus_teen_schemaid +$$); + result +--------------------------------------------------------------------- + DROP TABLE + DROP TABLE +(2 rows) + +-- show that we don't allow colocating a Citus table with a tenant table +CREATE TABLE regular_schema.null_shard_key_1(a int, b text); +SELECT create_distributed_table('regular_schema.null_shard_key_1', null, colocate_with => 'tenant_5.tbl_2'); +ERROR: cannot colocate tables tbl_2 and null_shard_key_1 +DETAIL: Cannot colocate tables with distributed schema tables by using colocate_with option. +HINT: Consider using "CREATE TABLE" statement to create this table as a single-shard distributed table in the same schema to automatically colocate it with tenant_5.tbl_2 +SELECT create_distributed_table('regular_schema.null_shard_key_1', 'a', colocate_with => 'tenant_5.tbl_2'); +ERROR: cannot colocate tables tbl_2 and null_shard_key_1 +DETAIL: Distribution column types don't match for tbl_2 and null_shard_key_1. +CREATE TABLE regular_schema.null_shard_key_table_2(a int, b text); +SELECT create_distributed_table('regular_schema.null_shard_key_table_2', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- Show that we don't chose to colocate regular single-shard tables with +-- tenant tables by default. +SELECT * FROM pg_dist_schema WHERE colocationid = ( + SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'regular_schema.null_shard_key_table_2'::regclass +); + schemaid | colocationid +--------------------------------------------------------------------- +(0 rows) + +-- save the colocation id used for tenant_5 +SELECT colocationid AS tenant_5_old_colocationid FROM pg_dist_schema +WHERE schemaid::regnamespace::text = 'tenant_5' \gset +-- drop all the tables that belong to tenant_5 and create a new one +DROP TABLE tenant_5.tbl_1, tenant_5.tbl_2, tenant_5.tbl_3; +CREATE TABLE tenant_5.tbl_4(a int, b text); +-- (on coordinator) verify that tenant_5 is still associated with the same colocation id +SELECT colocationid = :tenant_5_old_colocationid FROM pg_dist_schema +WHERE schemaid::regnamespace::text = 'tenant_5'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- (on workers) verify that tenant_5 is still associated with the same colocation id +SELECT format( + 'SELECT result FROM run_command_on_workers($$ + SELECT colocationid = %s FROM pg_dist_schema + WHERE schemaid::regnamespace::text = ''tenant_5''; + $$);', +:tenant_5_old_colocationid) AS verify_workers_query \gset +:verify_workers_query + result +--------------------------------------------------------------------- + t + t +(2 rows) + +SELECT schemaid AS tenant_1_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_1' \gset +SELECT colocationid AS tenant_1_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_1' \gset +SELECT schemaid AS tenant_2_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_2' \gset +SELECT colocationid AS tenant_2_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_2' \gset +SELECT result FROM run_command_on_workers($$ + SELECT schemaid INTO tenant_1_schemaid FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_1' +$$); + result +--------------------------------------------------------------------- + SELECT 1 + SELECT 1 +(2 rows) + +SELECT result FROM run_command_on_workers($$ + SELECT schemaid INTO tenant_2_schemaid FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_2' +$$); + result +--------------------------------------------------------------------- + SELECT 1 + SELECT 1 +(2 rows) + +SET client_min_messages TO WARNING; +SET citus.enable_schema_based_sharding TO OFF; +DROP SCHEMA tenant_1 CASCADE; +CREATE ROLE test_non_super_user; +ALTER ROLE test_non_super_user NOSUPERUSER; +ALTER SCHEMA tenant_2 OWNER TO non_existing_role; +ERROR: role "non_existing_role" does not exist +ALTER SCHEMA tenant_2 OWNER TO test_non_super_user; +SELECT pg_get_userbyid(nspowner) AS schema_owner + FROM pg_namespace + WHERE nspname = 'tenant_2'; + schema_owner +--------------------------------------------------------------------- + test_non_super_user +(1 row) + +select result from run_command_on_workers ($$ + SELECT pg_get_userbyid(nspowner) AS schema_owner + FROM pg_namespace + WHERE nspname = 'tenant_2' +$$); + result +--------------------------------------------------------------------- + test_non_super_user + test_non_super_user +(2 rows) + +DROP OWNED BY test_non_super_user CASCADE; +DROP ROLE test_non_super_user; +SET client_min_messages TO NOTICE; +-- (on coordinator) Verify that dropping a tenant schema always deletes +-- the associated pg_dist_schema entry even if the the schema was +-- dropped while the GUC was set to off. +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid IN (:tenant_1_schemaid, :tenant_2_schemaid); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid IN (:tenant_1_colocationid, :tenant_2_colocationid); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- (on workers) Verify that dropping a tenant schema always deletes +-- the associated pg_dist_schema entry even if the the schema was +-- dropped while the GUC was set to off. +SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_schema + WHERE schemaid IN (SELECT schemaid FROM tenant_1_schemaid UNION SELECT schemaid FROM tenant_2_schemaid) +$$); + result +--------------------------------------------------------------------- + t + t +(2 rows) + +SELECT format( + 'SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid IN (%s, %s); + $$);', +:tenant_1_colocationid, :tenant_2_colocationid) AS verify_workers_query \gset +:verify_workers_query + result +--------------------------------------------------------------------- + t + t +(2 rows) + +SELECT result FROM run_command_on_workers($$ + DROP TABLE tenant_1_schemaid +$$); + result +--------------------------------------------------------------------- + DROP TABLE + DROP TABLE +(2 rows) + +SELECT result FROM run_command_on_workers($$ + DROP TABLE tenant_2_schemaid +$$); + result +--------------------------------------------------------------------- + DROP TABLE + DROP TABLE +(2 rows) + +SET citus.enable_schema_based_sharding TO ON; +SET client_min_messages TO NOTICE; +-- show that all schemaid values are unique and non-null in pg_dist_schema +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid IS NULL; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT (SELECT COUNT(*) FROM pg_dist_schema) = + (SELECT COUNT(DISTINCT(schemaid)) FROM pg_dist_schema); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- show that all colocationid values are unique and non-null in pg_dist_schema +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE colocationid IS NULL; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT (SELECT COUNT(*) FROM pg_dist_schema) = + (SELECT COUNT(DISTINCT(colocationid)) FROM pg_dist_schema); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +CREATE TABLE public.cannot_be_a_tenant_table(a int, b text); +-- show that we don't consider public schema as a tenant schema +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'public'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +DROP TABLE public.cannot_be_a_tenant_table; +BEGIN; + ALTER SCHEMA public RENAME TO public_renamed; + CREATE SCHEMA public; + -- Show that we don't consider public schema as a tenant schema, + -- even if it's recreated. + SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'public'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +ROLLBACK; +CREATE TEMPORARY TABLE temp_table(a int, b text); +-- show that we don't consider temporary schemas as tenant schemas +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid::regnamespace::text = '%pg_temp%'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +DROP TABLE temp_table; +-- test creating a tenant schema and a tenant table for it in the same transaction +BEGIN; + CREATE SCHEMA tenant_7; + CREATE TABLE tenant_7.tbl_1(a int, b text); + CREATE TABLE tenant_7.tbl_2(a int, b text); + SELECT colocationid = ( + SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_7.tbl_1'::regclass + ) + FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_7'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + + -- make sure that both tables created in tenant_7 are colocated + SELECT COUNT(DISTINCT(colocationid)) = 1 FROM pg_dist_partition + WHERE logicalrelid IN ('tenant_7.tbl_1'::regclass, 'tenant_7.tbl_2'::regclass); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +COMMIT; +-- Test creating a tenant schema and a tenant table for it in the same transaction +-- but this time rollback the transaction. +BEGIN; + CREATE SCHEMA tenant_8; + CREATE TABLE tenant_8.tbl_1(a int, b text); + CREATE TABLE tenant_8.tbl_2(a int, b text); +ROLLBACK; +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_8'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT COUNT(*)=0 FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant_8.%'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- Verify that citus.enable_schema_based_sharding and citus.use_citus_managed_tables +-- GUC don't interfere with each other when creating a table in tenant schema. +-- +-- In utility hook, we check whether the CREATE TABLE command is issued on a tenant +-- schema before checking whether citus.use_citus_managed_tables is set to ON to +-- avoid converting the table into a Citus managed table unnecessarily. +-- +-- If the CREATE TABLE command is issued on a tenant schema, we skip the check +-- for citus.use_citus_managed_tables. +SET citus.use_citus_managed_tables TO ON; +CREATE TABLE tenant_7.tbl_3(a int, b text, PRIMARY KEY(a)); +RESET citus.use_citus_managed_tables; +-- Verify that we don't unnecessarily convert a table into a Citus managed +-- table when creating it with a pre-defined foreign key to a reference table. +CREATE TABLE reference_table(a int PRIMARY KEY); +SELECT create_reference_table('reference_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +-- Notice that tenant_7.tbl_4 have foreign keys both to tenant_7.tbl_3 and +-- to reference_table. +CREATE TABLE tenant_7.tbl_4(a int REFERENCES reference_table, FOREIGN KEY(a) REFERENCES tenant_7.tbl_3(a) ON DELETE CASCADE); +INSERT INTO tenant_7.tbl_3 VALUES (1, 'a'), (2, 'b'), (3, 'c'); +INSERT INTO reference_table VALUES (1), (2), (3); +INSERT INTO tenant_7.tbl_4 VALUES (1), (2), (3); +DELETE FROM tenant_7.tbl_3 WHERE a < 3; +SELECT * FROM tenant_7.tbl_4 ORDER BY a; + a +--------------------------------------------------------------------- + 3 +(1 row) + +SELECT COUNT(*)=2 FROM pg_dist_partition +WHERE logicalrelid IN ('tenant_7.tbl_3'::regclass, 'tenant_7.tbl_4'::regclass) AND + partmethod = 'n' AND repmodel = 's' AND + colocationid = (SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_7.tbl_1'::regclass); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +CREATE TABLE local_table(a int PRIMARY KEY); +-- fails because tenant tables cannot have foreign keys to local tables +CREATE TABLE tenant_7.tbl_5(a int REFERENCES local_table(a)); +ERROR: referenced table "local_table" must be a distributed table or a reference table +DETAIL: To enforce foreign keys, the referencing and referenced rows need to be stored on the same node. +HINT: You could use SELECT create_reference_table('local_table') to replicate the referenced table to all nodes or consider dropping the foreign key +-- Fails because tenant tables cannot have foreign keys to tenant tables +-- that belong to different tenant schemas. +CREATE TABLE tenant_5.tbl_5(a int, b text, FOREIGN KEY(a) REFERENCES tenant_7.tbl_3(a)); +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table +CREATE SCHEMA tenant_9; +SELECT schemaid AS tenant_9_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_9' \gset +SELECT colocationid AS tenant_9_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_9' \gset +SELECT result FROM run_command_on_workers($$ + SELECT schemaid INTO tenant_9_schemaid FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_9' +$$); + result +--------------------------------------------------------------------- + SELECT 1 + SELECT 1 +(2 rows) + +DROP SCHEMA tenant_9; +-- (on coordinator) Make sure that dropping an empty tenant schema +-- doesn't leave any dangling entries in pg_dist_schema and +-- pg_dist_colocation. +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid = :tenant_9_schemaid; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = :tenant_9_colocationid; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- (on workers) Make sure that dropping an empty tenant schema +-- doesn't leave any dangling entries in pg_dist_schema and +-- pg_dist_colocation. +SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_schema + WHERE schemaid = (SELECT schemaid FROM tenant_9_schemaid) +$$); + result +--------------------------------------------------------------------- + t + t +(2 rows) + +SELECT format( + 'SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = %s; + $$);', +:tenant_9_colocationid) AS verify_workers_query \gset +:verify_workers_query + result +--------------------------------------------------------------------- + t + t +(2 rows) + +SELECT result FROM run_command_on_workers($$ + DROP TABLE tenant_9_schemaid +$$); + result +--------------------------------------------------------------------- + DROP TABLE + DROP TABLE +(2 rows) + +CREATE TABLE tenant_3.search_path_test(a int); +INSERT INTO tenant_3.search_path_test VALUES (1), (10); +CREATE TABLE tenant_5.search_path_test(a int); +INSERT INTO tenant_5.search_path_test VALUES (2); +CREATE TABLE tenant_7.search_path_test(a int); +INSERT INTO tenant_7.search_path_test VALUES (3); +CREATE FUNCTION increment_one() +RETURNS void +LANGUAGE plpgsql +AS $$ +BEGIN + UPDATE search_path_test SET a = a + 1; +END; +$$; +CREATE FUNCTION decrement_one() +RETURNS void +LANGUAGE plpgsql +AS $$ +BEGIN + UPDATE search_path_test SET a = a - 1; +END; +$$; +SET search_path TO tenant_5; +PREPARE list_tuples AS SELECT * FROM search_path_test ORDER BY a; +SELECT * FROM search_path_test ORDER BY a; + a +--------------------------------------------------------------------- + 2 +(1 row) + +SET search_path TO tenant_3; +DELETE FROM search_path_test WHERE a = 1; +SELECT * FROM search_path_test ORDER BY a; + a +--------------------------------------------------------------------- + 10 +(1 row) + +SELECT regular_schema.increment_one(); + increment_one +--------------------------------------------------------------------- + +(1 row) + +EXECUTE list_tuples; + a +--------------------------------------------------------------------- + 11 +(1 row) + +SET search_path TO tenant_7; +DROP TABLE search_path_test; +SELECT * FROM pg_dist_partition WHERE logicalrelid::text = 'search_path_test'; + logicalrelid | partmethod | partkey | colocationid | repmodel | autoconverted +--------------------------------------------------------------------- +(0 rows) + +SET search_path TO tenant_5; +SELECT regular_schema.decrement_one(); + decrement_one +--------------------------------------------------------------------- + +(1 row) + +EXECUTE list_tuples; + a +--------------------------------------------------------------------- + 1 +(1 row) + +SET search_path TO regular_schema; +CREATE USER test_other_super_user WITH superuser; +\c - test_other_super_user +SET citus.enable_schema_based_sharding TO ON; +CREATE SCHEMA tenant_9; +\c - postgres +SET search_path TO regular_schema; +SET citus.next_shard_id TO 1930000; +SET citus.shard_count TO 32; +SET citus.shard_replication_factor TO 1; +SET client_min_messages TO NOTICE; +SET citus.enable_schema_based_sharding TO ON; +SELECT schemaid AS tenant_9_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_9' \gset +SELECT colocationid AS tenant_9_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_9' \gset +SELECT result FROM run_command_on_workers($$ + SELECT schemaid INTO tenant_9_schemaid FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_9' +$$); + result +--------------------------------------------------------------------- + SELECT 1 + SELECT 1 +(2 rows) + +DROP OWNED BY test_other_super_user; +-- (on coordinator) Make sure that dropping an empty tenant schema +-- (via DROP OWNED BY) doesn't leave any dangling entries in +-- pg_dist_schema and pg_dist_colocation. +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid = :tenant_9_schemaid; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = :tenant_9_colocationid; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- (on workers) Make sure that dropping an empty tenant schema +-- (via DROP OWNED BY) doesn't leave any dangling entries in +-- pg_dist_schema and pg_dist_colocation. +SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_schema + WHERE schemaid = (SELECT schemaid FROM tenant_9_schemaid) +$$); + result +--------------------------------------------------------------------- + t + t +(2 rows) + +SELECT format( + 'SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = %s; + $$);', +:tenant_9_colocationid) AS verify_workers_query \gset +:verify_workers_query + result +--------------------------------------------------------------------- + t + t +(2 rows) + +SELECT result FROM run_command_on_workers($$ + DROP TABLE tenant_9_schemaid +$$); + result +--------------------------------------------------------------------- + DROP TABLE + DROP TABLE +(2 rows) + +DROP USER test_other_super_user; +CREATE ROLE test_non_super_user WITH LOGIN; +ALTER ROLE test_non_super_user NOSUPERUSER; +GRANT CREATE ON DATABASE regression TO test_non_super_user; +SELECT result FROM run_command_on_workers($$GRANT CREATE ON DATABASE regression TO test_non_super_user$$); + result +--------------------------------------------------------------------- + GRANT + GRANT +(2 rows) + +GRANT CREATE ON SCHEMA public TO test_non_super_user ; +\c - test_non_super_user +SET search_path TO regular_schema; +SET citus.next_shard_id TO 1940000; +SET citus.shard_count TO 32; +SET citus.shard_replication_factor TO 1; +SET client_min_messages TO NOTICE; +SET citus.enable_schema_based_sharding TO ON; +-- test create / drop tenant schema / table +CREATE SCHEMA tenant_10; +CREATE TABLE tenant_10.tbl_1(a int, b text); +CREATE TABLE tenant_10.tbl_2(a int, b text); +DROP TABLE tenant_10.tbl_2; +CREATE SCHEMA tenant_11; +SELECT schemaid AS tenant_10_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_10' \gset +SELECT colocationid AS tenant_10_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_10' \gset +SELECT schemaid AS tenant_11_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_11' \gset +SELECT colocationid AS tenant_11_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_11' \gset +SELECT result FROM run_command_on_workers($$ + SELECT schemaid INTO tenant_10_schemaid FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_10' +$$); + result +--------------------------------------------------------------------- + SELECT 1 + SELECT 1 +(2 rows) + +SELECT result FROM run_command_on_workers($$ + SELECT schemaid INTO tenant_11_schemaid FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_11' +$$); + result +--------------------------------------------------------------------- + SELECT 1 + SELECT 1 +(2 rows) + +-- (on coordinator) Verify metadata for tenant schemas that are created via non-super-user. +SELECT COUNT(DISTINCT(schemaid))=2 FROM pg_dist_schema WHERE schemaid IN (:tenant_10_schemaid, :tenant_11_schemaid); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT COUNT(DISTINCT(colocationid))=2 FROM pg_dist_colocation WHERE colocationid IN (:tenant_10_colocationid, :tenant_11_colocationid); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- (on workers) Verify metadata for tenant schemas that are created via non-super-user. +SELECT result FROM run_command_on_workers($$ + SELECT COUNT(DISTINCT(schemaid))=2 FROM pg_dist_schema + WHERE schemaid IN (SELECT schemaid FROM tenant_10_schemaid UNION SELECT schemaid FROM tenant_11_schemaid) +$$); + result +--------------------------------------------------------------------- + t + t +(2 rows) + +SELECT format( + 'SELECT result FROM run_command_on_workers($$ + SELECT COUNT(DISTINCT(colocationid))=2 FROM pg_dist_colocation WHERE colocationid IN (%s, %s); + $$);', +:tenant_10_colocationid, :tenant_11_colocationid) AS verify_workers_query \gset +:verify_workers_query + result +--------------------------------------------------------------------- + t + t +(2 rows) + +SET client_min_messages TO WARNING; +DROP SCHEMA tenant_10, tenant_11 CASCADE; +SET client_min_messages TO NOTICE; +-- (on coordinator) Verify that dropping a tenant schema via non-super-user +-- deletes the associated pg_dist_schema entry. +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid IN (:tenant_10_schemaid, :tenant_11_schemaid); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid IN (:tenant_10_colocationid, :tenant_11_colocationid); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- (on workers) Verify that dropping a tenant schema via non-super-user +-- deletes the associated pg_dist_schema entry. +SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_schema + WHERE schemaid IN (SELECT schemaid FROM tenant_10_schemaid UNION SELECT schemaid FROM tenant_11_schemaid) +$$); + result +--------------------------------------------------------------------- + t + t +(2 rows) + +SELECT format( + 'SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid IN (%s, %s); + $$);', +:tenant_10_colocationid, :tenant_11_colocationid) AS verify_workers_query \gset +:verify_workers_query + result +--------------------------------------------------------------------- + t + t +(2 rows) + +SELECT result FROM run_command_on_workers($$ + DROP TABLE tenant_10_schemaid +$$); + result +--------------------------------------------------------------------- + DROP TABLE + DROP TABLE +(2 rows) + +SELECT result FROM run_command_on_workers($$ + DROP TABLE tenant_11_schemaid +$$); + result +--------------------------------------------------------------------- + DROP TABLE + DROP TABLE +(2 rows) + +\c - postgres +REVOKE CREATE ON DATABASE regression FROM test_non_super_user; +SELECT result FROM run_command_on_workers($$REVOKE CREATE ON DATABASE regression FROM test_non_super_user$$); + result +--------------------------------------------------------------------- + REVOKE + REVOKE +(2 rows) + +REVOKE CREATE ON SCHEMA public FROM test_non_super_user; +DROP ROLE test_non_super_user; +\c - - - :worker_1_port +-- test creating a tenant table from workers +CREATE TABLE tenant_3.tbl_1(a int, b text); +ERROR: cannot create tables in a distributed schema from a worker node +HINT: Connect to the coordinator node and try again. +-- test creating a tenant schema from workers +SET citus.enable_schema_based_sharding TO ON; +CREATE SCHEMA worker_tenant_schema; +ERROR: operation is not allowed on this node +HINT: Connect to the coordinator and run it again. +SET citus.enable_schema_based_sharding TO OFF; +-- Enable the GUC on workers to make sure that the CREATE SCHEMA/ TABLE +-- commands that we send to workers don't recursively try creating a +-- tenant schema / table. +ALTER SYSTEM SET citus.enable_schema_based_sharding TO ON; +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + +\c - - - :worker_2_port +ALTER SYSTEM SET citus.enable_schema_based_sharding TO ON; +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + +-- Verify that citus_internal_unregister_tenant_schema_globally is a no-op +-- on workers. +SELECT citus_internal_unregister_tenant_schema_globally('tenant_3'::regnamespace, 'tenant_3'); + citus_internal_unregister_tenant_schema_globally +--------------------------------------------------------------------- + +(1 row) + +\c - - - :master_port +SET search_path TO regular_schema; +SET citus.next_shard_id TO 1950000; +SET citus.shard_count TO 32; +SET citus.shard_replication_factor TO 1; +SET client_min_messages TO NOTICE; +CREATE TABLE tenant_3.tbl_1(a int, b text); +SET citus.enable_schema_based_sharding TO ON; +CREATE SCHEMA tenant_6; +CREATE TABLE tenant_6.tbl_1(a int, b text); +-- verify pg_dist_partition entries for tenant_3.tbl_1 and tenant_6.tbl_1 +SELECT COUNT(*)=2 FROM pg_dist_partition +WHERE logicalrelid IN ('tenant_3.tbl_1'::regclass, 'tenant_6.tbl_1'::regclass) AND + partmethod = 'n' AND repmodel = 's' AND colocationid > 0; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +\c - - - :worker_1_port +ALTER SYSTEM RESET citus.enable_schema_based_sharding; +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + +\c - - - :worker_2_port +ALTER SYSTEM RESET citus.enable_schema_based_sharding; +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + +\c - - - :master_port +SET search_path TO regular_schema; +CREATE TABLE type_sing(a INT); +-- errors out because shard_replication_factor = 2 +SELECT create_distributed_table('type_sing', NULL, colocate_with:='none'); +ERROR: could not create single shard table: citus.shard_replication_factor is greater than 1 +HINT: Consider setting citus.shard_replication_factor to 1 and try again +SET citus.shard_replication_factor TO 1; +SELECT create_distributed_table('type_sing', NULL, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SET citus.enable_schema_based_sharding TO ON; +CREATE SCHEMA type_sch; +CREATE TABLE type_sch.tbl (a INT); +SELECT table_name, citus_table_type FROM public.citus_tables WHERE table_name::text LIKE 'type_%'; + table_name | citus_table_type +--------------------------------------------------------------------- + type_sch.tbl | schema + type_sing | distributed +(2 rows) + +SELECT table_name, citus_table_type FROM citus_shards WHERE table_name::text LIKE 'type_%' AND nodeport IN (:worker_1_port, :worker_2_port); + table_name | citus_table_type +--------------------------------------------------------------------- + type_sch.tbl | schema + type_sing | distributed +(2 rows) + +RESET citus.enable_schema_based_sharding; +-- test citus_schemas +SET citus.enable_schema_based_sharding TO ON; +CREATE USER citus_schema_role SUPERUSER; +SET ROLE citus_schema_role; +CREATE SCHEMA citus_sch1; +CREATE TABLE citus_sch1.tbl1(a INT); +CREATE TABLE citus_sch1.tbl2(a INT); +RESET ROLE; +CREATE SCHEMA citus_sch2; +CREATE TABLE citus_sch2.tbl1(a INT); +SET citus.enable_schema_based_sharding TO OFF; +INSERT INTO citus_sch1.tbl1 SELECT * FROM generate_series(1, 10000); +INSERT INTO citus_sch1.tbl2 SELECT * FROM generate_series(1, 5000); +INSERT INTO citus_sch2.tbl1 SELECT * FROM generate_series(1, 12000); +SELECT + cs.schema_name, + cs.colocation_id = ctc.colocation_id AS correct_colocation_id, + cs.schema_size = ctc.calculated_size AS correct_size, + cs.schema_owner +FROM public.citus_schemas cs +JOIN +( + SELECT + c.relnamespace, ct.colocation_id, + pg_size_pretty(sum(citus_total_relation_size(ct.table_name))) AS calculated_size + FROM public.citus_tables ct, pg_class c + WHERE ct.table_name::oid = c.oid + GROUP BY 1, 2 +) ctc ON cs.schema_name = ctc.relnamespace +WHERE cs.schema_name::text LIKE 'citus\_sch_' +ORDER BY cs.schema_name::text; + schema_name | correct_colocation_id | correct_size | schema_owner +--------------------------------------------------------------------- + citus_sch1 | t | t | citus_schema_role + citus_sch2 | t | t | postgres +(2 rows) + +-- test empty schema and empty tables +SET citus.enable_schema_based_sharding TO ON; +CREATE SCHEMA citus_empty_sch1; +CREATE SCHEMA citus_empty_sch2; +CREATE TABLE citus_empty_sch2.tbl1(a INT); +SET citus.enable_schema_based_sharding TO OFF; +SELECT schema_name, schema_size FROM public.citus_schemas +WHERE schema_name::text LIKE 'citus\_empty\_sch_' ORDER BY schema_name::text; + schema_name | schema_size +--------------------------------------------------------------------- + citus_empty_sch1 | 0 bytes + citus_empty_sch2 | 0 bytes +(2 rows) + +-- test with non-privileged role +CREATE USER citus_schema_nonpri; +SET ROLE citus_schema_nonpri; +SET client_min_messages TO ERROR; +SELECT schema_name, colocation_id > 0 AS colocation_id_visible, schema_size IS NOT NULL AS schema_size_visible, schema_owner +FROM public.citus_schemas WHERE schema_name::text LIKE 'citus\_sch_' ORDER BY schema_name::text; + schema_name | colocation_id_visible | schema_size_visible | schema_owner +--------------------------------------------------------------------- + citus_sch1 | t | f | citus_schema_role + citus_sch2 | t | f | postgres +(2 rows) + +RESET client_min_messages; +RESET ROLE; +-- test using citus_tables from workers +\c - - - :worker_1_port +SELECT schema_name, colocation_id > 0 AS colocation_id_visible, schema_size IS NOT NULL AS schema_size_visible, schema_owner +FROM public.citus_schemas WHERE schema_name::text LIKE 'citus\_sch_' ORDER BY schema_name::text; + schema_name | colocation_id_visible | schema_size_visible | schema_owner +--------------------------------------------------------------------- + citus_sch1 | t | t | citus_schema_role + citus_sch2 | t | t | postgres +(2 rows) + +\c - - - :master_port +SET search_path TO regular_schema; +-- test we handle create schema with authorization properly for distributed schema +SET citus.enable_schema_based_sharding TO ON; +CREATE ROLE authschema; +CREATE SCHEMA AUTHORIZATION authschema; +SET citus.enable_schema_based_sharding TO OFF; +SELECT result FROM run_command_on_all_nodes($$ + SELECT COUNT(*)=1 + FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'authschema'; +$$); + result +--------------------------------------------------------------------- + t + t + t +(3 rows) + +SET client_min_messages TO WARNING; +DROP SCHEMA regular_schema, tenant_3, tenant_5, tenant_7, tenant_6, type_sch, citus_sch1, citus_sch2, citus_empty_sch1, citus_empty_sch2, authschema CASCADE; +DROP ROLE citus_schema_role, citus_schema_nonpri, authschema; +SELECT citus_remove_node('localhost', :master_port); + citus_remove_node +--------------------------------------------------------------------- + +(1 row) + diff --git a/src/test/regress/expected/sequential_modifications.out b/src/test/regress/expected/sequential_modifications.out index e5092ae56..c0acde1da 100644 --- a/src/test/regress/expected/sequential_modifications.out +++ b/src/test/regress/expected/sequential_modifications.out @@ -22,7 +22,7 @@ $$ DECLARE result bool; BEGIN - SELECT tx_count = worker_count FROM (SELECT count(*) as tx_count FROM pg_dist_transaction WHERE gid LIKE 'citus_%_' || pg_backend_pid() || '%_%') as s1, (SELECT count(*) as worker_count FROM pg_dist_node WHERE noderole = 'primary') as s2 INTO result; + SELECT tx_count = worker_count FROM (SELECT count(*) as tx_count FROM pg_dist_transaction WHERE gid LIKE 'citus_%_' || pg_backend_pid() || '%_%') as s1, (SELECT count(*) as worker_count FROM pg_dist_node WHERE noderole = 'primary' AND groupid <> 0 ) as s2 INTO result; RETURN result; END; $$ @@ -669,13 +669,14 @@ HINT: To remove the local data, run: SELECT truncate_local_data_after_distribut ABORT; SET search_path TO 'public'; DROP SCHEMA test_seq_ddl CASCADE; -NOTICE: drop cascades to 11 other objects +NOTICE: drop cascades to 12 other objects DETAIL: drop cascades to function test_seq_ddl.distributed_2pcs_are_equal_to_worker_count() drop cascades to function test_seq_ddl.distributed_2pcs_are_equal_to_placement_count() drop cascades to function test_seq_ddl.no_distributed_2pcs() drop cascades to function test_seq_ddl.set_local_multi_shard_modify_mode_to_sequential() drop cascades to table test_seq_ddl.test_table drop cascades to table test_seq_ddl.ref_test +drop cascades to table test_seq_ddl.ref_test_16004 drop cascades to table test_seq_ddl.test_table_rep_2 drop cascades to table test_seq_ddl.test_seq_truncate drop cascades to table test_seq_ddl.test_seq_truncate_rep_2 diff --git a/src/test/regress/expected/set_operation_and_local_tables.out b/src/test/regress/expected/set_operation_and_local_tables.out index 92cde1148..db9b36506 100644 --- a/src/test/regress/expected/set_operation_and_local_tables.out +++ b/src/test/regress/expected/set_operation_and_local_tables.out @@ -321,15 +321,33 @@ DEBUG: push down of limit count: 2 DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 5 DEBUG: pruning merge fetch taskId 2 @@ -346,6 +364,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 20 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 25 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 25 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 30 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 30 DEBUG: generating subplan XXX_1 for subquery SELECT t1.x FROM recursive_set_local.test t1, recursive_set_local.test t2 WHERE (t1.x OPERATOR(pg_catalog.=) t2.y) LIMIT 2 DEBUG: generating subplan XXX_2 for subquery SELECT x FROM recursive_set_local.local_test DEBUG: Router planner cannot handle multi-shard select queries @@ -360,9 +386,5 @@ DEBUG: Creating router plan 1 (2 rows) -RESET client_min_messages; +SET client_min_messages TO WARNING; DROP SCHEMA recursive_set_local CASCADE; -NOTICE: drop cascades to 3 other objects -DETAIL: drop cascades to table test -drop cascades to table ref -drop cascades to table local_test diff --git a/src/test/regress/expected/set_operations.out b/src/test/regress/expected/set_operations.out index 9c9d22fb9..a0dad36a8 100644 --- a/src/test/regress/expected/set_operations.out +++ b/src/test/regress/expected/set_operations.out @@ -916,15 +916,33 @@ DEBUG: push down of limit count: 0 DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 5 DEBUG: pruning merge fetch taskId 2 @@ -941,6 +959,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 20 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 25 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 25 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 30 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 30 DEBUG: generating subplan XXX_1 for subquery SELECT t1.x FROM recursive_union.test t1, recursive_union.test t2 WHERE (t1.x OPERATOR(pg_catalog.=) t2.y) LIMIT 0 DEBUG: Router planner cannot handle multi-shard select queries DEBUG: generating subplan XXX_2 for subquery SELECT x FROM recursive_union.test @@ -957,15 +983,33 @@ DEBUG: Router planner cannot handle multi-shard select queries DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 5 DEBUG: pruning merge fetch taskId 2 @@ -982,6 +1026,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 20 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 25 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 25 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 30 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 30 DEBUG: generating subplan XXX_1 for subquery SELECT t1.x FROM recursive_union.test t1, recursive_union.test t2 WHERE (t1.x OPERATOR(pg_catalog.=) t2.y) DEBUG: Router planner cannot handle multi-shard select queries DEBUG: generating subplan XXX_2 for subquery SELECT x FROM recursive_union.test @@ -1067,7 +1119,7 @@ DEBUG: Creating router plan -- queries on non-colocated tables that would push down if they were not colocated are recursivelu planned SELECT * FROM (SELECT * FROM test UNION SELECT * FROM test_not_colocated) u ORDER BY 1,2; -DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: router planner does not support queries that reference non-colocated distributed tables DEBUG: Router planner cannot handle multi-shard select queries DEBUG: generating subplan XXX_1 for subquery SELECT x, y FROM recursive_union.test DEBUG: Router planner cannot handle multi-shard select queries @@ -1083,7 +1135,7 @@ DEBUG: Creating router plan (2 rows) SELECT * FROM (SELECT * FROM test UNION ALL SELECT * FROM test_not_colocated) u ORDER BY 1,2; -DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: router planner does not support queries that reference non-colocated distributed tables DEBUG: Router planner cannot handle multi-shard select queries DEBUG: generating subplan XXX_1 for subquery SELECT x, y FROM recursive_union.test DEBUG: Router planner cannot handle multi-shard select queries @@ -1098,12 +1150,5 @@ DEBUG: Creating router plan 2 | 2 (2 rows) -RESET client_min_messages; +SET client_min_messages TO WARNING; DROP SCHEMA recursive_union CASCADE; -NOTICE: drop cascades to 6 other objects -DETAIL: drop cascades to table test -drop cascades to table ref -drop cascades to table test_not_colocated -drop cascades to view set_view_recursive -drop cascades to view set_view_pushdown -drop cascades to view set_view_recursive_second diff --git a/src/test/regress/expected/shard_move_constraints.out b/src/test/regress/expected/shard_move_constraints.out index 931e55644..72b49f262 100644 --- a/src/test/regress/expected/shard_move_constraints.out +++ b/src/test/regress/expected/shard_move_constraints.out @@ -472,12 +472,13 @@ SELECT start_metadata_sync_to_node('localhost', :worker_2_port); (1 row) DROP SCHEMA "shard Move Fkeys Indexes" CASCADE; -NOTICE: drop cascades to 7 other objects +NOTICE: drop cascades to 8 other objects DETAIL: drop cascades to extension btree_gist drop cascades to table "shard Move Fkeys Indexes".sensors drop cascades to table "shard Move Fkeys Indexes".colocated_dist_table drop cascades to table "shard Move Fkeys Indexes".colocated_partitioned_table drop cascades to table "shard Move Fkeys Indexes".reference_table +drop cascades to table "shard Move Fkeys Indexes".reference_table_8970028 drop cascades to table "shard Move Fkeys Indexes".index_backed_rep_identity drop cascades to table "shard Move Fkeys Indexes".multiple_unique_keys DROP ROLE mx_rebalancer_role_ent; diff --git a/src/test/regress/expected/shard_move_constraints_blocking.out b/src/test/regress/expected/shard_move_constraints_blocking.out index 82d925821..5f1b91cb6 100644 --- a/src/test/regress/expected/shard_move_constraints_blocking.out +++ b/src/test/regress/expected/shard_move_constraints_blocking.out @@ -358,10 +358,11 @@ ALTER TABLE sensors_2020_01_01 DROP CONSTRAINT fkey_from_child_to_child; -- cleanup \c - postgres - :master_port DROP SCHEMA "blocking shard Move Fkeys Indexes" CASCADE; -NOTICE: drop cascades to 5 other objects +NOTICE: drop cascades to 6 other objects DETAIL: drop cascades to table "blocking shard Move Fkeys Indexes".sensors drop cascades to table "blocking shard Move Fkeys Indexes".colocated_dist_table drop cascades to table "blocking shard Move Fkeys Indexes".colocated_partitioned_table drop cascades to table "blocking shard Move Fkeys Indexes".reference_table +drop cascades to table "blocking shard Move Fkeys Indexes".reference_table_8970028 drop cascades to table "blocking shard Move Fkeys Indexes".index_backed_rep_identity DROP ROLE mx_rebalancer_blocking_role_ent; diff --git a/src/test/regress/expected/shard_move_deferred_delete.out b/src/test/regress/expected/shard_move_deferred_delete.out index e87cd0f97..6a0c4fcd1 100644 --- a/src/test/regress/expected/shard_move_deferred_delete.out +++ b/src/test/regress/expected/shard_move_deferred_delete.out @@ -226,7 +226,7 @@ SET search_path TO shard_move_deferred_delete; SELECT citus_shard_cost_by_disk_size(20000001); citus_shard_cost_by_disk_size --------------------------------------------------------------------- - 8192 + 1.04866e+08 (1 row) -- When there's not enough space the move should fail diff --git a/src/test/regress/expected/shard_rebalancer.out b/src/test/regress/expected/shard_rebalancer.out index 1dea3b442..b8f4010b1 100644 --- a/src/test/regress/expected/shard_rebalancer.out +++ b/src/test/regress/expected/shard_rebalancer.out @@ -3,6 +3,23 @@ -- SET citus.next_shard_id TO 433000; SET citus.propagate_session_settings_for_loopback_connection TO ON; +-- Because of historic reasons this test was written in a way that assumes that +-- by_shard_count is the default strategy. +SELECT citus_set_default_rebalance_strategy('by_shard_count'); + citus_set_default_rebalance_strategy +--------------------------------------------------------------------- + +(1 row) + +-- Lower the minimum disk size that a shard group is considered as. Otherwise +-- we need to create shards of more than 100MB. +ALTER SYSTEM SET citus.rebalancer_by_disk_size_base_cost = 0; +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + CREATE TABLE ref_table_test(a int primary key); SELECT create_reference_table('ref_table_test'); create_reference_table @@ -20,13 +37,14 @@ SELECT create_distributed_table('dist_table_test', 'a'); CREATE TABLE postgres_table_test(a int primary key); -- make sure that all rebalance operations works fine when -- reference tables are replicated to the coordinator +SET client_min_messages TO ERROR; SELECT 1 FROM master_add_node('localhost', :master_port, groupId=>0); -NOTICE: localhost:xxxxx is the coordinator and already contains metadata, skipping syncing the metadata ?column? --------------------------------------------------------------------- 1 (1 row) +RESET client_min_messages; -- should just be noops even if we add the coordinator to the pg_dist_node SELECT rebalance_table_shards('dist_table_test'); rebalance_table_shards @@ -221,7 +239,7 @@ NOTICE: issuing SET LOCAL citus.shard_count TO '4'; DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing SET LOCAL citus.shard_replication_factor TO '2'; DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing SELECT citus_copy_shard_placement(43xxxx,xx,xx,'block_writes') +NOTICE: issuing SELECT pg_catalog.citus_copy_shard_placement(43xxxx,xx,xx,'block_writes') DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing COMMIT DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx @@ -244,7 +262,7 @@ NOTICE: issuing SET LOCAL citus.shard_count TO '4'; DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing SET LOCAL citus.shard_replication_factor TO '2'; DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing SELECT citus_copy_shard_placement(43xxxx,xx,xx,'block_writes') +NOTICE: issuing SELECT pg_catalog.citus_copy_shard_placement(43xxxx,xx,xx,'block_writes') DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing COMMIT DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx @@ -267,7 +285,7 @@ NOTICE: issuing SET LOCAL citus.shard_count TO '4'; DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing SET LOCAL citus.shard_replication_factor TO '2'; DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing SELECT citus_copy_shard_placement(43xxxx,xx,xx,'block_writes') +NOTICE: issuing SELECT pg_catalog.citus_copy_shard_placement(43xxxx,xx,xx,'block_writes') DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing COMMIT DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx @@ -290,7 +308,7 @@ NOTICE: issuing SET LOCAL citus.shard_count TO '4'; DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing SET LOCAL citus.shard_replication_factor TO '2'; DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing SELECT citus_copy_shard_placement(43xxxx,xx,xx,'block_writes') +NOTICE: issuing SELECT pg_catalog.citus_copy_shard_placement(43xxxx,xx,xx,'block_writes') DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing COMMIT DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx @@ -2181,7 +2199,7 @@ SELECT 1 FROM master_remove_node('localhost', :master_port); 1 (1 row) -SELECT public.wait_until_metadata_sync(30000); +SELECT public.wait_until_metadata_sync(60000); wait_until_metadata_sync --------------------------------------------------------------------- @@ -2713,6 +2731,113 @@ SELECT sh.logicalrelid, pl.nodeport (5 rows) DROP TABLE single_shard_colocation_1a, single_shard_colocation_1b, single_shard_colocation_1c, single_shard_colocation_2a, single_shard_colocation_2b CASCADE; +-- test the same with coordinator shouldhaveshards = false and shard_count = 2 +-- so that the shard allowed node count would be 2 when rebalancing +-- for such cases, we only count the nodes that are allowed for shard placements +UPDATE pg_dist_node SET shouldhaveshards=false WHERE nodeport = :master_port; +create table two_shard_colocation_1a (a int primary key); +create table two_shard_colocation_1b (a int primary key); +SET citus.shard_replication_factor = 1; +select create_distributed_table('two_shard_colocation_1a','a', colocate_with => 'none', shard_count => 2); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +select create_distributed_table('two_shard_colocation_1b','a',colocate_with=>'two_shard_colocation_1a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +create table two_shard_colocation_2a (a int primary key); +create table two_shard_colocation_2b (a int primary key); +select create_distributed_table('two_shard_colocation_2a','a', colocate_with => 'none', shard_count => 2); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +select create_distributed_table('two_shard_colocation_2b','a',colocate_with=>'two_shard_colocation_2a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- move shards of colocation group 1 to worker1 +SELECT citus_move_shard_placement(sh.shardid, 'localhost', :worker_2_port, 'localhost', :worker_1_port) + FROM pg_dist_shard sh JOIN pg_dist_shard_placement pl ON sh.shardid = pl.shardid + WHERE sh.logicalrelid = 'two_shard_colocation_1a'::regclass + AND pl.nodeport = :worker_2_port + LIMIT 1; + citus_move_shard_placement +--------------------------------------------------------------------- + +(1 row) + +-- move shards of colocation group 2 to worker2 +SELECT citus_move_shard_placement(sh.shardid, 'localhost', :worker_1_port, 'localhost', :worker_2_port) + FROM pg_dist_shard sh JOIN pg_dist_shard_placement pl ON sh.shardid = pl.shardid + WHERE sh.logicalrelid = 'two_shard_colocation_2a'::regclass + AND pl.nodeport = :worker_1_port + LIMIT 1; + citus_move_shard_placement +--------------------------------------------------------------------- + +(1 row) + +-- current state: +-- coordinator: [] +-- worker 1: [1_1, 1_2] +-- worker 2: [2_1, 2_2] +SELECT sh.logicalrelid, pl.nodeport + FROM pg_dist_shard sh JOIN pg_dist_shard_placement pl ON sh.shardid = pl.shardid + WHERE sh.logicalrelid::text IN ('two_shard_colocation_1a', 'two_shard_colocation_1b', 'two_shard_colocation_2a', 'two_shard_colocation_2b') + ORDER BY sh.logicalrelid, pl.nodeport; + logicalrelid | nodeport +--------------------------------------------------------------------- + two_shard_colocation_1a | 57637 + two_shard_colocation_1a | 57637 + two_shard_colocation_1b | 57637 + two_shard_colocation_1b | 57637 + two_shard_colocation_2a | 57638 + two_shard_colocation_2a | 57638 + two_shard_colocation_2b | 57638 + two_shard_colocation_2b | 57638 +(8 rows) + +-- If we take the coordinator into account, the rebalancer considers this as balanced and does nothing (shard_count < worker_count) +-- but because the coordinator is not allowed for shards, rebalancer will distribute each colocation group to both workers +select rebalance_table_shards(shard_transfer_mode:='block_writes'); +NOTICE: Moving shard xxxxx from localhost:xxxxx to localhost:xxxxx ... +NOTICE: Moving shard xxxxx from localhost:xxxxx to localhost:xxxxx ... + rebalance_table_shards +--------------------------------------------------------------------- + +(1 row) + +-- final state: +-- coordinator: [] +-- worker 1: [1_1, 2_1] +-- worker 2: [1_2, 2_2] +SELECT sh.logicalrelid, pl.nodeport + FROM pg_dist_shard sh JOIN pg_dist_shard_placement pl ON sh.shardid = pl.shardid + WHERE sh.logicalrelid::text IN ('two_shard_colocation_1a', 'two_shard_colocation_1b', 'two_shard_colocation_2a', 'two_shard_colocation_2b') + ORDER BY sh.logicalrelid, pl.nodeport; + logicalrelid | nodeport +--------------------------------------------------------------------- + two_shard_colocation_1a | 57637 + two_shard_colocation_1a | 57638 + two_shard_colocation_1b | 57637 + two_shard_colocation_1b | 57638 + two_shard_colocation_2a | 57637 + two_shard_colocation_2a | 57638 + two_shard_colocation_2b | 57637 + two_shard_colocation_2b | 57638 +(8 rows) + +-- cleanup +DROP TABLE two_shard_colocation_1a, two_shard_colocation_1b, two_shard_colocation_2a, two_shard_colocation_2b CASCADE; -- verify we detect if one of the tables do not have a replica identity or primary key -- and error out in case of shard transfer mode = auto SELECT 1 FROM citus_remove_node('localhost', :worker_2_port); @@ -2746,6 +2871,19 @@ select 1 from citus_add_node('localhost', :worker_2_port); select rebalance_table_shards(); ERROR: cannot use logical replication to transfer shards of the relation table_without_primary_key since it doesn't have a REPLICA IDENTITY or PRIMARY KEY DROP TABLE table_with_primary_key, table_without_primary_key; +SELECT citus_set_default_rebalance_strategy('by_disk_size'); + citus_set_default_rebalance_strategy +--------------------------------------------------------------------- + +(1 row) + +ALTER SYSTEM RESET citus.rebalancer_by_disk_size_base_cost; +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + \c - - - :worker_1_port SET citus.enable_ddl_propagation TO OFF; REVOKE ALL ON SCHEMA public FROM testrole; diff --git a/src/test/regress/expected/single_hash_repartition_join.out b/src/test/regress/expected/single_hash_repartition_join.out index 31a5c7e9f..7f7586355 100644 --- a/src/test/regress/expected/single_hash_repartition_join.out +++ b/src/test/regress/expected/single_hash_repartition_join.out @@ -196,15 +196,33 @@ DETAIL: Creating dependency on merge taskId 20 DEBUG: join prunable for task partitionId 0 and 1 DEBUG: join prunable for task partitionId 0 and 2 DEBUG: join prunable for task partitionId 0 and 3 +DEBUG: join prunable for task partitionId 0 and 4 +DEBUG: join prunable for task partitionId 0 and 5 DEBUG: join prunable for task partitionId 1 and 0 DEBUG: join prunable for task partitionId 1 and 2 DEBUG: join prunable for task partitionId 1 and 3 +DEBUG: join prunable for task partitionId 1 and 4 +DEBUG: join prunable for task partitionId 1 and 5 DEBUG: join prunable for task partitionId 2 and 0 DEBUG: join prunable for task partitionId 2 and 1 DEBUG: join prunable for task partitionId 2 and 3 +DEBUG: join prunable for task partitionId 2 and 4 +DEBUG: join prunable for task partitionId 2 and 5 DEBUG: join prunable for task partitionId 3 and 0 DEBUG: join prunable for task partitionId 3 and 1 DEBUG: join prunable for task partitionId 3 and 2 +DEBUG: join prunable for task partitionId 3 and 4 +DEBUG: join prunable for task partitionId 3 and 5 +DEBUG: join prunable for task partitionId 4 and 0 +DEBUG: join prunable for task partitionId 4 and 1 +DEBUG: join prunable for task partitionId 4 and 2 +DEBUG: join prunable for task partitionId 4 and 3 +DEBUG: join prunable for task partitionId 4 and 5 +DEBUG: join prunable for task partitionId 5 and 0 +DEBUG: join prunable for task partitionId 5 and 1 +DEBUG: join prunable for task partitionId 5 and 2 +DEBUG: join prunable for task partitionId 5 and 3 +DEBUG: join prunable for task partitionId 5 and 4 DEBUG: pruning merge fetch taskId 1 DETAIL: Creating dependency on merge taskId 9 DEBUG: pruning merge fetch taskId 2 @@ -221,6 +239,14 @@ DEBUG: pruning merge fetch taskId 10 DETAIL: Creating dependency on merge taskId 24 DEBUG: pruning merge fetch taskId 11 DETAIL: Creating dependency on merge taskId 20 +DEBUG: pruning merge fetch taskId 13 +DETAIL: Creating dependency on merge taskId 29 +DEBUG: pruning merge fetch taskId 14 +DETAIL: Creating dependency on merge taskId 25 +DEBUG: pruning merge fetch taskId 16 +DETAIL: Creating dependency on merge taskId 34 +DEBUG: pruning merge fetch taskId 17 +DETAIL: Creating dependency on merge taskId 30 ERROR: the query contains a join that requires repartitioning HINT: Set citus.enable_repartition_joins to on to enable repartitioning -- single hash repartitioning is not supported between different column types diff --git a/src/test/regress/expected/single_node.out b/src/test/regress/expected/single_node.out index 7f152decd..4b6ea0837 100644 --- a/src/test/regress/expected/single_node.out +++ b/src/test/regress/expected/single_node.out @@ -101,8 +101,85 @@ SELECT pg_reload_conf(); t (1 row) +CREATE TABLE single_node_nullkey_c1(a int, b int); +SELECT create_distributed_table('single_node_nullkey_c1', null, colocate_with=>'none', distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE single_node_nullkey_c2(a int, b int); +SELECT create_distributed_table('single_node_nullkey_c2', null, colocate_with=>'none', distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- created on different colocation groups .. +SELECT +( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'single_node.single_node_nullkey_c1'::regclass +) +!= +( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'single_node.single_node_nullkey_c2'::regclass +); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- .. but both are associated to coordinator +SELECT groupid = 0 FROM pg_dist_placement +WHERE shardid = ( + SELECT shardid FROM pg_dist_shard + WHERE logicalrelid = 'single_node.single_node_nullkey_c1'::regclass +); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT groupid = 0 FROM pg_dist_placement +WHERE shardid = ( + SELECT shardid FROM pg_dist_shard + WHERE logicalrelid = 'single_node.single_node_nullkey_c2'::regclass +); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- try creating a single-shard table from a shard relation +SELECT shardid AS round_robin_test_c1_shard_id FROM pg_dist_shard WHERE logicalrelid = 'single_node.single_node_nullkey_c1'::regclass \gset +SELECT create_distributed_table('single_node_nullkey_c1_' || :round_robin_test_c1_shard_id , null, colocate_with=>'none', distribution_type=>null); +ERROR: relation "single_node_nullkey_c1_90630532" is a shard relation +-- create a tenant schema on single node setup +SET citus.enable_schema_based_sharding TO ON; +CREATE SCHEMA tenant_1; +CREATE TABLE tenant_1.tbl_1 (a int); +-- verify that we recorded tenant_1 in pg_dist_schema +SELECT COUNT(*)=1 FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_1'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- verify that tenant_1.tbl_1 is recorded in pg_dist_partition, as a single-shard table +SELECT COUNT(*)=1 FROM pg_dist_partition +WHERE logicalrelid = 'tenant_1.tbl_1'::regclass AND + partmethod = 'n' AND repmodel = 's' AND colocationid IS NOT NULL; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +RESET citus.enable_schema_based_sharding; SET client_min_messages TO WARNING; -DROP TABLE failover_to_local; +DROP TABLE failover_to_local, single_node_nullkey_c1, single_node_nullkey_c2; +DROP SCHEMA tenant_1 CASCADE; RESET client_min_messages; -- so that we don't have to update rest of the test output SET citus.next_shard_id TO 90630500; diff --git a/src/test/regress/expected/single_node_0.out b/src/test/regress/expected/single_node_0.out index a21cdd28a..4749b9b81 100644 --- a/src/test/regress/expected/single_node_0.out +++ b/src/test/regress/expected/single_node_0.out @@ -101,8 +101,85 @@ SELECT pg_reload_conf(); t (1 row) +CREATE TABLE single_node_nullkey_c1(a int, b int); +SELECT create_distributed_table('single_node_nullkey_c1', null, colocate_with=>'none', distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE single_node_nullkey_c2(a int, b int); +SELECT create_distributed_table('single_node_nullkey_c2', null, colocate_with=>'none', distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- created on different colocation groups .. +SELECT +( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'single_node.single_node_nullkey_c1'::regclass +) +!= +( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'single_node.single_node_nullkey_c2'::regclass +); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- .. but both are associated to coordinator +SELECT groupid = 0 FROM pg_dist_placement +WHERE shardid = ( + SELECT shardid FROM pg_dist_shard + WHERE logicalrelid = 'single_node.single_node_nullkey_c1'::regclass +); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT groupid = 0 FROM pg_dist_placement +WHERE shardid = ( + SELECT shardid FROM pg_dist_shard + WHERE logicalrelid = 'single_node.single_node_nullkey_c2'::regclass +); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- try creating a single-shard table from a shard relation +SELECT shardid AS round_robin_test_c1_shard_id FROM pg_dist_shard WHERE logicalrelid = 'single_node.single_node_nullkey_c1'::regclass \gset +SELECT create_distributed_table('single_node_nullkey_c1_' || :round_robin_test_c1_shard_id , null, colocate_with=>'none', distribution_type=>null); +ERROR: relation "single_node_nullkey_c1_90630532" is a shard relation +-- create a tenant schema on single node setup +SET citus.enable_schema_based_sharding TO ON; +CREATE SCHEMA tenant_1; +CREATE TABLE tenant_1.tbl_1 (a int); +-- verify that we recorded tenant_1 in pg_dist_schema +SELECT COUNT(*)=1 FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_1'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- verify that tenant_1.tbl_1 is recorded in pg_dist_partition, as a single-shard table +SELECT COUNT(*)=1 FROM pg_dist_partition +WHERE logicalrelid = 'tenant_1.tbl_1'::regclass AND + partmethod = 'n' AND repmodel = 's' AND colocationid IS NOT NULL; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +RESET citus.enable_schema_based_sharding; SET client_min_messages TO WARNING; -DROP TABLE failover_to_local; +DROP TABLE failover_to_local, single_node_nullkey_c1, single_node_nullkey_c2; +DROP SCHEMA tenant_1 CASCADE; RESET client_min_messages; -- so that we don't have to update rest of the test output SET citus.next_shard_id TO 90630500; diff --git a/src/test/regress/expected/single_node_enterprise.out b/src/test/regress/expected/single_node_enterprise.out index f8d725beb..305a02b8e 100644 --- a/src/test/regress/expected/single_node_enterprise.out +++ b/src/test/regress/expected/single_node_enterprise.out @@ -59,6 +59,8 @@ BEGIN END; $$; SELECT create_distributed_function('notice(text)'); +NOTICE: procedure single_node_ent.notice is already distributed +DETAIL: Citus distributes procedures with CREATE [PROCEDURE|FUNCTION|AGGREGATE] commands create_distributed_function --------------------------------------------------------------------- diff --git a/src/test/regress/expected/single_shard_table_prep.out b/src/test/regress/expected/single_shard_table_prep.out new file mode 100644 index 000000000..7a861b06f --- /dev/null +++ b/src/test/regress/expected/single_shard_table_prep.out @@ -0,0 +1,13 @@ +ALTER FUNCTION create_distributed_table RENAME TO create_distributed_table_internal; +CREATE OR REPLACE FUNCTION pg_catalog.create_distributed_table(table_name regclass, + distribution_column text, + distribution_type citus.distribution_type DEFAULT 'hash', + colocate_with text DEFAULT 'default', + shard_count int DEFAULT NULL) +RETURNS void +LANGUAGE plpgsql +AS $function$ +BEGIN + PERFORM create_distributed_table_internal(table_name, NULL, NULL, colocate_with, NULL); +END; +$function$; diff --git a/src/test/regress/expected/single_shard_table_udfs.out b/src/test/regress/expected/single_shard_table_udfs.out new file mode 100644 index 000000000..d49027b60 --- /dev/null +++ b/src/test/regress/expected/single_shard_table_udfs.out @@ -0,0 +1,1323 @@ +CREATE SCHEMA null_dist_key_udfs; +SET search_path TO null_dist_key_udfs; +SET citus.next_shard_id TO 1820000; +SET citus.shard_count TO 32; +SET citus.shard_replication_factor TO 1; +ALTER SEQUENCE pg_catalog.pg_dist_colocationid_seq RESTART 198000; +SET client_min_messages TO ERROR; +SELECT 1 FROM citus_add_node('localhost', :master_port, groupid=>0); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +RESET client_min_messages; +CREATE FUNCTION get_referencing_relation_id_list(Oid) +RETURNS SETOF Oid +LANGUAGE C STABLE STRICT +AS 'citus', $$get_referencing_relation_id_list$$; +CREATE FUNCTION get_referenced_relation_id_list(Oid) +RETURNS SETOF Oid +LANGUAGE C STABLE STRICT +AS 'citus', $$get_referenced_relation_id_list$$; +CREATE OR REPLACE FUNCTION get_foreign_key_connected_relations(IN table_name regclass) +RETURNS SETOF RECORD +LANGUAGE C STRICT +AS 'citus', $$get_foreign_key_connected_relations$$; +CREATE OR REPLACE FUNCTION citus_get_all_dependencies_for_object(classid oid, objid oid, objsubid int) +RETURNS SETOF RECORD +LANGUAGE C STRICT +AS 'citus', $$citus_get_all_dependencies_for_object$$; +CREATE OR REPLACE FUNCTION citus_get_dependencies_for_object(classid oid, objid oid, objsubid int) +RETURNS SETOF RECORD +LANGUAGE C STRICT +AS 'citus', $$citus_get_dependencies_for_object$$; +CREATE OR REPLACE FUNCTION pg_catalog.is_citus_depended_object(oid,oid) +RETURNS bool +LANGUAGE C +AS 'citus', $$is_citus_depended_object$$; +CREATE FUNCTION shards_colocated(bigint, bigint) +RETURNS bool +AS 'citus' +LANGUAGE C STRICT; +-- test some other udf's with single shard tables +CREATE TABLE null_dist_key_table(a int); +SELECT create_distributed_table('null_dist_key_table', null, colocate_with=>'none', distribution_type=>null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT truncate_local_data_after_distributing_table('null_dist_key_table'); + truncate_local_data_after_distributing_table +--------------------------------------------------------------------- + +(1 row) + +-- should work -- +-- insert some data & create an index for table size udf's +INSERT INTO null_dist_key_table VALUES (1), (2), (3); +CREATE INDEX null_dist_key_idx ON null_dist_key_table(a); +SELECT citus_table_size('null_dist_key_table'); + citus_table_size +--------------------------------------------------------------------- + 8192 +(1 row) + +SELECT citus_total_relation_size('null_dist_key_table'); + citus_total_relation_size +--------------------------------------------------------------------- + 24576 +(1 row) + +SELECT citus_relation_size('null_dist_key_table'); + citus_relation_size +--------------------------------------------------------------------- + 8192 +(1 row) + +SELECT shard_name, shard_size FROM pg_catalog.citus_shard_sizes(), citus_shards +WHERE shardid = shard_id AND shard_name LIKE '%null_dist_key_table%' AND nodeport IN (:worker_1_port, :worker_2_port); + shard_name | shard_size +--------------------------------------------------------------------- + null_dist_key_udfs.null_dist_key_table_1820000 | 24576 +(1 row) + +BEGIN; + SELECT lock_relation_if_exists('null_dist_key_table', 'ACCESS SHARE'); + lock_relation_if_exists +--------------------------------------------------------------------- + t +(1 row) + + SELECT count(*) FROM pg_locks where relation='null_dist_key_table'::regclass; + count +--------------------------------------------------------------------- + 1 +(1 row) + +COMMIT; +SELECT partmethod, repmodel FROM pg_dist_partition WHERE logicalrelid = 'null_dist_key_table'::regclass; + partmethod | repmodel +--------------------------------------------------------------------- + n | s +(1 row) + +SELECT master_get_table_ddl_events('null_dist_key_table'); + master_get_table_ddl_events +--------------------------------------------------------------------- + CREATE TABLE null_dist_key_udfs.null_dist_key_table (a integer) USING heap + ALTER TABLE null_dist_key_udfs.null_dist_key_table OWNER TO postgres + CREATE INDEX null_dist_key_idx ON null_dist_key_udfs.null_dist_key_table USING btree (a) +(3 rows) + +SELECT column_to_column_name(logicalrelid, partkey) +FROM pg_dist_partition WHERE logicalrelid = 'null_dist_key_table'::regclass; + column_to_column_name +--------------------------------------------------------------------- + +(1 row) + +SELECT column_name_to_column('null_dist_key_table', 'a'); + column_name_to_column +--------------------------------------------------------------------- + {VAR :varno 1 :varattno 1 :vartype 23 :vartypmod -1 :varcollid 0 :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location -1} +(1 row) + +SELECT master_update_shard_statistics(shardid) +FROM (SELECT shardid FROM pg_dist_shard WHERE logicalrelid='null_dist_key_table'::regclass) as shardid; + master_update_shard_statistics +--------------------------------------------------------------------- + 8192 +(1 row) + +SELECT truncate_local_data_after_distributing_table('null_dist_key_table'); + truncate_local_data_after_distributing_table +--------------------------------------------------------------------- + +(1 row) + +-- should return a single element array that only includes its own shard id +SELECT shardid=unnest(get_colocated_shard_array(shardid)) +FROM (SELECT shardid FROM pg_dist_shard WHERE logicalrelid='null_dist_key_table'::regclass) as shardid; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +BEGIN; + SELECT master_remove_partition_metadata('null_dist_key_table'::regclass::oid, 'null_dist_key_udfs', 'null_dist_key_table'); + master_remove_partition_metadata +--------------------------------------------------------------------- + +(1 row) + + -- should print 0 + select count(*) from pg_dist_partition where logicalrelid='null_dist_key_table'::regclass; + count +--------------------------------------------------------------------- + 0 +(1 row) + +ROLLBACK; +SELECT master_create_empty_shard('null_dist_key_table'); +ERROR: relation "null_dist_key_table" is a single shard table +DETAIL: We currently don't support creating shards on single shard tables +-- return true +SELECT citus_table_is_visible('null_dist_key_table'::regclass::oid); + citus_table_is_visible +--------------------------------------------------------------------- + t +(1 row) + +-- return false +SELECT relation_is_a_known_shard('null_dist_key_table'); + relation_is_a_known_shard +--------------------------------------------------------------------- + f +(1 row) + +-- return | false | true | +SELECT citus_table_is_visible(tableName::regclass::oid), relation_is_a_known_shard(tableName::regclass) +FROM (SELECT tableName FROM pg_catalog.pg_tables WHERE tablename LIKE 'null_dist_key_table%') as tableName; + citus_table_is_visible | relation_is_a_known_shard +--------------------------------------------------------------------- + t | f +(1 row) + +-- should fail, maybe support in the future +SELECT create_reference_table('null_dist_key_table'); +ERROR: table "null_dist_key_table" is already distributed +SELECT create_distributed_table('null_dist_key_table', 'a'); +ERROR: table "null_dist_key_table" is already distributed +SELECT create_distributed_table_concurrently('null_dist_key_table', 'a'); +ERROR: table "null_dist_key_table" is already distributed +SELECT citus_add_local_table_to_metadata('null_dist_key_table'); +ERROR: table "null_dist_key_table" is already distributed +-- test altering distribution column, fails for single shard tables +SELECT alter_distributed_table('null_dist_key_table', distribution_column := 'a'); +ERROR: relation null_dist_key_table should be a hash distributed table +-- test altering shard count, fails for single shard tables +SELECT alter_distributed_table('null_dist_key_table', shard_count := 6); +ERROR: relation null_dist_key_table should be a hash distributed table +-- test shard splitting udf, fails for single shard tables +SELECT nodeid AS worker_1_node FROM pg_dist_node WHERE nodeport=:worker_1_port \gset +SELECT nodeid AS worker_2_node FROM pg_dist_node WHERE nodeport=:worker_2_port \gset +SELECT citus_split_shard_by_split_points( + 1820000, + ARRAY['-1073741826'], + ARRAY[:worker_1_node, :worker_2_node], + 'block_writes'); +ERROR: Cannot split shard as operation is only supported for hash distributed tables. +SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE '%null_dist_key_table%'; + colocationid +--------------------------------------------------------------------- + 198000 +(1 row) + +-- test alter_table_set_access_method and verify it doesn't change the colocation id +SELECT alter_table_set_access_method('null_dist_key_table', 'columnar'); +NOTICE: creating a new table for null_dist_key_udfs.null_dist_key_table +NOTICE: moving the data of null_dist_key_udfs.null_dist_key_table +NOTICE: dropping the old null_dist_key_udfs.null_dist_key_table +NOTICE: renaming the new table to null_dist_key_udfs.null_dist_key_table + alter_table_set_access_method +--------------------------------------------------------------------- + +(1 row) + +SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE '%null_dist_key_table%'; + colocationid +--------------------------------------------------------------------- + 198000 +(1 row) + +-- undistribute +SELECT undistribute_table('null_dist_key_table'); +NOTICE: creating a new table for null_dist_key_udfs.null_dist_key_table +NOTICE: moving the data of null_dist_key_udfs.null_dist_key_table +NOTICE: dropping the old null_dist_key_udfs.null_dist_key_table +NOTICE: renaming the new table to null_dist_key_udfs.null_dist_key_table + undistribute_table +--------------------------------------------------------------------- + +(1 row) + +-- verify that the metadata is gone +SELECT COUNT(*) = 0 FROM pg_dist_partition WHERE logicalrelid::text LIKE '%null_dist_key_table%'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT COUNT(*) = 0 FROM pg_dist_placement WHERE shardid IN (SELECT shardid FROM pg_dist_shard WHERE logicalrelid::text LIKE '%null_dist_key_table%'); + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT COUNT(*) = 0 FROM pg_dist_shard WHERE logicalrelid::text LIKE '%null_dist_key_table%'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- create 7 single shard tables, 3 of them are colocated, for testing shard moves / rebalance on them +CREATE TABLE single_shard_table_col1_1 (a INT PRIMARY KEY); +CREATE TABLE single_shard_table_col1_2 (a TEXT PRIMARY KEY); +CREATE TABLE single_shard_table_col1_3 (a TIMESTAMP PRIMARY KEY); +CREATE TABLE single_shard_table_col2_1 (a INT PRIMARY KEY); +CREATE TABLE single_shard_table_col3_1 (a INT PRIMARY KEY); +CREATE TABLE single_shard_table_col4_1 (a INT PRIMARY KEY); +CREATE TABLE single_shard_table_col5_1 (a INT PRIMARY KEY); +SELECT create_distributed_table('single_shard_table_col1_1', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('single_shard_table_col1_2', null, colocate_with=>'single_shard_table_col1_1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('single_shard_table_col1_3', null, colocate_with=>'single_shard_table_col1_2'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('single_shard_table_col2_1', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('single_shard_table_col3_1', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('single_shard_table_col4_1', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('single_shard_table_col5_1', null, colocate_with=>'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- initial status +SELECT shardid, nodeport FROM pg_dist_shard_placement WHERE shardid > 1820000 ORDER BY shardid; + shardid | nodeport +--------------------------------------------------------------------- + 1820002 | 57638 + 1820003 | 57638 + 1820004 | 57638 + 1820005 | 57637 + 1820006 | 57638 + 1820007 | 57637 + 1820008 | 57638 +(7 rows) + +-- errors out because streaming replicated +SELECT citus_copy_shard_placement(1820005, 'localhost', :worker_1_port, 'localhost', :worker_2_port); +ERROR: Table 'single_shard_table_col2_1' is streaming replicated. Shards of streaming replicated tables cannot be copied +SELECT master_copy_shard_placement(1820005, 'localhost', :worker_1_port, 'localhost', :worker_2_port); +WARNING: do_repair argument is deprecated +ERROR: Table 'single_shard_table_col2_1' is streaming replicated. Shards of streaming replicated tables cannot be copied +SELECT citus_copy_shard_placement(1820005, :worker_1_node, :worker_2_node); +ERROR: Table 'single_shard_table_col2_1' is streaming replicated. Shards of streaming replicated tables cannot be copied +-- no changes because it's already balanced +SELECT rebalance_table_shards(rebalance_strategy := 'by_shard_count'); + rebalance_table_shards +--------------------------------------------------------------------- + +(1 row) + +-- same placements +SELECT shardid, nodeport FROM pg_dist_shard_placement WHERE shardid > 1820000 ORDER BY shardid; + shardid | nodeport +--------------------------------------------------------------------- + 1820002 | 57638 + 1820003 | 57638 + 1820004 | 57638 + 1820005 | 57637 + 1820006 | 57638 + 1820007 | 57637 + 1820008 | 57638 +(7 rows) + +-- manually move 2 shard from 2 colocation groups to make the cluster unbalanced +SELECT citus_move_shard_placement(1820005, 'localhost', :worker_1_port, 'localhost', :worker_2_port); + citus_move_shard_placement +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_move_shard_placement(1820007, :worker_1_node, :worker_2_node); + citus_move_shard_placement +--------------------------------------------------------------------- + +(1 row) + +-- all placements are located on worker 2 +SELECT shardid, nodeport FROM pg_dist_shard_placement WHERE shardid > 1820000 ORDER BY shardid; + shardid | nodeport +--------------------------------------------------------------------- + 1820002 | 57638 + 1820003 | 57638 + 1820004 | 57638 + 1820005 | 57638 + 1820006 | 57638 + 1820007 | 57638 + 1820008 | 57638 +(7 rows) + +-- move some of them to worker 1 to balance the cluster +SELECT rebalance_table_shards(); +NOTICE: Moving shard xxxxx from localhost:xxxxx to localhost:xxxxx ... +NOTICE: Moving shard xxxxx from localhost:xxxxx to localhost:xxxxx ... + rebalance_table_shards +--------------------------------------------------------------------- + +(1 row) + +-- the final status, balanced +SELECT shardid, nodeport FROM pg_dist_shard_placement WHERE shardid > 1820000 ORDER BY shardid; + shardid | nodeport +--------------------------------------------------------------------- + 1820002 | 57637 + 1820003 | 57637 + 1820004 | 57637 + 1820005 | 57637 + 1820006 | 57638 + 1820007 | 57638 + 1820008 | 57638 +(7 rows) + +-- verify we didn't break any colocations +SELECT logicalrelid, colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE '%single_shard_table_col%' ORDER BY colocationid, logicalrelid; + logicalrelid | colocationid +--------------------------------------------------------------------- + single_shard_table_col1_1 | 198001 + single_shard_table_col1_2 | 198001 + single_shard_table_col1_3 | 198001 + single_shard_table_col2_1 | 198002 + single_shard_table_col3_1 | 198003 + single_shard_table_col4_1 | 198004 + single_shard_table_col5_1 | 198005 +(7 rows) + +-- drop preexisting tables +-- we can remove the drop commands once the issue is fixed: https://github.com/citusdata/citus/issues/6948 +SET client_min_messages TO ERROR; +DROP TABLE IF EXISTS public.lineitem, public.orders, public.customer_append, public.part_append, public.supplier_single_shard, + public.events, public.users, public.lineitem_hash_part, public.lineitem_subquery, public.orders_hash_part, + public.orders_subquery, public.unlogged_table CASCADE; +DROP SCHEMA IF EXISTS with_basics, subquery_and_ctes CASCADE; +DROP TABLE IF EXISTS public.users_table, public.events_table, public.agg_results, public.agg_results_second, public.agg_results_third, public.agg_results_fourth, public.agg_results_window CASCADE; +-- drain node +SELECT citus_drain_node('localhost', :worker_2_port, 'block_writes'); + citus_drain_node +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_set_node_property('localhost', :worker_2_port, 'shouldhaveshards', true); + citus_set_node_property +--------------------------------------------------------------------- + +(1 row) + +RESET client_min_messages; +-- see the plan for moving 4 shards, 3 of them are in the same colocation group +SELECT * FROM get_rebalance_table_shards_plan(); + table_name | shardid | shard_size | sourcename | sourceport | targetname | targetport +--------------------------------------------------------------------- + single_shard_table_col1_1 | 1820002 | 0 | localhost | 57637 | localhost | 57638 + single_shard_table_col1_2 | 1820003 | 0 | localhost | 57637 | localhost | 57638 + single_shard_table_col1_3 | 1820004 | 0 | localhost | 57637 | localhost | 57638 + single_shard_table_col2_1 | 1820005 | 0 | localhost | 57637 | localhost | 57638 +(4 rows) + +-- move some of them to worker 2 to balance the cluster +SELECT 1 FROM citus_rebalance_start(); +NOTICE: Scheduled 2 moves as job xxx +DETAIL: Rebalance scheduled as background job +HINT: To monitor progress, run: SELECT * FROM citus_rebalance_status(); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +-- stop it +SELECT * FROM citus_rebalance_stop(); + citus_rebalance_stop +--------------------------------------------------------------------- + +(1 row) + +-- show rebalance status, see the cancelled job for two moves +SELECT state, details FROM citus_rebalance_status(); + state | details +--------------------------------------------------------------------- + cancelled | {"tasks": [], "task_state_counts": {"cancelled": 2}} +(1 row) + +-- start again +SELECT 1 FROM citus_rebalance_start(); +NOTICE: Scheduled 2 moves as job xxx +DETAIL: Rebalance scheduled as background job +HINT: To monitor progress, run: SELECT * FROM citus_rebalance_status(); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +-- show rebalance status, scheduled a job for two moves +SELECT state, details FROM citus_rebalance_status(); + state | details +--------------------------------------------------------------------- + scheduled | {"tasks": [], "task_state_counts": {"runnable": 2}} +(1 row) + +-- wait for rebalance to be completed +SELECT * FROM citus_rebalance_wait(); + citus_rebalance_wait +--------------------------------------------------------------------- + +(1 row) + +-- the final status, balanced +SELECT shardid, nodeport FROM pg_dist_shard_placement WHERE shardid > 1820000 ORDER BY shardid; + shardid | nodeport +--------------------------------------------------------------------- + 1820002 | 57638 + 1820003 | 57638 + 1820004 | 57638 + 1820005 | 57638 + 1820006 | 57637 + 1820007 | 57637 + 1820008 | 57637 +(7 rows) + +-- test update_distributed_table_colocation +CREATE TABLE update_col_1 (a INT); +CREATE TABLE update_col_2 (a INT); +CREATE TABLE update_col_3 (a INT); +-- create colocated single shard distributed tables, so the shards will be +-- in the same worker node +SELECT create_distributed_table ('update_col_1', null, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table ('update_col_2', null, colocate_with:='update_col_1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- now create a third single shard distributed table that is not colocated, +-- with the new colocation id the new table will be in the other worker node +SELECT create_distributed_table ('update_col_3', null, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- make sure nodes are correct and test shards_colocated UDF +SELECT c1.nodeport = c2.nodeport AS same_node, shards_colocated(c1.shardid, c2.shardid) +FROM citus_shards c1, citus_shards c2, pg_dist_node p1, pg_dist_node p2 +WHERE c1.table_name::text = 'update_col_1' AND c2.table_name::text = 'update_col_2' AND + p1.nodeport = c1.nodeport AND p2.nodeport = c2.nodeport AND + p1.noderole = 'primary' AND p2.noderole = 'primary'; + same_node | shards_colocated +--------------------------------------------------------------------- + t | t +(1 row) + +SELECT c1.nodeport = c2.nodeport AS same_node, shards_colocated(c1.shardid, c2.shardid) +FROM citus_shards c1, citus_shards c2, pg_dist_node p1, pg_dist_node p2 +WHERE c1.table_name::text = 'update_col_1' AND c2.table_name::text = 'update_col_3' AND + p1.nodeport = c1.nodeport AND p2.nodeport = c2.nodeport AND + p1.noderole = 'primary' AND p2.noderole = 'primary'; + same_node | shards_colocated +--------------------------------------------------------------------- + f | f +(1 row) + +-- and the update_col_1 and update_col_2 are colocated +SELECT c1.colocation_id = c2.colocation_id AS colocated +FROM public.citus_tables c1, public.citus_tables c2 +WHERE c1.table_name::text = 'update_col_1' AND c2.table_name::text = 'update_col_2'; + colocated +--------------------------------------------------------------------- + t +(1 row) + +-- break the colocation +SELECT update_distributed_table_colocation('update_col_2', colocate_with:='none'); + update_distributed_table_colocation +--------------------------------------------------------------------- + +(1 row) + +SELECT c1.colocation_id = c2.colocation_id AS colocated +FROM public.citus_tables c1, public.citus_tables c2 +WHERE c1.table_name::text = 'update_col_1' AND c2.table_name::text = 'update_col_2'; + colocated +--------------------------------------------------------------------- + f +(1 row) + +-- test shards_colocated UDF with shards in same node but different colocation groups +SELECT shards_colocated(c1.shardid, c2.shardid) +FROM citus_shards c1, citus_shards c2, pg_dist_node p1, pg_dist_node p2 +WHERE c1.table_name::text = 'update_col_1' AND c2.table_name::text = 'update_col_2' AND + p1.nodeport = c1.nodeport AND p2.nodeport = c2.nodeport AND + p1.noderole = 'primary' AND p2.noderole = 'primary'; + shards_colocated +--------------------------------------------------------------------- + f +(1 row) + +-- re-colocate, the shards were already in the same node +SELECT update_distributed_table_colocation('update_col_2', colocate_with:='update_col_1'); + update_distributed_table_colocation +--------------------------------------------------------------------- + +(1 row) + +SELECT c1.colocation_id = c2.colocation_id AS colocated +FROM public.citus_tables c1, public.citus_tables c2 +WHERE c1.table_name::text = 'update_col_1' AND c2.table_name::text = 'update_col_2'; + colocated +--------------------------------------------------------------------- + t +(1 row) + +-- update_col_1 and update_col_3 are not colocated, because they are not in the some node +SELECT c1.colocation_id = c2.colocation_id AS colocated +FROM public.citus_tables c1, public.citus_tables c2 +WHERE c1.table_name::text = 'update_col_1' AND c2.table_name::text = 'update_col_3'; + colocated +--------------------------------------------------------------------- + f +(1 row) + +-- they should not be able to be colocated since the shards are in different nodes +SELECT update_distributed_table_colocation('update_col_3', colocate_with:='update_col_1'); +ERROR: cannot colocate tables update_col_1 and update_col_3 +DETAIL: Shard xxxxx of update_col_1 and shard xxxxx of update_col_3 are not colocated. +SELECT c1.colocation_id = c2.colocation_id AS colocated +FROM public.citus_tables c1, public.citus_tables c2 +WHERE c1.table_name::text = 'update_col_1' AND c2.table_name::text = 'update_col_3'; + colocated +--------------------------------------------------------------------- + f +(1 row) + +-- hash distributed and single shard distributed tables cannot be colocated +CREATE TABLE update_col_4 (a INT); +SELECT create_distributed_table ('update_col_4', 'a', colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT update_distributed_table_colocation('update_col_1', colocate_with:='update_col_4'); +ERROR: cannot colocate tables update_col_4 and update_col_1 +DETAIL: Distribution column types don't match for update_col_4 and update_col_1. +SELECT update_distributed_table_colocation('update_col_4', colocate_with:='update_col_1'); +ERROR: cannot colocate tables update_col_1 and update_col_4 +DETAIL: Distribution column types don't match for update_col_1 and update_col_4. +-- test columnar UDFs +CREATE TABLE columnar_tbl (a INT) USING COLUMNAR; +SELECT create_distributed_table('columnar_tbl', NULL, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT * FROM columnar.options WHERE relation = 'columnar_tbl'::regclass; + relation | chunk_group_row_limit | stripe_row_limit | compression | compression_level +--------------------------------------------------------------------- + columnar_tbl | 10000 | 150000 | zstd | 3 +(1 row) + +SELECT alter_columnar_table_set('columnar_tbl', compression_level => 2); + alter_columnar_table_set +--------------------------------------------------------------------- + +(1 row) + +SELECT * FROM columnar.options WHERE relation = 'columnar_tbl'::regclass; + relation | chunk_group_row_limit | stripe_row_limit | compression | compression_level +--------------------------------------------------------------------- + columnar_tbl | 10000 | 150000 | zstd | 2 +(1 row) + +SELECT alter_columnar_table_reset('columnar_tbl', compression_level => true); + alter_columnar_table_reset +--------------------------------------------------------------------- + +(1 row) + +SELECT * FROM columnar.options WHERE relation = 'columnar_tbl'::regclass; + relation | chunk_group_row_limit | stripe_row_limit | compression | compression_level +--------------------------------------------------------------------- + columnar_tbl | 10000 | 150000 | zstd | 3 +(1 row) + +SELECT columnar_internal.upgrade_columnar_storage(c.oid) +FROM pg_class c, pg_am a +WHERE c.relam = a.oid AND amname = 'columnar' AND relname = 'columnar_tbl'; + upgrade_columnar_storage +--------------------------------------------------------------------- + +(1 row) + +SELECT columnar_internal.downgrade_columnar_storage(c.oid) +FROM pg_class c, pg_am a +WHERE c.relam = a.oid AND amname = 'columnar' AND relname = 'columnar_tbl'; + downgrade_columnar_storage +--------------------------------------------------------------------- + +(1 row) + +CREATE OR REPLACE FUNCTION columnar_storage_info( + rel regclass, + version_major OUT int4, + version_minor OUT int4, + storage_id OUT int8, + reserved_stripe_id OUT int8, + reserved_row_number OUT int8, + reserved_offset OUT int8) + STRICT + LANGUAGE c AS 'citus', $$columnar_storage_info$$; +SELECT version_major, version_minor, reserved_stripe_id, reserved_row_number, reserved_offset FROM columnar_storage_info('columnar_tbl'); + version_major | version_minor | reserved_stripe_id | reserved_row_number | reserved_offset +--------------------------------------------------------------------- + 2 | 0 | 1 | 1 | 16336 +(1 row) + +SELECT columnar.get_storage_id(oid) = storage_id FROM pg_class, columnar_storage_info('columnar_tbl') WHERE relname = 'columnar_tbl'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- test time series functions +CREATE TABLE part_tbl (a DATE) PARTITION BY RANGE (a); +CREATE TABLE part_tbl_1 PARTITION OF part_tbl FOR VALUES FROM ('2000-01-01') TO ('2010-01-01'); +CREATE TABLE part_tbl_2 PARTITION OF part_tbl FOR VALUES FROM ('2020-01-01') TO ('2030-01-01'); +SELECT create_distributed_table('part_tbl', NULL, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT * FROM time_partitions WHERE parent_table::text = 'part_tbl'; + parent_table | partition_column | partition | from_value | to_value | access_method +--------------------------------------------------------------------- + part_tbl | a | part_tbl_1 | 01-01-2000 | 01-01-2010 | heap + part_tbl | a | part_tbl_2 | 01-01-2020 | 01-01-2030 | heap +(2 rows) + +SELECT time_partition_range('part_tbl_2'); + time_partition_range +--------------------------------------------------------------------- + (01-01-2020,01-01-2030) +(1 row) + +SELECT get_missing_time_partition_ranges('part_tbl', INTERVAL '10 years', '2050-01-01', '2000-01-01'); + get_missing_time_partition_ranges +--------------------------------------------------------------------- + (part_tbl_p2010,01-01-2010,01-01-2020) + (part_tbl_p2030,01-01-2030,01-01-2040) + (part_tbl_p2040,01-01-2040,01-01-2050) +(3 rows) + +SELECT create_time_partitions('part_tbl', INTERVAL '10 years', '2050-01-01', '2000-01-01'); + create_time_partitions +--------------------------------------------------------------------- + t +(1 row) + +CALL drop_old_time_partitions('part_tbl', '2030-01-01'); +NOTICE: dropping part_tbl_1 with start time 01-01-2000 and end time 01-01-2010 +CONTEXT: PL/pgSQL function drop_old_time_partitions(regclass,timestamp with time zone) line XX at RAISE +NOTICE: dropping part_tbl_p2010 with start time 01-01-2010 and end time 01-01-2020 +CONTEXT: PL/pgSQL function drop_old_time_partitions(regclass,timestamp with time zone) line XX at RAISE +NOTICE: dropping part_tbl_2 with start time 01-01-2020 and end time 01-01-2030 +CONTEXT: PL/pgSQL function drop_old_time_partitions(regclass,timestamp with time zone) line XX at RAISE +SELECT * FROM time_partitions WHERE parent_table::text = 'part_tbl'; + parent_table | partition_column | partition | from_value | to_value | access_method +--------------------------------------------------------------------- + part_tbl | a | part_tbl_p2030 | 01-01-2030 | 01-01-2040 | heap + part_tbl | a | part_tbl_p2040 | 01-01-2040 | 01-01-2050 | heap +(2 rows) + +-- test locking shards +CREATE TABLE lock_tbl_1 (a INT); +SELECT create_distributed_table('lock_tbl_1', NULL, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE lock_tbl_2 (a INT); +SELECT create_distributed_table('lock_tbl_2', NULL, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +BEGIN; +SELECT lock_shard_metadata(3, array_agg(distinct(shardid))) +FROM citus_shards WHERE table_name::text = 'lock_tbl_1'; + lock_shard_metadata +--------------------------------------------------------------------- + +(1 row) + +SELECT lock_shard_metadata(5, array_agg(distinct(shardid))) +FROM citus_shards WHERE table_name::text LIKE 'lock\_tbl\__'; + lock_shard_metadata +--------------------------------------------------------------------- + +(1 row) + +SELECT table_name, classid, mode, granted +FROM pg_locks, public.citus_tables +WHERE + locktype = 'advisory' AND + table_name::text LIKE 'lock\_tbl\__' AND + objid = colocation_id + ORDER BY 1, 3; + table_name | classid | mode | granted +--------------------------------------------------------------------- + lock_tbl_1 | 0 | RowExclusiveLock | t + lock_tbl_1 | 0 | ShareLock | t + lock_tbl_2 | 0 | ShareLock | t +(3 rows) + +END; +BEGIN; +SELECT lock_shard_resources(3, array_agg(distinct(shardid))) +FROM citus_shards WHERE table_name::text = 'lock_tbl_1'; + lock_shard_resources +--------------------------------------------------------------------- + +(1 row) + +SELECT lock_shard_resources(5, array_agg(distinct(shardid))) +FROM citus_shards WHERE table_name::text LIKE 'lock\_tbl\__'; + lock_shard_resources +--------------------------------------------------------------------- + +(1 row) + +SELECT locktype, table_name, mode, granted +FROM pg_locks, citus_shards, pg_dist_node +WHERE + objid = shardid AND + table_name::text LIKE 'lock\_tbl\__' AND + citus_shards.nodeport = pg_dist_node.nodeport AND + noderole = 'primary' + ORDER BY 2, 3; + locktype | table_name | mode | granted +--------------------------------------------------------------------- + advisory | lock_tbl_1 | RowExclusiveLock | t + advisory | lock_tbl_1 | ShareLock | t + advisory | lock_tbl_2 | ShareLock | t +(3 rows) + +END; +-- test foreign key UDFs +CREATE TABLE fkey_s1 (a INT UNIQUE); +CREATE TABLE fkey_r (a INT UNIQUE); +CREATE TABLE fkey_s2 (x INT, y INT); +CREATE TABLE fkey_s3 (x INT, y INT); +SELECT create_distributed_table('fkey_s1', NULL, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_reference_table('fkey_r'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('fkey_s2', NULL, colocate_with:='fkey_s1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('fkey_s3', NULL, colocate_with:='fkey_s1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE fkey_s2 ADD CONSTRAINT f1 FOREIGN KEY (x) REFERENCES fkey_s1 (a); +ALTER TABLE fkey_s2 ADD CONSTRAINT f2 FOREIGN KEY (y) REFERENCES fkey_r (a); +ALTER TABLE fkey_s3 ADD CONSTRAINT f3 FOREIGN KEY (x) REFERENCES fkey_s1 (a); +ALTER TABLE fkey_s3 ADD CONSTRAINT f4 FOREIGN KEY (y) REFERENCES fkey_r (a); +SELECT get_referencing_relation_id_list::regclass::text FROM get_referencing_relation_id_list('fkey_s1'::regclass) ORDER BY 1; + get_referencing_relation_id_list +--------------------------------------------------------------------- + fkey_s2 + fkey_s3 +(2 rows) + +SELECT get_referenced_relation_id_list::regclass::text FROM get_referenced_relation_id_list('fkey_s2'::regclass) ORDER BY 1; + get_referenced_relation_id_list +--------------------------------------------------------------------- + fkey_r + fkey_s1 +(2 rows) + +SELECT oid::regclass::text FROM get_foreign_key_connected_relations('fkey_s1'::regclass) AS f(oid oid) ORDER BY 1; + oid +--------------------------------------------------------------------- + fkey_r + fkey_s1 + fkey_s2 + fkey_s3 +(4 rows) + +--test dependency functions +CREATE TYPE dep_type AS (a INT); +CREATE TABLE dep_tbl(a INT, b dep_type); +SELECT create_distributed_table('dep_tbl', NULL, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE VIEW dep_view AS SELECT * FROM dep_tbl; +-- find all the dependencies of table dep_tbl +SELECT + pg_identify_object(t.classid, t.objid, t.objsubid) +FROM + (SELECT * FROM pg_get_object_address('table', '{dep_tbl}', '{}')) as addr +JOIN LATERAL + citus_get_all_dependencies_for_object(addr.classid, addr.objid, addr.objsubid) as t(classid oid, objid oid, objsubid int) +ON TRUE + ORDER BY 1; + pg_identify_object +--------------------------------------------------------------------- + ("composite type",null_dist_key_udfs,dep_type,null_dist_key_udfs.dep_type) + (schema,,null_dist_key_udfs,null_dist_key_udfs) + (type,null_dist_key_udfs,dep_type,null_dist_key_udfs.dep_type) +(3 rows) + +-- find all the dependencies of view dep_view +SELECT + pg_identify_object(t.classid, t.objid, t.objsubid) +FROM + (SELECT * FROM pg_get_object_address('view', '{dep_view}', '{}')) as addr +JOIN LATERAL + citus_get_all_dependencies_for_object(addr.classid, addr.objid, addr.objsubid) as t(classid oid, objid oid, objsubid int) +ON TRUE + ORDER BY 1; + pg_identify_object +--------------------------------------------------------------------- + ("composite type",null_dist_key_udfs,dep_type,null_dist_key_udfs.dep_type) + (schema,,null_dist_key_udfs,null_dist_key_udfs) + (table,null_dist_key_udfs,dep_tbl,null_dist_key_udfs.dep_tbl) + (type,null_dist_key_udfs,dep_type,null_dist_key_udfs.dep_type) +(4 rows) + +-- find non-distributed dependencies of table dep_tbl +SELECT + pg_identify_object(t.classid, t.objid, t.objsubid) +FROM + (SELECT * FROM pg_get_object_address('table', '{dep_tbl}', '{}')) as addr +JOIN LATERAL + citus_get_dependencies_for_object(addr.classid, addr.objid, addr.objsubid) as t(classid oid, objid oid, objsubid int) +ON TRUE + ORDER BY 1; + pg_identify_object +--------------------------------------------------------------------- +(0 rows) + +SET citus.hide_citus_dependent_objects TO true; +CREATE TABLE citus_dep_tbl (a noderole); +SELECT create_distributed_table('citus_dep_tbl', NULL, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT is_citus_depended_object('pg_class'::regclass, 'citus_dep_tbl'::regclass); + is_citus_depended_object +--------------------------------------------------------------------- + t +(1 row) + +RESET citus.hide_citus_dependent_objects; +-- test replicate_reference_tables +SET client_min_messages TO WARNING; +DROP SCHEMA null_dist_key_udfs CASCADE; +RESET client_min_messages; +CREATE SCHEMA null_dist_key_udfs; +SET search_path TO null_dist_key_udfs; +SELECT citus_remove_node('localhost', :worker_2_port); + citus_remove_node +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE rep_ref (a INT UNIQUE); +SELECT create_reference_table('rep_ref'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE rep_sing (a INT); +SELECT create_distributed_table('rep_sing', NULL, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE rep_sing ADD CONSTRAINT rep_fkey FOREIGN KEY (a) REFERENCES rep_ref(a); +SELECT 1 FROM citus_add_node('localhost', :worker_2_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT count(*) FROM citus_shards WHERE table_name = 'rep_ref'::regclass AND nodeport = :worker_2_port; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT replicate_reference_tables('block_writes'); + replicate_reference_tables +--------------------------------------------------------------------- + +(1 row) + +SELECT count(*) FROM citus_shards WHERE table_name = 'rep_ref'::regclass AND nodeport = :worker_2_port; + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- test fix_partition_shard_index_names +SET citus.next_shard_id TO 3820000; +CREATE TABLE part_tbl_sing (dist_col int, another_col int, partition_col timestamp) PARTITION BY RANGE (partition_col); +SELECT create_distributed_table('part_tbl_sing', NULL, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- create a partition with a long name and another with a short name +CREATE TABLE partition_table_with_very_long_name PARTITION OF part_tbl_sing FOR VALUES FROM ('2018-01-01') TO ('2019-01-01'); +CREATE TABLE p PARTITION OF part_tbl_sing FOR VALUES FROM ('2019-01-01') TO ('2020-01-01'); +-- create an index on parent table +-- we will see that it doesn't matter whether we name the index on parent or not +-- indexes auto-generated on partitions will not use this name +-- SELECT fix_partition_shard_index_names('dist_partitioned_table') will be executed +-- automatically at the end of the CREATE INDEX command +CREATE INDEX short ON part_tbl_sing USING btree (another_col, partition_col); +SELECT tablename, indexname FROM pg_indexes WHERE schemaname = 'null_dist_key_udfs' AND tablename SIMILAR TO 'p%' ORDER BY 1, 2; + tablename | indexname +--------------------------------------------------------------------- + p | p_another_col_partition_col_idx + part_tbl_sing | short + partition_table_with_very_long_name | partition_table_with_very_long_na_another_col_partition_col_idx +(3 rows) + +SELECT nodeport AS part_tbl_sing_port +FROM citus_shards +WHERE table_name = 'part_tbl_sing'::regclass AND + nodeport IN (:worker_1_port, :worker_2_port) \gset +\c - - - :part_tbl_sing_port +-- the names are generated correctly +-- shard id has been appended to all index names which didn't end in shard id +-- this goes in line with Citus's way of naming indexes of shards: always append shardid to the end +SELECT tablename, indexname FROM pg_indexes WHERE schemaname = 'null_dist_key_udfs' AND tablename SIMILAR TO 'p%\_\d*' ORDER BY 1, 2; + tablename | indexname +--------------------------------------------------------------------- + p_3820002 | p_another_col_partition_col_idx_3820002 + part_tbl_sing_3820000 | short_3820000 + partition_table_with_very_long_name_3820001 | partition_table_with_very_long_na_another_col__dd884a3b_3820001 +(3 rows) + +\c - - - :master_port +SET search_path TO null_dist_key_udfs; +--test isolate_tenant_to_new_shard +CREATE TABLE iso_tbl (a INT); +SET citus.shard_replication_factor TO 1; +SELECT create_distributed_table('iso_tbl', NULL, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT isolate_tenant_to_new_shard('iso_tbl', 5); +ERROR: cannot isolate tenant because tenant isolation is only support for hash distributed tables +-- test replicate_table_shards +CREATE TABLE rep_tbl (a INT); +SELECT create_distributed_table('rep_tbl', NULL, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT replicate_table_shards('rep_tbl'); +ERROR: cannot replicate single shard tables' shards +-- test debug_equality_expression +CREATE FUNCTION debug_equality_expression(regclass) +RETURNS cstring +AS 'citus' +LANGUAGE C STRICT; +CREATE TABLE debug_tbl (a INT); +SELECT create_distributed_table ('debug_tbl', NULL, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT debug_equality_expression('debug_tbl'::regclass); +ERROR: table needs to be hash distributed +-- test partition_column_id +CREATE FUNCTION partition_column_id(regclass) +RETURNS smallint +AS 'citus' +LANGUAGE C STRICT; +CREATE TABLE partcol_tbl (a INT); +SELECT create_distributed_table ('partcol_tbl', NULL, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT partition_column_id('partcol_tbl'::regclass); +ERROR: table needs to be hash distributed +-- test citus_shard_cost_by_disk_size +CREATE TABLE size_tbl_dist (a INT, b TEXT); +SELECT create_distributed_table('size_tbl_dist', 'a', shard_count:=4, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE size_tbl_single (a INT, b TEXT); +SELECT create_distributed_table('size_tbl_single', NULL, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO size_tbl_dist SELECT 1, '1234567890' FROM generate_series(1, 10000); +INSERT INTO size_tbl_single SELECT 1, '1234567890' FROM generate_series(1, 10000); +SELECT citus_shard_cost_by_disk_size(c1.shardid) = citus_shard_cost_by_disk_size(c2.shardid) AS equal_cost +FROM citus_shards c1, citus_shards c2 +WHERE c1.table_name::TEXT = 'size_tbl_dist' AND c2.table_name::TEXT = 'size_tbl_single' +ORDER BY c1.shard_size DESC +LIMIT 1; + equal_cost +--------------------------------------------------------------------- + t +(1 row) + +-- test update statistics UDFs +CREATE TABLE update_tbl_stat (a INT, b TEXT); +SELECT create_distributed_table('update_tbl_stat', NULL, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT shardid AS update_tbl_stat_shard +FROM citus_shards +WHERE table_name::TEXT = 'update_tbl_stat' +LIMIT 1 \gset +SELECT shardlength > 0 FROM pg_dist_shard_placement WHERE shardid = :update_tbl_stat_shard LIMIT 1; + ?column? +--------------------------------------------------------------------- + f +(1 row) + +INSERT INTO update_tbl_stat SELECT 1, '1234567890' FROM generate_series(1, 10000); +SELECT shardlength > 0 FROM pg_dist_shard_placement WHERE shardid = :update_tbl_stat_shard LIMIT 1; + ?column? +--------------------------------------------------------------------- + f +(1 row) + +SELECT citus_update_table_statistics('update_tbl_stat'); + citus_update_table_statistics +--------------------------------------------------------------------- + +(1 row) + +SELECT shardlength > 0 FROM pg_dist_shard_placement WHERE shardid = :update_tbl_stat_shard LIMIT 1; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +CREATE TABLE update_shard_stat (a INT, b TEXT); +SELECT create_distributed_table('update_shard_stat', NULL, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT shardid AS update_shard_stat_shard +FROM citus_shards +WHERE table_name::TEXT = 'update_shard_stat' +LIMIT 1 \gset +SELECT shardlength > 0 FROM pg_dist_shard_placement WHERE shardid = :update_shard_stat_shard LIMIT 1; + ?column? +--------------------------------------------------------------------- + f +(1 row) + +INSERT INTO update_shard_stat SELECT 1, '1234567890' FROM generate_series(1, 10000); +SELECT shardlength > 0 FROM pg_dist_shard_placement WHERE shardid = :update_shard_stat_shard LIMIT 1; + ?column? +--------------------------------------------------------------------- + f +(1 row) + +SELECT 1 FROM citus_update_shard_statistics(:update_shard_stat_shard); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT shardlength > 0 FROM pg_dist_shard_placement WHERE shardid = :update_shard_stat_shard LIMIT 1; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- test citus clock +SET citus.enable_cluster_clock TO ON; +CREATE TABLE clock_single(a INT); +SELECT create_distributed_table('clock_single', NULL, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT citus_get_node_clock() AS nc1 \gset +SELECT citus_get_node_clock() AS nc2 \gset +SELECT citus_get_node_clock() AS nc3 \gset +SELECT citus_is_clock_after(:'nc2', :'nc1'); + citus_is_clock_after +--------------------------------------------------------------------- + t +(1 row) + +SELECT citus_is_clock_after(:'nc3', :'nc2'); + citus_is_clock_after +--------------------------------------------------------------------- + t +(1 row) + +BEGIN; +SELECT citus_get_node_clock() AS nc4 \gset +COPY clock_single FROM STDIN; +SELECT citus_get_node_clock() AS nc5 \gset +END; +SELECT citus_is_clock_after(:'nc4', :'nc3'); + citus_is_clock_after +--------------------------------------------------------------------- + t +(1 row) + +SELECT citus_is_clock_after(:'nc5', :'nc4'); + citus_is_clock_after +--------------------------------------------------------------------- + t +(1 row) + +BEGIN; +SELECT citus_get_transaction_clock(); + citus_get_transaction_clock +--------------------------------------------------------------------- + (xxxxxxxxxxxxx,x) +(1 row) + +END; +-- Transaction with single shard table access +SELECT nodeport AS clock_shard_nodeport FROM citus_shards +WHERE table_name::text = 'clock_single' AND nodeport IN (:worker_1_port, :worker_2_port) \gset +BEGIN; +COPY clock_single FROM STDIN; +SELECT get_current_transaction_id() \gset tid +SET client_min_messages TO DEBUG1; +-- Capture the transaction timestamp +SELECT citus_get_transaction_clock() as txnclock \gset +DEBUG: node xxxx transaction clock xxxxxx +DEBUG: node xxxx transaction clock xxxxxx +DEBUG: final global transaction clock xxxxxx +COMMIT; +-- Check to see if the clock is persisted in the sequence. +SELECT result as logseq from run_command_on_workers($$SELECT last_value FROM pg_dist_clock_logical_seq$$) +WHERE nodeport = :clock_shard_nodeport \gset +SELECT cluster_clock_logical(:'txnclock') as txnlog \gset +SELECT :logseq = :txnlog; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +BEGIN; +COPY clock_single FROM STDIN; +SELECT get_current_transaction_id() \gset tid +SET client_min_messages TO DEBUG1; +-- Capture the transaction timestamp +SELECT citus_get_transaction_clock() as txnclock \gset +DEBUG: node xxxx transaction clock xxxxxx +DEBUG: node xxxx transaction clock xxxxxx +DEBUG: final global transaction clock xxxxxx +ROLLBACK; +SELECT result as logseq from run_command_on_workers($$SELECT last_value FROM pg_dist_clock_logical_seq$$) +WHERE nodeport = :clock_shard_nodeport \gset +SELECT cluster_clock_logical(:'txnclock') as txnlog \gset +SELECT :logseq = :txnlog; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- test table with space in its name in citus_shards +CREATE TABLE "t b l" (a INT); +SELECT create_distributed_table('"t b l"', NULL, colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT table_name, shard_size FROM citus_shards +WHERE table_name = '"t b l"'::regclass AND nodeport IN (:worker_1_port, :worker_2_port); + table_name | shard_size +--------------------------------------------------------------------- + "t b l" | 0 +(1 row) + +SET client_min_messages TO WARNING; +DROP SCHEMA null_dist_key_udfs CASCADE; diff --git a/src/test/regress/expected/sql_procedure.out b/src/test/regress/expected/sql_procedure.out index 9e3aae5c7..63802b354 100644 --- a/src/test/regress/expected/sql_procedure.out +++ b/src/test/regress/expected/sql_procedure.out @@ -37,7 +37,7 @@ CREATE PROCEDURE test_procedure_commit(tt_id int, tt_org_id int) LANGUAGE SQL AS COMMIT; $$; CALL test_procedure_commit(2,5); -ERROR: COMMIT is not allowed in a SQL function +ERROR: COMMIT is not allowed in an SQL function CONTEXT: SQL function "test_procedure_commit" during startup SELECT * FROM test_table ORDER BY 1, 2; id | org_id @@ -52,7 +52,7 @@ CREATE PROCEDURE test_procedure_rollback(tt_id int, tt_org_id int) LANGUAGE SQL COMMIT; $$; CALL test_procedure_rollback(2,15); -ERROR: ROLLBACK is not allowed in a SQL function +ERROR: ROLLBACK is not allowed in an SQL function CONTEXT: SQL function "test_procedure_rollback" during startup SELECT * FROM test_table ORDER BY 1, 2; id | org_id diff --git a/src/test/regress/expected/stat_statements.out b/src/test/regress/expected/stat_statements.out index 537bb4e9b..a3e2f673f 100644 --- a/src/test/regress/expected/stat_statements.out +++ b/src/test/regress/expected/stat_statements.out @@ -2,12 +2,7 @@ -- stat_statements -- -- tests citus_stat_statements functionality -SHOW server_version \gset -SELECT substring(:'server_version', '\d+')::int >= 14 AS server_version_ge_14 -\gset -\if :server_version_ge_14 SET compute_query_id = 'on'; -\endif -- check if pg_stat_statements is available SELECT name FROM pg_available_extensions WHERE name = 'pg_stat_statements'; name @@ -72,11 +67,7 @@ select query, calls from citus_stat_statements(); insert into test values($1) | 1 (1 row) -\if :server_version_ge_14 SET compute_query_id = 'off'; -\else -set citus.stat_statements_track = 'none'; -\endif -- for pg >= 14, since compute_query_id is off, this insert -- shouldn't be tracked -- for pg < 14, we disable it explicitly so that we don't need @@ -88,11 +79,7 @@ select query, calls from citus_stat_statements(); insert into test values($1) | 1 (1 row) -\if :server_version_ge_14 SET compute_query_id = 'on'; -\else -RESET citus.stat_statements_track; -\endif SELECT citus_stat_statements_reset(); citus_stat_statements_reset --------------------------------------------------------------------- @@ -646,6 +633,4 @@ CONTEXT: PL/pgSQL function citus_stat_statements() line XX at RAISE -- drop created tables DROP TABLE stat_test_text, stat_test_bigint, stat_test_bigint_other, stat_test_reference; DROP FUNCTION normalize_query_string(text); -\if :server_version_ge_14 SET compute_query_id = 'off'; -\endif diff --git a/src/test/regress/expected/subquery_and_cte.out b/src/test/regress/expected/subquery_and_cte.out index f3f12b975..c15e9b9d7 100644 --- a/src/test/regress/expected/subquery_and_cte.out +++ b/src/test/regress/expected/subquery_and_cte.out @@ -126,7 +126,6 @@ WITH cte1 AS MATERIALIZED (SELECT id, value FROM func()) UPDATE dist_table dt SET value = cte1.value FROM cte1 WHERE dt.id = 1; DEBUG: generating subplan XXX_1 for CTE cte1: SELECT id, value FROM subquery_and_ctes.func() func(id, value) -DEBUG: function does not have co-located tables DEBUG: Plan XXX query after replacing subqueries and CTEs: UPDATE subquery_and_ctes.dist_table dt SET value = cte1.value FROM (SELECT intermediate_result.id, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id integer, value integer)) cte1 WHERE (dt.id OPERATOR(pg_catalog.=) 1) -- CTEs are recursively planned, and subquery foo is also recursively planned -- final plan becomes a real-time plan since we also have events_table in the @@ -719,15 +718,6 @@ END; $$; ERROR: (3/3) failed to execute one of the tasks CONTEXT: PL/pgSQL function inline_code_block line XX at RAISE -SET client_min_messages TO DEFAULT; +SET client_min_messages TO WARNING; DROP SCHEMA subquery_and_ctes CASCADE; -NOTICE: drop cascades to 8 other objects -DETAIL: drop cascades to table users_table -drop cascades to table events_table -drop cascades to table users_table_local -drop cascades to table dist_table -drop cascades to function func() -drop cascades to table ref_table_1 -drop cascades to table ref_table_2 -drop cascades to table dist SET search_path TO public; diff --git a/src/test/regress/expected/subquery_append.out b/src/test/regress/expected/subquery_append.out index 493c0bc37..381c467a7 100644 --- a/src/test/regress/expected/subquery_append.out +++ b/src/test/regress/expected/subquery_append.out @@ -224,7 +224,5 @@ SELECT count(*) FROM append_table WHERE extra = 1; UPDATE append_table a sET extra = 1 FROM append_table b WHERE a.key = b.key; ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns END; +SET client_min_messages TO WARNING; DROP SCHEMA subquery_append CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to table append_table -drop cascades to table ref_table diff --git a/src/test/regress/expected/subquery_basics.out b/src/test/regress/expected/subquery_basics.out index 7a4fb77b7..9e2b226eb 100644 --- a/src/test/regress/expected/subquery_basics.out +++ b/src/test/regress/expected/subquery_basics.out @@ -579,6 +579,7 @@ DEBUG: Wrapping relation "dist" "d1" to a subquery DEBUG: generating subplan XXX_1 for subquery SELECT id FROM public.dist d1 WHERE true DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT (SELECT dist.id FROM public.dist WHERE (dist.id OPERATOR(pg_catalog.>) d1.id) GROUP BY dist.id) AS id FROM (public.ref FULL JOIN (SELECT d1_1.id, NULL::integer AS value FROM (SELECT intermediate_result.id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id integer)) d1_1) d1 USING (id)) ERROR: correlated subqueries are not supported when the FROM clause contains a reference table +SET client_min_messages TO WARNING; DROP TABLE dist; DROP TABLE ref; DROP TABLE local; diff --git a/src/test/regress/expected/subquery_view.out b/src/test/regress/expected/subquery_view.out index 32354e329..0fb4ec714 100644 --- a/src/test/regress/expected/subquery_view.out +++ b/src/test/regress/expected/subquery_view.out @@ -583,6 +583,25 @@ EXPLAIN (COSTS OFF) WITH cte AS ( SELECT application_name AS text_col FROM pg_stat_activity ) SELECT * FROM reference_table JOIN cte USING (text_col); +$Q$); + coordinator_plan_with_subplans +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + Task Count: 1 +(2 rows) + +CREATE TABLE dist_table(text_col text, int_col int); +SELECT create_distributed_table('dist_table', 'text_col'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT public.coordinator_plan_with_subplans($Q$ +EXPLAIN (COSTS OFF) WITH cte AS ( + SELECT application_name AS text_col + FROM pg_stat_activity +) SELECT * FROM dist_table JOIN cte USING (text_col); $Q$); coordinator_plan_with_subplans --------------------------------------------------------------------- @@ -592,7 +611,7 @@ $Q$); -> Distributed Subplan XXX_2 -> Custom Scan (Citus Adaptive) Task Count: 1 - Task Count: 1 + Task Count: 4 (7 rows) CREATE OR REPLACE VIEW view_on_views AS SELECT pg_stat_activity.application_name, pg_locks.pid FROM pg_stat_activity, pg_locks; @@ -601,6 +620,18 @@ EXPLAIN (COSTS OFF) WITH cte AS ( SELECT application_name AS text_col FROM view_on_views ) SELECT * FROM reference_table JOIN cte USING (text_col); +$Q$); + coordinator_plan_with_subplans +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + Task Count: 1 +(2 rows) + +SELECT public.coordinator_plan_with_subplans($Q$ +EXPLAIN (COSTS OFF) WITH cte AS ( + SELECT application_name AS text_col + FROM view_on_views +) SELECT * FROM dist_table JOIN cte USING (text_col); $Q$); coordinator_plan_with_subplans --------------------------------------------------------------------- @@ -609,28 +640,9 @@ $Q$); -> Nested Loop -> Function Scan on pg_stat_get_activity s -> Function Scan on pg_lock_status l - Task Count: 1 + Task Count: 4 (6 rows) +SET client_min_messages TO WARNING; DROP SCHEMA subquery_view CASCADE; -NOTICE: drop cascades to 19 other objects -DETAIL: drop cascades to table users_table_local -drop cascades to table events_table_local -drop cascades to view view_without_subquery -drop cascades to view view_without_subquery_second -drop cascades to view subquery_limit -drop cascades to view subquery_non_p_key_group_by -drop cascades to view final_query_router -drop cascades to view final_query_realtime -drop cascades to view subquery_in_where -drop cascades to view subquery_from_from_where -drop cascades to view subquery_from_from_where_local_table -drop cascades to view repartition_view -drop cascades to view all_executors_view -drop cascades to view subquery_and_ctes -drop cascades to view subquery_and_ctes_second -drop cascades to view deep_subquery -drop cascades to view result_of_view_is_also_recursively_planned -drop cascades to table reference_table -drop cascades to view view_on_views SET search_path TO public; diff --git a/src/test/regress/expected/tableam.out b/src/test/regress/expected/tableam.out index 36f3729dd..8e6fe5205 100644 --- a/src/test/regress/expected/tableam.out +++ b/src/test/regress/expected/tableam.out @@ -96,9 +96,7 @@ HINT: To remove the local data, run: SELECT truncate_local_data_after_distribut select * from test_ref; WARNING: fake_scan_getnextslot -DETAIL: from localhost:xxxxx WARNING: fake_scan_getnextslot -DETAIL: from localhost:xxxxx a --------------------------------------------------------------------- 1 @@ -109,13 +107,14 @@ WARNING: fake_tuple_insert DETAIL: from localhost:xxxxx WARNING: fake_tuple_insert DETAIL: from localhost:xxxxx +WARNING: fake_tuple_insert -- we should error on following, since this AM is append only SET client_min_messages TO ERROR; delete from test_ref; ERROR: fake_tuple_delete not implemented CONTEXT: while executing command on localhost:xxxxx update test_ref set a=2; -ERROR: fake_tuple_update not implemented +ERROR: fake_fetch_row_version not implemented CONTEXT: while executing command on localhost:xxxxx RESET client_min_messages; -- ddl events should include "USING fake_am" @@ -292,4 +291,4 @@ ERROR: specifying a table access method is not supported on a partitioned table ALTER EXTENSION citus DROP ACCESS METHOD fake_am; NOTICE: Citus does not propagate adding/dropping member objects drop schema test_tableam cascade; -NOTICE: drop cascades to 5 other objects +NOTICE: drop cascades to 6 other objects diff --git a/src/test/regress/expected/union_pushdown.out b/src/test/regress/expected/union_pushdown.out index cbee11f8e..040535b75 100644 --- a/src/test/regress/expected/union_pushdown.out +++ b/src/test/regress/expected/union_pushdown.out @@ -1469,14 +1469,5 @@ $$); f (1 row) -RESET client_min_messages; +SET client_min_messages TO WARNING; DROP SCHEMA union_pushdown CASCADE; -NOTICE: drop cascades to 8 other objects -DETAIL: drop cascades to table users_table_part -drop cascades to table events_table_part -drop cascades to table events_table_ref -drop cascades to table events_table_local -drop cascades to table test_a -drop cascades to table test_b -drop cascades to type comp_type -drop cascades to view v diff --git a/src/test/regress/expected/upgrade_basic_after.out b/src/test/regress/expected/upgrade_basic_after.out index ff118c593..e724b81d3 100644 --- a/src/test/regress/expected/upgrade_basic_after.out +++ b/src/test/regress/expected/upgrade_basic_after.out @@ -9,31 +9,31 @@ SELECT * FROM pg_indexes WHERE schemaname = 'upgrade_basic' and tablename NOT LI upgrade_basic | tp | tp_pkey | | CREATE UNIQUE INDEX tp_pkey ON upgrade_basic.tp USING btree (a) (3 rows) -SELECT nextval('pg_dist_shardid_seq') = MAX(shardid)+1 FROM pg_dist_shard; +SELECT nextval('pg_dist_shardid_seq') > MAX(shardid) FROM pg_dist_shard; ?column? --------------------------------------------------------------------- t (1 row) -SELECT nextval('pg_dist_placement_placementid_seq') = MAX(placementid)+1 FROM pg_dist_placement; +SELECT nextval('pg_dist_placement_placementid_seq') > MAX(placementid) FROM pg_dist_placement; ?column? --------------------------------------------------------------------- t (1 row) -SELECT nextval('pg_dist_groupid_seq') = MAX(groupid)+1 FROM pg_dist_node; +SELECT nextval('pg_dist_groupid_seq') > MAX(groupid) FROM pg_dist_node; ?column? --------------------------------------------------------------------- t (1 row) -SELECT nextval('pg_dist_node_nodeid_seq') = MAX(nodeid)+1 FROM pg_dist_node; +SELECT nextval('pg_dist_node_nodeid_seq') > MAX(nodeid) FROM pg_dist_node; ?column? --------------------------------------------------------------------- t (1 row) -SELECT nextval('pg_dist_colocationid_seq') = MAX(colocationid)+1 FROM pg_dist_colocation; +SELECT nextval('pg_dist_colocationid_seq') > MAX(colocationid) FROM pg_dist_colocation; ?column? --------------------------------------------------------------------- t @@ -45,7 +45,7 @@ SELECT nextval('pg_dist_colocationid_seq') = MAX(colocationid)+1 FROM pg_dist_co SELECT CASE WHEN MAX(operation_id) IS NULL THEN true - ELSE nextval('pg_dist_operationid_seq') = MAX(operation_id)+1 + ELSE nextval('pg_dist_operationid_seq') > MAX(operation_id) END AS check_operationid FROM pg_dist_cleanup; check_operationid @@ -56,7 +56,7 @@ SELECT SELECT CASE WHEN MAX(record_id) IS NULL THEN true - ELSE nextval('pg_dist_cleanup_recordid_seq') = MAX(record_id)+1 + ELSE nextval('pg_dist_cleanup_recordid_seq') > MAX(record_id) END AS check_recordid FROM pg_dist_cleanup; check_recordid @@ -93,8 +93,8 @@ SELECT sequence_name FROM information_schema.sequences 'pg_dist_groupid_seq', 'pg_dist_node_nodeid_seq', 'pg_dist_colocationid_seq', - 'pg_dist_operationid_seq', - 'pg_dist_cleanup_recordid_seq', + 'pg_dist_operationid_seq', + 'pg_dist_cleanup_recordid_seq', 'pg_dist_background_job_job_id_seq', 'pg_dist_background_task_task_id_seq', 'pg_dist_clock_logical_seq' diff --git a/src/test/regress/expected/upgrade_citus_finish_citus_upgrade.out b/src/test/regress/expected/upgrade_citus_finish_citus_upgrade.out index bb80d9103..e3ca353c2 100644 --- a/src/test/regress/expected/upgrade_citus_finish_citus_upgrade.out +++ b/src/test/regress/expected/upgrade_citus_finish_citus_upgrade.out @@ -1,4 +1,15 @@ -- Citus upgrades are finished by calling a procedure +-- Note that pg_catalog.citus_finish_citus_upgrade() behaves differently +-- when last upgrade citus version is less than 11 +-- so we have two alternative outputs for this test +\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` +SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 11 +AS upgrade_test_old_citus_version_lt_11_0; + upgrade_test_old_citus_version_lt_11_0 +--------------------------------------------------------------------- + t +(1 row) + -- this is a transactional procedure, so rollback should be fine BEGIN; CALL citus_finish_citus_upgrade(); diff --git a/src/test/regress/expected/upgrade_citus_finish_citus_upgrade_0.out b/src/test/regress/expected/upgrade_citus_finish_citus_upgrade_0.out new file mode 100644 index 000000000..3c8a9a25c --- /dev/null +++ b/src/test/regress/expected/upgrade_citus_finish_citus_upgrade_0.out @@ -0,0 +1,41 @@ +-- Citus upgrades are finished by calling a procedure +-- Note that pg_catalog.citus_finish_citus_upgrade() behaves differently +-- when last upgrade citus version is less than 11 +-- so we have two alternative outputs for this test +\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` +SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 11 +AS upgrade_test_old_citus_version_lt_11_0; + upgrade_test_old_citus_version_lt_11_0 +--------------------------------------------------------------------- + f +(1 row) + +-- this is a transactional procedure, so rollback should be fine +BEGIN; + CALL citus_finish_citus_upgrade(); +NOTICE: already at the latest distributed schema version (11.3-1) +ROLLBACK; +-- do the actual job +CALL citus_finish_citus_upgrade(); +NOTICE: already at the latest distributed schema version (11.3-1) +-- show that the upgrade is successfull +SELECT metadata->>'last_upgrade_version' = extversion +FROM pg_dist_node_metadata, pg_extension WHERE extname = 'citus'; + ?column? +--------------------------------------------------------------------- + f +(1 row) + +-- idempotent, should be called multiple times +-- still, do not NOTICE the version as it changes per release +SET client_min_messages TO WARNING; +CALL citus_finish_citus_upgrade(); +-- we should be able to sync metadata in nontransactional way as well +SET citus.metadata_sync_mode TO 'nontransactional'; +SELECT start_metadata_sync_to_all_nodes(); + start_metadata_sync_to_all_nodes +--------------------------------------------------------------------- + t +(1 row) + +RESET citus.metadata_sync_mode; diff --git a/src/test/regress/expected/upgrade_columnar_metapage_after.out b/src/test/regress/expected/upgrade_columnar_metapage_after.out deleted file mode 100644 index 87e2424cb..000000000 --- a/src/test/regress/expected/upgrade_columnar_metapage_after.out +++ /dev/null @@ -1,127 +0,0 @@ -\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` -SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int >= 10 AND - substring(:'upgrade_test_old_citus_version', 'v\d+\.(\d+)\.\d+')::int >= 0 -AS upgrade_test_old_citus_version_ge_10_0; - upgrade_test_old_citus_version_ge_10_0 ---------------------------------------------------------------------- - t -(1 row) - -\gset -\if :upgrade_test_old_citus_version_ge_10_0 -\else -\q -\endif --- it's not the best practice to define this here, but we don't want to include --- columnar_test_helpers in upgrade test schedule -CREATE OR REPLACE FUNCTION columnar_storage_info( - rel regclass, - version_major OUT int4, - version_minor OUT int4, - storage_id OUT int8, - reserved_stripe_id OUT int8, - reserved_row_number OUT int8, - reserved_offset OUT int8) -STRICT -LANGUAGE c AS 'citus', 'columnar_storage_info'; -CREATE VIEW columnar_table_stripe_info AS -SELECT columnar_table_storageids.relname relname, - columnar.stripe.stripe_num stripe_num, - columnar.stripe.row_count row_count, - columnar.stripe.first_row_number first_row_number -FROM columnar.stripe, -( - SELECT c.oid relid, c.relname relname, (columnar_storage_info(c.oid)).storage_id relstorageid - FROM pg_class c, pg_am a - WHERE c.relam = a.oid AND amname = 'columnar' -) columnar_table_storageids -WHERE relstorageid = columnar.stripe.storage_id; -SET search_path TO upgrade_columnar_metapage, public; --- show that first_row_number values are equal to MAX(row_count) * stripe_num + COLUMNAR_FIRST_ROW_NUMBER -SELECT * FROM columnar_table_stripe_info ORDER BY relname, stripe_num; - relname | stripe_num | row_count | first_row_number ---------------------------------------------------------------------- - columnar_table_1 | 1 | 150000 | 1 - columnar_table_1 | 2 | 10000 | 150001 - columnar_table_2 | 1 | 1000 | 1 - columnar_table_2 | 2 | 901 | 150001 - columnar_table_3 | 1 | 2 | 1 -(5 rows) - --- should work since we upgrade metapages when upgrading schema version -INSERT INTO columnar_table_1 VALUES (3); --- state of stripe metadata for columnar_table_1 after post-upgrade insert -SELECT * FROM columnar_table_stripe_info WHERE relname = 'columnar_table_1' ORDER BY stripe_num; - relname | stripe_num | row_count | first_row_number ---------------------------------------------------------------------- - columnar_table_1 | 1 | 150000 | 1 - columnar_table_1 | 2 | 10000 | 150001 - columnar_table_1 | 3 | 1 | 160001 -(3 rows) - --- show that all columnar relation's metapage's are upgraded to "2.0" -SELECT count(*)=0 -FROM (SELECT (columnar_storage_info(c.oid)).* t - FROM pg_class c, pg_am a - WHERE c.relam = a.oid AND amname = 'columnar') t -WHERE t.version_major != 2 and t.version_minor != 0; - ?column? ---------------------------------------------------------------------- - t -(1 row) - --- print metapage for two of the tables -SELECT version_major, version_minor, reserved_stripe_id, reserved_row_number - FROM columnar_storage_info('columnar_table_1'); - version_major | version_minor | reserved_stripe_id | reserved_row_number ---------------------------------------------------------------------- - 2 | 0 | 4 | 310001 -(1 row) - -SELECT version_major, version_minor, reserved_stripe_id, reserved_row_number - FROM columnar_storage_info('columnar_table_2'); - version_major | version_minor | reserved_stripe_id | reserved_row_number ---------------------------------------------------------------------- - 2 | 0 | 3 | 150902 -(1 row) - --- show that no_data_columnar_table also has metapage after upgrade -SELECT version_major, version_minor, reserved_stripe_id, reserved_row_number - FROM columnar_storage_info('no_data_columnar_table'); - version_major | version_minor | reserved_stripe_id | reserved_row_number ---------------------------------------------------------------------- - 2 | 0 | 1 | 1 -(1 row) - --- table is already upgraded, make sure that upgrade_columnar_metapage is no-op -SELECT columnar_internal.upgrade_columnar_storage(c.oid) -FROM pg_class c, pg_am a -WHERE c.relam = a.oid AND amname = 'columnar' and relname = 'columnar_table_2'; - upgrade_columnar_storage ---------------------------------------------------------------------- - -(1 row) - -SELECT version_major, version_minor, reserved_stripe_id, reserved_row_number - FROM columnar_storage_info('columnar_table_2'); - version_major | version_minor | reserved_stripe_id | reserved_row_number ---------------------------------------------------------------------- - 2 | 0 | 3 | 150902 -(1 row) - -VACUUM FULL columnar_table_2; --- print metapage and stripe metadata after post-upgrade vacuum full -SELECT version_major, version_minor, reserved_stripe_id, reserved_row_number - FROM columnar_storage_info('columnar_table_2'); - version_major | version_minor | reserved_stripe_id | reserved_row_number ---------------------------------------------------------------------- - 2 | 0 | 3 | 2001 -(1 row) - -SELECT * FROM columnar_table_stripe_info WHERE relname = 'columnar_table_2' ORDER BY stripe_num; - relname | stripe_num | row_count | first_row_number ---------------------------------------------------------------------- - columnar_table_2 | 1 | 1000 | 1 - columnar_table_2 | 2 | 901 | 1001 -(2 rows) - diff --git a/src/test/regress/expected/upgrade_columnar_metapage_after_0.out b/src/test/regress/expected/upgrade_columnar_metapage_after_0.out deleted file mode 100644 index e515fe47d..000000000 --- a/src/test/regress/expected/upgrade_columnar_metapage_after_0.out +++ /dev/null @@ -1,13 +0,0 @@ -\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` -SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int >= 10 AND - substring(:'upgrade_test_old_citus_version', 'v\d+\.(\d+)\.\d+')::int >= 0 -AS upgrade_test_old_citus_version_ge_10_0; - upgrade_test_old_citus_version_ge_10_0 ---------------------------------------------------------------------- - f -(1 row) - -\gset -\if :upgrade_test_old_citus_version_ge_10_0 -\else -\q diff --git a/src/test/regress/expected/upgrade_columnar_metapage_before.out b/src/test/regress/expected/upgrade_columnar_metapage_before.out deleted file mode 100644 index 936ad7f10..000000000 --- a/src/test/regress/expected/upgrade_columnar_metapage_before.out +++ /dev/null @@ -1,31 +0,0 @@ -\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` -SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int >= 10 AND - substring(:'upgrade_test_old_citus_version', 'v\d+\.(\d+)\.\d+')::int >= 0 -AS upgrade_test_old_citus_version_ge_10_0; - upgrade_test_old_citus_version_ge_10_0 ---------------------------------------------------------------------- - t -(1 row) - -\gset -\if :upgrade_test_old_citus_version_ge_10_0 -\else -\q -\endif -CREATE SCHEMA upgrade_columnar_metapage; -SET search_path TO upgrade_columnar_metapage, public; -CREATE TABLE columnar_table_1(a INT, b INT) USING columnar; -INSERT INTO columnar_table_1 SELECT i FROM generate_series(160001, 320000) i; -CREATE TABLE columnar_table_2(b INT) USING columnar; -SELECT alter_columnar_table_set('columnar_table_2', - chunk_group_row_limit => 1000, - stripe_row_limit => 1000); - alter_columnar_table_set ---------------------------------------------------------------------- - -(1 row) - -INSERT INTO columnar_table_2 SELECT i FROM generate_series(1600, 3500) i; -CREATE TABLE columnar_table_3(b INT) USING columnar; -INSERT INTO columnar_table_3 VALUES (1), (2); -CREATE TABLE no_data_columnar_table(a INT, b INT, c TEXT) USING columnar; diff --git a/src/test/regress/expected/upgrade_columnar_metapage_before_0.out b/src/test/regress/expected/upgrade_columnar_metapage_before_0.out deleted file mode 100644 index e515fe47d..000000000 --- a/src/test/regress/expected/upgrade_columnar_metapage_before_0.out +++ /dev/null @@ -1,13 +0,0 @@ -\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` -SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int >= 10 AND - substring(:'upgrade_test_old_citus_version', 'v\d+\.(\d+)\.\d+')::int >= 0 -AS upgrade_test_old_citus_version_ge_10_0; - upgrade_test_old_citus_version_ge_10_0 ---------------------------------------------------------------------- - f -(1 row) - -\gset -\if :upgrade_test_old_citus_version_ge_10_0 -\else -\q diff --git a/src/test/regress/expected/upgrade_distributed_triggers_after.out b/src/test/regress/expected/upgrade_distributed_triggers_after.out index 7037b4c50..f36bf1749 100644 --- a/src/test/regress/expected/upgrade_distributed_triggers_after.out +++ b/src/test/regress/expected/upgrade_distributed_triggers_after.out @@ -9,9 +9,9 @@ -- this test is relevant only for pg14-15 upgrade -- SHOW server_version \gset -SELECT substring(:'server_version', '\d+')::int = 15 AS server_version_eq_15 +SELECT substring(:'server_version', '\d+')::int = 15 AND EXISTS (SELECT * FROM pg_namespace WHERE nspname = 'upgrade_distributed_triggers') AS is_14_15_pg_upgrade \gset -\if :server_version_eq_15 +\if :is_14_15_pg_upgrade \else \q \endif diff --git a/src/test/regress/expected/upgrade_distributed_triggers_after_0.out b/src/test/regress/expected/upgrade_distributed_triggers_after_0.out index 2b1f5cac1..c6a0d053a 100644 --- a/src/test/regress/expected/upgrade_distributed_triggers_after_0.out +++ b/src/test/regress/expected/upgrade_distributed_triggers_after_0.out @@ -9,8 +9,8 @@ -- this test is relevant only for pg14-15 upgrade -- SHOW server_version \gset -SELECT substring(:'server_version', '\d+')::int = 15 AS server_version_eq_15 +SELECT substring(:'server_version', '\d+')::int = 15 AND EXISTS (SELECT * FROM pg_namespace WHERE nspname = 'upgrade_distributed_triggers') AS is_14_15_pg_upgrade \gset -\if :server_version_eq_15 +\if :is_14_15_pg_upgrade \else \q diff --git a/src/test/regress/expected/upgrade_list_citus_objects.out b/src/test/regress/expected/upgrade_list_citus_objects.out index 29db2faea..3e9698788 100644 --- a/src/test/regress/expected/upgrade_list_citus_objects.out +++ b/src/test/regress/expected/upgrade_list_citus_objects.out @@ -69,16 +69,19 @@ ORDER BY 1; function citus_internal_add_placement_metadata(bigint,bigint,integer,bigint) function citus_internal_add_placement_metadata(bigint,integer,bigint,integer,bigint) function citus_internal_add_shard_metadata(regclass,bigint,"char",text,text) + function citus_internal_add_tenant_schema(oid,integer) function citus_internal_adjust_local_clock_to_remote(cluster_clock) function citus_internal_delete_colocation_metadata(integer) function citus_internal_delete_partition_metadata(regclass) function citus_internal_delete_shard_metadata(bigint) + function citus_internal_delete_tenant_schema(oid) function citus_internal_global_blocked_processes() function citus_internal_is_replication_origin_tracking_active() function citus_internal_local_blocked_processes() function citus_internal_mark_node_not_synced(integer,integer) function citus_internal_start_replication_origin_tracking() function citus_internal_stop_replication_origin_tracking() + function citus_internal_unregister_tenant_schema_globally(oid,text) function citus_internal_update_placement_metadata(bigint,integer,integer) function citus_internal_update_relation_colocation(oid,integer) function citus_is_clock_after(cluster_clock,cluster_clock) @@ -111,6 +114,8 @@ ORDER BY 1; function citus_remote_connection_stats() function citus_remove_node(text,integer) function citus_run_local_command(text) + function citus_schema_distribute(regnamespace) + function citus_schema_undistribute(regnamespace) function citus_server_id() function citus_set_coordinator_host(text,integer,noderole,name) function citus_set_default_rebalance_strategy(text) @@ -127,6 +132,7 @@ ORDER BY 1; function citus_stat_statements_reset() function citus_stat_tenants(boolean) function citus_stat_tenants_local(boolean) + function citus_stat_tenants_local_internal(boolean) function citus_stat_tenants_local_reset() function citus_stat_tenants_reset() function citus_table_is_visible(oid) @@ -305,6 +311,7 @@ ORDER BY 1; table pg_dist_placement table pg_dist_poolinfo table pg_dist_rebalance_strategy + table pg_dist_schema table pg_dist_shard table pg_dist_transaction type citus.distribution_type @@ -320,6 +327,7 @@ ORDER BY 1; view citus_dist_stat_activity view citus_lock_waits view citus_locks + view citus_schema.citus_schemas view citus_schema.citus_tables view citus_shard_indexes_on_worker view citus_shards @@ -330,5 +338,5 @@ ORDER BY 1; view citus_stat_tenants_local view pg_dist_shard_placement view time_partitions -(322 rows) +(330 rows) diff --git a/src/test/regress/expected/upgrade_partition_constraints_after.out b/src/test/regress/expected/upgrade_partition_constraints_after.out deleted file mode 100644 index b8980fabc..000000000 --- a/src/test/regress/expected/upgrade_partition_constraints_after.out +++ /dev/null @@ -1,33 +0,0 @@ --- run this test only when old citus version is earlier than 10.0 -\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` -SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 10 -AS upgrade_test_old_citus_version_lt_10_0; - upgrade_test_old_citus_version_lt_10_0 ---------------------------------------------------------------------- - t -(1 row) - -\gset -\if :upgrade_test_old_citus_version_lt_10_0 -\else -\q -\endif --- test cases for #3970 -SET search_path = test_3970; ---5. add a partition --- This command will fail as the child table has a wrong constraint name -CREATE TABLE part_table_p202009 PARTITION OF part_table FOR VALUES FROM ('2020-09-01 00:00:00') TO ('2020-10-01 00:00:00'); -ERROR: child table is missing constraint "ck_01234567890123456789012345678901234567890123_8478db72_xxxxxx" -CONTEXT: while executing command on localhost:xxxxx --- fix constraint names on partitioned table shards -SELECT fix_pre_citus10_partitioned_table_constraint_names('part_table'::regclass); - fix_pre_citus10_partitioned_table_constraint_names ---------------------------------------------------------------------- - -(1 row) - ---5. add a partition -CREATE TABLE part_table_p202009 PARTITION OF part_table FOR VALUES FROM ('2020-09-01 00:00:00') TO ('2020-10-01 00:00:00'); -RESET search_path; -DROP SCHEMA test_3970 CASCADE; -NOTICE: drop cascades to table test_3970.part_table diff --git a/src/test/regress/expected/upgrade_partition_constraints_after_0.out b/src/test/regress/expected/upgrade_partition_constraints_after_0.out deleted file mode 100644 index 1654fa35c..000000000 --- a/src/test/regress/expected/upgrade_partition_constraints_after_0.out +++ /dev/null @@ -1,13 +0,0 @@ --- run this test only when old citus version is earlier than 10.0 -\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` -SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 10 -AS upgrade_test_old_citus_version_lt_10_0; - upgrade_test_old_citus_version_lt_10_0 ---------------------------------------------------------------------- - f -(1 row) - -\gset -\if :upgrade_test_old_citus_version_lt_10_0 -\else -\q diff --git a/src/test/regress/expected/upgrade_partition_constraints_before.out b/src/test/regress/expected/upgrade_partition_constraints_before.out deleted file mode 100644 index 7e589005a..000000000 --- a/src/test/regress/expected/upgrade_partition_constraints_before.out +++ /dev/null @@ -1,44 +0,0 @@ --- run this test only when old citus version is earlier than 10.0 -\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` -SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 10 -AS upgrade_test_old_citus_version_lt_10_0; - upgrade_test_old_citus_version_lt_10_0 ---------------------------------------------------------------------- - t -(1 row) - -\gset -\if :upgrade_test_old_citus_version_lt_10_0 -\else -\q -\endif --- test cases for #3970 -CREATE SCHEMA test_3970; -SET search_path = test_3970; ---1. create a partitioned table -CREATE TABLE part_table ( - work_ymdt timestamp without time zone NOT NULL, - seq bigint NOT NULL, - my_seq bigint NOT NULL, - work_memo character varying(150), - CONSTRAINT work_memo_check CHECK ((octet_length((work_memo)::text) <= 150)) -) -PARTITION BY RANGE (work_ymdt); ---2. perform create_distributed_table -SELECT create_distributed_table('part_table', 'seq'); - create_distributed_table ---------------------------------------------------------------------- - -(1 row) - ---3. add a partition -CREATE TABLE part_table_p202008 PARTITION OF part_table FOR VALUES FROM ('2020-08-01 00:00:00') TO ('2020-09-01 00:00:00'); ---4. add a check constraint -ALTER TABLE part_table ADD CONSTRAINT my_seq CHECK (my_seq > 0); ---5. add a partition --- This command will fail as the child table has a wrong constraint name -CREATE TABLE part_table_p202009 PARTITION OF part_table FOR VALUES FROM ('2020-09-01 00:00:00') TO ('2020-10-01 00:00:00'); -ERROR: child table is missing constraint "my_seq_xxxxxx" -CONTEXT: while executing command on localhost:xxxxx --- Add another constraint with a long name that will get truncated with a hash -ALTER TABLE part_table ADD CONSTRAINT ck_012345678901234567890123456789012345678901234567890123456789 CHECK (my_seq > 0); diff --git a/src/test/regress/expected/upgrade_partition_constraints_before_0.out b/src/test/regress/expected/upgrade_partition_constraints_before_0.out deleted file mode 100644 index 1654fa35c..000000000 --- a/src/test/regress/expected/upgrade_partition_constraints_before_0.out +++ /dev/null @@ -1,13 +0,0 @@ --- run this test only when old citus version is earlier than 10.0 -\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` -SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 10 -AS upgrade_test_old_citus_version_lt_10_0; - upgrade_test_old_citus_version_lt_10_0 ---------------------------------------------------------------------- - f -(1 row) - -\gset -\if :upgrade_test_old_citus_version_lt_10_0 -\else -\q diff --git a/src/test/regress/expected/upgrade_pg_dist_object_test_after.out b/src/test/regress/expected/upgrade_pg_dist_object_test_after.out deleted file mode 100644 index 2490664fc..000000000 --- a/src/test/regress/expected/upgrade_pg_dist_object_test_after.out +++ /dev/null @@ -1,90 +0,0 @@ --- run this test only when old citus version is earlier than 10.0 -\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` -SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 10 -AS upgrade_test_old_citus_version_lt_10_0; - upgrade_test_old_citus_version_lt_10_0 ---------------------------------------------------------------------- - t -(1 row) - -\gset -\if :upgrade_test_old_citus_version_lt_10_0 -\else -\q -\endif --- drop objects from previous test (uprade_basic_after.sql) for a clean test --- drop upgrade_basic schema and switch back to public schema -SET search_path to public; -DROP SCHEMA upgrade_basic CASCADE; -NOTICE: drop cascades to 7 other objects -DETAIL: drop cascades to table upgrade_basic.t -drop cascades to table upgrade_basic.tp -drop cascades to table upgrade_basic.t_ab -drop cascades to table upgrade_basic.t2 -drop cascades to table upgrade_basic.r -drop cascades to table upgrade_basic.tr -drop cascades to table upgrade_basic.t_range --- as we updated citus to available version, --- "isn" extension --- "new_schema" schema --- "public" schema --- "fooschema" schema --- "footype" type (under schema 'fooschema') - -- will now be marked as distributed - -- but, - -- "seg" extension - -- will not be marked as distributed --- see underlying objects -SELECT i.* FROM pg_catalog.pg_dist_object, pg_identify_object_as_address(classid, objid, objsubid) i ORDER BY 1, 2, 3; - type | object_names | object_args ---------------------------------------------------------------------- - collation | {post_11_upgrade,german_phonebook_unpropagated} | {} - database | {postgres} | {} - extension | {plpgsql} | {} - function | {post_11_upgrade,func_in_transaction_def} | {} - role | {postgres} | {} - schema | {fooschema} | {} - schema | {new_schema} | {} - schema | {post_11_upgrade} | {} - schema | {public} | {} - sequence | {post_11_upgrade,SC1} | {} - sequence | {post_11_upgrade,seq_bigint} | {} - sequence | {post_11_upgrade,unrelated_sequence} | {} - table | {fooschema,footable} | {} - table | {new_schema,another_dist_table} | {} - table | {post_11_upgrade,colocated_dist_table} | {} - table | {post_11_upgrade,colocated_partitioned_table} | {} - table | {post_11_upgrade,colocated_partitioned_table_2020_01_01} | {} - table | {post_11_upgrade,dist} | {} - table | {post_11_upgrade,employees} | {} - table | {post_11_upgrade,index_backed_rep_identity} | {} - table | {post_11_upgrade,part_table} | {} - table | {post_11_upgrade,part_table_p202008} | {} - table | {post_11_upgrade,part_table_p202009} | {} - table | {post_11_upgrade,reference_table} | {} - table | {post_11_upgrade,sensors} | {} - table | {post_11_upgrade,sensors_2020_01_01} | {} - table | {post_11_upgrade,sensors_news} | {} - table | {post_11_upgrade,sensors_old} | {} - table | {post_11_upgrade,sensors_parser} | {} - table | {post_11_upgrade,sensors_parser_a_partition} | {} - table | {post_11_upgrade,test} | {} - table | {post_11_upgrade,test_propagate_collate} | {} - table | {public,dist_table} | {} - text search configuration | {post_11_upgrade,partial_index_test_config} | {} - type | {fooschema.footype} | {} - type | {post_11_upgrade.my_type} | {} - type | {post_11_upgrade.my_type_for_view} | {} - view | {post_11_upgrade,depends_on_citus} | {} - view | {post_11_upgrade,depends_on_nothing_1} | {} - view | {post_11_upgrade,depends_on_nothing_2} | {} - view | {post_11_upgrade,depends_on_pg} | {} - view | {post_11_upgrade,depends_on_seq} | {} - view | {post_11_upgrade,non_dist_upgrade_multiple_dist_view} | {} - view | {post_11_upgrade,non_dist_upgrade_ref_view} | {} - view | {post_11_upgrade,non_dist_upgrade_ref_view_2} | {} - view | {post_11_upgrade,reporting_line} | {} - view | {post_11_upgrade,view_for_upgrade_test} | {} - view | {post_11_upgrade,view_for_upgrade_test_my_type} | {} - (48 rows) - diff --git a/src/test/regress/expected/upgrade_pg_dist_object_test_after_1.out b/src/test/regress/expected/upgrade_pg_dist_object_test_after_1.out deleted file mode 100644 index 61e3ef713..000000000 --- a/src/test/regress/expected/upgrade_pg_dist_object_test_after_1.out +++ /dev/null @@ -1,51 +0,0 @@ --- run this test only when old citus version is earlier than 10.0 -\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` -SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 10 -AS upgrade_test_old_citus_version_lt_10_0; - upgrade_test_old_citus_version_lt_10_0 ---------------------------------------------------------------------- - t -(1 row) - -\gset -\if :upgrade_test_old_citus_version_lt_10_0 -\else -\q -\endif --- drop objects from previous test (uprade_basic_after.sql) for a clean test --- drop upgrade_basic schema and switch back to public schema -SET search_path to public; -DROP SCHEMA upgrade_basic CASCADE; -NOTICE: drop cascades to 7 other objects -DETAIL: drop cascades to table upgrade_basic.t -drop cascades to table upgrade_basic.tp -drop cascades to table upgrade_basic.t_ab -drop cascades to table upgrade_basic.t2 -drop cascades to table upgrade_basic.r -drop cascades to table upgrade_basic.tr -drop cascades to table upgrade_basic.t_range --- as we updated citus to available version, --- "isn" extension --- "new_schema" schema --- "public" schema --- "fooschema" schema --- "footype" type (under schema 'fooschema') - -- will now be marked as distributed - -- but, - -- "seg" extension - -- will not be marked as distributed --- see underlying objects -SELECT i.* FROM pg_catalog.pg_dist_object, pg_identify_object_as_address(classid, objid, objsubid) i ORDER BY 1, 2, 3; - type | object_names | object_args ---------------------------------------------------------------------- - database | {postgres} | {} - role | {postgres} | {} - schema | {fooschema} | {} - schema | {new_schema} | {} - schema | {public} | {} - table | {fooschema,footable} | {} - table | {new_schema,another_dist_table} | {} - table | {public,dist_table} | {} - type | {fooschema.footype} | {} - (9 rows) - diff --git a/src/test/regress/expected/upgrade_pg_dist_object_test_before.out b/src/test/regress/expected/upgrade_pg_dist_object_test_before.out deleted file mode 100644 index c5ae71852..000000000 --- a/src/test/regress/expected/upgrade_pg_dist_object_test_before.out +++ /dev/null @@ -1,46 +0,0 @@ --- run this test only when old citus version is earlier than 10.0 -\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` -SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 10 -AS upgrade_test_old_citus_version_lt_10_0; - upgrade_test_old_citus_version_lt_10_0 ---------------------------------------------------------------------- - t -(1 row) - -\gset -\if :upgrade_test_old_citus_version_lt_10_0 -\else -\q -\endif --- schema propagation -- --- public schema -CREATE TABLE dist_table (a int); -SELECT create_reference_table('dist_table'); - create_reference_table ---------------------------------------------------------------------- - -(1 row) - --- custom schema -CREATE SCHEMA new_schema; -SET search_path to new_schema; -CREATE TABLE another_dist_table (a int); -SELECT create_reference_table('another_dist_table'); - create_reference_table ---------------------------------------------------------------------- - -(1 row) - --- another custom schema and a type --- create table that depends both on a type & schema here (actually type depends on the schema) --- here we test if schema is marked as distributed successfully. --- This is because tracking the dependencies will hit to the schema for two times -CREATE SCHEMA fooschema; -CREATE TYPE fooschema.footype AS (x int, y int); -CREATE TABLE fooschema.footable (f fooschema.footype); -SELECT create_reference_table('fooschema.footable'); - create_reference_table ---------------------------------------------------------------------- - -(1 row) - diff --git a/src/test/regress/expected/upgrade_post_11_after.out b/src/test/regress/expected/upgrade_post_11_after.out index cf41da8e1..422bc846f 100644 --- a/src/test/regress/expected/upgrade_post_11_after.out +++ b/src/test/regress/expected/upgrade_post_11_after.out @@ -1,3 +1,17 @@ +-- run this test only when old citus version is earlier than 11.0 +\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` +SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 11 +AS upgrade_test_old_citus_version_lt_11_0; + upgrade_test_old_citus_version_lt_11_0 +--------------------------------------------------------------------- + t +(1 row) + +\gset +\if :upgrade_test_old_citus_version_lt_11_0 +\else +\q +\endif SET search_path = post_11_upgrade; -- tables, views and their dependencies become objects with Citus 11+ SELECT pg_identify_object_as_address(classid, objid, objsubid) FROM pg_catalog.pg_dist_object WHERE objid IN ('post_11_upgrade'::regnamespace, 'post_11_upgrade.part_table'::regclass, 'post_11_upgrade.sensors'::regclass, 'post_11_upgrade.func_in_transaction_def'::regproc, 'post_11_upgrade.partial_index_test_config'::regconfig, 'post_11_upgrade.my_type'::regtype, 'post_11_upgrade.employees'::regclass, 'post_11_upgrade.view_for_upgrade_test'::regclass, 'post_11_upgrade.my_type_for_view'::regtype, 'post_11_upgrade.view_for_upgrade_test_my_type'::regclass, 'post_11_upgrade.non_dist_table_for_view'::regclass, 'post_11_upgrade.non_dist_upgrade_test_view'::regclass, 'post_11_upgrade.non_dist_upgrade_test_view_local_join'::regclass, 'post_11_upgrade.non_dist_upgrade_multiple_dist_view'::regclass, 'post_11_upgrade.non_dist_upgrade_ref_view'::regclass, 'post_11_upgrade.non_dist_upgrade_ref_view_2'::regclass, 'post_11_upgrade.reporting_line'::regclass, 'post_11_upgrade.v_test_1'::regclass, 'post_11_upgrade.v_test_2'::regclass, 'post_11_upgrade.owned_by_extension_table'::regclass, 'post_11_upgrade.materialized_view'::regclass, 'post_11_upgrade.owned_by_extension_view'::regclass, 'post_11_upgrade.local_type'::regtype, 'post_11_upgrade.non_dist_dist_table_for_view'::regclass, 'post_11_upgrade.depends_on_nothing_1'::regclass, 'post_11_upgrade.depends_on_nothing_2'::regclass, 'post_11_upgrade.depends_on_pg'::regclass, 'post_11_upgrade.depends_on_citus'::regclass, 'post_11_upgrade.depends_on_seq'::regclass, 'post_11_upgrade.depends_on_seq_and_no_support'::regclass) ORDER BY 1; diff --git a/src/test/regress/expected/upgrade_pg_dist_object_test_after_0.out b/src/test/regress/expected/upgrade_post_11_after_0.out similarity index 52% rename from src/test/regress/expected/upgrade_pg_dist_object_test_after_0.out rename to src/test/regress/expected/upgrade_post_11_after_0.out index 1654fa35c..7ddb3fe39 100644 --- a/src/test/regress/expected/upgrade_pg_dist_object_test_after_0.out +++ b/src/test/regress/expected/upgrade_post_11_after_0.out @@ -1,13 +1,13 @@ --- run this test only when old citus version is earlier than 10.0 +-- run this test only when old citus version is earlier than 11.0 \set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` -SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 10 -AS upgrade_test_old_citus_version_lt_10_0; - upgrade_test_old_citus_version_lt_10_0 +SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 11 +AS upgrade_test_old_citus_version_lt_11_0; + upgrade_test_old_citus_version_lt_11_0 --------------------------------------------------------------------- f (1 row) \gset -\if :upgrade_test_old_citus_version_lt_10_0 +\if :upgrade_test_old_citus_version_lt_11_0 \else \q diff --git a/src/test/regress/expected/upgrade_post_11_before.out b/src/test/regress/expected/upgrade_post_11_before.out index 05ff85578..695e5f743 100644 --- a/src/test/regress/expected/upgrade_post_11_before.out +++ b/src/test/regress/expected/upgrade_post_11_before.out @@ -1,3 +1,17 @@ +-- run this test only when old citus version is earlier than 11.0 +\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` +SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 11 +AS upgrade_test_old_citus_version_lt_11_0; + upgrade_test_old_citus_version_lt_11_0 +--------------------------------------------------------------------- + t +(1 row) + +\gset +\if :upgrade_test_old_citus_version_lt_11_0 +\else +\q +\endif -- test cases for #3970 SET citus.shard_count TO 32; SET citus.shard_replication_factor TO 1; diff --git a/src/test/regress/expected/upgrade_pg_dist_object_test_before_0.out b/src/test/regress/expected/upgrade_post_11_before_0.out similarity index 52% rename from src/test/regress/expected/upgrade_pg_dist_object_test_before_0.out rename to src/test/regress/expected/upgrade_post_11_before_0.out index 1654fa35c..7ddb3fe39 100644 --- a/src/test/regress/expected/upgrade_pg_dist_object_test_before_0.out +++ b/src/test/regress/expected/upgrade_post_11_before_0.out @@ -1,13 +1,13 @@ --- run this test only when old citus version is earlier than 10.0 +-- run this test only when old citus version is earlier than 11.0 \set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` -SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 10 -AS upgrade_test_old_citus_version_lt_10_0; - upgrade_test_old_citus_version_lt_10_0 +SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 11 +AS upgrade_test_old_citus_version_lt_11_0; + upgrade_test_old_citus_version_lt_11_0 --------------------------------------------------------------------- f (1 row) \gset -\if :upgrade_test_old_citus_version_lt_10_0 +\if :upgrade_test_old_citus_version_lt_11_0 \else \q diff --git a/src/test/regress/expected/upgrade_schema_based_sharding_after.out b/src/test/regress/expected/upgrade_schema_based_sharding_after.out new file mode 100644 index 000000000..52d501b16 --- /dev/null +++ b/src/test/regress/expected/upgrade_schema_based_sharding_after.out @@ -0,0 +1,98 @@ +ALTER SCHEMA "tenant\'_1" RENAME TO tenant_1; +ALTER SCHEMA "tenant\'_2" RENAME TO tenant_2; +-- verify that colocation id is set even for empty tenant +SELECT colocationid > 0 FROM pg_dist_schema +WHERE schemaid::regnamespace::text = 'tenant_1'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- verify the same on workers +SELECT result FROM run_command_on_workers($$ + SELECT colocationid > 0 FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_1'; +$$); + result +--------------------------------------------------------------------- + t + t +(2 rows) + +-- verify that colocation id is set for non-empty tenant +SELECT colocationid = ( + SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_2.test_table'::regclass +) +FROM pg_dist_schema +WHERE schemaid::regnamespace::text = 'tenant_2'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- verify the same on workers +SELECT result FROM run_command_on_workers($$ + SELECT colocationid = ( + SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_2.test_table'::regclass + ) + FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_2'; +$$); + result +--------------------------------------------------------------------- + t + t +(2 rows) + +CREATE TABLE tenant_1.tbl_1(a int, b text); +CREATE TABLE tenant_2.tbl_1(a int, b text); +-- Show that we can create further tenant tables in the tenant schemas +-- after pg upgrade. +SELECT COUNT(*)=2 FROM pg_dist_partition +WHERE logicalrelid IN ('tenant_1.tbl_1'::regclass, 'tenant_2.tbl_1'::regclass) AND + partmethod = 'n' AND repmodel = 's' AND colocationid > 0; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT colocationid = ( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'tenant_1.tbl_1'::regclass AND + partmethod = 'n' AND repmodel = 's' +) +FROM pg_dist_schema +WHERE schemaid::regnamespace::text = 'tenant_1'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT colocationid = ( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'tenant_2.tbl_1'::regclass AND + partmethod = 'n' AND repmodel = 's' +) +FROM pg_dist_schema +WHERE schemaid::regnamespace::text = 'tenant_2'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- rollback the changes made on following schemas to make this test idempotent +DROP TABLE tenant_1.tbl_1, tenant_2.tbl_1; +ALTER SCHEMA tenant_1 RENAME TO "tenant\'_1"; +ALTER SCHEMA tenant_2 RENAME TO "tenant\'_2"; +SET citus.enable_schema_based_sharding TO ON; +CREATE SCHEMA tenant_3; +-- Show that we can create furher tenant schemas after pg upgrade. +SELECT COUNT(*)=1 FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_3'; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +-- drop the schema created in this test to this test idempotent +DROP SCHEMA tenant_3 CASCADE; +RESET citus.enable_schema_based_sharding; diff --git a/src/test/regress/expected/upgrade_schema_based_sharding_before.out b/src/test/regress/expected/upgrade_schema_based_sharding_before.out new file mode 100644 index 000000000..e9f0c5e46 --- /dev/null +++ b/src/test/regress/expected/upgrade_schema_based_sharding_before.out @@ -0,0 +1,10 @@ +SET citus.enable_schema_based_sharding TO ON; +-- Create tenant tables with schema names that need escaping +-- to verify that citus_prepare_pg_upgrade() correctly saves +-- them into public schema. +-- empty tenant +CREATE SCHEMA "tenant\'_1"; +-- non-empty tenant +CREATE SCHEMA "tenant\'_2"; +CREATE TABLE "tenant\'_2".test_table(a int, b text); +RESET citus.enable_schema_based_sharding; diff --git a/src/test/regress/expected/upgrade_single_shard_table_after.out b/src/test/regress/expected/upgrade_single_shard_table_after.out new file mode 100644 index 000000000..5c6b81c66 --- /dev/null +++ b/src/test/regress/expected/upgrade_single_shard_table_after.out @@ -0,0 +1,37 @@ +-- check that we properly retained the single-shard table +SELECT 1 FROM pg_dist_partition +WHERE logicalrelid = 'citus_schema.null_shard_key'::regclass AND + partmethod = 'n' AND repmodel = 's' AND colocationid != 0; + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +BEGIN; + INSERT INTO citus_schema.null_shard_key (name) VALUES ('c'); + SELECT * FROM citus_schema.null_shard_key ORDER BY id; + id | name +--------------------------------------------------------------------- + 1 | a + 2 | b + | c +(3 rows) + +ROLLBACK; +-- Check that we can create a distributed table with a single-shard +-- after upgrade. +CREATE TABLE citus_schema.null_shard_key_after_upgrade (id bigserial, name text); +SELECT create_distributed_table('citus_schema.null_shard_key_after_upgrade', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO citus_schema.null_shard_key_after_upgrade (name) VALUES ('c'); +SELECT * FROM citus_schema.null_shard_key_after_upgrade ORDER BY id; + id | name +--------------------------------------------------------------------- + 1 | c +(1 row) + +DROP TABLE citus_schema.null_shard_key_after_upgrade; diff --git a/src/test/regress/expected/upgrade_single_shard_table_before.out b/src/test/regress/expected/upgrade_single_shard_table_before.out new file mode 100644 index 000000000..d5abe2ae7 --- /dev/null +++ b/src/test/regress/expected/upgrade_single_shard_table_before.out @@ -0,0 +1,8 @@ +CREATE TABLE null_shard_key (id int, name text); +SELECT create_distributed_table('null_shard_key', null); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO null_shard_key (id, name) VALUES (1, 'a'), (2, 'b'); diff --git a/src/test/regress/expected/values.out b/src/test/regress/expected/values.out index ad5f8a911..575fd6866 100644 --- a/src/test/regress/expected/values.out +++ b/src/test/regress/expected/values.out @@ -636,9 +636,5 @@ DEBUG: CTE cte_1 is going to be inlined via distributed planning (1 row) COMMIT; -RESET client_min_messages; +SET client_min_messages TO WARNING; DROP SCHEMA values_subquery CASCADE; -NOTICE: drop cascades to 3 other objects -DETAIL: drop cascades to table test_values -drop cascades to table test_values_ref -drop cascades to function fixed_volatile_value() diff --git a/src/test/regress/expected/view_propagation.out b/src/test/regress/expected/view_propagation.out index 6ff1ab5a3..d3d5bdb7b 100644 --- a/src/test/regress/expected/view_propagation.out +++ b/src/test/regress/expected/view_propagation.out @@ -1,6 +1,7 @@ -- Tests to check propagation of all view commands CREATE SCHEMA view_prop_schema; SET search_path to view_prop_schema; +SET citus.next_shard_id TO 1420195; -- Check creating views depending on different types of tables -- and from multiple schemas -- Check the most basic one @@ -411,7 +412,7 @@ DROP TABLE view_table_2 CASCADE; NOTICE: drop cascades to 2 other objects DETAIL: drop cascades to view prop_view_2 drop cascades to constraint f_key_for_local_table on table view_table_3 -NOTICE: drop cascades to constraint f_key_for_local_table_1410200 on table view_prop_schema.view_table_3_1410200 +NOTICE: drop cascades to constraint f_key_for_local_table_1420200 on table view_prop_schema.view_table_3_1420200 CONTEXT: SQL statement "SELECT citus_drop_all_shards(v_obj.objid, v_obj.schema_name, v_obj.object_name, drop_shards_metadata_only := false)" PL/pgSQL function citus_drop_trigger() line XX at PERFORM NOTICE: removing table view_prop_schema.view_table_3 from metadata as it is not connected to any reference tables via foreign keys @@ -935,5 +936,7 @@ DETAIL: "view vv3" circularly depends itself, resolve circular dependency first RESET citus.enable_unsupported_feature_messages; RESET citus.enforce_object_restrictions_for_local_objects; SET client_min_messages TO ERROR; +DROP TABLE public.parent_1, public.employees CASCADE; DROP SCHEMA view_prop_schema_inner CASCADE; DROP SCHEMA view_prop_schema, axx CASCADE; +DROP ROLE view_creation_user, alter_view_user, grant_view_user; diff --git a/src/test/regress/expected/window_functions.out b/src/test/regress/expected/window_functions.out index 6f30a49e3..d4718c4dd 100644 --- a/src/test/regress/expected/window_functions.out +++ b/src/test/regress/expected/window_functions.out @@ -3,8 +3,6 @@ -- =================================================================== -- test top level window functions that are pushdownable -- =================================================================== --- This test file has an alternative output because of use of --- incremental sort in some explain outputs in PG13 -- -- a very simple window function with an aggregate and a window function -- distribution column is on the partition by clause diff --git a/src/test/regress/expected/window_functions_0.out b/src/test/regress/expected/window_functions_0.out deleted file mode 100644 index c5a132301..000000000 --- a/src/test/regress/expected/window_functions_0.out +++ /dev/null @@ -1,1657 +0,0 @@ --- --- WINDOW_FUNCTIONS --- =================================================================== --- test top level window functions that are pushdownable --- =================================================================== --- This test file has an alternative output because of use of --- incremental sort in some explain outputs in PG13 --- --- a very simple window function with an aggregate and a window function --- distribution column is on the partition by clause -SELECT - user_id, COUNT(*) OVER (PARTITION BY user_id), - rank() OVER (PARTITION BY user_id) -FROM - users_table -ORDER BY - 1 DESC, 2 DESC, 3 DESC -LIMIT 5; - user_id | count | rank ---------------------------------------------------------------------- - 6 | 10 | 1 - 6 | 10 | 1 - 6 | 10 | 1 - 6 | 10 | 1 - 6 | 10 | 1 -(5 rows) - --- a more complicated window clause, including an aggregate --- in both the window clause and the target entry -SELECT - user_id, avg(avg(value_3)) OVER (PARTITION BY user_id, MIN(value_2)) -FROM - users_table -GROUP BY - 1 -ORDER BY - 2 DESC NULLS LAST, 1 DESC; - user_id | avg ---------------------------------------------------------------------- - 2 | 3 - 4 | 2.82608695652174 - 3 | 2.70588235294118 - 6 | 2.6 - 1 | 2.57142857142857 - 5 | 2.46153846153846 -(6 rows) - --- window clause operates on the results of a subquery -SELECT - user_id, max(value_1) OVER (PARTITION BY user_id, MIN(value_2)) -FROM ( - SELECT - DISTINCT us.user_id, us.value_2, value_1, random() as r1 - FROM - users_table as us, events_table - WHERE - us.user_id = events_table.user_id AND event_type IN (1,2) - ORDER BY - user_id, value_2 - ) s -GROUP BY - 1, value_1 -ORDER BY - 2 DESC, 1; - user_id | max ---------------------------------------------------------------------- - 1 | 5 - 3 | 5 - 3 | 5 - 4 | 5 - 5 | 5 - 5 | 5 - 6 | 5 - 6 | 5 - 1 | 4 - 2 | 4 - 3 | 4 - 3 | 4 - 3 | 4 - 4 | 4 - 4 | 4 - 5 | 4 - 5 | 4 - 1 | 3 - 2 | 3 - 2 | 3 - 2 | 3 - 6 | 3 - 2 | 2 - 4 | 2 - 4 | 2 - 4 | 2 - 6 | 2 - 1 | 1 - 3 | 1 - 5 | 1 - 6 | 1 - 5 | 0 -(32 rows) - --- window function operates on the results of --- a join --- we also want to verify that this doesn't crash --- when the logging level is DEBUG4 -SET log_min_messages TO DEBUG4; -SELECT - us.user_id, - SUM(us.value_1) OVER (PARTITION BY us.user_id) -FROM - users_table us - JOIN - events_table ev - ON (us.user_id = ev.user_id) -GROUP BY - 1, - value_1 -ORDER BY - 1, - 2 -LIMIT 5; - user_id | sum ---------------------------------------------------------------------- - 1 | 13 - 1 | 13 - 1 | 13 - 1 | 13 - 2 | 10 -(5 rows) - --- the same query, but this time join with an alias -SELECT - user_id, value_1, SUM(j.value_1) OVER (PARTITION BY j.user_id) -FROM - (users_table us - JOIN - events_table ev - USING (user_id ) - ) j -GROUP BY - user_id, - value_1 -ORDER BY - 3 DESC, 2 DESC, 1 DESC -LIMIT 5; - user_id | value_1 | sum ---------------------------------------------------------------------- - 5 | 5 | 15 - 4 | 5 | 15 - 3 | 5 | 15 - 5 | 4 | 15 - 4 | 4 | 15 -(5 rows) - --- querying views that have window functions should be ok -CREATE VIEW window_view AS -SELECT - DISTINCT user_id, rank() OVER (PARTITION BY user_id ORDER BY value_1) -FROM - users_table -GROUP BY - user_id, value_1 -HAVING count(*) > 1; --- Window function in View works -SELECT * -FROM - window_view -ORDER BY - 2 DESC, 1 -LIMIT 10; - user_id | rank ---------------------------------------------------------------------- - 5 | 6 - 2 | 5 - 4 | 5 - 5 | 5 - 2 | 4 - 3 | 4 - 4 | 4 - 5 | 4 - 6 | 4 - 2 | 3 -(10 rows) - --- the other way around also should work fine --- query a view using window functions -CREATE VIEW users_view AS SELECT * FROM users_table; -SELECT - DISTINCT user_id, rank() OVER (PARTITION BY user_id ORDER BY value_1) -FROM - users_view -GROUP BY - user_id, value_1 -HAVING count(*) > 4 -ORDER BY - 2 DESC, 1; - user_id | rank ---------------------------------------------------------------------- - 4 | 2 - 5 | 2 - 2 | 1 - 3 | 1 - 4 | 1 - 5 | 1 -(6 rows) - -DROP VIEW users_view, window_view; --- window functions along with subquery in HAVING -SELECT - user_id, count (user_id) OVER (PARTITION BY user_id) -FROM - users_table -GROUP BY - user_id HAVING avg(value_1) < (SELECT min(k_no) FROM users_ref_test_table) -ORDER BY 1 DESC,2 DESC -LIMIT 1; - user_id | count ---------------------------------------------------------------------- - 6 | 1 -(1 row) - --- window function uses columns from two different tables -SELECT - DISTINCT ON (events_table.user_id, rnk) events_table.user_id, rank() OVER my_win AS rnk -FROM - events_table, users_table -WHERE - users_table.user_id = events_table.user_id -WINDOW - my_win AS (PARTITION BY events_table.user_id, users_table.value_1 ORDER BY events_table.time DESC) -ORDER BY - rnk DESC, 1 DESC -LIMIT 10; - user_id | rnk ---------------------------------------------------------------------- - 3 | 121 - 5 | 118 - 2 | 116 - 3 | 115 - 4 | 113 - 2 | 111 - 5 | 109 - 3 | 109 - 4 | 106 - 2 | 106 -(10 rows) - --- the same query with reference table column is also on the partition by clause -SELECT - DISTINCT ON (events_table.user_id, rnk) events_table.user_id, rank() OVER my_win AS rnk -FROM - events_table, users_ref_test_table uref -WHERE - uref.id = events_table.user_id -WINDOW - my_win AS (PARTITION BY events_table.user_id, uref.k_no ORDER BY events_table.time DESC) -ORDER BY - rnk DESC, 1 DESC -LIMIT 10; - user_id | rnk ---------------------------------------------------------------------- - 2 | 24 - 2 | 23 - 2 | 22 - 3 | 21 - 2 | 21 - 3 | 20 - 2 | 20 - 3 | 19 - 2 | 19 - 3 | 18 -(10 rows) - --- similar query with no distribution column on the partition by clause -SELECT - DISTINCT ON (events_table.user_id, rnk) events_table.user_id, rank() OVER my_win AS rnk -FROM - events_table, users_ref_test_table uref -WHERE - uref.id = events_table.user_id -WINDOW - my_win AS (PARTITION BY events_table.value_2, uref.k_no ORDER BY events_table.time DESC) -ORDER BY - rnk DESC, 1 DESC -LIMIT 10; - user_id | rnk ---------------------------------------------------------------------- - 3 | 7 - 2 | 7 - 3 | 6 - 2 | 6 - 4 | 5 - 3 | 5 - 2 | 5 - 1 | 5 - 6 | 4 - 5 | 4 -(10 rows) - --- ORDER BY in the window function is an aggregate -SELECT - user_id, rank() OVER my_win as rnk, avg(value_2) as avg_val_2 -FROM - events_table -GROUP BY - user_id, date_trunc('day', time) -WINDOW - my_win AS (PARTITION BY user_id ORDER BY avg(event_type) DESC) -ORDER BY - 3 DESC, 2 DESC, 1 DESC; - user_id | rnk | avg_val_2 ---------------------------------------------------------------------- - 1 | 1 | 3.3750000000000000 - 3 | 2 | 3.1666666666666667 - 5 | 1 | 2.6666666666666667 - 6 | 1 | 2.5000000000000000 - 4 | 1 | 2.5000000000000000 - 2 | 1 | 2.4736842105263158 - 4 | 2 | 2.4000000000000000 - 1 | 2 | 2.1428571428571429 - 5 | 2 | 2.0909090909090909 - 6 | 2 | 2.0000000000000000 - 2 | 2 | 2.0000000000000000 - 3 | 1 | 1.8000000000000000 -(12 rows) - --- lets push the limits of writing complex expressions aling with the window functions -SELECT - COUNT(*) OVER (PARTITION BY user_id, user_id + 1), - rank() OVER (PARTITION BY user_id) as cnt1, - COUNT(*) OVER (PARTITION BY user_id, abs(value_1 - value_2)) as cnt2, - date_trunc('min', lag(time) OVER (PARTITION BY user_id ORDER BY time)) as datee, - rank() OVER my_win as rnnk, - avg(CASE - WHEN user_id > 4 - THEN value_1 - ELSE value_2 - END) FILTER (WHERE user_id > 2) OVER my_win_2 as filtered_count, - sum(user_id * (5.0 / (value_1 + value_2 + 0.1)) * value_3) FILTER (WHERE value_1::text LIKE '%1%') OVER my_win_4 as cnt_with_filter_2 -FROM - users_table -WINDOW - my_win AS (PARTITION BY user_id, (value_1%3)::int ORDER BY time DESC), - my_win_2 AS (PARTITION BY user_id, (value_1)::int ORDER BY time DESC), - my_win_3 AS (PARTITION BY user_id, date_trunc('min', time)), - my_win_4 AS (my_win_3 ORDER BY value_2, value_3) -ORDER BY - cnt_with_filter_2 DESC NULLS LAST, filtered_count DESC NULLS LAST, datee DESC NULLS LAST, rnnk DESC, cnt2 DESC, cnt1 DESC, user_id DESC -LIMIT 5; - count | cnt1 | cnt2 | datee | rnnk | filtered_count | cnt_with_filter_2 ---------------------------------------------------------------------- - 23 | 1 | 7 | Thu Nov 23 02:14:00 2017 | 6 | 0.00000000000000000000 | 72.7272727272727 - 10 | 1 | 3 | Wed Nov 22 23:01:00 2017 | 1 | 1.00000000000000000000 | 57.1428571428571 - 17 | 1 | 5 | Wed Nov 22 23:24:00 2017 | 8 | 3.0000000000000000 | 28.5714285714286 - 17 | 1 | 5 | | 10 | 2.6666666666666667 | 28.5714285714286 - 17 | 1 | 5 | Thu Nov 23 00:15:00 2017 | 7 | 3.6666666666666667 | 24.1935483870968 -(5 rows) - --- some tests with GROUP BY along with PARTITION BY -SELECT - user_id, - rank() OVER my_win as my_rank, - avg(avg(event_type)) OVER my_win_2 as avg, - max(time) as mx_time -FROM - events_table -GROUP BY - user_id, - value_2 -WINDOW - my_win AS (PARTITION BY user_id, max(event_type) ORDER BY count(*) DESC), - my_win_2 AS (PARTITION BY user_id, avg(user_id) ORDER BY count(*) DESC) -ORDER BY - avg DESC, - mx_time DESC, - my_rank DESC, - user_id DESC; - user_id | my_rank | avg | mx_time ---------------------------------------------------------------------- - 6 | 1 | 3.0000000000000000 | Thu Nov 23 14:00:13.20013 2017 - 6 | 2 | 3.0000000000000000 | Thu Nov 23 11:16:13.106691 2017 - 6 | 1 | 3.0000000000000000 | Thu Nov 23 07:27:32.822068 2017 - 3 | 1 | 2.9857142857142857 | Thu Nov 23 16:31:56.219594 2017 - 4 | 2 | 2.9555555555555556 | Thu Nov 23 14:19:25.765876 2017 - 4 | 1 | 2.9555555555555556 | Thu Nov 23 08:36:53.871919 2017 - 1 | 4 | 2.8633333333333333 | Wed Nov 22 21:06:57.457147 2017 - 1 | 1 | 2.8250000000000000 | Thu Nov 23 21:54:46.924477 2017 - 2 | 2 | 2.7738095238095238 | Thu Nov 23 13:27:37.441959 2017 - 1 | 2 | 2.7722222222222222 | Thu Nov 23 09:23:30.994345 2017 - 3 | 1 | 2.7682539682539682 | Thu Nov 23 01:17:49.040685 2017 - 2 | 1 | 2.7142857142857143 | Thu Nov 23 15:58:49.273421 2017 - 1 | 3 | 2.5791666666666667 | Thu Nov 23 11:09:38.074595 2017 - 3 | 1 | 2.5714285714285714 | Thu Nov 23 16:44:41.903713 2017 - 2 | 1 | 2.5158730158730159 | Thu Nov 23 14:02:47.738901 2017 - 4 | 1 | 2.47777777777777778333 | Thu Nov 23 16:20:33.264457 2017 - 4 | 3 | 2.47777777777777778333 | Thu Nov 23 08:14:18.231273 2017 - 4 | 3 | 2.47777777777777778333 | Thu Nov 23 07:32:45.521278 2017 - 1 | 1 | 2.4000000000000000 | Thu Nov 23 10:23:27.617726 2017 - 2 | 1 | 2.3869047619047619 | Thu Nov 23 17:26:14.563216 2017 - 3 | 1 | 2.3841269841269841 | Thu Nov 23 18:08:26.550729 2017 - 3 | 1 | 2.3841269841269841 | Thu Nov 23 09:38:45.338008 2017 - 3 | 2 | 2.3841269841269841 | Thu Nov 23 06:44:50.887182 2017 - 2 | 2 | 2.3095238095238095 | Thu Nov 23 04:05:16.217731 2017 - 5 | 2 | 2.3000000000000000 | Thu Nov 23 14:28:51.833214 2017 - 5 | 2 | 2.3000000000000000 | Thu Nov 23 14:23:09.889786 2017 - 4 | 1 | 2.2000000000000000 | Thu Nov 23 18:10:21.338399 2017 - 2 | 1 | 2.09126984126984126667 | Thu Nov 23 03:35:04.321504 2017 - 5 | 1 | 2.0000000000000000 | Thu Nov 23 16:11:02.929469 2017 - 5 | 1 | 2.0000000000000000 | Thu Nov 23 14:40:40.467511 2017 - 5 | 1 | 2.0000000000000000 | Thu Nov 23 13:26:45.571108 2017 -(31 rows) - --- test for range and rows mode and different window functions --- mostly to make sure that deparsing works fine -SELECT - user_id, - rank() OVER (PARTITION BY user_id ROWS BETWEEN - UNBOUNDED PRECEDING AND CURRENT ROW), - dense_rank() OVER (PARTITION BY user_id RANGE BETWEEN - UNBOUNDED PRECEDING AND CURRENT ROW), - CUME_DIST() OVER (PARTITION BY user_id RANGE BETWEEN - UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING), - PERCENT_RANK() OVER (PARTITION BY user_id ORDER BY avg(value_1) RANGE BETWEEN - UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) -FROM - users_table -GROUP BY - 1 -ORDER BY - 4 DESC,3 DESC,2 DESC ,1 DESC; - user_id | rank | dense_rank | cume_dist | percent_rank ---------------------------------------------------------------------- - 6 | 1 | 1 | 1 | 0 - 5 | 1 | 1 | 1 | 0 - 4 | 1 | 1 | 1 | 0 - 3 | 1 | 1 | 1 | 0 - 2 | 1 | 1 | 1 | 0 - 1 | 1 | 1 | 1 | 0 -(6 rows) - --- test exclude supported -SELECT - user_id, - value_1, - array_agg(value_1) OVER (PARTITION BY user_id ORDER BY value_1 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), - array_agg(value_1) OVER (PARTITION BY user_id ORDER BY value_1 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE CURRENT ROW) -FROM - users_table -WHERE - user_id > 2 AND user_id < 6 -ORDER BY - user_id, value_1, 3, 4; - user_id | value_1 | array_agg | array_agg ---------------------------------------------------------------------- - 3 | 0 | {0} | - 3 | 1 | {0,1,1,1,1,1,1} | {0,1,1,1,1,1} - 3 | 1 | {0,1,1,1,1,1,1} | {0,1,1,1,1,1} - 3 | 1 | {0,1,1,1,1,1,1} | {0,1,1,1,1,1} - 3 | 1 | {0,1,1,1,1,1,1} | {0,1,1,1,1,1} - 3 | 1 | {0,1,1,1,1,1,1} | {0,1,1,1,1,1} - 3 | 1 | {0,1,1,1,1,1,1} | {0,1,1,1,1,1} - 3 | 2 | {0,1,1,1,1,1,1,2,2} | {0,1,1,1,1,1,1,2} - 3 | 2 | {0,1,1,1,1,1,1,2,2} | {0,1,1,1,1,1,1,2} - 3 | 3 | {0,1,1,1,1,1,1,2,2,3,3,3} | {0,1,1,1,1,1,1,2,2,3,3} - 3 | 3 | {0,1,1,1,1,1,1,2,2,3,3,3} | {0,1,1,1,1,1,1,2,2,3,3} - 3 | 3 | {0,1,1,1,1,1,1,2,2,3,3,3} | {0,1,1,1,1,1,1,2,2,3,3} - 3 | 4 | {0,1,1,1,1,1,1,2,2,3,3,3,4,4,4,4} | {0,1,1,1,1,1,1,2,2,3,3,3,4,4,4} - 3 | 4 | {0,1,1,1,1,1,1,2,2,3,3,3,4,4,4,4} | {0,1,1,1,1,1,1,2,2,3,3,3,4,4,4} - 3 | 4 | {0,1,1,1,1,1,1,2,2,3,3,3,4,4,4,4} | {0,1,1,1,1,1,1,2,2,3,3,3,4,4,4} - 3 | 4 | {0,1,1,1,1,1,1,2,2,3,3,3,4,4,4,4} | {0,1,1,1,1,1,1,2,2,3,3,3,4,4,4} - 3 | 5 | {0,1,1,1,1,1,1,2,2,3,3,3,4,4,4,4,5} | {0,1,1,1,1,1,1,2,2,3,3,3,4,4,4,4} - 4 | 0 | {0,0,0,0} | {0,0,0} - 4 | 0 | {0,0,0,0} | {0,0,0} - 4 | 0 | {0,0,0,0} | {0,0,0} - 4 | 0 | {0,0,0,0} | {0,0,0} - 4 | 1 | {0,0,0,0,1} | {0,0,0,0} - 4 | 2 | {0,0,0,0,1,2,2,2} | {0,0,0,0,1,2,2} - 4 | 2 | {0,0,0,0,1,2,2,2} | {0,0,0,0,1,2,2} - 4 | 2 | {0,0,0,0,1,2,2,2} | {0,0,0,0,1,2,2} - 4 | 3 | {0,0,0,0,1,2,2,2,3,3,3,3,3,3} | {0,0,0,0,1,2,2,2,3,3,3,3,3} - 4 | 3 | {0,0,0,0,1,2,2,2,3,3,3,3,3,3} | {0,0,0,0,1,2,2,2,3,3,3,3,3} - 4 | 3 | {0,0,0,0,1,2,2,2,3,3,3,3,3,3} | {0,0,0,0,1,2,2,2,3,3,3,3,3} - 4 | 3 | {0,0,0,0,1,2,2,2,3,3,3,3,3,3} | {0,0,0,0,1,2,2,2,3,3,3,3,3} - 4 | 3 | {0,0,0,0,1,2,2,2,3,3,3,3,3,3} | {0,0,0,0,1,2,2,2,3,3,3,3,3} - 4 | 3 | {0,0,0,0,1,2,2,2,3,3,3,3,3,3} | {0,0,0,0,1,2,2,2,3,3,3,3,3} - 4 | 4 | {0,0,0,0,1,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4,4} | {0,0,0,0,1,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4} - 4 | 4 | {0,0,0,0,1,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4,4} | {0,0,0,0,1,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4} - 4 | 4 | {0,0,0,0,1,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4,4} | {0,0,0,0,1,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4} - 4 | 4 | {0,0,0,0,1,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4,4} | {0,0,0,0,1,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4} - 4 | 4 | {0,0,0,0,1,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4,4} | {0,0,0,0,1,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4} - 4 | 4 | {0,0,0,0,1,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4,4} | {0,0,0,0,1,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4} - 4 | 4 | {0,0,0,0,1,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4,4} | {0,0,0,0,1,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4} - 4 | 5 | {0,0,0,0,1,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4,4,5,5} | {0,0,0,0,1,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4,4,5} - 4 | 5 | {0,0,0,0,1,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4,4,5,5} | {0,0,0,0,1,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4,4,5} - 5 | 0 | {0,0} | {0} - 5 | 0 | {0,0} | {0} - 5 | 1 | {0,0,1,1,1} | {0,0,1,1} - 5 | 1 | {0,0,1,1,1} | {0,0,1,1} - 5 | 1 | {0,0,1,1,1} | {0,0,1,1} - 5 | 2 | {0,0,1,1,1,2,2,2,2,2,2} | {0,0,1,1,1,2,2,2,2,2} - 5 | 2 | {0,0,1,1,1,2,2,2,2,2,2} | {0,0,1,1,1,2,2,2,2,2} - 5 | 2 | {0,0,1,1,1,2,2,2,2,2,2} | {0,0,1,1,1,2,2,2,2,2} - 5 | 2 | {0,0,1,1,1,2,2,2,2,2,2} | {0,0,1,1,1,2,2,2,2,2} - 5 | 2 | {0,0,1,1,1,2,2,2,2,2,2} | {0,0,1,1,1,2,2,2,2,2} - 5 | 2 | {0,0,1,1,1,2,2,2,2,2,2} | {0,0,1,1,1,2,2,2,2,2} - 5 | 3 | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3} | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3} - 5 | 3 | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3} | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3} - 5 | 3 | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3} | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3} - 5 | 3 | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3} | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3} - 5 | 3 | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3} | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3} - 5 | 3 | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3} | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3} - 5 | 3 | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3} | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3} - 5 | 3 | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3} | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3} - 5 | 3 | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3} | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3} - 5 | 4 | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4,4} | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4} - 5 | 4 | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4,4} | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4} - 5 | 4 | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4,4} | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4} - 5 | 5 | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4,4,5,5,5} | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4,4,5,5} - 5 | 5 | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4,4,5,5,5} | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4,4,5,5} - 5 | 5 | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4,4,5,5,5} | {0,0,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4,4,5,5} -(66 rows) - --- test preceding and following on RANGE window -SELECT - user_id, - value_1, - array_agg(value_1) OVER range_window, - array_agg(value_1) OVER range_window_exclude -FROM - users_table -WHERE - user_id > 2 AND user_id < 6 -WINDOW - range_window as (PARTITION BY user_id ORDER BY value_1 RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING), - range_window_exclude as (PARTITION BY user_id ORDER BY value_1 RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) -ORDER BY - user_id, value_1, 3, 4; - user_id | value_1 | array_agg | array_agg ---------------------------------------------------------------------- - 3 | 0 | {0,1,1,1,1,1,1} | {1,1,1,1,1,1} - 3 | 1 | {0,1,1,1,1,1,1,2,2} | {0,1,1,1,1,1,2,2} - 3 | 1 | {0,1,1,1,1,1,1,2,2} | {0,1,1,1,1,1,2,2} - 3 | 1 | {0,1,1,1,1,1,1,2,2} | {0,1,1,1,1,1,2,2} - 3 | 1 | {0,1,1,1,1,1,1,2,2} | {0,1,1,1,1,1,2,2} - 3 | 1 | {0,1,1,1,1,1,1,2,2} | {0,1,1,1,1,1,2,2} - 3 | 1 | {0,1,1,1,1,1,1,2,2} | {0,1,1,1,1,1,2,2} - 3 | 2 | {1,1,1,1,1,1,2,2,3,3,3} | {1,1,1,1,1,1,2,3,3,3} - 3 | 2 | {1,1,1,1,1,1,2,2,3,3,3} | {1,1,1,1,1,1,2,3,3,3} - 3 | 3 | {2,2,3,3,3,4,4,4,4} | {2,2,3,3,4,4,4,4} - 3 | 3 | {2,2,3,3,3,4,4,4,4} | {2,2,3,3,4,4,4,4} - 3 | 3 | {2,2,3,3,3,4,4,4,4} | {2,2,3,3,4,4,4,4} - 3 | 4 | {3,3,3,4,4,4,4,5} | {3,3,3,4,4,4,5} - 3 | 4 | {3,3,3,4,4,4,4,5} | {3,3,3,4,4,4,5} - 3 | 4 | {3,3,3,4,4,4,4,5} | {3,3,3,4,4,4,5} - 3 | 4 | {3,3,3,4,4,4,4,5} | {3,3,3,4,4,4,5} - 3 | 5 | {4,4,4,4,5} | {4,4,4,4} - 4 | 0 | {0,0,0,0,1} | {0,0,0,1} - 4 | 0 | {0,0,0,0,1} | {0,0,0,1} - 4 | 0 | {0,0,0,0,1} | {0,0,0,1} - 4 | 0 | {0,0,0,0,1} | {0,0,0,1} - 4 | 1 | {0,0,0,0,1,2,2,2} | {0,0,0,0,2,2,2} - 4 | 2 | {1,2,2,2,3,3,3,3,3,3} | {1,2,2,3,3,3,3,3,3} - 4 | 2 | {1,2,2,2,3,3,3,3,3,3} | {1,2,2,3,3,3,3,3,3} - 4 | 2 | {1,2,2,2,3,3,3,3,3,3} | {1,2,2,3,3,3,3,3,3} - 4 | 3 | {2,2,2,3,3,3,3,3,3,4,4,4,4,4,4,4} | {2,2,2,3,3,3,3,3,4,4,4,4,4,4,4} - 4 | 3 | {2,2,2,3,3,3,3,3,3,4,4,4,4,4,4,4} | {2,2,2,3,3,3,3,3,4,4,4,4,4,4,4} - 4 | 3 | {2,2,2,3,3,3,3,3,3,4,4,4,4,4,4,4} | {2,2,2,3,3,3,3,3,4,4,4,4,4,4,4} - 4 | 3 | {2,2,2,3,3,3,3,3,3,4,4,4,4,4,4,4} | {2,2,2,3,3,3,3,3,4,4,4,4,4,4,4} - 4 | 3 | {2,2,2,3,3,3,3,3,3,4,4,4,4,4,4,4} | {2,2,2,3,3,3,3,3,4,4,4,4,4,4,4} - 4 | 3 | {2,2,2,3,3,3,3,3,3,4,4,4,4,4,4,4} | {2,2,2,3,3,3,3,3,4,4,4,4,4,4,4} - 4 | 4 | {3,3,3,3,3,3,4,4,4,4,4,4,4,5,5} | {3,3,3,3,3,3,4,4,4,4,4,4,5,5} - 4 | 4 | {3,3,3,3,3,3,4,4,4,4,4,4,4,5,5} | {3,3,3,3,3,3,4,4,4,4,4,4,5,5} - 4 | 4 | {3,3,3,3,3,3,4,4,4,4,4,4,4,5,5} | {3,3,3,3,3,3,4,4,4,4,4,4,5,5} - 4 | 4 | {3,3,3,3,3,3,4,4,4,4,4,4,4,5,5} | {3,3,3,3,3,3,4,4,4,4,4,4,5,5} - 4 | 4 | {3,3,3,3,3,3,4,4,4,4,4,4,4,5,5} | {3,3,3,3,3,3,4,4,4,4,4,4,5,5} - 4 | 4 | {3,3,3,3,3,3,4,4,4,4,4,4,4,5,5} | {3,3,3,3,3,3,4,4,4,4,4,4,5,5} - 4 | 4 | {3,3,3,3,3,3,4,4,4,4,4,4,4,5,5} | {3,3,3,3,3,3,4,4,4,4,4,4,5,5} - 4 | 5 | {4,4,4,4,4,4,4,5,5} | {4,4,4,4,4,4,4,5} - 4 | 5 | {4,4,4,4,4,4,4,5,5} | {4,4,4,4,4,4,4,5} - 5 | 0 | {0,0,1,1,1} | {0,1,1,1} - 5 | 0 | {0,0,1,1,1} | {0,1,1,1} - 5 | 1 | {0,0,1,1,1,2,2,2,2,2,2} | {0,0,1,1,2,2,2,2,2,2} - 5 | 1 | {0,0,1,1,1,2,2,2,2,2,2} | {0,0,1,1,2,2,2,2,2,2} - 5 | 1 | {0,0,1,1,1,2,2,2,2,2,2} | {0,0,1,1,2,2,2,2,2,2} - 5 | 2 | {1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3} | {1,1,1,2,2,2,2,2,3,3,3,3,3,3,3,3,3} - 5 | 2 | {1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3} | {1,1,1,2,2,2,2,2,3,3,3,3,3,3,3,3,3} - 5 | 2 | {1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3} | {1,1,1,2,2,2,2,2,3,3,3,3,3,3,3,3,3} - 5 | 2 | {1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3} | {1,1,1,2,2,2,2,2,3,3,3,3,3,3,3,3,3} - 5 | 2 | {1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3} | {1,1,1,2,2,2,2,2,3,3,3,3,3,3,3,3,3} - 5 | 2 | {1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3} | {1,1,1,2,2,2,2,2,3,3,3,3,3,3,3,3,3} - 5 | 3 | {2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4,4} | {2,2,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4} - 5 | 3 | {2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4,4} | {2,2,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4} - 5 | 3 | {2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4,4} | {2,2,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4} - 5 | 3 | {2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4,4} | {2,2,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4} - 5 | 3 | {2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4,4} | {2,2,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4} - 5 | 3 | {2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4,4} | {2,2,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4} - 5 | 3 | {2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4,4} | {2,2,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4} - 5 | 3 | {2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4,4} | {2,2,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4} - 5 | 3 | {2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4,4} | {2,2,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4} - 5 | 4 | {3,3,3,3,3,3,3,3,3,4,4,4,5,5,5} | {3,3,3,3,3,3,3,3,3,4,4,5,5,5} - 5 | 4 | {3,3,3,3,3,3,3,3,3,4,4,4,5,5,5} | {3,3,3,3,3,3,3,3,3,4,4,5,5,5} - 5 | 4 | {3,3,3,3,3,3,3,3,3,4,4,4,5,5,5} | {3,3,3,3,3,3,3,3,3,4,4,5,5,5} - 5 | 5 | {4,4,4,5,5,5} | {4,4,4,5,5} - 5 | 5 | {4,4,4,5,5,5} | {4,4,4,5,5} - 5 | 5 | {4,4,4,5,5,5} | {4,4,4,5,5} -(66 rows) - --- test preceding and following on ROW window -SELECT - user_id, - value_1, - array_agg(value_1) OVER row_window, - array_agg(value_1) OVER row_window_exclude -FROM - users_table -WHERE - user_id > 2 and user_id < 6 -WINDOW - row_window as (PARTITION BY user_id ORDER BY value_1 ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING), - row_window_exclude as (PARTITION BY user_id ORDER BY value_1 ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) -ORDER BY - user_id, value_1, 3, 4; - user_id | value_1 | array_agg | array_agg ---------------------------------------------------------------------- - 3 | 0 | {0,1} | {1} - 3 | 1 | {0,1,1} | {0,1} - 3 | 1 | {1,1,1} | {1,1} - 3 | 1 | {1,1,1} | {1,1} - 3 | 1 | {1,1,1} | {1,1} - 3 | 1 | {1,1,1} | {1,1} - 3 | 1 | {1,1,2} | {1,2} - 3 | 2 | {1,2,2} | {1,2} - 3 | 2 | {2,2,3} | {2,3} - 3 | 3 | {2,3,3} | {2,3} - 3 | 3 | {3,3,3} | {3,3} - 3 | 3 | {3,3,4} | {3,4} - 3 | 4 | {3,4,4} | {3,4} - 3 | 4 | {4,4,4} | {4,4} - 3 | 4 | {4,4,4} | {4,4} - 3 | 4 | {4,4,5} | {4,5} - 3 | 5 | {4,5} | {4} - 4 | 0 | {0,0} | {0} - 4 | 0 | {0,0,0} | {0,0} - 4 | 0 | {0,0,0} | {0,0} - 4 | 0 | {0,0,1} | {0,1} - 4 | 1 | {0,1,2} | {0,2} - 4 | 2 | {1,2,2} | {1,2} - 4 | 2 | {2,2,2} | {2,2} - 4 | 2 | {2,2,3} | {2,3} - 4 | 3 | {2,3,3} | {2,3} - 4 | 3 | {3,3,3} | {3,3} - 4 | 3 | {3,3,3} | {3,3} - 4 | 3 | {3,3,3} | {3,3} - 4 | 3 | {3,3,3} | {3,3} - 4 | 3 | {3,3,4} | {3,4} - 4 | 4 | {3,4,4} | {3,4} - 4 | 4 | {4,4,4} | {4,4} - 4 | 4 | {4,4,4} | {4,4} - 4 | 4 | {4,4,4} | {4,4} - 4 | 4 | {4,4,4} | {4,4} - 4 | 4 | {4,4,4} | {4,4} - 4 | 4 | {4,4,5} | {4,5} - 4 | 5 | {4,5,5} | {4,5} - 4 | 5 | {5,5} | {5} - 5 | 0 | {0,0} | {0} - 5 | 0 | {0,0,1} | {0,1} - 5 | 1 | {0,1,1} | {0,1} - 5 | 1 | {1,1,1} | {1,1} - 5 | 1 | {1,1,2} | {1,2} - 5 | 2 | {1,2,2} | {1,2} - 5 | 2 | {2,2,2} | {2,2} - 5 | 2 | {2,2,2} | {2,2} - 5 | 2 | {2,2,2} | {2,2} - 5 | 2 | {2,2,2} | {2,2} - 5 | 2 | {2,2,3} | {2,3} - 5 | 3 | {2,3,3} | {2,3} - 5 | 3 | {3,3,3} | {3,3} - 5 | 3 | {3,3,3} | {3,3} - 5 | 3 | {3,3,3} | {3,3} - 5 | 3 | {3,3,3} | {3,3} - 5 | 3 | {3,3,3} | {3,3} - 5 | 3 | {3,3,3} | {3,3} - 5 | 3 | {3,3,3} | {3,3} - 5 | 3 | {3,3,4} | {3,4} - 5 | 4 | {3,4,4} | {3,4} - 5 | 4 | {4,4,4} | {4,4} - 5 | 4 | {4,4,5} | {4,5} - 5 | 5 | {4,5,5} | {4,5} - 5 | 5 | {5,5} | {5} - 5 | 5 | {5,5,5} | {5,5} -(66 rows) - --- repeat above 3 tests without grouping by distribution column -SELECT - value_2, - rank() OVER (PARTITION BY value_2 ROWS BETWEEN - UNBOUNDED PRECEDING AND CURRENT ROW), - dense_rank() OVER (PARTITION BY value_2 RANGE BETWEEN - UNBOUNDED PRECEDING AND CURRENT ROW), - CUME_DIST() OVER (PARTITION BY value_2 RANGE BETWEEN - UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING), - PERCENT_RANK() OVER (PARTITION BY value_2 ORDER BY avg(value_1) RANGE BETWEEN - UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) -FROM - users_table -GROUP BY - 1 -ORDER BY - 4 DESC,3 DESC,2 DESC ,1 DESC; - value_2 | rank | dense_rank | cume_dist | percent_rank ---------------------------------------------------------------------- - 5 | 1 | 1 | 1 | 0 - 4 | 1 | 1 | 1 | 0 - 3 | 1 | 1 | 1 | 0 - 2 | 1 | 1 | 1 | 0 - 1 | 1 | 1 | 1 | 0 - 0 | 1 | 1 | 1 | 0 -(6 rows) - --- test exclude supported -SELECT - value_2, - value_1, - array_agg(value_1) OVER (PARTITION BY value_2 ORDER BY value_1 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), - array_agg(value_1) OVER (PARTITION BY value_2 ORDER BY value_1 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE CURRENT ROW) -FROM - users_table -WHERE - value_2 > 2 AND value_2 < 6 -ORDER BY - value_2, value_1, 3, 4; - value_2 | value_1 | array_agg | array_agg ---------------------------------------------------------------------- - 3 | 0 | {0,0,0} | {0,0} - 3 | 0 | {0,0,0} | {0,0} - 3 | 0 | {0,0,0} | {0,0} - 3 | 1 | {0,0,0,1,1,1,1} | {0,0,0,1,1,1} - 3 | 1 | {0,0,0,1,1,1,1} | {0,0,0,1,1,1} - 3 | 1 | {0,0,0,1,1,1,1} | {0,0,0,1,1,1} - 3 | 1 | {0,0,0,1,1,1,1} | {0,0,0,1,1,1} - 3 | 2 | {0,0,0,1,1,1,1,2,2} | {0,0,0,1,1,1,1,2} - 3 | 2 | {0,0,0,1,1,1,1,2,2} | {0,0,0,1,1,1,1,2} - 3 | 3 | {0,0,0,1,1,1,1,2,2,3,3} | {0,0,0,1,1,1,1,2,2,3} - 3 | 3 | {0,0,0,1,1,1,1,2,2,3,3} | {0,0,0,1,1,1,1,2,2,3} - 3 | 4 | {0,0,0,1,1,1,1,2,2,3,3,4,4,4,4,4} | {0,0,0,1,1,1,1,2,2,3,3,4,4,4,4} - 3 | 4 | {0,0,0,1,1,1,1,2,2,3,3,4,4,4,4,4} | {0,0,0,1,1,1,1,2,2,3,3,4,4,4,4} - 3 | 4 | {0,0,0,1,1,1,1,2,2,3,3,4,4,4,4,4} | {0,0,0,1,1,1,1,2,2,3,3,4,4,4,4} - 3 | 4 | {0,0,0,1,1,1,1,2,2,3,3,4,4,4,4,4} | {0,0,0,1,1,1,1,2,2,3,3,4,4,4,4} - 3 | 4 | {0,0,0,1,1,1,1,2,2,3,3,4,4,4,4,4} | {0,0,0,1,1,1,1,2,2,3,3,4,4,4,4} - 3 | 5 | {0,0,0,1,1,1,1,2,2,3,3,4,4,4,4,4,5} | {0,0,0,1,1,1,1,2,2,3,3,4,4,4,4,4} - 4 | 0 | {0,0} | {0} - 4 | 0 | {0,0} | {0} - 4 | 1 | {0,0,1,1} | {0,0,1} - 4 | 1 | {0,0,1,1} | {0,0,1} - 4 | 2 | {0,0,1,1,2,2,2} | {0,0,1,1,2,2} - 4 | 2 | {0,0,1,1,2,2,2} | {0,0,1,1,2,2} - 4 | 2 | {0,0,1,1,2,2,2} | {0,0,1,1,2,2} - 4 | 3 | {0,0,1,1,2,2,2,3,3,3,3,3,3,3} | {0,0,1,1,2,2,2,3,3,3,3,3,3} - 4 | 3 | {0,0,1,1,2,2,2,3,3,3,3,3,3,3} | {0,0,1,1,2,2,2,3,3,3,3,3,3} - 4 | 3 | {0,0,1,1,2,2,2,3,3,3,3,3,3,3} | {0,0,1,1,2,2,2,3,3,3,3,3,3} - 4 | 3 | {0,0,1,1,2,2,2,3,3,3,3,3,3,3} | {0,0,1,1,2,2,2,3,3,3,3,3,3} - 4 | 3 | {0,0,1,1,2,2,2,3,3,3,3,3,3,3} | {0,0,1,1,2,2,2,3,3,3,3,3,3} - 4 | 3 | {0,0,1,1,2,2,2,3,3,3,3,3,3,3} | {0,0,1,1,2,2,2,3,3,3,3,3,3} - 4 | 3 | {0,0,1,1,2,2,2,3,3,3,3,3,3,3} | {0,0,1,1,2,2,2,3,3,3,3,3,3} - 4 | 4 | {0,0,1,1,2,2,2,3,3,3,3,3,3,3,4,4,4,4} | {0,0,1,1,2,2,2,3,3,3,3,3,3,3,4,4,4} - 4 | 4 | {0,0,1,1,2,2,2,3,3,3,3,3,3,3,4,4,4,4} | {0,0,1,1,2,2,2,3,3,3,3,3,3,3,4,4,4} - 4 | 4 | {0,0,1,1,2,2,2,3,3,3,3,3,3,3,4,4,4,4} | {0,0,1,1,2,2,2,3,3,3,3,3,3,3,4,4,4} - 4 | 4 | {0,0,1,1,2,2,2,3,3,3,3,3,3,3,4,4,4,4} | {0,0,1,1,2,2,2,3,3,3,3,3,3,3,4,4,4} - 4 | 5 | {0,0,1,1,2,2,2,3,3,3,3,3,3,3,4,4,4,4,5,5} | {0,0,1,1,2,2,2,3,3,3,3,3,3,3,4,4,4,4,5} - 4 | 5 | {0,0,1,1,2,2,2,3,3,3,3,3,3,3,4,4,4,4,5,5} | {0,0,1,1,2,2,2,3,3,3,3,3,3,3,4,4,4,4,5} - 5 | 0 | {0,0} | {0} - 5 | 0 | {0,0} | {0} - 5 | 1 | {0,0,1} | {0,0} - 5 | 2 | {0,0,1,2,2} | {0,0,1,2} - 5 | 2 | {0,0,1,2,2} | {0,0,1,2} - 5 | 3 | {0,0,1,2,2,3,3,3,3} | {0,0,1,2,2,3,3,3} - 5 | 3 | {0,0,1,2,2,3,3,3,3} | {0,0,1,2,2,3,3,3} - 5 | 3 | {0,0,1,2,2,3,3,3,3} | {0,0,1,2,2,3,3,3} - 5 | 3 | {0,0,1,2,2,3,3,3,3} | {0,0,1,2,2,3,3,3} - 5 | 4 | {0,0,1,2,2,3,3,3,3,4,4} | {0,0,1,2,2,3,3,3,3,4} - 5 | 4 | {0,0,1,2,2,3,3,3,3,4,4} | {0,0,1,2,2,3,3,3,3,4} - 5 | 5 | {0,0,1,2,2,3,3,3,3,4,4,5,5} | {0,0,1,2,2,3,3,3,3,4,4,5} - 5 | 5 | {0,0,1,2,2,3,3,3,3,4,4,5,5} | {0,0,1,2,2,3,3,3,3,4,4,5} -(50 rows) - --- test preceding and following on RANGE window -SELECT - value_2, - value_1, - array_agg(value_1) OVER range_window, - array_agg(value_1) OVER range_window_exclude -FROM - users_table -WHERE - value_2 > 2 AND value_2 < 6 -WINDOW - range_window as (PARTITION BY value_2 ORDER BY value_1 RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING), - range_window_exclude as (PARTITION BY value_2 ORDER BY value_1 RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) -ORDER BY - value_2, value_1, 3, 4; - value_2 | value_1 | array_agg | array_agg ---------------------------------------------------------------------- - 3 | 0 | {0,0,0,1,1,1,1} | {0,0,1,1,1,1} - 3 | 0 | {0,0,0,1,1,1,1} | {0,0,1,1,1,1} - 3 | 0 | {0,0,0,1,1,1,1} | {0,0,1,1,1,1} - 3 | 1 | {0,0,0,1,1,1,1,2,2} | {0,0,0,1,1,1,2,2} - 3 | 1 | {0,0,0,1,1,1,1,2,2} | {0,0,0,1,1,1,2,2} - 3 | 1 | {0,0,0,1,1,1,1,2,2} | {0,0,0,1,1,1,2,2} - 3 | 1 | {0,0,0,1,1,1,1,2,2} | {0,0,0,1,1,1,2,2} - 3 | 2 | {1,1,1,1,2,2,3,3} | {1,1,1,1,2,3,3} - 3 | 2 | {1,1,1,1,2,2,3,3} | {1,1,1,1,2,3,3} - 3 | 3 | {2,2,3,3,4,4,4,4,4} | {2,2,3,4,4,4,4,4} - 3 | 3 | {2,2,3,3,4,4,4,4,4} | {2,2,3,4,4,4,4,4} - 3 | 4 | {3,3,4,4,4,4,4,5} | {3,3,4,4,4,4,5} - 3 | 4 | {3,3,4,4,4,4,4,5} | {3,3,4,4,4,4,5} - 3 | 4 | {3,3,4,4,4,4,4,5} | {3,3,4,4,4,4,5} - 3 | 4 | {3,3,4,4,4,4,4,5} | {3,3,4,4,4,4,5} - 3 | 4 | {3,3,4,4,4,4,4,5} | {3,3,4,4,4,4,5} - 3 | 5 | {4,4,4,4,4,5} | {4,4,4,4,4} - 4 | 0 | {0,0,1,1} | {0,1,1} - 4 | 0 | {0,0,1,1} | {0,1,1} - 4 | 1 | {0,0,1,1,2,2,2} | {0,0,1,2,2,2} - 4 | 1 | {0,0,1,1,2,2,2} | {0,0,1,2,2,2} - 4 | 2 | {1,1,2,2,2,3,3,3,3,3,3,3} | {1,1,2,2,3,3,3,3,3,3,3} - 4 | 2 | {1,1,2,2,2,3,3,3,3,3,3,3} | {1,1,2,2,3,3,3,3,3,3,3} - 4 | 2 | {1,1,2,2,2,3,3,3,3,3,3,3} | {1,1,2,2,3,3,3,3,3,3,3} - 4 | 3 | {2,2,2,3,3,3,3,3,3,3,4,4,4,4} | {2,2,2,3,3,3,3,3,3,4,4,4,4} - 4 | 3 | {2,2,2,3,3,3,3,3,3,3,4,4,4,4} | {2,2,2,3,3,3,3,3,3,4,4,4,4} - 4 | 3 | {2,2,2,3,3,3,3,3,3,3,4,4,4,4} | {2,2,2,3,3,3,3,3,3,4,4,4,4} - 4 | 3 | {2,2,2,3,3,3,3,3,3,3,4,4,4,4} | {2,2,2,3,3,3,3,3,3,4,4,4,4} - 4 | 3 | {2,2,2,3,3,3,3,3,3,3,4,4,4,4} | {2,2,2,3,3,3,3,3,3,4,4,4,4} - 4 | 3 | {2,2,2,3,3,3,3,3,3,3,4,4,4,4} | {2,2,2,3,3,3,3,3,3,4,4,4,4} - 4 | 3 | {2,2,2,3,3,3,3,3,3,3,4,4,4,4} | {2,2,2,3,3,3,3,3,3,4,4,4,4} - 4 | 4 | {3,3,3,3,3,3,3,4,4,4,4,5,5} | {3,3,3,3,3,3,3,4,4,4,5,5} - 4 | 4 | {3,3,3,3,3,3,3,4,4,4,4,5,5} | {3,3,3,3,3,3,3,4,4,4,5,5} - 4 | 4 | {3,3,3,3,3,3,3,4,4,4,4,5,5} | {3,3,3,3,3,3,3,4,4,4,5,5} - 4 | 4 | {3,3,3,3,3,3,3,4,4,4,4,5,5} | {3,3,3,3,3,3,3,4,4,4,5,5} - 4 | 5 | {4,4,4,4,5,5} | {4,4,4,4,5} - 4 | 5 | {4,4,4,4,5,5} | {4,4,4,4,5} - 5 | 0 | {0,0,1} | {0,1} - 5 | 0 | {0,0,1} | {0,1} - 5 | 1 | {0,0,1,2,2} | {0,0,2,2} - 5 | 2 | {1,2,2,3,3,3,3} | {1,2,3,3,3,3} - 5 | 2 | {1,2,2,3,3,3,3} | {1,2,3,3,3,3} - 5 | 3 | {2,2,3,3,3,3,4,4} | {2,2,3,3,3,4,4} - 5 | 3 | {2,2,3,3,3,3,4,4} | {2,2,3,3,3,4,4} - 5 | 3 | {2,2,3,3,3,3,4,4} | {2,2,3,3,3,4,4} - 5 | 3 | {2,2,3,3,3,3,4,4} | {2,2,3,3,3,4,4} - 5 | 4 | {3,3,3,3,4,4,5,5} | {3,3,3,3,4,5,5} - 5 | 4 | {3,3,3,3,4,4,5,5} | {3,3,3,3,4,5,5} - 5 | 5 | {4,4,5,5} | {4,4,5} - 5 | 5 | {4,4,5,5} | {4,4,5} -(50 rows) - --- test preceding and following on ROW window -SELECT - value_2, - value_1, - array_agg(value_1) OVER row_window, - array_agg(value_1) OVER row_window_exclude -FROM - users_table -WHERE - value_2 > 2 and value_2 < 6 -WINDOW - row_window as (PARTITION BY value_2 ORDER BY value_1 ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING), - row_window_exclude as (PARTITION BY value_2 ORDER BY value_1 ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) -ORDER BY - value_2, value_1, 3, 4; - value_2 | value_1 | array_agg | array_agg ---------------------------------------------------------------------- - 3 | 0 | {0,0} | {0} - 3 | 0 | {0,0,0} | {0,0} - 3 | 0 | {0,0,1} | {0,1} - 3 | 1 | {0,1,1} | {0,1} - 3 | 1 | {1,1,1} | {1,1} - 3 | 1 | {1,1,1} | {1,1} - 3 | 1 | {1,1,2} | {1,2} - 3 | 2 | {1,2,2} | {1,2} - 3 | 2 | {2,2,3} | {2,3} - 3 | 3 | {2,3,3} | {2,3} - 3 | 3 | {3,3,4} | {3,4} - 3 | 4 | {3,4,4} | {3,4} - 3 | 4 | {4,4,4} | {4,4} - 3 | 4 | {4,4,4} | {4,4} - 3 | 4 | {4,4,4} | {4,4} - 3 | 4 | {4,4,5} | {4,5} - 3 | 5 | {4,5} | {4} - 4 | 0 | {0,0} | {0} - 4 | 0 | {0,0,1} | {0,1} - 4 | 1 | {0,1,1} | {0,1} - 4 | 1 | {1,1,2} | {1,2} - 4 | 2 | {1,2,2} | {1,2} - 4 | 2 | {2,2,2} | {2,2} - 4 | 2 | {2,2,3} | {2,3} - 4 | 3 | {2,3,3} | {2,3} - 4 | 3 | {3,3,3} | {3,3} - 4 | 3 | {3,3,3} | {3,3} - 4 | 3 | {3,3,3} | {3,3} - 4 | 3 | {3,3,3} | {3,3} - 4 | 3 | {3,3,3} | {3,3} - 4 | 3 | {3,3,4} | {3,4} - 4 | 4 | {3,4,4} | {3,4} - 4 | 4 | {4,4,4} | {4,4} - 4 | 4 | {4,4,4} | {4,4} - 4 | 4 | {4,4,5} | {4,5} - 4 | 5 | {4,5,5} | {4,5} - 4 | 5 | {5,5} | {5} - 5 | 0 | {0,0} | {0} - 5 | 0 | {0,0,1} | {0,1} - 5 | 1 | {0,1,2} | {0,2} - 5 | 2 | {1,2,2} | {1,2} - 5 | 2 | {2,2,3} | {2,3} - 5 | 3 | {2,3,3} | {2,3} - 5 | 3 | {3,3,3} | {3,3} - 5 | 3 | {3,3,3} | {3,3} - 5 | 3 | {3,3,4} | {3,4} - 5 | 4 | {3,4,4} | {3,4} - 5 | 4 | {4,4,5} | {4,5} - 5 | 5 | {4,5,5} | {4,5} - 5 | 5 | {5,5} | {5} -(50 rows) - --- some tests with GROUP BY, HAVING and LIMIT -SELECT - user_id, sum(event_type) OVER my_win , event_type -FROM - events_table -GROUP BY - user_id, event_type -HAVING count(*) > 2 - WINDOW my_win AS (PARTITION BY user_id, max(event_type) ORDER BY count(*) DESC) -ORDER BY - 2 DESC, 3 DESC, 1 DESC -LIMIT - 5; - user_id | sum | event_type ---------------------------------------------------------------------- - 4 | 4 | 4 - 3 | 4 | 4 - 2 | 4 | 4 - 1 | 4 | 4 - 5 | 3 | 3 -(5 rows) - --- test PARTITION BY avg(...) ORDER BY avg(...) -SELECT - value_1, - avg(value_3), - dense_rank() OVER (PARTITION BY avg(value_3) ORDER BY avg(value_2)) -FROM - users_table -GROUP BY - 1 -ORDER BY - 1; - value_1 | avg | dense_rank ---------------------------------------------------------------------- - 0 | 3.08333333333333 | 1 - 1 | 2.93333333333333 | 1 - 2 | 2.22222222222222 | 1 - 3 | 2.73076923076923 | 1 - 4 | 2.9047619047619 | 1 - 5 | 2.22222222222222 | 2 -(6 rows) - --- Group by has more columns than partition by -SELECT - DISTINCT user_id, SUM(value_2) OVER (PARTITION BY user_id) -FROM - users_table -GROUP BY - user_id, value_1, value_2 -HAVING count(*) > 2 -ORDER BY - 2 DESC, 1 -LIMIT - 10; - user_id | sum ---------------------------------------------------------------------- - 5 | 3 - 4 | 2 -(2 rows) - -SELECT - DISTINCT ON (user_id) user_id, SUM(value_2) OVER (PARTITION BY user_id) -FROM - users_table -GROUP BY - user_id, value_1, value_2 -HAVING count(*) > 2 -ORDER BY - 1, 2 DESC -LIMIT - 10; - user_id | sum ---------------------------------------------------------------------- - 4 | 2 - 5 | 3 -(2 rows) - -SELECT - DISTINCT ON (SUM(value_1) OVER (PARTITION BY user_id)) user_id, SUM(value_2) OVER (PARTITION BY user_id) -FROM - users_table -GROUP BY - user_id, value_1, value_2 -HAVING count(*) > 2 -ORDER BY - (SUM(value_1) OVER (PARTITION BY user_id)) , 2 DESC, 1 -LIMIT - 10; - user_id | sum ---------------------------------------------------------------------- - 5 | 3 - 4 | 2 -(2 rows) - --- not a meaningful query, with interesting syntax -SELECT - user_id, - AVG(avg(value_1)) OVER (PARTITION BY user_id, max(user_id), MIN(value_2)), - AVG(avg(user_id)) OVER (PARTITION BY user_id, min(user_id), AVG(value_1)) -FROM - users_table -GROUP BY - 1 -ORDER BY - 3 DESC, 2 DESC, 1 DESC; - user_id | avg | avg ---------------------------------------------------------------------- - 6 | 2.1000000000000000 | 6.0000000000000000 - 5 | 2.6538461538461538 | 5.0000000000000000 - 4 | 2.7391304347826087 | 4.0000000000000000 - 3 | 2.3529411764705882 | 3.0000000000000000 - 2 | 2.3333333333333333 | 2.0000000000000000 - 1 | 3.2857142857142857 | 1.00000000000000000000 -(6 rows) - -SELECT coordinator_plan($Q$ -EXPLAIN (COSTS FALSE) -SELECT - user_id, - AVG(avg(value_1)) OVER (PARTITION BY user_id, max(user_id), MIN(value_2)), - AVG(avg(user_id)) OVER (PARTITION BY user_id, min(user_id), AVG(value_1)) -FROM - users_table -GROUP BY - 1 -ORDER BY - 3 DESC, 2 DESC, 1 DESC; -$Q$); - coordinator_plan ---------------------------------------------------------------------- - Sort - Sort Key: remote_scan.avg_1 DESC, remote_scan.avg DESC, remote_scan.user_id DESC - -> Custom Scan (Citus Adaptive) - Task Count: 4 -(4 rows) - -SELECT - value_2, - AVG(avg(value_1)) OVER (PARTITION BY value_2, max(value_2), MIN(value_2)), - AVG(avg(value_2)) OVER (PARTITION BY value_2, min(value_2), AVG(value_1)) -FROM - users_table -GROUP BY - 1 -ORDER BY - 3 DESC, 2 DESC, 1 DESC; - value_2 | avg | avg ---------------------------------------------------------------------- - 5 | 2.6923076923076923 | 5.0000000000000000 - 4 | 2.7500000000000000 | 4.0000000000000000 - 3 | 2.2941176470588235 | 3.0000000000000000 - 2 | 2.7619047619047619 | 2.0000000000000000 - 1 | 2.4285714285714286 | 1.00000000000000000000 - 0 | 2.2222222222222222 | 0.00000000000000000000 -(6 rows) - -SELECT - value_2, user_id, - AVG(avg(value_1)) OVER (PARTITION BY value_2, max(value_2), MIN(value_2)), - AVG(avg(value_2)) OVER (PARTITION BY user_id, min(value_2), AVG(value_1)) -FROM - users_table -GROUP BY - 1, 2 -ORDER BY - 3 DESC, 2 DESC, 1 DESC; - value_2 | user_id | avg | avg ---------------------------------------------------------------------- - 5 | 5 | 2.6666666666666667 | 5.0000000000000000 - 5 | 4 | 2.6666666666666667 | 5.0000000000000000 - 5 | 3 | 2.6666666666666667 | 5.0000000000000000 - 5 | 2 | 2.6666666666666667 | 5.0000000000000000 - 2 | 6 | 2.54583333333333333333 | 2.0000000000000000 - 2 | 5 | 2.54583333333333333333 | 2.0000000000000000 - 2 | 4 | 2.54583333333333333333 | 2.0000000000000000 - 2 | 3 | 2.54583333333333333333 | 2.0000000000000000 - 2 | 2 | 2.54583333333333333333 | 2.0000000000000000 - 2 | 1 | 2.54583333333333333333 | 2.0000000000000000 - 0 | 6 | 2.50000000000000000000 | 0.00000000000000000000 - 0 | 5 | 2.50000000000000000000 | 0.00000000000000000000 - 0 | 4 | 2.50000000000000000000 | 0.00000000000000000000 - 0 | 2 | 2.50000000000000000000 | 0.00000000000000000000 - 0 | 1 | 2.50000000000000000000 | 0.00000000000000000000 - 4 | 6 | 2.45555555555555555000 | 4.0000000000000000 - 4 | 5 | 2.45555555555555555000 | 4.0000000000000000 - 4 | 4 | 2.45555555555555555000 | 4.0000000000000000 - 4 | 3 | 2.45555555555555555000 | 4.0000000000000000 - 4 | 2 | 2.45555555555555555000 | 4.0000000000000000 - 4 | 1 | 2.45555555555555555000 | 4.0000000000000000 - 3 | 6 | 2.3500000000000000 | 3.0000000000000000 - 3 | 5 | 2.3500000000000000 | 3.0000000000000000 - 3 | 4 | 2.3500000000000000 | 3.0000000000000000 - 3 | 3 | 2.3500000000000000 | 3.0000000000000000 - 3 | 2 | 2.3500000000000000 | 3.0000000000000000 - 3 | 1 | 2.3500000000000000 | 3.0000000000000000 - 1 | 6 | 1.90666666666666666000 | 1.00000000000000000000 - 1 | 5 | 1.90666666666666666000 | 1.00000000000000000000 - 1 | 4 | 1.90666666666666666000 | 1.00000000000000000000 - 1 | 3 | 1.90666666666666666000 | 1.00000000000000000000 - 1 | 2 | 1.90666666666666666000 | 1.00000000000000000000 -(32 rows) - -SELECT user_id, sum(avg(user_id)) OVER () -FROM users_table -GROUP BY user_id -ORDER BY 1 -LIMIT 10; - user_id | sum ---------------------------------------------------------------------- - 1 | 21.00000000000000000000 - 2 | 21.00000000000000000000 - 3 | 21.00000000000000000000 - 4 | 21.00000000000000000000 - 5 | 21.00000000000000000000 - 6 | 21.00000000000000000000 -(6 rows) - -SELECT - user_id, - 1 + sum(value_1), - 1 + AVG(value_2) OVER (partition by user_id) -FROM - users_table -GROUP BY - user_id, value_2 -ORDER BY - user_id, value_2; - user_id | ?column? | ?column? ---------------------------------------------------------------------- - 1 | 5 | 3.2500000000000000 - 1 | 4 | 3.2500000000000000 - 1 | 6 | 3.2500000000000000 - 1 | 12 | 3.2500000000000000 - 2 | 3 | 3.5000000000000000 - 2 | 5 | 3.5000000000000000 - 2 | 13 | 3.5000000000000000 - 2 | 6 | 3.5000000000000000 - 2 | 17 | 3.5000000000000000 - 2 | 4 | 3.5000000000000000 - 3 | 3 | 4.0000000000000000 - 3 | 13 | 4.0000000000000000 - 3 | 10 | 4.0000000000000000 - 3 | 2 | 4.0000000000000000 - 3 | 17 | 4.0000000000000000 - 4 | 4 | 3.5000000000000000 - 4 | 28 | 3.5000000000000000 - 4 | 1 | 3.5000000000000000 - 4 | 11 | 3.5000000000000000 - 4 | 17 | 3.5000000000000000 - 4 | 8 | 3.5000000000000000 - 5 | 7 | 3.5000000000000000 - 5 | 17 | 3.5000000000000000 - 5 | 24 | 3.5000000000000000 - 5 | 9 | 3.5000000000000000 - 5 | 8 | 3.5000000000000000 - 5 | 10 | 3.5000000000000000 - 6 | 6 | 3.0000000000000000 - 6 | 3 | 3.0000000000000000 - 6 | 9 | 3.0000000000000000 - 6 | 3 | 3.0000000000000000 - 6 | 5 | 3.0000000000000000 -(32 rows) - -SELECT - user_id, - 1 + sum(value_1), - 1 + AVG(value_2) OVER (partition by user_id) -FROM - users_table -GROUP BY - user_id, value_2 -ORDER BY - 2 DESC, 1 -LIMIT 5; - user_id | ?column? | ?column? ---------------------------------------------------------------------- - 4 | 28 | 3.5000000000000000 - 5 | 24 | 3.5000000000000000 - 2 | 17 | 3.5000000000000000 - 3 | 17 | 4.0000000000000000 - 4 | 17 | 3.5000000000000000 -(5 rows) - --- rank and ordering in the reverse order -SELECT - user_id, - avg(value_1), - RANK() OVER (partition by user_id order by value_2) -FROM - users_table -GROUP BY user_id, value_2 -ORDER BY user_id, value_2 DESC; - user_id | avg | rank ---------------------------------------------------------------------- - 1 | 3.6666666666666667 | 4 - 1 | 2.5000000000000000 | 3 - 1 | 3.0000000000000000 | 2 - 1 | 4.0000000000000000 | 1 - 2 | 1.5000000000000000 | 6 - 2 | 3.2000000000000000 | 5 - 2 | 1.6666666666666667 | 4 - 2 | 3.0000000000000000 | 3 - 2 | 1.3333333333333333 | 2 - 2 | 2.0000000000000000 | 1 - 3 | 2.6666666666666667 | 5 - 3 | 1.00000000000000000000 | 4 - 3 | 3.0000000000000000 | 3 - 3 | 2.4000000000000000 | 2 - 3 | 1.00000000000000000000 | 1 - 4 | 3.5000000000000000 | 6 - 4 | 3.2000000000000000 | 5 - 4 | 3.3333333333333333 | 4 - 4 | 0.00000000000000000000 | 3 - 4 | 3.0000000000000000 | 2 - 4 | 1.00000000000000000000 | 1 - 5 | 3.0000000000000000 | 6 - 5 | 2.3333333333333333 | 5 - 5 | 1.6000000000000000 | 4 - 5 | 2.8750000000000000 | 3 - 5 | 3.2000000000000000 | 2 - 5 | 3.0000000000000000 | 1 - 6 | 1.3333333333333333 | 5 - 6 | 2.0000000000000000 | 4 - 6 | 4.0000000000000000 | 3 - 6 | 1.00000000000000000000 | 2 - 6 | 2.5000000000000000 | 1 -(32 rows) - --- order by in the window function is same as avg(value_1) DESC -SELECT - user_id, - avg(value_1), - RANK() OVER (partition by user_id order by 1 / (1 + avg(value_1))) -FROM - users_table -GROUP BY user_id, value_2 -ORDER BY user_id, avg(value_1) DESC; - user_id | avg | rank ---------------------------------------------------------------------- - 1 | 4.0000000000000000 | 1 - 1 | 3.6666666666666667 | 2 - 1 | 3.0000000000000000 | 3 - 1 | 2.5000000000000000 | 4 - 2 | 3.2000000000000000 | 1 - 2 | 3.0000000000000000 | 2 - 2 | 2.0000000000000000 | 3 - 2 | 1.6666666666666667 | 4 - 2 | 1.5000000000000000 | 5 - 2 | 1.3333333333333333 | 6 - 3 | 3.0000000000000000 | 1 - 3 | 2.6666666666666667 | 2 - 3 | 2.4000000000000000 | 3 - 3 | 1.00000000000000000000 | 4 - 3 | 1.00000000000000000000 | 4 - 4 | 3.5000000000000000 | 1 - 4 | 3.3333333333333333 | 2 - 4 | 3.2000000000000000 | 3 - 4 | 3.0000000000000000 | 4 - 4 | 1.00000000000000000000 | 5 - 4 | 0.00000000000000000000 | 6 - 5 | 3.2000000000000000 | 1 - 5 | 3.0000000000000000 | 2 - 5 | 3.0000000000000000 | 2 - 5 | 2.8750000000000000 | 4 - 5 | 2.3333333333333333 | 5 - 5 | 1.6000000000000000 | 6 - 6 | 4.0000000000000000 | 1 - 6 | 2.5000000000000000 | 2 - 6 | 2.0000000000000000 | 3 - 6 | 1.3333333333333333 | 4 - 6 | 1.00000000000000000000 | 5 -(32 rows) - -EXPLAIN (COSTS FALSE) -SELECT - user_id, - avg(value_1), - RANK() OVER (partition by user_id order by 1 / (1 + avg(value_1))) -FROM - users_table -GROUP BY user_id, value_2 -ORDER BY user_id, avg(value_1) DESC; - QUERY PLAN ---------------------------------------------------------------------- - Sort - Sort Key: remote_scan.user_id, remote_scan.avg DESC - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> WindowAgg - -> Sort - Sort Key: users_table.user_id, (('1'::numeric / ('1'::numeric + avg(users_table.value_1)))) - -> HashAggregate - Group Key: users_table.user_id, users_table.value_2 - -> Seq Scan on users_table_1400256 users_table -(13 rows) - --- order by in the window function is same as avg(value_1) DESC -SELECT - user_id, - avg(value_1), - RANK() OVER (partition by user_id order by 1 / (1 + avg(value_1))) -FROM - users_table -GROUP BY user_id, value_2 -ORDER BY user_id, avg(value_1) DESC; - user_id | avg | rank ---------------------------------------------------------------------- - 1 | 4.0000000000000000 | 1 - 1 | 3.6666666666666667 | 2 - 1 | 3.0000000000000000 | 3 - 1 | 2.5000000000000000 | 4 - 2 | 3.2000000000000000 | 1 - 2 | 3.0000000000000000 | 2 - 2 | 2.0000000000000000 | 3 - 2 | 1.6666666666666667 | 4 - 2 | 1.5000000000000000 | 5 - 2 | 1.3333333333333333 | 6 - 3 | 3.0000000000000000 | 1 - 3 | 2.6666666666666667 | 2 - 3 | 2.4000000000000000 | 3 - 3 | 1.00000000000000000000 | 4 - 3 | 1.00000000000000000000 | 4 - 4 | 3.5000000000000000 | 1 - 4 | 3.3333333333333333 | 2 - 4 | 3.2000000000000000 | 3 - 4 | 3.0000000000000000 | 4 - 4 | 1.00000000000000000000 | 5 - 4 | 0.00000000000000000000 | 6 - 5 | 3.2000000000000000 | 1 - 5 | 3.0000000000000000 | 2 - 5 | 3.0000000000000000 | 2 - 5 | 2.8750000000000000 | 4 - 5 | 2.3333333333333333 | 5 - 5 | 1.6000000000000000 | 6 - 6 | 4.0000000000000000 | 1 - 6 | 2.5000000000000000 | 2 - 6 | 2.0000000000000000 | 3 - 6 | 1.3333333333333333 | 4 - 6 | 1.00000000000000000000 | 5 -(32 rows) - --- limit is not pushed down to worker !! -EXPLAIN (COSTS FALSE) -SELECT - user_id, - avg(value_1), - RANK() OVER (partition by user_id order by 1 / (1 + avg(value_1))) -FROM - users_table -GROUP BY user_id, value_2 -ORDER BY user_id, avg(value_1) DESC -LIMIT 5; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Sort - Sort Key: remote_scan.user_id, remote_scan.avg DESC - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Limit - -> Incremental Sort - Sort Key: users_table.user_id, (avg(users_table.value_1)) DESC - Presorted Key: users_table.user_id - -> WindowAgg - -> Sort - Sort Key: users_table.user_id, (('1'::numeric / ('1'::numeric + avg(users_table.value_1)))) - -> HashAggregate - Group Key: users_table.user_id, users_table.value_2 - -> Seq Scan on users_table_1400256 users_table -(18 rows) - -EXPLAIN (COSTS FALSE) -SELECT - user_id, - avg(value_1), - RANK() OVER (partition by user_id order by 1 / (1 + avg(value_1))) -FROM - users_table -GROUP BY user_id, value_2 -ORDER BY user_id, avg(value_1) DESC -LIMIT 5; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Sort - Sort Key: remote_scan.user_id, remote_scan.avg DESC - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Limit - -> Incremental Sort - Sort Key: users_table.user_id, (avg(users_table.value_1)) DESC - Presorted Key: users_table.user_id - -> WindowAgg - -> Sort - Sort Key: users_table.user_id, (('1'::numeric / ('1'::numeric + avg(users_table.value_1)))) - -> HashAggregate - Group Key: users_table.user_id, users_table.value_2 - -> Seq Scan on users_table_1400256 users_table -(18 rows) - -EXPLAIN (COSTS FALSE) -SELECT - user_id, - avg(value_1), - RANK() OVER (partition by user_id order by 1 / (1 + sum(value_2))) -FROM - users_table -GROUP BY user_id, value_2 -ORDER BY user_id, avg(value_1) DESC -LIMIT 5; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Sort - Sort Key: remote_scan.user_id, remote_scan.avg DESC - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Limit - -> Incremental Sort - Sort Key: users_table.user_id, (avg(users_table.value_1)) DESC - Presorted Key: users_table.user_id - -> WindowAgg - -> Sort - Sort Key: users_table.user_id, ((1 / (1 + sum(users_table.value_2)))) - -> HashAggregate - Group Key: users_table.user_id, users_table.value_2 - -> Seq Scan on users_table_1400256 users_table -(18 rows) - -EXPLAIN (COSTS FALSE) -SELECT - user_id, - avg(value_1), - RANK() OVER (partition by user_id order by sum(value_2)) -FROM - users_table -GROUP BY user_id, value_2 -ORDER BY user_id, avg(value_1) DESC -LIMIT 5; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Sort - Sort Key: remote_scan.user_id, remote_scan.avg DESC - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Limit - -> Incremental Sort - Sort Key: users_table.user_id, (avg(users_table.value_1)) DESC - Presorted Key: users_table.user_id - -> WindowAgg - -> Sort - Sort Key: users_table.user_id, (sum(users_table.value_2)) - -> HashAggregate - Group Key: users_table.user_id, users_table.value_2 - -> Seq Scan on users_table_1400256 users_table -(18 rows) - --- Grouping can be pushed down with aggregates even when window function can't -EXPLAIN (COSTS FALSE) -SELECT user_id, count(value_1), stddev(value_1), count(user_id) OVER (PARTITION BY random()) -FROM users_table GROUP BY user_id HAVING avg(value_1) > 2 LIMIT 1; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> WindowAgg - -> Sort - Sort Key: remote_scan.worker_column_5 - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> HashAggregate - Group Key: user_id - Filter: (avg(value_1) > '2'::numeric) - -> Seq Scan on users_table_1400256 users_table -(13 rows) - --- Window function with inlined CTE -WITH cte as ( - SELECT uref.id user_id, events_table.value_2, count(*) c - FROM events_table - JOIN users_ref_test_table uref ON uref.id = events_table.user_id - GROUP BY 1, 2 -) -SELECT DISTINCT cte.value_2, cte.c, sum(cte.value_2) OVER (PARTITION BY cte.c) -FROM cte JOIN events_table et ON et.value_2 = cte.value_2 and et.value_2 = cte.c -ORDER BY 1; - value_2 | c | sum ---------------------------------------------------------------------- - 3 | 3 | 108 - 4 | 4 | 56 -(2 rows) - --- There was a strange bug where this wouldn't have window functions being pushed down --- Bug dependent on column ordering -CREATE TABLE daily_uniques (value_2 float, user_id bigint); -SELECT create_distributed_table('daily_uniques', 'user_id'); - create_distributed_table ---------------------------------------------------------------------- - -(1 row) - -EXPLAIN (COSTS FALSE) SELECT - user_id, - sum(value_2) AS commits, - RANK () OVER ( - PARTITION BY user_id - ORDER BY - sum(value_2) DESC - ) -FROM daily_uniques -GROUP BY user_id -HAVING - sum(value_2) > 0 -ORDER BY commits DESC -LIMIT 10; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Sort - Sort Key: remote_scan.commits DESC - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Limit - -> Sort - Sort Key: (sum(daily_uniques.value_2)) DESC - -> WindowAgg - -> Sort - Sort Key: daily_uniques.user_id, (sum(daily_uniques.value_2)) DESC - -> HashAggregate - Group Key: daily_uniques.user_id - Filter: (sum(daily_uniques.value_2) > '0'::double precision) - -> Seq Scan on daily_uniques_xxxxxxx daily_uniques -(18 rows) - -DROP TABLE daily_uniques; --- Partition by reference table column joined to distribution column -SELECT DISTINCT value_2, array_agg(rnk ORDER BY rnk) FROM ( -SELECT events_table.value_2, sum(uref.k_no) OVER (PARTITION BY uref.id) AS rnk -FROM events_table -JOIN users_ref_test_table uref ON uref.id = events_table.user_id) sq -GROUP BY 1 ORDER BY 1; - value_2 | array_agg ---------------------------------------------------------------------- - 0 | {686,686,816,816,987,987,1104} - 1 | {500,500,675,675,675,686,686,816,816,816,987,987,987,987,987,1104,1104,1104,1104,1104,1104,1104} - 2 | {500,500,500,500,675,675,675,675,675,686,686,686,686,816,816,816,816,816,987,987,987,987,987,987,987,1104,1104,1104,1104,1104,1104} - 3 | {500,500,500,500,675,686,686,686,816,816,987,987,987,1104,1104,1104,1104,1104} - 4 | {675,675,675,675,686,686,686,816,816,816,987,987,1104,1104} - 5 | {675,675,816,816,987,987,1104,1104,1104} -(6 rows) - --- https://github.com/citusdata/citus/issues/3754 -select null = sum(null::int2) over () -from public.users_table as ut limit 1; - ?column? ---------------------------------------------------------------------- - -(1 row) - --- verify that this doesn't crash with DEBUG4 -SET log_min_messages TO DEBUG4; -SELECT - user_id, max(value_1) OVER (PARTITION BY user_id, MIN(value_2)) -FROM ( - SELECT - DISTINCT us.user_id, us.value_2, value_1, random() as r1 - FROM - users_table as us, events_table - WHERE - us.user_id = events_table.user_id AND event_type IN (1,2) - ORDER BY - user_id, value_2 - ) s -GROUP BY - 1, value_1 -ORDER BY - 2 DESC, 1; - user_id | max ---------------------------------------------------------------------- - 1 | 5 - 3 | 5 - 3 | 5 - 4 | 5 - 5 | 5 - 5 | 5 - 6 | 5 - 6 | 5 - 1 | 4 - 2 | 4 - 3 | 4 - 3 | 4 - 3 | 4 - 4 | 4 - 4 | 4 - 5 | 4 - 5 | 4 - 1 | 3 - 2 | 3 - 2 | 3 - 2 | 3 - 6 | 3 - 2 | 2 - 4 | 2 - 4 | 2 - 4 | 2 - 6 | 2 - 1 | 1 - 3 | 1 - 5 | 1 - 6 | 1 - 5 | 0 -(32 rows) - diff --git a/src/test/regress/expected/with_dml.out b/src/test/regress/expected/with_dml.out index b5141db33..4f2170082 100644 --- a/src/test/regress/expected/with_dml.out +++ b/src/test/regress/expected/with_dml.out @@ -103,9 +103,7 @@ WITH ids_to_insert AS INSERT INTO distributed_table SELECT DISTINCT ids_to_insert.tenant_id FROM ids_to_insert, distributed_table WHERE distributed_table.tenant_id < ids_to_insert.tenant_id; -DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match -DETAIL: Subquery contains an expression that is not a simple column reference in the same position as the target table's partition column. -HINT: Ensure the target table's partition column has a corresponding simple column reference to a distributed table's partition column in the subquery. +DEBUG: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns DEBUG: CTE ids_to_insert is going to be inlined via distributed planning DEBUG: generating subplan XXX_1 for subquery SELECT (((tenant_id)::integer OPERATOR(pg_catalog.*) 100))::text AS tenant_id FROM with_dml.distributed_table WHERE (dept OPERATOR(pg_catalog.>) 7) DEBUG: generating subplan XXX_2 for subquery SELECT DISTINCT ids_to_insert.tenant_id FROM (SELECT intermediate_result.tenant_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(tenant_id text)) ids_to_insert, with_dml.distributed_table WHERE (distributed_table.tenant_id OPERATOR(pg_catalog.<) ids_to_insert.tenant_id) @@ -178,9 +176,5 @@ WITH ids_to_delete AS ( SELECT id FROM reference_table ) DELETE FROM reference_table WHERE id = ANY(SELECT id FROM ids_to_delete); -RESET client_min_messages; +SET client_min_messages TO WARNING; DROP SCHEMA with_dml CASCADE; -NOTICE: drop cascades to 3 other objects -DETAIL: drop cascades to table distributed_table -drop cascades to table second_distributed_table -drop cascades to table reference_table diff --git a/src/test/regress/expected/with_executors.out b/src/test/regress/expected/with_executors.out index df1c80625..0ef716cac 100644 --- a/src/test/regress/expected/with_executors.out +++ b/src/test/regress/expected/with_executors.out @@ -427,7 +427,5 @@ WHERE 4365606 (1 row) +SET client_min_messages TO WARNING; DROP SCHEMA with_executors CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to table local_table -drop cascades to table ref_table diff --git a/src/test/regress/expected/with_join.out b/src/test/regress/expected/with_join.out index 23cafd2cc..c5df67ca7 100644 --- a/src/test/regress/expected/with_join.out +++ b/src/test/regress/expected/with_join.out @@ -385,9 +385,12 @@ join cte_1 ON cte_1.col1=d1.distrib_col; RESET client_min_messages; DROP SCHEMA with_join CASCADE; -NOTICE: drop cascades to 5 other objects +NOTICE: drop cascades to 8 other objects DETAIL: drop cascades to table reference_table +drop cascades to table reference_table_1501000 drop cascades to table distributed_1 drop cascades to table distributed_2 drop cascades to table reference_1 drop cascades to table reference_2 +drop cascades to table reference_1_1501009 +drop cascades to table reference_2_1501010 diff --git a/src/test/regress/expected/with_modifying.out b/src/test/regress/expected/with_modifying.out index 9f62271d5..70418251b 100644 --- a/src/test/regress/expected/with_modifying.out +++ b/src/test/regress/expected/with_modifying.out @@ -956,7 +956,8 @@ WITH first_query AS (INSERT INTO modify_table (id) VALUES (10001)), SET client_min_messages TO debug2; -- pushed down without the insert WITH mb AS (UPDATE modify_table SET val = 3 WHERE id = 3 RETURNING NULL) INSERT INTO modify_table WITH ma AS (SELECT * FROM modify_table LIMIT 10) SELECT count(*) FROM mb; -DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a subquery references a column from another query DEBUG: Creating router plan DEBUG: query has a single distribution column value: 3 DEBUG: Collecting INSERT ... SELECT results on coordinator @@ -1082,4 +1083,4 @@ WITH mb AS (DELETE FROM modify_table WHERE id = 3 RETURNING NULL, NULL) SELECT * \set VERBOSITY terse DROP SCHEMA with_modifying CASCADE; -NOTICE: drop cascades to 9 other objects +NOTICE: drop cascades to 10 other objects diff --git a/src/test/regress/isolation_schedule b/src/test/regress/isolation_schedule index ca893ba8d..1484c712f 100644 --- a/src/test/regress/isolation_schedule +++ b/src/test/regress/isolation_schedule @@ -76,6 +76,8 @@ test: isolation_fix_partition_shard_index_names test: isolation_global_pid test: isolation_citus_locks test: isolation_reference_table +test: isolation_schema_based_sharding +test: isolation_citus_schema_distribute_undistribute # Rebalancer test: isolation_blocking_move_single_shard_commands diff --git a/src/test/regress/minimal_pg_upgrade_schedule b/src/test/regress/minimal_pg_upgrade_schedule new file mode 100644 index 000000000..64e5c982d --- /dev/null +++ b/src/test/regress/minimal_pg_upgrade_schedule @@ -0,0 +1 @@ +test: multi_test_helpers multi_test_helpers_superuser multi_test_catalog_views diff --git a/src/test/regress/mixed_after_citus_upgrade_schedule b/src/test/regress/mixed_after_citus_upgrade_schedule index 223d7f349..9722b7317 100644 --- a/src/test/regress/mixed_after_citus_upgrade_schedule +++ b/src/test/regress/mixed_after_citus_upgrade_schedule @@ -1,2 +1 @@ test: upgrade_basic_after -test: upgrade_pg_dist_object_test_after diff --git a/src/test/regress/mixed_before_citus_upgrade_schedule b/src/test/regress/mixed_before_citus_upgrade_schedule index d7447b6df..7ed0eb5bf 100644 --- a/src/test/regress/mixed_before_citus_upgrade_schedule +++ b/src/test/regress/mixed_before_citus_upgrade_schedule @@ -1,2 +1 @@ test: upgrade_basic_before -test: upgrade_pg_dist_object_test_before diff --git a/src/test/regress/multi_1_schedule b/src/test/regress/multi_1_schedule index cefb1777f..67473e471 100644 --- a/src/test/regress/multi_1_schedule +++ b/src/test/regress/multi_1_schedule @@ -32,6 +32,11 @@ test: escape_extension_name test: ref_citus_local_fkeys test: alter_database_owner test: distributed_triggers +test: create_single_shard_table +# don't parallelize single_shard_table_udfs to make sure colocation ids are sequential +test: single_shard_table_udfs +test: schema_based_sharding +test: citus_schema_distribute_undistribute test: multi_test_catalog_views test: multi_table_ddl @@ -64,10 +69,15 @@ test: multi_remove_node_reference_table test: multi_create_table test: multi_create_table_superuser test: multi_master_protocol multi_load_data multi_load_data_superuser multi_behavioral_analytics_create_table -test: multi_behavioral_analytics_basics multi_behavioral_analytics_single_shard_queries multi_insert_select_non_pushable_queries multi_insert_select multi_behavioral_analytics_create_table_superuser +test: multi_behavioral_analytics_basics multi_behavioral_analytics_single_shard_queries multi_behavioral_analytics_create_table_superuser + +# We don't parallelize the following test with the ones above because they're +# not idempotent and hence causing flaky test detection check to fail. +test: multi_insert_select_non_pushable_queries multi_insert_select + test: multi_shard_update_delete recursive_dml_with_different_planners_executors test: insert_select_repartition window_functions dml_recursive multi_insert_select_window -test: multi_insert_select_conflict citus_table_triggers +test: multi_insert_select_conflict citus_table_triggers alter_table_single_shard_table test: multi_row_insert insert_select_into_local_table alter_index # following should not run in parallel because it relies on connection counts to workers @@ -199,6 +209,8 @@ test: local_table_join test: local_dist_join_mixed test: citus_local_dist_joins test: recurring_outer_join +test: query_single_shard_table +test: insert_select_single_shard_table test: pg_dump # --------- @@ -363,3 +375,4 @@ test: ensure_no_intermediate_data_leak test: ensure_no_shared_connection_leak test: check_mx +test: check_cluster_state diff --git a/src/test/regress/multi_mx_schedule b/src/test/regress/multi_mx_schedule index 181f9288f..682379b78 100644 --- a/src/test/regress/multi_mx_schedule +++ b/src/test/regress/multi_mx_schedule @@ -17,6 +17,7 @@ test: multi_extension test: multi_test_helpers multi_test_helpers_superuser test: multi_mx_node_metadata test: multi_cluster_management +test: remove_coordinator_from_metadata test: multi_mx_function_table_reference test: multi_test_catalog_views diff --git a/src/test/regress/multi_schedule b/src/test/regress/multi_schedule index 1d5ce0798..4d42dbc78 100644 --- a/src/test/regress/multi_schedule +++ b/src/test/regress/multi_schedule @@ -12,7 +12,12 @@ test: replicated_table_disable_node test: multi_create_table test: multi_create_table_superuser test: multi_create_table_constraints multi_master_protocol multi_load_data multi_load_data_superuser multi_behavioral_analytics_create_table -test: multi_behavioral_analytics_basics multi_behavioral_analytics_single_shard_queries multi_insert_select_non_pushable_queries multi_insert_select multi_behavioral_analytics_create_table_superuser +test: multi_behavioral_analytics_basics multi_behavioral_analytics_single_shard_queries multi_behavioral_analytics_create_table_superuser + +# We don't parallelize the following test with the ones above because they're +# not idempotent and hence causing flaky test detection check to fail. +test: multi_insert_select_non_pushable_queries multi_insert_select + test: multi_shard_update_delete recursive_dml_with_different_planners_executors test: insert_select_repartition window_functions dml_recursive multi_insert_select_window test: multi_insert_select_conflict citus_table_triggers @@ -67,7 +72,8 @@ test: tableam # Miscellaneous tests to check our query planning behavior # ---------- test: multi_deparse_shard_query multi_distributed_transaction_id intermediate_results limit_intermediate_size -test: multi_explain hyperscale_tutorial partitioned_intermediate_results distributed_intermediate_results multi_real_time_transaction +test: multi_explain +test: hyperscale_tutorial partitioned_intermediate_results distributed_intermediate_results multi_real_time_transaction test: multi_basic_queries cross_join multi_complex_expressions multi_subquery multi_subquery_complex_queries multi_subquery_behavioral_analytics test: multi_subquery_complex_reference_clause multi_subquery_window_functions multi_view multi_sql_function multi_prepare_sql test: sql_procedure multi_function_in_join row_types materialized_view @@ -95,7 +101,7 @@ test: multi_dropped_column_aliases foreign_key_restriction_enforcement test: binary_protocol test: alter_table_set_access_method test: alter_distributed_table -test: issue_5248 issue_5099 issue_5763 issue_6543 +test: issue_5248 issue_5099 issue_5763 issue_6543 issue_6758 test: object_propagation_debug test: undistribute_table test: run_command_on_all_nodes @@ -105,8 +111,9 @@ test: background_task_queue_monitor test: clock # MERGE tests -test: merge -test: pgmerge +test: merge pgmerge merge_repartition2 +test: merge_repartition1 +test: merge_partition_tables # --------- # test that no tests leaked intermediate results. This should always be last @@ -122,3 +129,4 @@ test: check_mx test: generated_identity test: drop_database +test: check_cluster_state diff --git a/src/test/regress/operations_schedule b/src/test/regress/operations_schedule index f5e77c835..6dbc303c2 100644 --- a/src/test/regress/operations_schedule +++ b/src/test/regress/operations_schedule @@ -1,10 +1,10 @@ test: multi_test_helpers multi_test_helpers_superuser test: multi_cluster_management test: multi_test_catalog_views +test: worker_copy_table_to_node test: shard_rebalancer_unit test: shard_rebalancer test: background_rebalance -test: worker_copy_table_to_node test: background_rebalance_parallel test: foreign_key_to_reference_shard_rebalance test: multi_move_mx diff --git a/src/test/regress/pg_regress_multi.pl b/src/test/regress/pg_regress_multi.pl index 7b02ca5cc..4cc022198 100755 --- a/src/test/regress/pg_regress_multi.pl +++ b/src/test/regress/pg_regress_multi.pl @@ -489,7 +489,7 @@ push(@pgOptions, "citus.enable_manual_changes_to_shards=on"); push(@pgOptions, "citus.allow_unsafe_locks_from_workers=on"); push(@pgOptions, "citus.stat_statements_track = 'all'"); push(@pgOptions, "citus.enable_change_data_capture=on"); -push(@pgOptions, "citus.stat_tenants_limit = 10"); +push(@pgOptions, "citus.stat_tenants_limit = 2"); push(@pgOptions, "citus.stat_tenants_track = 'ALL'"); # Some tests look at shards in pg_class, make sure we can usually see them: diff --git a/src/test/regress/single_shard_table_prep_schedule b/src/test/regress/single_shard_table_prep_schedule new file mode 100644 index 000000000..7410cf05f --- /dev/null +++ b/src/test/regress/single_shard_table_prep_schedule @@ -0,0 +1 @@ +test: single_shard_table_prep diff --git a/src/test/regress/spec/isolation_citus_schema_distribute_undistribute.spec b/src/test/regress/spec/isolation_citus_schema_distribute_undistribute.spec new file mode 100644 index 000000000..bb40918f7 --- /dev/null +++ b/src/test/regress/spec/isolation_citus_schema_distribute_undistribute.spec @@ -0,0 +1,173 @@ +setup +{ + SELECT citus_set_coordinator_host('localhost', 57636); + SET citus.shard_replication_factor TO 1; + CREATE SCHEMA tenant1; + CREATE TABLE tenant1.table1(id int PRIMARY KEY, name text, col bigint); + INSERT INTO tenant1.table1 SELECT i, 'asd', i*1000 FROM generate_series(11, 20) i; + + CREATE TABLE tenant1.table2(id int PRIMARY KEY, name text, col bigint); + + CREATE TABLE public.ref(id int PRIMARY KEY); + SELECT create_reference_table('public.ref'); + + CREATE TABLE tenant1.table3(id int PRIMARY KEY, name text, col1 int, col int REFERENCES public.ref(id)); + SELECT citus_add_local_table_to_metadata('tenant1.table3'); +} + +teardown +{ + DROP TABLE public.ref CASCADE; + DROP SCHEMA IF EXISTS tenant1, tenant2 CASCADE; + SELECT citus_remove_node('localhost', 57636); +} + +session "s1" + +step "s1-begin" +{ + BEGIN; + SET citus.shard_replication_factor TO 1; +} + +step "s1-schema-distribute" +{ + SELECT citus_schema_distribute('tenant1'); +} + +step "s1-schema-undistribute" +{ + SELECT citus_schema_undistribute('tenant1'); +} + +step "s1-verify-distributed-schema" +{ + SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid; +} + +step "s1-commit" +{ + COMMIT; +} + +session "s2" + +step "s2-drop-schema" +{ + DROP SCHEMA tenant1 CASCADE; +} + +step "s2-rename-schema" +{ + ALTER SCHEMA tenant1 RENAME TO tenant2; +} + +step "s2-add-table" +{ + SET citus.shard_replication_factor TO 1; + CREATE TABLE tenant1.table4(id int PRIMARY KEY, name text, col bigint); +} + +step "s2-drop-table" +{ + DROP TABLE tenant1.table3; +} + +step "s2-alter-col-type" +{ + ALTER TABLE tenant1.table3 ALTER COLUMN col1 TYPE text; +} + +step "s2-add-foreign-key" +{ + ALTER TABLE tenant1.table3 ADD CONSTRAINT table3_fk1 FOREIGN KEY (id) REFERENCES tenant1.table2 (id); +} + +step "s2-drop-foreign-key" +{ + ALTER TABLE tenant1.table3 DROP CONSTRAINT table3_col_fkey; +} + +step "s2-create-unique-index" +{ + CREATE UNIQUE INDEX idx_2 ON tenant1.table3 (col); +} + +step "s2-create-unique-index-concurrently" +{ + CREATE UNIQUE INDEX CONCURRENTLY idx_3 ON tenant1.table3 (col); +} + +step "s2-reindex-unique-concurrently" +{ + REINDEX INDEX CONCURRENTLY tenant1.idx_2; +} + +step "s2-insert" +{ + // we insert into public.ref table as well to prevent fkey violation + INSERT INTO public.ref SELECT i FROM generate_series(11, 20) i; + INSERT INTO tenant1.table3 SELECT i, 'asd', i*1000 FROM generate_series(11, 20) i; +} + +step "s2-delete" +{ + DELETE FROM tenant1.table3; +} + +step "s2-update" +{ + UPDATE tenant1.table3 SET col = 11 WHERE col = 11; +} + +// DROP SCHEMA +permutation "s1-begin" "s1-schema-distribute" "s2-drop-schema" "s1-commit" "s1-verify-distributed-schema" +permutation "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-drop-schema" "s1-commit" "s1-verify-distributed-schema" + +// RENAME SCHEMA +permutation "s1-begin" "s1-schema-distribute" "s2-rename-schema" "s1-commit" "s1-verify-distributed-schema" +permutation "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-rename-schema" "s1-commit" "s1-verify-distributed-schema" + +// CREATE TABLE +permutation "s1-begin" "s1-schema-distribute" "s2-add-table" "s1-commit" "s1-verify-distributed-schema" +permutation "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-add-table" "s1-commit" "s1-verify-distributed-schema" + +// DROP TABLE +permutation "s1-begin" "s1-schema-distribute" "s2-drop-table" "s1-commit" "s1-verify-distributed-schema" +permutation "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-drop-table" "s1-commit" "s1-verify-distributed-schema" + +// ALTER TABLE ALTER COLUMN TYPE +permutation "s1-begin" "s1-schema-distribute" "s2-alter-col-type" "s1-commit" "s1-verify-distributed-schema" +permutation "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-alter-col-type" "s1-commit" "s1-verify-distributed-schema" + +// ADD FOREIGN KEY +permutation "s1-begin" "s1-schema-distribute" "s2-add-foreign-key" "s1-commit" "s1-verify-distributed-schema" +permutation "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-add-foreign-key" "s1-commit" "s1-verify-distributed-schema" + +// DROP FOREIGN KEY +permutation "s1-begin" "s1-schema-distribute" "s2-drop-foreign-key" "s1-commit" "s1-verify-distributed-schema" +permutation "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-drop-foreign-key" "s1-commit" "s1-verify-distributed-schema" + +// CREATE UNIQUE INDEX +permutation "s1-begin" "s1-schema-distribute" "s2-create-unique-index" "s1-commit" "s1-verify-distributed-schema" +permutation "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-create-unique-index" "s1-commit" "s1-verify-distributed-schema" + +// CREATE UNIQUE INDEX CONCURRENTLY +permutation "s1-begin" "s1-schema-distribute" "s2-create-unique-index-concurrently" "s1-commit" "s1-verify-distributed-schema" +permutation "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-create-unique-index-concurrently" "s1-commit" "s1-verify-distributed-schema" + +// REINDEX CONCURRENTLY +permutation "s2-create-unique-index" "s1-begin" "s1-schema-distribute" "s2-reindex-unique-concurrently" "s1-commit" "s1-verify-distributed-schema" +permutation "s2-create-unique-index" "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-reindex-unique-concurrently" "s1-commit" "s1-verify-distributed-schema" + +// INSERT +permutation "s1-begin" "s1-schema-distribute" "s2-insert" "s1-commit" "s1-verify-distributed-schema" +permutation "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-insert" "s1-commit" "s1-verify-distributed-schema" + +// UPDATE +permutation "s2-insert" "s1-begin" "s1-schema-distribute" "s2-update" "s1-commit" "s1-verify-distributed-schema" +permutation "s2-insert" "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-update" "s1-commit" "s1-verify-distributed-schema" + +// DELETE +permutation "s2-insert" "s1-begin" "s1-schema-distribute" "s2-delete" "s1-commit" "s1-verify-distributed-schema" +permutation "s2-insert" "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-delete" "s1-commit" "s1-verify-distributed-schema" diff --git a/src/test/regress/spec/isolation_extension_commands.spec b/src/test/regress/spec/isolation_extension_commands.spec index 98a8eb6dc..0b81e8adf 100644 --- a/src/test/regress/spec/isolation_extension_commands.spec +++ b/src/test/regress/spec/isolation_extension_commands.spec @@ -41,13 +41,18 @@ step "s1-create-extension-with-schema2" step "s1-print" { - select count(*) from pg_catalog.pg_dist_object ; + select pg_identify_object_as_address(classid, objid, objsubid)::text as obj_repr from pg_dist_object order by obj_repr; select extname, extversion, nspname from pg_extension, pg_namespace where pg_namespace.oid=pg_extension.extnamespace and extname='seg'; SELECT run_command_on_workers($$select extname from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select extversion from pg_extension where extname='seg'$$); SELECT run_command_on_workers($$select nspname from pg_extension, pg_namespace where extname='seg' and pg_extension.extnamespace=pg_namespace.oid$$); } +step "s1-cleanup-node-1" +{ + SELECT run_command_on_workers($$drop extension if exists seg$$); +} + session "s2" step "s2-begin" @@ -121,4 +126,4 @@ permutation "s2-add-node-1" "s2-begin" "s2-drop-extension" "s1-remove-node-1" "s permutation "s2-begin" "s2-create-extension-with-schema1" "s1-add-node-1" "s2-commit" "s1-print" permutation "s2-drop-extension" "s2-add-node-1" "s2-create-extension-with-schema2" "s2-begin" "s2-alter-extension-version-13" "s1-remove-node-1" "s2-commit" "s1-print" permutation "s2-drop-extension" "s2-add-node-1" "s2-begin" "s2-create-extension-version-11" "s1-remove-node-1" "s2-commit" "s1-print" -permutation "s2-drop-extension" "s2-add-node-1" "s2-create-extension-version-11" "s2-remove-node-1" "s2-begin" "s2-drop-extension" "s1-add-node-1" "s2-commit" "s1-print" +permutation "s2-drop-extension" "s2-add-node-1" "s2-create-extension-version-11" "s2-remove-node-1" "s2-begin" "s2-drop-extension" "s1-add-node-1" "s2-commit" "s1-print" "s1-cleanup-node-1" diff --git a/src/test/regress/spec/isolation_master_update_node.spec b/src/test/regress/spec/isolation_master_update_node.spec index 5c5a1bb48..3715b6afd 100644 --- a/src/test/regress/spec/isolation_master_update_node.spec +++ b/src/test/regress/spec/isolation_master_update_node.spec @@ -1,7 +1,6 @@ -// Three alternative test outputs: +// Two alternative test outputs: // isolation_master_update_node.out for PG15 // isolation_master_update_node_0.out for PG14 -// isolation_master_update_node_1.out for PG13 setup { diff --git a/src/test/regress/spec/isolation_schema_based_sharding.spec b/src/test/regress/spec/isolation_schema_based_sharding.spec new file mode 100644 index 000000000..6f3fe9dc5 --- /dev/null +++ b/src/test/regress/spec/isolation_schema_based_sharding.spec @@ -0,0 +1,43 @@ +setup +{ + SET citus.enable_schema_based_sharding TO ON; + SET citus.shard_replication_factor TO 1; + CREATE SCHEMA tenant_1; + CREATE SCHEMA tenant_2; + CREATE SCHEMA tenant_3; + + CREATE SCHEMA tenant_4; + CREATE TABLE tenant_4.first_table (a int); +} + +teardown +{ + DROP SCHEMA tenant_1, tenant_2, tenant_3, tenant_4 CASCADE; +} + +session "s1" + +step "s1-begin" { BEGIN; SET citus.shard_replication_factor TO 1;} +step "s1-tenant-1-create-table-1" { CREATE TABLE tenant_1.tbl_1 (a int); } +step "s1-tenant-4-create-table-1" { CREATE TABLE tenant_4.tbl_1 (a int); } +step "s1-tenant-2-create-table-1" { CREATE TABLE tenant_2.tbl_1 (a int); } +step "s1-commit" { COMMIT; } + +session "s2" + +step "s2-begin" { BEGIN; SET citus.shard_replication_factor TO 1;} +step "s2-tenant-1-create-table-2" { CREATE TABLE tenant_1.tbl_2 (a int); } +step "s2-tenant-4-create-table-2" { CREATE TABLE tenant_4.tbl_2 (a int); } +step "s2-tenant-1-verify-colocation" { SELECT COUNT(DISTINCT(colocationid))=1 FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant_1.%'; } +step "s2-tenant-4-verify-colocation" { SELECT COUNT(DISTINCT(colocationid))=1 FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant_4.%'; } +step "s2-tenant-3-create-table-1" { CREATE TABLE tenant_3.tbl_1 (a int); } +step "s2-commit" { COMMIT; } + +// two sessions competing with each other to create the first table in the same schema +permutation "s1-begin" "s2-begin" "s1-tenant-1-create-table-1" "s2-tenant-1-create-table-2" "s1-commit" "s2-tenant-1-verify-colocation" "s2-commit" + +// two sessions creating further tenant tables in the same schema +permutation "s1-begin" "s2-begin" "s1-tenant-4-create-table-1" "s2-tenant-4-create-table-2" "s1-commit" "s2-tenant-4-verify-colocation" "s2-commit" + +// two sessions creating tenant tables in different schemas +permutation "s1-begin" "s2-begin" "s1-tenant-2-create-table-1" "s2-tenant-3-create-table-1" "s1-commit" "s2-commit" diff --git a/src/test/regress/spec/isolation_select_vs_all.spec b/src/test/regress/spec/isolation_select_vs_all.spec index a3b65ca77..3611e627e 100644 --- a/src/test/regress/spec/isolation_select_vs_all.spec +++ b/src/test/regress/spec/isolation_select_vs_all.spec @@ -35,7 +35,6 @@ step "s1-adaptive-select" SELECT * FROM select_append AS t1 JOIN select_append AS t2 ON t1.id = t2.int_data ORDER BY 1, 2, 3, 4; } step "s1-insert" { INSERT INTO select_append VALUES(0, 'k', 0); } -step "s1-insert-select" { INSERT INTO select_append SELECT * FROM select_append; } step "s1-update" { UPDATE select_append SET data = 'l' WHERE id = 0; } step "s1-delete" { DELETE FROM select_append WHERE id = 1; } step "s1-truncate" { TRUNCATE select_append; } @@ -65,7 +64,6 @@ step "s2-adaptive-select" SELECT * FROM select_append AS t1 JOIN select_append AS t2 ON t1.id = t2.int_data ORDER BY 1, 2, 3, 4; } step "s2-insert" { INSERT INTO select_append VALUES(0, 'k', 0); } -step "s2-insert-select" { INSERT INTO select_append SELECT * FROM select_append; } step "s2-update" { UPDATE select_append SET data = 'l' WHERE id = 0; } step "s2-delete" { DELETE FROM select_append WHERE id = 1; } step "s2-truncate" { TRUNCATE select_append; } @@ -101,7 +99,6 @@ permutation "s1-initialize" "s1-begin" "s1-adaptive-select" "s2-adaptive-select" // permutations - router SELECT first permutation "s1-initialize" "s1-begin" "s1-router-select" "s2-insert" "s1-commit" "s1-select-count" -permutation "s1-initialize" "s1-begin" "s1-router-select" "s2-insert-select" "s1-commit" "s1-select-count" permutation "s1-initialize" "s1-begin" "s1-router-select" "s2-update" "s1-commit" "s1-select-count" permutation "s1-initialize" "s1-begin" "s1-router-select" "s2-delete" "s1-commit" "s1-select-count" permutation "s1-initialize" "s1-begin" "s1-router-select" "s2-truncate" "s1-commit" "s1-select-count" @@ -119,7 +116,6 @@ permutation "s1-drop" "s1-create-non-distributed-table" "s1-begin" "s1-router-se // permutations - router SELECT second permutation "s1-initialize" "s1-begin" "s1-insert" "s2-router-select" "s1-commit" "s1-select-count" -permutation "s1-initialize" "s1-begin" "s1-insert-select" "s2-router-select" "s1-commit" "s1-select-count" permutation "s1-initialize" "s1-begin" "s1-update" "s2-router-select" "s1-commit" "s1-select-count" permutation "s1-initialize" "s1-begin" "s1-delete" "s2-router-select" "s1-commit" "s1-select-count" permutation "s1-initialize" "s1-begin" "s1-truncate" "s2-router-select" "s1-commit" "s1-select-count" @@ -136,7 +132,6 @@ permutation "s1-drop" "s1-create-non-distributed-table" "s1-begin" "s1-distribut // permutations - real-time SELECT first permutation "s1-initialize" "s1-begin" "s1-real-time-select" "s2-insert" "s1-commit" "s1-select-count" -permutation "s1-initialize" "s1-begin" "s1-real-time-select" "s2-insert-select" "s1-commit" "s1-select-count" permutation "s1-initialize" "s1-begin" "s1-real-time-select" "s2-update" "s1-commit" "s1-select-count" permutation "s1-initialize" "s1-begin" "s1-real-time-select" "s2-delete" "s1-commit" "s1-select-count" permutation "s1-initialize" "s1-begin" "s1-real-time-select" "s2-truncate" "s1-commit" "s1-select-count" @@ -153,7 +148,6 @@ permutation "s1-drop" "s1-create-non-distributed-table" "s1-begin" "s1-real-time // permutations - real-time SELECT second permutation "s1-initialize" "s1-begin" "s1-insert" "s2-real-time-select" "s1-commit" "s1-select-count" -permutation "s1-initialize" "s1-begin" "s1-insert-select" "s2-real-time-select" "s1-commit" "s1-select-count" permutation "s1-initialize" "s1-begin" "s1-update" "s2-real-time-select" "s1-commit" "s1-select-count" permutation "s1-initialize" "s1-begin" "s1-delete" "s2-real-time-select" "s1-commit" "s1-select-count" permutation "s1-initialize" "s1-begin" "s1-truncate" "s2-real-time-select" "s1-commit" "s1-select-count" @@ -169,7 +163,6 @@ permutation "s1-drop" "s1-create-non-distributed-table" "s1-begin" "s1-distribut // permutations - adaptive SELECT first permutation "s1-initialize" "s1-begin" "s1-adaptive-select" "s2-insert" "s1-commit" "s1-select-count" -permutation "s1-initialize" "s1-begin" "s1-adaptive-select" "s2-insert-select" "s1-commit" "s1-select-count" permutation "s1-initialize" "s1-begin" "s1-adaptive-select" "s2-update" "s1-commit" "s1-select-count" permutation "s1-initialize" "s1-begin" "s1-adaptive-select" "s2-delete" "s1-commit" "s1-select-count" permutation "s1-initialize" "s1-begin" "s1-adaptive-select" "s2-truncate" "s1-commit" "s1-select-count" @@ -186,7 +179,6 @@ permutation "s1-drop" "s1-create-non-distributed-table" "s1-begin" "s1-adaptive- // permutations - adaptive SELECT second permutation "s1-initialize" "s1-begin" "s1-insert" "s2-adaptive-select" "s1-commit" "s1-select-count" -permutation "s1-initialize" "s1-begin" "s1-insert-select" "s2-adaptive-select" "s1-commit" "s1-select-count" permutation "s1-initialize" "s1-begin" "s1-update" "s2-adaptive-select" "s1-commit" "s1-select-count" permutation "s1-initialize" "s1-begin" "s1-delete" "s2-adaptive-select" "s1-commit" "s1-select-count" permutation "s1-initialize" "s1-begin" "s1-truncate" "s2-adaptive-select" "s1-commit" "s1-select-count" diff --git a/src/test/regress/split_schedule b/src/test/regress/split_schedule index 2510260fb..b47acd828 100644 --- a/src/test/regress/split_schedule +++ b/src/test/regress/split_schedule @@ -2,6 +2,7 @@ # Include tests from 'minimal_schedule' for setup. test: multi_test_helpers multi_test_helpers_superuser columnar_test_helpers test: multi_cluster_management +test: remove_coordinator_from_metadata test: multi_test_catalog_views test: tablespace # Helpers for foreign key catalogs. diff --git a/src/test/regress/sql/adaptive_executor_repartition.sql b/src/test/regress/sql/adaptive_executor_repartition.sql index 1f2e21951..06f54b82b 100644 --- a/src/test/regress/sql/adaptive_executor_repartition.sql +++ b/src/test/regress/sql/adaptive_executor_repartition.sql @@ -79,4 +79,5 @@ select count(*) from trips t1, cars r1, trips t2, cars r2 where t1.trip_id = t2. set citus.enable_single_hash_repartition_joins to on; select count(*) from trips t1, cars r1, trips t2, cars r2 where t1.trip_id = t2.trip_id and t1.car_id = r1.car_id and t2.car_id = r2.car_id; +SET client_min_messages TO WARNING; DROP SCHEMA adaptive_executor CASCADE; diff --git a/src/test/regress/sql/add_coordinator.sql b/src/test/regress/sql/add_coordinator.sql index 2dba78064..81b77bfcd 100644 --- a/src/test/regress/sql/add_coordinator.sql +++ b/src/test/regress/sql/add_coordinator.sql @@ -3,6 +3,8 @@ -- -- node trying to add itself without specifying groupid => 0 should error out +-- first remove the coordinator to for testing master_add_node for coordinator +SELECT master_remove_node('localhost', :master_port); SELECT master_add_node('localhost', :master_port); SELECT master_add_node('localhost', :master_port, groupid => 0) AS master_nodeid \gset diff --git a/src/test/regress/sql/alter_distributed_table.sql b/src/test/regress/sql/alter_distributed_table.sql index 4b86fa5ef..0577421de 100644 --- a/src/test/regress/sql/alter_distributed_table.sql +++ b/src/test/regress/sql/alter_distributed_table.sql @@ -195,14 +195,18 @@ SELECT COUNT(DISTINCT colocationid) FROM pg_dist_partition WHERE logicalrelid::r -- test references CREATE TABLE referenced_dist_table (a INT UNIQUE); CREATE TABLE referenced_ref_table (a INT UNIQUE); -CREATE TABLE table_with_references (a1 INT UNIQUE REFERENCES referenced_dist_table(a), a2 INT REFERENCES referenced_ref_table(a)); -CREATE TABLE referencing_dist_table (a INT REFERENCES table_with_references(a1)); +CREATE TABLE table_with_references (a1 INT UNIQUE, a2 INT); +CREATE TABLE referencing_dist_table (a INT); SELECT create_distributed_table('referenced_dist_table', 'a', colocate_with:='none'); SELECT create_reference_table('referenced_ref_table'); SELECT create_distributed_table('table_with_references', 'a1', colocate_with:='referenced_dist_table'); SELECT create_distributed_table('referencing_dist_table', 'a', colocate_with:='referenced_dist_table'); +ALTER TABLE table_with_references ADD FOREIGN KEY (a1) REFERENCES referenced_dist_table(a); +ALTER TABLE table_with_references ADD FOREIGN KEY (a2) REFERENCES referenced_ref_table(a); +ALTER TABLE referencing_dist_table ADD FOREIGN KEY (a) REFERENCES table_with_references(a1); + SET client_min_messages TO WARNING; SELECT conrelid::regclass::text AS "Referencing Table", pg_get_constraintdef(oid, true) AS "Definition" FROM pg_constraint WHERE (conrelid::regclass::text = 'table_with_references' OR confrelid::regclass::text = 'table_with_references') AND contype = 'f' ORDER BY 1,2; @@ -386,6 +390,11 @@ CREATE TABLE reference_table (a INT); SELECT create_reference_table('reference_table'); SELECT alter_distributed_table('dist_table', colocate_with:='reference_table'); +-- test colocating with single shard table +CREATE TABLE single_shard_table (a INT); +SELECT create_distributed_table('single_shard_table', null); +SELECT alter_distributed_table('dist_table', colocate_with:='single_shard_table'); + -- test append table CREATE TABLE append_table (a INT); SELECT create_distributed_table('append_table', 'a', 'append'); @@ -472,3 +481,4 @@ RESET search_path; DROP SCHEMA alter_distributed_table CASCADE; DROP SCHEMA schema_to_test_alter_dist_table CASCADE; +DROP USER alter_dist_table_test_user; diff --git a/src/test/regress/sql/alter_table_set_access_method.sql b/src/test/regress/sql/alter_table_set_access_method.sql index 87055e364..d7720cfda 100644 --- a/src/test/regress/sql/alter_table_set_access_method.sql +++ b/src/test/regress/sql/alter_table_set_access_method.sql @@ -278,4 +278,3 @@ select alter_table_set_access_method('view_test_view','columnar'); SET client_min_messages TO WARNING; DROP SCHEMA alter_table_set_access_method CASCADE; -SELECT 1 FROM master_remove_node('localhost', :master_port); diff --git a/src/test/regress/sql/alter_table_single_shard_table.sql b/src/test/regress/sql/alter_table_single_shard_table.sql new file mode 100644 index 000000000..bcf0b4f74 --- /dev/null +++ b/src/test/regress/sql/alter_table_single_shard_table.sql @@ -0,0 +1,98 @@ +CREATE SCHEMA alter_null_dist_key; +SET search_path TO alter_null_dist_key; + +SET citus.next_shard_id TO 1720000; +SET citus.shard_count TO 32; +SET citus.shard_replication_factor TO 1; + +CREATE SEQUENCE dist_seq; +CREATE TABLE null_dist_table(a bigint DEFAULT nextval('dist_seq') UNIQUE, "b" text, c bigint GENERATED BY DEFAULT AS IDENTITY); +INSERT INTO null_dist_table("b") VALUES ('test'); +SELECT create_distributed_table('null_dist_table', null, colocate_with=>'none', distribution_type=>null); + +-- add column +ALTER TABLE null_dist_table ADD COLUMN d bigint DEFAULT 2; +SELECT * FROM null_dist_table ORDER BY c; + +-- alter default, set to 3 +ALTER TABLE null_dist_table ALTER COLUMN d SET DEFAULT 3; +INSERT INTO null_dist_table("b") VALUES ('test'); +SELECT * FROM null_dist_table ORDER BY c; + +-- drop default, see null +ALTER TABLE null_dist_table ALTER COLUMN d DROP DEFAULT; +INSERT INTO null_dist_table("b") VALUES ('test'); +SELECT * FROM null_dist_table ORDER BY c; + +-- cleanup the rows that were added to test the default behavior +DELETE FROM null_dist_table WHERE "b" = 'test' AND a > 1; + +-- alter column type +ALTER TABLE null_dist_table ALTER COLUMN d TYPE text; +UPDATE null_dist_table SET d = 'this is a text' WHERE d = '2'; +SELECT * FROM null_dist_table ORDER BY c; + +-- drop seq column +ALTER TABLE null_dist_table DROP COLUMN a; +SELECT * FROM null_dist_table ORDER BY c; + +-- add not null constraint +ALTER TABLE null_dist_table ALTER COLUMN b SET NOT NULL; + +-- not null constraint violation, error out +INSERT INTO null_dist_table VALUES (NULL, 2, 'test'); +-- drop not null constraint and try again +ALTER TABLE null_dist_table ALTER COLUMN b DROP NOT NULL; +INSERT INTO null_dist_table VALUES (NULL, 3, 'test'); +SELECT * FROM null_dist_table ORDER BY c; + +-- add exclusion constraint +ALTER TABLE null_dist_table ADD CONSTRAINT exc_b EXCLUDE USING btree (b with =); +-- rename the exclusion constraint, errors out +ALTER TABLE null_dist_table RENAME CONSTRAINT exc_b TO exc_b_1; +-- create exclusion constraint without a name +ALTER TABLE null_dist_table ADD EXCLUDE USING btree (b with =); + +-- test setting autovacuum option +ALTER TABLE null_dist_table SET (autovacuum_enabled = false); + +-- test multiple subcommands +ALTER TABLE null_dist_table ADD COLUMN int_column1 INTEGER, + DROP COLUMN d; + +SELECT * FROM null_dist_table ORDER BY c; + +-- test policy and row level security +CREATE TABLE null_dist_key_with_policy (table_user text); +INSERT INTO null_dist_key_with_policy VALUES ('user_1'); +SELECT create_distributed_table('null_dist_key_with_policy', null); + +-- enable rls +ALTER TABLE null_dist_key_with_policy ENABLE ROW LEVEL SECURITY; + +-- user_1 will be allowed to see the inserted row +CREATE ROLE user_1 WITH LOGIN; +GRANT ALL ON SCHEMA alter_null_dist_key TO user_1; +GRANT ALL ON TABLE alter_null_dist_key.null_dist_key_with_policy TO user_1; +CREATE POLICY table_policy ON null_dist_key_with_policy TO user_1 + USING (table_user = current_user); + +-- user_2 will not be allowed to see the inserted row +CREATE ROLE user_2 WITH LOGIN; +GRANT ALL ON SCHEMA alter_null_dist_key TO user_2; +GRANT ALL ON TABLE alter_null_dist_key.null_dist_key_with_policy TO user_2; +CREATE POLICY table_policy_1 ON null_dist_key_with_policy TO user_2 + USING (table_user = current_user); + +\c - user_1 - +SELECT * FROM alter_null_dist_key.null_dist_key_with_policy; +\c - user_2 - +SELECT * FROM alter_null_dist_key.null_dist_key_with_policy; +-- postgres will always be allowed to see the row as a superuser +\c - postgres - +SELECT * FROM alter_null_dist_key.null_dist_key_with_policy; + +-- cleanup +SET client_min_messages TO ERROR; +DROP SCHEMA alter_null_dist_key CASCADE; +DROP ROLE user_1, user_2; diff --git a/src/test/regress/sql/auto_undist_citus_local.sql b/src/test/regress/sql/auto_undist_citus_local.sql index 7c7814a46..895e2f902 100644 --- a/src/test/regress/sql/auto_undist_citus_local.sql +++ b/src/test/regress/sql/auto_undist_citus_local.sql @@ -214,7 +214,11 @@ DROP TABLE IF EXISTS citus_local_table_1, citus_local_table_2, citus_local_table -- this GUC will add the next three tables to metadata automatically SET citus.use_citus_managed_tables TO ON; -CREATE TABLE citus_local_table_1(a INT UNIQUE); + +-- try to create the table twice by using IF NOT EXISTS syntax +CREATE TABLE IF NOT EXISTS citus_local_table_1(a INT UNIQUE); +CREATE TABLE IF NOT EXISTS citus_local_table_1(a INT UNIQUE); + CREATE TABLE citus_local_table_2(a INT UNIQUE); CREATE TABLE citus_local_table_3(a INT UNIQUE); RESET citus.use_citus_managed_tables; @@ -738,3 +742,4 @@ CALL drop_constraint_via_proc_exception(); SELECT logicalrelid, partmethod, repmodel FROM pg_dist_partition WHERE logicalrelid IN ('reference_table_1'::regclass, 'citus_local_table_1'::regclass) ORDER BY logicalrelid; DROP SCHEMA drop_fkey_cascade CASCADE; +DROP USER another_user; diff --git a/src/test/regress/sql/background_rebalance.sql b/src/test/regress/sql/background_rebalance.sql index 59b296576..86a592d64 100644 --- a/src/test/regress/sql/background_rebalance.sql +++ b/src/test/regress/sql/background_rebalance.sql @@ -110,6 +110,27 @@ SELECT public.wait_for_resource_cleanup(); SELECT 1 FROM citus_remove_node('localhost', :master_port); SELECT public.wait_until_metadata_sync(30000); +-- make sure a non-super user can rebalance when there are reference tables to replicate +CREATE TABLE ref_table(a int primary key); +SELECT create_reference_table('ref_table'); + +-- add a new node to trigger replicate_reference_tables task +SELECT 1 FROM citus_add_node('localhost', :worker_3_port); + +SET ROLE non_super_user_rebalance; +SELECT 1 FROM citus_rebalance_start(shard_transfer_mode := 'force_logical'); + +-- wait for success +SELECT citus_rebalance_wait(); +SELECT state, details from citus_rebalance_status(); + +RESET ROLE; + +-- reset the the number of nodes by removing the previously added node +SELECT 1 FROM citus_drain_node('localhost', :worker_3_port); +CALL citus_cleanup_orphaned_resources(); +SELECT 1 FROM citus_remove_node('localhost', :worker_3_port); + SET client_min_messages TO WARNING; DROP SCHEMA background_rebalance CASCADE; DROP USER non_super_user_rebalance; diff --git a/src/test/regress/sql/background_rebalance_parallel.sql b/src/test/regress/sql/background_rebalance_parallel.sql index 5229e7f88..8f8ed4d74 100644 --- a/src/test/regress/sql/background_rebalance_parallel.sql +++ b/src/test/regress/sql/background_rebalance_parallel.sql @@ -204,11 +204,10 @@ SELECT citus_rebalance_start AS job_id from citus_rebalance_start() \gset -- see dependent tasks to understand which tasks remain runnable because of -- citus.max_background_task_executors_per_node -- and which tasks are actually blocked from colocation group dependencies -SELECT D.task_id, - (SELECT T.command FROM pg_dist_background_task T WHERE T.task_id = D.task_id), - D.depends_on, - (SELECT T.command FROM pg_dist_background_task T WHERE T.task_id = D.depends_on) -FROM pg_dist_background_task_depend D WHERE job_id in (:job_id) ORDER BY D.task_id, D.depends_on ASC; +SELECT task_id, depends_on +FROM pg_dist_background_task_depend +WHERE job_id in (:job_id) +ORDER BY 1, 2 ASC; -- default citus.max_background_task_executors_per_node is 1 -- show that first exactly one task per node is running @@ -218,10 +217,12 @@ SELECT job_id, task_id, status, nodes_involved FROM pg_dist_background_task WHERE job_id in (:job_id) ORDER BY task_id; -- increase citus.max_background_task_executors_per_node +SELECT citus_task_wait(1013, desired_status => 'done'); ALTER SYSTEM SET citus.max_background_task_executors_per_node = 2; SELECT pg_reload_conf(); + +SELECT citus_task_wait(1014, desired_status => 'running'); SELECT citus_task_wait(1015, desired_status => 'running'); -SELECT citus_task_wait(1013, desired_status => 'done'); -- show that at most 2 tasks per node are running -- among the tasks that are not blocked diff --git a/src/test/regress/sql/check_cluster_state.sql b/src/test/regress/sql/check_cluster_state.sql new file mode 100644 index 000000000..bcdd8f1a7 --- /dev/null +++ b/src/test/regress/sql/check_cluster_state.sql @@ -0,0 +1 @@ +SELECT count(*) >= 1 as coordinator_exists FROM pg_dist_node WHERE groupid = 0 AND isactive; diff --git a/src/test/regress/sql/citus_local_dist_joins.sql b/src/test/regress/sql/citus_local_dist_joins.sql index c68fbbfd8..2a7bbacf9 100644 --- a/src/test/regress/sql/citus_local_dist_joins.sql +++ b/src/test/regress/sql/citus_local_dist_joins.sql @@ -2,8 +2,6 @@ CREATE SCHEMA citus_local_dist_joins; SET search_path TO citus_local_dist_joins; SET client_min_messages to ERROR; -SELECT master_add_node('localhost', :master_port, groupId => 0) AS coordinator_nodeid \gset - CREATE TABLE citus_local(key int, value text); SELECT citus_add_local_table_to_metadata('citus_local'); @@ -273,6 +271,5 @@ RESET citus.local_table_join_policy; SET client_min_messages to ERROR; DROP TABLE citus_local; -SELECT master_remove_node('localhost', :master_port); \set VERBOSITY terse DROP SCHEMA citus_local_dist_joins CASCADE; diff --git a/src/test/regress/sql/citus_local_tables_mx.sql b/src/test/regress/sql/citus_local_tables_mx.sql index 86307cacb..2bb79a802 100644 --- a/src/test/regress/sql/citus_local_tables_mx.sql +++ b/src/test/regress/sql/citus_local_tables_mx.sql @@ -422,7 +422,7 @@ SELECT citus_add_local_table_to_metadata('view_tbl_1'); SELECT viewname, definition FROM pg_views WHERE viewname LIKE 'prop_view%' ORDER BY viewname; SELECT run_command_on_workers($$SELECT COUNT(*) FROM pg_views WHERE viewname LIKE 'prop_view%';$$); -SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid IN('viewsc.prop_view'::regclass::oid, 'viewsc.prop_view2'::regclass::oid); +SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid IN('viewsc.prop_view'::regclass::oid, 'viewsc.prop_view2'::regclass::oid) ORDER BY 1; -- drop views DROP VIEW viewsc.prop_view; DROP VIEW viewsc.prop_view2; @@ -441,7 +441,7 @@ SELECT run_command_on_workers($$SELECT COUNT(*) FROM pg_views WHERE viewname LIK SELECT citus_add_local_table_to_metadata('view_tbl_2'); -- verify both views are distributed SELECT run_command_on_workers($$SELECT COUNT(*) FROM pg_views WHERE viewname LIKE 'prop_view%';$$); -SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid IN('viewsc.prop_view3'::regclass::oid, 'viewsc.prop_view4'::regclass::oid); +SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid IN('viewsc.prop_view3'::regclass::oid, 'viewsc.prop_view4'::regclass::oid) ORDER BY 1; -- test with fkey cascading create table ref_tb(a int primary key); diff --git a/src/test/regress/sql/citus_schema_distribute_undistribute.sql b/src/test/regress/sql/citus_schema_distribute_undistribute.sql new file mode 100644 index 000000000..1008b90b2 --- /dev/null +++ b/src/test/regress/sql/citus_schema_distribute_undistribute.sql @@ -0,0 +1,437 @@ +SET citus.next_shard_id TO 1730000; +SET citus.shard_replication_factor TO 1; +SET client_min_messages TO WARNING; +SET citus.enable_schema_based_sharding TO off; + +SELECT 1 FROM citus_add_node('localhost', :master_port, groupid => 0); + +CREATE USER tenantuser superuser; +SET role tenantuser; + +-- check invalid input +SELECT citus_schema_distribute(1); +SELECT citus_schema_undistribute(1); + +-- noop +SELECT citus_schema_distribute(null); +SELECT citus_schema_undistribute(null); + +-- public and some others cannot be distributed as a tenant schema, but check what happens +-- if we try to call citus_schema_undistribute() for such a schema. +SELECT citus_schema_undistribute('public'); + +-- create non-tenant schema +CREATE SCHEMA citus_schema_distribute_undistribute; + +-- create tenant schema +CREATE SCHEMA tenant1; + +CREATE TABLE tenant1.table1(id int PRIMARY KEY, name text); +INSERT INTO tenant1.table1 SELECT i, 'asd'::text FROM generate_series(1,20) i; + +CREATE TABLE tenant1.table2(id int REFERENCES tenant1.table1(id), num bigint UNIQUE); +INSERT INTO tenant1.table2 SELECT i, i FROM generate_series(1,20) i; + +CREATE TABLE citus_schema_distribute_undistribute.ref(id int PRIMARY KEY); +SELECT create_reference_table('citus_schema_distribute_undistribute.ref'); +INSERT INTO citus_schema_distribute_undistribute.ref SELECT i FROM generate_series(1,100) i; + +-- autoconverted to Citus local table due to foreign key to reference table +CREATE TABLE tenant1.table3(id int REFERENCES citus_schema_distribute_undistribute.ref(id)); +INSERT INTO tenant1.table3 SELECT i FROM generate_series(1,100) i; + +-- Citus local table with autoconverted=false +CREATE TABLE tenant1.table3x(id int PRIMARY KEY REFERENCES citus_schema_distribute_undistribute.ref(id)); +SELECT citus_add_local_table_to_metadata('tenant1.table3x'); +INSERT INTO tenant1.table3x SELECT i FROM generate_series(1,100) i; + +-- foreign key to another local table in the same schema +CREATE TABLE tenant1.table3y(id int PRIMARY KEY REFERENCES tenant1.table3x(id)); +SELECT citus_add_local_table_to_metadata('tenant1.table3y'); +INSERT INTO tenant1.table3y SELECT i FROM generate_series(1,100) i; + +-- table with composite type +CREATE TYPE tenant1.catname AS ENUM ('baby', 'teen', 'mid'); +CREATE TYPE tenant1.agecat AS (below_age int, name tenant1.catname); +CREATE TABLE tenant1.table4(id int, age tenant1.agecat); + +-- create autoconverted partitioned table +CREATE TABLE tenant1.partitioned_table(id int REFERENCES citus_schema_distribute_undistribute.ref(id)) PARTITION BY RANGE(id); +CREATE TABLE tenant1.partition1 PARTITION OF tenant1.partitioned_table FOR VALUES FROM (1) TO (11); +CREATE TABLE tenant1.partition2 PARTITION OF tenant1.partitioned_table FOR VALUES FROM (11) TO (21); +INSERT INTO tenant1.partitioned_table SELECT i FROM generate_series(1,20) i; + +-- create view +CREATE VIEW tenant1.view1 AS SELECT * FROM tenant1.table1 JOIN tenant1.table2 USING(id); + +-- create view in regular schema +CREATE VIEW citus_schema_distribute_undistribute.view2 AS SELECT * FROM tenant1.view1; + +-- create materialized view +CREATE MATERIALIZED VIEW tenant1.view2 AS SELECT * FROM tenant1.table1; + +-- create collation +CREATE COLLATION citus_schema_distribute_undistribute.german_phonebook (provider = icu, locale = 'de-u-co-phonebk'); + +-- create type +CREATE TYPE citus_schema_distribute_undistribute.pair_type AS (a int, b int); + +-- Create function +CREATE FUNCTION citus_schema_distribute_undistribute.one_as_result() RETURNS INT LANGUAGE SQL AS +$$ + SELECT 1; +$$; + +-- create text search dictionary +CREATE TEXT SEARCH DICTIONARY citus_schema_distribute_undistribute.my_german_dict ( + template = snowball, + language = german, + stopwords = german +); + +-- create text search config +CREATE TEXT SEARCH CONFIGURATION citus_schema_distribute_undistribute.my_ts_config ( parser = default ); +ALTER TEXT SEARCH CONFIGURATION citus_schema_distribute_undistribute.my_ts_config ALTER MAPPING FOR asciiword WITH citus_schema_distribute_undistribute.my_german_dict; + +-- create sequence +CREATE SEQUENCE citus_schema_distribute_undistribute.seq; + +-- create complex table +CREATE TABLE tenant1.complextable (id int PRIMARY KEY default nextval('citus_schema_distribute_undistribute.seq'), col int default (citus_schema_distribute_undistribute.one_as_result()), myserial serial, phone text COLLATE citus_schema_distribute_undistribute.german_phonebook, initials citus_schema_distribute_undistribute.pair_type); +CREATE SEQUENCE tenant1.seq_owned OWNED BY tenant1.complextable.id; + +-- not allowed from workers +SELECT run_command_on_workers($$SELECT citus_schema_distribute('tenant1');$$); +SELECT run_command_on_workers($$SELECT citus_schema_undistribute('tenant1');$$); + +-- inherited table not allowed +CREATE TABLE citus_schema_distribute_undistribute.cities ( + name text, + population real, + elevation int +); +CREATE TABLE citus_schema_distribute_undistribute.capitals ( + state char(2) UNIQUE NOT NULL +) INHERITS (citus_schema_distribute_undistribute.cities); + +-- temporarily move "cities" into tenant1 (not a tenant schema yet) to test citus_schema_distribute() with a table that is inherited +ALTER TABLE citus_schema_distribute_undistribute.cities SET SCHEMA tenant1; +SELECT citus_schema_distribute('tenant1'); +ALTER TABLE tenant1.cities SET SCHEMA citus_schema_distribute_undistribute; + +-- temporarily move "capitals" into tenant1 (not a tenant schema yet) to test citus_schema_distribute() with a table that inherits +ALTER TABLE citus_schema_distribute_undistribute.capitals SET SCHEMA tenant1; +SELECT citus_schema_distribute('tenant1'); +ALTER TABLE tenant1.capitals SET SCHEMA citus_schema_distribute_undistribute; + +SELECT citus_schema_distribute('tenant1'); +SELECT citus_schema_undistribute('tenant1'); + +CREATE TABLE citus_schema_distribute_undistribute.illegal_partitioned_table(id int) PARTITION BY RANGE(id); +CREATE TABLE citus_schema_distribute_undistribute.illegal_partition1 PARTITION OF citus_schema_distribute_undistribute.illegal_partitioned_table FOR VALUES FROM (1) TO (11); + +-- temporarily move "illegal_partitioned_table" into tenant1 (not a tenant schema yet) to test citus_schema_distribute() with a partition table whose parent is created in another schema +ALTER TABLE citus_schema_distribute_undistribute.illegal_partitioned_table SET SCHEMA tenant1; +SELECT citus_schema_distribute('tenant1'); +ALTER TABLE tenant1.illegal_partitioned_table SET SCHEMA citus_schema_distribute_undistribute; + +-- temporarily move "illegal_partition1" into tenant1 (not a tenant schema yet) to test citus_schema_distribute() with a parent table whose partition is created in another schema +ALTER TABLE citus_schema_distribute_undistribute.illegal_partition1 SET SCHEMA tenant1; +SELECT citus_schema_distribute('tenant1'); +ALTER TABLE tenant1.illegal_partition1 SET SCHEMA citus_schema_distribute_undistribute; + +SELECT citus_schema_distribute('tenant1'); +SELECT citus_schema_undistribute('tenant1'); + +-- foreign key to a local table in another schema is not allowed +CREATE TABLE citus_schema_distribute_undistribute.tbl1(id int PRIMARY KEY); +CREATE TABLE tenant1.table3z(id int PRIMARY KEY REFERENCES citus_schema_distribute_undistribute.tbl1(id)); +SELECT citus_schema_distribute('tenant1'); +SELECT create_reference_table('citus_schema_distribute_undistribute.tbl1'); +SELECT citus_schema_distribute('tenant1'); +SELECT citus_schema_undistribute('tenant1'); + +-- foreign key to a distributed table in another schema is not allowed +CREATE TABLE citus_schema_distribute_undistribute.tbl2(id int PRIMARY KEY); +SELECT create_distributed_table('citus_schema_distribute_undistribute.tbl2','id'); +CREATE TABLE tenant1.table3w(id int PRIMARY KEY REFERENCES citus_schema_distribute_undistribute.tbl2(id)); +SELECT citus_schema_distribute('tenant1'); +DROP TABLE citus_schema_distribute_undistribute.tbl2 CASCADE; +SELECT citus_schema_distribute('tenant1'); +SELECT citus_schema_undistribute('tenant1'); + +-- foreign key from a local table in another schema is not allowed +CREATE TABLE tenant1.table3q(id int PRIMARY KEY); +CREATE TABLE citus_schema_distribute_undistribute.tbl3(id int PRIMARY KEY REFERENCES tenant1.table3q(id)); +SELECT citus_schema_distribute('tenant1'); +DROP TABLE citus_schema_distribute_undistribute.tbl3 CASCADE; +SELECT citus_schema_distribute('tenant1'); +SELECT citus_schema_undistribute('tenant1'); + +-- foreign key from a reference table in another schema is not allowed +CREATE TABLE tenant1.table3t(id int PRIMARY KEY); +CREATE TABLE citus_schema_distribute_undistribute.tbl4(id int PRIMARY KEY REFERENCES tenant1.table3t(id)); +SELECT create_reference_table('citus_schema_distribute_undistribute.tbl4'); +SELECT citus_schema_distribute('tenant1'); +DROP TABLE citus_schema_distribute_undistribute.tbl4 CASCADE; +SELECT citus_schema_distribute('tenant1'); +SELECT citus_schema_undistribute('tenant1'); + +-- only allowed for schema owner or superuser +CREATE USER dummyregular; +SET role dummyregular; +SELECT citus_schema_distribute('tenant1'); +SELECT citus_schema_undistribute('tenant1'); + +-- assign all tables to dummyregular except table5 +SET role tenantuser; +SELECT result FROM run_command_on_all_nodes($$ REASSIGN OWNED BY tenantuser TO dummyregular; $$); +CREATE TABLE tenant1.table5(id int); + +-- table owner check fails the distribution +SET role dummyregular; +SELECT citus_schema_distribute('tenant1'); + +-- alter table owner, then redistribute +SET role tenantuser; +ALTER TABLE tenant1.table5 OWNER TO dummyregular; +SET role dummyregular; +SELECT citus_schema_distribute('tenant1'); + +-- show the schema is a tenant schema now +SELECT colocationid AS tenant1_colocid FROM pg_dist_schema schemaid \gset +-- below query verifies the same colocationid in pg_dist_schema, pg_dist_colocation and all entries in pg_dist_partition at the same time +SELECT '$$' || + ' SELECT colocationid = ' || :tenant1_colocid || + ' FROM pg_dist_schema JOIN pg_dist_colocation USING(colocationid)' || + ' WHERE colocationid = ALL(SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE ''tenant1%'')' || + '$$' +AS verify_tenant_query \gset +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*) AS total FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1%' $$); +SELECT result FROM run_command_on_all_nodes(:verify_tenant_query); + +SELECT citus_schema_undistribute('tenant1'); + +-- show the schema is a regular schema +SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_schema $$); +-- below query verifies the tenant colocationid is removed from both pg_dist_colocation and all entries in pg_dist_partition at the same time +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$); +SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$); + +RESET role; +SELECT result FROM run_command_on_all_nodes($$ REASSIGN OWNED BY dummyregular TO tenantuser; $$); +DROP USER dummyregular; + +CREATE USER dummysuper superuser; +SET role dummysuper; +SELECT citus_schema_distribute('tenant1'); + +-- show the schema is a tenant schema now +SELECT colocationid AS tenant1_colocid FROM pg_dist_schema schemaid \gset +SELECT '$$' || + ' SELECT colocationid = ' || :tenant1_colocid || + ' FROM pg_dist_schema JOIN pg_dist_colocation USING(colocationid)' || + ' WHERE colocationid = ALL(SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE ''tenant1%'')' || + '$$' +AS verify_tenant_query \gset +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*) AS total FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1%' $$); +SELECT result FROM run_command_on_all_nodes(:verify_tenant_query); + +SELECT citus_schema_undistribute('tenant1'); + +-- show the schema is a regular schema +SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_schema $$); +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$); +SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$); + +RESET role; +DROP USER dummysuper; + +-- foreign table +CREATE TABLE tenant1.foreign_table_test (id integer NOT NULL, data text, a bigserial); +INSERT INTO tenant1.foreign_table_test SELECT i FROM generate_series(1,100) i; +CREATE EXTENSION postgres_fdw; +CREATE SERVER foreign_server + FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (host 'localhost', port :'master_port', dbname 'regression'); +CREATE USER MAPPING FOR CURRENT_USER + SERVER foreign_server + OPTIONS (user 'postgres'); +CREATE FOREIGN TABLE tenant1.foreign_table ( + id integer NOT NULL, + data text, + a bigserial +) + SERVER foreign_server + OPTIONS (schema_name 'tenant1', table_name 'foreign_table_test'); + +-- foreign table not allowed +SELECT citus_schema_distribute('tenant1'); +ALTER FOREIGN TABLE tenant1.foreign_table SET SCHEMA citus_schema_distribute_undistribute; +SELECT citus_schema_distribute('tenant1'); +SELECT citus_schema_undistribute('tenant1'); + +-- already have distributed table error +CREATE TABLE tenant1.dist(id int); +SELECT create_distributed_table('tenant1.dist', 'id'); +SELECT citus_schema_distribute('tenant1'); +SELECT undistribute_table('tenant1.dist'); +SELECT citus_schema_distribute('tenant1'); +SELECT citus_schema_undistribute('tenant1'); + +CREATE TABLE tenant1.ref2(id int); +SELECT create_reference_table('tenant1.ref2'); +SELECT citus_schema_distribute('tenant1'); +SELECT undistribute_table('tenant1.ref2'); +SELECT citus_schema_distribute('tenant1'); +SELECT citus_schema_undistribute('tenant1'); + +BEGIN; +SELECT citus_schema_distribute('tenant1'); +ROLLBACK; + +-- show the schema is a regular schema +SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_schema $$); +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$); +SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$); + +-- errors not a tenant schema +SELECT citus_schema_undistribute('tenant1'); + +-- make it a tenant schema +BEGIN; +SELECT citus_schema_distribute('tenant1'); +COMMIT; + +-- show the schema is a tenant schema now +SELECT colocationid AS tenant1_colocid FROM pg_dist_schema schemaid \gset +SELECT '$$' || + ' SELECT colocationid = ' || :tenant1_colocid || + ' FROM pg_dist_schema JOIN pg_dist_colocation USING(colocationid)' || + ' WHERE colocationid = ALL(SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE ''tenant1%'')' || + '$$' +AS verify_tenant_query \gset +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*) AS total FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1%' $$); +SELECT result FROM run_command_on_all_nodes(:verify_tenant_query); + +-- already a tenant schema notice +SET client_min_messages TO NOTICE; +SELECT citus_schema_distribute('tenant1'); +SET client_min_messages TO WARNING; + +-- convert back to a regular schema +SELECT citus_schema_undistribute('tenant1'); + +-- show the schema is a regular schema now +SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_schema $$); +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$); +SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$); + +-- tables still have valid data +SELECT COUNT(*) FROM tenant1.partitioned_table; +SELECT COUNT(*) FROM citus_schema_distribute_undistribute.foreign_table; +SELECT COUNT(*) FROM tenant1.table3; +TRUNCATE citus_schema_distribute_undistribute.ref CASCADE; +SELECT COUNT(*) FROM tenant1.table3; + +-- disallowed namespaces +SELECT citus_schema_distribute('public'); +SELECT citus_schema_distribute('pg_catalog'); +SELECT citus_schema_distribute('pg_toast'); +CREATE TEMP TABLE xx(id int); -- create a temp table in case we do not have any pg_temp_xx schema yet +SELECT nspname AS temp_schema_name FROM pg_namespace WHERE nspname LIKE 'pg_temp%' LIMIT 1 \gset +SELECT nspname AS temp_toast_schema_name FROM pg_namespace WHERE nspname LIKE 'pg_toast_temp%' LIMIT 1 \gset +SELECT citus_schema_distribute(:'temp_schema_name'); +SELECT citus_schema_distribute(:'temp_toast_schema_name'); +SELECT citus_schema_distribute('citus'); +CREATE SCHEMA extensionschema; +CREATE EXTENSION citext SCHEMA extensionschema; +SELECT citus_schema_distribute('extensionschema'); +DROP SCHEMA extensionschema CASCADE; +ALTER EXTENSION citus ADD TABLE tenant1.table1; +SELECT citus_schema_distribute('tenant1'); +ALTER EXTENSION citus DROP TABLE tenant1.table1; + +-- weird schema and table names +CREATE SCHEMA "CiTuS.TeeN"; +CREATE TABLE "CiTuS.TeeN"."TeeNTabLE.1!?!"(id int PRIMARY KEY, name text); +INSERT INTO "CiTuS.TeeN"."TeeNTabLE.1!?!" SELECT i, 'asd'::text FROM generate_series(1,20) i; +SELECT citus_schema_distribute('"CiTuS.TeeN"'); + +-- show the schema is a tenant schema now +SELECT colocationid AS tenant1_colocid FROM pg_dist_schema schemaid \gset +SELECT '$$' || + ' SELECT colocationid = ' || :tenant1_colocid || + ' FROM pg_dist_schema JOIN pg_dist_colocation USING(colocationid)' || + ' WHERE colocationid = ALL(SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE ''"CiTuS.TeeN"%'')' || + '$$' +AS verify_tenant_query \gset +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*) AS total FROM pg_dist_partition WHERE logicalrelid::text LIKE '"CiTuS.TeeN"%' $$); +SELECT result FROM run_command_on_all_nodes(:verify_tenant_query); + +SELECT citus_schema_undistribute('"CiTuS.TeeN"'); + +-- show the schema is a regular schema +SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_schema $$); +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$); +SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$); + +-- try setting the schema again after adding a distributed table into the schema. It should complain about distributed table. +CREATE TABLE tenant1.new_dist(id int); +SELECT create_distributed_table('tenant1.new_dist', 'id'); +SELECT citus_schema_distribute('tenant1'); +SELECT undistribute_table('tenant1.new_dist'); +SELECT citus_schema_distribute('tenant1'); +SELECT citus_schema_undistribute('tenant1'); + +-- try setting the schema again after adding a single shard table into the schema. It should complain about distributed table. +CREATE TABLE tenant1.single_shard_t(id int); +SELECT create_distributed_table('tenant1.single_shard_t', NULL); +SELECT citus_schema_distribute('tenant1'); +SELECT undistribute_table('tenant1.single_shard_t'); + +-- try setting the schema again. It should succeed now. +SELECT citus_schema_distribute('tenant1'); + +-- show the schema is a tenant schema now +SELECT colocationid AS tenant1_colocid FROM pg_dist_schema schemaid \gset +SELECT '$$' || + ' SELECT colocationid = ' || :tenant1_colocid || + ' FROM pg_dist_schema JOIN pg_dist_colocation USING(colocationid)' || + ' WHERE colocationid = ALL(SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE ''tenant1%'')' || + '$$' +AS verify_tenant_query \gset +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*) AS total FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1%' $$); +SELECT result FROM run_command_on_all_nodes(:verify_tenant_query); + +SELECT citus_schema_undistribute('tenant1'); + +-- show the schema is a regular schema now +SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_schema $$); +SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$); +SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$); + +-- create an empty tenant schema to verify colocation id is removed successfully after we undistribute it +CREATE SCHEMA empty_tenant; +SELECT citus_schema_distribute('empty_tenant'); + +-- show the schema is a tenant schema now +SELECT colocationid AS empty_tenant_colocid FROM pg_dist_schema schemaid \gset +SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace FROM pg_dist_colocation JOIN pg_dist_schema USING(colocationid) $$); + +SELECT citus_schema_undistribute('empty_tenant'); + +-- show the schema is a regular schema now +SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_schema $$); +SELECT '$$' || 'SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = ' || :empty_tenant_colocid || '$$' +AS verify_empty_tenant_query \gset +SELECT result FROM run_command_on_all_nodes(:verify_empty_tenant_query); + +-- cleanup +DROP SCHEMA "CiTuS.TeeN" CASCADE; +DROP SCHEMA tenant1 CASCADE; +DROP SCHEMA empty_tenant CASCADE; +DROP EXTENSION postgres_fdw CASCADE; +DROP SCHEMA citus_schema_distribute_undistribute CASCADE; +DROP USER tenantuser; +SELECT citus_remove_node('localhost', :master_port); diff --git a/src/test/regress/sql/citus_stat_tenants.sql b/src/test/regress/sql/citus_stat_tenants.sql index eb1e0af42..2e4ed1450 100644 --- a/src/test/regress/sql/citus_stat_tenants.sql +++ b/src/test/regress/sql/citus_stat_tenants.sql @@ -35,7 +35,10 @@ UPDATE dist_tbl SET b = a + 1 WHERE a = 3; UPDATE dist_tbl SET b = a + 1 WHERE a = 4; DELETE FROM dist_tbl WHERE a = 5; -SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants(true) ORDER BY tenant_attribute; +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period, + (cpu_usage_in_this_period>0) AS cpu_is_used_in_this_period, (cpu_usage_in_last_period>0) AS cpu_is_used_in_last_period +FROM citus_stat_tenants(true) +ORDER BY tenant_attribute; SELECT citus_stat_tenants_reset(); @@ -58,6 +61,8 @@ SELECT tenant_attribute, query_count_in_this_period FROM citus_stat_tenants(true SELECT nodeid AS worker_2_nodeid FROM pg_dist_node WHERE nodeport = :worker_2_port \gset SELECT count(*)>=0 FROM dist_tbl WHERE a = 2; +SELECT count(*)>=0 FROM dist_tbl WHERE a = 2; +SELECT count(*)>=0 FROM dist_tbl WHERE a = 3; SELECT count(*)>=0 FROM dist_tbl WHERE a = 3; SELECT count(*)>=0 FROM dist_tbl WHERE a = 4; SELECT count(*)>=0 FROM dist_tbl_text WHERE a = 'abcd'; @@ -78,23 +83,37 @@ SELECT count(*)>=0 FROM dist_tbl_text WHERE a = 'defg'; SELECT tenant_attribute, query_count_in_this_period, score FROM citus_stat_tenants(true) WHERE nodeid = :worker_2_nodeid ORDER BY score DESC, tenant_attribute; -- test period passing +\c - - - :worker_1_port + +SET search_path TO citus_stat_tenants; +SET citus.stat_tenants_period TO 2; SELECT citus_stat_tenants_reset(); +SELECT sleep_until_next_period(); SELECT count(*)>=0 FROM dist_tbl WHERE a = 1; INSERT INTO dist_tbl VALUES (5, 'abcd'); -\c - - - :worker_1_port -SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants_local ORDER BY tenant_attribute; +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period, + (cpu_usage_in_this_period>0) AS cpu_is_used_in_this_period, (cpu_usage_in_last_period>0) AS cpu_is_used_in_last_period +FROM citus_stat_tenants_local +ORDER BY tenant_attribute; -- simulate passing the period -SET citus.stat_tenants_period TO 2; SELECT sleep_until_next_period(); +SELECT pg_sleep(1); -SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants_local ORDER BY tenant_attribute; +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period, + (cpu_usage_in_this_period>0) AS cpu_is_used_in_this_period, (cpu_usage_in_last_period>0) AS cpu_is_used_in_last_period +FROM citus_stat_tenants_local +ORDER BY tenant_attribute; SELECT sleep_until_next_period(); +SELECT pg_sleep(1); -SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants_local ORDER BY tenant_attribute; +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period, + (cpu_usage_in_this_period>0) AS cpu_is_used_in_this_period, (cpu_usage_in_last_period>0) AS cpu_is_used_in_last_period +FROM citus_stat_tenants_local +ORDER BY tenant_attribute; \c - - - :master_port SET search_path TO citus_stat_tenants; @@ -146,11 +165,11 @@ SELECT count(*)>=0 FROM dist_tbl_text WHERE a = 'bcde*/'; SELECT count(*)>=0 FROM dist_tbl_text WHERE a = U&'\0061\0308bc'; \c - - - :worker_1_port -SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants ORDER BY tenant_attribute; +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants(true) ORDER BY tenant_attribute; \c - - - :worker_2_port SET search_path TO citus_stat_tenants; -SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants ORDER BY tenant_attribute; +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants(true) ORDER BY tenant_attribute; SELECT citus_stat_tenants_reset(); @@ -162,7 +181,12 @@ SELECT count(*)>=0 FROM dist_tbl_text WHERE a = '/bcde'; SELECT count(*)>=0 FROM dist_tbl_text WHERE a = U&'\0061\0308bc'; SELECT count(*)>=0 FROM dist_tbl_text WHERE a = 'bcde*'; -SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants_local ORDER BY tenant_attribute; +DELETE FROM dist_tbl_text WHERE a = '/b*c/de'; +DELETE FROM dist_tbl_text WHERE a = '/bcde'; +DELETE FROM dist_tbl_text WHERE a = U&'\0061\0308bc'; +DELETE FROM dist_tbl_text WHERE a = 'bcde*'; + +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants_local(true) ORDER BY tenant_attribute; -- test local cached queries & prepared statements @@ -181,7 +205,7 @@ EXECUTE dist_tbl_text_select_plan('/bcde'); EXECUTE dist_tbl_text_select_plan(U&'\0061\0308bc'); EXECUTE dist_tbl_text_select_plan('bcde*'); -SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants_local ORDER BY tenant_attribute; +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants_local(true) ORDER BY tenant_attribute; \c - - - :master_port SET search_path TO citus_stat_tenants; @@ -204,7 +228,7 @@ EXECUTE dist_tbl_text_select_plan('bcde*'); \c - - - :worker_2_port SET search_path TO citus_stat_tenants; -SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants ORDER BY tenant_attribute; +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants(true) ORDER BY tenant_attribute; \c - - - :master_port SET search_path TO citus_stat_tenants; @@ -235,5 +259,165 @@ SELECT count(*)>=0 FROM citus_stat_tenants_local(); RESET ROLE; DROP ROLE stats_non_superuser; +-- test function push down +CREATE OR REPLACE FUNCTION + select_from_dist_tbl_text(p_keyword text) +RETURNS boolean LANGUAGE plpgsql AS $fn$ +BEGIN + RETURN(SELECT count(*)>=0 FROM citus_stat_tenants.dist_tbl_text WHERE a = $1); +END; +$fn$; + +SELECT create_distributed_function( + 'select_from_dist_tbl_text(text)', 'p_keyword', colocate_with => 'dist_tbl_text' +); + +SELECT citus_stat_tenants_reset(); + +SELECT select_from_dist_tbl_text('/b*c/de'); +SELECT select_from_dist_tbl_text('/b*c/de'); +SELECT select_from_dist_tbl_text(U&'\0061\0308bc'); +SELECT select_from_dist_tbl_text(U&'\0061\0308bc'); + +SELECT tenant_attribute, query_count_in_this_period FROM citus_stat_tenants ORDER BY tenant_attribute; + +CREATE OR REPLACE PROCEDURE select_from_dist_tbl_text_proc( + p_keyword text +) +LANGUAGE plpgsql +AS $$ +BEGIN + PERFORM select_from_dist_tbl_text(p_keyword); + PERFORM count(*)>=0 FROM citus_stat_tenants.dist_tbl_text WHERE b < 0; + PERFORM count(*)>=0 FROM citus_stat_tenants.dist_tbl_text; + PERFORM count(*)>=0 FROM citus_stat_tenants.dist_tbl_text WHERE a = p_keyword; + COMMIT; +END;$$; + +CALL citus_stat_tenants.select_from_dist_tbl_text_proc('/b*c/de'); +CALL citus_stat_tenants.select_from_dist_tbl_text_proc('/b*c/de'); +CALL citus_stat_tenants.select_from_dist_tbl_text_proc('/b*c/de'); +CALL citus_stat_tenants.select_from_dist_tbl_text_proc(U&'\0061\0308bc'); +CALL citus_stat_tenants.select_from_dist_tbl_text_proc(U&'\0061\0308bc'); +CALL citus_stat_tenants.select_from_dist_tbl_text_proc(U&'\0061\0308bc'); +CALL citus_stat_tenants.select_from_dist_tbl_text_proc(NULL); + +SELECT tenant_attribute, query_count_in_this_period FROM citus_stat_tenants ORDER BY tenant_attribute; + +CREATE OR REPLACE VIEW + select_from_dist_tbl_text_view +AS + SELECT * FROM citus_stat_tenants.dist_tbl_text; + +SELECT count(*)>=0 FROM select_from_dist_tbl_text_view WHERE a = '/b*c/de'; +SELECT count(*)>=0 FROM select_from_dist_tbl_text_view WHERE a = '/b*c/de'; +SELECT count(*)>=0 FROM select_from_dist_tbl_text_view WHERE a = '/b*c/de'; +SELECT count(*)>=0 FROM select_from_dist_tbl_text_view WHERE a = U&'\0061\0308bc'; +SELECT count(*)>=0 FROM select_from_dist_tbl_text_view WHERE a = U&'\0061\0308bc'; +SELECT count(*)>=0 FROM select_from_dist_tbl_text_view WHERE a = U&'\0061\0308bc'; + +SELECT tenant_attribute, query_count_in_this_period FROM citus_stat_tenants ORDER BY tenant_attribute; + +-- single shard distributed table, which is not part of a tenant schema +SELECT citus_stat_tenants_reset(); + +SET citus.shard_replication_factor TO 1; +CREATE TABLE dist_tbl_text_single_shard(a text, b int); +select create_distributed_table('dist_tbl_text_single_shard', NULL); + +INSERT INTO dist_tbl_text_single_shard VALUES ('/b*c/de', 1); +SELECT count(*)>=0 FROM dist_tbl_text_single_shard WHERE a = '/b*c/de'; +DELETE FROM dist_tbl_text_single_shard WHERE a = '/b*c/de'; +UPDATE dist_tbl_text_single_shard SET b = 1 WHERE a = '/b*c/de'; + +SELECT tenant_attribute, query_count_in_this_period FROM citus_stat_tenants; + +-- schema based tenants +SELECT citus_stat_tenants_reset(); + +SET citus.enable_schema_based_sharding TO ON; + +CREATE SCHEMA citus_stat_tenants_t1; +CREATE TABLE citus_stat_tenants_t1.users(id int); + +SELECT id FROM citus_stat_tenants_t1.users WHERE id = 2; +INSERT INTO citus_stat_tenants_t1.users VALUES (1); +UPDATE citus_stat_tenants_t1.users SET id = 2 WHERE id = 1; +DELETE FROM citus_stat_tenants_t1.users WHERE id = 2; + +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants ORDER BY tenant_attribute; + +SELECT citus_stat_tenants_reset(); + +PREPARE schema_tenant_insert_plan (int) AS insert into citus_stat_tenants_t1.users values ($1); +EXECUTE schema_tenant_insert_plan(1); + +PREPARE schema_tenant_select_plan (int) AS SELECT count(*) > 1 FROM citus_stat_tenants_t1.users where Id = $1; +EXECUTE schema_tenant_select_plan(1); + +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants ORDER BY tenant_attribute; + +SELECT citus_stat_tenants_reset(); + +-- local execution & prepared statements +\c - - - :worker_2_port +SET search_path TO citus_stat_tenants; + +PREPARE schema_tenant_insert_plan (int) AS insert into citus_stat_tenants_t1.users values ($1); +EXECUTE schema_tenant_insert_plan(1); +EXECUTE schema_tenant_insert_plan(1); +EXECUTE schema_tenant_insert_plan(1); +EXECUTE schema_tenant_insert_plan(1); +EXECUTE schema_tenant_insert_plan(1); + +PREPARE schema_tenant_select_plan (int) AS SELECT count(*) > 1 FROM citus_stat_tenants_t1.users where Id = $1; +EXECUTE schema_tenant_select_plan(1); +EXECUTE schema_tenant_select_plan(1); +EXECUTE schema_tenant_select_plan(1); +EXECUTE schema_tenant_select_plan(1); +EXECUTE schema_tenant_select_plan(1); + +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants ORDER BY tenant_attribute; + +\c - - - :master_port +SET search_path TO citus_stat_tenants; + +SET citus.enable_schema_based_sharding TO OFF; + +SELECT citus_stat_tenants_reset(); + +-- test sampling +-- set rate to 0 to disable sampling +SELECT result FROM run_command_on_all_nodes('ALTER SYSTEM set citus.stat_tenants_untracked_sample_rate to 0;'); +SELECT result FROM run_command_on_all_nodes('SELECT pg_reload_conf()'); + +INSERT INTO dist_tbl VALUES (1, 'abcd'); +INSERT INTO dist_tbl VALUES (2, 'abcd'); +UPDATE dist_tbl SET b = a + 1 WHERE a = 3; +UPDATE dist_tbl SET b = a + 1 WHERE a = 4; +DELETE FROM dist_tbl WHERE a = 5; + +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period FROM citus_stat_tenants ORDER BY tenant_attribute; + +-- test sampling +-- set rate to 1 to track all tenants +SELECT result FROM run_command_on_all_nodes('ALTER SYSTEM set citus.stat_tenants_untracked_sample_rate to 1;'); +SELECT result FROM run_command_on_all_nodes('SELECT pg_reload_conf()'); + +SELECT sleep_until_next_period(); +SELECT pg_sleep(0.1); + +INSERT INTO dist_tbl VALUES (1, 'abcd'); +INSERT INTO dist_tbl VALUES (2, 'abcd'); +UPDATE dist_tbl SET b = a + 1 WHERE a = 3; +UPDATE dist_tbl SET b = a + 1 WHERE a = 4; +DELETE FROM dist_tbl WHERE a = 5; + +SELECT tenant_attribute, read_count_in_this_period, read_count_in_last_period, query_count_in_this_period, query_count_in_last_period, + (cpu_usage_in_this_period>0) AS cpu_is_used_in_this_period, (cpu_usage_in_last_period>0) AS cpu_is_used_in_last_period +FROM citus_stat_tenants(true) +ORDER BY tenant_attribute; + SET client_min_messages TO ERROR; DROP SCHEMA citus_stat_tenants CASCADE; +DROP SCHEMA citus_stat_tenants_t1 CASCADE; diff --git a/src/test/regress/sql/columnar_permissions.sql b/src/test/regress/sql/columnar_permissions.sql index 11492bfed..d0f26bc96 100644 --- a/src/test/regress/sql/columnar_permissions.sql +++ b/src/test/regress/sql/columnar_permissions.sql @@ -75,7 +75,7 @@ select relation, stripe_num, row_count, first_row_number select relation, stripe_num, attr_num, chunk_group_num, value_count from columnar.chunk where relation in ('no_access'::regclass, 'columnar_permissions'::regclass) - order by relation, stripe_num; + order by relation, stripe_num, attr_num; select relation, stripe_num, chunk_group_num, row_count from columnar.chunk_group where relation in ('no_access'::regclass, 'columnar_permissions'::regclass) diff --git a/src/test/regress/sql/cpu_priority.sql b/src/test/regress/sql/cpu_priority.sql index beb156fa8..ec2206f6f 100644 --- a/src/test/regress/sql/cpu_priority.sql +++ b/src/test/regress/sql/cpu_priority.sql @@ -63,9 +63,6 @@ SET search_path TO cpu_priority; -- in their CREATE SUBSCRIPTION commands. SET citus.log_remote_commands TO ON; SET citus.grep_remote_commands = '%CREATE SUBSCRIPTION%'; --- We disable binary protocol, so we have consistent output between PG13 and --- PG14, beacuse PG13 doesn't support binary logical replication. -SET citus.enable_binary_protocol = false; SELECT master_move_shard_placement(11568900, 'localhost', :worker_1_port, 'localhost', :worker_2_port, 'force_logical'); SET citus.cpu_priority_for_logical_replication_senders = 15; SELECT master_move_shard_placement(11568900, 'localhost', :worker_2_port, 'localhost', :worker_1_port, 'force_logical'); diff --git a/src/test/regress/sql/create_single_shard_table.sql b/src/test/regress/sql/create_single_shard_table.sql new file mode 100644 index 000000000..8df785429 --- /dev/null +++ b/src/test/regress/sql/create_single_shard_table.sql @@ -0,0 +1,1248 @@ +CREATE SCHEMA create_single_shard_table; +SET search_path TO create_single_shard_table; + +SET citus.next_shard_id TO 1720000; +SET citus.shard_count TO 32; +SET citus.shard_replication_factor TO 1; + +SELECT 1 FROM citus_remove_node('localhost', :worker_1_port); +SELECT 1 FROM citus_remove_node('localhost', :worker_2_port); + +CREATE TABLE add_node_test(a int, "b" text); + +-- add a node before creating the single-shard table +SELECT 1 FROM citus_add_node('localhost', :worker_1_port); + +SELECT create_distributed_table('add_node_test', null, colocate_with=>'none', distribution_type=>null); + +-- add a node after creating the single-shard table +SELECT 1 FROM citus_add_node('localhost', :worker_2_port); + +-- make sure that table is created on the worker nodes added before/after create_distributed_table +SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=1 FROM pg_class WHERE relnamespace = 'create_single_shard_table'::regnamespace AND + relname='add_node_test' +$$); + +-- and check the metadata tables + +SELECT result FROM run_command_on_workers($$ + SELECT (partmethod, partkey, repmodel, autoconverted) FROM pg_dist_partition + WHERE logicalrelid = 'create_single_shard_table.add_node_test'::regclass +$$); + +SELECT result FROM run_command_on_workers($$ + SELECT (shardstorage, shardminvalue, shardmaxvalue) FROM pg_dist_shard + WHERE logicalrelid = 'create_single_shard_table.add_node_test'::regclass +$$); + +SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=1 FROM pg_dist_placement + WHERE shardid = ( + SELECT shardid FROM pg_dist_shard + WHERE logicalrelid = 'create_single_shard_table.add_node_test'::regclass + ); +$$); + +SELECT result FROM run_command_on_workers($$ + SELECT (shardcount, replicationfactor, distributioncolumntype, distributioncolumncollation) FROM pg_dist_colocation + WHERE colocationid = ( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'create_single_shard_table.add_node_test'::regclass + ); +$$); + +SET client_min_messages TO WARNING; +SELECT 1 FROM citus_add_node('localhost', :master_port, groupid => 0); + +SET client_min_messages TO NOTICE; + +CREATE TABLE invalid_configs_1(a int primary key); +SELECT create_distributed_table('invalid_configs_1', null, shard_count=>2); +SELECT create_distributed_table('invalid_configs_1', null, shard_count=>1); + +CREATE TABLE nullkey_c1_t1(a int, b int); +CREATE TABLE nullkey_c1_t2(a int, b int); +CREATE TABLE nullkey_c1_t3(a int, b int); +SELECT create_distributed_table('nullkey_c1_t1', null, colocate_with=>'none'); + +SELECT colocationid AS nullkey_c1_t1_colocationid FROM pg_dist_partition WHERE logicalrelid = 'create_single_shard_table.nullkey_c1_t1'::regclass \gset + +BEGIN; + DROP TABLE nullkey_c1_t1; + -- make sure that we delete the colocation group after dropping the last table that belongs to it + SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = :'nullkey_c1_t1_colocationid'; +ROLLBACK; + +SELECT create_distributed_table('nullkey_c1_t2', null, colocate_with=>'nullkey_c1_t1'); +SELECT create_distributed_table('nullkey_c1_t3', null, colocate_with=>'nullkey_c1_t1', distribution_type=>'append'); +SELECT create_distributed_table('nullkey_c1_t3', null, colocate_with=>'nullkey_c1_t1'); + +CREATE TABLE nullkey_c2_t1(a int, b int); +CREATE TABLE nullkey_c2_t2(a int, b int); +CREATE TABLE nullkey_c2_t3(a int, b int); + +-- create_distributed_table_concurrently is not yet supported yet +SELECT create_distributed_table_concurrently('nullkey_c2_t1', null); +SELECT create_distributed_table_concurrently('nullkey_c2_t1', null, colocate_with=>'none'); + +SELECT create_distributed_table('nullkey_c2_t1', null, colocate_with=>'none'); +SELECT create_distributed_table('nullkey_c2_t2', null, colocate_with=>'nullkey_c2_t1', distribution_type=>'hash'); -- distribution_type is ignored anyway +SELECT create_distributed_table('nullkey_c2_t3', null, colocate_with=>'nullkey_c2_t2', distribution_type=>null); + +-- check the metadata for the colocated tables whose names start with nullkey_c1_ + +SELECT logicalrelid, partmethod, partkey, repmodel, autoconverted FROM pg_dist_partition +WHERE logicalrelid IN ( + SELECT oid FROM pg_class + WHERE relnamespace = 'create_single_shard_table'::regnamespace AND + relname LIKE 'nullkey_c1_%' +) +ORDER BY 1; + +-- make sure that all those 3 tables belong to same colocation group +SELECT COUNT(*) FROM pg_dist_partition +WHERE logicalrelid IN ( + SELECT oid FROM pg_class + WHERE relnamespace = 'create_single_shard_table'::regnamespace AND + relname LIKE 'nullkey_c1_%' +) +GROUP BY colocationid; + +SELECT logicalrelid, shardstorage, shardminvalue, shardmaxvalue FROM pg_dist_shard +WHERE logicalrelid IN ( + SELECT oid FROM pg_class + WHERE relnamespace = 'create_single_shard_table'::regnamespace AND + relname LIKE 'nullkey_c1_%' +) +ORDER BY 1; + +-- make sure that all those 3 shards are created on the same node group +SELECT COUNT(*) FROM pg_dist_placement +WHERE shardid IN ( + SELECT shardid FROM pg_dist_shard + WHERE logicalrelid IN ( + SELECT oid FROM pg_class + WHERE relnamespace = 'create_single_shard_table'::regnamespace AND + relname LIKE 'nullkey_c1_%' + ) +) +GROUP BY groupid; + +-- check the metadata for the colocated tables whose names start with nullkey_c2_ + +SELECT logicalrelid, partmethod, partkey, repmodel, autoconverted FROM pg_dist_partition +WHERE logicalrelid IN ( + SELECT oid FROM pg_class + WHERE relnamespace = 'create_single_shard_table'::regnamespace AND + relname LIKE 'nullkey_c2_%' +) +ORDER BY 1; + +-- make sure that all those 3 tables belong to same colocation group +SELECT COUNT(*) FROM pg_dist_partition +WHERE logicalrelid IN ( + SELECT oid FROM pg_class + WHERE relnamespace = 'create_single_shard_table'::regnamespace AND + relname LIKE 'nullkey_c2_%' +) +GROUP BY colocationid; + +SELECT logicalrelid, shardstorage, shardminvalue, shardmaxvalue FROM pg_dist_shard +WHERE logicalrelid IN ( + SELECT oid FROM pg_class + WHERE relnamespace = 'create_single_shard_table'::regnamespace AND + relname LIKE 'nullkey_c2_%' +) +ORDER BY 1; + +-- make sure that all those 3 shards created on the same node group +SELECT COUNT(*) FROM pg_dist_placement +WHERE shardid IN ( + SELECT shardid FROM pg_dist_shard + WHERE logicalrelid IN ( + SELECT oid FROM pg_class + WHERE relnamespace = 'create_single_shard_table'::regnamespace AND + relname LIKE 'nullkey_c2_%' + ) +) +GROUP BY groupid; + +-- Make sure that the colocated tables whose names start with nullkey_c1_ +-- belong to a different colocation group than the ones whose names start +-- with nullkey_c2_. +-- +-- It's ok to only compare nullkey_c1_t1 and nullkey_c2_t1 because we already +-- verified that null_dist_key.nullkey_c1_t1 is colocated with the other two +-- and null_dist_key.nullkey_c2_t1 is colocated with the other two. +SELECT +( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'create_single_shard_table.nullkey_c1_t1'::regclass +) +!= +( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'create_single_shard_table.nullkey_c2_t1'::regclass +); + +-- Since we determine node for the placement based on the module of colocation id, +-- we don't expect those two colocation groups to get assigned to same node. +SELECT +( + SELECT groupid FROM pg_dist_placement + WHERE shardid = ( + SELECT shardid FROM pg_dist_shard + WHERE logicalrelid = 'create_single_shard_table.nullkey_c1_t1'::regclass + ) +) +!= +( + SELECT groupid FROM pg_dist_placement + WHERE shardid = ( + SELECT shardid FROM pg_dist_shard + WHERE logicalrelid = 'create_single_shard_table.nullkey_c2_t1'::regclass + ) +); + +-- It's ok to only check nullkey_c1_t1 and nullkey_c2_t1 because we already +-- verified that null_dist_key.nullkey_c1_t1 is colocated with the other two +-- and null_dist_key.nullkey_c2_t1 is colocated with the other two. +SELECT shardcount, replicationfactor, distributioncolumntype, distributioncolumncollation FROM pg_dist_colocation +WHERE colocationid = ( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'create_single_shard_table.nullkey_c1_t1'::regclass +); + +SELECT shardcount, replicationfactor, distributioncolumntype, distributioncolumncollation FROM pg_dist_colocation +WHERE colocationid = ( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'create_single_shard_table.nullkey_c2_t1'::regclass +); + +CREATE TABLE round_robin_test_c1(a int, b int); +SELECT create_distributed_table('round_robin_test_c1', null, colocate_with=>'none', distribution_type=>null); + +\c - - - :master_port +SET search_path TO create_single_shard_table; +SET citus.next_shard_id TO 1730000; +SET citus.shard_count TO 32; +SET citus.shard_replication_factor TO 1; +SET client_min_messages TO NOTICE; + +CREATE TABLE round_robin_test_c2(a int, b int); +SELECT create_distributed_table('round_robin_test_c2', null, colocate_with=>'none', distribution_type=>null); + +-- Since we determine node for the placement based on the module of colocation id, +-- we don't expect those two colocation groups to get assigned to same node even +-- after reconnecting to the coordinator. +SELECT +( + SELECT groupid FROM pg_dist_placement + WHERE shardid = ( + SELECT shardid FROM pg_dist_shard + WHERE logicalrelid = 'create_single_shard_table.round_robin_test_c1'::regclass + ) +) +!= +( + SELECT groupid FROM pg_dist_placement + WHERE shardid = ( + SELECT shardid FROM pg_dist_shard + WHERE logicalrelid = 'create_single_shard_table.round_robin_test_c2'::regclass + ) +); + +CREATE TABLE distributed_table(a int, b int); + +-- cannot colocate a sharded table with single-shard table +SELECT create_distributed_table('distributed_table', 'a', colocate_with=>'nullkey_c1_t1'); + +CREATE TABLE reference_table(a int, b int); +CREATE TABLE local(a int, b int); +SELECT create_reference_table('reference_table'); +SELECT create_distributed_table('distributed_table', 'a'); + +-- cannot colocate single-shard tables with other table types +CREATE TABLE cannot_colocate_with_other_types (a int, b int); +SELECT create_distributed_table('cannot_colocate_with_other_types', null, colocate_with=>'reference_table'); +SELECT create_distributed_table('cannot_colocate_with_other_types', null, colocate_with=>'distributed_table'); +SELECT create_distributed_table('cannot_colocate_with_other_types', null, colocate_with=>'local'); -- postgres local + +SELECT citus_add_local_table_to_metadata('local'); + +-- cannot colocate single-shard tables with citus local tables +SELECT create_distributed_table('cannot_colocate_with_other_types', null, colocate_with=>'local'); -- citus local + +SET client_min_messages TO WARNING; + +-- can't create such a distributed table from another Citus table, except Citus local tables +SELECT create_distributed_table('reference_table', null, colocate_with=>'none'); +SELECT create_distributed_table('distributed_table', null, colocate_with=>'none'); +SELECT create_distributed_table('local', null, colocate_with=>'none'); + +BEGIN; + -- creating a single-shard table from a temporary table is not supported + CREATE TEMPORARY TABLE temp_table (a int); + SELECT create_distributed_table('temp_table', null, colocate_with=>'none', distribution_type=>null); +ROLLBACK; + +-- creating a single-shard table from a catalog table is not supported +SELECT create_distributed_table('pg_catalog.pg_index', NULL, distribution_type=>null); + +-- creating a single-shard table from an unlogged table is supported +CREATE UNLOGGED TABLE unlogged_table (a int); +SELECT create_distributed_table('unlogged_table', null, colocate_with=>'none', distribution_type=>null); + +-- creating a single-shard table from a foreign table is not supported +CREATE FOREIGN TABLE foreign_table ( + id bigint not null, + full_name text not null default '' +) SERVER fake_fdw_server OPTIONS (encoding 'utf-8', compression 'true', table_name 'foreign_table'); +SELECT create_distributed_table('foreign_table', null, colocate_with=>'none', distribution_type=>null); + +-- create a single-shard table that has no tuples +CREATE TABLE null_dist_key_table_1 (a int primary key); +SELECT create_distributed_table('null_dist_key_table_1', null, colocate_with=>'none'); + +-- create a single-shard table that has some tuples +CREATE TABLE null_dist_key_table_2(a int primary key); +INSERT INTO null_dist_key_table_2 VALUES(1); +SELECT create_distributed_table('null_dist_key_table_2', null, colocate_with=>'none'); + +SELECT * FROM null_dist_key_table_2 ORDER BY a; + +DROP TABLE null_dist_key_table_1, null_dist_key_table_2; + +-- create indexes before creating the single-shard tables + +-- .. for an initially empty table +CREATE TABLE null_dist_key_table_1(a int, b int); +CREATE STATISTICS s1 (dependencies) ON a, b FROM null_dist_key_table_1; +CREATE INDEX null_dist_key_table_1_idx ON null_dist_key_table_1(a); +SELECT create_distributed_table('null_dist_key_table_1', null, colocate_with=>'none'); +CREATE STATISTICS s2 (dependencies) ON a, b FROM null_dist_key_table_1; + +-- .. and for another table having data in it before creating single-shard table +CREATE TABLE null_dist_key_table_2(a int); +INSERT INTO null_dist_key_table_2 VALUES(1); +CREATE INDEX null_dist_key_table_2_idx ON null_dist_key_table_2(a); +SELECT create_distributed_table('null_dist_key_table_2', null, colocate_with=>'none'); + +-- test create index concurrently, then reindex +CREATE INDEX CONCURRENTLY ind_conc ON null_dist_key_table_2(a); +REINDEX INDEX ind_conc; +REINDEX INDEX CONCURRENTLY ind_conc; +DROP INDEX ind_conc; + +SELECT * FROM null_dist_key_table_2 ORDER BY a; + +-- show that we do not support inheritance relationships +CREATE TABLE parent_table (a int, b text); +CREATE TABLE child_table () INHERITS (parent_table); + +-- both of below should error out +SELECT create_distributed_table('parent_table', null, colocate_with=>'none'); +SELECT create_distributed_table('child_table', null, colocate_with=>'none'); + +-- show that we support policies +BEGIN; + CREATE TABLE null_dist_key_table_3 (table_user text); + + ALTER TABLE null_dist_key_table_3 ENABLE ROW LEVEL SECURITY; + + CREATE ROLE table_users; + CREATE POLICY table_policy ON null_dist_key_table_3 TO table_users + USING (table_user = current_user); + + GRANT ALL ON TABLE null_dist_key_table_3 TO table_users; + ALTER TABLE null_dist_key_table_3 OWNER TO table_users; + + SELECT create_distributed_table('null_dist_key_table_3', null, colocate_with=>'none'); + + REVOKE ALL ON TABLE null_dist_key_table_3 FROM table_users; + ALTER TABLE null_dist_key_table_3 OWNER TO postgres; + GRANT ALL ON TABLE null_dist_key_table_3 TO table_users; +ROLLBACK; + +ALTER STATISTICS s2 SET STATISTICS 46; +ALTER TABLE null_dist_key_table_1 SET SCHEMA public; +DROP STATISTICS s1, s2; + +-- drop them for next tests +DROP TABLE public.null_dist_key_table_1, null_dist_key_table_2, distributed_table; + +-- tests for object names that should be escaped properly + +CREATE SCHEMA "NULL_!_dist_key"; + +CREATE TABLE "NULL_!_dist_key"."my_TABLE.1!?!"(id int, "Second_Id" int); +SELECT create_distributed_table('"NULL_!_dist_key"."my_TABLE.1!?!"', null, colocate_with=>'none'); + +-- drop the table before creating it when the search path is set +SET search_path to "NULL_!_dist_key" ; +DROP TABLE "my_TABLE.1!?!"; + +CREATE TYPE int_jsonb_type AS (key int, value jsonb); +CREATE DOMAIN age_with_default AS int CHECK (value >= 0) DEFAULT 0; +CREATE TYPE yes_no_enum AS ENUM ('yes', 'no'); +CREATE EXTENSION btree_gist; + +CREATE SEQUENCE my_seq_1 START WITH 10; + +CREATE TABLE "Table?!.1Table"( + id int PRIMARY KEY, + "Second_Id" int, + "local_Type" int_jsonb_type, + "jsondata" jsonb NOT NULL, + name text, + price numeric CHECK (price > 0), + age_with_default_col age_with_default, + yes_no_enum_col yes_no_enum, + seq_col_1 bigserial, + seq_col_2 int DEFAULT nextval('my_seq_1'), + generated_column int GENERATED ALWAYS AS (seq_col_1 * seq_col_2 + 4) STORED, + UNIQUE (id, price), + EXCLUDE USING GIST (name WITH =)); + +-- create some objects before create_distributed_table +CREATE INDEX "my!Index1" ON "Table?!.1Table"(id) WITH ( fillfactor = 80 ) WHERE id > 10; +CREATE INDEX text_index ON "Table?!.1Table"(name); +CREATE UNIQUE INDEX uniqueIndex ON "Table?!.1Table" (id); +CREATE STATISTICS stats_1 ON id, price FROM "Table?!.1Table"; + +CREATE TEXT SEARCH CONFIGURATION text_search_cfg (parser = default); +CREATE INDEX text_search_index ON "Table?!.1Table" +USING gin (to_tsvector('text_search_cfg'::regconfig, (COALESCE(name, ''::character varying))::text)); + +-- ingest some data before create_distributed_table +INSERT INTO "Table?!.1Table" VALUES (1, 1, (1, row_to_json(row(1,1)))::int_jsonb_type, row_to_json(row(1,1), true)), + (2, 1, (2, row_to_json(row(2,2)))::int_jsonb_type, row_to_json(row(2,2), 'false')); + +-- create a replica identity before create_distributed_table +ALTER TABLE "Table?!.1Table" REPLICA IDENTITY USING INDEX uniqueIndex; + +SELECT create_distributed_table('"Table?!.1Table"', null, colocate_with=>'none'); + +INSERT INTO "Table?!.1Table" VALUES (10, 15, (150, row_to_json(row(4,8)))::int_jsonb_type, '{}', 'text_1', 10, 27, 'yes', 60, 70); +INSERT INTO "Table?!.1Table" VALUES (5, 5, (5, row_to_json(row(5,5)))::int_jsonb_type, row_to_json(row(5,5), true)); + +-- tuples that are supposed to violate different data type / check constraints +INSERT INTO "Table?!.1Table"(id, jsondata, name) VALUES (101, '{"a": 1}', 'text_1'); +INSERT INTO "Table?!.1Table"(id, jsondata, price) VALUES (101, '{"a": 1}', -1); +INSERT INTO "Table?!.1Table"(id, jsondata, age_with_default_col) VALUES (101, '{"a": 1}', -1); +INSERT INTO "Table?!.1Table"(id, jsondata, yes_no_enum_col) VALUES (101, '{"a": 1}', 'what?'); + +SELECT * FROM "Table?!.1Table" ORDER BY id; + +SET search_path TO create_single_shard_table; + +-- create a partitioned table with some columns that +-- are going to be dropped within the tests +CREATE TABLE sensors( + col_to_drop_1 text, + measureid integer, + eventdatetime date, + measure_data jsonb, +PRIMARY KEY (measureid, eventdatetime, measure_data)) +PARTITION BY RANGE(eventdatetime); + +-- drop column even before attaching any partitions +ALTER TABLE sensors DROP COLUMN col_to_drop_1; + +CREATE TABLE sensors_2000 PARTITION OF sensors FOR VALUES FROM ('2000-01-01') TO ('2001-01-01'); + +-- cannot distribute child table without distributing the parent +SELECT create_distributed_table('sensors_2000', NULL, distribution_type=>null); + +SELECT create_distributed_table('sensors', NULL, distribution_type=>null); + +-- verify we can create new partitions after distributing the parent table +CREATE TABLE sensors_2001 PARTITION OF sensors FOR VALUES FROM ('2001-01-01') TO ('2002-01-01'); + +-- verify we can attach to a single-shard table +CREATE TABLE sensors_2002 (measureid integer, eventdatetime date, measure_data jsonb, PRIMARY KEY (measureid, eventdatetime, measure_data)); +ALTER TABLE sensors ATTACH PARTITION sensors_2002 FOR VALUES FROM ('2002-01-01') TO ('2003-01-01'); + +-- verify we can detach from a single-shard table +ALTER TABLE sensors DETACH PARTITION sensors_2001; + +-- error out when attaching a noncolocated partition +CREATE TABLE sensors_2003 (measureid integer, eventdatetime date, measure_data jsonb, PRIMARY KEY (measureid, eventdatetime, measure_data)); +SELECT create_distributed_table('sensors_2003', NULL, distribution_type=>null, colocate_with=>'none'); +ALTER TABLE sensors ATTACH PARTITION sensors_2003 FOR VALUES FROM ('2003-01-01') TO ('2004-01-01'); +DROP TABLE sensors_2003; + +-- verify we can attach after distributing, if the parent and partition are colocated +CREATE TABLE sensors_2004 (measureid integer, eventdatetime date, measure_data jsonb, PRIMARY KEY (measureid, eventdatetime, measure_data)); +SELECT create_distributed_table('sensors_2004', NULL, distribution_type=>null, colocate_with=>'sensors'); +ALTER TABLE sensors ATTACH PARTITION sensors_2004 FOR VALUES FROM ('2004-01-01') TO ('2005-01-01'); + +-- verify we can attach a citus local table +CREATE TABLE sensors_2005 (measureid integer, eventdatetime date, measure_data jsonb, PRIMARY KEY (measureid, eventdatetime, measure_data)); +SELECT citus_add_local_table_to_metadata('sensors_2005'); +ALTER TABLE sensors ATTACH PARTITION sensors_2005 FOR VALUES FROM ('2005-01-01') TO ('2006-01-01'); + +-- check metadata +-- check all partitions and the parent on pg_dist_partition +SELECT logicalrelid::text FROM pg_dist_partition WHERE logicalrelid::text IN ('sensors', 'sensors_2000', 'sensors_2001', 'sensors_2002', 'sensors_2004', 'sensors_2005') ORDER BY logicalrelid::text; +-- verify they are all colocated +SELECT COUNT(DISTINCT(colocationid)) FROM pg_dist_partition WHERE logicalrelid::text IN ('sensors', 'sensors_2000', 'sensors_2001', 'sensors_2002', 'sensors_2004', 'sensors_2005'); +-- verify all partitions are placed on the same node +SELECT COUNT(DISTINCT(groupid)) FROM pg_dist_placement WHERE shardid IN + (SELECT shardid FROM pg_dist_shard WHERE logicalrelid::text IN ('sensors', 'sensors_2000', 'sensors_2001', 'sensors_2002', 'sensors_2004', 'sensors_2005')); + +-- verify the shard of sensors_2000 is attached to the parent shard, on the worker node +SELECT COUNT(*) FROM run_command_on_workers($$ + SELECT relpartbound FROM pg_class WHERE relname LIKE 'sensors_2000_1______';$$) + WHERE length(result) > 0; + +-- verify the shard of sensors_2001 is detached from the parent shard, on the worker node +SELECT COUNT(*) FROM run_command_on_workers($$ + SELECT relpartbound FROM pg_class WHERE relname LIKE 'sensors_2001_1______';$$) + WHERE length(result) > 0; + +-- verify the shard of sensors_2002 is attached to the parent shard, on the worker node +SELECT COUNT(*) FROM run_command_on_workers($$ + SELECT relpartbound FROM pg_class WHERE relname LIKE 'sensors_2002_1______';$$) + WHERE length(result) > 0; + +-- create a partitioned citus local table and verify we error out when attaching a partition with single-shard +CREATE TABLE partitioned_citus_local_tbl( + measureid integer, + eventdatetime date, + measure_data jsonb, +PRIMARY KEY (measureid, eventdatetime, measure_data)) +PARTITION BY RANGE(eventdatetime); +SELECT citus_add_local_table_to_metadata('partitioned_citus_local_tbl'); +CREATE TABLE partition_with_null_key (measureid integer, eventdatetime date, measure_data jsonb, PRIMARY KEY (measureid, eventdatetime, measure_data)); +SELECT create_distributed_table('partition_with_null_key', NULL, distribution_type=>null); +ALTER TABLE partitioned_citus_local_tbl ATTACH PARTITION partition_with_null_key FOR VALUES FROM ('2004-01-01') TO ('2005-01-01'); + +-- test partitioned tables + indexes with long names +CREATE TABLE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"( + id int PRIMARY KEY, + "TeNANt_Id" int, + "jsondata" jsonb NOT NULL, + name text, + price numeric CHECK (price > 0), + serial_data bigserial, UNIQUE (id, price)) + PARTITION BY LIST(id); + +CREATE TABLE "NULL_!_dist_key"."partition1_nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + PARTITION OF "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + FOR VALUES IN (1); +CREATE TABLE "NULL_!_dist_key"."partition2_nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + PARTITION OF "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + FOR VALUES IN (2); +CREATE TABLE "NULL_!_dist_key"."partition100_nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + PARTITION OF "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + FOR VALUES IN (100); + +-- create some objects before create_distributed_table +CREATE INDEX "my!Index1New" ON "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"(id) WITH ( fillfactor = 80 ) WHERE id > 10; +CREATE UNIQUE INDEX uniqueIndexNew ON "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" (id); + +-- ingest some data before create_distributed_table +set client_min_messages to ERROR; +INSERT INTO "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" VALUES (1, 1, row_to_json(row(1,1), true)), + (2, 1, row_to_json(row(2,2), 'false')); +reset client_min_messages; +-- create a replica identity before create_distributed_table +ALTER TABLE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" REPLICA IDENTITY USING INDEX uniqueIndexNew; + +-- test triggers +SET client_min_messages TO ERROR; +CREATE FUNCTION insert_id_100() RETURNS trigger AS $insert_100$ +BEGIN + INSERT INTO "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" VALUES (100, 1, row_to_json(row(1,1), true)); + RETURN NEW; +END; +$insert_100$ LANGUAGE plpgsql; + +CREATE TABLE null_key_table_with_trigger(a INT); +SELECT create_distributed_table('null_key_table_with_trigger', null); +-- try to add a trigger after distributing the table, fails +CREATE TRIGGER insert_100_trigger + AFTER UPDATE ON null_key_table_with_trigger + FOR EACH STATEMENT EXECUTE FUNCTION insert_id_100(); + +-- now try to distribute a table that already has a trigger on it +CREATE TRIGGER insert_100_trigger + AFTER UPDATE ON "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + FOR EACH STATEMENT EXECUTE FUNCTION insert_id_100(); + +-- error out because of the trigger +SELECT create_distributed_table('"NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"', null); + +SET citus.enable_unsafe_triggers TO ON; +RESET client_min_messages; + +-- this shouldn't give any syntax errors +SELECT create_distributed_table('"NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"', null); + +-- now we can add triggers on distributed tables, because we set the GUC to on +CREATE TRIGGER insert_100_trigger_2 + AFTER UPDATE ON null_key_table_with_trigger + FOR EACH STATEMENT EXECUTE FUNCTION insert_id_100(); + +SET client_min_messages TO ERROR; +UPDATE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" SET "TeNANt_Id"="TeNANt_Id"+1; +-- we should see one row with id = 100 +SELECT COUNT(*) FROM "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" WHERE id = 100; + +-- create some objects after create_distributed_table +CREATE INDEX "my!Index2New" ON "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"(id) WITH ( fillfactor = 90 ) WHERE id < 20; +CREATE UNIQUE INDEX uniqueIndex2New ON "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"(id); + +-- error out for already existing, because of the unique index +INSERT INTO "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" VALUES (1, 1, row_to_json(row(1,1), true)); + +-- verify all 4 shard indexes are created on the same node +SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*) FROM pg_indexes WHERE indexname LIKE '%my!Index_New_1%' OR indexname LIKE '%uniqueindex%new_1%';$$) + ORDER BY nodeport; + +-- foreign key to a ref table +CREATE TABLE dummy_reference_table (a INT PRIMARY KEY); +SELECT create_reference_table('dummy_reference_table'); +TRUNCATE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"; +ALTER TABLE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + ADD CONSTRAINT fkey_to_dummy_ref FOREIGN KEY (id) REFERENCES dummy_reference_table(a); +BEGIN; -- try to add the same fkey, reversed + ALTER TABLE dummy_reference_table + ADD CONSTRAINT fkey_to_dummy_ref FOREIGN KEY (a) REFERENCES "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"(id); +ROLLBACK; + +-- errors out because of foreign key violation +INSERT INTO "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" VALUES (100, 1, row_to_json(row(1,1), true)); + +-- now inserts successfully +INSERT INTO dummy_reference_table VALUES (100); +INSERT INTO "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" VALUES (100, 1, row_to_json(row(1,1), true)); +DELETE FROM "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" WHERE id = 100; + +-- foreign key to a local table, errors out +CREATE TABLE local_table_for_fkey (a INT PRIMARY KEY); +ALTER TABLE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + ADD CONSTRAINT fkey_to_dummy_local FOREIGN KEY (id) REFERENCES local_table_for_fkey(a); + +-- foreign key from a local table +ALTER TABLE local_table_for_fkey + ADD CONSTRAINT fkey_from_dummy_local FOREIGN KEY (a) REFERENCES "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"(id); + +SELECT create_distributed_table('local_table_for_fkey', null, colocate_with=>'none'); +SELECT create_distributed_table('local_table_for_fkey', null, colocate_with=>'"NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"'); + +-- foreign key to a citus local table, errors out +CREATE TABLE citus_local_table_for_fkey (a INT PRIMARY KEY); +SELECT citus_add_local_table_to_metadata('citus_local_table_for_fkey'); +ALTER TABLE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + ADD CONSTRAINT fkey_to_dummy_citus_local FOREIGN KEY (id) REFERENCES citus_local_table_for_fkey(a); +-- reversed, still fails +ALTER TABLE citus_local_table_for_fkey + ADD CONSTRAINT fkey_from_dummy_citus_local FOREIGN KEY (a) REFERENCES "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"(id); + +-- foreign key to a distributed table, errors out because not colocated +CREATE TABLE dist_table_for_fkey (a INT PRIMARY KEY); +SELECT create_distributed_table('dist_table_for_fkey', 'a'); +ALTER TABLE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + ADD CONSTRAINT fkey_to_dummy_dist FOREIGN KEY (id) REFERENCES dist_table_for_fkey(a); +-- reversed, still fails +ALTER TABLE dist_table_for_fkey + ADD CONSTRAINT fkey_to_dummy_dist FOREIGN KEY (a) REFERENCES "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"(id); + +-- create a null key distributed table, not colocated with the partitioned table, and then try to create a fkey +CREATE TABLE null_key_dist_not_colocated (a INT PRIMARY KEY); +SELECT create_distributed_table('null_key_dist_not_colocated', null, colocate_with=>'none'); +ALTER TABLE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + ADD CONSTRAINT fkey_to_dummy_dist FOREIGN KEY (id) REFERENCES null_key_dist_not_colocated(a); + +-- create a null key distributed table, colocated with the partitioned table, and then create a fkey +CREATE TABLE null_key_dist (a INT PRIMARY KEY); +SELECT create_distributed_table('null_key_dist', null, colocate_with=>'"NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"'); +ALTER TABLE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" + ADD CONSTRAINT fkey_to_dummy_dist FOREIGN KEY (id) REFERENCES null_key_dist(a); + +-- check supported ON DELETE and ON UPDATE commands +ALTER TABLE null_key_dist ADD CONSTRAINT fkey_add_test_1 FOREIGN KEY(a) + REFERENCES "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"(id) ON DELETE SET DEFAULT; +ALTER TABLE null_key_dist ADD CONSTRAINT fkey_add_test_2 FOREIGN KEY(a) + REFERENCES "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789"(id) ON UPDATE CASCADE; +ALTER TABLE null_key_dist ADD CONSTRAINT fkey_add_test_3 FOREIGN KEY(a) + REFERENCES dummy_reference_table(a) ON DELETE SET DEFAULT; +ALTER TABLE null_key_dist ADD CONSTRAINT fkey_add_test_4 FOREIGN KEY(a) + REFERENCES dummy_reference_table(a) ON UPDATE CASCADE; + +ALTER TABLE null_key_dist DROP CONSTRAINT fkey_add_test_1; +ALTER TABLE null_key_dist DROP CONSTRAINT fkey_add_test_2; +ALTER TABLE null_key_dist DROP CONSTRAINT fkey_add_test_3; + +-- mimic a conjoined django command +CREATE FUNCTION drop_fkey_4() + RETURNS void + LANGUAGE plpgsql +AS $function$ +BEGIN + EXECUTE $$ + SET CONSTRAINTS fkey_add_test_4 IMMEDIATE; + ALTER TABLE null_key_dist DROP CONSTRAINT fkey_add_test_4; + $$; +END +$function$; +SELECT drop_fkey_4(); + +ALTER TABLE "NULL_!_dist_key"."nullKeyTable.1!?!9012345678901234567890123456789012345678901234567890123456789" DROP CONSTRAINT fkey_to_dummy_dist; + +-- create a view that depends on the single-shard table +CREATE VIEW public.v1 AS SELECT * FROM null_key_dist; +SELECT * FROM public.v1; + +DELETE FROM null_key_dist; +VACUUM null_key_dist; +TRUNCATE null_key_dist; +DROP TABLE null_key_dist CASCADE; + +RESET client_min_messages; + +CREATE TABLE multi_level_partitioning_parent( + measureid integer, + eventdatetime date, + measure_data jsonb) +PARTITION BY RANGE(eventdatetime); + +CREATE TABLE multi_level_partitioning_level_1( + measureid integer, + eventdatetime date, + measure_data jsonb) +PARTITION BY RANGE(eventdatetime); + +ALTER TABLE multi_level_partitioning_parent ATTACH PARTITION multi_level_partitioning_level_1 +FOR VALUES FROM ('2000-01-01') TO ('2001-01-01'); + +CREATE TABLE multi_level_partitioning_level_2 PARTITION OF multi_level_partitioning_level_1 +FOR VALUES FROM ('2000-01-01') TO ('2000-06-06'); + +-- multi-level partitioning is not supported +SELECT create_distributed_table('multi_level_partitioning_parent', NULL, distribution_type=>null); + +CREATE FUNCTION normalize_generate_always_as_error(query text) RETURNS void AS $$ +BEGIN + EXECUTE query; + EXCEPTION WHEN OTHERS THEN + IF SQLERRM LIKE 'cannot insert into column %' OR + SQLERRM LIKE 'cannot insert a non-DEFAULT value into column %' + THEN + RAISE 'cannot insert a non-DEFAULT value into column'; + ELSE + RAISE 'unknown error'; + END IF; +END; +$$LANGUAGE plpgsql; + +CREATE TABLE identity_test ( + a int GENERATED BY DEFAULT AS IDENTITY (START WITH 10 INCREMENT BY 10), + b bigint GENERATED ALWAYS AS IDENTITY (START WITH 100 INCREMENT BY 100), + c bigint GENERATED BY DEFAULT AS IDENTITY (START WITH 1000 INCREMENT BY 1000) +); + +SELECT create_distributed_table('identity_test', NULL, distribution_type=>null); + +INSERT INTO identity_test (a) VALUES (DEFAULT) RETURNING a; +SELECT result FROM run_command_on_workers($$ + INSERT INTO create_single_shard_table.identity_test (a) VALUES (DEFAULT) +$$); + +DROP TABLE identity_test; + +CREATE TABLE identity_test ( + a bigint GENERATED BY DEFAULT AS IDENTITY (START WITH 10 INCREMENT BY 10), + b bigint GENERATED ALWAYS AS IDENTITY (START WITH 100 INCREMENT BY 100), + c bigint GENERATED BY DEFAULT AS IDENTITY (START WITH 1000 INCREMENT BY 1000) +); + +SELECT create_distributed_table('identity_test', NULL, distribution_type=>null); + +INSERT INTO identity_test (a) VALUES (5); + +SELECT normalize_generate_always_as_error($$INSERT INTO identity_test (b) VALUES (5)$$); -- fails due to missing OVERRIDING SYSTEM VALUE +INSERT INTO identity_test (b) OVERRIDING SYSTEM VALUE VALUES (5); + +INSERT INTO identity_test (c) VALUES (5); + +SELECT result, success FROM run_command_on_workers($$ + INSERT INTO create_single_shard_table.identity_test (a) VALUES (6) +$$); + +SELECT result, success FROM run_command_on_workers($$ + SELECT create_single_shard_table.normalize_generate_always_as_error('INSERT INTO create_single_shard_table.identity_test (b) VALUES (1)') +$$); + +-- This should fail due to missing OVERRIDING SYSTEM VALUE. +SELECT result, success FROM run_command_on_workers($$ + SELECT create_single_shard_table.normalize_generate_always_as_error('INSERT INTO create_single_shard_table.identity_test (a, b) VALUES (1, 1)') +$$); + +SELECT result, success FROM run_command_on_workers($$ + INSERT INTO create_single_shard_table.identity_test (a, b) OVERRIDING SYSTEM VALUE VALUES (7, 7) +$$); + +SELECT result, success FROM run_command_on_workers($$ + INSERT INTO create_single_shard_table.identity_test (c, a) OVERRIDING SYSTEM VALUE VALUES (8, 8) +$$); + +-- test foreign keys + +CREATE TABLE referenced_table(a int UNIQUE, b int); +CREATE TABLE referencing_table(a int, b int, + FOREIGN KEY (a) REFERENCES referenced_table(a)); + +-- to a colocated single-shard table +BEGIN; + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'referenced_table'); + + INSERT INTO referenced_table VALUES (1, 1); + INSERT INTO referencing_table VALUES (1, 2); + + -- fails + INSERT INTO referencing_table VALUES (2, 2); +ROLLBACK; + +-- to a non-colocated single-shard table +BEGIN; + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'none'); +ROLLBACK; + +-- to a sharded table +BEGIN; + SELECT create_distributed_table('referenced_table', 'a'); + + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null); +ROLLBACK; + +-- to a reference table +BEGIN; + SELECT create_reference_table('referenced_table'); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null); + + INSERT INTO referenced_table VALUES (1, 1); + INSERT INTO referencing_table VALUES (1, 2); + + -- fails + INSERT INTO referencing_table VALUES (2, 2); +ROLLBACK; + +-- to a citus local table +BEGIN; + SELECT citus_add_local_table_to_metadata('referenced_table', cascade_via_foreign_keys=>true); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null); +ROLLBACK; + +-- to a postgres table +SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null); + +-- from a reference table +BEGIN; + SELECT create_reference_table('referencing_table'); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); +ROLLBACK; + +BEGIN; + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + SELECT create_reference_table('referencing_table'); +ROLLBACK; + +-- from a sharded table +BEGIN; + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + SELECT create_distributed_table('referencing_table', 'a'); +ROLLBACK; + +-- from a citus local table +BEGIN; + SELECT citus_add_local_table_to_metadata('referencing_table', cascade_via_foreign_keys=>true); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); +ROLLBACK; + +BEGIN; + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + SELECT citus_add_local_table_to_metadata('referencing_table', cascade_via_foreign_keys=>true); +ROLLBACK; + +-- from a postgres table (only useful to preserve legacy behavior) +BEGIN; + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); +ROLLBACK; + +-- make sure that we enforce the foreign key constraint when inserting from workers too +SELECT create_reference_table('referenced_table'); +SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null); +INSERT INTO referenced_table VALUES (1, 1); +-- ok +SELECT result, success FROM run_command_on_workers($$ + INSERT INTO create_single_shard_table.referencing_table VALUES (1, 2) +$$); +-- fails +SELECT result, success FROM run_command_on_workers($$ + INSERT INTO create_single_shard_table.referencing_table VALUES (2, 2) +$$); + +DROP TABLE referencing_table, referenced_table; + +CREATE TABLE self_fkey_test(a int UNIQUE, b int, + FOREIGN KEY (b) REFERENCES self_fkey_test(a), + FOREIGN KEY (a) REFERENCES self_fkey_test(a)); +SELECT create_distributed_table('self_fkey_test', NULL, distribution_type=>null); + +INSERT INTO self_fkey_test VALUES (1, 1); -- ok +INSERT INTO self_fkey_test VALUES (2, 3); -- fails + +-- similar foreign key tests but this time create the referencing table later on + +-- referencing table is a single-shard table + +-- to a colocated single-shard table +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a)); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'referenced_table'); + + INSERT INTO referenced_table VALUES (1, 1); + INSERT INTO referencing_table VALUES (1, 2); + + -- fails + INSERT INTO referencing_table VALUES (2, 2); +ROLLBACK; + +BEGIN; + CREATE TABLE referenced_table(a int, b int, UNIQUE(b, a)); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a, b) REFERENCES referenced_table(b, a)); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'referenced_table'); + + INSERT INTO referenced_table VALUES (1, 2); + INSERT INTO referencing_table VALUES (2, 1); + + -- fails + INSERT INTO referencing_table VALUES (1, 2); +ROLLBACK; + +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a) ON UPDATE SET NULL); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'referenced_table'); + + INSERT INTO referenced_table VALUES (1, 1); + INSERT INTO referencing_table VALUES (1, 2); + + UPDATE referenced_table SET a = 5; + SELECT * FROM referencing_table; +ROLLBACK; + +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + + CREATE TABLE referencing_table(a serial, b int, FOREIGN KEY (a) REFERENCES referenced_table(a) ON UPDATE SET DEFAULT); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'referenced_table'); +ROLLBACK; + +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + + CREATE TABLE referencing_table(a serial, b int); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'referenced_table'); + ALTER TABLE referencing_table ADD CONSTRAINT fkey_to_dummy_ref_on_update FOREIGN KEY (a) REFERENCES referenced_table(a) ON UPDATE SET DEFAULT; +ROLLBACK; + +-- to a non-colocated single-shard table +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null); + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a)); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'none'); +ROLLBACK; + +-- to a sharded table +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_distributed_table('referenced_table', 'a'); + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a)); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'none'); +ROLLBACK; + +-- to a reference table +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_reference_table('referenced_table'); + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a)); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'none'); + + INSERT INTO referenced_table VALUES (1, 1); + INSERT INTO referencing_table VALUES (1, 2); + + -- fails + INSERT INTO referencing_table VALUES (2, 2); +ROLLBACK; + +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_reference_table('referenced_table'); + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a) ON DELETE CASCADE); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'none'); + + INSERT INTO referenced_table VALUES (1, 1); + INSERT INTO referencing_table VALUES (1, 2); + + DELETE FROM referenced_table CASCADE; + SELECT * FROM referencing_table; +ROLLBACK; + +-- to a citus local table +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT citus_add_local_table_to_metadata('referenced_table'); + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a)); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'none'); +ROLLBACK; + +-- to a postgres table +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a)); + SELECT create_distributed_table('referencing_table', NULL, distribution_type=>null, colocate_with=>'none'); +ROLLBACK; + +-- referenced table is a single-shard table + +-- from a sharded table +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null, colocate_with=>'none'); + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a)); + SELECT create_distributed_table('referencing_table', 'a'); +ROLLBACK; + +-- from a reference table +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null, colocate_with=>'none'); + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a)); + SELECT create_reference_table('referencing_table'); +ROLLBACK; + +-- from a citus local table +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null, colocate_with=>'none'); + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a)); + SELECT citus_add_local_table_to_metadata('referencing_table', cascade_via_foreign_keys=>true); +ROLLBACK; + +-- from a postgres table (only useful to preserve legacy behavior) +BEGIN; + CREATE TABLE referenced_table(a int UNIQUE, b int); + SELECT create_distributed_table('referenced_table', NULL, distribution_type=>null, colocate_with=>'none'); + + CREATE TABLE referencing_table(a int, b int, FOREIGN KEY (a) REFERENCES referenced_table(a)); +ROLLBACK; + +-- Test whether we switch to sequential execution to enforce foreign +-- key restrictions. + +CREATE TABLE referenced_table(id int PRIMARY KEY, value_1 int); +SELECT create_reference_table('referenced_table'); + +CREATE TABLE referencing_table(id int PRIMARY KEY, value_1 int, CONSTRAINT fkey FOREIGN KEY(value_1) REFERENCES referenced_table(id) ON UPDATE CASCADE); +SELECT create_distributed_table('referencing_table', null, colocate_with=>'none', distribution_type=>null); + +SET client_min_messages TO DEBUG1; + +BEGIN; + -- Switches to sequential execution because referenced_table is a reference table + -- and referenced by a single-shard table. + -- + -- Given that we cannot do parallel access on a single-shard table, this is not useful. + -- However, this is already what we're doing for, e.g., a foreign key from a + -- reference table to another reference table. + TRUNCATE referenced_table CASCADE; + SELECT COUNT(*) FROM referencing_table; +COMMIT; + +BEGIN; + SELECT COUNT(*) FROM referencing_table; + -- Doesn't fail because the SELECT didn't perform parallel execution. + TRUNCATE referenced_table CASCADE; +COMMIT; + +BEGIN; + UPDATE referencing_table SET value_1 = 15; + -- Doesn't fail because the UPDATE didn't perform parallel execution. + TRUNCATE referenced_table CASCADE; +COMMIT; + +BEGIN; + SELECT COUNT(*) FROM referenced_table; + -- doesn't switch to sequential execution + ALTER TABLE referencing_table ADD COLUMN X INT; +ROLLBACK; + +BEGIN; + -- Switches to sequential execution because referenced_table is a reference table + -- and referenced by a single-shard table. + -- + -- Given that we cannot do parallel access on a single-shard table, this is not useful. + -- However, this is already what we're doing for, e.g., a foreign key from a + -- reference table to another reference table. + UPDATE referenced_table SET id = 101 WHERE id = 99; + UPDATE referencing_table SET value_1 = 15; +ROLLBACK; + +BEGIN; + UPDATE referencing_table SET value_1 = 15; + -- Doesn't fail because prior UPDATE didn't perform parallel execution. + UPDATE referenced_table SET id = 101 WHERE id = 99; +ROLLBACK; + +SET client_min_messages TO WARNING; + +DROP TABLE referenced_table, referencing_table; + +-- Test whether we unnecessarily switch to sequential execution +-- when the referenced relation is a single-shard table. + +CREATE TABLE referenced_table(id int PRIMARY KEY, value_1 int); +SELECT create_distributed_table('referenced_table', null, colocate_with=>'none', distribution_type=>null); + +CREATE TABLE referencing_table(id int PRIMARY KEY, value_1 int, CONSTRAINT fkey FOREIGN KEY(value_1) REFERENCES referenced_table(id) ON UPDATE CASCADE); +SELECT create_distributed_table('referencing_table', null, colocate_with=>'referenced_table', distribution_type=>null); + +SET client_min_messages TO DEBUG1; + +BEGIN; + SELECT COUNT(*) FROM referenced_table; + -- Doesn't switch to sequential execution because the referenced_table is + -- a single-shard table. + ALTER TABLE referencing_table ADD COLUMN X INT; +ROLLBACK; + +BEGIN; + -- Doesn't switch to sequential execution because the referenced_table is + -- a single-shard table. + TRUNCATE referenced_table CASCADE; + SELECT COUNT(*) FROM referencing_table; +COMMIT; + +SET client_min_messages TO WARNING; + +CREATE FUNCTION increment_value() RETURNS trigger AS $increment_value$ +BEGIN + NEW.value := NEW.value+1; + RETURN NEW; +END; +$increment_value$ LANGUAGE plpgsql; + +CREATE TABLE trigger_table_1 (value int); + +CREATE TRIGGER trigger_1 +BEFORE INSERT ON trigger_table_1 +FOR EACH ROW EXECUTE FUNCTION increment_value(); + +SELECT create_distributed_table('trigger_table_1', NULL, distribution_type=>null); + +INSERT INTO trigger_table_1 VALUES(1), (2); +SELECT * FROM trigger_table_1 ORDER BY value; + +CREATE FUNCTION insert_some() RETURNS trigger AS $insert_some$ +BEGIN + RAISE NOTICE 'inserted some rows'; + RETURN NEW; +END; +$insert_some$ LANGUAGE plpgsql; + +CREATE TABLE trigger_table_2 (value int); + +CREATE TRIGGER trigger_2 +AFTER INSERT ON trigger_table_2 +FOR EACH STATEMENT EXECUTE FUNCTION insert_some(); + +ALTER TABLE trigger_table_2 DISABLE TRIGGER trigger_2; + +SELECT create_distributed_table('trigger_table_2', NULL, distribution_type=>null); + +SET client_min_messages TO NOTICE; +INSERT INTO trigger_table_2 VALUES(3), (4); +SET client_min_messages TO WARNING; + +SELECT * FROM trigger_table_2 ORDER BY value; + +CREATE FUNCTION combine_old_new_val() RETURNS trigger AS $combine_old_new_val$ +BEGIN + NEW.value = NEW.value * 10 + OLD.value; + RETURN NEW; +END; +$combine_old_new_val$ LANGUAGE plpgsql; + +CREATE FUNCTION notice_truncate() RETURNS trigger AS $notice_truncate$ +BEGIN + RAISE NOTICE 'notice_truncate()'; + RETURN NEW; +END; +$notice_truncate$ LANGUAGE plpgsql; + +CREATE TABLE trigger_table_3 (value int); + +CREATE TRIGGER trigger_3 +BEFORE UPDATE ON trigger_table_3 +FOR EACH ROW EXECUTE FUNCTION combine_old_new_val(); + +CREATE TRIGGER trigger_4 +AFTER TRUNCATE ON trigger_table_3 +FOR EACH STATEMENT EXECUTE FUNCTION notice_truncate(); + +INSERT INTO trigger_table_3 VALUES(3), (4); + +SELECT create_distributed_table('trigger_table_3', NULL, distribution_type=>null); + +UPDATE trigger_table_3 SET value = 5; +SELECT * FROM trigger_table_3 ORDER BY value; + +SET client_min_messages TO NOTICE; +TRUNCATE trigger_table_3; +SET client_min_messages TO WARNING; + +-- test rename, disable and drop trigger +ALTER TRIGGER trigger_4 ON trigger_table_3 RENAME TO trigger_new_name; +ALTER TABLE trigger_table_3 DISABLE TRIGGER ALL; +DROP TRIGGER trigger_new_name ON trigger_table_3; +-- enable the remaining triggers +ALTER TABLE trigger_table_3 ENABLE TRIGGER ALL; + +-- try a few simple queries at least to make sure that we don't crash +BEGIN; + INSERT INTO nullkey_c1_t1 SELECT * FROM nullkey_c2_t1; +ROLLBACK; + +DROP TRIGGER IF EXISTS trigger_1 ON trigger_table_1; +DROP TRIGGER trigger_2 ON trigger_table_2 CASCADE; +DROP TRIGGER trigger_3 ON trigger_table_3 RESTRICT; + +-- cleanup at exit +SET client_min_messages TO ERROR; +DROP SCHEMA create_single_shard_table, "NULL_!_dist_key" CASCADE; diff --git a/src/test/regress/sql/distributed_functions.sql b/src/test/regress/sql/distributed_functions.sql index b155cf986..9eb2fe730 100644 --- a/src/test/regress/sql/distributed_functions.sql +++ b/src/test/regress/sql/distributed_functions.sql @@ -419,6 +419,11 @@ FROM pg_dist_partition, pg_catalog.pg_dist_object as objects WHERE pg_dist_partition.logicalrelid = 'replicated_table_func_test_4'::regclass AND objects.objid = 'eq_with_param_names(macaddr, macaddr)'::regprocedure; +-- a function cannot be colocated with a single shard distributed table when a distribution column is provided +SELECT create_distributed_table('replicated_table_func_test_3', null); +SELECT create_distributed_function('eq_with_param_names(macaddr, macaddr)', 'val1', colocate_with:='replicated_table_func_test_3'); +SELECT undistribute_table('replicated_table_func_test_3'); + -- a function cannot be colocated with a reference table when a distribution column is provided SELECT create_reference_table('replicated_table_func_test_3'); SELECT create_distributed_function('eq_with_param_names(macaddr, macaddr)', 'val1', colocate_with:='replicated_table_func_test_3'); @@ -691,7 +696,7 @@ DROP SCHEMA function_tests CASCADE; DROP SCHEMA function_tests2 CASCADE; -- clear objects -SELECT stop_metadata_sync_to_node(nodename,nodeport) FROM pg_dist_node WHERE isactive AND noderole = 'primary'; +SELECT stop_metadata_sync_to_node(nodename,nodeport) FROM pg_dist_node WHERE isactive AND noderole = 'primary' AND groupid <> 0; -- This is hacky, but we should clean-up the resources as below \c - - - :worker_1_port @@ -704,7 +709,9 @@ TRUNCATE pg_dist_node; SET client_min_messages TO ERROR; DROP USER functionuser; +DROP ROLE r1; + SELECT 1 FROM run_command_on_workers($$DROP USER functionuser$$); -- sync metadata again -SELECT start_metadata_sync_to_node(nodename,nodeport) FROM pg_dist_node WHERE isactive AND noderole = 'primary'; +SELECT start_metadata_sync_to_node(nodename,nodeport) FROM pg_dist_node WHERE isactive AND noderole = 'primary' AND groupid <> 0; diff --git a/src/test/regress/sql/failure_add_disable_node.sql b/src/test/regress/sql/failure_add_disable_node.sql index 75a92456c..167439549 100644 --- a/src/test/regress/sql/failure_add_disable_node.sql +++ b/src/test/regress/sql/failure_add_disable_node.sql @@ -97,6 +97,8 @@ WHERE s.logicalrelid = 'user_table'::regclass AND n.isactive ORDER BY placementid; -- reset cluster to original state +ALTER SEQUENCE pg_dist_node_nodeid_seq RESTART 2; +ALTER SEQUENCE pg_dist_groupid_seq RESTART 2; SELECT citus.mitmproxy('conn.allow()'); SELECT master_add_node('localhost', :worker_2_proxy_port); diff --git a/src/test/regress/sql/failure_create_distributed_table_concurrently.sql b/src/test/regress/sql/failure_create_distributed_table_concurrently.sql index 502c3940a..3fd2a217d 100644 --- a/src/test/regress/sql/failure_create_distributed_table_concurrently.sql +++ b/src/test/regress/sql/failure_create_distributed_table_concurrently.sql @@ -15,6 +15,9 @@ SET citus.shard_replication_factor TO 1; SET citus.max_adaptive_executor_pool_size TO 1; SELECT pg_backend_pid() as pid \gset +ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 222222; +ALTER SEQUENCE pg_catalog.pg_dist_placement_placementid_seq RESTART 333333; + -- make sure coordinator is in the metadata SELECT citus_set_coordinator_host('localhost', 57636); @@ -108,3 +111,5 @@ SELECT * FROM pg_dist_shard WHERE logicalrelid = 'table_1'::regclass; DROP SCHEMA create_dist_tbl_con CASCADE; SET search_path TO default; SELECT citus_remove_node('localhost', 57636); +ALTER SEQUENCE pg_dist_node_nodeid_seq RESTART 3; +ALTER SEQUENCE pg_dist_groupid_seq RESTART 3; diff --git a/src/test/regress/sql/failure_mx_metadata_sync_multi_trans.sql b/src/test/regress/sql/failure_mx_metadata_sync_multi_trans.sql index 0540827e7..c0e575c14 100644 --- a/src/test/regress/sql/failure_mx_metadata_sync_multi_trans.sql +++ b/src/test/regress/sql/failure_mx_metadata_sync_multi_trans.sql @@ -18,11 +18,36 @@ SET client_min_messages TO ERROR; CREATE ROLE foo1; CREATE ROLE foo2; +-- Create collation +CREATE COLLATION german_phonebook (provider = icu, locale = 'de-u-co-phonebk'); + +-- Create type +CREATE TYPE pair_type AS (a int, b int); + +-- Create function +CREATE FUNCTION one_as_result() RETURNS INT LANGUAGE SQL AS +$$ + SELECT 1; +$$; + +-- Create text search dictionary +CREATE TEXT SEARCH DICTIONARY my_german_dict ( + template = snowball, + language = german, + stopwords = german +); + +-- Create text search config +CREATE TEXT SEARCH CONFIGURATION my_ts_config ( parser = default ); +ALTER TEXT SEARCH CONFIGURATION my_ts_config ALTER MAPPING FOR asciiword WITH my_german_dict; + -- Create sequence CREATE SEQUENCE seq; -- Create colocated distributed tables -CREATE TABLE dist1 (id int PRIMARY KEY default nextval('seq')); +CREATE TABLE dist1 (id int PRIMARY KEY default nextval('seq'), col int default (one_as_result()), myserial serial, phone text COLLATE german_phonebook, initials pair_type); +CREATE SEQUENCE seq_owned OWNED BY dist1.id; +CREATE INDEX dist1_search_phone_idx ON dist1 USING gin (to_tsvector('my_ts_config'::regconfig, (COALESCE(phone, ''::text))::text)); SELECT create_distributed_table('dist1', 'id'); INSERT INTO dist1 SELECT i FROM generate_series(1,100) i; @@ -42,6 +67,9 @@ INSERT INTO loc1 SELECT i FROM generate_series(1,100) i; CREATE TABLE loc2 (id int REFERENCES loc1(id)); INSERT INTO loc2 SELECT i FROM generate_series(1,100) i; +-- Create publication +CREATE PUBLICATION pub_all; + -- citus_set_coordinator_host with wrong port SELECT citus_set_coordinator_host('localhost', 9999); -- citus_set_coordinator_host with correct port @@ -88,10 +116,10 @@ SELECT citus_activate_node('localhost', :worker_2_proxy_port); SELECT citus.mitmproxy('conn.onQuery(query="INSERT INTO pg_dist_node").kill()'); SELECT citus_activate_node('localhost', :worker_2_proxy_port); --- Failure to drop sequence -SELECT citus.mitmproxy('conn.onQuery(query="SELECT pg_catalog.worker_drop_sequence_dependency").cancel(' || :pid || ')'); +-- Failure to drop sequence dependency for all tables +SELECT citus.mitmproxy('conn.onQuery(query="SELECT pg_catalog.worker_drop_sequence_dependency.*FROM pg_dist_partition").cancel(' || :pid || ')'); SELECT citus_activate_node('localhost', :worker_2_proxy_port); -SELECT citus.mitmproxy('conn.onQuery(query="SELECT pg_catalog.worker_drop_sequence_dependency").kill()'); +SELECT citus.mitmproxy('conn.onQuery(query="SELECT pg_catalog.worker_drop_sequence_dependency.*FROM pg_dist_partition").kill()'); SELECT citus_activate_node('localhost', :worker_2_proxy_port); -- Failure to drop shell table @@ -142,24 +170,84 @@ SELECT citus_activate_node('localhost', :worker_2_proxy_port); SELECT citus.mitmproxy('conn.onQuery(query="ALTER DATABASE.*OWNER TO").kill()'); SELECT citus_activate_node('localhost', :worker_2_proxy_port); --- Filure to create schema +-- Failure to create schema SELECT citus.mitmproxy('conn.onQuery(query="CREATE SCHEMA IF NOT EXISTS mx_metadata_sync_multi_trans AUTHORIZATION").cancel(' || :pid || ')'); SELECT citus_activate_node('localhost', :worker_2_proxy_port); SELECT citus.mitmproxy('conn.onQuery(query="CREATE SCHEMA IF NOT EXISTS mx_metadata_sync_multi_trans AUTHORIZATION").kill()'); SELECT citus_activate_node('localhost', :worker_2_proxy_port); +-- Failure to create collation +SELECT citus.mitmproxy('conn.onQuery(query="SELECT worker_create_or_replace_object.*CREATE COLLATION mx_metadata_sync_multi_trans.german_phonebook").cancel(' || :pid || ')'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +SELECT citus.mitmproxy('conn.onQuery(query="SELECT worker_create_or_replace_object.*CREATE COLLATION mx_metadata_sync_multi_trans.german_phonebook").kill()'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); + +-- Failure to create function +SELECT citus.mitmproxy('conn.onQuery(query="CREATE OR REPLACE FUNCTION mx_metadata_sync_multi_trans.one_as_result").cancel(' || :pid || ')'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +SELECT citus.mitmproxy('conn.onQuery(query="CREATE OR REPLACE FUNCTION mx_metadata_sync_multi_trans.one_as_result").kill()'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); + +-- Failure to create text search dictionary +SELECT citus.mitmproxy('conn.onQuery(query="SELECT worker_create_or_replace_object.*my_german_dict").cancel(' || :pid || ')'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +SELECT citus.mitmproxy('conn.onQuery(query="SELECT worker_create_or_replace_object.*my_german_dict").kill()'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); + +-- Failure to create text search config +SELECT citus.mitmproxy('conn.onQuery(query="SELECT worker_create_or_replace_object.*my_ts_config").cancel(' || :pid || ')'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +SELECT citus.mitmproxy('conn.onQuery(query="SELECT worker_create_or_replace_object.*my_ts_config").kill()'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); + +-- Failure to create type +SELECT citus.mitmproxy('conn.onQuery(query="SELECT worker_create_or_replace_object.*pair_type").cancel(' || :pid || ')'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +SELECT citus.mitmproxy('conn.onQuery(query="SELECT worker_create_or_replace_object.*pair_type").kill()'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); + +-- Failure to create publication +SELECT citus.mitmproxy('conn.onQuery(query="CREATE PUBLICATION.*pub_all").cancel(' || :pid || ')'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +SELECT citus.mitmproxy('conn.onQuery(query="CREATE PUBLICATION.*pub_all").kill()'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); + -- Failure to create sequence SELECT citus.mitmproxy('conn.onQuery(query="SELECT worker_apply_sequence_command").cancel(' || :pid || ')'); SELECT citus_activate_node('localhost', :worker_2_proxy_port); SELECT citus.mitmproxy('conn.onQuery(query="SELECT worker_apply_sequence_command").kill()'); SELECT citus_activate_node('localhost', :worker_2_proxy_port); +-- Failure to drop sequence dependency for distributed table +SELECT citus.mitmproxy('conn.onQuery(query="SELECT pg_catalog.worker_drop_sequence_dependency.*mx_metadata_sync_multi_trans.dist1").cancel(' || :pid || ')'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +SELECT citus.mitmproxy('conn.onQuery(query="SELECT pg_catalog.worker_drop_sequence_dependency.*mx_metadata_sync_multi_trans.dist1").kill()'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); + +-- Failure to drop distributed table if exists +SELECT citus.mitmproxy('conn.onQuery(query="DROP TABLE IF EXISTS mx_metadata_sync_multi_trans.dist1").cancel(' || :pid || ')'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +SELECT citus.mitmproxy('conn.onQuery(query="DROP TABLE IF EXISTS mx_metadata_sync_multi_trans.dist1").kill()'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); + -- Failure to create distributed table SELECT citus.mitmproxy('conn.onQuery(query="CREATE TABLE mx_metadata_sync_multi_trans.dist1").cancel(' || :pid || ')'); SELECT citus_activate_node('localhost', :worker_2_proxy_port); SELECT citus.mitmproxy('conn.onQuery(query="CREATE TABLE mx_metadata_sync_multi_trans.dist1").kill()'); SELECT citus_activate_node('localhost', :worker_2_proxy_port); +-- Failure to record sequence dependency for table +SELECT citus.mitmproxy('conn.onQuery(query="SELECT pg_catalog.worker_record_sequence_dependency").cancel(' || :pid || ')'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +SELECT citus.mitmproxy('conn.onQuery(query="SELECT pg_catalog.worker_record_sequence_dependency").kill()'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); + +-- Failure to create index for table +SELECT citus.mitmproxy('conn.onQuery(query="CREATE INDEX dist1_search_phone_idx ON mx_metadata_sync_multi_trans.dist1 USING gin").cancel(' || :pid || ')'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +SELECT citus.mitmproxy('conn.onQuery(query="CREATE INDEX dist1_search_phone_idx ON mx_metadata_sync_multi_trans.dist1 USING gin").kill()'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); + -- Failure to create reference table SELECT citus.mitmproxy('conn.onQuery(query="CREATE TABLE mx_metadata_sync_multi_trans.ref").cancel(' || :pid || ')'); SELECT citus_activate_node('localhost', :worker_2_proxy_port); @@ -220,6 +308,48 @@ SELECT citus_activate_node('localhost', :worker_2_proxy_port); SELECT citus.mitmproxy('conn.onQuery(query="SELECT citus_internal_add_object_metadata").kill()'); SELECT citus_activate_node('localhost', :worker_2_proxy_port); +-- Failure to mark function as distributed +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*one_as_result").cancel(' || :pid || ')'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*one_as_result").kill()'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); + +-- Failure to mark collation as distributed +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*german_phonebook").cancel(' || :pid || ')'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*german_phonebook").kill()'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); + +-- Failure to mark text search dictionary as distributed +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*my_german_dict").cancel(' || :pid || ')'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*my_german_dict").kill()'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); + +-- Failure to mark text search configuration as distributed +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*my_ts_config").cancel(' || :pid || ')'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*my_ts_config").kill()'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); + +-- Failure to mark type as distributed +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*pair_type").cancel(' || :pid || ')'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*pair_type").kill()'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); + +-- Failure to mark sequence as distributed +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*seq_owned").cancel(' || :pid || ')'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*seq_owned").kill()'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); + +-- Failure to mark publication as distributed +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*pub_all").cancel(' || :pid || ')'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); +SELECT citus.mitmproxy('conn.onQuery(query="WITH distributed_object_data.*pub_all").kill()'); +SELECT citus_activate_node('localhost', :worker_2_proxy_port); + -- Failure to set isactive to true SELECT citus.mitmproxy('conn.onQuery(query="UPDATE pg_dist_node SET isactive = TRUE").cancel(' || :pid || ')'); SELECT citus_activate_node('localhost', :worker_2_proxy_port); @@ -260,11 +390,8 @@ UPDATE dist1 SET id = :failed_node_val WHERE id = :failed_node_val; DELETE FROM dist1 WHERE id = :failed_node_val; -- Show that DDL would still propagate to the node -SET client_min_messages TO NOTICE; -SET citus.log_remote_commands TO 1; CREATE SCHEMA dummy; -SET citus.log_remote_commands TO 0; -SET client_min_messages TO ERROR; +SELECT * FROM run_command_on_workers($$SELECT nspname FROM pg_namespace WHERE nspname = 'dummy'$$); -- Successfully activate the node after many failures SELECT citus.mitmproxy('conn.allow()'); @@ -280,8 +407,11 @@ SELECT * FROM pg_dist_node ORDER BY nodeport; SELECT citus.mitmproxy('conn.allow()'); RESET citus.metadata_sync_mode; +DROP PUBLICATION pub_all; DROP SCHEMA dummy; DROP SCHEMA mx_metadata_sync_multi_trans CASCADE; DROP ROLE foo1; DROP ROLE foo2; SELECT citus_remove_node('localhost', :master_port); +ALTER SEQUENCE pg_dist_node_nodeid_seq RESTART 3; +ALTER SEQUENCE pg_dist_groupid_seq RESTART 3; diff --git a/src/test/regress/sql/foreign_key_restriction_enforcement.sql b/src/test/regress/sql/foreign_key_restriction_enforcement.sql index cdef1c798..f6c7bd68b 100644 --- a/src/test/regress/sql/foreign_key_restriction_enforcement.sql +++ b/src/test/regress/sql/foreign_key_restriction_enforcement.sql @@ -680,6 +680,8 @@ ROLLBACK; -- the fails since we're trying to switch sequential mode after -- already executed a parallel query BEGIN; + SELECT master_remove_node('localhost', :master_port); + CREATE TABLE test_table_1(id int PRIMARY KEY); SELECT create_reference_table('test_table_1'); @@ -697,6 +699,8 @@ ROLLBACK; -- same test with the above, but this time using -- sequential mode, succeeds BEGIN; + SELECT master_remove_node('localhost', :master_port); + SET LOCAL citus.multi_shard_modify_mode TO 'sequential'; CREATE TABLE test_table_1(id int PRIMARY KEY); SELECT create_reference_table('test_table_1'); diff --git a/src/test/regress/sql/function_propagation.sql b/src/test/regress/sql/function_propagation.sql index eca10beb5..d3e4a74d4 100644 --- a/src/test/regress/sql/function_propagation.sql +++ b/src/test/regress/sql/function_propagation.sql @@ -565,7 +565,6 @@ BEGIN; SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_for_local_table'::regproc::oid; CREATE TABLE citus_local_table_to_test_func(l1 int DEFAULT func_in_transaction_for_local_table()); - SELECT 1 FROM master_add_node('localhost', :master_port, groupid => 0); SELECT citus_add_local_table_to_metadata('citus_local_table_to_test_func'); -- Function should be marked as distributed after distributing the table that depends on it @@ -717,6 +716,9 @@ SELECT create_distributed_table('tbl_to_colocate', 'a'); -- first test colocating function with a ref table CREATE TABLE tbl_to_colocate_ref (a int); SELECT create_reference_table('tbl_to_colocate_ref'); +-- test colocating function with single shard table +CREATE TABLE single_shard_tbl (a int); +SELECT create_distributed_table('single_shard_tbl', null); CREATE FUNCTION func_to_colocate (a int) returns int as $$select 1;$$ language sql; -- see the empty pg_dist_object entries @@ -727,6 +729,11 @@ SELECT create_distributed_function('func_to_colocate(int)', colocate_with:='tbl_ -- see the pg_dist_object entry SELECT distribution_argument_index, colocationid, force_delegation FROM pg_catalog.pg_dist_object WHERE objid = 'func_to_colocate'::regproc; +-- colocate the function with single shard table table +SELECT create_distributed_function('func_to_colocate(int)', colocate_with:='single_shard_tbl'); +-- see the pg_dist_object entry +SELECT distribution_argument_index, colocationid, force_delegation FROM pg_catalog.pg_dist_object WHERE objid = 'func_to_colocate'::regproc; + -- convert to non-delegated SELECT create_distributed_function('func_to_colocate(int)'); -- show that the pg_dist_object fields are gone diff --git a/src/test/regress/sql/generated_identity.sql b/src/test/regress/sql/generated_identity.sql index c2980d0bd..df967ddd0 100644 --- a/src/test/regress/sql/generated_identity.sql +++ b/src/test/regress/sql/generated_identity.sql @@ -1,7 +1,3 @@ --- This test file has an alternative output because of error messages vary for PG13 -SHOW server_version \gset -SELECT substring(:'server_version', '\d+')::int <= 13 AS server_version_le_13; - CREATE SCHEMA generated_identities; SET search_path TO generated_identities; SET client_min_messages to ERROR; @@ -9,26 +5,78 @@ SET citus.shard_replication_factor TO 1; SELECT 1 from citus_add_node('localhost', :master_port, groupId=>0); --- smallint identity column can not be distributed CREATE TABLE smallint_identity_column ( a smallint GENERATED BY DEFAULT AS IDENTITY ); -SELECT create_distributed_table('smallint_identity_column', 'a'); + +CREATE VIEW verify_smallint_identity_column AS +SELECT attidentity, attgenerated FROM pg_attribute WHERE attrelid = 'smallint_identity_column'::regclass AND attname = 'a'; + +BEGIN; + SELECT create_distributed_table('smallint_identity_column', 'a'); + SELECT * FROM verify_smallint_identity_column; +ROLLBACK; + +BEGIN; + SELECT create_reference_table('smallint_identity_column'); + SELECT * FROM verify_smallint_identity_column; +ROLLBACK; + +BEGIN; + SELECT citus_add_local_table_to_metadata('smallint_identity_column'); + SELECT * FROM verify_smallint_identity_column; +ROLLBACK; + SELECT create_distributed_table_concurrently('smallint_identity_column', 'a'); -SELECT create_reference_table('smallint_identity_column'); -SELECT citus_add_local_table_to_metadata('smallint_identity_column'); +SELECT * FROM verify_smallint_identity_column; +SELECT result FROM run_command_on_workers('INSERT INTO generated_identities.smallint_identity_column (a) VALUES (DEFAULT);'); -DROP TABLE smallint_identity_column; +DROP TABLE smallint_identity_column CASCADE; --- int identity column can not be distributed CREATE TABLE int_identity_column ( a int GENERATED BY DEFAULT AS IDENTITY ); -SELECT create_distributed_table('int_identity_column', 'a'); + +CREATE VIEW verify_int_identity_column AS +SELECT attidentity, attgenerated FROM pg_attribute WHERE attrelid = 'int_identity_column'::regclass AND attname = 'a'; + +BEGIN; + SELECT create_distributed_table('int_identity_column', 'a'); + SELECT * FROM verify_int_identity_column; +ROLLBACK; + +BEGIN; + SELECT create_reference_table('int_identity_column'); + SELECT * FROM verify_int_identity_column; +ROLLBACK; + +BEGIN; + SELECT citus_add_local_table_to_metadata('int_identity_column'); + SELECT * FROM verify_int_identity_column; +ROLLBACK; + SELECT create_distributed_table_concurrently('int_identity_column', 'a'); -SELECT create_reference_table('int_identity_column'); -SELECT citus_add_local_table_to_metadata('int_identity_column'); -DROP TABLE int_identity_column; +SELECT * FROM verify_int_identity_column; +SELECT result FROM run_command_on_workers('INSERT INTO generated_identities.int_identity_column (a) VALUES (DEFAULT);'); + +DROP TABLE int_identity_column CASCADE; + +CREATE TABLE reference_int_identity_column ( + a int GENERATED BY DEFAULT AS IDENTITY +); +SELECT create_reference_table('reference_int_identity_column'); +INSERT INTO generated_identities.reference_int_identity_column (a) VALUES (DEFAULT) RETURNING a; +SELECT result FROM run_command_on_workers('INSERT INTO generated_identities.reference_int_identity_column (a) VALUES (DEFAULT);'); + +CREATE TABLE citus_local_int_identity_column ( + a int GENERATED BY DEFAULT AS IDENTITY +); +SELECT citus_add_local_table_to_metadata('citus_local_int_identity_column'); +INSERT INTO generated_identities.citus_local_int_identity_column (a) VALUES (DEFAULT) RETURNING a; +SELECT result FROM run_command_on_workers('INSERT INTO generated_identities.citus_local_int_identity_column (a) VALUES (DEFAULT);'); + +DROP TABLE reference_int_identity_column, citus_local_int_identity_column; + RESET citus.shard_replication_factor; diff --git a/src/test/regress/sql/grant_on_foreign_server_propagation.sql b/src/test/regress/sql/grant_on_foreign_server_propagation.sql index d2ecd482b..df0d01f62 100644 --- a/src/test/regress/sql/grant_on_foreign_server_propagation.sql +++ b/src/test/regress/sql/grant_on_foreign_server_propagation.sql @@ -6,6 +6,7 @@ CREATE SCHEMA "grant on server"; SET search_path TO "grant on server"; -- remove one of the worker nodes to test adding a new node later +SELECT 1 FROM citus_remove_node('localhost', :master_port); SELECT 1 FROM citus_remove_node('localhost', :worker_2_port); select 1 from citus_add_node('localhost',:master_port,groupId=>0); @@ -133,5 +134,3 @@ SET client_min_messages TO ERROR; DROP SERVER "Foreign Server" CASCADE; DROP SCHEMA "grant on server" CASCADE; DROP ROLE role_test_servers, role_test_servers_2, ownerrole; - -SELECT 1 FROM citus_remove_node('localhost', :master_port); diff --git a/src/test/regress/sql/grant_on_schema_propagation.sql b/src/test/regress/sql/grant_on_schema_propagation.sql index 1cb601ad6..f0bd233a2 100644 --- a/src/test/regress/sql/grant_on_schema_propagation.sql +++ b/src/test/regress/sql/grant_on_schema_propagation.sql @@ -1,7 +1,7 @@ -- -- GRANT_ON_SCHEMA_PROPAGATION -- --- this test has different output for PG13/14 compared to PG15 +-- this test has different output for PG14 compared to PG15 -- In PG15, public schema is owned by pg_database_owner role -- Relevant PG commit: b073c3ccd06e4cb845e121387a43faa8c68a7b62 SHOW server_version \gset @@ -189,6 +189,9 @@ DROP SCHEMA dist_schema CASCADE; SET citus.shard_replication_factor TO 1; SELECT master_remove_node('localhost', :worker_2_port); +-- to avoid different output in PG15 +GRANT CREATE ON SCHEMA public TO public; + -- distribute the public schema (it has to be distributed by now but just in case) CREATE TABLE public_schema_table (id INT); SELECT create_distributed_table('public_schema_table', 'id'); diff --git a/src/test/regress/sql/insert_select_repartition.sql b/src/test/regress/sql/insert_select_repartition.sql index 94a16fed0..4d13a83f4 100644 --- a/src/test/regress/sql/insert_select_repartition.sql +++ b/src/test/regress/sql/insert_select_repartition.sql @@ -389,10 +389,12 @@ DEALLOCATE insert_plan; TRUNCATE target_table; SET client_min_messages TO DEBUG2; +SET citus.enable_non_colocated_router_query_pushdown TO ON; WITH r AS ( INSERT INTO target_table SELECT * FROM source_table RETURNING * ) INSERT INTO target_table SELECT source_table.a, max(source_table.b) FROM source_table NATURAL JOIN r GROUP BY source_table.a; +RESET citus.enable_non_colocated_router_query_pushdown; RESET client_min_messages; SELECT * FROM target_table ORDER BY a, b; @@ -645,6 +647,17 @@ insert into table_with_user_sequences values (1,1); select create_distributed_table('table_with_user_sequences','x'); explain (costs off) insert into table_with_user_sequences select y, x from table_with_user_sequences; +CREATE TABLE dist_table_1(id int); +SELECT create_distributed_table('dist_table_1','id'); +CREATE TABLE dist_table_2(id int); +SELECT create_distributed_table('dist_table_2','id'); + +-- verify that insert select with union can be repartitioned. We cannot push down the query +-- since UNION clause has no FROM clause at top level query. +SELECT public.coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_1(id) SELECT id FROM dist_table_1 UNION SELECT id FROM dist_table_2; +$$); + -- clean-up SET client_min_messages TO WARNING; DROP SCHEMA insert_select_repartition CASCADE; diff --git a/src/test/regress/sql/insert_select_single_shard_table.sql b/src/test/regress/sql/insert_select_single_shard_table.sql new file mode 100644 index 000000000..fb080d206 --- /dev/null +++ b/src/test/regress/sql/insert_select_single_shard_table.sql @@ -0,0 +1,495 @@ +CREATE SCHEMA insert_select_single_shard_table; +SET search_path TO insert_select_single_shard_table; + +SET citus.next_shard_id TO 1820000; +SET citus.shard_count TO 32; +SET citus.shard_replication_factor TO 1; + +SET client_min_messages TO NOTICE; + +CREATE TABLE nullkey_c1_t1(a int, b int); +CREATE TABLE nullkey_c1_t2(a int, b int); +SELECT create_distributed_table('nullkey_c1_t1', null, colocate_with=>'none'); +SELECT create_distributed_table('nullkey_c1_t2', null, colocate_with=>'nullkey_c1_t1'); + +CREATE TABLE nullkey_c2_t1(a int, b int); +SELECT create_distributed_table('nullkey_c2_t1', null, colocate_with=>'none'); + +CREATE TABLE reference_table(a int, b int); +SELECT create_reference_table('reference_table'); + +CREATE TABLE distributed_table_c1_t1(a int, b int); +SELECT create_distributed_table('distributed_table_c1_t1', 'a'); + +CREATE TABLE distributed_table_c1_t2(a int, b int); +SELECT create_distributed_table('distributed_table_c1_t2', 'a'); + +CREATE TABLE distributed_table_c2_t1(a int, b int); +SELECT create_distributed_table('distributed_table_c2_t1', 'a', colocate_with=>'none'); + +CREATE TABLE citus_local_table(a int, b int); +SELECT citus_add_local_table_to_metadata('citus_local_table'); + +CREATE TABLE postgres_local_table(a int, b int); + +CREATE FUNCTION reload_tables() RETURNS void AS $$ + BEGIN + SET LOCAL client_min_messages TO WARNING; + + TRUNCATE nullkey_c1_t1, nullkey_c1_t2, nullkey_c2_t1, reference_table, distributed_table_c1_t1, + distributed_table_c1_t2, distributed_table_c2_t1, citus_local_table, postgres_local_table; + + INSERT INTO nullkey_c1_t1 SELECT i, i FROM generate_series(1, 8) i; + INSERT INTO nullkey_c1_t2 SELECT i, i FROM generate_series(2, 7) i; + INSERT INTO nullkey_c2_t1 SELECT i, i FROM generate_series(2, 7) i; + INSERT INTO reference_table SELECT i, i FROM generate_series(0, 5) i; + INSERT INTO distributed_table_c1_t1 SELECT i, i FROM generate_series(3, 8) i; + INSERT INTO distributed_table_c1_t2 SELECT i, i FROM generate_series(2, 9) i; + INSERT INTO distributed_table_c2_t1 SELECT i, i FROM generate_series(5, 10) i; + INSERT INTO citus_local_table SELECT i, i FROM generate_series(0, 10) i; + INSERT INTO postgres_local_table SELECT i, i FROM generate_series(5, 10) i; + END; +$$ LANGUAGE plpgsql; + +SELECT reload_tables(); + +CREATE TABLE append_table (a int, b int); +SELECT create_distributed_table('append_table', 'a', 'append'); +SELECT master_create_empty_shard('append_table') AS shardid1 \gset +SELECT master_create_empty_shard('append_table') AS shardid2 \gset +SELECT master_create_empty_shard('append_table') AS shardid3 \gset + +COPY append_table (a, b) FROM STDIN WITH (format 'csv', append_to_shard :shardid1); +1, 40 +2, 42 +3, 44 +4, 46 +5, 48 +\. + +COPY append_table (a, b) FROM STDIN WITH (format 'csv', append_to_shard :shardid2); +6, 50 +7, 52 +8, 54 +9, 56 +10, 58 +\. + +CREATE TABLE range_table(a int, b int); +SELECT create_distributed_table('range_table', 'a', 'range'); +CALL public.create_range_partitioned_shards('range_table', '{"0","25"}','{"24","49"}'); +INSERT INTO range_table VALUES (0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 50); + +CREATE MATERIALIZED VIEW matview AS SELECT b*2+a AS a, a*a AS b FROM nullkey_c1_t1; + +SET client_min_messages TO DEBUG2; + +-- Test inserting into a distributed table by selecting from a combination of +-- different table types together with single-shard tables. + +-- use a single-shard table +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1; + +-- use a reference table +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN reference_table USING (a); +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 RIGHT JOIN reference_table USING (b) WHERE reference_table.a >= 1 AND reference_table.a <= 5; +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM nullkey_c1_t2 LEFT JOIN reference_table USING (b); +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 INTERSECT SELECT * FROM reference_table; + +-- use a colocated single-shard table +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN nullkey_c1_t2 USING (b); +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 FULL JOIN nullkey_c1_t2 USING (a); +INSERT INTO distributed_table_c1_t1 SELECT COALESCE(nullkey_c1_t1.a, 1), nullkey_c1_t1.b FROM nullkey_c1_t1 FULL JOIN matview USING (a); +INSERT INTO distributed_table_c1_t1 SELECT * FROM nullkey_c1_t1 UNION SELECT * FROM nullkey_c1_t2; + +-- use a non-colocated single-shard table +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM nullkey_c1_t2 LEFT JOIN nullkey_c2_t1 USING (a); +INSERT INTO distributed_table_c1_t1 SELECT * FROM nullkey_c1_t1 UNION SELECT * FROM nullkey_c2_t1; + +SET client_min_messages TO DEBUG1; +SET citus.enable_repartition_joins TO ON; + +-- use a distributed table that is colocated with the target table, with repartition joins enabled +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN distributed_table_c1_t2 USING (a); +INSERT INTO distributed_table_c1_t1 SELECT distributed_table_c1_t2.a, distributed_table_c1_t2.b FROM nullkey_c1_t1 JOIN distributed_table_c1_t2 USING (a); +INSERT INTO distributed_table_c1_t1 SELECT distributed_table_c1_t2.a, distributed_table_c1_t2.b FROM nullkey_c1_t1 JOIN distributed_table_c1_t2 USING (b); +INSERT INTO distributed_table_c1_t1 SELECT distributed_table_c1_t2.a, distributed_table_c1_t2.b FROM nullkey_c1_t1 JOIN distributed_table_c1_t2 USING (a) WHERE distributed_table_c1_t2.a = 1; + +-- use a distributed table that is not colocated with the target table, with repartition joins enabled +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM nullkey_c1_t2 JOIN distributed_table_c2_t1 USING (a); + +RESET citus.enable_repartition_joins; +SET client_min_messages TO DEBUG2; + +-- use a citus local table +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN citus_local_table USING (a); + +-- use a postgres local table +INSERT INTO distributed_table_c1_t1 SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM nullkey_c1_t2 JOIN postgres_local_table USING (a); + +-- use append / range distributed tables +INSERT INTO range_table SELECT * FROM nullkey_c1_t1; +INSERT INTO append_table SELECT * FROM nullkey_c1_t1; + +SELECT avg(a), avg(b) FROM distributed_table_c1_t1 ORDER BY 1, 2; +TRUNCATE distributed_table_c1_t1; +INSERT INTO distributed_table_c1_t1 SELECT i, i FROM generate_series(3, 8) i; + +-- Test inserting into a reference table by selecting from a combination of +-- different table types together with single-shard tables. + +-- use a single-shard table +INSERT INTO reference_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1; + +-- use a reference table +INSERT INTO reference_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN reference_table USING (a); +INSERT INTO reference_table SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM nullkey_c1_t2 LEFT JOIN reference_table USING (b); +INSERT INTO reference_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 UNION SELECT * FROM reference_table; +INSERT INTO reference_table SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM nullkey_c1_t2 LEFT JOIN reference_table USING (b) WHERE b IN (SELECT b FROM matview); + +-- use a colocated single-shard table +INSERT INTO reference_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN nullkey_c1_t2 USING (b); +INSERT INTO reference_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 FULL JOIN nullkey_c1_t2 USING (a); + +-- use a non-colocated single-shard table +INSERT INTO reference_table SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM nullkey_c1_t2 LEFT JOIN nullkey_c2_t1 USING (a); + +-- use a distributed table + +SET client_min_messages TO DEBUG1; +SET citus.enable_repartition_joins TO ON; + +INSERT INTO reference_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN distributed_table_c1_t2 USING (a); +INSERT INTO reference_table SELECT distributed_table_c1_t2.a, distributed_table_c1_t2.b FROM nullkey_c1_t1 JOIN distributed_table_c1_t2 USING (a); +INSERT INTO reference_table SELECT distributed_table_c1_t2.a, distributed_table_c1_t2.b FROM nullkey_c1_t1 JOIN distributed_table_c1_t2 USING (b); +INSERT INTO reference_table SELECT distributed_table_c1_t2.a, distributed_table_c1_t2.b FROM nullkey_c1_t1 JOIN distributed_table_c1_t2 USING (a) WHERE distributed_table_c1_t2.a = 1; + +RESET citus.enable_repartition_joins; +SET client_min_messages TO DEBUG2; + +-- use a citus local table +INSERT INTO reference_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN citus_local_table USING (a); + +-- use a postgres local table +INSERT INTO reference_table SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM nullkey_c1_t2 JOIN postgres_local_table USING (a); + +SELECT avg(a), avg(b) FROM reference_table ORDER BY 1, 2; +TRUNCATE reference_table; +INSERT INTO reference_table SELECT i, i FROM generate_series(0, 5) i; + +-- Test inserting into a citus local table by selecting from a combination of +-- different table types together with single-shard tables. + +-- use a single-shard table +INSERT INTO citus_local_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1; + +-- use a reference table +INSERT INTO citus_local_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN reference_table USING (a); + +-- use a colocated single-shard table +INSERT INTO citus_local_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN nullkey_c1_t2 USING (b); + +-- use a distributed table +SET client_min_messages TO DEBUG1; +SET citus.enable_repartition_joins TO ON; +INSERT INTO citus_local_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN distributed_table_c1_t2 USING (a); +RESET citus.enable_repartition_joins; +SET client_min_messages TO DEBUG2; + +-- use a citus local table +INSERT INTO citus_local_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN citus_local_table USING (a); + +-- use a postgres local table +INSERT INTO citus_local_table SELECT nullkey_c1_t2.a, nullkey_c1_t2.b FROM nullkey_c1_t2 JOIN postgres_local_table USING (a); + +SELECT avg(a), avg(b) FROM citus_local_table ORDER BY 1, 2; +TRUNCATE citus_local_table; +INSERT INTO citus_local_table SELECT i, i FROM generate_series(0, 10) i; + +-- Test inserting into a single-shard table by selecting from a combination of +-- different table types, together with or without single-shard tables. + +-- use a postgres local table +INSERT INTO nullkey_c1_t1 SELECT postgres_local_table.a, postgres_local_table.b FROM postgres_local_table; +INSERT INTO nullkey_c1_t1 SELECT postgres_local_table.a, postgres_local_table.b FROM postgres_local_table JOIN reference_table USING (a); +INSERT INTO nullkey_c1_t1 SELECT postgres_local_table.a, postgres_local_table.b FROM postgres_local_table LEFT JOIN nullkey_c1_t1 USING (a); + +-- use a citus local table +INSERT INTO nullkey_c1_t1 SELECT citus_local_table.a, citus_local_table.b FROM citus_local_table; +INSERT INTO nullkey_c1_t1 SELECT citus_local_table.a, citus_local_table.b FROM citus_local_table JOIN reference_table USING (a) JOIN postgres_local_table USING (a) ORDER BY 1,2 OFFSET 7; +INSERT INTO nullkey_c1_t1 SELECT citus_local_table.a, citus_local_table.b FROM citus_local_table JOIN nullkey_c1_t1 USING (a); + +-- use a distributed table +INSERT INTO nullkey_c1_t1 SELECT distributed_table_c1_t2.a, distributed_table_c1_t2.b FROM distributed_table_c1_t2; +INSERT INTO nullkey_c1_t1 SELECT distributed_table_c1_t2.a, distributed_table_c1_t2.b FROM distributed_table_c1_t2 JOIN reference_table USING (a); + +SET client_min_messages TO DEBUG1; +SET citus.enable_repartition_joins TO ON; +INSERT INTO nullkey_c1_t1 SELECT distributed_table_c1_t2.a, distributed_table_c1_t2.b FROM distributed_table_c1_t2 JOIN nullkey_c1_t1 USING (a); +RESET citus.enable_repartition_joins; +SET client_min_messages TO DEBUG2; + +-- use a non-colocated single-shard table +INSERT INTO nullkey_c2_t1 SELECT q.* FROM (SELECT reference_table.* FROM reference_table LEFT JOIN nullkey_c1_t1 USING (a)) q JOIN nullkey_c1_t2 USING (a); + +-- use a materialized view +INSERT INTO nullkey_c1_t1 SELECT * FROM matview; +INSERT INTO nullkey_c1_t1 SELECT reference_table.a, reference_table.b FROM reference_table JOIN matview ON (reference_table.a = matview.a); +INSERT INTO nullkey_c1_t1 SELECT q.* FROM (SELECT reference_table.* FROM reference_table JOIN nullkey_c1_t1 USING (a)) q JOIN matview USING (a); + +-- use append / range distributed tables +INSERT INTO nullkey_c1_t1 SELECT * FROM range_table; +INSERT INTO nullkey_c1_t1 SELECT * FROM append_table; + +SELECT avg(a), avg(b) FROM nullkey_c1_t1 ORDER BY 1, 2; +SELECT avg(a), avg(b) FROM nullkey_c2_t1 ORDER BY 1, 2; +TRUNCATE nullkey_c1_t1, nullkey_c2_t1; +INSERT INTO nullkey_c1_t1 SELECT i, i FROM generate_series(1, 8) i; +INSERT INTO nullkey_c2_t1 SELECT i, i FROM generate_series(2, 7) i; + +-- Test inserting into a local table by selecting from a combination of +-- different table types, together with or without single-shard tables. + +INSERT INTO postgres_local_table SELECT nullkey_c1_t1.a, nullkey_c1_t1.b FROM nullkey_c1_t1 JOIN reference_table USING (a); + +INSERT INTO postgres_local_table SELECT * FROM nullkey_c1_t1 ORDER BY 1,2 OFFSET 3 LIMIT 2; + +WITH cte_1 AS ( + DELETE FROM nullkey_c1_t1 WHERE a >= 1 and a <= 4 RETURNING * +) +INSERT INTO postgres_local_table SELECT cte_1.* FROM cte_1 LEFT JOIN nullkey_c1_t2 USING (a) WHERE nullkey_c1_t2.a IS NULL; + +INSERT INTO postgres_local_table SELECT * FROM nullkey_c1_t1 EXCEPT SELECT * FROM postgres_local_table; + +SELECT avg(a), avg(b) FROM postgres_local_table ORDER BY 1, 2; +TRUNCATE postgres_local_table; +INSERT INTO postgres_local_table SELECT i, i FROM generate_series(5, 10) i; + +-- Try slightly more complex queries. + +SET client_min_messages TO DEBUG1; + +WITH cte_1 AS ( + SELECT nullkey_c1_t1.a, reference_table.b FROM nullkey_c1_t1 JOIN reference_table USING (a) +), +cte_2 AS ( + SELECT reference_table.a, postgres_local_table.b FROM postgres_local_table LEFT JOIN reference_table USING (b) +) +INSERT INTO distributed_table_c1_t1 +SELECT cte_1.* FROM cte_1 JOIN cte_2 USING (a) JOIN distributed_table_c1_t2 USING (a) ORDER BY 1,2; + +SET client_min_messages TO DEBUG2; + +WITH cte_1 AS ( + SELECT nullkey_c1_t1.a, reference_table.b FROM nullkey_c1_t1 JOIN reference_table USING (a) +), +cte_2 AS ( + SELECT * FROM nullkey_c1_t2 WHERE EXISTS ( + SELECT 1 FROM reference_table WHERE reference_table.a = nullkey_c1_t2.a + ) + ORDER BY 1,2 OFFSET 1 LIMIT 4 +) +INSERT INTO distributed_table_c1_t1 +SELECT * FROM cte_1 UNION SELECT * FROM cte_2 EXCEPT SELECT * FROM reference_table; + +INSERT INTO distributed_table_c1_t1 (a, b) +SELECT t1.a, t2.b +FROM nullkey_c1_t1 t1 +JOIN ( + SELECT b FROM nullkey_c1_t2 ORDER BY b DESC LIMIT 1 +) t2 +ON t1.b < t2.b; + +INSERT INTO distributed_table_c1_t1 (a, b) +WITH cte AS ( + SELECT a, b, + (SELECT a FROM nullkey_c1_t2 WHERE b = t.b) AS d1, + (SELECT a FROM reference_table WHERE b = t.b) AS d2 + FROM nullkey_c1_t1 t +) +SELECT d1, COALESCE(d2, a) FROM cte WHERE d1 IS NOT NULL AND d2 IS NOT NULL; + +INSERT INTO citus_local_table (a, b) +SELECT t1.a, t2.b +FROM nullkey_c1_t1 t1 +CROSS JOIN ( + SELECT b FROM nullkey_c2_t1 ORDER BY b LIMIT 1 +) t2; + +INSERT INTO distributed_table_c1_t1 (a, b) +SELECT t1.a, t2.b +FROM reference_table t1 +LEFT JOIN ( + SELECT b, ROW_NUMBER() OVER (ORDER BY b DESC) AS rn + FROM nullkey_c1_t1 +) t2 ON t1.b = t2.b +WHERE t2.rn > 0; + +INSERT INTO nullkey_c1_t1 (a, b) +SELECT t1.a, t2.b +FROM nullkey_c1_t1 t1 +JOIN ( + SELECT rn, b + FROM ( + SELECT b, ROW_NUMBER() OVER (ORDER BY b DESC) AS rn + FROM distributed_table_c2_t1 + ) q +) t2 ON t1.b = t2.b +WHERE t2.rn > 2; + +INSERT INTO distributed_table_c1_t1 (a, b) +SELECT t1.a, t2.b +FROM nullkey_c1_t1 t1 +JOIN ( + SELECT sum_val, b + FROM ( + SELECT b, SUM(a) OVER (PARTITION BY b) AS sum_val + FROM nullkey_c1_t1 + ) q +) t2 ON t1.b = t2.b +WHERE t2.sum_val > 2; + +-- Temporaryly reduce the verbosity to avoid noise +-- in the output of the next query. +SET client_min_messages TO DEBUG1; + +INSERT INTO nullkey_c1_t1 SELECT DISTINCT ON (a) a, b FROM nullkey_c1_t2; + +SET client_min_messages TO DEBUG2; + +-- Similarly, we could push down the following query as well. see +-- https://github.com/citusdata/citus/pull/6831. +INSERT INTO nullkey_c1_t1 SELECT b, SUM(a) OVER (ORDER BY b) AS sum_val FROM nullkey_c1_t1; + +INSERT INTO nullkey_c2_t1 +SELECT t2.a, t2.b +FROM nullkey_c1_t1 AS t2 +JOIN reference_table AS t3 ON (t2.a = t3.a) +WHERE NOT EXISTS ( + SELECT 1 FROM nullkey_c1_t2 AS t1 WHERE t1.b = t3.b +); + +INSERT INTO distributed_table_c1_t1 +SELECT t1.a, t1.b +FROM nullkey_c1_t1 AS t1 +WHERE t1.a NOT IN ( + SELECT DISTINCT t2.a FROM distributed_table_c1_t2 AS t2 +); + +INSERT INTO distributed_table_c1_t1 +SELECT t1.a, t1.b +FROM reference_table AS t1 +JOIN ( + SELECT t2.a FROM ( + SELECT a FROM nullkey_c1_t1 + UNION + SELECT a FROM nullkey_c1_t2 + ) AS t2 +) AS t3 ON t1.a = t3.a; + +-- Temporaryly reduce the verbosity to avoid noise +-- in the output of the next query. +SET client_min_messages TO DEBUG1; + +INSERT INTO nullkey_c1_t1 +SELECT t1.a, t1.b +FROM reference_table AS t1 +WHERE t1.a IN ( + SELECT t2.a FROM ( + SELECT t3.a FROM ( + SELECT a FROM distributed_table_c1_t1 WHERE b > 4 + ) AS t3 + JOIN ( + SELECT a FROM distributed_table_c1_t2 WHERE b < 7 + ) AS t4 ON t3.a = t4.a + ) AS t2 +); + +SET client_min_messages TO DEBUG2; + +-- test upsert with plain INSERT query + +CREATE TABLE upsert_test_1 +( + unique_col int UNIQUE, + other_col int, + third_col int +); +SELECT create_distributed_table('upsert_test_1', null); + +CREATE TABLE upsert_test_2(key int primary key, value text); +SELECT create_distributed_table('upsert_test_2', null); + +INSERT INTO upsert_test_2 AS upsert_test_2_alias (key, value) VALUES (1, '5') ON CONFLICT(key) + DO UPDATE SET value = (upsert_test_2_alias.value::int * 2)::text; + +INSERT INTO upsert_test_2 (key, value) VALUES (1, '5') ON CONFLICT(key) + DO UPDATE SET value = (upsert_test_2.value::int * 3)::text; + +INSERT INTO upsert_test_1 (unique_col, other_col) VALUES (1, 1) ON CONFLICT (unique_col) + DO UPDATE SET other_col = (SELECT count(*) from upsert_test_1); + +INSERT INTO upsert_test_1 (unique_col, other_col) VALUES (1, 1) ON CONFLICT (unique_col) + DO UPDATE SET other_col = random()::int; + +INSERT INTO upsert_test_1 (unique_col, other_col) VALUES (1, 1) ON CONFLICT (unique_col) + DO UPDATE SET other_col = 5 WHERE upsert_test_1.other_col = random()::int; + +INSERT INTO upsert_test_1 VALUES (3, 5, 7); + +INSERT INTO upsert_test_1 (unique_col, other_col) VALUES (1, 1) ON CONFLICT (unique_col) WHERE unique_col = random()::int + DO UPDATE SET other_col = 5; + +CREATE TABLE upsert_test_3 (key_1 int, key_2 bigserial, value text DEFAULT 'default_value', PRIMARY KEY (key_1, key_2)); +SELECT create_distributed_table('upsert_test_3', null); + +INSERT INTO upsert_test_3 VALUES (1, DEFAULT, '1') RETURNING *; +INSERT INTO upsert_test_3 VALUES (5, DEFAULT, DEFAULT) RETURNING *; + +SET client_min_messages TO DEBUG1; +INSERT INTO upsert_test_3 SELECT 7, other_col, 'harcoded_text_value' FROM upsert_test_1 RETURNING *; +SET client_min_messages TO DEBUG2; + +-- test upsert with INSERT .. SELECT queries + +SET client_min_messages TO DEBUG1; +INSERT INTO upsert_test_1 (unique_col, other_col) SELECT unique_col, other_col FROM upsert_test_1 ON CONFLICT (unique_col) + DO UPDATE SET other_col = upsert_test_1.other_col + 1; +-- Fails due to https://github.com/citusdata/citus/issues/6826. +INSERT INTO upsert_test_1 (unique_col, other_col) SELECT unique_col, other_col FROM upsert_test_1 ON CONFLICT (unique_col) + DO UPDATE SET other_col = (SELECT count(*) from upsert_test_1); +SET client_min_messages TO DEBUG2; + +INSERT INTO upsert_test_1 (unique_col, other_col) SELECT unique_col, other_col FROM upsert_test_1 ON CONFLICT (unique_col) + DO UPDATE SET other_col = random()::int; + +INSERT INTO upsert_test_1 (unique_col, other_col) SELECT unique_col, other_col FROM upsert_test_1 ON CONFLICT (unique_col) + DO UPDATE SET other_col = 5 WHERE upsert_test_1.other_col = random()::int; + +SELECT reload_tables(); + +ALTER TABLE nullkey_c1_t1 ADD PRIMARY KEY (a); +ALTER TABLE distributed_table_c1_t1 ADD PRIMARY KEY (a,b); + +INSERT INTO nullkey_c1_t1 AS t1 (a, b) SELECT t3.a, t3.b FROM nullkey_c1_t2 t2 JOIN reference_table t3 ON (t2.a = t3.a) ON CONFLICT (a) + DO UPDATE SET a = t1.a + 10; + +SET client_min_messages TO DEBUG1; +INSERT INTO distributed_table_c1_t1 AS t1 (a, b) SELECT t3.a, t3.b FROM nullkey_c1_t2 t2 JOIN reference_table t3 ON (t2.a = t3.a) ON CONFLICT (a, b) + DO UPDATE SET b = t1.b + 10; +INSERT INTO nullkey_c1_t1 AS t1 (a, b) SELECT t3.a, t3.b FROM distributed_table_c1_t1 t2 JOIN reference_table t3 ON (t2.a = t3.a) ON CONFLICT (a) + DO UPDATE SET a = t1.a + 10; +-- This also fails due to https://github.com/citusdata/citus/issues/6826. +INSERT INTO nullkey_c1_t1 AS t1 (a, b) SELECT t3.a, t3.b FROM distributed_table_c1_t1 t2 JOIN reference_table t3 ON (t2.a = t3.a) WHERE t2.a = 3 ON CONFLICT (a) + DO UPDATE SET a = (SELECT max(b)+1 FROM distributed_table_c1_t1 WHERE a = 3); +SET client_min_messages TO DEBUG2; + +SELECT avg(a), avg(b) FROM distributed_table_c1_t1; +SELECT avg(a), avg(b) FROM nullkey_c1_t1; +SELECT avg(a), avg(b) FROM nullkey_c1_t2; +SELECT * FROM upsert_test_1 ORDER BY unique_col; +SELECT * FROM upsert_test_2 ORDER BY key; +SELECT * FROM upsert_test_3 ORDER BY key_1, key_2; + +SET client_min_messages TO WARNING; +DROP SCHEMA insert_select_single_shard_table CASCADE; diff --git a/src/test/regress/sql/intermediate_results.sql b/src/test/regress/sql/intermediate_results.sql index 44eadf0e5..4cd54b29b 100644 --- a/src/test/regress/sql/intermediate_results.sql +++ b/src/test/regress/sql/intermediate_results.sql @@ -337,3 +337,5 @@ COMMIT; SET client_min_messages TO ERROR; DROP SCHEMA other_schema CASCADE; DROP SCHEMA intermediate_results CASCADE; +DROP OWNED BY some_other_user; +DROP USER some_other_user; diff --git a/src/test/regress/sql/issue_6758.sql b/src/test/regress/sql/issue_6758.sql new file mode 100644 index 000000000..dae340526 --- /dev/null +++ b/src/test/regress/sql/issue_6758.sql @@ -0,0 +1,55 @@ +CREATE SCHEMA issue_6758; +SET search_path to 'issue_6758'; + +CREATE TABLE dist0(id int); +SELECT create_distributed_table('dist0','id'); + +CREATE TABLE dist1(id int); +SELECT create_distributed_table('dist1','id'); + +-- added to verify we fixed the issue https://github.com/citusdata/citus/issues/6758 +-- generated by Citus query generator tool +SELECT + avg(avgsub.id) +FROM + ( + SELECT + table_0.id + FROM + ( + SELECT + table_1.id + FROM + ( + SELECT + table_2.id + FROM + ( + SELECT + table_3.id + FROM + ( + VALUES + (838) + ) AS table_3(id) FULL + JOIN dist0 AS table_4 USING (id) + WHERE + table_4.id = 3 + ) AS table_2 + WHERE + table_2.id = 2 + ORDER BY + id + LIMIT + 77 + ) AS table_1 + LEFT JOIN dist0 AS table_5 USING (id) + ORDER BY + id + LIMIT + 44 + ) AS table_0 FULL + JOIN dist1 AS table_6 USING (id) + ) AS avgsub; + +DROP SCHEMA issue_6758 CASCADE; diff --git a/src/test/regress/sql/limit_intermediate_size.sql b/src/test/regress/sql/limit_intermediate_size.sql index 5b013bf75..8f64c31fd 100644 --- a/src/test/regress/sql/limit_intermediate_size.sql +++ b/src/test/regress/sql/limit_intermediate_size.sql @@ -17,7 +17,7 @@ cte2 AS MATERIALIZED ( SELECT cte.user_id, cte.value_2 FROM cte,cte2 ORDER BY 1,2 LIMIT 10; -SET citus.max_intermediate_result_size TO 9; +SET citus.max_intermediate_result_size TO 17; WITH cte AS MATERIALIZED ( SELECT diff --git a/src/test/regress/sql/local_table_join.sql b/src/test/regress/sql/local_table_join.sql index 96b51ff69..8d0d7d332 100644 --- a/src/test/regress/sql/local_table_join.sql +++ b/src/test/regress/sql/local_table_join.sql @@ -60,11 +60,16 @@ CREATE MATERIALIZED VIEW mv2 AS SELECT * FROM distributed_table; SET client_min_messages TO DEBUG1; --- the user doesn't allow local / distributed table joinn +-- the user doesn't allow local / distributed table join + +SELECT master_remove_node('localhost', :master_port); -- https://github.com/citusdata/citus/issues/6958 + SET citus.local_table_join_policy TO 'never'; SELECT count(*) FROM postgres_table JOIN distributed_table USING(key); SELECT count(*) FROM postgres_table JOIN reference_table USING(key); +SELECT citus_set_coordinator_host('localhost'); -- https://github.com/citusdata/citus/issues/6958 + -- the user prefers local table recursively planned SET citus.local_table_join_policy TO 'prefer-local'; SELECT count(*) FROM postgres_table JOIN distributed_table USING(key); @@ -466,6 +471,7 @@ SELECT create_distributed_table('table2', 'a'); SELECT 1 AS res FROM table2 RIGHT JOIN (SELECT 1 FROM table1, table2) AS sub1 ON false; ROLLBACK; +SELECT master_remove_node('localhost', :master_port); -- https://github.com/citusdata/citus/issues/6958 BEGIN; SELECT create_reference_table('table1'); SELECT 1 AS res FROM table2 RIGHT JOIN (SELECT 1 FROM table1, table2) AS sub1 ON false; @@ -476,6 +482,7 @@ SELECT create_reference_table('table2'); SELECT 1 AS res FROM table2 RIGHT JOIN (SELECT 1 FROM table1, table2) AS sub1 ON false; ROLLBACK; +SELECT citus_set_coordinator_host('localhost'); -- https://github.com/citusdata/citus/issues/6958 RESET client_min_messages; \set VERBOSITY terse diff --git a/src/test/regress/sql/logical_replication.sql b/src/test/regress/sql/logical_replication.sql index e78b0a393..3f8e048ca 100644 --- a/src/test/regress/sql/logical_replication.sql +++ b/src/test/regress/sql/logical_replication.sql @@ -15,8 +15,6 @@ SELECT oid AS postgres_oid FROM pg_roles where rolname = 'postgres' \gset SELECT create_distributed_table('dist', 'id'); INSERT INTO dist SELECT generate_series(1, 100); -SELECT 1 from citus_add_node('localhost', :master_port, groupId := 0); - -- Create a publiction and subscription (including replication slot) manually. -- This allows us to test the cleanup logic at the start of the shard move. \c - - - :worker_1_port @@ -55,8 +53,6 @@ SET search_path TO logical_replication; select citus_move_shard_placement(6830002, 'localhost', :worker_1_port, 'localhost', :worker_2_port, 'force_logical'); -SELECT citus_remove_node('localhost', :master_port); - -- the subscription is still there, as there is no cleanup record for it -- we have created it manually SELECT count(*) from pg_subscription; diff --git a/src/test/regress/sql/merge.sql b/src/test/regress/sql/merge.sql index d663491ae..4fb911736 100644 --- a/src/test/regress/sql/merge.sql +++ b/src/test/regress/sql/merge.sql @@ -21,7 +21,9 @@ SET citus.next_shard_id TO 4000000; SET citus.explain_all_tasks TO true; SET citus.shard_replication_factor TO 1; SET citus.max_adaptive_executor_pool_size TO 1; +SET client_min_messages = warning; SELECT 1 FROM master_add_node('localhost', :master_port, groupid => 0); +RESET client_min_messages; CREATE TABLE source ( @@ -143,7 +145,7 @@ SELECT insert_data(); SELECT undistribute_table('target'); SELECT undistribute_table('source'); SELECT create_distributed_table('target', 'customer_id'); -SELECT create_distributed_table('source', 'customer_id'); +SELECT create_distributed_table('source', 'customer_id', colocate_with=>'target'); -- Updates one of the row with customer_id = 30002 SELECT * from target t WHERE t.customer_id = 30002; @@ -281,7 +283,7 @@ TRUNCATE t1; TRUNCATE s1; SELECT load(); SELECT create_distributed_table('t1', 'id'); -SELECT create_distributed_table('s1', 'id'); +SELECT create_distributed_table('s1', 'id', colocate_with=>'t1'); SELECT * FROM t1 order by id; @@ -369,7 +371,7 @@ SELECT insert_data(); SELECT undistribute_table('t2'); SELECT undistribute_table('s2'); SELECT create_distributed_table('t2', 'id'); -SELECT create_distributed_table('s2', 'id'); +SELECT create_distributed_table('s2', 'id', colocate_with => 't2'); SELECT * FROM t2 ORDER BY 1; SET citus.log_remote_commands to true; @@ -925,27 +927,25 @@ ROLLBACK; -- Test the same scenarios with distributed tables SELECT create_distributed_table('target_cj', 'tid'); -SELECT create_distributed_table('source_cj1', 'sid1'); -SELECT create_distributed_table('source_cj2', 'sid2'); +SELECT create_distributed_table('source_cj1', 'sid1', colocate_with => 'target_cj'); +SELECT create_distributed_table('source_cj2', 'sid2', colocate_with => 'target_cj'); BEGIN; -SET citus.log_remote_commands to true; MERGE INTO target_cj t -USING source_cj1 s1 INNER JOIN source_cj2 s2 ON sid1 = sid2 +USING (SELECT * FROM source_cj1 s1 INNER JOIN source_cj2 s2 ON sid1 = sid2) s ON t.tid = sid1 AND t.tid = 2 WHEN MATCHED THEN UPDATE SET src = src2 WHEN NOT MATCHED THEN DO NOTHING; -SET citus.log_remote_commands to false; SELECT * FROM target_cj ORDER BY 1; ROLLBACK; BEGIN; -- try accessing columns from either side of the source join MERGE INTO target_cj t -USING source_cj1 s2 - INNER JOIN source_cj2 s1 ON sid1 = sid2 AND val1 = 10 +USING (SELECT * FROM source_cj1 s2 + INNER JOIN source_cj2 s1 ON sid1 = sid2 AND val1 = 10) s ON t.tid = sid1 AND t.tid = 2 WHEN MATCHED THEN UPDATE SET src = src1, val = val2 @@ -983,7 +983,7 @@ ROLLBACK; -- Test PREPARE -PREPARE foo(int) AS +PREPARE merge_prepare(int) AS MERGE INTO target_cj target USING (SELECT * FROM source_cj1) sub ON target.tid = sub.sid1 AND target.tid = $1 @@ -995,11 +995,11 @@ WHEN NOT MATCHED THEN SELECT * FROM target_cj ORDER BY 1; BEGIN; -EXECUTE foo(2); -EXECUTE foo(2); -EXECUTE foo(2); -EXECUTE foo(2); -EXECUTE foo(2); +EXECUTE merge_prepare(2); +EXECUTE merge_prepare(2); +EXECUTE merge_prepare(2); +EXECUTE merge_prepare(2); +EXECUTE merge_prepare(2); SELECT * FROM target_cj ORDER BY 1; ROLLBACK; @@ -1007,10 +1007,10 @@ BEGIN; SET citus.log_remote_commands to true; SET client_min_messages TO DEBUG1; -EXECUTE foo(2); +EXECUTE merge_prepare(2); RESET client_min_messages; -EXECUTE foo(2); +EXECUTE merge_prepare(2); SET citus.log_remote_commands to false; SELECT * FROM target_cj ORDER BY 1; @@ -1037,7 +1037,7 @@ INSERT INTO citus_target SELECT i, 'target' FROM generate_series(250, 500) i; INSERT INTO citus_source SELECT i, 'source' FROM generate_series(1, 500) i; SELECT create_distributed_table('citus_target', 'id'); -SELECT create_distributed_table('citus_source', 'id'); +SELECT create_distributed_table('citus_source', 'id', colocate_with => 'citus_target'); -- -- This routine compares the target tables of Postgres and Citus and @@ -1536,32 +1536,472 @@ WHEN NOT MATCHED THEN INSERT VALUES(foo.s1); SELECT * FROM target_set ORDER BY 1, 2; +-- +-- Reference as a source +-- +CREATE TABLE reftarget_local(t1 int, t2 int); +CREATE TABLE refsource_ref(s1 int, s2 int); + +INSERT INTO reftarget_local VALUES(1, 0); +INSERT INTO reftarget_local VALUES(3, 100); +INSERT INTO refsource_ref VALUES(1, 1); +INSERT INTO refsource_ref VALUES(2, 2); +INSERT INTO refsource_ref VALUES(3, 3); + +MERGE INTO reftarget_local +USING (SELECT * FROM refsource_ref UNION SELECT * FROM refsource_ref) AS foo ON reftarget_local.t1 = foo.s1 +WHEN MATCHED AND reftarget_local.t2 = 100 THEN + DELETE +WHEN MATCHED THEN + UPDATE SET t2 = t2 + 100 +WHEN NOT MATCHED THEN + INSERT VALUES(foo.s1); + +DROP TABLE IF EXISTS pg_result; +SELECT * INTO pg_result FROM reftarget_local ORDER BY 1, 2; + +-- Make source table as reference (target is Postgres) +TRUNCATE reftarget_local; +TRUNCATE refsource_ref; +INSERT INTO reftarget_local VALUES(1, 0); +INSERT INTO reftarget_local VALUES(3, 100); +INSERT INTO refsource_ref VALUES(1, 1); +INSERT INTO refsource_ref VALUES(2, 2); +INSERT INTO refsource_ref VALUES(3, 3); +SELECT create_reference_table('refsource_ref'); + +MERGE INTO reftarget_local +USING (SELECT * FROM refsource_ref UNION SELECT * FROM refsource_ref) AS foo ON reftarget_local.t1 = foo.s1 +WHEN MATCHED AND reftarget_local.t2 = 100 THEN + DELETE +WHEN MATCHED THEN + UPDATE SET t2 = t2 + 100 +WHEN NOT MATCHED THEN + INSERT VALUES(foo.s1); +SELECT * INTO pg_ref FROM reftarget_local ORDER BY 1, 2; + +-- Should be equal +SELECT c.*, p.* +FROM pg_ref c, pg_result p +WHERE c.t1 = p.t1 +ORDER BY 1,2; + +-- Must return zero rows +SELECT count(*) +FROM pg_result FULL OUTER JOIN pg_ref ON pg_result.t1 = pg_ref.t1 +WHERE pg_result.t1 IS NULL OR pg_ref.t1 IS NULL; + +-- Now make both Citus tables, reference as source, local as target +TRUNCATE reftarget_local; +TRUNCATE refsource_ref; +INSERT INTO reftarget_local VALUES(1, 0); +INSERT INTO reftarget_local VALUES(3, 100); +INSERT INTO refsource_ref VALUES(1, 1); +INSERT INTO refsource_ref VALUES(2, 2); +INSERT INTO refsource_ref VALUES(3, 3); + +SELECT citus_add_local_table_to_metadata('reftarget_local'); + +MERGE INTO reftarget_local +USING (SELECT * FROM refsource_ref UNION SELECT * FROM refsource_ref) AS foo ON reftarget_local.t1 = foo.s1 +WHEN MATCHED AND reftarget_local.t2 = 100 THEN + DELETE +WHEN MATCHED THEN + UPDATE SET t2 = t2 + 100 +WHEN NOT MATCHED THEN + INSERT VALUES(foo.s1); +SELECT * INTO local_ref FROM reftarget_local ORDER BY 1, 2; + +-- Should be equal +SELECT c.*, p.* +FROM local_ref c, pg_result p +WHERE c.t1 = p.t1 +ORDER BY 1,2; + +-- Must return zero rows +SELECT count(*) +FROM pg_result FULL OUTER JOIN local_ref ON pg_result.t1 = local_ref.t1 +WHERE pg_result.t1 IS NULL OR local_ref.t1 IS NULL; + +-- Now make target as distributed, keep reference as source +TRUNCATE reftarget_local; +TRUNCATE refsource_ref; +INSERT INTO reftarget_local VALUES(1, 0); +INSERT INTO reftarget_local VALUES(3, 100); +INSERT INTO refsource_ref VALUES(1, 1); +INSERT INTO refsource_ref VALUES(2, 2); +INSERT INTO refsource_ref VALUES(3, 3); + +SELECT create_distributed_table('reftarget_local', 't1'); + +MERGE INTO reftarget_local +USING (SELECT * FROM refsource_ref UNION SELECT * FROM refsource_ref) AS foo ON reftarget_local.t1 = foo.s1 +WHEN MATCHED AND reftarget_local.t2 = 100 THEN + DELETE +WHEN MATCHED THEN + UPDATE SET t2 = t2 + 100 +WHEN NOT MATCHED THEN + INSERT VALUES(foo.s1); +SELECT * INTO dist_reftarget FROM reftarget_local ORDER BY 1, 2; + +-- Should be equal +SELECT c.*, p.* +FROM dist_reftarget c, pg_result p +WHERE c.t1 = p.t1 +ORDER BY 1,2; + +-- Must return zero rows +SELECT count(*) +FROM pg_result FULL OUTER JOIN dist_reftarget ON pg_result.t1 = dist_reftarget.t1 +WHERE pg_result.t1 IS NULL OR dist_reftarget.t1 IS NULL; + +-- +-- Distributed (target), Reference(source) +-- +CREATE TABLE demo_distributed(id1 int, val1 int); +CREATE TABLE demo_source_table(id2 int, val2 int); + +CREATE FUNCTION setup_demo_data() RETURNS VOID AS $$ +INSERT INTO demo_distributed VALUES(1, 100); +INSERT INTO demo_distributed VALUES(7, 100); +INSERT INTO demo_distributed VALUES(15, 100); +INSERT INTO demo_distributed VALUES(100, 0); +INSERT INTO demo_distributed VALUES(300, 100); +INSERT INTO demo_distributed VALUES(400, 0); + +INSERT INTO demo_source_table VALUES(1, 77); +INSERT INTO demo_source_table VALUES(15, 77); +INSERT INTO demo_source_table VALUES(75, 77); +INSERT INTO demo_source_table VALUES(100, 77); +INSERT INTO demo_source_table VALUES(300, 77); +INSERT INTO demo_source_table VALUES(400, 77); +INSERT INTO demo_source_table VALUES(500, 77); +$$ +LANGUAGE SQL; + +CREATE FUNCTION merge_demo_data() RETURNS VOID AS $$ +MERGE INTO demo_distributed t +USING demo_source_table s ON s.id2 = t.id1 +WHEN MATCHED AND t.val1= 0 THEN + DELETE +WHEN MATCHED THEN + UPDATE SET val1 = val1 + s.val2 +WHEN NOT MATCHED THEN + INSERT VALUES(s.id2, s.val2); +$$ +LANGUAGE SQL; + +SELECT setup_demo_data(); +SELECT merge_demo_data(); +SELECT * INTO pg_demo_result FROM demo_distributed ORDER BY 1, 2; + +TRUNCATE demo_distributed; +TRUNCATE demo_source_table; + +SELECT create_distributed_table('demo_distributed', 'id1'); +SELECT create_reference_table('demo_source_table'); + +SELECT setup_demo_data(); +SELECT merge_demo_data(); + +SELECT * INTO dist_demo_result FROM demo_distributed ORDER BY 1, 2; + +-- Should be equal +SELECT c.*, p.* +FROM dist_demo_result c, pg_demo_result p +WHERE c.id1 = p.id1 +ORDER BY 1,2; + +-- Must return zero rows +SELECT count(*) +FROM pg_demo_result p FULL OUTER JOIN dist_demo_result d ON p.id1 = d.id1 +WHERE p.id1 IS NULL OR d.id1 IS NULL; + +-- Now convert source as distributed, but non-colocated with target +DROP TABLE pg_demo_result, dist_demo_result; +SELECT undistribute_table('demo_distributed'); +SELECT undistribute_table('demo_source_table'); + +CREATE OR REPLACE FUNCTION merge_demo_data() RETURNS VOID AS $$ +MERGE INTO demo_distributed t +USING (SELECT id2,val2 FROM demo_source_table UNION SELECT val2,id2 FROM demo_source_table) AS s +ON t.id1 = s.id2 +WHEN MATCHED THEN + UPDATE SET val1 = val1 + 1; +$$ +LANGUAGE SQL; + +TRUNCATE demo_distributed; +TRUNCATE demo_source_table; + +SELECT setup_demo_data(); +SELECT merge_demo_data(); +SELECT * INTO pg_demo_result FROM demo_distributed ORDER BY 1, 2; + +SELECT create_distributed_table('demo_distributed', 'id1'); +SELECT create_distributed_table('demo_source_table', 'id2', colocate_with=>'none'); + +TRUNCATE demo_distributed; +TRUNCATE demo_source_table; + +SELECT setup_demo_data(); +SELECT merge_demo_data(); +SELECT * INTO dist_demo_result FROM demo_distributed ORDER BY 1, 2; + +-- Should be equal +SELECT c.*, p.* +FROM dist_demo_result c, pg_demo_result p +WHERE c.id1 = p.id1 +ORDER BY 1,2; + +-- Must return zero rows +SELECT count(*) +FROM pg_demo_result p FULL OUTER JOIN dist_demo_result d ON p.id1 = d.id1 +WHERE p.id1 IS NULL OR d.id1 IS NULL; + +-- Test with LIMIT + +CREATE OR REPLACE FUNCTION merge_demo_data() RETURNS VOID AS $$ +MERGE INTO demo_distributed t +USING (SELECT 999 as s3, demo_source_table.* FROM (SELECT * FROM demo_source_table ORDER BY 1 LIMIT 3) as foo LEFT JOIN demo_source_table USING(id2)) AS s +ON t.id1 = s.id2 +WHEN MATCHED THEN + UPDATE SET val1 = s3 +WHEN NOT MATCHED THEN + INSERT VALUES(id2, s3); +$$ +LANGUAGE SQL; + +DROP TABLE pg_demo_result, dist_demo_result; +SELECT undistribute_table('demo_distributed'); +SELECT undistribute_table('demo_source_table'); + +TRUNCATE demo_distributed; +TRUNCATE demo_source_table; + +SELECT setup_demo_data(); +SELECT merge_demo_data(); +SELECT * INTO pg_demo_result FROM demo_distributed ORDER BY 1, 2; + +SELECT create_distributed_table('demo_distributed', 'id1'); +SELECT create_distributed_table('demo_source_table', 'id2', colocate_with=>'none'); + +TRUNCATE demo_distributed; +TRUNCATE demo_source_table; + +SELECT setup_demo_data(); +SELECT merge_demo_data(); +SELECT * INTO dist_demo_result FROM demo_distributed ORDER BY 1, 2; + +-- Should be equal +SELECT c.*, p.* +FROM dist_demo_result c, pg_demo_result p +WHERE c.id1 = p.id1 +ORDER BY 1,2; + +-- Must return zero rows +SELECT count(*) +FROM pg_demo_result p FULL OUTER JOIN dist_demo_result d ON p.id1 = d.id1 +WHERE p.id1 IS NULL OR d.id1 IS NULL; + +-- Test explain with repartition +SET citus.explain_all_tasks TO false; +EXPLAIN (COSTS OFF) +MERGE INTO demo_distributed t +USING (SELECT 999 as s3, demo_source_table.* FROM (SELECT * FROM demo_source_table ORDER BY 1 LIMIT 3) as foo LEFT JOIN demo_source_table USING(id2)) AS s +ON t.id1 = s.id2 +WHEN MATCHED THEN + UPDATE SET val1 = s3 +WHEN NOT MATCHED THEN + INSERT VALUES(id2, s3); + +-- Test multiple join conditions on distribution column +MERGE INTO demo_distributed t +USING (SELECT id2+1 as key, id2+3 as key2 FROM demo_source_table) s +ON t.id1 = s.key2 ANd t.id1 = s.key +WHEN NOT MATCHED THEN + INSERT VALUES(s.key2, 333); + +MERGE INTO demo_distributed t +USING (SELECT id2+1 as key, id2+2 as key2 FROM demo_source_table) s +ON t.id1 = s.key2 AND t.id1 = s.key +WHEN NOT MATCHED THEN + DO NOTHING; + +MERGE INTO demo_distributed t +USING (SELECT id2+1 as key, id2+3 as key2 FROM demo_source_table) s +ON t.val1 = s.key2 AND t.id1 = s.key AND t.id1 = s.key2 +WHEN NOT MATCHED THEN + INSERT VALUES(s.key2, 444); + +-- Test aggregate functions in source-query +SELECT COUNT(*) FROM demo_distributed where val1 = 150; +SELECT COUNT(*) FROM demo_distributed where id1 = 2; + +-- One row with Key=7 updated in demo_distributed to 150 +MERGE INTO demo_distributed t +USING (SELECT count(DISTINCT id2)::int4 as key FROM demo_source_table GROUP BY val2) s +ON t.id1 = s.key +WHEN NOT MATCHED THEN INSERT VALUES(s.key, 1) +WHEN MATCHED THEN UPDATE SET val1 = 150; + +-- Seven rows with Key=2 inserted in demo_distributed +MERGE INTO demo_distributed t +USING (SELECT (count(DISTINCT val2) + 1)::int4 as key FROM demo_source_table GROUP BY id2) s +ON t.id1 = s.key +WHEN NOT MATCHED THEN INSERT VALUES(s.key, 1) +WHEN MATCHED THEN UPDATE SET val1 = 150; + +SELECT COUNT(*) FROM demo_distributed where val1 = 150; +SELECT COUNT(*) FROM demo_distributed where id1 = 2; + +-- +-- Test FALSE filters +-- +CREATE TABLE source_filter(order_id INT, customer_id INT, order_center VARCHAR, order_time timestamp); +CREATE TABLE target_filter(customer_id INT, last_order_id INT, order_center VARCHAR, order_count INT, last_order timestamp); + +SELECT create_distributed_table('source_filter', 'customer_id'); +SELECT create_distributed_table('target_filter', 'customer_id', colocate_with => 'source_filter'); + +CREATE FUNCTION load_filter() RETURNS VOID AS $$ + +TRUNCATE target_filter; +TRUNCATE source_filter; + +INSERT INTO target_filter VALUES(100, 11, 'trg', -1, '2022-01-01 00:00:00'); -- Match UPDATE +INSERT INTO target_filter VALUES(200, 11, 'trg', -1, '2022-01-01 00:00:00'); -- Match DELETE + +INSERT INTO source_filter VALUES(12, 100, 'src', '2022-01-01 00:00:00'); +INSERT INTO source_filter VALUES(12, 200, 'src', '2022-01-01 00:00:00'); +INSERT INTO source_filter VALUES(12, 300, 'src', '2022-01-01 00:00:00'); + +$$ +LANGUAGE SQL; + +--WHEN MATCH and FALSE +SELECT load_filter(); +MERGE INTO target_filter t +USING source_filter s +ON s.customer_id = t.customer_id +WHEN MATCHED AND t.customer_id = 100 AND (FALSE) THEN + UPDATE SET order_count = 999 +WHEN MATCHED AND t.customer_id = 200 THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.customer_id, s.order_id, s.order_center, 1, s.order_time); + +SELECT * FROM target_filter ORDER BY 1, 2; + +--WHEN NOT MATCH and 1=0 +SELECT load_filter(); +MERGE INTO target_filter t +USING source_filter s +ON s.customer_id = t.customer_id +WHEN MATCHED AND t.customer_id = 100 THEN + UPDATE SET order_count = 999 +WHEN MATCHED AND t.customer_id = 200 THEN + DELETE +WHEN NOT MATCHED AND (1=0) THEN + INSERT VALUES(s.customer_id, s.order_id, s.order_center, 1, s.order_time); + +SELECT * FROM target_filter ORDER BY 1, 2; + +--ON t.key = s.key AND 1 < 0 +SELECT load_filter(); +MERGE INTO target_filter t +USING source_filter s +ON s.customer_id = t.customer_id AND 1 < 0 +WHEN MATCHED AND t.customer_id = 100 THEN + UPDATE SET order_count = 999 +WHEN MATCHED AND t.customer_id = 200 THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.customer_id, s.order_id, s.order_center, 1, s.order_time); + +SELECT * FROM target_filter ORDER BY 1, 2; + +--(SELECT * FROM source_filter WHERE false) as source_filter +SELECT load_filter(); +MERGE INTO target_filter t +USING (SELECT * FROM source_filter WHERE false) s +ON s.customer_id = t.customer_id +WHEN MATCHED AND t.customer_id = 100 THEN + UPDATE SET order_count = 999 +WHEN MATCHED AND t.customer_id = 200 THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.customer_id, s.order_id, s.order_center, 1, s.order_time); + +SELECT * FROM target_filter ORDER BY 1, 2; + +-- Bug 6785 +CREATE TABLE source_6785( id integer, z int, d jsonb); +CREATE TABLE target_6785( id integer, z int, d jsonb); +SELECT create_distributed_table('target_6785','id'), create_distributed_table('source_6785', 'id'); +INSERT INTO source_6785 SELECT i,i FROM generate_series(0,5)i; + +SET client_min_messages TO DEBUG1; +MERGE INTO target_6785 sda +USING (SELECT * FROM source_6785 WHERE id = 1) sdn +ON sda.id = sdn.id AND sda.id = 2 +WHEN NOT matched THEN + INSERT (id, z) VALUES (sdn.id, 5); +RESET client_min_messages; + +SELECT * FROM target_6785 ORDER BY 1; + -- -- Error and Unsupported scenarios -- + +-- Test explain analyze with repartition +EXPLAIN ANALYZE +MERGE INTO demo_distributed t +USING (SELECT 999 as s3, demo_source_table.* FROM (SELECT * FROM demo_source_table ORDER BY 1 LIMIT 3) as foo LEFT JOIN demo_source_table USING(id2)) AS s +ON t.id1 = s.id2 +WHEN MATCHED THEN + UPDATE SET val1 = s3 +WHEN NOT MATCHED THEN + INSERT VALUES(id2, s3); + +-- Source without a table +MERGE INTO target_cj t +USING (VALUES (1, 1), (2, 1), (3, 3)) as s (sid, val) +ON t.tid = s.sid AND t.tid = 2 +WHEN MATCHED THEN + UPDATE SET val = s.val +WHEN NOT MATCHED THEN + DO NOTHING; + +-- Incomplete source +MERGE INTO target_cj t +USING (source_cj1 s1 INNER JOIN source_cj2 s2 ON sid1 = val2) s +ON t.tid = s.sid1 AND t.tid = 2 +WHEN MATCHED THEN + UPDATE SET src = src2 +WHEN NOT MATCHED THEN + DO NOTHING; + +-- Reference as a target and local as source +MERGE INTO refsource_ref +USING (SELECT * FROM reftarget_local UNION SELECT * FROM reftarget_local) AS foo ON refsource_ref.s1 = foo.t1 +WHEN MATCHED THEN + UPDATE SET s2 = s2 + 100 +WHEN NOT MATCHED THEN + INSERT VALUES(foo.t1); + MERGE INTO target_set USING source_set AS foo ON target_set.t1 = foo.s1 WHEN MATCHED THEN UPDATE SET ctid = '(0,100)'; -MERGE INTO target_set -USING (SELECT s1,s2 FROM source_set UNION SELECT s2,s1 FROM source_set) AS foo ON target_set.t1 = foo.s1 -WHEN MATCHED THEN - UPDATE SET t2 = t2 + 1; - -MERGE INTO target_set -USING (SELECT 2 as s3, source_set.* FROM (SELECT * FROM source_set LIMIT 1) as foo LEFT JOIN source_set USING( s1)) AS foo -ON target_set.t1 = foo.s1 -WHEN MATCHED THEN UPDATE SET t2 = t2 + 1 -WHEN NOT MATCHED THEN INSERT VALUES(s1, s3); - - -- modifying CTE not supported EXPLAIN -WITH cte_1 AS (DELETE FROM target_json) +WITH cte_1 AS (DELETE FROM target_json RETURNING *) MERGE INTO target_json sda -USING source_json sdn +USING cte_1 sdn ON sda.id = sdn.id WHEN NOT matched THEN INSERT (id, z) VALUES (sdn.id, 5); @@ -1610,6 +2050,7 @@ ON t.id = s.id WHEN NOT MATCHED THEN INSERT (id) VALUES(1000); +-- Colocated merge MERGE INTO t1 t USING s1 s ON t.id = s.id @@ -1622,6 +2063,13 @@ ON t.id = s.id WHEN NOT MATCHED THEN INSERT (val) VALUES(s.val); +-- Non-colocated merge +MERGE INTO t1 t +USING s1 s +ON t.id = s.val +WHEN NOT MATCHED THEN + INSERT (id) VALUES(s.id); + -- try updating the distribution key column BEGIN; MERGE INTO target_cj t @@ -1710,17 +2158,7 @@ WHEN MATCHED AND (merge_when_and_write()) THEN ROLLBACK; --- Joining on partition columns with sub-query -MERGE INTO t1 - USING (SELECT * FROM s1) sub ON (sub.val = t1.id) -- sub.val is not a distribution column - WHEN MATCHED AND sub.val = 0 THEN - DELETE - WHEN MATCHED THEN - UPDATE SET val = t1.val + 1 - WHEN NOT MATCHED THEN - INSERT (id, val) VALUES (sub.id, sub.val); - --- Joining on partition columns with CTE +-- Joining on non-partition columns with CTE source, but INSERT incorrect column WITH s1_res AS ( SELECT * FROM s1 ) @@ -1746,7 +2184,7 @@ MERGE INTO t1 WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s1_res.id, s1_res.val); --- With a single WHEN clause, which causes a non-left join +-- Join condition without target distribution column WITH s1_res AS ( SELECT * FROM s1 ) @@ -1853,34 +2291,12 @@ WHEN MATCHED THEN WHEN NOT MATCHED THEN INSERT VALUES(mv_source.id, mv_source.val); --- Distributed tables *must* be colocated +-- Do not allow constant values into the distribution column CREATE TABLE dist_target(id int, val varchar); SELECT create_distributed_table('dist_target', 'id'); CREATE TABLE dist_source(id int, val varchar); SELECT create_distributed_table('dist_source', 'id', colocate_with => 'none'); -MERGE INTO dist_target -USING dist_source -ON dist_target.id = dist_source.id -WHEN MATCHED THEN -UPDATE SET val = dist_source.val -WHEN NOT MATCHED THEN -INSERT VALUES(dist_source.id, dist_source.val); - --- Distributed tables *must* be joined on distribution column -CREATE TABLE dist_colocated(id int, val int); -SELECT create_distributed_table('dist_colocated', 'id', colocate_with => 'dist_target'); - -MERGE INTO dist_target -USING dist_colocated -ON dist_target.id = dist_colocated.val -- val is not the distribution column -WHEN MATCHED THEN -UPDATE SET val = dist_colocated.val -WHEN NOT MATCHED THEN -INSERT VALUES(dist_colocated.id, dist_colocated.val); - - --- Both the source and target must be distributed MERGE INTO dist_target USING (SELECT 100 id) AS source ON dist_target.id = source.id AND dist_target.val = 'const' @@ -1950,7 +2366,227 @@ UPDATE SET val = dist_source.val WHEN NOT MATCHED THEN INSERT VALUES(dist_source.id, dist_source.val); +-- test merge with single-shard tables + +CREATE SCHEMA query_single_shard_table; + +SET search_path TO query_single_shard_table; + +CREATE TABLE nullkey_c1_t1(a int, b int); +CREATE TABLE nullkey_c1_t2(a int, b int); +SELECT create_distributed_table('nullkey_c1_t1', null, colocate_with=>'none'); +SELECT create_distributed_table('nullkey_c1_t2', null, colocate_with=>'nullkey_c1_t1'); + +CREATE TABLE nullkey_c2_t1(a int, b int); +CREATE TABLE nullkey_c2_t2(a int, b int); +SELECT create_distributed_table('nullkey_c2_t1', null, colocate_with=>'none'); +SELECT create_distributed_table('nullkey_c2_t2', null, colocate_with=>'nullkey_c2_t1', distribution_type=>null); + +CREATE TABLE reference_table(a int, b int); +CREATE TABLE distributed_table(a int, b int); +CREATE TABLE citus_local_table(a int, b int); +SELECT create_reference_table('reference_table'); +SELECT create_distributed_table('distributed_table', 'a'); +SELECT citus_add_local_table_to_metadata('citus_local_table'); + +SET client_min_messages TO DEBUG2; +INSERT INTO reference_table SELECT i, i FROM generate_series(0, 5) i; + +INSERT INTO distributed_table SELECT i, i FROM generate_series(3, 8) i; + +INSERT INTO citus_local_table SELECT i, i FROM generate_series(0, 10) i; + +CREATE TABLE postgres_local_table(a int, b int); +INSERT INTO postgres_local_table SELECT i, i FROM generate_series(5, 10) i; + +-- with a colocated table +MERGE INTO nullkey_c1_t1 USING nullkey_c1_t2 ON (nullkey_c1_t1.a = nullkey_c1_t2.a) +WHEN MATCHED THEN UPDATE SET b = nullkey_c1_t2.b; + +MERGE INTO nullkey_c1_t1 USING nullkey_c1_t2 ON (nullkey_c1_t1.a = nullkey_c1_t2.a) +WHEN MATCHED THEN DELETE; + +MERGE INTO nullkey_c1_t1 USING nullkey_c1_t2 ON (nullkey_c1_t1.a = nullkey_c1_t2.a) +WHEN MATCHED THEN UPDATE SET b = nullkey_c1_t2.b +WHEN NOT MATCHED THEN INSERT VALUES (nullkey_c1_t2.a, nullkey_c1_t2.b); + +MERGE INTO nullkey_c1_t1 USING nullkey_c1_t2 ON (nullkey_c1_t1.a = nullkey_c1_t2.a) +WHEN MATCHED THEN DELETE +WHEN NOT MATCHED THEN INSERT VALUES (nullkey_c1_t2.a, nullkey_c1_t2.b); + +-- with non-colocated single-shard table +MERGE INTO nullkey_c1_t1 USING nullkey_c2_t1 ON (nullkey_c1_t1.a = nullkey_c2_t1.a) +WHEN MATCHED THEN UPDATE SET b = nullkey_c2_t1.b; + +MERGE INTO nullkey_c1_t1 USING nullkey_c2_t1 ON (nullkey_c1_t1.a = nullkey_c2_t1.a) +WHEN MATCHED THEN UPDATE SET b = nullkey_c2_t1.b +WHEN NOT MATCHED THEN INSERT VALUES (nullkey_c2_t1.a, nullkey_c2_t1.b); + +-- with a distributed table +MERGE INTO nullkey_c1_t1 USING distributed_table ON (nullkey_c1_t1.a = distributed_table.a) +WHEN MATCHED THEN UPDATE SET b = distributed_table.b +WHEN NOT MATCHED THEN INSERT VALUES (distributed_table.a, distributed_table.b); + +MERGE INTO distributed_table USING nullkey_c1_t1 ON (nullkey_c1_t1.a = distributed_table.a) +WHEN MATCHED THEN DELETE +WHEN NOT MATCHED THEN INSERT VALUES (nullkey_c1_t1.a, nullkey_c1_t1.b); + +-- with a reference table +MERGE INTO nullkey_c1_t1 USING reference_table ON (nullkey_c1_t1.a = reference_table.a) +WHEN MATCHED THEN UPDATE SET b = reference_table.b; + +MERGE INTO reference_table USING nullkey_c1_t1 ON (nullkey_c1_t1.a = reference_table.a) +WHEN MATCHED THEN UPDATE SET b = nullkey_c1_t1.b +WHEN NOT MATCHED THEN INSERT VALUES (nullkey_c1_t1.a, nullkey_c1_t1.b); + +-- with a citus local table +MERGE INTO nullkey_c1_t1 USING citus_local_table ON (nullkey_c1_t1.a = citus_local_table.a) +WHEN MATCHED THEN UPDATE SET b = citus_local_table.b; + +MERGE INTO citus_local_table USING nullkey_c1_t1 ON (nullkey_c1_t1.a = citus_local_table.a) +WHEN MATCHED THEN DELETE; + +-- with a postgres table +MERGE INTO nullkey_c1_t1 USING postgres_local_table ON (nullkey_c1_t1.a = postgres_local_table.a) +WHEN MATCHED THEN UPDATE SET b = postgres_local_table.b; + +MERGE INTO postgres_local_table USING nullkey_c1_t1 ON (nullkey_c1_t1.a = postgres_local_table.a) +WHEN MATCHED THEN UPDATE SET b = nullkey_c1_t1.b +WHEN NOT MATCHED THEN INSERT VALUES (nullkey_c1_t1.a, nullkey_c1_t1.b); + +-- using ctes +WITH cte AS ( + SELECT * FROM nullkey_c1_t1 +) +MERGE INTO nullkey_c1_t1 USING cte ON (nullkey_c1_t1.a = cte.a) +WHEN MATCHED THEN UPDATE SET b = cte.b; + +WITH cte AS ( + SELECT * FROM distributed_table +) +MERGE INTO nullkey_c1_t1 USING cte ON (nullkey_c1_t1.a = cte.a) +WHEN MATCHED THEN UPDATE SET b = cte.b; + +WITH cte AS materialized ( + SELECT * FROM distributed_table +) +MERGE INTO nullkey_c1_t1 USING cte ON (nullkey_c1_t1.a = cte.a) +WHEN MATCHED THEN UPDATE SET b = cte.b; + +SET client_min_messages TO WARNING; +DROP SCHEMA query_single_shard_table CASCADE; + +SET search_path TO merge_schema; + +-- Test Columnar table +CREATE TABLE target_columnar(cid int, name text) USING columnar; +SELECT create_distributed_table('target_columnar', 'cid'); +MERGE INTO target_columnar t +USING demo_source_table s +ON t.cid = s.id2 +WHEN MATCHED THEN + UPDATE SET name = 'Columnar table updated by MERGE' +WHEN NOT MATCHED THEN + DO NOTHING; + +MERGE INTO demo_distributed t +USING generate_series(0,100) as source(key) +ON (source.key + 1 = t.id1) + WHEN MATCHED THEN UPDATE SET val1 = 15; + +-- This should fail in planning stage itself +EXPLAIN MERGE INTO demo_distributed t +USING demo_source_table s +ON (s.id2 + 1 = t.id1) + WHEN MATCHED THEN UPDATE SET val1 = 15; + +-- Sub-queries and CTEs are not allowed in actions and ON clause +CREATE TABLE target_1 (a int, b int, c int); +SELECT create_distributed_table('target_1', 'a'); + +CREATE TABLE source_2 (a int, b int, c int); +SELECT create_distributed_table('source_2', 'a'); + +INSERT INTO target_1 VALUES(1, 2, 3); +INSERT INTO target_1 VALUES(4, 5, 6); +INSERT INTO target_1 VALUES(11, 12, 13); + +INSERT INTO source_2 VALUES(1, 2, 3); + +WITH cte_1 as (SELECT max(a) as max_a, max(b) as b FROM source_2) +MERGE INTO target_1 +USING cte_1 +ON (target_1.a = cte_1.b) +WHEN NOT MATCHED AND (SELECT max_a > 10 FROM cte_1) THEN + INSERT VALUES (cte_1.b, 100); + +WITH cte_1 as (SELECT a, b FROM source_2) +MERGE INTO target_1 +USING cte_1 +ON (target_1.a = cte_1.b) +WHEN NOT MATCHED AND (SELECT a > 10 FROM cte_1) THEN + INSERT VALUES (cte_1.b, 100); + +MERGE INTO target_1 +USING source_2 +ON (target_1.a = source_2.b) +WHEN NOT MATCHED AND (SELECT max_a > 10 FROM (SELECT max(a) as max_a, max(b) as b FROM target_1) as foo) THEN + INSERT VALUES (source_2.b, 100); + +-- or same with CTEs +WITH cte_1 as (SELECT max(a) as max_a, max(b) as b FROM target_1) +MERGE INTO target_1 +USING source_2 +ON (target_1.a = source_2.b) +WHEN NOT MATCHED AND (SELECT max_a > 10 FROM (SELECT max(a) as max_a, max(b) as b FROM target_1) as foo) THEN + INSERT VALUES (source_2.b, 100); + +WITH cte_1 as (SELECT a, b FROM target_1), cte_2 as (select b,a from target_1) +MERGE INTO target_1 +USING (SELECT * FROM source_2) as subq +ON (target_1.a = subq.b) +WHEN NOT MATCHED AND (SELECT a > 10 FROM cte_2) THEN + INSERT VALUES (subq.b, 100); + +MERGE INTO source_2 +USING target_1 +ON (target_1.a = source_2.a) +WHEN MATCHED THEN + UPDATE SET b = (SELECT max(a) FROM source_2); + +MERGE INTO source_2 +USING target_1 +ON (target_1.a = source_2.a) +WHEN NOT MATCHED THEN + INSERT VALUES (target_1.a,(select max(a) from target_1)); + +MERGE INTO target_1 +USING source_2 +ON (target_1.a = source_2.b) +WHEN NOT MATCHED AND (SELECT max(c) > 10 FROM source_2) THEN + INSERT VALUES (source_2.b, 100); + +-- Test in ON clause +MERGE INTO target_1 t2 +USING (SELECT * FROM source_2) AS t1 +ON (t1.a = t2.a AND (SELECT 1=1 FROM target_1)) +WHEN MATCHED THEN + DELETE; + +MERGE INTO target_1 t2 +USING (SELECT * FROM source_2) AS t1 +ON (t1.a = t2.a AND (SELECT max(a) > 55 FROM target_1)) +WHEN MATCHED THEN + DELETE; + +WITH cte_1 as (SELECT a, b FROM target_1), cte_2 as (select b,a from target_1) +MERGE INTO target_1 t2 +USING (SELECT * FROM cte_1) AS t1 +ON (t1.a = t2.a AND (SELECT max(a) > 55 FROM cte_2)) +WHEN MATCHED THEN + DELETE; + +RESET client_min_messages; DROP SERVER foreign_server CASCADE; DROP FUNCTION merge_when_and_write(); DROP SCHEMA merge_schema CASCADE; -SELECT 1 FROM master_remove_node('localhost', :master_port); diff --git a/src/test/regress/sql/merge_arbitrary.sql b/src/test/regress/sql/merge_arbitrary.sql index 17b7d4f90..6c0a931dc 100644 --- a/src/test/regress/sql/merge_arbitrary.sql +++ b/src/test/regress/sql/merge_arbitrary.sql @@ -131,3 +131,48 @@ BEGIN; EXECUTE local(0, 1); SELECT * FROM t1 order by id; ROLLBACK; + +-- Test prepared statements with repartition +PREPARE merge_repartition_pg(int,int,int,int) as + MERGE INTO pg_target target + USING (SELECT id+1+$1 as key, val FROM (SELECT * FROM pg_source UNION SELECT * FROM pg_source WHERE id = $2) as foo) as source + ON (source.key = target.id AND $3 < 10000) + WHEN MATCHED THEN UPDATE SET val = (source.key::int+$4) + WHEN NOT MATCHED THEN INSERT VALUES (source.key, source.val); + +PREPARE merge_repartition_citus(int,int,int,int) as + MERGE INTO citus_target target + USING (SELECT id+1+$1 as key, val FROM (SELECT * FROM citus_source UNION SELECT * FROM citus_source WHERE id = $2) as foo) as source + ON (source.key = target.id AND $3 < 10000) + WHEN MATCHED THEN UPDATE SET val = (source.key::int+$4) + WHEN NOT MATCHED THEN INSERT VALUES (source.key, source.val); + +EXECUTE merge_repartition_pg(1,1,1,1); +EXECUTE merge_repartition_citus(1,1,1,1); + +SET client_min_messages = NOTICE; +SELECT compare_data(); +RESET client_min_messages; + +EXECUTE merge_repartition_pg(1,100,1,1); +EXECUTE merge_repartition_citus(1,100,1,1); + +EXECUTE merge_repartition_pg(2,200,1,1); +EXECUTE merge_repartition_citus(2,200,1,1); + +EXECUTE merge_repartition_pg(3,300,1,1); +EXECUTE merge_repartition_citus(3,300,1,1); + +EXECUTE merge_repartition_pg(4,400,1,1); +EXECUTE merge_repartition_citus(4,400,1,1); + +EXECUTE merge_repartition_pg(5,500,1,1); +EXECUTE merge_repartition_citus(5,500,1,1); + +-- Sixth time +EXECUTE merge_repartition_pg(6,600,1,6); +EXECUTE merge_repartition_citus(6,600,1,6); + +SET client_min_messages = NOTICE; +SELECT compare_data(); +RESET client_min_messages; diff --git a/src/test/regress/sql/merge_arbitrary_create.sql b/src/test/regress/sql/merge_arbitrary_create.sql index edf9b0d9d..efa3185da 100644 --- a/src/test/regress/sql/merge_arbitrary_create.sql +++ b/src/test/regress/sql/merge_arbitrary_create.sql @@ -48,3 +48,57 @@ CREATE TABLE s1(id int, val int); SELECT citus_add_local_table_to_metadata('t1'); SELECT citus_add_local_table_to_metadata('s1'); + +-- Test prepared statements with repartition +CREATE TABLE pg_target(id int, val int); +CREATE TABLE pg_source(id int, val int, const int); +CREATE TABLE citus_target(id int, val int); +CREATE TABLE citus_source(id int, val int, const int); +SELECT citus_add_local_table_to_metadata('pg_target'); +SELECT citus_add_local_table_to_metadata('pg_source'); + +-- +-- Load same set of data to both Postgres and Citus tables +-- +CREATE OR REPLACE FUNCTION setup_data() RETURNS VOID AS $$ + INSERT INTO pg_source SELECT i, i+1, 1 FROM generate_series(1, 10000) i; + INSERT INTO pg_target SELECT i, 1 FROM generate_series(5001, 10000) i; + INSERT INTO citus_source SELECT i, i+1, 1 FROM generate_series(1, 10000) i; + INSERT INTO citus_target SELECT i, 1 FROM generate_series(5001, 10000) i; +$$ +LANGUAGE SQL; + +-- +-- Compares the final target tables, merge-modified data, of both Postgres and Citus tables +-- +CREATE OR REPLACE FUNCTION check_data(table1_name text, column1_name text, table2_name text, column2_name text) +RETURNS VOID AS $$ +DECLARE + table1_avg numeric; + table2_avg numeric; +BEGIN + EXECUTE format('SELECT COALESCE(AVG(%I), 0) FROM %I', column1_name, table1_name) INTO table1_avg; + EXECUTE format('SELECT COALESCE(AVG(%I), 0) FROM %I', column2_name, table2_name) INTO table2_avg; + + IF table1_avg > table2_avg THEN + RAISE EXCEPTION 'The average of %.% is greater than %.%', table1_name, column1_name, table2_name, column2_name; + ELSIF table1_avg < table2_avg THEN + RAISE EXCEPTION 'The average of %.% is less than %.%', table1_name, column1_name, table2_name, column2_name; + ELSE + RAISE NOTICE 'The average of %.% is equal to %.%', table1_name, column1_name, table2_name, column2_name; + END IF; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION compare_data() RETURNS VOID AS $$ + SELECT check_data('pg_target', 'id', 'citus_target', 'id'); + SELECT check_data('pg_target', 'val', 'citus_target', 'val'); +$$ +LANGUAGE SQL; + +-- +-- Target and source are distributed, and non-colocated +-- +SELECT setup_data(); +SELECT create_distributed_table('citus_target', 'id'); +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'none'); diff --git a/src/test/regress/sql/merge_partition_tables.sql b/src/test/regress/sql/merge_partition_tables.sql new file mode 100644 index 000000000..ab40fd23e --- /dev/null +++ b/src/test/regress/sql/merge_partition_tables.sql @@ -0,0 +1,164 @@ + +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +\else +\q +\endif + +-- We create two sets of source and target tables, one set in Postgres and +-- the other in Citus distributed. We run the _exact_ MERGE SQL on both sets +-- and compare the final results of the target tables in Postgres and Citus. +-- The results should match. This process is repeated for various combinations +-- of MERGE SQL. + +DROP SCHEMA IF EXISTS merge_partition_tables CASCADE; +CREATE SCHEMA merge_partition_tables; +SET search_path TO merge_partition_tables; +SET citus.shard_count TO 4; +SET citus.next_shard_id TO 7000000; +SET citus.explain_all_tasks TO true; +SET citus.shard_replication_factor TO 1; +SET citus.max_adaptive_executor_pool_size TO 1; +SET client_min_messages = warning; +SELECT 1 FROM master_add_node('localhost', :master_port, groupid => 0); +RESET client_min_messages; + + +CREATE TABLE pg_target(id int, val int) PARTITION BY RANGE(id); +CREATE TABLE pg_source(id int, val int, const int) PARTITION BY RANGE(val); +CREATE TABLE citus_target(id int, val int) PARTITION BY RANGE(id); +CREATE TABLE citus_source(id int, val int, const int) PARTITION BY RANGE(val); +SELECT citus_add_local_table_to_metadata('citus_target'); +SELECT citus_add_local_table_to_metadata('citus_source'); + +CREATE TABLE part1 PARTITION OF pg_target FOR VALUES FROM (1) TO (2500) WITH (autovacuum_enabled=off); +CREATE TABLE part2 PARTITION OF pg_target FOR VALUES FROM (2501) TO (5000) WITH (autovacuum_enabled=off); +CREATE TABLE part3 PARTITION OF pg_target FOR VALUES FROM (5001) TO (7500) WITH (autovacuum_enabled=off); +CREATE TABLE part4 PARTITION OF pg_target DEFAULT WITH (autovacuum_enabled=off); +CREATE TABLE part5 PARTITION OF citus_target FOR VALUES FROM (1) TO (2500) WITH (autovacuum_enabled=off); +CREATE TABLE part6 PARTITION OF citus_target FOR VALUES FROM (2501) TO (5000) WITH (autovacuum_enabled=off); +CREATE TABLE part7 PARTITION OF citus_target FOR VALUES FROM (5001) TO (7500) WITH (autovacuum_enabled=off); +CREATE TABLE part8 PARTITION OF citus_target DEFAULT WITH (autovacuum_enabled=off); + +CREATE TABLE part9 PARTITION OF pg_source FOR VALUES FROM (1) TO (2500) WITH (autovacuum_enabled=off); +CREATE TABLE part10 PARTITION OF pg_source FOR VALUES FROM (2501) TO (5000) WITH (autovacuum_enabled=off); +CREATE TABLE part11 PARTITION OF pg_source FOR VALUES FROM (5001) TO (7500) WITH (autovacuum_enabled=off); +CREATE TABLE part12 PARTITION OF pg_source DEFAULT WITH (autovacuum_enabled=off); +CREATE TABLE part13 PARTITION OF citus_source FOR VALUES FROM (1) TO (2500) WITH (autovacuum_enabled=off); +CREATE TABLE part14 PARTITION OF citus_source FOR VALUES FROM (2501) TO (5000) WITH (autovacuum_enabled=off); +CREATE TABLE part15 PARTITION OF citus_source FOR VALUES FROM (5001) TO (7500) WITH (autovacuum_enabled=off); +CREATE TABLE part16 PARTITION OF citus_source DEFAULT WITH (autovacuum_enabled=off); + +CREATE OR REPLACE FUNCTION cleanup_data() RETURNS VOID SET search_path TO merge_partition_tables AS $$ + TRUNCATE pg_target; + TRUNCATE pg_source; + TRUNCATE citus_target; + TRUNCATE citus_source; + SELECT undistribute_table('citus_target'); + SELECT undistribute_table('citus_source'); +$$ +LANGUAGE SQL; + +-- +-- Load same set of data to both Postgres and Citus tables +-- +CREATE OR REPLACE FUNCTION setup_data() RETURNS VOID SET search_path TO merge_partition_tables AS $$ + INSERT INTO pg_source SELECT i, i+1, 1 FROM generate_series(1, 10000) i; + INSERT INTO pg_target SELECT i, 1 FROM generate_series(5001, 10000) i; + INSERT INTO citus_source SELECT i, i+1, 1 FROM generate_series(1, 10000) i; + INSERT INTO citus_target SELECT i, 1 FROM generate_series(5001, 10000) i; +$$ +LANGUAGE SQL; + +-- +-- Compares the final target tables, merge-modified data, of both Postgres and Citus tables +-- +CREATE OR REPLACE FUNCTION check_data(table1_name text, column1_name text, table2_name text, column2_name text) +RETURNS VOID SET search_path TO merge_partition_tables AS $$ +DECLARE + table1_avg numeric; + table2_avg numeric; +BEGIN + EXECUTE format('SELECT COALESCE(AVG(%I), 0) FROM %I', column1_name, table1_name) INTO table1_avg; + EXECUTE format('SELECT COALESCE(AVG(%I), 0) FROM %I', column2_name, table2_name) INTO table2_avg; + + IF table1_avg > table2_avg THEN + RAISE EXCEPTION 'The average of %.% is greater than %.%', table1_name, column1_name, table2_name, column2_name; + ELSIF table1_avg < table2_avg THEN + RAISE EXCEPTION 'The average of %.% is less than %.%', table1_name, column1_name, table2_name, column2_name; + ELSE + RAISE NOTICE 'The average of %.% is equal to %.%', table1_name, column1_name, table2_name, column2_name; + END IF; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION compare_data() RETURNS VOID SET search_path TO merge_partition_tables AS $$ + SELECT check_data('pg_target', 'id', 'citus_target', 'id'); + SELECT check_data('pg_target', 'val', 'citus_target', 'val'); +$$ +LANGUAGE SQL; + +-- Test colocated partition tables + +SET client_min_messages = ERROR; +SELECT cleanup_data(); +SELECT setup_data(); +SELECT create_distributed_table('citus_target', 'id'); +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'citus_target'); +RESET client_min_messages; + +MERGE INTO pg_target t +USING pg_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); + +MERGE INTO citus_target t +USING citus_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); + +SELECT compare_data(); + +-- Test non-colocated partition tables + +SET client_min_messages = ERROR; +SELECT cleanup_data(); +SELECT setup_data(); +SELECT create_distributed_table('citus_target', 'id'); +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'none'); +RESET client_min_messages; + +MERGE INTO pg_target t +USING pg_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); + +MERGE INTO citus_target t +USING citus_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); + +SELECT compare_data(); +DROP SCHEMA merge_partition_tables CASCADE; diff --git a/src/test/regress/sql/merge_repartition1.sql b/src/test/regress/sql/merge_repartition1.sql new file mode 100644 index 000000000..858f4710c --- /dev/null +++ b/src/test/regress/sql/merge_repartition1.sql @@ -0,0 +1,580 @@ +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +\else +\q +\endif + +-- We create two sets of source and target tables, one set in Postgres and +-- the other in Citus distributed. We run the _exact_ MERGE SQL on both sets +-- and compare the final results of the target tables in Postgres and Citus. +-- The results should match. This process is repeated for various combinations +-- of MERGE SQL. + +DROP SCHEMA IF EXISTS merge_repartition1_schema CASCADE; +CREATE SCHEMA merge_repartition1_schema; +SET search_path TO merge_repartition1_schema; +SET citus.shard_count TO 4; +SET citus.next_shard_id TO 5000000; +SET citus.explain_all_tasks TO true; +SET citus.shard_replication_factor TO 1; +SET citus.max_adaptive_executor_pool_size TO 1; +SET client_min_messages = warning; +SELECT 1 FROM master_add_node('localhost', :master_port, groupid => 0); +RESET client_min_messages; + + +CREATE TABLE pg_target(id int, val int); +CREATE TABLE pg_source(id int, val int, const int); +CREATE TABLE citus_target(id int, val int); +CREATE TABLE citus_source(id int, val int, const int); +SELECT citus_add_local_table_to_metadata('citus_target'); +SELECT citus_add_local_table_to_metadata('citus_source'); + +CREATE OR REPLACE FUNCTION cleanup_data() RETURNS VOID SET search_path TO merge_repartition1_schema AS $$ + TRUNCATE pg_target; + TRUNCATE pg_source; + TRUNCATE citus_target; + TRUNCATE citus_source; + SELECT undistribute_table('citus_target'); + SELECT undistribute_table('citus_source'); +$$ +LANGUAGE SQL; +-- +-- Load same set of data to both Postgres and Citus tables +-- +CREATE OR REPLACE FUNCTION setup_data() RETURNS VOID SET search_path TO merge_repartition1_schema AS $$ + INSERT INTO pg_source SELECT i, i+1, 1 FROM generate_series(1, 10000) i; + INSERT INTO pg_target SELECT i, 1 FROM generate_series(5001, 10000) i; + INSERT INTO citus_source SELECT i, i+1, 1 FROM generate_series(1, 10000) i; + INSERT INTO citus_target SELECT i, 1 FROM generate_series(5001, 10000) i; +$$ +LANGUAGE SQL; + +-- +-- Compares the final target tables, merge-modified data, of both Postgres and Citus tables +-- +CREATE OR REPLACE FUNCTION check_data(table1_name text, column1_name text, table2_name text, column2_name text) +RETURNS VOID SET search_path TO merge_repartition1_schema AS $$ +DECLARE + table1_avg numeric; + table2_avg numeric; +BEGIN + EXECUTE format('SELECT COALESCE(AVG(%I), 0) FROM %I', column1_name, table1_name) INTO table1_avg; + EXECUTE format('SELECT COALESCE(AVG(%I), 0) FROM %I', column2_name, table2_name) INTO table2_avg; + + IF table1_avg > table2_avg THEN + RAISE EXCEPTION 'The average of %.% is greater than %.%', table1_name, column1_name, table2_name, column2_name; + ELSIF table1_avg < table2_avg THEN + RAISE EXCEPTION 'The average of %.% is less than %.%', table1_name, column1_name, table2_name, column2_name; + ELSE + RAISE NOTICE 'The average of %.% is equal to %.%', table1_name, column1_name, table2_name, column2_name; + END IF; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION compare_data() RETURNS VOID SET search_path TO merge_repartition1_schema AS $$ + SELECT check_data('pg_target', 'id', 'citus_target', 'id'); + SELECT check_data('pg_target', 'val', 'citus_target', 'val'); +$$ +LANGUAGE SQL; + +-- +-- Target and source are distributed, and non-colocated +-- +SELECT cleanup_data(); +SELECT setup_data(); +SELECT create_distributed_table('citus_target', 'id'); +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'none'); + +MERGE INTO pg_target t +USING pg_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); + +MERGE INTO citus_target t +USING citus_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); + +SELECT compare_data(); + +-- +-- Target and source are distributed, and colocated but not joined on distribution column +-- +SELECT cleanup_data(); +SELECT setup_data(); +SELECT create_distributed_table('citus_target', 'id'); +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'citus_target'); + +MERGE INTO pg_target t +USING (SELECT * FROM pg_source) subq +ON (subq.val = t.id) +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = subq.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(subq.val, subq.id); + +MERGE INTO citus_target t +USING (SELECT * FROM citus_source) subq +ON (subq.val = t.id) +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = subq.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(subq.val, subq.id); + +SELECT compare_data(); + +-- +-- Target and source are distributed, colocated, joined on distribution column +-- but with nondistribution values +-- +SELECT cleanup_data(); +SELECT setup_data(); +SELECT create_distributed_table('citus_target', 'id'); +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'citus_target'); + +MERGE INTO pg_target t +USING (SELECT id,const FROM pg_source UNION SELECT const,id FROM pg_source ) AS s +ON t.id = s.id +WHEN MATCHED THEN + UPDATE SET val = s.const + 1 +WHEN NOT MATCHED THEN + INSERT VALUES(id, const); + +MERGE INTO citus_target t +USING (SELECT id,const FROM citus_source UNION SELECT const,id FROM citus_source) AS s +ON t.id = s.id +WHEN MATCHED THEN + UPDATE SET val = s.const + 1 +WHEN NOT MATCHED THEN + INSERT VALUES(id, const); + +SELECT compare_data(); + +-- +-- Repartition with a predicate on target_table_name rows in ON clause +-- +SELECT cleanup_data(); +SELECT setup_data(); +SELECT create_distributed_table('citus_target', 'id'); +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'none'); + +MERGE INTO pg_target t +USING (SELECT * FROM pg_source WHERE id < 9500) s +ON t.id = s.id AND t.id < 9000 +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); + +MERGE INTO citus_target t +USING (SELECT * FROM citus_source WHERE id < 9500) s +ON t.id = s.id AND t.id < 9000 +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); + +SELECT compare_data(); + +-- +-- Test CTE and non-colocated tables +-- +SELECT cleanup_data(); +SELECT setup_data(); +SELECT create_distributed_table('citus_target', 'id'); +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'none'); + +WITH cte AS ( + SELECT * FROM pg_source +) +MERGE INTO pg_target t +USING cte s +ON s.id = t.id +WHEN MATCHED AND t.id > 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES (s.id, s.val); + +WITH cte AS ( + SELECT * FROM citus_source +) +MERGE INTO citus_target t +USING cte s +ON s.id = t.id +WHEN MATCHED AND t.id > 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES (s.id, s.val); + +SELECT compare_data(); + +-- +-- Test nested CTEs +-- +SELECT cleanup_data(); +SELECT setup_data(); +SELECT create_distributed_table('citus_target', 'id'); +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'none'); + +WITH cte1 AS ( + SELECT * FROM pg_source ORDER BY 1 LIMIT 9000 +), +cte2 AS( + SELECT * FROM cte1 +), +cte3 AS( + SELECT * FROM cte2 +) +MERGE INTO pg_target t +USING cte3 s +ON (s.id=t.id) +WHEN MATCHED AND t.id > 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES (s.id, s.val); + +WITH cte1 AS ( + SELECT * FROM citus_source ORDER BY 1 LIMIT 9000 +), +cte2 AS( + SELECT * FROM cte1 +), +cte3 AS( + SELECT * FROM cte2 +) +MERGE INTO citus_target t +USING cte3 s +ON (s.id=t.id) +WHEN MATCHED AND t.id > 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES (s.id, s.val); + +SELECT compare_data(); + +-- +-- Target and source are distributed and colocated +-- +SELECT cleanup_data(); +SELECT setup_data(); +SELECT create_distributed_table('citus_target', 'id'); +SELECT create_distributed_table('citus_source', 'id', colocate_with => 'citus_target'); + +MERGE INTO pg_target t +USING (SELECT 999 as newval, pg_source.* FROM (SELECT * FROM pg_source ORDER BY 1 LIMIT 6000) as src LEFT JOIN pg_source USING(id)) AS s +ON t.id = s.id +WHEN MATCHED AND t.id <= 5500 THEN + UPDATE SET val = newval +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(id, newval); + +MERGE INTO citus_target t +USING (SELECT 999 as newval, citus_source.* FROM (SELECT * FROM citus_source ORDER BY 1 LIMIT 6000) as src LEFT JOIN citus_source USING(id)) AS s +ON t.id = s.id +WHEN MATCHED AND t.id <= 5500 THEN + UPDATE SET val = newval +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(id, newval); + +SELECT compare_data(); + +-- +-- Target is distributed and source is reference +-- +SELECT cleanup_data(); +SELECT setup_data(); +SELECT create_distributed_table('citus_target', 'id'); +SELECT create_reference_table('citus_source'); + +MERGE INTO pg_target t +USING pg_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); + +MERGE INTO citus_target t +USING citus_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); + +SELECT compare_data(); + +-- +-- Target is distributed and reference as source in a sub-query +-- +SELECT cleanup_data(); +SELECT setup_data(); +SELECT create_distributed_table('citus_target', 'id'); +SELECT create_reference_table('citus_source'); + +MERGE INTO pg_target t +USING (SELECT * FROM pg_source UNION SELECT * FROM pg_source) AS s ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + t.val +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); + +MERGE INTO citus_target t +USING (SELECT * FROM citus_source UNION SELECT * FROM citus_source) AS s ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + t.val +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); +SELECT compare_data(); + +-- +-- Target is distributed and citus-local as source +-- +SELECT cleanup_data(); +SELECT setup_data(); +SELECT create_distributed_table('citus_target', 'id'); +SELECT citus_add_local_table_to_metadata('citus_source'); + +MERGE INTO pg_target t +USING pg_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); + +MERGE INTO citus_target t +USING citus_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); + +SELECT compare_data(); + +-- +-- Target and source distributed and non-colocated. The source query requires evaluation +-- at the coordinator +-- +SELECT cleanup_data(); +SELECT setup_data(); +SELECT create_distributed_table('citus_target', 'id'); +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'none'); + +MERGE INTO pg_target t +USING (SELECT 100 AS insval, MAX(const) AS updval, val, MAX(id) AS sid + FROM pg_source + GROUP BY val ORDER BY sid LIMIT 6000) AS s +ON t.id = s.sid +WHEN MATCHED AND t.id <= 5500 THEN + UPDATE SET val = updval + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(sid, insval); + +MERGE INTO citus_target t +USING (SELECT 100 AS insval, MAX(const) AS updval, val, MAX(id) AS sid + FROM citus_source + GROUP BY val ORDER BY sid LIMIT 6000) AS s +ON t.id = s.sid +WHEN MATCHED AND t.id <= 5500 THEN + UPDATE SET val = updval + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(sid, insval); + +SELECT compare_data(); + +-- Test source-query that requires repartitioning on top of MERGE repartitioning +SET client_min_messages TO WARNING; +SELECT cleanup_data(); +RESET client_min_messages; +SELECT setup_data(); +SELECT create_distributed_table('citus_target', 'id'); +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'none'); + +MERGE INTO pg_target t +USING (SELECT s1.val FROM pg_source s1 JOIN pg_source s2 USING (val)) AS s +ON t.id = s.val +WHEN MATCHED THEN + UPDATE SET val = t.val + 1; + +SET citus.enable_repartition_joins TO true; +MERGE INTO citus_target t +USING (SELECT s1.val FROM citus_source s1 JOIN citus_source s2 USING (val)) AS s +ON t.id = s.val +WHEN MATCHED THEN + UPDATE SET val = t.val + 1; + +SELECT compare_data(); + +-- +-- Test columnar as source table +-- +SET client_min_messages TO WARNING; +SELECT cleanup_data(); +RESET client_min_messages; +SELECT setup_data(); +SELECT create_distributed_table('citus_target', 'id'); +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'none'); +SELECT alter_table_set_access_method('citus_source', 'columnar'); + +MERGE INTO pg_target t +USING pg_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); + +MERGE INTO citus_target t +USING citus_source s +ON t.id = s.id +WHEN MATCHED AND t.id <= 7500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); + +SELECT compare_data(); +SELECT alter_table_set_access_method('citus_source', 'heap'); + +-- Test CTE/Subquery in merge-actions (works only for router query) +SET client_min_messages TO WARNING; +SELECT cleanup_data(); +RESET client_min_messages; +SELECT setup_data(); +SELECT create_distributed_table('citus_target', 'id'); +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'citus_target'); + +MERGE INTO pg_target +USING pg_source +ON (pg_target.id = pg_source.id) +WHEN MATCHED AND (SELECT max_a > 5001 FROM (SELECT max(id) as max_a, max(val) as b FROM pg_target WHERE id = pg_source.id) AS foo) THEN + DELETE +WHEN NOT MATCHED AND (SELECT max_a < 5001 FROM (SELECT max(id) as max_a, max(val) as b FROM pg_target WHERE id = pg_source.id) AS foo) THEN + INSERT VALUES (pg_source.id, 100); + +MERGE INTO citus_target +USING citus_source +ON (citus_target.id = citus_source.id) +WHEN MATCHED AND (SELECT max_a > 5001 FROM (SELECT max(id) as max_a, max(val) as b FROM citus_target WHERE id = citus_source.id) AS foo) THEN + DELETE +WHEN NOT MATCHED AND (SELECT max_a < 5001 FROM (SELECT max(id) as max_a, max(val) as b FROM citus_target WHERE id = citus_source.id) AS foo) THEN + INSERT VALUES (citus_source.id, 100); + +SELECT compare_data(); + +-- +-- Test target with false clause +-- +SET client_min_messages TO WARNING; +SELECT cleanup_data(); +RESET client_min_messages; +SELECT setup_data(); +SELECT create_distributed_table('citus_target', 'id'); +SELECT create_distributed_table('citus_source', 'id', colocate_with => 'citus_target'); + +MERGE INTO pg_target t +USING (SELECT * FROM pg_source WHERE id > 2500) AS s +ON t.id = s.id AND t.id < 2500 +WHEN MATCHED AND t.id <= 5500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); + +MERGE INTO citus_target t +USING (SELECT * FROM citus_source WHERE id > 2500) AS s +ON t.id = s.id AND t.id < 2500 +WHEN MATCHED AND t.id <= 5500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); + +SELECT compare_data(); + +SET client_min_messages TO WARNING; +SELECT cleanup_data(); +RESET client_min_messages; +SELECT setup_data(); +SELECT create_distributed_table('citus_target', 'id'); +SELECT create_distributed_table('citus_source', 'id', colocate_with => 'citus_target'); + +MERGE INTO pg_target t +USING (SELECT * FROM pg_source WHERE id = 2500) AS s +ON t.id = s.id AND t.id = 5000 +WHEN MATCHED AND t.id <= 5500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); + +MERGE INTO citus_target t +USING (SELECT * FROM citus_source WHERE id = 2500) AS s +ON t.id = s.id AND t.id = 5000 +WHEN MATCHED AND t.id <= 5500 THEN + UPDATE SET val = s.val + 1 +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.id, s.val); + +SELECT compare_data(); + +DROP SCHEMA merge_repartition1_schema CASCADE; diff --git a/src/test/regress/sql/merge_repartition2.sql b/src/test/regress/sql/merge_repartition2.sql new file mode 100644 index 000000000..7a4812274 --- /dev/null +++ b/src/test/regress/sql/merge_repartition2.sql @@ -0,0 +1,139 @@ + +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +\else +\q +\endif + +-- We create two sets of source and target tables, one set in Postgres and +-- the other in Citus distributed. We run the _exact_ MERGE SQL on both sets +-- and compare the final results of the target tables in Postgres and Citus. +-- The results should match. This process is repeated for various combinations +-- of MERGE SQL. + +DROP SCHEMA IF EXISTS merge_repartition2_schema CASCADE; +CREATE SCHEMA merge_repartition2_schema; +SET search_path TO merge_repartition2_schema; +SET citus.shard_count TO 4; +SET citus.next_shard_id TO 6000000; +SET citus.explain_all_tasks TO true; +SET citus.shard_replication_factor TO 1; +SET citus.max_adaptive_executor_pool_size TO 1; +SET client_min_messages = warning; +SELECT 1 FROM master_add_node('localhost', :master_port, groupid => 0); +RESET client_min_messages; + + +CREATE TABLE pg_target(id int, val int); +CREATE TABLE pg_source(id int, val int, const int); +CREATE TABLE citus_target(id int, val int); +CREATE TABLE citus_source(id int, val int, const int); +SELECT citus_add_local_table_to_metadata('citus_target'); +SELECT citus_add_local_table_to_metadata('citus_source'); + +CREATE OR REPLACE FUNCTION cleanup_data() RETURNS VOID SET search_path TO merge_repartition2_schema AS $$ + TRUNCATE pg_target; + TRUNCATE pg_source; + TRUNCATE citus_target; + TRUNCATE citus_source; + SELECT undistribute_table('citus_target'); + SELECT undistribute_table('citus_source'); +$$ +LANGUAGE SQL; +-- +-- Load same set of data to both Postgres and Citus tables +-- +CREATE OR REPLACE FUNCTION setup_data() RETURNS VOID SET search_path TO merge_repartition2_schema AS $$ + INSERT INTO pg_source SELECT i, i+1, 1 FROM generate_series(1, 100000) i; + INSERT INTO pg_target SELECT i, 1 FROM generate_series(50001, 100000) i; + INSERT INTO citus_source SELECT i, i+1, 1 FROM generate_series(1, 100000) i; + INSERT INTO citus_target SELECT i, 1 FROM generate_series(50001, 100000) i; +$$ +LANGUAGE SQL; + +-- +-- Compares the final target tables, merge-modified data, of both Postgres and Citus tables +-- +CREATE OR REPLACE FUNCTION check_data(table1_name text, column1_name text, table2_name text, column2_name text) +RETURNS VOID SET search_path TO merge_repartition2_schema AS $$ +DECLARE + table1_avg numeric; + table2_avg numeric; +BEGIN + EXECUTE format('SELECT COALESCE(AVG(%I), 0) FROM %I', column1_name, table1_name) INTO table1_avg; + EXECUTE format('SELECT COALESCE(AVG(%I), 0) FROM %I', column2_name, table2_name) INTO table2_avg; + + IF table1_avg > table2_avg THEN + RAISE EXCEPTION 'The average of %.% is greater than %.%', table1_name, column1_name, table2_name, column2_name; + ELSIF table1_avg < table2_avg THEN + RAISE EXCEPTION 'The average of %.% is less than %.%', table1_name, column1_name, table2_name, column2_name; + ELSE + RAISE NOTICE 'The average of %.% is equal to %.%', table1_name, column1_name, table2_name, column2_name; + END IF; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION compare_data() RETURNS VOID SET search_path TO merge_repartition2_schema AS $$ + SELECT check_data('pg_target', 'id', 'citus_target', 'id'); + SELECT check_data('pg_target', 'val', 'citus_target', 'val'); +$$ +LANGUAGE SQL; + +-- Test nested cte +SELECT cleanup_data(); +SELECT setup_data(); +SELECT create_distributed_table('citus_target', 'id'); +SELECT create_distributed_table('citus_source', 'id', colocate_with=>'none'); + +WITH cte_top AS(WITH cte_1 AS (WITH cte_2 AS (SELECT id, val FROM pg_source) SELECT * FROM cte_2) SELECT * FROM cte_1) +MERGE INTO pg_target t +USING (SELECT const, val, id FROM pg_source WHERE id IN (SELECT id FROM cte_top)) as s +ON (s.id = t.id) +WHEN MATCHED AND t.id <= 75000 THEN + UPDATE SET val = (s.val::int8+1) +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES (s.id, s.val); + +WITH cte_top AS(WITH cte_1 AS (WITH cte_2 AS (SELECT id, val FROM citus_source) SELECT * FROM cte_2) SELECT * FROM cte_1) +MERGE INTO citus_target t +USING (SELECT const, val, id FROM citus_source WHERE id IN (SELECT id FROM cte_top)) as s +ON (s.id = t.id) +WHEN MATCHED AND t.id <= 75000 THEN + UPDATE SET val = (s.val::int8+1) +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES (s.id, s.val); + +SELECT compare_data(); + +-- Test aggregate function in source query + +MERGE INTO pg_target t +USING (SELECT count(id+1)::text as value, val as key FROM pg_source group by key) s +ON t.id = s.key +WHEN MATCHED AND t.id <= 75000 THEN + UPDATE SET val = (s.value::int8+1) +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.key, value::int4+10); + +MERGE INTO citus_target t +USING (SELECT count(id+1)::text as value, val as key FROM citus_source group by key) s +ON t.id = s.key +WHEN MATCHED AND t.id <= 75000 THEN + UPDATE SET val = (s.value::int8+1) +WHEN MATCHED THEN + DELETE +WHEN NOT MATCHED THEN + INSERT VALUES(s.key, value::int4+10); + +SELECT compare_data(); + +DROP SCHEMA merge_repartition2_schema CASCADE; + diff --git a/src/test/regress/sql/minimal_cluster_management.sql b/src/test/regress/sql/minimal_cluster_management.sql index 424daccac..30f69d43d 100644 --- a/src/test/regress/sql/minimal_cluster_management.sql +++ b/src/test/regress/sql/minimal_cluster_management.sql @@ -13,6 +13,9 @@ ALTER SEQUENCE pg_catalog.pg_dist_node_nodeid_seq RESTART 16; ALTER SEQUENCE pg_catalog.pg_dist_groupid_seq RESTART 14; SELECT 1 FROM master_add_node('localhost', :worker_1_port); +-- make sure coordinator is always in metadata. +SELECT citus_set_coordinator_host('localhost'); + -- Create the same colocation groups as multi_cluster_management.sql SET citus.shard_count TO 16; SET citus.shard_replication_factor TO 1; diff --git a/src/test/regress/sql/multi_alter_table_add_constraints_without_name.sql b/src/test/regress/sql/multi_alter_table_add_constraints_without_name.sql index fe8bb4b20..700e37f6e 100644 --- a/src/test/regress/sql/multi_alter_table_add_constraints_without_name.sql +++ b/src/test/regress/sql/multi_alter_table_add_constraints_without_name.sql @@ -620,10 +620,6 @@ DROP TABLE AT_AddConstNoName.dist_partitioned_table; -- Test "ADD PRIMARY KEY" \c - - :master_host :master_port -SET client_min_messages to ERROR; -SELECT 1 FROM master_add_node('localhost', :master_port, groupId => 0); -RESET client_min_messages; - CREATE TABLE AT_AddConstNoName.citus_local_table(id int, other_column int); SELECT citus_add_local_table_to_metadata('AT_AddConstNoName.citus_local_table'); @@ -821,8 +817,6 @@ SELECT con.conname WHERE rel.relname LIKE 'longlonglonglonglonglonglonglonglong%' ORDER BY con.conname ASC; \c - - :master_host :master_port -SELECT 1 FROM master_remove_node('localhost', :master_port); - -- Test with unusual table and column names CREATE TABLE AT_AddConstNoName."2nd table" ( "2nd id" INTEGER, "3rd id" INTEGER); SELECT create_distributed_table('AT_AddConstNoName."2nd table"','2nd id'); diff --git a/src/test/regress/sql/multi_alter_table_add_foreign_key_without_name.sql b/src/test/regress/sql/multi_alter_table_add_foreign_key_without_name.sql index 330ac0c45..f7ffd66a0 100644 --- a/src/test/regress/sql/multi_alter_table_add_foreign_key_without_name.sql +++ b/src/test/regress/sql/multi_alter_table_add_foreign_key_without_name.sql @@ -69,14 +69,14 @@ ALTER TABLE referencing_table ADD FOREIGN KEY (id) REFERENCES referenced_table(i DROP TABLE referencing_table; DROP TABLE referenced_table; --- test foreign constraint creation is not supported when one of the tables is not a citus table +-- test foreign constraint creation is supported when coordinator is in metadata CREATE TABLE referenced_local_table(id int PRIMARY KEY, other_column int); CREATE TABLE reference_table(id int, referencing_column int); SELECT create_reference_table('reference_table'); ALTER TABLE reference_table ADD FOREIGN KEY (referencing_column) REFERENCES referenced_local_table(id); DROP TABLE referenced_local_table; -DROP TABLE reference_table; +DROP TABLE reference_table CASCADE; -- test foreign constraint with correct conditions CREATE TABLE referenced_table(id int PRIMARY KEY, test_column int); @@ -89,14 +89,14 @@ SELECT con.conname FROM pg_catalog.pg_constraint con INNER JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid INNER JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace - WHERE rel.relname = 'referencing_table'; + WHERE rel.relname = 'referencing_table' ORDER BY con.conname ASC; \c - - :public_worker_1_host :worker_1_port SELECT con.conname FROM pg_catalog.pg_constraint con INNER JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid INNER JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace - WHERE rel.relname LIKE 'referencing_table%'; + WHERE rel.relname LIKE 'referencing_table%' ORDER BY con.conname ASC; \c - - :master_host :master_port SET SEARCH_PATH = at_add_fk; @@ -109,14 +109,14 @@ SELECT con.conname FROM pg_catalog.pg_constraint con INNER JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid INNER JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace - WHERE rel.relname = 'referencing_table'; + WHERE rel.relname = 'referencing_table' ORDER BY con.conname ASC; \c - - :public_worker_1_host :worker_1_port SELECT con.conname FROM pg_catalog.pg_constraint con INNER JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid INNER JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace - WHERE rel.relname LIKE 'referencing_table%'; + WHERE rel.relname LIKE 'referencing_table%' ORDER BY con.conname ASC; \c - - :master_host :master_port SET SEARCH_PATH = at_add_fk; @@ -352,7 +352,6 @@ DROP TABLE dist_table CASCADE; DROP TABLE reference_table CASCADE; -- test ADD FOREIGN KEY from citus local to reference table -SELECT 1 FROM master_add_node('localhost', :master_port, groupId => 0); CREATE TABLE citus_local_table(l1 int); SELECT citus_add_local_table_to_metadata('citus_local_table'); @@ -373,8 +372,20 @@ ALTER TABLE citus_local_table ADD FOREIGN KEY(l1) REFERENCES reference_table(r1) ALTER TABLE citus_local_table ADD FOREIGN KEY(l1) REFERENCES reference_table(r1) ON DELETE RESTRICT; DROP TABLE citus_local_table CASCADE; -SELECT 1 FROM master_remove_node('localhost', :master_port); RESET SEARCH_PATH; RESET client_min_messages; DROP SCHEMA at_add_fk CASCADE; + +-- test ADD FOREIGN KEY when REFERENCED table is in another schema. +CREATE SCHEMA schema_1; + +CREATE TABLE schema_1.referenced_table(a int PRIMARY KEY, b int); +SELECT create_reference_table('schema_1.referenced_table'); + +CREATE SCHEMA schema_2; + +CREATE TABLE schema_2.referencing_table (a int PRIMARY KEY, b int, c text); +ALTER TABLE schema_2.referencing_table ADD FOREIGN KEY (b) REFERENCES schema_1.referenced_table(a); + +DROP SCHEMA schema_1, schema_2 CASCADE; diff --git a/src/test/regress/sql/multi_cluster_management.sql b/src/test/regress/sql/multi_cluster_management.sql index 126d8385e..9ec0eb28e 100644 --- a/src/test/regress/sql/multi_cluster_management.sql +++ b/src/test/regress/sql/multi_cluster_management.sql @@ -13,6 +13,10 @@ RESET citus.metadata_sync_mode; -- I am coordinator SELECT citus_is_coordinator(); + +-- make sure coordinator is always in metadata. +SELECT citus_set_coordinator_host('localhost'); + -- workers are not coordinator SELECT result FROM run_command_on_workers('SELECT citus_is_coordinator()'); @@ -268,6 +272,7 @@ SELECT nodename, nodeport FROM pg_dist_node WHERE nodename='localhost' AND nodep \c - - - :master_port SELECT master_remove_node(nodename, nodeport) FROM pg_dist_node; +SELECT citus_set_coordinator_host('localhost'); SELECT 1 FROM master_add_node('localhost', :worker_1_port); SELECT 1 FROM master_add_node('localhost', :worker_2_port); diff --git a/src/test/regress/sql/multi_drop_extension.sql b/src/test/regress/sql/multi_drop_extension.sql index b1c569bfd..0bb3c3ecd 100644 --- a/src/test/regress/sql/multi_drop_extension.sql +++ b/src/test/regress/sql/multi_drop_extension.sql @@ -23,8 +23,6 @@ BEGIN; SET search_path TO public; CREATE EXTENSION citus; - SELECT 1 FROM master_add_node('localhost', :master_port, groupId => 0); - create table l1 (a int unique); SELECT create_reference_table('l1'); @@ -34,8 +32,13 @@ BEGIN; alter table other_schema.l3 add constraint fkey foreign key (a) references l1(a); - -- show that works fine - drop schema public cascade; + -- Commented out because it fails due to the issue documented in + -- https://github.com/citusdata/citus/issues/6901. + -- + -- This wasn't the case before https://github.com/citusdata/citus/pull/6900. + -- This is because, we were not marking the schemas as distributed when there + -- are no worker nodes in the cluster before that PR. + -- drop schema public cascade; ROLLBACK; CREATE EXTENSION citus; @@ -130,6 +133,7 @@ ROLLBACK; CREATE EXTENSION citus; -- re-add the nodes to the cluster +SELECT citus_set_coordinator_host('localhost'); SELECT 1 FROM master_add_node('localhost', :worker_1_port); SELECT 1 FROM master_add_node('localhost', :worker_2_port); diff --git a/src/test/regress/sql/multi_explain.sql b/src/test/regress/sql/multi_explain.sql index 4429e46e7..dd4615434 100644 --- a/src/test/regress/sql/multi_explain.sql +++ b/src/test/regress/sql/multi_explain.sql @@ -13,7 +13,7 @@ SET citus.enable_repartition_joins to ON; SET citus.enable_binary_protocol = TRUE; -- Function that parses explain output as JSON -CREATE FUNCTION explain_json(query text) +CREATE OR REPLACE FUNCTION explain_json(query text) RETURNS jsonb AS $BODY$ DECLARE @@ -24,7 +24,7 @@ BEGIN END; $BODY$ LANGUAGE plpgsql; -CREATE FUNCTION explain_analyze_json(query text) +CREATE OR REPLACE FUNCTION explain_analyze_json(query text) RETURNS jsonb AS $BODY$ DECLARE @@ -36,7 +36,7 @@ END; $BODY$ LANGUAGE plpgsql; -- Function that parses explain output as XML -CREATE FUNCTION explain_xml(query text) +CREATE OR REPLACE FUNCTION explain_xml(query text) RETURNS xml AS $BODY$ DECLARE @@ -48,7 +48,7 @@ END; $BODY$ LANGUAGE plpgsql; -- Function that parses explain output as XML -CREATE FUNCTION explain_analyze_xml(query text) +CREATE OR REPLACE FUNCTION explain_analyze_xml(query text) RETURNS xml AS $BODY$ DECLARE @@ -559,6 +559,7 @@ EXPLAIN (COSTS FALSE, FORMAT YAML) -- ensure local plans display correctly CREATE TABLE lineitem_clone (LIKE lineitem); EXPLAIN (COSTS FALSE) SELECT avg(l_linenumber) FROM lineitem_clone; +DROP TABLE lineitem_clone; -- ensure distributed plans don't break EXPLAIN (COSTS FALSE) SELECT avg(l_linenumber) FROM lineitem; @@ -1048,6 +1049,8 @@ EXPLAIN :default_explain_flags EXECUTE distributed_insert_select('x', 'y'); EXPLAIN :default_analyze_flags EXECUTE distributed_insert_select('x', 'y'); deallocate distributed_insert_select; +DROP TABLE simple; + -- prepared cte BEGIN; PREPARE cte_query AS @@ -1079,6 +1082,8 @@ EXPLAIN :default_analyze_flags execute p4(20,20); -- simple test to confirm we can fetch long (>4KB) plans EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM users_table_2 WHERE value_1::text = '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000X'; +DROP TABLE users_table_2; + -- sorted explain analyze output CREATE TABLE explain_analyze_execution_time (a int); INSERT INTO explain_analyze_execution_time VALUES (2); @@ -1142,5 +1147,21 @@ PREPARE q2(int_wrapper_type) AS WITH a AS (UPDATE tbl SET b = $1 WHERE a = 1 RET EXPLAIN (COSTS false) EXECUTE q2('(1)'); EXPLAIN :default_analyze_flags EXECUTE q2('(1)'); +-- check when auto explain + analyze is enabled, we do not allow local execution. +CREATE SCHEMA test_auto_explain; +SET search_path TO 'test_auto_explain'; + +CREATE TABLE test_ref_table (key int PRIMARY KEY); +SELECT create_reference_table('test_ref_table'); + +LOAD 'auto_explain'; +SET auto_explain.log_min_duration = 0; +set auto_explain.log_analyze to true; + +-- the following should not be locally executed since explain analyze is on +select * from test_ref_table; + +DROP SCHEMA test_auto_explain CASCADE; + SET client_min_messages TO ERROR; DROP SCHEMA multi_explain CASCADE; diff --git a/src/test/regress/sql/multi_extension.sql b/src/test/regress/sql/multi_extension.sql index cbad97524..72d939867 100644 --- a/src/test/regress/sql/multi_extension.sql +++ b/src/test/regress/sql/multi_extension.sql @@ -593,6 +593,17 @@ SELECT * FROM multi_extension.print_extension_changes(); -- Test downgrade to 11.3-1 from 12.0-1 ALTER EXTENSION citus UPDATE TO '12.0-1'; + +CREATE TABLE null_shard_key (x int, y int); +SET citus.shard_replication_factor TO 1; +SELECT create_distributed_table('null_shard_key', null); + +-- Show that we cannot downgrade to 11.3-1 becuase the cluster has a +-- distributed table with single-shard. +ALTER EXTENSION citus UPDATE TO '11.3-1'; + +DROP TABLE null_shard_key; + ALTER EXTENSION citus UPDATE TO '11.3-1'; -- Should be empty result since upgrade+downgrade should be a no-op SELECT * FROM multi_extension.print_extension_changes(); @@ -904,6 +915,55 @@ SELECT create_distributed_table('test','x'); DROP TABLE test; TRUNCATE pg_dist_node; +-- confirm that we can create a single-shard table on an empty node +CREATE TABLE test (x int, y int); +INSERT INTO test VALUES (1,2); +SET citus.shard_replication_factor TO 1; +SELECT create_distributed_table('test', null, colocate_with=>'none', distribution_type=>null); + +-- and make sure that we can't remove the coordinator due to "test" +SELECT citus_remove_node('localhost', :master_port); + +DROP TABLE test; + +-- and now we should be able to remove the coordinator +SELECT citus_remove_node('localhost', :master_port); + +-- confirm that we can create a tenant schema / table on an empty node + +SET citus.enable_schema_based_sharding TO ON; + +CREATE SCHEMA tenant_schema; +CREATE TABLE tenant_schema.test(x int, y int); + +SELECT colocationid = ( + SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_schema.test'::regclass +) +FROM pg_dist_schema +WHERE schemaid::regnamespace::text = 'tenant_schema'; + +-- and make sure that we can't remove the coordinator due to "test" +SELECT citus_remove_node('localhost', :master_port); + +BEGIN; + SET LOCAL client_min_messages TO WARNING; + DROP SCHEMA tenant_schema CASCADE; +COMMIT; + +-- and now we should be able to remove the coordinator +SELECT citus_remove_node('localhost', :master_port); + +CREATE SCHEMA tenant_schema; + +-- Make sure that we can sync metadata for empty tenant schemas +-- when adding the first node to the cluster. +SELECT 1 FROM citus_add_node('localhost', :worker_1_port); + +DROP SCHEMA tenant_schema; +SELECT citus_remove_node('localhost', :worker_1_port); + +RESET citus.enable_schema_based_sharding; + -- confirm that we can create a reference table on an empty node CREATE TABLE test (x int, y int); INSERT INTO test VALUES (1,2); @@ -916,6 +976,19 @@ CREATE TABLE test (x int, y int); INSERT INTO test VALUES (1,2); SELECT citus_add_local_table_to_metadata('test'); DROP TABLE test; + +-- Verify that we don't consider the schemas created by extensions as tenant schemas. +-- Easiest way of verifying this is to drop and re-create columnar extension. +DROP EXTENSION citus_columnar; + +SET citus.enable_schema_based_sharding TO ON; + +CREATE EXTENSION citus_columnar; +SELECT COUNT(*)=0 FROM pg_dist_schema +WHERE schemaid IN ('columnar'::regnamespace, 'columnar_internal'::regnamespace); + +RESET citus.enable_schema_based_sharding; + DROP EXTENSION citus; CREATE EXTENSION citus; diff --git a/src/test/regress/sql/multi_fix_partition_shard_index_names.sql b/src/test/regress/sql/multi_fix_partition_shard_index_names.sql index 5f87708a2..d0f789cd9 100644 --- a/src/test/regress/sql/multi_fix_partition_shard_index_names.sql +++ b/src/test/regress/sql/multi_fix_partition_shard_index_names.sql @@ -278,10 +278,6 @@ DROP TABLE dist_partitioned_table; SET citus.next_shard_id TO 910040; -- test with citus local table -SET client_min_messages TO WARNING; -SELECT 1 FROM citus_add_node('localhost', :master_port, groupid=>0); -RESET client_min_messages; - CREATE TABLE date_partitioned_citus_local_table( measureid integer, eventdate date, @@ -345,4 +341,3 @@ ALTER TABLE parent_table DROP CONSTRAINT unique_cst CASCADE; SET client_min_messages TO WARNING; DROP SCHEMA fix_idx_names CASCADE; -SELECT citus_remove_node('localhost', :master_port); diff --git a/src/test/regress/sql/multi_foreign_key.sql b/src/test/regress/sql/multi_foreign_key.sql index 041202dff..328df1fb0 100644 --- a/src/test/regress/sql/multi_foreign_key.sql +++ b/src/test/regress/sql/multi_foreign_key.sql @@ -709,11 +709,6 @@ CREATE TABLE set_on_default_test_referencing( ON UPDATE SET DEFAULT ); --- from distributed / reference to reference, fkey exists before calling the UDFs -SELECT create_distributed_table('set_on_default_test_referencing', 'col_1'); -SELECT create_reference_table('set_on_default_test_referencing'); - -DROP TABLE set_on_default_test_referencing; CREATE TABLE set_on_default_test_referencing( col_1 serial, col_2 int, col_3 int, col_4 int ); diff --git a/src/test/regress/sql/multi_insert_select.sql b/src/test/regress/sql/multi_insert_select.sql index b14affddf..4d202041f 100644 --- a/src/test/regress/sql/multi_insert_select.sql +++ b/src/test/regress/sql/multi_insert_select.sql @@ -536,7 +536,7 @@ INSERT INTO agg_events fist_table_agg; ROLLBACK; --- We don't support CTEs that are referenced in the target list +-- We do support CTEs that are referenced in the target list INSERT INTO agg_events WITH sub_cte AS (SELECT 1) SELECT @@ -544,7 +544,7 @@ INSERT INTO agg_events FROM raw_events_first; --- We support set operations via the coordinator +-- We support set operations BEGIN; INSERT INTO @@ -2341,5 +2341,121 @@ join dist_table_2 t2 using (dist_col) limit 1 returning text_col_1; +CREATE TABLE dist_table_3( +dist_col bigint, +int_col integer +); + +SELECT create_distributed_table('dist_table_3', 'dist_col'); + +-- dist_table_2 and dist_table_3 are non-colocated source tables. Repartitioning is also not possible due to +-- different types for distribution columns. Citus would not be able to handle this complex insert select. +INSERT INTO dist_table_1 SELECT dist_table_2.dist_col FROM dist_table_2 JOIN dist_table_3 USING(dist_col); + +CREATE TABLE dist_table_4( +dist_col integer, +int_col integer +); +SELECT create_distributed_table('dist_table_4', 'dist_col'); + +-- Even if target table distribution column is colocated with dist_table_2's distributed column, source tables dist_table_2 and dist_table_4 +-- are non-colocated. Hence, SELECT part of the query should be pulled to coordinator. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_1 SELECT dist_table_2.dist_col FROM dist_table_2 JOIN dist_table_4 ON dist_table_2.dist_col = dist_table_4.int_col; +$$); + +-- For INSERT SELECT, when a lateral query references an outer query, push-down is possible even if limit clause exists in the lateral query. +-- It is because subquery with limit does not need to be merged at coordinator as it is a lateral query. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_1 SELECT d1.dist_col FROM dist_table_1 d1 LEFT JOIN LATERAL (SELECT * FROM dist_table_2 d2 WHERE d1.dist_col = d2.dist_col LIMIT 3) dummy USING(dist_col); +$$); + +-- For INSERT SELECT, when push-down is NOT possible when limit clause exists in a subquery at SELECT part of INSERT SELECT. +-- It is because the subquery with limit needs to be merged at coordinator. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_1 SELECT d1.dist_col FROM dist_table_1 d1 LEFT JOIN (SELECT * FROM dist_table_2 LIMIT 3) dummy USING(dist_col); +$$); + +CREATE TABLE dist_table_5(id int, id2 int); +SELECT create_distributed_table('dist_table_5','id'); +CREATE TABLE dist_table_6(id int, id2 int); +SELECT create_distributed_table('dist_table_6','id'); + +-- verify that insert select with union can be pushed down since UNION clause has FROM clause at top level query. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5(id) SELECT id FROM (SELECT id FROM dist_table_5 UNION SELECT id FROM dist_table_6) dummy; +$$); + +-- verify that insert select with sublink can be pushed down when tables are colocated. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT id, (SELECT id FROM dist_table_5 WHERE dist_table_5.id = dist_table_6.id) FROM dist_table_6; +$$); + +CREATE TABLE ref_table_1(id int); +SELECT create_reference_table('ref_table_1'); + +-- verify that insert select with sublink cannot be pushed down when from clause does not contain any distributed relation. +INSERT INTO dist_table_5 SELECT id, (SELECT id FROM dist_table_5 WHERE dist_table_5.id = ref_table_1.id) FROM ref_table_1; + +-- verify that insert select cannot be pushed down when we have recurring range table in from clause. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT id, (SELECT id FROM ref_table_1 WHERE id = 1) FROM ref_table_1; +$$); + +-- verify that insert select cannot be pushed down when we have reference table in outside of outer join. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT a.id FROM dist_table_5 a LEFT JOIN ref_table_1 b ON (true) RIGHT JOIN ref_table_1 c ON (true); +$$); + +-- verify that insert select cannot be pushed down when it has a recurring outer join in a subquery. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT id FROM ref_table_1 LEFT JOIN dist_table_5 USING(id); +$$); + +CREATE TABLE loc_table_1(id int); + +-- verify that insert select cannot be pushed down when it contains join between local and distributed tables. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT id FROM dist_table_5 JOIN loc_table_1 USING(id); +$$); + +CREATE VIEW view_1 AS + SELECT id FROM dist_table_6; + +CREATE MATERIALIZED VIEW view_2 AS + SELECT id FROM dist_table_6; + +-- verify that insert select cannot be pushed down when it contains view. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT * FROM view_1; +$$); + +-- verify that insert select cannot be pushed down when it contains materialized view. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 SELECT * FROM view_2; +$$); + +CREATE TABLE append_table(id integer, data text, int_data int); +SELECT create_distributed_table('append_table', 'id', 'append'); +SELECT master_create_empty_shard('append_table'); + +-- verify that insert select push down for append tables are not supported. +INSERT INTO append_table SELECT * FROM append_table; + +-- verify that CTEs at top level of INSERT SELECT, that can normally be inlined, would not be inlined by INSERT SELECT pushdown planner +-- and handled by pull to coordinator. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) WITH cte_1 AS (SELECT id FROM dist_table_5 WHERE id = 5) + INSERT INTO dist_table_5 + SELECT id FROM dist_table_5 JOIN cte_1 USING(id); +$$); + +-- verify that CTEs at top level of SELECT part, would be inlined by Postgres and pushed down by INSERT SELECT planner. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_5 + WITH cte_1 AS (SELECT id FROM dist_table_5 WHERE id = 5) + SELECT id FROM dist_table_5 JOIN cte_1 USING(id); +$$); + SET client_min_messages TO ERROR; DROP SCHEMA multi_insert_select CASCADE; diff --git a/src/test/regress/sql/multi_insert_select_non_pushable_queries.sql b/src/test/regress/sql/multi_insert_select_non_pushable_queries.sql index b4654144b..539024141 100644 --- a/src/test/regress/sql/multi_insert_select_non_pushable_queries.sql +++ b/src/test/regress/sql/multi_insert_select_non_pushable_queries.sql @@ -4,6 +4,9 @@ ------------------------------------ ------------------------------------ +CREATE SCHEMA multi_insert_select_non_pushable_queries; +SET search_path = multi_insert_select_non_pushable_queries,public; + -- not pushable since the JOIN is not an equi join INSERT INTO agg_results_third (user_id, value_1_agg) SELECT user_id, array_length(events_table, 1) @@ -716,3 +719,16 @@ FROM ( GROUP BY user_id ) AS shard_union ORDER BY user_lastseen DESC; + +CREATE TABLE dist_table_1(id int); +SELECT create_distributed_table('dist_table_1','id'); +CREATE TABLE dist_table_2(id int, id2 int); +SELECT create_distributed_table('dist_table_2','id2'); + +-- verify that insert select with union can be pulled to coordinator. We cannot push down the query +-- since UNION clause has no FROM clause at top level query. +SELECT coordinator_plan($$ + EXPLAIN (COSTS FALSE) INSERT INTO dist_table_1(id) SELECT id FROM dist_table_1 UNION SELECT id FROM dist_table_2; +$$); + +DROP SCHEMA multi_insert_select_non_pushable_queries CASCADE; diff --git a/src/test/regress/sql/multi_insert_select_window.sql b/src/test/regress/sql/multi_insert_select_window.sql index 10c527ad4..ebadc356f 100644 --- a/src/test/regress/sql/multi_insert_select_window.sql +++ b/src/test/regress/sql/multi_insert_select_window.sql @@ -751,4 +751,6 @@ FROM ( ) ) AS ftop; +TRUNCATE agg_results_window; + DROP VIEW view_with_window_func; diff --git a/src/test/regress/sql/multi_join_pruning.sql b/src/test/regress/sql/multi_join_pruning.sql index d655f7c52..1b8f22706 100644 --- a/src/test/regress/sql/multi_join_pruning.sql +++ b/src/test/regress/sql/multi_join_pruning.sql @@ -66,3 +66,8 @@ EXPLAIN (COSTS OFF) SELECT count(*) FROM varchar_partitioned_table table1, varchar_partitioned_table table2 WHERE table1.varchar_column = table2.varchar_column; + +SET client_min_messages TO WARNING; +DROP TABLE varchar_partitioned_table; +DROP TABLE array_partitioned_table; +DROP TABLE composite_partitioned_table; diff --git a/src/test/regress/sql/multi_level_recursive_queries.sql b/src/test/regress/sql/multi_level_recursive_queries.sql index 29db13b6e..a708dd3dc 100644 --- a/src/test/regress/sql/multi_level_recursive_queries.sql +++ b/src/test/regress/sql/multi_level_recursive_queries.sql @@ -170,5 +170,5 @@ SELECT avg(table_5.id) FROM ( ) AS table_5 INNER JOIN dist0 AS table_9 USING (id); -RESET client_min_messages; +SET client_min_messages TO WARNING; DROP SCHEMA multi_recursive CASCADE; diff --git a/src/test/regress/sql/multi_metadata_sync.sql b/src/test/regress/sql/multi_metadata_sync.sql index 0b9d46fe2..1b8043cdd 100644 --- a/src/test/regress/sql/multi_metadata_sync.sql +++ b/src/test/regress/sql/multi_metadata_sync.sql @@ -1,7 +1,7 @@ -- -- MULTI_METADATA_SYNC -- --- this test has different output for PG13/14 compared to PG15 +-- this test has different output for PG14 compared to PG15 -- In PG15, public schema is owned by pg_database_owner role -- Relevant PG commit: b073c3ccd06e4cb845e121387a43faa8c68a7b62 SHOW server_version \gset @@ -56,15 +56,24 @@ set citus.shard_count to 8; set citus.shard_replication_factor to 1; SELECT create_distributed_table('mx_test_table', 'col_1'); reset citus.shard_count; -reset citus.shard_replication_factor; -- Set the replication model of the test table to streaming replication so that it is -- considered as an MX table UPDATE pg_dist_partition SET repmodel='s' WHERE logicalrelid='mx_test_table'::regclass; +-- add a single shard table and verify the creation commands are included in the activate node snapshot +CREATE TABLE single_shard_tbl(a int); +SELECT create_distributed_table('single_shard_tbl', null); +INSERT INTO single_shard_tbl VALUES (1); + +reset citus.shard_replication_factor; + -- Show that the created MX table is and its sequences are included in the activate node snapshot SELECT unnest(activate_node_snapshot()) order by 1; +-- Drop single shard table +DROP TABLE single_shard_tbl; + -- Show that CREATE INDEX commands are included in the activate node snapshot CREATE INDEX mx_index ON mx_test_table(col_2); SELECT unnest(activate_node_snapshot()) order by 1; @@ -87,7 +96,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; -- Test start_metadata_sync_to_node and citus_activate_node UDFs --- Ensure that hasmetadata=false for all nodes +-- Ensure that hasmetadata=false for all nodes except for the coordinator node SELECT count(*) FROM pg_dist_node WHERE hasmetadata=true; -- Show that metadata can not be synced on secondary node @@ -187,6 +196,10 @@ SELECT 1 FROM citus_activate_node('localhost', :worker_1_port); CREATE TABLE mx_query_test (a int, b text, c int); SELECT create_distributed_table('mx_query_test', 'a'); +CREATE TABLE single_shard_tbl(a int); +SELECT create_distributed_table('single_shard_tbl', null); +INSERT INTO single_shard_tbl VALUES (1); + SELECT repmodel FROM pg_dist_partition WHERE logicalrelid='mx_query_test'::regclass; INSERT INTO mx_query_test VALUES (1, 'one', 1); @@ -200,11 +213,16 @@ SELECT * FROM mx_query_test ORDER BY a; INSERT INTO mx_query_test VALUES (6, 'six', 36); UPDATE mx_query_test SET c = 25 WHERE a = 5; +SELECT * FROM single_shard_tbl ORDER BY a; +INSERT INTO single_shard_tbl VALUES (2); + \c - - - :master_port SELECT * FROM mx_query_test ORDER BY a; +SELECT * FROM single_shard_tbl ORDER BY a; \c - - - :master_port DROP TABLE mx_query_test; +DROP TABLE single_shard_tbl; -- Check that stop_metadata_sync_to_node function sets hasmetadata of the node to false \c - - - :master_port @@ -753,7 +771,6 @@ SELECT create_reference_table('dist_table_2'); ALTER TABLE dist_table_1 ADD COLUMN b int; -SELECT master_add_node('localhost', :master_port, groupid => 0); SELECT citus_disable_node_and_wait('localhost', :worker_1_port); SELECT citus_disable_node_and_wait('localhost', :worker_2_port); SELECT master_remove_node('localhost', :worker_1_port); diff --git a/src/test/regress/sql/multi_mx_function_call_delegation.sql b/src/test/regress/sql/multi_mx_function_call_delegation.sql index e4eeaebc2..84b96648b 100644 --- a/src/test/regress/sql/multi_mx_function_call_delegation.sql +++ b/src/test/regress/sql/multi_mx_function_call_delegation.sql @@ -28,6 +28,10 @@ create table mx_call_dist_table_bigint(id bigint, val bigint); select create_distributed_table('mx_call_dist_table_bigint', 'id'); insert into mx_call_dist_table_bigint values (1,1),(1,2),(2,2),(3,3),(3,4); +create table mx_call_dist_table_single_shard(id int, val int); +select create_distributed_table('mx_call_dist_table_single_shard', null); +insert into mx_call_dist_table_single_shard values (2,7),(1,8),(2,8),(1,8),(2,8); + create table mx_call_dist_table_ref(id int, val int); select create_reference_table('mx_call_dist_table_ref'); insert into mx_call_dist_table_ref values (2,7),(1,8),(2,8),(1,8),(2,8); @@ -157,6 +161,10 @@ select mx_call_func(2, 0); select colocate_proc_with_table('mx_call_func', 'mx_call_dist_table_ref'::regclass, 1); select mx_call_func(2, 0); +-- We support colocating with single shard tables +select colocate_proc_with_table('mx_call_func', 'mx_call_dist_table_single_shard'::regclass, 1); +select mx_call_func(2, 0); + -- We don't currently support colocating with replicated tables select colocate_proc_with_table('mx_call_func', 'mx_call_dist_table_replica'::regclass, 1); select mx_call_func(2, 0); diff --git a/src/test/regress/sql/multi_partitioning.sql b/src/test/regress/sql/multi_partitioning.sql index 85a3ece66..39f7e8316 100644 --- a/src/test/regress/sql/multi_partitioning.sql +++ b/src/test/regress/sql/multi_partitioning.sql @@ -1786,7 +1786,6 @@ ROLLBACK; DROP TABLE pi_table; -- 6) test with citus local table -select 1 from citus_add_node('localhost', :master_port, groupid=>0); CREATE TABLE date_partitioned_citus_local_table( measureid integer, eventdate date, @@ -1840,6 +1839,40 @@ BEGIN; SELECT * FROM time_partitions WHERE parent_table = 'date_partitioned_citus_local_table'::regclass ORDER BY 3; ROLLBACK; set client_min_messages to notice; + +-- 7) test with bigint partition column +CREATE FUNCTION nanos_to_timestamptz(nanos bigint) RETURNS timestamptz LANGUAGE plpgsql AS +$$ +DECLARE + value timestamptz; +BEGIN + select to_timestamp(nanos * 1.0 / 1000000000) into value; + return value; +END; +$$; +CREATE CAST (bigint AS timestamptz) WITH FUNCTION nanos_to_timestamptz(bigint); + +CREATE FUNCTION timestamptz_to_nanos(ts timestamptz) RETURNS bigint LANGUAGE plpgsql AS +$$ +DECLARE + value bigint; +BEGIN + select extract(epoch from ts) * 1000000000 into value; + return value; +END; +$$; +CREATE CAST (timestamptz AS bigint) WITH FUNCTION timestamptz_to_nanos(timestamptz); + +CREATE TABLE bigint_partitioned_table (timestamp bigint, description text) partition by range (timestamp); + +BEGIN; + SELECT create_time_partitions('bigint_partitioned_table', INTERVAL '1 month', '2023-05-01', '2023-01-1'); + SELECT * FROM time_partitions WHERE parent_table = 'bigint_partitioned_table'::regclass ORDER BY 3; +ROLLBACK; + +DROP CAST (bigint AS timestamptz); +DROP CAST (timestamptz AS bigint); + -- c) test drop_old_time_partitions -- 1) test with date partitioned table CREATE TABLE date_partitioned_table_to_exp (event_date date, event int) partition by range (event_date); @@ -1938,8 +1971,6 @@ DROP TABLE date_partitioned_citus_local_table CASCADE; DROP TABLE date_partitioned_citus_local_table_2; set client_min_messages to notice; -SELECT citus_remove_node('localhost', :master_port); - -- d) invalid tables for helper UDFs CREATE TABLE multiple_partition_column_table( event_id bigserial, diff --git a/src/test/regress/sql/multi_remove_node_reference_table.sql b/src/test/regress/sql/multi_remove_node_reference_table.sql index fbc9f9524..cc8a67239 100644 --- a/src/test/regress/sql/multi_remove_node_reference_table.sql +++ b/src/test/regress/sql/multi_remove_node_reference_table.sql @@ -107,8 +107,13 @@ WHERE colocationid IN FROM pg_dist_partition WHERE logicalrelid = 'remove_node_reference_table'::regclass); +-- test that we cannot remove a node if it has the only placement for a shard +SELECT master_remove_node('localhost', :master_port); SELECT master_remove_node('localhost', :worker_1_port); +-- restore the coordinator +SELECT citus_set_coordinator_host('localhost'); + \c - - - :worker_1_port SELECT COUNT(*) FROM pg_dist_node WHERE nodeport = :worker_2_port; @@ -574,7 +579,6 @@ WHERE ORDER BY shardid ASC; \c - - - :master_port -SELECT 1 FROM citus_set_coordinator_host('localhost', :master_port); SELECT citus_disable_node('localhost', :worker_2_port); SELECT public.wait_until_metadata_sync(); @@ -584,8 +588,6 @@ SELECT COUNT(*) FROM pg_dist_node WHERE nodeport = :worker_2_port; -- never mark coordinator metadatasynced = false SELECT hasmetadata, metadatasynced FROM pg_dist_node WHERE nodeport = :master_port; -SELECT 1 FROM citus_remove_node('localhost', :master_port); - SELECT shardid, shardstate, shardlength, nodename, nodeport diff --git a/src/test/regress/sql/multi_repartition_join_planning.sql b/src/test/regress/sql/multi_repartition_join_planning.sql index 30cfc7156..c2f379d23 100644 --- a/src/test/regress/sql/multi_repartition_join_planning.sql +++ b/src/test/regress/sql/multi_repartition_join_planning.sql @@ -9,6 +9,7 @@ SET citus.next_shard_id TO 690000; SET citus.enable_unique_job_ids TO off; SET citus.enable_repartition_joins to ON; +SET citus.shard_replication_factor to 1; create schema repartition_join; DROP TABLE IF EXISTS repartition_join.order_line; diff --git a/src/test/regress/sql/multi_replicate_reference_table.sql b/src/test/regress/sql/multi_replicate_reference_table.sql index 39aaf44c6..4d5594126 100644 --- a/src/test/regress/sql/multi_replicate_reference_table.sql +++ b/src/test/regress/sql/multi_replicate_reference_table.sql @@ -238,14 +238,12 @@ SELECT create_reference_table('replicate_reference_table_cdtc'); SELECT citus_add_node('localhost', :worker_2_port); -- required for create_distributed_table_concurrently -SELECT 1 FROM citus_set_coordinator_host('localhost', :master_port); SET citus.shard_replication_factor TO 1; CREATE TABLE distributed_table_cdtc(column1 int primary key); SELECT create_distributed_table_concurrently('distributed_table_cdtc', 'column1'); RESET citus.shard_replication_factor; -SELECT citus_remove_node('localhost', :master_port); SELECT shardid, shardstate, shardlength, nodename, nodeport @@ -456,9 +454,9 @@ CREATE TABLE ref_table_1(id int primary key, v int); CREATE TABLE ref_table_2(id int primary key, v int references ref_table_1(id)); CREATE TABLE ref_table_3(id int primary key, v int references ref_table_2(id)); -SELECT create_reference_table('ref_table_1'), - create_reference_table('ref_table_2'), - create_reference_table('ref_table_3'); +SELECT create_reference_table('ref_table_1'); +SELECT create_reference_table('ref_table_2'); +SELECT create_reference_table('ref_table_3'); -- status before master_add_node SELECT diff --git a/src/test/regress/sql/multi_schema_support.sql b/src/test/regress/sql/multi_schema_support.sql index 944667c2a..7ca60162e 100644 --- a/src/test/regress/sql/multi_schema_support.sql +++ b/src/test/regress/sql/multi_schema_support.sql @@ -671,7 +671,7 @@ CREATE SCHEMA new_schema; SELECT objid::oid::regnamespace as "Distributed Schemas" FROM pg_catalog.pg_dist_object - WHERE objid::oid::regnamespace IN ('old_schema', 'new_schema'); + WHERE objid::oid::regnamespace IN ('old_schema', 'new_schema') ORDER BY objid::oid::regnamespace; \c - - - :worker_1_port SELECT table_schema AS "Shards' Schema" FROM information_schema.tables @@ -684,7 +684,7 @@ ALTER TABLE old_schema.table_set_schema SET SCHEMA new_schema; SELECT objid::oid::regnamespace as "Distributed Schemas" FROM pg_catalog.pg_dist_object - WHERE objid::oid::regnamespace IN ('old_schema', 'new_schema'); + WHERE objid::oid::regnamespace IN ('old_schema', 'new_schema') ORDER BY objid::oid::regnamespace; \c - - - :worker_1_port SELECT table_schema AS "Shards' Schema" FROM information_schema.tables @@ -742,7 +742,7 @@ CREATE SCHEMA new_schema; SELECT objid::oid::regnamespace as "Distributed Schemas" FROM pg_catalog.pg_dist_object - WHERE objid::oid::regnamespace IN ('old_schema', 'new_schema'); + WHERE objid::oid::regnamespace IN ('old_schema', 'new_schema') ORDER BY objid::oid::regnamespace; \c - - - :worker_1_port SELECT table_schema AS "Shards' Schema", COUNT(*) AS "Counts" FROM information_schema.tables @@ -756,7 +756,7 @@ ALTER TABLE table_set_schema SET SCHEMA new_schema; SELECT objid::oid::regnamespace as "Distributed Schemas" FROM pg_catalog.pg_dist_object - WHERE objid::oid::regnamespace IN ('old_schema', 'new_schema'); + WHERE objid::oid::regnamespace IN ('old_schema', 'new_schema') ORDER BY objid::oid::regnamespace; \c - - - :worker_1_port SELECT table_schema AS "Shards' Schema", COUNT(*) AS "Counts" FROM information_schema.tables @@ -802,6 +802,9 @@ ALTER TABLE IF EXISTS non_existent_table SET SCHEMA non_existent_schema; DROP SCHEMA existing_schema, another_existing_schema CASCADE; +-- test DROP SCHEMA with nonexisting schemas +DROP SCHEMA ax, bx, cx, dx, ex, fx, gx, jx; + -- test ALTER TABLE SET SCHEMA with interesting names CREATE SCHEMA "cItuS.T E E N'sSchema"; CREATE SCHEMA "citus-teen's scnd schm."; @@ -844,8 +847,6 @@ SET citus.next_shard_id TO 1197000; DROP OWNED BY "test-user" CASCADE; DROP USER "test-user"; -DROP FUNCTION run_command_on_coordinator_and_workers(p_sql text); - -- test run_command_on_* UDFs with schema CREATE SCHEMA run_test_schema; CREATE TABLE run_test_schema.test_table(id int); @@ -949,6 +950,25 @@ SELECT COUNT(*) FROM bar.test; ALTER SCHEMA "CiTuS.TeeN" RENAME TO "Citus'Teen123"; SELECT * FROM "Citus'Teen123"."TeeNTabLE.1!?!" ORDER BY id; +-- test alter owner propagation +CREATE ROLE test_non_super_user; +ALTER ROLE test_non_super_user NOSUPERUSER; + +SELECT pg_get_userbyid(nspowner) AS schema_owner + FROM pg_namespace + WHERE nspname = 'bar'; + +ALTER SCHEMA bar OWNER TO test_non_super_user; + +select result from run_command_on_workers ($$ + SELECT pg_get_userbyid(nspowner) AS schema_owner + FROM pg_namespace + WHERE nspname = 'bar' +$$); + +ALTER SCHEMA bar OWNER TO postgres; +DROP ROLE test_non_super_user; + -- test error INSERT INTO bar.test VALUES (3,3), (4,4), (5,5), (6,6), (7,7), (8,8), (9,9); @@ -968,6 +988,7 @@ SET client_min_messages TO WARNING; SELECT pg_identify_object_as_address(classid, objid, objsubid) FROM pg_catalog.pg_dist_object WHERE classid=2615 and objid IN (select oid from pg_namespace where nspname='run_test_schema'); +DROP TABLE public.nation_local; DROP SCHEMA run_test_schema, test_schema_support_join_1, test_schema_support_join_2, "Citus'Teen123", "CiTUS.TEEN2", bar, test_schema_support CASCADE; -- verify that the dropped schema is removed from worker's pg_dist_object SELECT pg_identify_object_as_address(classid, objid, objsubid) FROM pg_catalog.pg_dist_object diff --git a/src/test/regress/sql/multi_sequence_default.sql b/src/test/regress/sql/multi_sequence_default.sql index 3b1dd188b..b41aba577 100644 --- a/src/test/regress/sql/multi_sequence_default.sql +++ b/src/test/regress/sql/multi_sequence_default.sql @@ -12,7 +12,6 @@ SET search_path = sequence_default, public; -- test both distributed and citus local tables -SELECT 1 FROM citus_add_node('localhost', :master_port, groupId => 0); -- Cannot add a column involving DEFAULT nextval('..') because the table is not empty CREATE SEQUENCE seq_0; CREATE SEQUENCE seq_0_local_table; @@ -451,5 +450,4 @@ DROP TABLE test_seq_dist; DROP TABLE sequence_default.seq_test_7_par; SET client_min_messages TO error; -- suppress cascading objects dropping DROP SCHEMA sequence_default CASCADE; -SELECT master_remove_node('localhost', :master_port); SET search_path TO public; diff --git a/src/test/regress/sql/multi_table_ddl.sql b/src/test/regress/sql/multi_table_ddl.sql index fc6539ac9..ee826add0 100644 --- a/src/test/regress/sql/multi_table_ddl.sql +++ b/src/test/regress/sql/multi_table_ddl.sql @@ -58,6 +58,7 @@ DROP EXTENSION citus; CREATE EXTENSION citus; -- re-add the nodes to the cluster +SELECT 1 FROM citus_set_coordinator_host('localhost'); SELECT 1 FROM master_add_node('localhost', :worker_1_port); SELECT 1 FROM master_add_node('localhost', :worker_2_port); diff --git a/src/test/regress/sql/multi_tenant_isolation.sql b/src/test/regress/sql/multi_tenant_isolation.sql index 52ce044dc..c3e51b6cc 100644 --- a/src/test/regress/sql/multi_tenant_isolation.sql +++ b/src/test/regress/sql/multi_tenant_isolation.sql @@ -497,12 +497,19 @@ SELECT create_reference_table('test_reference_table_fkey'); CREATE TABLE test_colocated_table_1(id int PRIMARY KEY, value_1 int, FOREIGN KEY(id) REFERENCES test_colocated_table_1(id)); SELECT create_distributed_table('test_colocated_table_1', 'id', colocate_with => 'NONE'); -CREATE TABLE test_colocated_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_reference_table_fkey(id), FOREIGN KEY(id) REFERENCES test_colocated_table_1(id)); +CREATE TABLE test_colocated_table_2(id int PRIMARY KEY, value_1 int); SELECT create_distributed_table('test_colocated_table_2', 'id', colocate_with => 'test_colocated_table_1'); -CREATE TABLE test_colocated_table_3(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_reference_table_fkey(id), FOREIGN KEY(id) REFERENCES test_colocated_table_1(id), FOREIGN KEY(id) REFERENCES test_colocated_table_2(id)); +ALTER TABLE test_colocated_table_2 ADD FOREIGN KEY(value_1) REFERENCES test_reference_table_fkey(id); +ALTER TABLE test_colocated_table_2 ADD FOREIGN KEY(id) REFERENCES test_colocated_table_1(id); + +CREATE TABLE test_colocated_table_3(id int PRIMARY KEY, value_1 int); SELECT create_distributed_table('test_colocated_table_3', 'id', colocate_with => 'test_colocated_table_1'); +ALTER TABLE test_colocated_table_3 ADD FOREIGN KEY(value_1) REFERENCES test_reference_table_fkey(id); +ALTER TABLE test_colocated_table_3 ADD FOREIGN KEY(id) REFERENCES test_colocated_table_1(id); +ALTER TABLE test_colocated_table_3 ADD FOREIGN KEY(id) REFERENCES test_colocated_table_2(id); + INSERT INTO test_reference_table_fkey SELECT i FROM generate_series (0, 100) i; INSERT INTO test_colocated_table_1 SELECT i, i FROM generate_series (0, 100) i; INSERT INTO test_colocated_table_2 SELECT i, i FROM generate_series (0, 100) i; diff --git a/src/test/regress/sql/multi_tenant_isolation_nonblocking.sql b/src/test/regress/sql/multi_tenant_isolation_nonblocking.sql index f74835108..1299c9282 100644 --- a/src/test/regress/sql/multi_tenant_isolation_nonblocking.sql +++ b/src/test/regress/sql/multi_tenant_isolation_nonblocking.sql @@ -607,3 +607,6 @@ TRUNCATE TABLE pg_catalog.pg_dist_colocation; ALTER SEQUENCE pg_catalog.pg_dist_colocationid_seq RESTART 100; ALTER SEQUENCE pg_catalog.pg_dist_placement_placementid_seq RESTART :last_placement_id; + +SELECT citus_set_coordinator_host('localhost'); + diff --git a/src/test/regress/sql/multi_transaction_recovery.sql b/src/test/regress/sql/multi_transaction_recovery.sql index 333807267..b1072fe6b 100644 --- a/src/test/regress/sql/multi_transaction_recovery.sql +++ b/src/test/regress/sql/multi_transaction_recovery.sql @@ -1,13 +1,6 @@ -- Tests for prepared transaction recovery SET citus.next_shard_id TO 1220000; --- reference tables can have placements on the coordinator. Add it so --- verify we recover transactions which do DML on coordinator placements --- properly. -SET client_min_messages TO ERROR; -SELECT 1 FROM master_add_node('localhost', :master_port, groupid => 0); -RESET client_min_messages; - -- enforce 1 connection per placement since -- the tests are prepared for that SET citus.force_max_query_parallelization TO ON; @@ -264,5 +257,3 @@ DROP TABLE test_recovery; DROP TABLE test_recovery_single; DROP TABLE test_2pcskip; DROP TABLE test_reference; - -SELECT 1 FROM master_remove_node('localhost', :master_port); diff --git a/src/test/regress/sql/multi_transactional_drop_shards.sql b/src/test/regress/sql/multi_transactional_drop_shards.sql index dd7ba43a5..3ab463b7f 100644 --- a/src/test/regress/sql/multi_transactional_drop_shards.sql +++ b/src/test/regress/sql/multi_transactional_drop_shards.sql @@ -379,13 +379,10 @@ ORDER BY \c - - - :master_port SET client_min_messages TO WARNING; --- try using the coordinator as a worker and then dropping the table -SELECT 1 FROM master_add_node('localhost', :master_port, groupid := 0); CREATE TABLE citus_local (id serial, k int); SELECT create_distributed_table('citus_local', 'id'); INSERT INTO citus_local (k) VALUES (2); DROP TABLE citus_local; -SELECT master_remove_node('localhost', :master_port); -- clean the workspace DROP TABLE transactional_drop_shards, transactional_drop_reference; diff --git a/src/test/regress/sql/multi_utility_statements.sql b/src/test/regress/sql/multi_utility_statements.sql index 36f1bf876..bec722aef 100644 --- a/src/test/regress/sql/multi_utility_statements.sql +++ b/src/test/regress/sql/multi_utility_statements.sql @@ -137,6 +137,30 @@ FETCH FORWARD 3 FROM holdCursor; CLOSE holdCursor; +-- Test DECLARE CURSOR .. WITH HOLD inside transaction block +BEGIN; +DECLARE holdCursor CURSOR WITH HOLD FOR + SELECT * FROM cursor_me WHERE x = 1 ORDER BY y; +FETCH 3 FROM holdCursor; +FETCH BACKWARD 3 FROM holdCursor; +FETCH FORWARD 3 FROM holdCursor; +COMMIT; + +FETCH 3 FROM holdCursor; +CLOSE holdCursor; + +-- Test DECLARE NO SCROLL CURSOR .. WITH HOLD inside transaction block +BEGIN; +DECLARE holdCursor NO SCROLL CURSOR WITH HOLD FOR + SELECT * FROM cursor_me WHERE x = 1 ORDER BY y; +FETCH 3 FROM holdCursor; +FETCH FORWARD 3 FROM holdCursor; +COMMIT; + +FETCH 3 FROM holdCursor; +FETCH BACKWARD 3 FROM holdCursor; +CLOSE holdCursor; + -- Test DECLARE CURSOR .. WITH HOLD with parameter CREATE OR REPLACE FUNCTION declares_cursor(p int) RETURNS void AS $$ diff --git a/src/test/regress/sql/pg12.sql b/src/test/regress/sql/pg12.sql index 5624a70eb..a86dbbb42 100644 --- a/src/test/regress/sql/pg12.sql +++ b/src/test/regress/sql/pg12.sql @@ -267,8 +267,6 @@ select count(*) from col_test where val = 'asdf'; -SELECT 1 FROM citus_add_node('localhost', :master_port, groupId => 0); - BEGIN; CREATE TABLE generated_stored_col_test (x int, y int generated always as (x+1) stored); SELECT citus_add_local_table_to_metadata('generated_stored_col_test'); @@ -374,8 +372,6 @@ BEGIN; SELECT * FROM generated_stored_ref; ROLLBACK; -SELECT citus_remove_node('localhost', :master_port); - CREATE TABLE superuser_columnar_table (a int) USING columnar; CREATE USER read_access; diff --git a/src/test/regress/sql/pg14.sql b/src/test/regress/sql/pg14.sql index 77f3e1cc5..afac00174 100644 --- a/src/test/regress/sql/pg14.sql +++ b/src/test/regress/sql/pg14.sql @@ -1,11 +1,3 @@ -SHOW server_version \gset -SELECT substring(:'server_version', '\d+')::int >= 14 AS server_version_ge_14 -\gset -\if :server_version_ge_14 -\else -\q -\endif - create schema pg14; set search_path to pg14; SET citus.shard_replication_factor TO 1; @@ -671,8 +663,6 @@ drop schema pg14 cascade; create schema pg14; set search_path to pg14; -select 1 from citus_add_node('localhost',:master_port,groupid=>0); - -- test adding foreign table to metadata with the guc -- will test truncating foreign tables later CREATE TABLE foreign_table_test (id integer NOT NULL, data text, a bigserial); @@ -786,4 +776,3 @@ set client_min_messages to error; drop extension postgres_fdw cascade; drop schema pg14 cascade; reset client_min_messages; -select 1 from citus_remove_node('localhost',:master_port); diff --git a/src/test/regress/sql/pg15.sql b/src/test/regress/sql/pg15.sql index ac8062c65..a8ac91901 100644 --- a/src/test/regress/sql/pg15.sql +++ b/src/test/regress/sql/pg15.sql @@ -179,11 +179,6 @@ CREATE TABLE tbl2 MERGE INTO tbl1 USING tbl2 ON (true) WHEN MATCHED THEN DELETE; --- add coordinator node as a worker -SET client_min_messages to ERROR; -SELECT 1 FROM master_add_node('localhost', :master_port, groupId => 0); -RESET client_min_messages; - -- one table is Citus local table, fails SELECT citus_add_local_table_to_metadata('tbl1'); @@ -198,13 +193,13 @@ SELECT citus_add_local_table_to_metadata('tbl2'); MERGE INTO tbl1 USING tbl2 ON (true) WHEN MATCHED THEN DELETE; --- one table is reference, the other local, not supported +-- source table is reference, the target is local, supported SELECT create_reference_table('tbl2'); MERGE INTO tbl1 USING tbl2 ON (true) WHEN MATCHED THEN DELETE; --- now, both are reference, still not supported +-- now, both are reference, not supported SELECT create_reference_table('tbl1'); MERGE INTO tbl1 USING tbl2 ON (true) @@ -254,15 +249,13 @@ SET client_min_messages to ERROR; DROP TABLE FKTABLE_local, PKTABLE_local; RESET client_min_messages; -SELECT 1 FROM citus_remove_node('localhost', :master_port); - SELECT create_distributed_table('tbl1', 'x'); SELECT create_distributed_table('tbl2', 'x'); MERGE INTO tbl1 USING tbl2 ON (true) WHEN MATCHED THEN DELETE; --- also, not inside subqueries & ctes +-- also, inside subqueries & ctes WITH targq AS ( SELECT * FROM tbl2 ) @@ -810,6 +803,7 @@ CREATE TABLE set_on_default_test_referenced( ); SELECT create_reference_table('set_on_default_test_referenced'); +-- should error since col_3 defaults to a sequence CREATE TABLE set_on_default_test_referencing( col_1 int, col_2 int, col_3 serial, col_4 int, FOREIGN KEY(col_1, col_3) @@ -818,10 +812,6 @@ CREATE TABLE set_on_default_test_referencing( ON UPDATE SET DEFAULT ); --- should error since col_3 defaults to a sequence -SELECT create_reference_table('set_on_default_test_referencing'); - -DROP TABLE set_on_default_test_referencing; CREATE TABLE set_on_default_test_referencing( col_1 int, col_2 int, col_3 serial, col_4 int, FOREIGN KEY(col_1, col_3) @@ -921,7 +911,6 @@ SELECT * FROM foreign_table WHERE c1::text LIKE 'foo' LIMIT 1; -- ERROR; cast no RESET citus.use_citus_managed_tables; SELECT undistribute_table('foreign_table'); SELECT undistribute_table('foreign_table_test'); -SELECT 1 FROM citus_remove_node('localhost', :master_port); DROP SERVER foreign_server CASCADE; -- PG15 now supports specifying oid on CREATE DATABASE diff --git a/src/test/regress/sql/pgmerge.sql b/src/test/regress/sql/pgmerge.sql index 9b828f27e..ab1f4a40d 100644 --- a/src/test/regress/sql/pgmerge.sql +++ b/src/test/regress/sql/pgmerge.sql @@ -19,7 +19,9 @@ SET citus.use_citus_managed_tables to true; SET citus.next_shard_id TO 4001000; +SET client_min_messages = warning; SELECT 1 FROM master_add_node('localhost', :master_port, groupid => 0); +RESET client_min_messages; CREATE USER regress_merge_privs; CREATE USER regress_merge_no_privs; @@ -1374,4 +1376,3 @@ REVOKE ALL ON SCHEMA pgmerge_schema FROM regress_merge_no_privs; DROP SCHEMA pgmerge_schema CASCADE; DROP USER regress_merge_privs; DROP USER regress_merge_no_privs; -SELECT 1 FROM master_remove_node('localhost', :master_port); diff --git a/src/test/regress/sql/prepared_statements_4.sql b/src/test/regress/sql/prepared_statements_4.sql index a8f124568..bdbc32b08 100644 --- a/src/test/regress/sql/prepared_statements_4.sql +++ b/src/test/regress/sql/prepared_statements_4.sql @@ -43,3 +43,85 @@ EXECUTE foo; SELECT count(distinct ingest_time) FROM http_request WHERE site_id = 1; +-- Standard planner converted text and varchar casts to cstring in some cases +-- We make sure we convert it back to text when parsing the expression +INSERT INTO test VALUES ('2022-02-02', 0); +INSERT INTO test VALUES ('2022-01-01', 1); +INSERT INTO test VALUES ('2021-01-01', 2); + +-- try different planners +PREPARE test_statement_regular(text) AS +SELECT user_id FROM test WHERE t >= $1::timestamp ORDER BY user_id; + +EXECUTE test_statement_regular('2022-01-01'); +EXECUTE test_statement_regular('2022-01-01'); +EXECUTE test_statement_regular('2022-01-01'); +EXECUTE test_statement_regular('2022-01-01'); +EXECUTE test_statement_regular('2022-01-01'); +EXECUTE test_statement_regular('2022-01-01'); +EXECUTE test_statement_regular('2022-01-01'); +EXECUTE test_statement_regular('2022-01-01'); + +PREPARE test_statement_router(int, text) AS +SELECT user_id FROM test WHERE user_id = $1 AND t >= $2::timestamp ORDER BY user_id; + +EXECUTE test_statement_router(1, '2022-01-01'); +EXECUTE test_statement_router(1, '2022-01-01'); +EXECUTE test_statement_router(1, '2022-01-01'); +EXECUTE test_statement_router(1, '2022-01-01'); +EXECUTE test_statement_router(1, '2022-01-01'); +EXECUTE test_statement_router(1, '2022-01-01'); +EXECUTE test_statement_router(1, '2022-01-01'); +EXECUTE test_statement_router(1, '2022-01-01'); + +PREPARE test_statement_repartition(int, text) AS +SELECT count(*) FROM test t1 JOIN test t2 USING (t) WHERE t1.user_id = $1 AND t >= $2::timestamp; + +EXECUTE test_statement_repartition(1, '2022-01-01'); +EXECUTE test_statement_repartition(1, '2022-01-01'); +EXECUTE test_statement_repartition(1, '2022-01-01'); +EXECUTE test_statement_repartition(1, '2022-01-01'); +EXECUTE test_statement_repartition(1, '2022-01-01'); +EXECUTE test_statement_repartition(1, '2022-01-01'); +EXECUTE test_statement_repartition(1, '2022-01-01'); +EXECUTE test_statement_repartition(1, '2022-01-01'); + +PREPARE test_statement_cte(text, text) AS +WITH cte_1 AS MATERIALIZED (SELECT user_id, t FROM test WHERE t >= $1::timestamp ORDER BY user_id) +SELECT user_id FROM cte_1 WHERE t <= $2::timestamp; + +EXECUTE test_statement_cte('2022-01-01', '2022-01-01'); +EXECUTE test_statement_cte('2022-01-01', '2022-01-01'); +EXECUTE test_statement_cte('2022-01-01', '2022-01-01'); +EXECUTE test_statement_cte('2022-01-01', '2022-01-01'); +EXECUTE test_statement_cte('2022-01-01', '2022-01-01'); +EXECUTE test_statement_cte('2022-01-01', '2022-01-01'); +EXECUTE test_statement_cte('2022-01-01', '2022-01-01'); +EXECUTE test_statement_cte('2022-01-01', '2022-01-01'); + +PREPARE test_statement_insert(int, text) AS +INSERT INTO test VALUES ($2::timestamp, $1); + +EXECUTE test_statement_insert(3, '2022-03-03'); +EXECUTE test_statement_insert(4, '2022-04-04'); +EXECUTE test_statement_insert(5, '2022-05-05'); +EXECUTE test_statement_insert(6, '2022-06-06'); +EXECUTE test_statement_insert(7, '2022-07-07'); +EXECUTE test_statement_insert(8, '2022-08-08'); +EXECUTE test_statement_insert(9, '2022-09-09'); +EXECUTE test_statement_insert(10, '2022-10-10'); + +SELECT count(*) FROM test; +EXECUTE test_statement_regular('2022-01-01'); + +PREPARE test_statement_null(text) AS +SELECT user_id , $1::timestamp FROM test ORDER BY user_id LIMIT 2; + +EXECUTE test_statement_null(NULL); +EXECUTE test_statement_null(NULL); +EXECUTE test_statement_null(NULL); +EXECUTE test_statement_null(NULL); +EXECUTE test_statement_null(NULL); +EXECUTE test_statement_null(NULL); +EXECUTE test_statement_null(NULL); +EXECUTE test_statement_null(NULL); diff --git a/src/test/regress/sql/prepared_statements_create_load.sql b/src/test/regress/sql/prepared_statements_create_load.sql index 6afb1a12f..025ff1f6a 100644 --- a/src/test/regress/sql/prepared_statements_create_load.sql +++ b/src/test/regress/sql/prepared_statements_create_load.sql @@ -73,3 +73,12 @@ CREATE TABLE http_request ( ); SELECT create_distributed_table('http_request', 'site_id'); + +-- Standard planner converted text and varchar casts to cstring in some cases +-- We make sure we convert it back to text when parsing the expression +-- https://github.com/citusdata/citus/issues/6061 +-- https://github.com/citusdata/citus/issues/5646 +-- https://github.com/citusdata/citus/issues/5033 + +CREATE TABLE test(t timestamp, user_id int); +SELECT create_distributed_table('test', 'user_id'); diff --git a/src/test/regress/sql/propagate_foreign_servers.sql b/src/test/regress/sql/propagate_foreign_servers.sql index 32cba12ef..a9f93a702 100644 --- a/src/test/regress/sql/propagate_foreign_servers.sql +++ b/src/test/regress/sql/propagate_foreign_servers.sql @@ -29,8 +29,6 @@ CREATE FOREIGN TABLE foreign_table ( SERVER foreign_server_dependent_schema OPTIONS (schema_name 'test_dependent_schema', table_name 'foreign_table_test'); -SELECT 1 FROM citus_add_node('localhost', :master_port, groupId=>0); - -- verify that the aggregate is propagated to the new node SELECT run_command_on_workers($$select aggfnoid from pg_aggregate where aggfnoid::text like '%propagate_foreign_server.array_agg%';$$); diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql index 3fd6128b8..8bd2ea923 100644 --- a/src/test/regress/sql/publication.sql +++ b/src/test/regress/sql/publication.sql @@ -3,9 +3,6 @@ CREATE SCHEMA "publication-1"; SET search_path TO publication; SET citus.shard_replication_factor TO 1; --- for citus_add_local_table_to_metadata / create_distributed_table_concurrently -SELECT citus_set_coordinator_host('localhost', :master_port); - CREATE OR REPLACE FUNCTION activate_node_snapshot() RETURNS text[] LANGUAGE C STRICT @@ -87,8 +84,13 @@ SELECT DISTINCT c FROM ( SELECT array_agg(c) FROM (SELECT c FROM unnest(activate_node_snapshot()) c WHERE c LIKE '%CREATE PUBLICATION%' AND c LIKE '%pubtables%' ORDER BY 1) s$$) ORDER BY c) s; --- distribute a table, creating a mixed publication +-- distribute a table and create a tenant schema, creating a mixed publication SELECT create_distributed_table('test','x', colocate_with := 'none'); +SET citus.enable_schema_based_sharding TO ON; +CREATE SCHEMA citus_schema_1; +CREATE TABLE citus_schema_1.test (x int primary key, y int, "column-1" int, doc xml); +SET citus.enable_schema_based_sharding TO OFF; +ALTER PUBLICATION pubtables_orig ADD TABLE citus_schema_1.test; -- some generic operations ALTER PUBLICATION pubtables_orig RENAME TO pubtables; @@ -100,7 +102,12 @@ ALTER PUBLICATION pubtables ADD TABLE notexist; -- operations with a distributed table ALTER PUBLICATION pubtables DROP TABLE test; ALTER PUBLICATION pubtables ADD TABLE test; -ALTER PUBLICATION pubtables SET TABLE test, "test-pubs", "publication-1"."test-pubs"; +ALTER PUBLICATION pubtables SET TABLE test, "test-pubs", "publication-1"."test-pubs", citus_schema_1.test; + +-- operations with a tenant schema table +ALTER PUBLICATION pubtables DROP TABLE citus_schema_1.test; +ALTER PUBLICATION pubtables ADD TABLE citus_schema_1.test; +ALTER PUBLICATION pubtables SET TABLE test, "test-pubs", "publication-1"."test-pubs", citus_schema_1.test; -- operations with a local table in a mixed publication ALTER PUBLICATION pubtables DROP TABLE "test-pubs"; @@ -121,7 +128,7 @@ ALTER PUBLICATION pubtables ADD TABLE "test-pubs"; -- create a publication with distributed and local tables DROP PUBLICATION pubtables; -CREATE PUBLICATION pubtables FOR TABLE test, "test-pubs", "publication-1"."test-pubs"; +CREATE PUBLICATION pubtables FOR TABLE test, "test-pubs", "publication-1"."test-pubs", citus_schema_1.test; -- change distributed tables SELECT alter_distributed_table('test', shard_count := 5, cascade_to_colocated := true); @@ -187,13 +194,12 @@ SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 SET client_min_messages TO ERROR; DROP SCHEMA publication CASCADE; DROP SCHEMA "publication-1" CASCADE; - -SELECT citus_remove_node('localhost', :master_port); +DROP SCHEMA citus_schema_1 CASCADE; \q \endif -- recreate a mixed publication -CREATE PUBLICATION pubtables FOR TABLE test, "publication-1"."test-pubs"; +CREATE PUBLICATION pubtables FOR TABLE test, "publication-1"."test-pubs", citus_schema_1.test; -- operations on an existing distributed table ALTER PUBLICATION pubtables DROP TABLE test; @@ -202,6 +208,19 @@ ALTER PUBLICATION pubtables SET TABLE test WHERE (doc IS DOCUMENT); ALTER PUBLICATION pubtables SET TABLE test WHERE (xmlexists('//foo[text() = ''bar'']' PASSING BY VALUE doc)); ALTER PUBLICATION pubtables SET TABLE test WHERE (CASE x WHEN 5 THEN true ELSE false END); +SELECT DISTINCT c FROM ( + SELECT unnest(result::text[]) c + FROM run_command_on_workers($$ + SELECT array_agg(c) FROM (SELECT c FROM unnest(activate_node_snapshot()) c WHERE c LIKE '%CREATE PUBLICATION%' AND c LIKE '%pubtables%' ORDER BY 1) s$$) + ORDER BY c) s; + +-- operations on an existing tenant schema table +ALTER PUBLICATION pubtables ADD TABLE citus_schema_1.test (y); +ALTER PUBLICATION pubtables DROP TABLE citus_schema_1.test; +ALTER PUBLICATION pubtables SET TABLE citus_schema_1.test WHERE (doc IS DOCUMENT); +ALTER PUBLICATION pubtables SET TABLE citus_schema_1.test WHERE (xmlexists('//foo[text() = ''bar'']' PASSING BY VALUE doc)); +ALTER PUBLICATION pubtables SET TABLE citus_schema_1.test WHERE (CASE x WHEN 5 THEN true ELSE false END); + SELECT DISTINCT c FROM ( SELECT unnest(result::text[]) c FROM run_command_on_workers($$ @@ -265,5 +284,4 @@ DROP PUBLICATION pubpartitioned; SET client_min_messages TO ERROR; DROP SCHEMA publication CASCADE; DROP SCHEMA "publication-1" CASCADE; - -SELECT citus_remove_node('localhost', :master_port); +DROP SCHEMA citus_schema_1 CASCADE; diff --git a/src/test/regress/sql/query_single_shard_table.sql b/src/test/regress/sql/query_single_shard_table.sql new file mode 100644 index 000000000..c77d5b1dd --- /dev/null +++ b/src/test/regress/sql/query_single_shard_table.sql @@ -0,0 +1,1702 @@ +CREATE SCHEMA query_single_shard_table; +SET search_path TO query_single_shard_table; + +SET citus.next_shard_id TO 1620000; +SET citus.shard_count TO 32; +SET citus.shard_replication_factor TO 1; + +SET client_min_messages TO NOTICE; + +CREATE TABLE nullkey_c1_t1(a int, b int); +CREATE TABLE nullkey_c1_t2(a int, b int); +SELECT create_distributed_table('nullkey_c1_t1', null, colocate_with=>'none'); +SELECT create_distributed_table('nullkey_c1_t2', null, colocate_with=>'nullkey_c1_t1'); +INSERT INTO nullkey_c1_t1 SELECT i, i FROM generate_series(1, 8) i; +INSERT INTO nullkey_c1_t2 SELECT i, i FROM generate_series(2, 7) i; + +CREATE TABLE nullkey_c2_t1(a int, b int); +CREATE TABLE nullkey_c2_t2(a int, b int); +SELECT create_distributed_table('nullkey_c2_t1', null, colocate_with=>'none'); +SELECT create_distributed_table('nullkey_c2_t2', null, colocate_with=>'nullkey_c2_t1', distribution_type=>null); +INSERT INTO nullkey_c2_t1 SELECT i, i FROM generate_series(2, 7) i; +INSERT INTO nullkey_c2_t2 SELECT i, i FROM generate_series(1, 8) i; + +CREATE TABLE nullkey_c3_t1(a int, b int); +SELECT create_distributed_table('nullkey_c3_t1', null, colocate_with=>'none'); +INSERT INTO nullkey_c3_t1 SELECT i, i FROM generate_series(1, 8) i; + +RESET citus.shard_replication_factor; + +CREATE TABLE reference_table(a int, b int); +SELECT create_reference_table('reference_table'); +INSERT INTO reference_table SELECT i, i FROM generate_series(0, 5) i; + +CREATE TABLE distributed_table(a int, b int); +SELECT create_distributed_table('distributed_table', 'a'); +INSERT INTO distributed_table SELECT i, i FROM generate_series(3, 8) i; + +CREATE TABLE citus_local_table(a int, b int); +SELECT citus_add_local_table_to_metadata('citus_local_table'); +INSERT INTO citus_local_table SELECT i, i FROM generate_series(0, 10) i; + +CREATE TABLE postgres_local_table(a int, b int); +INSERT INTO postgres_local_table SELECT i, i FROM generate_series(5, 10) i; + +CREATE TABLE articles_hash ( + id bigint NOT NULL, + author_id bigint NOT NULL, + title varchar(20) NOT NULL, + word_count integer +); + +INSERT INTO articles_hash VALUES ( 4, 4, 'altdorfer', 14551),( 5, 5, 'aruru', 11389), + (13, 3, 'aseyev', 2255),(15, 5, 'adversa', 3164), + (18, 8, 'assembly', 911),(19, 9, 'aubergiste', 4981), + (28, 8, 'aerophyte', 5454),(29, 9, 'amateur', 9524), + (42, 2, 'ausable', 15885),(43, 3, 'affixal', 12723), + (49, 9, 'anyone', 2681),(50, 10, 'anjanette', 19519); + +SET citus.shard_replication_factor TO 1; +SELECT create_distributed_table('articles_hash', null, colocate_with=>'none'); + +CREATE TABLE raw_events_first (user_id int, time timestamp, value_1 int, value_2 int, value_3 float, value_4 bigint, UNIQUE(user_id, value_1)); +SELECT create_distributed_table('raw_events_first', null, colocate_with=>'none', distribution_type=>null); + +CREATE TABLE raw_events_second (user_id int, time timestamp, value_1 int, value_2 int, value_3 float, value_4 bigint, UNIQUE(user_id, value_1)); +SELECT create_distributed_table('raw_events_second', null, colocate_with=>'raw_events_first', distribution_type=>null); + +CREATE TABLE agg_events (user_id int, value_1_agg int, value_2_agg int, value_3_agg float, value_4_agg bigint, agg_time timestamp, UNIQUE(user_id, value_1_agg)); +SELECT create_distributed_table('agg_events', null, colocate_with=>'raw_events_first', distribution_type=>null); + +CREATE TABLE users_ref_table (user_id int); +SELECT create_reference_table('users_ref_table'); + +INSERT INTO raw_events_first VALUES (1, '1970-01-01', 10, 100, 1000.1, 10000), (3, '1971-01-01', 30, 300, 3000.1, 30000), + (5, '1972-01-01', 50, 500, 5000.1, 50000), (2, '1973-01-01', 20, 200, 2000.1, 20000), + (4, '1974-01-01', 40, 400, 4000.1, 40000), (6, '1975-01-01', 60, 600, 6000.1, 60000); + +CREATE TABLE modify_fast_path(key int, value_1 int, value_2 text); +SELECT create_distributed_table('modify_fast_path', null); + +CREATE TABLE modify_fast_path_reference(key int, value_1 int, value_2 text); +SELECT create_reference_table('modify_fast_path_reference'); + +CREATE TABLE bigserial_test (x int, y int, z bigserial); +SELECT create_distributed_table('bigserial_test', null); + +RESET citus.shard_replication_factor; + +CREATE TABLE append_table (text_col text, a int); +SELECT create_distributed_table('append_table', 'a', 'append'); +SELECT master_create_empty_shard('append_table') AS shardid1 \gset +SELECT master_create_empty_shard('append_table') AS shardid2 \gset +SELECT master_create_empty_shard('append_table') AS shardid3 \gset + +COPY append_table (text_col, a) FROM STDIN WITH (format 'csv', append_to_shard :shardid1); +abc,234 +bcd,123 +bcd,234 +cde,345 +def,456 +efg,234 +\. + +COPY append_table (text_col, a) FROM STDIN WITH (format 'csv', append_to_shard :shardid2); +abc,123 +efg,123 +hij,123 +hij,234 +ijk,1 +jkl,0 +\. + +CREATE TABLE range_table(a int, b int); +SELECT create_distributed_table('range_table', 'a', 'range'); +CALL public.create_range_partitioned_shards('range_table', '{"0","25"}','{"24","49"}'); +INSERT INTO range_table VALUES (0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 50); + +\set users_table_data_file :abs_srcdir '/data/users_table.data' +\set events_table_data_file :abs_srcdir '/data/events_table.data' + +SET citus.shard_replication_factor TO 1; +CREATE TABLE users_table (user_id int, time timestamp, value_1 int, value_2 int, value_3 float, value_4 bigint); +SELECT create_distributed_table('users_table', null, colocate_with=>'none'); +\set client_side_copy_command '\\copy users_table FROM ' :'users_table_data_file' ' WITH CSV;' +:client_side_copy_command + +CREATE TABLE non_colocated_users_table (id int, value int); +SELECT create_distributed_table('non_colocated_users_table', null, colocate_with => 'none'); +INSERT INTO non_colocated_users_table (id, value) VALUES(1, 2),(2, 3),(3,4); + +CREATE TABLE colocated_events_table (user_id int, time timestamp, event_type int, value_2 int, value_3 float, value_4 bigint); +SELECT create_distributed_table('colocated_events_table', null, colocate_with=>'users_table'); +\set client_side_copy_command '\\copy colocated_events_table FROM ' :'events_table_data_file' ' WITH CSV;' +:client_side_copy_command + +CREATE TABLE non_colocated_events_table (user_id int, time timestamp, event_type int, value_2 int, value_3 float, value_4 bigint); +SELECT create_distributed_table('non_colocated_events_table', null, colocate_with=>'non_colocated_users_table'); +\set client_side_copy_command '\\copy non_colocated_events_table FROM ' :'events_table_data_file' ' WITH CSV;' +:client_side_copy_command + +CREATE TABLE users_table_local AS SELECT * FROM users_table; + +CREATE TABLE colocated_users_table (id int, value int); +SELECT create_distributed_table('colocated_users_table', null, colocate_with => 'users_table'); +INSERT INTO colocated_users_table (id, value) VALUES(1, 2),(2, 3),(3,4); + +CREATE TABLE users_reference_table (like users_table including all); +SELECT create_reference_table('users_reference_table'); + +CREATE TABLE events_reference_table (like colocated_events_table including all); +SELECT create_reference_table('events_reference_table'); + +CREATE FUNCTION func() RETURNS TABLE (id int, value int) AS $$ + SELECT 1, 2 +$$ LANGUAGE SQL; + +SET client_min_messages to DEBUG2; + +-- simple insert +INSERT INTO nullkey_c1_t1 VALUES (1,2), (2,2), (3,4); +INSERT INTO nullkey_c1_t2 VALUES (1,3), (3,4), (5,1), (6,2); + +INSERT INTO nullkey_c2_t1 VALUES (1,0), (2,5), (4,3), (5,2); +INSERT INTO nullkey_c2_t2 VALUES (2,4), (3,2), (5,2), (7,4); + +-- simple select +SELECT * FROM nullkey_c1_t1 ORDER BY 1,2; + +-- for update / share +SELECT * FROM modify_fast_path WHERE key = 1 FOR UPDATE; +SELECT * FROM modify_fast_path WHERE key = 1 FOR SHARE; +SELECT * FROM modify_fast_path FOR UPDATE; +SELECT * FROM modify_fast_path FOR SHARE; + +-- cartesian product with different table types + +-- with other table types +SELECT COUNT(*) FROM distributed_table d1, nullkey_c1_t1; +SELECT COUNT(*) FROM reference_table d1, nullkey_c1_t1; +SELECT COUNT(*) FROM citus_local_table d1, nullkey_c1_t1; +SELECT COUNT(*) FROM postgres_local_table d1, nullkey_c1_t1; + +-- with a colocated single-shard table +SELECT COUNT(*) FROM nullkey_c1_t1 d1, nullkey_c1_t2; + +-- with a non-colocated single-shard table +SELECT COUNT(*) FROM nullkey_c1_t1 d1, nullkey_c2_t1; + +-- First, show that nullkey_c1_t1 and nullkey_c3_t1 are not colocated. +SELECT + (SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'query_single_shard_table.nullkey_c1_t1'::regclass) != + (SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'query_single_shard_table.nullkey_c3_t1'::regclass); + +-- Now verify that we can join them via router planner because it doesn't care +-- about whether two tables are colocated or not but physical location of shards +-- when citus.enable_non_colocated_router_query_pushdown is set to on. + +SET citus.enable_non_colocated_router_query_pushdown TO ON; + +SELECT COUNT(*) FROM nullkey_c1_t1 JOIN nullkey_c3_t1 USING(a); + +SET citus.enable_non_colocated_router_query_pushdown TO OFF; +SET citus.enable_repartition_joins TO ON; +SET client_min_messages TO DEBUG1; + +SELECT COUNT(*) FROM nullkey_c1_t1 JOIN nullkey_c3_t1 USING(a); + +SET client_min_messages TO DEBUG2; +SET citus.enable_repartition_joins TO OFF; +RESET citus.enable_non_colocated_router_query_pushdown; + +-- colocated join between single-shard tables +SELECT COUNT(*) FROM nullkey_c1_t1 JOIN nullkey_c1_t2 USING(a); +SELECT COUNT(*) FROM nullkey_c1_t1 LEFT JOIN nullkey_c1_t2 USING(a); +SELECT COUNT(*) FROM nullkey_c1_t1 FULL JOIN nullkey_c1_t2 USING(a); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +LEFT JOIN LATERAL ( + SELECT * FROM nullkey_c1_t2 t2 WHERE t2.b > t1.a +) q USING(a); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +JOIN LATERAL ( + SELECT * FROM nullkey_c1_t2 t2 WHERE t2.b > t1.a +) q USING(a); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE EXISTS ( + SELECT * FROM nullkey_c1_t2 t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b IN ( + SELECT b+1 FROM nullkey_c1_t2 t2 WHERE t2.b = t1.a +); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b NOT IN ( + SELECT a FROM nullkey_c1_t2 t2 WHERE t2.b > t1.a +); + +-- non-colocated inner joins between single-shard tables + +SET client_min_messages to DEBUG1; +SET citus.enable_repartition_joins TO ON; + +SELECT * FROM nullkey_c1_t1 JOIN nullkey_c2_t1 USING(a) ORDER BY 1,2,3; + +SELECT * FROM (SELECT * FROM nullkey_c1_t1) nullkey_c1_t1 JOIN nullkey_c2_t1 USING(a) ORDER BY 1,2,3; +SELECT * FROM nullkey_c2_t1 JOIN (SELECT * FROM nullkey_c1_t1) nullkey_c1_t1 USING(a) ORDER BY 1,2,3; + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +JOIN LATERAL ( + SELECT * FROM nullkey_c2_t2 t2 WHERE t2.b > t1.a +) q USING(a); + +SET citus.enable_repartition_joins TO OFF; +SET client_min_messages to DEBUG2; + +-- non-colocated outer joins between single-shard tables +SELECT * FROM nullkey_c1_t1 LEFT JOIN nullkey_c2_t2 USING(a) ORDER BY 1,2,3 LIMIT 4; +SELECT * FROM nullkey_c1_t1 FULL JOIN nullkey_c2_t2 USING(a) ORDER BY 1,2,3 LIMIT 4; +SELECT * FROM nullkey_c1_t1 t1 +LEFT JOIN LATERAL ( + SELECT * FROM nullkey_c2_t2 t2 WHERE t2.b > t1.a +) q USING(a) ORDER BY 1,2,3 OFFSET 3 LIMIT 4; + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +LEFT JOIN LATERAL ( + SELECT * FROM nullkey_c2_t2 t2 WHERE t2.b > t1.a +) q USING(a); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE EXISTS ( + SELECT * FROM nullkey_c2_t2 t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b IN ( + SELECT b+1 FROM nullkey_c2_t2 t2 WHERE t2.b = t1.a +); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b NOT IN ( + SELECT a FROM nullkey_c2_t2 t2 WHERE t2.b > t1.a +); + +-- join with a reference table +SELECT COUNT(*) FROM nullkey_c1_t1, reference_table WHERE nullkey_c1_t1.a = reference_table.a; + +WITH cte_1 AS + (SELECT * FROM nullkey_c1_t1, reference_table WHERE nullkey_c1_t1.a = reference_table.a ORDER BY 1,2,3,4 FOR UPDATE) +SELECT COUNT(*) FROM cte_1; + +-- join with postgres / citus local tables +SELECT * FROM nullkey_c1_t1 JOIN postgres_local_table USING(a) ORDER BY 1,2,3; +SELECT * FROM nullkey_c1_t1 JOIN citus_local_table USING(a) ORDER BY 1,2,3; + +SET citus.local_table_join_policy TO 'prefer-distributed'; +SELECT * FROM nullkey_c1_t1 JOIN citus_local_table USING(a) ORDER BY 1,2,3; +RESET citus.local_table_join_policy; + +-- join with a distributed table + +SET citus.enable_repartition_joins TO ON; +SET client_min_messages TO DEBUG1; + +SELECT * FROM distributed_table d1 JOIN nullkey_c1_t1 USING(a) ORDER BY 1,2,3; + +SELECT * FROM (SELECT * FROM distributed_table) d1 JOIN nullkey_c1_t1 USING(a) ORDER BY 1,2,3; +SELECT * FROM nullkey_c1_t1 JOIN (SELECT * FROM distributed_table) d1 USING(a) ORDER BY 1,2,3; +SELECT * FROM distributed_table d1 JOIN (SELECT * FROM nullkey_c1_t1) nullkey_c1_t1 USING(a) ORDER BY 1,2,3; +SELECT * FROM (SELECT * FROM nullkey_c1_t1) nullkey_c1_t1 JOIN distributed_table d1 USING(a) ORDER BY 1,2,3; + +-- test joins with non-colocated distributed tables, by using subqueries +SELECT * FROM nullkey_c1_t1 t1 JOIN (SELECT * FROM distributed_table) t2 USING (a) JOIN (SELECT * FROM nullkey_c1_t2) t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +SELECT * FROM (SELECT * FROM nullkey_c1_t1) t1 JOIN nullkey_c2_t1 t2 USING (a) JOIN (SELECT * FROM nullkey_c1_t2) t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +SELECT * FROM distributed_table t1 JOIN (SELECT * FROM nullkey_c1_t1) t2 USING (a) JOIN (SELECT b as a FROM distributed_table) t3 USING (a) ORDER BY 1,2,3 LIMIT 1; +SELECT * FROM (SELECT * FROM nullkey_c2_t1) t1 JOIN nullkey_c1_t1 t2 USING (a) JOIN (SELECT * FROM nullkey_c2_t1) t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +SELECT * FROM nullkey_c1_t1 t1 JOIN (SELECT * FROM distributed_table) t2 USING (a) JOIN (SELECT * FROM distributed_table) t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +SELECT * FROM (SELECT * FROM nullkey_c1_t1) t1 JOIN nullkey_c2_t1 t2 USING (a) JOIN (SELECT * FROM nullkey_c2_t1) t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +SELECT * FROM distributed_table t1 JOIN (SELECT * FROM nullkey_c1_t1) t2 USING (a) JOIN (SELECT * FROM nullkey_c1_t1) t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +SELECT * FROM (SELECT * FROM nullkey_c2_t1) t1 JOIN nullkey_c1_t1 t2 USING (a) JOIN (SELECT * FROM nullkey_c1_t1) t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +SELECT * FROM nullkey_c1_t1 t1 JOIN (SELECT * FROM nullkey_c1_t1) t2 USING (a) JOIN distributed_table t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +SELECT * FROM nullkey_c1_t1 t1 JOIN nullkey_c1_t1 t2 USING (a) JOIN nullkey_c2_t1 t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +SELECT * FROM (SELECT * FROM distributed_table) t1 JOIN distributed_table t2 USING (a) JOIN (SELECT * FROM nullkey_c1_t1) t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; +SELECT * FROM (SELECT * FROM nullkey_c2_t1) t1 JOIN nullkey_c2_t1 t2 USING (a) JOIN (SELECT * FROM nullkey_c1_t1) t3 USING (a) ORDER BY 1,2,3,4 LIMIT 1; + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +JOIN LATERAL ( + SELECT * FROM distributed_table t2 WHERE t2.b > t1.a +) q USING(a); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +JOIN LATERAL ( + SELECT *, random() FROM distributed_table t2 WHERE t2.b > t1.a +) q USING(a); + +SELECT COUNT(*) FROM distributed_table t1 +JOIN LATERAL ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +) q USING(a); + +SET client_min_messages TO DEBUG2; +SET citus.enable_repartition_joins TO OFF; + +-- outer joins with different table types +SELECT COUNT(*) FROM nullkey_c1_t1 LEFT JOIN reference_table USING(a); +SELECT COUNT(*) FROM reference_table LEFT JOIN nullkey_c1_t1 USING(a); + +SELECT COUNT(*) FROM nullkey_c1_t1 LEFT JOIN citus_local_table USING(a); +SELECT COUNT(*) FROM citus_local_table LEFT JOIN nullkey_c1_t1 USING(a); + +SELECT COUNT(*) FROM nullkey_c1_t1 LEFT JOIN postgres_local_table USING(a); +SELECT COUNT(*) FROM postgres_local_table LEFT JOIN nullkey_c1_t1 USING(a); + +SELECT COUNT(*) FROM nullkey_c1_t1 FULL JOIN citus_local_table USING(a); +SELECT COUNT(*) FROM nullkey_c1_t1 FULL JOIN postgres_local_table USING(a); +SELECT COUNT(*) FROM nullkey_c1_t1 FULL JOIN reference_table USING(a); + +SET citus.enable_repartition_joins TO ON; +SET client_min_messages TO DEBUG1; + +SELECT COUNT(*) FROM nullkey_c1_t1 JOIN append_table USING(a); +SELECT COUNT(*) FROM nullkey_c1_t1 JOIN range_table USING(a); + +SET client_min_messages TO DEBUG2; +SET citus.enable_repartition_joins TO OFF; + +SET citus.enable_non_colocated_router_query_pushdown TO ON; + +SELECT COUNT(*) FROM nullkey_c1_t1 JOIN range_table USING(a) WHERE range_table.a = 20; + +SET citus.enable_non_colocated_router_query_pushdown TO OFF; +SET citus.enable_repartition_joins TO ON; +SET client_min_messages TO DEBUG1; + +SELECT COUNT(*) FROM nullkey_c1_t1 JOIN range_table USING(a) WHERE range_table.a = 20; + +SET client_min_messages TO DEBUG2; +SET citus.enable_repartition_joins TO OFF; +RESET citus.enable_non_colocated_router_query_pushdown; + +-- lateral / semi / anti joins with different table types + +-- with a reference table +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +LEFT JOIN LATERAL ( + SELECT * FROM reference_table t2 WHERE t2.b > t1.a +) q USING(a); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE EXISTS ( + SELECT * FROM reference_table t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE NOT EXISTS ( + SELECT * FROM reference_table t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b IN ( + SELECT b+1 FROM reference_table t2 WHERE t2.b = t1.a +); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b NOT IN ( + SELECT a FROM reference_table t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +JOIN LATERAL ( + SELECT * FROM reference_table t2 WHERE t2.b > t1.a +) q USING(a); + +SELECT COUNT(*) FROM reference_table t1 +LEFT JOIN LATERAL ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +) q USING(a); + +SELECT COUNT(*) FROM reference_table t1 +WHERE EXISTS ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM reference_table t1 +WHERE t1.b IN ( + SELECT b+1 FROM nullkey_c1_t1 t2 WHERE t2.b = t1.a +); + +SELECT COUNT(*) FROM reference_table t1 +WHERE t1.b NOT IN ( + SELECT a FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM reference_table t1 +JOIN LATERAL ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +) q USING(a); + +-- with a distributed table +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +LEFT JOIN LATERAL ( + SELECT * FROM distributed_table t2 WHERE t2.b > t1.a +) q USING(a); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE EXISTS ( + SELECT * FROM distributed_table t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE NOT EXISTS ( + SELECT * FROM distributed_table t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b IN ( + SELECT b+1 FROM distributed_table t2 WHERE t2.b = t1.a +); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b NOT IN ( + SELECT a FROM distributed_table t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM distributed_table t1 +LEFT JOIN LATERAL ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +) q USING(a); + +SELECT COUNT(*) FROM distributed_table t1 +WHERE EXISTS ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM distributed_table t1 +WHERE t1.b IN ( + SELECT b+1 FROM nullkey_c1_t1 t2 WHERE t2.b = t1.a +); + +SELECT COUNT(*) FROM distributed_table t1 +WHERE t1.b NOT IN ( + SELECT a FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +); + +-- with postgres / citus local tables +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +LEFT JOIN LATERAL ( + SELECT * FROM citus_local_table t2 WHERE t2.b > t1.a +) q USING(a); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE EXISTS ( + SELECT * FROM citus_local_table t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE NOT EXISTS ( + SELECT * FROM citus_local_table t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b IN ( + SELECT b+1 FROM citus_local_table t2 WHERE t2.b = t1.a +); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b NOT IN ( + SELECT a FROM citus_local_table t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +JOIN LATERAL ( + SELECT * FROM citus_local_table t2 WHERE t2.b > t1.a +) q USING(a); + +-- The following and a few other tests in this file unnecessarily go through +-- recursive planning. This is because we recursive plan distributed tables +-- when they are referred in the inner side of an outer join, if the outer +-- side is a recurring rel. In future, we can optimize that such that we +-- can skip recursively planning the single-shard table because such a join +-- wouldn't result in returning recurring tuples. +-- +-- And specifically for the tests that contains a sublink (as below), things +-- get even more interesting. We try to recursively plan the single-shard +-- table but we cannot do so due to the sublink. However, the final query +-- can go through router planner and hence is supported. +SELECT COUNT(*) FROM citus_local_table t1 +LEFT JOIN LATERAL ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +) q USING(a); + +SELECT COUNT(*) FROM postgres_local_table t1 +LEFT JOIN LATERAL ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +) q USING(a); + +SELECT COUNT(*) FROM citus_local_table t1 +WHERE EXISTS ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM citus_local_table t1 +WHERE t1.b IN ( + SELECT b+1 FROM nullkey_c1_t1 t2 WHERE t2.b = t1.a +); + +SELECT COUNT(*) FROM citus_local_table t1 +WHERE t1.b NOT IN ( + SELECT a FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM citus_local_table t1 +JOIN LATERAL ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +) q USING(a); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +LEFT JOIN LATERAL ( + SELECT * FROM postgres_local_table t2 WHERE t2.b > t1.a +) q USING(a); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE EXISTS ( + SELECT * FROM postgres_local_table t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE NOT EXISTS ( + SELECT * FROM postgres_local_table t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b IN ( + SELECT b+1 FROM postgres_local_table t2 WHERE t2.b = t1.a +); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +WHERE t1.b NOT IN ( + SELECT a FROM postgres_local_table t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM nullkey_c1_t1 t1 +JOIN LATERAL ( + SELECT * FROM postgres_local_table t2 WHERE t2.b > t1.a +) q USING(a); + +SELECT COUNT(*) FROM postgres_local_table t1 +WHERE EXISTS ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM postgres_local_table t1 +WHERE t1.b IN ( + SELECT b+1 FROM nullkey_c1_t1 t2 WHERE t2.b = t1.a +); + +SELECT COUNT(*) FROM postgres_local_table t1 +WHERE t1.b NOT IN ( + SELECT a FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +); + +SELECT COUNT(*) FROM postgres_local_table t1 +JOIN LATERAL ( + SELECT * FROM nullkey_c1_t1 t2 WHERE t2.b > t1.a +) q USING(a); + +-- insert .. select + +-- between two colocated single-shard tables + +-- The target list of "distributed statement"s that we send to workers +-- differ(*) in Postgres versions < 15. For this reason, we temporarily +-- disable debug messages here and run the EXPLAIN'ed version of the +-- command. +-- +-- (*): < SELECT a, b > vs < SELECT table_name.a, table_name.b > +SET client_min_messages TO WARNING; +EXPLAIN (ANALYZE TRUE, TIMING FALSE, COSTS FALSE, SUMMARY FALSE, VERBOSE FALSE) +INSERT INTO nullkey_c1_t1 SELECT * FROM nullkey_c1_t2; +SET client_min_messages TO DEBUG2; + +-- between two non-colocated single-shard tables +INSERT INTO nullkey_c1_t1 SELECT * FROM nullkey_c2_t1; + +-- between a single-shard table and a table of different type +SET client_min_messages TO WARNING; +EXPLAIN (ANALYZE TRUE, TIMING FALSE, COSTS FALSE, SUMMARY FALSE, VERBOSE FALSE) +INSERT INTO nullkey_c1_t1 SELECT * FROM reference_table; +SET client_min_messages TO DEBUG2; + +INSERT INTO nullkey_c1_t1 SELECT * FROM distributed_table; +INSERT INTO nullkey_c1_t1 SELECT * FROM citus_local_table; +INSERT INTO nullkey_c1_t1 SELECT * FROM postgres_local_table; + +INSERT INTO reference_table SELECT * FROM nullkey_c1_t1; +INSERT INTO distributed_table SELECT * FROM nullkey_c1_t1; +INSERT INTO citus_local_table SELECT * FROM nullkey_c1_t1; +INSERT INTO postgres_local_table SELECT * FROM nullkey_c1_t1; + +-- test subquery +SELECT count(*) FROM +( + SELECT * FROM (SELECT * FROM nullkey_c1_t2) as subquery_inner +) AS subquery_top; + +-- test cte inlining +WITH cte_nullkey_c1_t1 AS (SELECT * FROM nullkey_c1_t1), + cte_postgres_local_table AS (SELECT * FROM postgres_local_table), + cte_distributed_table AS (SELECT * FROM distributed_table) +SELECT COUNT(*) FROM cte_distributed_table, cte_nullkey_c1_t1, cte_postgres_local_table +WHERE cte_nullkey_c1_t1.a > 3 AND cte_distributed_table.a < 5; + +-- test recursive ctes +WITH level_0 AS ( + WITH level_1 AS ( + WITH RECURSIVE level_2_recursive(x) AS ( + VALUES (1) + UNION ALL + SELECT a + 1 FROM nullkey_c1_t1 JOIN level_2_recursive ON (a = x) WHERE a < 2 + ) + SELECT * FROM level_2_recursive RIGHT JOIN reference_table ON (level_2_recursive.x = reference_table.a) + ) + SELECT * FROM level_1 +) +SELECT COUNT(*) FROM level_0; + +WITH level_0 AS ( + WITH level_1 AS ( + WITH RECURSIVE level_2_recursive(x) AS ( + VALUES (1) + UNION ALL + SELECT a + 1 FROM nullkey_c1_t1 JOIN level_2_recursive ON (a = x) WHERE a < 100 + ) + SELECT * FROM level_2_recursive JOIN distributed_table ON (level_2_recursive.x = distributed_table.a) + ) + SELECT * FROM level_1 +) +SELECT COUNT(*) FROM level_0; + +-- grouping set +SELECT + id, substring(title, 2, 1) AS subtitle, count(*) + FROM articles_hash + WHERE author_id = 1 or author_id = 2 + GROUP BY GROUPING SETS ((id),(subtitle)) + ORDER BY id, subtitle; + +-- subquery in SELECT clause +SELECT a.title AS name, (SELECT a2.id FROM articles_hash a2 WHERE a.id = a2.id LIMIT 1) + AS special_price FROM articles_hash a +ORDER BY 1,2; + +-- test having clause +SELECT COUNT(*), b FROM nullkey_c1_t1 GROUP BY 2 +HAVING (SELECT COUNT(*) FROM nullkey_c1_t2) > 0 +ORDER BY 1,2; + +SELECT COUNT(*), b FROM nullkey_c1_t1 GROUP BY 2 +HAVING (SELECT COUNT(*) FROM nullkey_c2_t1) > 0 +ORDER BY 1,2; + +SELECT COUNT(*), b FROM nullkey_c1_t1 GROUP BY 2 +HAVING (SELECT COUNT(*) FROM distributed_table) > 0 +ORDER BY 1,2; + +SELECT COUNT(*), b FROM nullkey_c1_t1 t4 GROUP BY 2 +HAVING ( + SELECT COUNT(*) FROM nullkey_c1_t1 t1 JOIN (SELECT * FROM nullkey_c1_t2) t2 USING (a) JOIN (SELECT * FROM nullkey_c1_t2) t3 USING (a) + WHERE t2.b > t4.b +) > 5 +ORDER BY 1,2; + +SELECT COUNT(*), b FROM distributed_table t4 GROUP BY 2 +HAVING ( + SELECT COUNT(*) FROM nullkey_c1_t1 t1 JOIN (SELECT * FROM distributed_table) t2 USING (a) JOIN (SELECT * FROM nullkey_c1_t2) t3 USING (a) + WHERE t2.b > t4.b +) > 5 +ORDER BY 1,2; + +-- test prepared statements + +-- prepare queries can be router plannable +PREPARE author_1_articles as + SELECT * + FROM articles_hash + WHERE author_id = 1; + +EXECUTE author_1_articles; +EXECUTE author_1_articles; +EXECUTE author_1_articles; +EXECUTE author_1_articles; +EXECUTE author_1_articles; +EXECUTE author_1_articles; + +-- parametric prepare queries can be router plannable +PREPARE author_articles(int) as + SELECT * + FROM articles_hash + WHERE author_id = $1; + +EXECUTE author_articles(1); +EXECUTE author_articles(1); +EXECUTE author_articles(1); +EXECUTE author_articles(1); +EXECUTE author_articles(1); +EXECUTE author_articles(1); + +EXECUTE author_articles(NULL); +EXECUTE author_articles(NULL); +EXECUTE author_articles(NULL); +EXECUTE author_articles(NULL); +EXECUTE author_articles(NULL); +EXECUTE author_articles(NULL); +EXECUTE author_articles(NULL); + +PREPARE author_articles_update(int) AS + UPDATE articles_hash SET title = 'test' WHERE author_id = $1; + +EXECUTE author_articles_update(NULL); +EXECUTE author_articles_update(NULL); +EXECUTE author_articles_update(NULL); +EXECUTE author_articles_update(NULL); +EXECUTE author_articles_update(NULL); +EXECUTE author_articles_update(NULL); +EXECUTE author_articles_update(NULL); + +-- More tests with insert .. select. +-- +-- The target list of "distributed statement"s that we send to workers +-- might differ(*) in Postgres versions < 15 and they are reported when +-- "log level >= DEBUG2". For this reason, we set log level to DEBUG1 to +-- avoid reporting them. +-- +-- DEBUG1 still allows reporting the reason why given INSERT .. SELECT +-- query is not distributed / requires pull-to-coordinator. + +SET client_min_messages TO DEBUG1; + +INSERT INTO bigserial_test (x, y) SELECT x, y FROM bigserial_test; + +INSERT INTO bigserial_test (x, y) SELECT a, a FROM reference_table; + +INSERT INTO agg_events + (user_id) +SELECT f2.id FROM + +(SELECT + id +FROM (SELECT users_ref_table.user_id AS id + FROM raw_events_first, + users_ref_table + WHERE raw_events_first.user_id = users_ref_table.user_id ) AS foo) as f +INNER JOIN +(SELECT v4, + v1, + id +FROM (SELECT SUM(raw_events_second.value_4) AS v4, + SUM(raw_events_first.value_1) AS v1, + raw_events_second.user_id AS id + FROM raw_events_first, + raw_events_second + WHERE raw_events_first.user_id = raw_events_second.user_id + GROUP BY raw_events_second.user_id + HAVING SUM(raw_events_second.value_4) > 1000) AS foo2 ) as f2 +ON (f.id = f2.id) +WHERE f.id IN (SELECT user_id + FROM raw_events_second); + +-- upsert with returning +INSERT INTO agg_events AS ae + ( + user_id, + value_1_agg, + agg_time + ) +SELECT user_id, + value_1, + time +FROM raw_events_first +ON conflict (user_id, value_1_agg) +DO UPDATE + SET agg_time = EXCLUDED.agg_time + WHERE ae.agg_time < EXCLUDED.agg_time +RETURNING user_id, value_1_agg; + +-- using a left join +INSERT INTO agg_events (user_id) +SELECT + raw_events_first.user_id +FROM + raw_events_first LEFT JOIN raw_events_second ON raw_events_first.user_id = raw_events_second.user_id + WHERE raw_events_second.user_id = 10 OR raw_events_second.user_id = 11; + +INSERT INTO agg_events (user_id) +SELECT + users_ref_table.user_id +FROM + users_ref_table LEFT JOIN raw_events_second ON users_ref_table.user_id = raw_events_second.user_id + WHERE raw_events_second.user_id = 10 OR raw_events_second.user_id = 11; + +INSERT INTO agg_events (user_id) +SELECT COALESCE(raw_events_first.user_id, users_ref_table.user_id) +FROM raw_events_first + RIGHT JOIN (users_ref_table LEFT JOIN raw_events_second ON users_ref_table.user_id = raw_events_second.user_id) + ON raw_events_first.user_id = users_ref_table.user_id; + +-- using a full join +INSERT INTO agg_events (user_id, value_1_agg) +SELECT t1.user_id AS col1, + t2.user_id AS col2 +FROM raw_events_first t1 + FULL JOIN raw_events_second t2 + ON t1.user_id = t2.user_id; + +-- using semi join +INSERT INTO raw_events_second + (user_id) +SELECT user_id +FROM raw_events_first +WHERE user_id IN (SELECT raw_events_second.user_id + FROM raw_events_second, raw_events_first + WHERE raw_events_second.user_id = raw_events_first.user_id AND raw_events_first.user_id = 200); + +-- using lateral join +INSERT INTO raw_events_second + (user_id) +SELECT user_id +FROM raw_events_first +WHERE NOT EXISTS (SELECT 1 + FROM raw_events_second + WHERE raw_events_second.user_id =raw_events_first.user_id); + +INSERT INTO raw_events_second + (user_id) +SELECT user_id +FROM users_ref_table +WHERE NOT EXISTS (SELECT 1 + FROM raw_events_second + WHERE raw_events_second.user_id = users_ref_table.user_id); + +-- using inner join +INSERT INTO agg_events (user_id) +SELECT raw_events_first.user_id +FROM raw_events_first INNER JOIN raw_events_second ON raw_events_first.user_id = raw_events_second.value_1 +WHERE raw_events_first.value_1 IN (10, 11,12) OR raw_events_second.user_id IN (1,2,3,4); + +INSERT INTO agg_events (user_id) +SELECT raw_events_first.user_id +FROM raw_events_first INNER JOIN users_ref_table ON raw_events_first.user_id = users_ref_table.user_id +WHERE raw_events_first.value_1 IN (10, 11,12) OR users_ref_table.user_id IN (1,2,3,4); + +-- We could relax distributed insert .. select checks to allow pushing +-- down more clauses down to the worker nodes when inserting into a single +-- shard by selecting from a colocated one. We might want to do something +-- like https://github.com/citusdata/citus/pull/6772. +-- +-- e.g., insert into null_shard_key_1/citus_local/reference +-- select * from null_shard_key_1/citus_local/reference limit 1 +-- +-- Below "limit / offset clause" test and some others are examples of this. + +-- limit / offset clause +INSERT INTO agg_events (user_id) SELECT raw_events_first.user_id FROM raw_events_first LIMIT 1; +INSERT INTO agg_events (user_id) SELECT raw_events_first.user_id FROM raw_events_first OFFSET 1; +INSERT INTO agg_events (user_id) SELECT users_ref_table.user_id FROM users_ref_table LIMIT 1; + +-- using a materialized cte +WITH cte AS MATERIALIZED + (SELECT max(value_1)+1 as v1_agg, user_id FROM raw_events_first GROUP BY user_id) +INSERT INTO agg_events (value_1_agg, user_id) +SELECT v1_agg, user_id FROM cte; + +INSERT INTO raw_events_second + WITH cte AS MATERIALIZED (SELECT * FROM raw_events_first) + SELECT user_id * 1000, time, value_1, value_2, value_3, value_4 FROM cte; + +INSERT INTO raw_events_second (user_id) + WITH cte AS MATERIALIZED (SELECT * FROM users_ref_table) + SELECT user_id FROM cte; + +-- using a regular cte +WITH cte AS (SELECT * FROM raw_events_first) +INSERT INTO raw_events_second + SELECT user_id * 7000, time, value_1, value_2, value_3, value_4 FROM cte; + +INSERT INTO raw_events_second + WITH cte AS (SELECT * FROM raw_events_first) + SELECT * FROM cte; + +INSERT INTO agg_events + WITH sub_cte AS (SELECT 1) + SELECT + raw_events_first.user_id, (SELECT * FROM sub_cte) + FROM + raw_events_first; + +-- we still support complex joins via INSERT's cte list .. +WITH cte AS ( + SELECT DISTINCT(reference_table.a) AS a, 1 AS b + FROM distributed_table RIGHT JOIN reference_table USING (a) +) +INSERT INTO raw_events_second (user_id, value_1) + SELECT (a+5)*-1, b FROM cte; + +-- .. and via SELECT's cte list too +INSERT INTO raw_events_second (user_id, value_1) +WITH cte AS ( + SELECT DISTINCT(reference_table.a) AS a, 1 AS b + FROM distributed_table RIGHT JOIN reference_table USING (a) +) + SELECT (a+5)*2, b FROM cte; + +-- using set operations +INSERT INTO + raw_events_first(user_id) + (SELECT user_id FROM raw_events_first) INTERSECT + (SELECT user_id FROM raw_events_first); + +INSERT INTO + raw_events_first(user_id) + (SELECT user_id FROM users_ref_table) INTERSECT + (SELECT user_id FROM raw_events_first); + +-- group by clause inside subquery +INSERT INTO agg_events + (user_id) +SELECT f2.id FROM + +(SELECT + id +FROM (SELECT raw_events_second.user_id AS id + FROM raw_events_first, + raw_events_second + WHERE raw_events_first.user_id = raw_events_second.user_id ) AS foo) as f +INNER JOIN +(SELECT v4, + v1, + id +FROM (SELECT SUM(raw_events_second.value_4) AS v4, + SUM(raw_events_first.value_1) AS v1, + raw_events_second.user_id AS id + FROM raw_events_first, + raw_events_second + WHERE raw_events_first.user_id = raw_events_second.user_id + GROUP BY raw_events_second.user_id + HAVING SUM(raw_events_second.value_4) > 1000) AS foo2 ) as f2 +ON (f.id = f2.id) +WHERE f.id IN (SELECT user_id + FROM raw_events_second); + +-- group by clause inside lateral subquery +INSERT INTO agg_events (user_id, value_4_agg) +SELECT + averages.user_id, avg(averages.value_4) +FROM + (SELECT + t1.user_id + FROM + raw_events_second t1 JOIN raw_events_second t2 on (t1.user_id = t2.user_id) + ) reference_ids + JOIN LATERAL + (SELECT + user_id, value_4 + FROM + raw_events_first) as averages ON averages.value_4 = reference_ids.user_id + GROUP BY averages.user_id; + +-- using aggregates +INSERT INTO agg_events + (value_3_agg, + value_4_agg, + value_1_agg, + value_2_agg, + user_id) +SELECT SUM(value_3), + Count(value_4), + user_id, + SUM(value_1), + Avg(value_2) +FROM raw_events_first +GROUP BY user_id; + +INSERT INTO agg_events (value_3_agg, value_1_agg) +SELECT AVG(user_id), SUM(user_id) +FROM users_ref_table +GROUP BY user_id; + +-- using generate_series +INSERT INTO raw_events_first (user_id, value_1, value_2) +SELECT s, s, s FROM generate_series(1, 5) s; + +CREATE SEQUENCE insert_select_test_seq; + +-- nextval() expression in select's targetlist +INSERT INTO raw_events_first (user_id, value_1, value_2) +SELECT s, nextval('insert_select_test_seq'), (random()*10)::int +FROM generate_series(100, 105) s; + +-- non-immutable function +INSERT INTO modify_fast_path (key, value_1) VALUES (2,1) RETURNING value_1, random() * key; + +SET client_min_messages TO DEBUG2; + +-- update / delete + +UPDATE nullkey_c1_t1 SET a = 1 WHERE b = 5; +UPDATE nullkey_c1_t1 SET a = 1 WHERE a = 5; +UPDATE nullkey_c1_t1 SET a = random(); +UPDATE nullkey_c1_t1 SET a = 1 WHERE a = random(); + +DELETE FROM nullkey_c1_t1 WHERE b = 5; +DELETE FROM nullkey_c1_t1 WHERE a = random(); + +-- simple update queries between different table types / colocated tables +UPDATE nullkey_c1_t1 SET b = 5 FROM nullkey_c1_t2 WHERE nullkey_c1_t1.b = nullkey_c1_t2.b; +UPDATE nullkey_c1_t1 SET b = 5 FROM nullkey_c2_t1 WHERE nullkey_c1_t1.b = nullkey_c2_t1.b; +UPDATE nullkey_c1_t1 SET b = 5 FROM reference_table WHERE nullkey_c1_t1.b = reference_table.b; +UPDATE nullkey_c1_t1 SET b = 5 FROM distributed_table WHERE nullkey_c1_t1.b = distributed_table.b; +UPDATE nullkey_c1_t1 SET b = 5 FROM distributed_table WHERE nullkey_c1_t1.b = distributed_table.a; +UPDATE nullkey_c1_t1 SET b = 5 FROM citus_local_table WHERE nullkey_c1_t1.b = citus_local_table.b; +UPDATE nullkey_c1_t1 SET b = 5 FROM postgres_local_table WHERE nullkey_c1_t1.b = postgres_local_table.b; + +UPDATE reference_table SET b = 5 FROM nullkey_c1_t1 WHERE nullkey_c1_t1.b = reference_table.b; +UPDATE distributed_table SET b = 5 FROM nullkey_c1_t1 WHERE nullkey_c1_t1.b = distributed_table.b; +UPDATE distributed_table SET b = 5 FROM nullkey_c1_t1 WHERE nullkey_c1_t1.b = distributed_table.a; +UPDATE citus_local_table SET b = 5 FROM nullkey_c1_t1 WHERE nullkey_c1_t1.b = citus_local_table.b; +UPDATE postgres_local_table SET b = 5 FROM nullkey_c1_t1 WHERE nullkey_c1_t1.b = postgres_local_table.b; + +-- simple delete queries between different table types / colocated tables +DELETE FROM nullkey_c1_t1 USING nullkey_c1_t2 WHERE nullkey_c1_t1.b = nullkey_c1_t2.b; +DELETE FROM nullkey_c1_t1 USING nullkey_c2_t1 WHERE nullkey_c1_t1.b = nullkey_c2_t1.b; +DELETE FROM nullkey_c1_t1 USING reference_table WHERE nullkey_c1_t1.b = reference_table.b; +DELETE FROM nullkey_c1_t1 USING distributed_table WHERE nullkey_c1_t1.b = distributed_table.b; +DELETE FROM nullkey_c1_t1 USING distributed_table WHERE nullkey_c1_t1.b = distributed_table.a; +DELETE FROM nullkey_c1_t1 USING citus_local_table WHERE nullkey_c1_t1.b = citus_local_table.b; +DELETE FROM nullkey_c1_t1 USING postgres_local_table WHERE nullkey_c1_t1.b = postgres_local_table.b; + +DELETE FROM reference_table USING nullkey_c1_t1 WHERE nullkey_c1_t1.b = reference_table.b; +DELETE FROM distributed_table USING nullkey_c1_t1 WHERE nullkey_c1_t1.b = distributed_table.b; +DELETE FROM distributed_table USING nullkey_c1_t1 WHERE nullkey_c1_t1.b = distributed_table.a; +DELETE FROM citus_local_table USING nullkey_c1_t1 WHERE nullkey_c1_t1.b = citus_local_table.b; +DELETE FROM postgres_local_table USING nullkey_c1_t1 WHERE nullkey_c1_t1.b = postgres_local_table.b; + +-- slightly more complex update queries +UPDATE nullkey_c1_t1 SET b = 5 WHERE nullkey_c1_t1.b IN (SELECT b FROM distributed_table); + +WITH cte AS materialized( + SELECT * FROM distributed_table +) +UPDATE nullkey_c1_t1 SET b = 5 FROM cte WHERE nullkey_c1_t1.b = cte.a; + +WITH cte AS ( + SELECT reference_table.a AS a, 1 AS b + FROM distributed_table RIGHT JOIN reference_table USING (a) +) +UPDATE nullkey_c1_t1 SET b = 5 WHERE nullkey_c1_t1.b IN (SELECT b FROM cte); + +UPDATE nullkey_c1_t1 SET b = 5 FROM reference_table WHERE EXISTS ( + SELECT 1 FROM reference_table LEFT JOIN nullkey_c1_t1 USING (a) WHERE nullkey_c1_t1.b IS NULL +); + +UPDATE nullkey_c1_t1 tx SET b = ( + SELECT nullkey_c1_t2.b FROM nullkey_c1_t2 JOIN nullkey_c1_t1 ON (nullkey_c1_t1.a != nullkey_c1_t2.a) WHERE nullkey_c1_t1.a = tx.a ORDER BY 1 LIMIT 1 +); + +UPDATE nullkey_c1_t1 tx SET b = t2.b FROM nullkey_c1_t1 t1 JOIN nullkey_c1_t2 t2 ON (t1.a = t2.a); + +WITH cte AS ( + SELECT * FROM nullkey_c1_t2 ORDER BY 1,2 LIMIT 10 +) +UPDATE nullkey_c1_t1 SET b = 5 WHERE nullkey_c1_t1.a IN (SELECT b FROM cte); + +UPDATE modify_fast_path SET value_1 = value_1 + 12 * value_1 WHERE key = 1; +UPDATE modify_fast_path SET value_1 = NULL WHERE value_1 = 15 AND (key = 1 OR value_2 = 'citus'); +UPDATE modify_fast_path SET value_1 = 5 WHERE key = 2 RETURNING value_1 * 15, value_1::numeric * 16; +UPDATE modify_fast_path + SET value_1 = 1 + FROM modify_fast_path_reference + WHERE + modify_fast_path.key = modify_fast_path_reference.key AND + modify_fast_path.key = 1 AND + modify_fast_path_reference.key = 1; + +PREPARE p1 (int, int, int) AS + UPDATE modify_fast_path SET value_1 = value_1 + $1 WHERE key = $2 AND value_1 = $3; +EXECUTE p1(1,1,1); +EXECUTE p1(2,2,2); +EXECUTE p1(3,3,3); +EXECUTE p1(4,4,4); +EXECUTE p1(5,5,5); +EXECUTE p1(6,6,6); +EXECUTE p1(7,7,7); + +PREPARE prepared_zero_shard_update(int) AS UPDATE modify_fast_path SET value_1 = 1 WHERE key = $1 AND false; +EXECUTE prepared_zero_shard_update(1); +EXECUTE prepared_zero_shard_update(2); +EXECUTE prepared_zero_shard_update(3); +EXECUTE prepared_zero_shard_update(4); +EXECUTE prepared_zero_shard_update(5); +EXECUTE prepared_zero_shard_update(6); +EXECUTE prepared_zero_shard_update(7); + +-- slightly more complex delete queries +DELETE FROM nullkey_c1_t1 WHERE nullkey_c1_t1.b IN (SELECT b FROM distributed_table); + +WITH cte AS materialized( + SELECT * FROM distributed_table +) +DELETE FROM nullkey_c1_t1 USING cte WHERE nullkey_c1_t1.b = cte.a; + +WITH cte AS ( + SELECT reference_table.a AS a, 1 AS b + FROM distributed_table RIGHT JOIN reference_table USING (a) +) +DELETE FROM nullkey_c1_t1 WHERE nullkey_c1_t1.b IN (SELECT b FROM cte); + +DELETE FROM nullkey_c1_t1 USING reference_table WHERE EXISTS ( + SELECT 1 FROM reference_table LEFT JOIN nullkey_c1_t1 USING (a) WHERE nullkey_c1_t1.b IS NULL +); + +DELETE FROM nullkey_c1_t1 tx USING nullkey_c1_t1 t1 JOIN nullkey_c1_t2 t2 ON (t1.a = t2.a); + +WITH cte AS ( + SELECT * FROM nullkey_c1_t2 ORDER BY 1,2 LIMIT 10 +) +DELETE FROM nullkey_c1_t1 WHERE nullkey_c1_t1.a IN (SELECT b FROM cte); + +DELETE FROM modify_fast_path WHERE value_1 = 15 AND (key = 1 OR value_2 = 'citus'); +DELETE FROM modify_fast_path WHERE key = 2 RETURNING value_1 * 15, value_1::numeric * 16; +DELETE FROM modify_fast_path + USING modify_fast_path_reference + WHERE + modify_fast_path.key = modify_fast_path_reference.key AND + modify_fast_path.key = 1 AND + modify_fast_path_reference.key = 1; + +PREPARE p2 (int, int, int) AS + DELETE FROM modify_fast_path WHERE key = ($2)*$1 AND value_1 = $3; +EXECUTE p2(1,1,1); +EXECUTE p2(2,2,2); +EXECUTE p2(3,3,3); +EXECUTE p2(4,4,4); +EXECUTE p2(5,5,5); +EXECUTE p2(6,6,6); +EXECUTE p2(7,7,7); + +PREPARE prepared_zero_shard_delete(int) AS DELETE FROM modify_fast_path WHERE key = $1 AND false; +EXECUTE prepared_zero_shard_delete(1); +EXECUTE prepared_zero_shard_delete(2); +EXECUTE prepared_zero_shard_delete(3); +EXECUTE prepared_zero_shard_delete(4); +EXECUTE prepared_zero_shard_delete(5); +EXECUTE prepared_zero_shard_delete(6); +EXECUTE prepared_zero_shard_delete(7); + +-- test modifying ctes + +WITH cte AS ( + UPDATE modify_fast_path SET value_1 = value_1 + 1 WHERE key = 1 RETURNING * +) +SELECT * FROM cte; + +WITH cte AS ( + DELETE FROM modify_fast_path WHERE key = 1 RETURNING * +) +SELECT * FROM modify_fast_path; + +WITH cte AS ( + DELETE FROM modify_fast_path WHERE key = 1 RETURNING * +) +SELECT * FROM modify_fast_path_reference WHERE key IN (SELECT key FROM cte); + +WITH cte AS ( + DELETE FROM reference_table WHERE a = 1 RETURNING * +) +SELECT * FROM nullkey_c1_t1 WHERE a IN (SELECT a FROM cte); + +WITH cte AS ( + DELETE FROM nullkey_c1_t1 WHERE a = 1 RETURNING * +) +SELECT * FROM nullkey_c1_t2 WHERE a IN (SELECT a FROM cte); + +WITH cte AS ( + DELETE FROM nullkey_c1_t1 WHERE a = 1 RETURNING * +) +SELECT * FROM nullkey_c2_t1 WHERE a IN (SELECT a FROM cte); + +WITH cte AS ( + DELETE FROM nullkey_c1_t1 WHERE a = 1 RETURNING * +) +SELECT * FROM distributed_table WHERE a IN (SELECT a FROM cte); + +-- Below two queries fail very late when +-- citus.enable_non_colocated_router_query_pushdown is set to on. + +SET citus.enable_non_colocated_router_query_pushdown TO ON; + +WITH cte AS ( + DELETE FROM distributed_table WHERE a = 1 RETURNING * +) +SELECT * FROM nullkey_c1_t1 WHERE a IN (SELECT a FROM cte); + +WITH cte AS ( + DELETE FROM distributed_table WHERE a = 1 RETURNING * +) +SELECT * FROM nullkey_c1_t1 WHERE b IN (SELECT b FROM cte); + +SET citus.enable_non_colocated_router_query_pushdown TO OFF; + +WITH cte AS ( + DELETE FROM distributed_table WHERE a = 1 RETURNING * +) +SELECT * FROM nullkey_c1_t1 WHERE a IN (SELECT a FROM cte); + +WITH cte AS ( + DELETE FROM distributed_table WHERE a = 1 RETURNING * +) +SELECT * FROM nullkey_c1_t1 WHERE b IN (SELECT b FROM cte); + +RESET citus.enable_non_colocated_router_query_pushdown; + +WITH cte AS ( + UPDATE modify_fast_path SET value_1 = value_1 + 1 WHERE key = 1 RETURNING * +) +UPDATE modify_fast_path SET value_1 = value_1 + 1 WHERE key = 1; + +WITH cte AS ( + DELETE FROM modify_fast_path WHERE key = 1 RETURNING * +) +DELETE FROM modify_fast_path WHERE key = 1; + +-- test window functions + +SELECT + user_id, avg(avg(value_3)) OVER (PARTITION BY user_id, MIN(value_2)) +FROM + raw_events_first +GROUP BY + 1 +ORDER BY + 2 DESC NULLS LAST, 1 DESC; + +SELECT + user_id, max(value_1) OVER (PARTITION BY user_id, MIN(value_2)) +FROM ( + SELECT + DISTINCT us.user_id, us.value_2, us.value_1, random() as r1 + FROM + raw_events_first as us, raw_events_second + WHERE + us.user_id = raw_events_second.user_id + ORDER BY + user_id, value_2 + ) s +GROUP BY + 1, value_1 +ORDER BY + 2 DESC, 1; + +SELECT + DISTINCT ON (raw_events_second.user_id, rnk) raw_events_second.user_id, rank() OVER my_win AS rnk +FROM + raw_events_second, raw_events_first +WHERE + raw_events_first.user_id = raw_events_second.user_id +WINDOW + my_win AS (PARTITION BY raw_events_second.user_id, raw_events_first.value_1 ORDER BY raw_events_second.time DESC) +ORDER BY + rnk DESC, 1 DESC +LIMIT 10; + +-- more tests with ctes and subqueries + +-- CTEs are recursively planned, and subquery foo is also recursively planned. +-- Then the final plan becomes a router plan. +WITH cte AS MATERIALIZED ( + WITH local_cte AS MATERIALIZED ( + SELECT * FROM users_table_local + ), + dist_cte AS MATERIALIZED ( + SELECT user_id FROM colocated_events_table + ) + SELECT dist_cte.user_id FROM local_cte JOIN dist_cte ON dist_cte.user_id=local_cte.user_id +) +SELECT count(*) +FROM cte, + ( + SELECT DISTINCT users_table.user_id + FROM users_table, colocated_events_table + WHERE users_table.user_id = colocated_events_table.user_id AND event_type IN (1,2,3,4) + ORDER BY 1 DESC LIMIT 5 + ) AS foo +WHERE foo.user_id = cte.user_id; + +-- CTEs are colocated, route entire query. +WITH cte1 AS ( + SELECT * FROM users_table WHERE user_id = 1 +), cte2 AS ( + SELECT * FROM colocated_events_table WHERE user_id = 1 +) +SELECT cte1.user_id, cte1.value_1, cte2.user_id, cte2.event_type +FROM cte1, cte2 +ORDER BY cte1.user_id, cte1.value_1, cte2.user_id, cte2.event_type +LIMIT 5; + +-- CTEs aren't colocated, CTEs become intermediate results. +WITH cte1 AS MATERIALIZED ( + SELECT * FROM users_table WHERE user_id = 1 +), cte2 AS MATERIALIZED ( + SELECT * FROM non_colocated_events_table WHERE user_id = 6 +) +SELECT cte1.user_id, cte1.value_1, cte2.user_id, cte2.user_id +FROM cte1, cte2 +ORDER BY cte1.user_id, cte1.value_1, cte2.user_id, cte2.event_type +LIMIT 5; + +-- users_table & colocated_users_table are colocated, route entire query. +WITH cte1 AS ( + SELECT * FROM users_table WHERE user_id = 1 +) +UPDATE colocated_users_table dt SET value = cte1.value_1 +FROM cte1 WHERE cte1.user_id = dt.id AND dt.id = 1; + +-- users_table & non_colocated_users_table are not colocated, cte is recursive planned. +WITH cte1 AS ( + SELECT * FROM users_table WHERE user_id = 1 +) +UPDATE non_colocated_users_table dt SET value = cte1.value_1 +FROM cte1 WHERE cte1.user_id = dt.id AND dt.id = 1; + +-- All relations are not colocated, CTEs become intermediate results. +WITH cte1 AS MATERIALIZED ( + SELECT * FROM users_table WHERE user_id = 1 +), cte2 AS MATERIALIZED ( + SELECT * FROM non_colocated_events_table WHERE user_id = 6 +) +UPDATE non_colocated_users_table dt SET value = cte1.value_1 + cte2.event_type +FROM cte1, cte2 WHERE cte1.user_id = dt.id AND dt.id = 1; + +-- Volatile function calls should not be routed. +WITH cte1 AS MATERIALIZED (SELECT id, value FROM func()) +UPDATE colocated_users_table dt SET value = cte1.value +FROM cte1 WHERE dt.id = 1; + +-- CTEs are recursively planned, and subquery foo is also recursively planned. +WITH cte AS MATERIALIZED ( + WITH local_cte AS MATERIALIZED ( + SELECT * FROM users_table_local + ), + dist_cte AS MATERIALIZED ( + SELECT user_id FROM colocated_events_table + ) + SELECT dist_cte.user_id FROM local_cte JOIN dist_cte ON dist_cte.user_id=local_cte.user_id +) +SELECT count(*) +FROM + cte, + ( + SELECT DISTINCT users_table.user_id + FROM users_table, colocated_events_table + WHERE users_table.user_id = colocated_events_table.user_id AND event_type IN (1,2,3,4) + ORDER BY 1 DESC LIMIT 5 + ) AS foo, colocated_events_table +WHERE foo.user_id = cte.user_id AND colocated_events_table.user_id = cte.user_id; + +-- CTEs are replaced and subquery in WHERE is also replaced. +WITH cte AS MATERIALIZED ( + WITH local_cte AS MATERIALIZED ( + SELECT * FROM users_table_local + ), + dist_cte AS MATERIALIZED ( + SELECT user_id FROM colocated_events_table + ) + SELECT dist_cte.user_id FROM local_cte JOIN dist_cte ON dist_cte.user_id=local_cte.user_id +) +SELECT DISTINCT cte.user_id +FROM users_table, cte +WHERE users_table.user_id = cte.user_id AND + users_table.user_id IN ( + SELECT DISTINCT value_2 FROM users_table WHERE value_1 >= 1 AND value_1 <= 20 ORDER BY 1 LIMIT 5 + ) +ORDER BY 1 DESC; + +-- Subquery in WHERE clause is planned recursively due to the recurring table +-- in FROM clause. +WITH cte AS MATERIALIZED ( + WITH local_cte AS MATERIALIZED ( + SELECT * FROM users_table_local + ), + dist_cte AS MATERIALIZED ( + SELECT user_id FROM colocated_events_table + ) + SELECT dist_cte.user_id FROM local_cte JOIN dist_cte ON dist_cte.user_id=local_cte.user_id +) +SELECT DISTINCT cte.user_id +FROM cte +WHERE cte.user_id IN (SELECT DISTINCT user_id FROM users_table WHERE value_1 >= 1 AND value_1 <= 20) +ORDER BY 1 DESC; + +-- CTEs inside a subquery and the final query becomes a router +-- query. +SELECT + user_id +FROM + ( + WITH cte AS MATERIALIZED ( + SELECT DISTINCT users_table.user_id + FROM users_table, colocated_events_table + WHERE users_table.user_id = colocated_events_table.user_id AND + event_type IN (1,2,3,4) + ) + SELECT * FROM cte ORDER BY 1 DESC + ) AS foo +ORDER BY 1 DESC; + +-- CTEs inside a deeper subquery and also the subquery that contains the CTE are +-- recursively planned. +SELECT DISTINCT bar.user_id +FROM + ( + WITH cte AS MATERIALIZED ( + SELECT DISTINCT users_table.user_id + FROM users_table, colocated_events_table + WHERE users_table.user_id = colocated_events_table.user_id AND event_type IN (1,2,3,4) + ) + SELECT * FROM cte ORDER BY 1 DESC + ) AS foo, + ( + SELECT users_table.user_id, some_events.event_type + FROM + users_table, + ( + WITH cte AS MATERIALIZED ( + SELECT event_type, users_table.user_id + FROM users_table, colocated_events_table + WHERE users_table.user_id = colocated_events_table.user_id AND value_1 IN (1,2) + ) SELECT * FROM cte ORDER BY 1 DESC + ) AS some_events + WHERE users_table.user_id = some_events.user_id AND event_type IN (1,2,3,4) + ORDER BY 2,1 LIMIT 2 + ) AS bar +WHERE foo.user_id = bar.user_id +ORDER BY 1 DESC LIMIT 5; + +-- Recursively plan subqueries inside the CTEs that contains LIMIT and OFFSET. +WITH cte AS MATERIALIZED ( + WITH local_cte AS MATERIALIZED ( + SELECT * FROM users_table_local + ), + dist_cte AS MATERIALIZED ( + SELECT + user_id + FROM + colocated_events_table, + (SELECT DISTINCT value_2 FROM users_table OFFSET 0) as foo + WHERE + colocated_events_table.user_id = foo.value_2 AND + colocated_events_table.user_id IN (SELECT DISTINCT value_1 FROM users_table ORDER BY 1 LIMIT 3) + ) + SELECT dist_cte.user_id FROM local_cte JOIN dist_cte ON dist_cte.user_id=local_cte.user_id +) +SELECT count(*) +FROM + cte, + ( + SELECT DISTINCT users_table.user_id + FROM users_table, colocated_events_table + WHERE users_table.user_id = colocated_events_table.user_id AND event_type IN (1,2,3,4) + ORDER BY 1 DESC LIMIT 5 + ) AS foo +WHERE foo.user_id = cte.user_id; + +-- more tests with sublinks and subqueries in targetlist + +SELECT event_type, (SELECT e.value_2 FROM users_reference_table WHERE user_id = 1 AND value_1 = 1), (SELECT e.value_2) +FROM non_colocated_events_table e +ORDER BY 1,2 LIMIT 1; + +SELECT event_type, (SELECT time FROM users_table WHERE user_id = e.user_id ORDER BY time LIMIT 1) +FROM non_colocated_events_table e +ORDER BY 1,2 LIMIT 1; + +SELECT event_type, (SELECT max(time) FROM users_table WHERE user_id = e.value_2) +FROM non_colocated_events_table e +ORDER BY 1,2 LIMIT 1; + +SELECT event_type, (SELECT max(time) FROM users_table) +FROM non_colocated_events_table e +ORDER BY 1,2 LIMIT 1; + +WITH cte_1 AS (SELECT max(time) FROM users_table) +SELECT event_type, (SELECT * FROM cte_1) +FROM non_colocated_events_table e +ORDER BY 1,2 LIMIT 1; + +WITH cte_1 AS (SELECT max(time) FROM users_table) +SELECT event_type, (SELECT * FROM cte_1 LIMIT 1) +FROM non_colocated_events_table e +ORDER BY 1,2 LIMIT 1; + +WITH cte_1 AS (SELECT max(time) m FROM users_table) +SELECT count(*), (SELECT * FROM cte_1 c1 join cte_1 c2 using (m)) +FROM non_colocated_events_table e +GROUP BY 2 +ORDER BY 1,2 LIMIT 1; + +WITH cte_1 AS (SELECT min(user_id) u, max(time) m FROM users_table) +SELECT count(*), (SELECT max(time) FROM users_table WHERE user_id = cte_1.u GROUP BY user_id) +FROM cte_1 +GROUP BY 2 +ORDER BY 1,2 LIMIT 1; + +SELECT sum(e.user_id) + (SELECT max(value_3) FROM users_table WHERE user_id = e.user_id GROUP BY user_id) +FROM non_colocated_events_table e +GROUP BY e.user_id +ORDER BY 1 LIMIT 3; + +SELECT e.user_id, sum((SELECT any_value(value_3) FROM users_reference_table WHERE user_id = e.user_id GROUP BY user_id)) OVER (PARTITION BY e.user_id) +FROM non_colocated_events_table e +ORDER BY 1, 2 LIMIT 3; + +SELECT (SELECT (SELECT e.user_id + user_id) FROM users_table WHERE user_id = e.user_id GROUP BY user_id) +FROM non_colocated_events_table e +GROUP BY 1 +ORDER BY 1 LIMIT 3; + +SELECT (SELECT (SELECT e.user_id + user_id) FROM users_reference_table WHERE user_id = e.user_id GROUP BY user_id) +FROM non_colocated_events_table e +GROUP BY 1 +ORDER BY 1 LIMIT 3; + +WITH cte_1 AS (SELECT user_id FROM users_table ORDER BY 1 LIMIT 1) +SELECT (SELECT (SELECT e.user_id + user_id) FROM cte_1 WHERE user_id = e.user_id GROUP BY user_id) +FROM non_colocated_events_table e +GROUP BY 1 +ORDER BY 1 LIMIT 3; + +SELECT (SELECT (SELECT e.user_id + user_id) FROM (SELECT 1 AS user_id) s WHERE user_id = e.user_id GROUP BY user_id) +FROM non_colocated_events_table e +GROUP BY 1 +ORDER BY 1 LIMIT 3; + +CREATE TEMP VIEW view_1 AS (SELECT user_id, value_2 FROM users_table WHERE user_id = 1 AND value_1 = 1 ORDER BY 1,2); + +SELECT (SELECT value_2 FROM view_1 WHERE user_id = e.user_id GROUP BY value_2) +FROM non_colocated_events_table e +GROUP BY 1 +ORDER BY 1 LIMIT 3; + +SELECT + user_id, count(*) +FROM + non_colocated_events_table e1 +GROUP BY user_id + HAVING + count(*) > (SELECT count(*) FROM (SELECT + (SELECT sum(user_id) FROM users_table WHERE user_id = u1.user_id GROUP BY user_id) + FROM users_table u1 + GROUP BY user_id) as foo) ORDER BY 1 DESC; + +SELECT count(*) FROM (SELECT + (SELECT user_id FROM users_table WHERE user_id = u1.user_id FOR UPDATE) +FROM users_table u1 +GROUP BY user_id) as foo; + +-- test single hash repartition join + +SET citus.log_multi_join_order TO ON; +SET client_min_messages TO DEBUG1; +SET citus.enable_repartition_joins TO ON; +SET citus.enable_single_hash_repartition_joins TO ON; + +SELECT count(*) FROM nullkey_c1_t1 JOIN distributed_table USING(a); +select count(*) from nullkey_c1_t1 JOIN nullkey_c2_t2 USING(a); + +RESET citus.log_multi_join_order; +SET client_min_messages TO DEBUG2; +RESET citus.enable_repartition_joins; +RESET citus.enable_single_hash_repartition_joins; + +SET client_min_messages TO DEBUG1; +SET citus.enable_repartition_joins TO ON; +SET citus.log_multi_join_order TO ON; + +SELECT count(*), avg(avgsub.a) +FROM ( + SELECT table_0.a + FROM reference_table AS table_0 + INNER JOIN nullkey_c1_t1 AS table_1 USING (a) + INNER JOIN reference_table AS table_2 USING (a) + INNER JOIN nullkey_c2_t1 AS table_3 USING (a) + ORDER BY a LIMIT 7 +) AS avgsub; + +SET citus.enable_single_hash_repartition_joins TO ON; + +-- We prefer dual-hash repartition join over single-hash repartition join +-- even if citus.enable_single_hash_repartition_joins is set to ON. This +-- happens because single shard tables don't have a shard key. + +SELECT count(*), avg(avgsub.a) +FROM ( + SELECT table_0.a + FROM reference_table AS table_0 + INNER JOIN nullkey_c1_t1 AS table_1 USING (a) + INNER JOIN reference_table AS table_2 USING (a) + INNER JOIN nullkey_c2_t1 AS table_3 USING (a) + ORDER BY a LIMIT 7 +) AS avgsub; + +RESET citus.enable_single_hash_repartition_joins; + +SET client_min_messages TO DEBUG2; +RESET citus.enable_repartition_joins; +RESET citus.log_multi_join_order; + +SELECT count(*), avg(avgsub.a) +FROM ( + SELECT table_0.a + FROM nullkey_c1_t1 AS table_0 + RIGHT JOIN ( + SELECT table_2.a FROM ( + SELECT table_3.a FROM nullkey_c2_t1 AS table_3 + ORDER BY a LIMIT 0 + ) AS table_2 + INNER JOIN nullkey_c2_t1 AS table_4 USING (a) + WHERE table_4.a < 8 + ) AS table_1 USING (a) +) AS avgsub; + +-- test nested exec + +CREATE FUNCTION dist_query_single_shard(p_key int) +RETURNS bigint +LANGUAGE plpgsql AS $$ +DECLARE + result bigint; +BEGIN + SELECT count(*) INTO result FROM query_single_shard_table.nullkey_c1_t1 WHERE a = p_key; + RETURN result; +END; +$$; + +CREATE FUNCTION ref_query() +RETURNS bigint +LANGUAGE plpgsql AS $$ +DECLARE + result bigint; +BEGIN + SELECT count(*) INTO result FROM query_single_shard_table.reference_table; + RETURN result; +END; +$$; + +SELECT dist_query_single_shard(count(*)::int) FROM nullkey_c1_t1; +SELECT ref_query()+count(*) FROM nullkey_c1_t1; + +SET client_min_messages TO ERROR; +DROP SCHEMA query_single_shard_table CASCADE; diff --git a/src/test/regress/sql/recurring_outer_join.sql b/src/test/regress/sql/recurring_outer_join.sql index e26df4b86..595d734ec 100644 --- a/src/test/regress/sql/recurring_outer_join.sql +++ b/src/test/regress/sql/recurring_outer_join.sql @@ -4,12 +4,7 @@ SET search_path TO recurring_outer_join; SET citus.next_shard_id TO 1520000; SET citus.shard_count TO 32; --- idempotently add node to allow this test to run without add_coordinator -SET client_min_messages TO WARNING; -SELECT 1 FROM citus_add_node('localhost', :master_port, groupid => 0); - SET client_min_messages TO DEBUG1; - CREATE TABLE dist_1 (a int, b int); SELECT create_distributed_table('dist_1', 'a'); INSERT INTO dist_1 VALUES @@ -1026,5 +1021,3 @@ ROLLBACK; SET client_min_messages TO ERROR; DROP SCHEMA recurring_outer_join CASCADE; - -SELECT master_remove_node('localhost', :master_port); diff --git a/src/test/regress/sql/recursive_dml_with_different_planners_executors.sql b/src/test/regress/sql/recursive_dml_with_different_planners_executors.sql index 179fcb198..e5fb8a6c5 100644 --- a/src/test/regress/sql/recursive_dml_with_different_planners_executors.sql +++ b/src/test/regress/sql/recursive_dml_with_different_planners_executors.sql @@ -60,7 +60,6 @@ UPDATE distributed_table SET dept = foo.max_dept FROM (SELECT tenant_id FROM second_distributed_table WHERE dept IN (1, 2, 3, 4)) ) as foo WHERE foo.max_dept >= dept and tenant_id = '8'; - -RESET client_min_messages; +SET client_min_messages TO WARNING; DROP SCHEMA recursive_dml_with_different_planner_executors CASCADE; SET search_path TO public; diff --git a/src/test/regress/sql/recursive_view_local_table.sql b/src/test/regress/sql/recursive_view_local_table.sql index c33a95e99..e813dc455 100644 --- a/src/test/regress/sql/recursive_view_local_table.sql +++ b/src/test/regress/sql/recursive_view_local_table.sql @@ -51,5 +51,5 @@ SELECT ref_table.* FROM ref_table WHERE EXISTS (SELECT * FROM recursive_defined_ SELECT ref_table.* FROM ref_table WHERE EXISTS (SELECT * FROM recursive_defined_non_recursive_view l WHERE l.c = ref_table.a) AND false; SELECT ref_table.* FROM ref_table WHERE EXISTS (SELECT * FROM recursive_defined_non_recursive_view l WHERE l.c = ref_table.a AND false); - +SET client_min_messages TO WARNING; DROP SCHEMA postgres_local_table CASCADE; diff --git a/src/test/regress/sql/relation_access_tracking.sql b/src/test/regress/sql/relation_access_tracking.sql index 3a4581e59..f0f132e6b 100644 --- a/src/test/regress/sql/relation_access_tracking.sql +++ b/src/test/regress/sql/relation_access_tracking.sql @@ -583,5 +583,6 @@ BEGIN; SELECT * FROM relation_accesses WHERE table_name IN ('table_3') ORDER BY 1; COMMIT; +SET client_min_messages TO WARNING; SET search_path TO 'public'; DROP SCHEMA access_tracking CASCADE; diff --git a/src/test/regress/sql/remove_coordinator.sql b/src/test/regress/sql/remove_coordinator.sql index 2db26d4d7..b0df327d1 100644 --- a/src/test/regress/sql/remove_coordinator.sql +++ b/src/test/regress/sql/remove_coordinator.sql @@ -1,2 +1,5 @@ -- removing coordinator from pg_dist_node should update pg_dist_colocation SELECT master_remove_node('localhost', :master_port); + +-- restore coordinator for the rest of the tests +SELECT citus_set_coordinator_host('localhost', :master_port); diff --git a/src/test/regress/sql/remove_coordinator_from_metadata.sql b/src/test/regress/sql/remove_coordinator_from_metadata.sql new file mode 100644 index 000000000..8ec16cfaf --- /dev/null +++ b/src/test/regress/sql/remove_coordinator_from_metadata.sql @@ -0,0 +1 @@ +SELECT master_remove_node('localhost', :master_port); diff --git a/src/test/regress/sql/run_command_on_all_nodes.sql b/src/test/regress/sql/run_command_on_all_nodes.sql index 0004a74e7..9b7b083af 100644 --- a/src/test/regress/sql/run_command_on_all_nodes.sql +++ b/src/test/regress/sql/run_command_on_all_nodes.sql @@ -1,6 +1,8 @@ CREATE SCHEMA run_command_on_all_nodes; SET search_path TO run_command_on_all_nodes; +SELECT master_remove_node('localhost', :master_port); + -- check coordinator isn't in metadata SELECT count(*) != 0 AS "Coordinator is in Metadata" FROM pg_dist_node @@ -85,3 +87,6 @@ SELECT success, result FROM run_command_on_all_nodes($$select count(*) from run_ SELECT success, result FROM run_command_on_all_nodes($$create index on run_command_on_all_nodes.test (x)$$); DROP SCHEMA run_command_on_all_nodes CASCADE; + +SELECT citus_set_coordinator_host('localhost'); + diff --git a/src/test/regress/sql/schema_based_sharding.sql b/src/test/regress/sql/schema_based_sharding.sql new file mode 100644 index 000000000..1e5208332 --- /dev/null +++ b/src/test/regress/sql/schema_based_sharding.sql @@ -0,0 +1,1160 @@ +CREATE SCHEMA regular_schema; +SET search_path TO regular_schema; + +SET citus.next_shard_id TO 1920000; +SET citus.shard_count TO 32; +SET citus.shard_replication_factor TO 1; + +SET client_min_messages TO WARNING; +SELECT 1 FROM citus_add_node('localhost', :master_port, groupid => 0); + +SET client_min_messages TO NOTICE; + +-- Verify that the UDFs used to sync tenant schema metadata to workers +-- fail on NULL input. +SELECT citus_internal_add_tenant_schema(NULL, 1); +SELECT citus_internal_add_tenant_schema(1, NULL); +SELECT citus_internal_delete_tenant_schema(NULL); +SELECT citus_internal_unregister_tenant_schema_globally(1, NULL); +SELECT citus_internal_unregister_tenant_schema_globally(NULL, 'text'); + +-- Verify that citus_internal_unregister_tenant_schema_globally can only +-- be called on schemas that are dropped already. +SELECT citus_internal_unregister_tenant_schema_globally('regular_schema'::regnamespace, 'regular_schema'); + +SELECT 1 FROM citus_remove_node('localhost', :worker_2_port); + +CREATE TABLE regular_schema.test_table(a int, b text); +SELECT create_distributed_table('regular_schema.test_table', 'a'); +SET citus.enable_schema_based_sharding TO ON; + +-- show that regular_schema doesn't show up in pg_dist_schema +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'regular_schema'; + +-- empty tenant +CREATE SCHEMA "tenant\'_1"; + +-- non-empty tenant +CREATE SCHEMA "tenant\'_2"; +CREATE TABLE "tenant\'_2".test_table(a int, b text); + +-- empty tenant +CREATE SCHEMA "tenant\'_3"; +CREATE TABLE "tenant\'_3".test_table(a int, b text); +DROP TABLE "tenant\'_3".test_table; + +-- add a node after creating tenant schemas +SELECT 1 FROM citus_add_node('localhost', :worker_2_port); + +ALTER SCHEMA "tenant\'_1" RENAME TO tenant_1; +ALTER SCHEMA "tenant\'_2" RENAME TO tenant_2; +ALTER SCHEMA "tenant\'_3" RENAME TO tenant_3; + +-- verify that create_distributed_table() and others fail when called on tenant tables +SELECT create_distributed_table('tenant_2.test_table', 'a'); +SELECT create_reference_table('tenant_2.test_table'); +SELECT citus_add_local_table_to_metadata('tenant_2.test_table'); + +-- verify we don't allow update_distributed_table_colocation for tenant tables +SELECT update_distributed_table_colocation('tenant_2.test_table', colocate_with => 'none'); +-- verify we also don't allow colocate_with a tenant table +SELECT update_distributed_table_colocation('regular_schema.test_table', colocate_with => 'tenant_2.test_table'); + +-- verify we do not allow undistribute_table for tenant tables +CREATE TABLE tenant_2.undist_table(id int); +SELECT undistribute_table('tenant_2.undist_table'); + +-- verify we don't allow alter_distributed_table for tenant tables +SELECT alter_distributed_table('tenant_2.test_table', colocate_with => 'none'); +-- verify we also don't allow colocate_with a tenant table +SELECT alter_distributed_table('regular_schema.test_table', colocate_with => 'tenant_2.test_table'); + +-- verify we can set tenant table's schema to regular schema +CREATE TABLE tenant_2.test_table2(id int); +ALTER TABLE tenant_2.test_table2 SET SCHEMA regular_schema; +-- verify that regular_schema.test_table2 does not exist in pg_dist_partition +SELECT COUNT(*)=0 FROM pg_dist_partition +WHERE logicalrelid = 'regular_schema.test_table2'::regclass AND + partmethod = 'n' AND repmodel = 's' AND colocationid > 0; +-- verify that tenant_2.test_table2 does not exist +SELECT * FROM tenant_2.test_table2; + +-- verify we can set regular table's schema to distributed schema +CREATE TABLE regular_schema.test_table3(id int); +ALTER TABLE regular_schema.test_table3 SET SCHEMA tenant_2; +-- verify that tenant_2.test_table3 is recorded in pg_dist_partition as a single-shard table. +SELECT COUNT(*)=1 FROM pg_dist_partition +WHERE logicalrelid = 'tenant_2.test_table3'::regclass AND + partmethod = 'n' AND repmodel = 's' AND colocationid > 0; +-- verify that regular_schema.test_table3 does not exist +SELECT * FROM regular_schema.test_table3; + +-- verify we can set tenant table's schema to another distributed schema +CREATE TABLE tenant_2.test_table4(id int); +ALTER TABLE tenant_2.test_table4 SET SCHEMA tenant_3; +-- verify that tenant_3.test_table4 is recorded in pg_dist_partition as a single-shard table. +SELECT COUNT(*)=1 FROM pg_dist_partition +WHERE logicalrelid = 'tenant_3.test_table4'::regclass AND + partmethod = 'n' AND repmodel = 's' AND colocationid > 0; +-- verify that tenant_2.test_table4 does not exist +SELECT * FROM tenant_2.test_table4; + +-- verify that we can put a local table in regular schema into distributed schema +CREATE TABLE regular_schema.pg_local_tbl(id int); +ALTER TABLE regular_schema.pg_local_tbl SET SCHEMA tenant_2; + +-- verify that we can put a Citus local table in regular schema into distributed schema +CREATE TABLE regular_schema.citus_local_tbl(id int); +SELECT citus_add_local_table_to_metadata('regular_schema.citus_local_tbl'); +ALTER TABLE regular_schema.citus_local_tbl SET SCHEMA tenant_2; + +-- verify that we do not allow a hash distributed table in regular schema into distributed schema +CREATE TABLE regular_schema.hash_dist_tbl(id int); +SELECT create_distributed_table('regular_schema.hash_dist_tbl', 'id'); +ALTER TABLE regular_schema.hash_dist_tbl SET SCHEMA tenant_2; + +-- verify that we do not allow a reference table in regular schema into distributed schema +CREATE TABLE regular_schema.ref_tbl(id int PRIMARY KEY); +SELECT create_reference_table('regular_schema.ref_tbl'); +ALTER TABLE regular_schema.ref_tbl SET SCHEMA tenant_2; + +-- verify that we can put a table in tenant schema into regular schema +CREATE TABLE tenant_2.tenant_tbl(id int); +ALTER TABLE tenant_2.tenant_tbl SET SCHEMA regular_schema; + +-- verify that we can put a table in tenant schema into another tenant schema +CREATE TABLE tenant_2.tenant_tbl2(id int); +ALTER TABLE tenant_2.tenant_tbl2 SET SCHEMA tenant_3; + +-- verify that we do not allow a local table in regular schema into distributed schema if it has foreign key to a non-reference table in another schema +CREATE TABLE regular_schema.pg_local_tbl1(id int PRIMARY KEY); +CREATE TABLE regular_schema.pg_local_tbl2(id int REFERENCES regular_schema.pg_local_tbl1(id)); +ALTER TABLE regular_schema.pg_local_tbl2 SET SCHEMA tenant_2; + +-- verify that we allow a local table in regular schema into distributed schema if it has foreign key to a reference table in another schema +CREATE TABLE regular_schema.pg_local_tbl3(id int REFERENCES regular_schema.ref_tbl(id)); +ALTER TABLE regular_schema.pg_local_tbl3 SET SCHEMA tenant_2; + +-- verify that we do not allow a table in tenant schema into regular schema if it has foreign key to/from another table in the same schema +CREATE TABLE tenant_2.tenant_tbl1(id int PRIMARY KEY); +CREATE TABLE tenant_2.tenant_tbl2(id int REFERENCES tenant_2.tenant_tbl1(id)); +ALTER TABLE tenant_2.tenant_tbl1 SET SCHEMA regular_schema; +ALTER TABLE tenant_2.tenant_tbl2 SET SCHEMA regular_schema; + +-- verify that we do not allow a table in distributed schema into another distributed schema if it has foreign key to/from another table in the same schema +CREATE TABLE tenant_2.tenant_tbl3(id int PRIMARY KEY); +CREATE TABLE tenant_2.tenant_tbl4(id int REFERENCES tenant_2.tenant_tbl3(id)); +ALTER TABLE tenant_2.tenant_tbl3 SET SCHEMA tenant_3; +ALTER TABLE tenant_2.tenant_tbl4 SET SCHEMA tenant_3; + +-- alter set non-existent schema +ALTER TABLE tenant_2.test_table SET SCHEMA ghost_schema; +ALTER TABLE IF EXISTS tenant_2.test_table SET SCHEMA ghost_schema; +-- alter set non-existent table +ALTER TABLE tenant_2.ghost_table SET SCHEMA ghost_schema; +ALTER TABLE IF EXISTS tenant_2.ghost_table SET SCHEMA ghost_schema; + +-- (on coordinator) verify that colocation id is set for empty tenants too +SELECT colocationid > 0 FROM pg_dist_schema +WHERE schemaid::regnamespace::text IN ('tenant_1', 'tenant_3'); + +-- (on workers) verify that colocation id is set for empty tenants too +SELECT result FROM run_command_on_workers($$ + SELECT array_agg(colocationid > 0) FROM pg_dist_schema + WHERE schemaid::regnamespace::text IN ('tenant_1', 'tenant_3'); +$$); + +-- Verify that tenant_2.test_table is recorded in pg_dist_partition as a +-- single-shard table. +SELECT COUNT(*)=1 FROM pg_dist_partition +WHERE logicalrelid = 'tenant_2.test_table'::regclass AND + partmethod = 'n' AND repmodel = 's' AND colocationid > 0; + +-- (on coordinator) verify that colocation id is properly set for non-empty tenant schema +SELECT colocationid = ( + SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_2.test_table'::regclass +) +FROM pg_dist_schema +WHERE schemaid::regnamespace::text = 'tenant_2'; + +-- (on workers) verify that colocation id is properly set for non-empty tenant schema +SELECT result FROM run_command_on_workers($$ + SELECT colocationid = ( + SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_2.test_table'::regclass + ) + FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_2'; +$$); + +-- create a tenant table for tenant_1 after add_node +CREATE TABLE tenant_1.test_table(a int, b text); + +-- (on coordinator) verify that colocation id is properly set for now-non-empty tenant schema +SELECT colocationid = ( + SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_1.test_table'::regclass +) +FROM pg_dist_schema +WHERE schemaid::regnamespace::text = 'tenant_1'; + +-- (on workers) verify that colocation id is properly set for now-non-empty tenant schema +SELECT result FROM run_command_on_workers($$ + SELECT colocationid = ( + SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_1.test_table'::regclass + ) + FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_1'; +$$); + +-- verify that tenant_1 and tenant_2 have different colocation ids +SELECT COUNT(DISTINCT(colocationid))=2 FROM pg_dist_schema +WHERE schemaid::regnamespace::text IN ('tenant_1', 'tenant_2'); + +-- verify that we don't allow creating tenant tables via CREATE SCHEMA command +CREATE SCHEMA schema_using_schema_elements CREATE TABLE test_table(a int, b text); + +CREATE SCHEMA tenant_4; +CREATE TABLE tenant_4.tbl_1(a int, b text); +CREATE TABLE tenant_4.tbl_2(a int, b text); + +-- verify that we don't allow creating a foreign table in a tenant schema, with a nice error message +CREATE FOREIGN TABLE tenant_4.foreign_table ( + id bigint not null, + full_name text not null default '' +) SERVER fake_fdw_server OPTIONS (encoding 'utf-8', compression 'true', table_name 'foreign_table'); + +-- verify that we don't allow creating a foreign table in a tenant schema +CREATE TEMPORARY TABLE tenant_4.temp_table (a int, b text); + +CREATE TABLE tenant_4.partitioned_table(a int, b text, PRIMARY KEY (a)) PARTITION BY RANGE (a); +CREATE TABLE tenant_4.partitioned_table_child_1 PARTITION OF tenant_4.partitioned_table FOR VALUES FROM (1) TO (2); + +CREATE TABLE tenant_4.another_partitioned_table(a int, b text, FOREIGN KEY (a) REFERENCES tenant_4.partitioned_table(a)) PARTITION BY RANGE (a); +CREATE TABLE tenant_4.another_partitioned_table_child PARTITION OF tenant_4.another_partitioned_table FOR VALUES FROM (1) TO (2); + +-- verify that we allow creating partitioned tables in a tenant schema +SELECT COUNT(*)=1 FROM pg_dist_partition +WHERE logicalrelid = 'tenant_4.partitioned_table_child_1'::regclass AND + partmethod = 'n' AND repmodel = 's' AND colocationid = ( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'tenant_4.partitioned_table'::regclass); + +SELECT EXISTS( + SELECT 1 + FROM pg_inherits + WHERE inhrelid = 'tenant_4.partitioned_table_child_1'::regclass AND + inhparent = 'tenant_4.partitioned_table'::regclass +) AS is_partition; + +SELECT COUNT(*)=1 FROM pg_dist_partition +WHERE logicalrelid = 'tenant_4.another_partitioned_table_child'::regclass AND + partmethod = 'n' AND repmodel = 's' AND colocationid = ( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'tenant_4.another_partitioned_table'::regclass); + +SELECT EXISTS( + SELECT 1 + FROM pg_inherits + WHERE inhrelid = 'tenant_4.another_partitioned_table_child'::regclass AND + inhparent = 'tenant_4.another_partitioned_table'::regclass +) AS is_partition; + +-- verify the foreign key between parents +SELECT EXISTS( + SELECT 1 + FROM pg_constraint + WHERE conrelid = 'tenant_4.another_partitioned_table'::regclass AND + confrelid = 'tenant_4.partitioned_table'::regclass AND + contype = 'f' +) AS foreign_key_exists; + +INSERT INTO tenant_4.another_partitioned_table VALUES (1, 'a'); + +INSERT INTO tenant_4.partitioned_table VALUES (1, 'a'); +INSERT INTO tenant_4.another_partitioned_table VALUES (1, 'a'); + +CREATE SCHEMA tenant_5; +CREATE TABLE tenant_5.tbl_1(a int, b text); + +CREATE TABLE tenant_5.partitioned_table(a int, b text) PARTITION BY RANGE (a); + +-- verify that we don't allow creating a partition table that is child of a partitioned table in a different tenant schema +CREATE TABLE tenant_4.partitioned_table_child_2 PARTITION OF tenant_5.partitioned_table FOR VALUES FROM (1) TO (2); + +-- verify that we don't allow creating a local partition table that is child of a tenant partitioned table +CREATE TABLE regular_schema.local_child_table PARTITION OF tenant_5.partitioned_table FOR VALUES FROM (1) TO (2); + +SET citus.use_citus_managed_tables TO ON; +CREATE TABLE regular_schema.local_child_table PARTITION OF tenant_5.partitioned_table FOR VALUES FROM (1) TO (2); +RESET citus.use_citus_managed_tables; + +CREATE TABLE regular_schema.local_partitioned_table(a int, b text) PARTITION BY RANGE (a); + +CREATE TABLE regular_schema.citus_local_partitioned_table(a int, b text) PARTITION BY RANGE (a); +SELECT citus_add_local_table_to_metadata('regular_schema.citus_local_partitioned_table'); + +CREATE TABLE regular_schema.dist_partitioned_table(a int, b text) PARTITION BY RANGE (a); +SELECT create_distributed_table('regular_schema.dist_partitioned_table', 'a'); + +-- verify that we don't allow creating a partition table that is child of a non-tenant partitioned table +CREATE TABLE tenant_4.partitioned_table_child_2 PARTITION OF regular_schema.local_partitioned_table FOR VALUES FROM (1) TO (2); +CREATE TABLE tenant_4.partitioned_table_child_2 PARTITION OF regular_schema.citus_local_partitioned_table FOR VALUES FROM (1) TO (2); +CREATE TABLE tenant_4.partitioned_table_child_2 PARTITION OF regular_schema.dist_partitioned_table FOR VALUES FROM (1) TO (2); + +CREATE TABLE tenant_4.parent_attach_test(a int, b text) PARTITION BY RANGE (a); +CREATE TABLE tenant_4.child_attach_test(a int, b text); + +CREATE TABLE tenant_5.parent_attach_test(a int, b text) PARTITION BY RANGE (a); +CREATE TABLE tenant_5.child_attach_test(a int, b text); + +CREATE TABLE regular_schema.parent_attach_test_local(a int, b text) PARTITION BY RANGE (a); + +CREATE TABLE regular_schema.parent_attach_test_citus_local(a int, b text) PARTITION BY RANGE (a); +SELECT citus_add_local_table_to_metadata('regular_schema.parent_attach_test_citus_local'); + +CREATE TABLE regular_schema.parent_attach_test_dist(a int, b text) PARTITION BY RANGE (a); +SELECT create_distributed_table('regular_schema.parent_attach_test_dist', 'a'); + +CREATE TABLE regular_schema.child_attach_test_local(a int, b text); + +CREATE TABLE regular_schema.child_attach_test_citus_local(a int, b text); +SELECT citus_add_local_table_to_metadata('regular_schema.child_attach_test_citus_local'); + +CREATE TABLE regular_schema.child_attach_test_dist(a int, b text); +SELECT create_distributed_table('regular_schema.child_attach_test_dist', 'a'); + +-- verify that we don't allow attaching a tenant table into a tenant partitioned table, if they are not in the same schema +ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION tenant_5.child_attach_test FOR VALUES FROM (1) TO (2); + +-- verify that we don't allow attaching a non-tenant table into a tenant partitioned table +ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION regular_schema.child_attach_test_local FOR VALUES FROM (1) TO (2); +ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION regular_schema.child_attach_test_citus_local FOR VALUES FROM (1) TO (2); +ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION regular_schema.child_attach_test_dist FOR VALUES FROM (1) TO (2); + +-- verify that we don't allow attaching a tenant table into a non-tenant partitioned table +ALTER TABLE regular_schema.parent_attach_test_local ATTACH PARTITION tenant_4.child_attach_test FOR VALUES FROM (1) TO (2); +ALTER TABLE regular_schema.parent_attach_test_citus_local ATTACH PARTITION tenant_4.child_attach_test FOR VALUES FROM (1) TO (2); +ALTER TABLE regular_schema.parent_attach_test_dist ATTACH PARTITION tenant_4.child_attach_test FOR VALUES FROM (1) TO (2); + +ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION tenant_4.child_attach_test FOR VALUES FROM (1) TO (2); + +-- verify that we don't allow multi-level partitioning on tenant tables +CREATE TABLE tenant_4.multi_level_test(a int, b text) PARTITION BY RANGE (a); +ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION tenant_4.multi_level_test FOR VALUES FROM (1) TO (2); + +-- verify that we allow attaching a tenant table into a tenant partitioned table, if they are in the same schema +SELECT COUNT(*)=1 FROM pg_dist_partition +WHERE logicalrelid = 'tenant_4.parent_attach_test'::regclass AND + partmethod = 'n' AND repmodel = 's' AND colocationid = ( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'tenant_4.child_attach_test'::regclass); + +SELECT EXISTS( + SELECT 1 + FROM pg_inherits + WHERE inhrelid = 'tenant_4.child_attach_test'::regclass AND + inhparent = 'tenant_4.parent_attach_test'::regclass +) AS is_partition; + +-- errors out because shard replication factor > 1 +SET citus.shard_replication_factor TO 2; +CREATE TABLE tenant_4.tbl_3 AS SELECT 1 AS a, 'text' as b; +SET citus.shard_replication_factor TO 1; +-- verify that we allow creating tenant tables by using CREATE TABLE AS / SELECT INTO commands +CREATE TABLE tenant_4.tbl_3 AS SELECT 1 AS a, 'text' as b; +CREATE TEMP TABLE IF NOT EXISTS tenant_4.tbl_4 AS SELECT 1 as a, 'text' as b; +CREATE UNLOGGED TABLE IF NOT EXISTS tenant_4.tbl_4 AS SELECT 1 as a, 'text' as b WITH NO DATA; +-- the same command, no changes because of IF NOT EXISTS +CREATE UNLOGGED TABLE IF NOT EXISTS tenant_4.tbl_4 AS SELECT 1 as a, 'text' as b WITH NO DATA; +SELECT 1 as a, 'text' as b INTO tenant_4.tbl_5; + +-- verify we can query the newly created tenant tables +SELECT * FROM tenant_4.tbl_3; +SELECT COUNT(*) FROM tenant_4.tbl_5; + +CREATE TYPE employee_type AS (name text, salary numeric); + +-- verify that we don't allow creating tenant tables by using CREATE TABLE OF commands +CREATE TABLE tenant_4.employees OF employee_type ( + PRIMARY KEY (name), + salary WITH OPTIONS DEFAULT 1000 +); + +-- verify that we act accordingly when if not exists is used +CREATE TABLE IF NOT EXISTS tenant_4.tbl_6(a int, b text); +CREATE TABLE IF NOT EXISTS tenant_4.tbl_6(a int, b text); + +SELECT logicalrelid, partmethod + FROM pg_dist_partition + WHERE logicalrelid::text LIKE 'tenant_4.tbl%' + ORDER BY logicalrelid; + +CREATE TABLE regular_schema.local(a int, b text); + +CREATE TABLE regular_schema.citus_local(a int, b text); +SELECT citus_add_local_table_to_metadata('regular_schema.citus_local'); + +CREATE TABLE regular_schema.dist(a int, b text); +SELECT create_distributed_table('regular_schema.dist', 'a'); + +-- verify that we can create a table LIKE another table +CREATE TABLE tenant_5.test_table_like_1(LIKE tenant_5.tbl_1); -- using a table from the same schema +CREATE TABLE tenant_5.test_table_like_2(LIKE tenant_4.tbl_1); -- using a table from another schema +CREATE TABLE tenant_5.test_table_like_3(LIKE regular_schema.local); -- using a local table +CREATE TABLE tenant_5.test_table_like_4(LIKE regular_schema.citus_local); -- using a citus local table +CREATE TABLE tenant_5.test_table_like_5(LIKE regular_schema.dist); -- using a distributed table + +-- verify that all of them are converted to tenant tables +SELECT COUNT(*) = 5 +FROM pg_dist_partition +WHERE logicalrelid::text LIKE 'tenant_5.test_table_like_%' AND + partmethod = 'n' AND repmodel = 's' AND colocationid = ( + SELECT colocationid FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_5' + ); + +CREATE TABLE regular_schema.local_table_using_like(LIKE tenant_5.tbl_1); + +-- verify that regular_schema.local_table_using_like is not a tenant table +SELECT COUNT(*) = 0 FROM pg_dist_partition +WHERE logicalrelid = 'regular_schema.local_table_using_like'::regclass; + +-- verify that INHERITS syntax is not supported when creating a tenant table +CREATE TABLE tenant_5.test_table_inherits_1(x int) INHERITS (tenant_5.tbl_1); -- using a table from the same schema +CREATE TABLE tenant_5.test_table_inherits_2(x int) INHERITS (tenant_4.tbl_1); -- using a table from another schema +CREATE TABLE tenant_5.test_table_inherits_3(x int) INHERITS (regular_schema.local); -- using a local table +CREATE TABLE tenant_5.test_table_inherits_4(x int) INHERITS (regular_schema.citus_local); -- using a citus local table +CREATE TABLE tenant_5.test_table_inherits_5(x int) INHERITS (regular_schema.dist); -- using a distributed table + +-- verify that INHERITS syntax is not supported when creating a local table based on a tenant table +CREATE TABLE regular_schema.local_table_using_inherits(x int) INHERITS (tenant_5.tbl_1); + +CREATE TABLE tenant_5.tbl_2(a int, b text); + +CREATE SCHEMA "CiTuS.TeeN_108"; +ALTER SCHEMA "CiTuS.TeeN_108" RENAME TO citus_teen_proper; + +SELECT schemaid AS citus_teen_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'citus_teen_proper' \gset +SELECT colocationid AS citus_teen_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'citus_teen_proper' \gset + +SELECT result FROM run_command_on_workers($$ + SELECT schemaid INTO citus_teen_schemaid FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'citus_teen_proper' +$$); + +-- (on coordinator) verify that colocation id is set for the tenant with a weird name too +SELECT :citus_teen_colocationid > 0; + +-- (on workers) verify that the same colocation id is used on workers too +SELECT format( + 'SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=1 FROM pg_dist_schema + WHERE schemaid::regnamespace::text = ''citus_teen_proper'' AND + colocationid = %s; + $$);', +:citus_teen_colocationid) AS verify_workers_query \gset + +:verify_workers_query + +ALTER SCHEMA citus_teen_proper RENAME TO "CiTuS.TeeN_108"; + +SET citus.enable_schema_based_sharding TO OFF; + +-- Show that the tables created in tenant schemas are considered to be +-- tenant tables even if the GUC was set to off when creating the table. +CREATE TABLE tenant_5.tbl_3(a int, b text); +SELECT COUNT(*)=1 FROM pg_dist_partition WHERE logicalrelid = 'tenant_5.tbl_3'::regclass; + +SET citus.enable_schema_based_sharding TO ON; + +-- Verify that tables that belong to tenant_4 and tenant_5 are stored on +-- different worker nodes due to order we followed when creating first tenant +-- tables in each of them. +SELECT COUNT(DISTINCT(nodename, nodeport))=2 FROM citus_shards +WHERE table_name IN ('tenant_4.tbl_1'::regclass, 'tenant_5.tbl_1'::regclass); + +-- show that all the tables in tenant_4 are colocated with each other. +SELECT COUNT(DISTINCT(colocationid))=1 FROM pg_dist_partition +WHERE logicalrelid::regclass::text LIKE 'tenant_4.%'; + +-- verify the same for tenant_5 too +SELECT COUNT(DISTINCT(colocationid))=1 FROM pg_dist_partition +WHERE logicalrelid::regclass::text LIKE 'tenant_5.%'; + +SELECT schemaid AS tenant_4_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_4' \gset +SELECT colocationid AS tenant_4_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_4' \gset + +SELECT result FROM run_command_on_workers($$ + SELECT schemaid INTO tenant_4_schemaid FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_4' +$$); + +SET client_min_messages TO WARNING; + +-- Rename it to a name that contains a single quote to verify that we properly +-- escape its name when sending the command to delete the pg_dist_schema +-- entry on workers. +ALTER SCHEMA tenant_4 RENAME TO "tenant\'_4"; + +DROP SCHEMA "tenant\'_4", "CiTuS.TeeN_108" CASCADE; + +SET client_min_messages TO NOTICE; + +-- (on coordinator) Verify that dropping a tenant schema deletes the associated +-- pg_dist_schema entry and pg_dist_colocation too. +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid = :tenant_4_schemaid; +SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = :tenant_4_colocationid; + +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid = :citus_teen_schemaid; +SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = :citus_teen_colocationid; + +-- (on workers) Verify that dropping a tenant schema deletes the associated +-- pg_dist_schema entry and pg_dist_colocation too. +SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_schema + WHERE schemaid = (SELECT schemaid FROM tenant_4_schemaid) +$$); + +SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_schema + WHERE schemaid = (SELECT schemaid FROM citus_teen_schemaid) +$$); + +SELECT format( + 'SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = %s; + $$);', +:tenant_4_colocationid) AS verify_workers_query \gset + +:verify_workers_query + +SELECT result FROM run_command_on_workers($$ + DROP TABLE tenant_4_schemaid +$$); + +SELECT format( + 'SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = %s; + $$);', +:citus_teen_colocationid) AS verify_workers_query \gset + +:verify_workers_query + +SELECT result FROM run_command_on_workers($$ + DROP TABLE citus_teen_schemaid +$$); + +-- show that we don't allow colocating a Citus table with a tenant table +CREATE TABLE regular_schema.null_shard_key_1(a int, b text); +SELECT create_distributed_table('regular_schema.null_shard_key_1', null, colocate_with => 'tenant_5.tbl_2'); +SELECT create_distributed_table('regular_schema.null_shard_key_1', 'a', colocate_with => 'tenant_5.tbl_2'); + +CREATE TABLE regular_schema.null_shard_key_table_2(a int, b text); +SELECT create_distributed_table('regular_schema.null_shard_key_table_2', null); + +-- Show that we don't chose to colocate regular single-shard tables with +-- tenant tables by default. +SELECT * FROM pg_dist_schema WHERE colocationid = ( + SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'regular_schema.null_shard_key_table_2'::regclass +); + +-- save the colocation id used for tenant_5 +SELECT colocationid AS tenant_5_old_colocationid FROM pg_dist_schema +WHERE schemaid::regnamespace::text = 'tenant_5' \gset + +-- drop all the tables that belong to tenant_5 and create a new one +DROP TABLE tenant_5.tbl_1, tenant_5.tbl_2, tenant_5.tbl_3; +CREATE TABLE tenant_5.tbl_4(a int, b text); + +-- (on coordinator) verify that tenant_5 is still associated with the same colocation id +SELECT colocationid = :tenant_5_old_colocationid FROM pg_dist_schema +WHERE schemaid::regnamespace::text = 'tenant_5'; + +-- (on workers) verify that tenant_5 is still associated with the same colocation id +SELECT format( + 'SELECT result FROM run_command_on_workers($$ + SELECT colocationid = %s FROM pg_dist_schema + WHERE schemaid::regnamespace::text = ''tenant_5''; + $$);', +:tenant_5_old_colocationid) AS verify_workers_query \gset + +:verify_workers_query + +SELECT schemaid AS tenant_1_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_1' \gset +SELECT colocationid AS tenant_1_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_1' \gset + +SELECT schemaid AS tenant_2_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_2' \gset +SELECT colocationid AS tenant_2_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_2' \gset + +SELECT result FROM run_command_on_workers($$ + SELECT schemaid INTO tenant_1_schemaid FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_1' +$$); + +SELECT result FROM run_command_on_workers($$ + SELECT schemaid INTO tenant_2_schemaid FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_2' +$$); + +SET client_min_messages TO WARNING; +SET citus.enable_schema_based_sharding TO OFF; + +DROP SCHEMA tenant_1 CASCADE; + +CREATE ROLE test_non_super_user; +ALTER ROLE test_non_super_user NOSUPERUSER; + +ALTER SCHEMA tenant_2 OWNER TO non_existing_role; +ALTER SCHEMA tenant_2 OWNER TO test_non_super_user; + +SELECT pg_get_userbyid(nspowner) AS schema_owner + FROM pg_namespace + WHERE nspname = 'tenant_2'; + +select result from run_command_on_workers ($$ + SELECT pg_get_userbyid(nspowner) AS schema_owner + FROM pg_namespace + WHERE nspname = 'tenant_2' +$$); + +DROP OWNED BY test_non_super_user CASCADE; + +DROP ROLE test_non_super_user; + +SET client_min_messages TO NOTICE; + +-- (on coordinator) Verify that dropping a tenant schema always deletes +-- the associated pg_dist_schema entry even if the the schema was +-- dropped while the GUC was set to off. +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid IN (:tenant_1_schemaid, :tenant_2_schemaid); +SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid IN (:tenant_1_colocationid, :tenant_2_colocationid); + +-- (on workers) Verify that dropping a tenant schema always deletes +-- the associated pg_dist_schema entry even if the the schema was +-- dropped while the GUC was set to off. +SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_schema + WHERE schemaid IN (SELECT schemaid FROM tenant_1_schemaid UNION SELECT schemaid FROM tenant_2_schemaid) +$$); + +SELECT format( + 'SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid IN (%s, %s); + $$);', +:tenant_1_colocationid, :tenant_2_colocationid) AS verify_workers_query \gset + +:verify_workers_query + +SELECT result FROM run_command_on_workers($$ + DROP TABLE tenant_1_schemaid +$$); + +SELECT result FROM run_command_on_workers($$ + DROP TABLE tenant_2_schemaid +$$); + +SET citus.enable_schema_based_sharding TO ON; +SET client_min_messages TO NOTICE; + +-- show that all schemaid values are unique and non-null in pg_dist_schema +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid IS NULL; +SELECT (SELECT COUNT(*) FROM pg_dist_schema) = + (SELECT COUNT(DISTINCT(schemaid)) FROM pg_dist_schema); + +-- show that all colocationid values are unique and non-null in pg_dist_schema +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE colocationid IS NULL; +SELECT (SELECT COUNT(*) FROM pg_dist_schema) = + (SELECT COUNT(DISTINCT(colocationid)) FROM pg_dist_schema); + +CREATE TABLE public.cannot_be_a_tenant_table(a int, b text); + +-- show that we don't consider public schema as a tenant schema +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'public'; + +DROP TABLE public.cannot_be_a_tenant_table; + +BEGIN; + ALTER SCHEMA public RENAME TO public_renamed; + CREATE SCHEMA public; + + -- Show that we don't consider public schema as a tenant schema, + -- even if it's recreated. + SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'public'; +ROLLBACK; + +CREATE TEMPORARY TABLE temp_table(a int, b text); + +-- show that we don't consider temporary schemas as tenant schemas +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid::regnamespace::text = '%pg_temp%'; + +DROP TABLE temp_table; + +-- test creating a tenant schema and a tenant table for it in the same transaction +BEGIN; + CREATE SCHEMA tenant_7; + CREATE TABLE tenant_7.tbl_1(a int, b text); + CREATE TABLE tenant_7.tbl_2(a int, b text); + + SELECT colocationid = ( + SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_7.tbl_1'::regclass + ) + FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_7'; + + -- make sure that both tables created in tenant_7 are colocated + SELECT COUNT(DISTINCT(colocationid)) = 1 FROM pg_dist_partition + WHERE logicalrelid IN ('tenant_7.tbl_1'::regclass, 'tenant_7.tbl_2'::regclass); +COMMIT; + +-- Test creating a tenant schema and a tenant table for it in the same transaction +-- but this time rollback the transaction. +BEGIN; + CREATE SCHEMA tenant_8; + CREATE TABLE tenant_8.tbl_1(a int, b text); + CREATE TABLE tenant_8.tbl_2(a int, b text); +ROLLBACK; + +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_8'; +SELECT COUNT(*)=0 FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant_8.%'; + +-- Verify that citus.enable_schema_based_sharding and citus.use_citus_managed_tables +-- GUC don't interfere with each other when creating a table in tenant schema. +-- +-- In utility hook, we check whether the CREATE TABLE command is issued on a tenant +-- schema before checking whether citus.use_citus_managed_tables is set to ON to +-- avoid converting the table into a Citus managed table unnecessarily. +-- +-- If the CREATE TABLE command is issued on a tenant schema, we skip the check +-- for citus.use_citus_managed_tables. +SET citus.use_citus_managed_tables TO ON; +CREATE TABLE tenant_7.tbl_3(a int, b text, PRIMARY KEY(a)); +RESET citus.use_citus_managed_tables; + +-- Verify that we don't unnecessarily convert a table into a Citus managed +-- table when creating it with a pre-defined foreign key to a reference table. +CREATE TABLE reference_table(a int PRIMARY KEY); +SELECT create_reference_table('reference_table'); + +-- Notice that tenant_7.tbl_4 have foreign keys both to tenant_7.tbl_3 and +-- to reference_table. +CREATE TABLE tenant_7.tbl_4(a int REFERENCES reference_table, FOREIGN KEY(a) REFERENCES tenant_7.tbl_3(a) ON DELETE CASCADE); + +INSERT INTO tenant_7.tbl_3 VALUES (1, 'a'), (2, 'b'), (3, 'c'); +INSERT INTO reference_table VALUES (1), (2), (3); +INSERT INTO tenant_7.tbl_4 VALUES (1), (2), (3); + +DELETE FROM tenant_7.tbl_3 WHERE a < 3; +SELECT * FROM tenant_7.tbl_4 ORDER BY a; + +SELECT COUNT(*)=2 FROM pg_dist_partition +WHERE logicalrelid IN ('tenant_7.tbl_3'::regclass, 'tenant_7.tbl_4'::regclass) AND + partmethod = 'n' AND repmodel = 's' AND + colocationid = (SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_7.tbl_1'::regclass); + +CREATE TABLE local_table(a int PRIMARY KEY); + +-- fails because tenant tables cannot have foreign keys to local tables +CREATE TABLE tenant_7.tbl_5(a int REFERENCES local_table(a)); + +-- Fails because tenant tables cannot have foreign keys to tenant tables +-- that belong to different tenant schemas. +CREATE TABLE tenant_5.tbl_5(a int, b text, FOREIGN KEY(a) REFERENCES tenant_7.tbl_3(a)); + +CREATE SCHEMA tenant_9; + +SELECT schemaid AS tenant_9_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_9' \gset +SELECT colocationid AS tenant_9_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_9' \gset + +SELECT result FROM run_command_on_workers($$ + SELECT schemaid INTO tenant_9_schemaid FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_9' +$$); + +DROP SCHEMA tenant_9; + +-- (on coordinator) Make sure that dropping an empty tenant schema +-- doesn't leave any dangling entries in pg_dist_schema and +-- pg_dist_colocation. +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid = :tenant_9_schemaid; +SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = :tenant_9_colocationid; + +-- (on workers) Make sure that dropping an empty tenant schema +-- doesn't leave any dangling entries in pg_dist_schema and +-- pg_dist_colocation. +SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_schema + WHERE schemaid = (SELECT schemaid FROM tenant_9_schemaid) +$$); + +SELECT format( + 'SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = %s; + $$);', +:tenant_9_colocationid) AS verify_workers_query \gset + +:verify_workers_query + +SELECT result FROM run_command_on_workers($$ + DROP TABLE tenant_9_schemaid +$$); + +CREATE TABLE tenant_3.search_path_test(a int); +INSERT INTO tenant_3.search_path_test VALUES (1), (10); + +CREATE TABLE tenant_5.search_path_test(a int); +INSERT INTO tenant_5.search_path_test VALUES (2); + +CREATE TABLE tenant_7.search_path_test(a int); +INSERT INTO tenant_7.search_path_test VALUES (3); + +CREATE FUNCTION increment_one() +RETURNS void +LANGUAGE plpgsql +AS $$ +BEGIN + UPDATE search_path_test SET a = a + 1; +END; +$$; + +CREATE FUNCTION decrement_one() +RETURNS void +LANGUAGE plpgsql +AS $$ +BEGIN + UPDATE search_path_test SET a = a - 1; +END; +$$; + +SET search_path TO tenant_5; + +PREPARE list_tuples AS SELECT * FROM search_path_test ORDER BY a; + +SELECT * FROM search_path_test ORDER BY a; + +SET search_path TO tenant_3; +DELETE FROM search_path_test WHERE a = 1; +SELECT * FROM search_path_test ORDER BY a; +SELECT regular_schema.increment_one(); +EXECUTE list_tuples; + +SET search_path TO tenant_7; +DROP TABLE search_path_test; +SELECT * FROM pg_dist_partition WHERE logicalrelid::text = 'search_path_test'; + +SET search_path TO tenant_5; +SELECT regular_schema.decrement_one(); +EXECUTE list_tuples; + +SET search_path TO regular_schema; + +CREATE USER test_other_super_user WITH superuser; + +\c - test_other_super_user + +SET citus.enable_schema_based_sharding TO ON; +CREATE SCHEMA tenant_9; + +\c - postgres + +SET search_path TO regular_schema; +SET citus.next_shard_id TO 1930000; +SET citus.shard_count TO 32; +SET citus.shard_replication_factor TO 1; +SET client_min_messages TO NOTICE; +SET citus.enable_schema_based_sharding TO ON; + +SELECT schemaid AS tenant_9_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_9' \gset +SELECT colocationid AS tenant_9_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_9' \gset + +SELECT result FROM run_command_on_workers($$ + SELECT schemaid INTO tenant_9_schemaid FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_9' +$$); + +DROP OWNED BY test_other_super_user; + +-- (on coordinator) Make sure that dropping an empty tenant schema +-- (via DROP OWNED BY) doesn't leave any dangling entries in +-- pg_dist_schema and pg_dist_colocation. +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid = :tenant_9_schemaid; +SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = :tenant_9_colocationid; + +-- (on workers) Make sure that dropping an empty tenant schema +-- (via DROP OWNED BY) doesn't leave any dangling entries in +-- pg_dist_schema and pg_dist_colocation. +SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_schema + WHERE schemaid = (SELECT schemaid FROM tenant_9_schemaid) +$$); + +SELECT format( + 'SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = %s; + $$);', +:tenant_9_colocationid) AS verify_workers_query \gset + +:verify_workers_query + +SELECT result FROM run_command_on_workers($$ + DROP TABLE tenant_9_schemaid +$$); + +DROP USER test_other_super_user; + +CREATE ROLE test_non_super_user WITH LOGIN; +ALTER ROLE test_non_super_user NOSUPERUSER; + +GRANT CREATE ON DATABASE regression TO test_non_super_user; +SELECT result FROM run_command_on_workers($$GRANT CREATE ON DATABASE regression TO test_non_super_user$$); + +GRANT CREATE ON SCHEMA public TO test_non_super_user ; + +\c - test_non_super_user + +SET search_path TO regular_schema; +SET citus.next_shard_id TO 1940000; +SET citus.shard_count TO 32; +SET citus.shard_replication_factor TO 1; +SET client_min_messages TO NOTICE; +SET citus.enable_schema_based_sharding TO ON; + +-- test create / drop tenant schema / table + +CREATE SCHEMA tenant_10; +CREATE TABLE tenant_10.tbl_1(a int, b text); +CREATE TABLE tenant_10.tbl_2(a int, b text); + +DROP TABLE tenant_10.tbl_2; + +CREATE SCHEMA tenant_11; + +SELECT schemaid AS tenant_10_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_10' \gset +SELECT colocationid AS tenant_10_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_10' \gset + +SELECT schemaid AS tenant_11_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_11' \gset +SELECT colocationid AS tenant_11_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_11' \gset + +SELECT result FROM run_command_on_workers($$ + SELECT schemaid INTO tenant_10_schemaid FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_10' +$$); + +SELECT result FROM run_command_on_workers($$ + SELECT schemaid INTO tenant_11_schemaid FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_11' +$$); + +-- (on coordinator) Verify metadata for tenant schemas that are created via non-super-user. +SELECT COUNT(DISTINCT(schemaid))=2 FROM pg_dist_schema WHERE schemaid IN (:tenant_10_schemaid, :tenant_11_schemaid); +SELECT COUNT(DISTINCT(colocationid))=2 FROM pg_dist_colocation WHERE colocationid IN (:tenant_10_colocationid, :tenant_11_colocationid); + +-- (on workers) Verify metadata for tenant schemas that are created via non-super-user. +SELECT result FROM run_command_on_workers($$ + SELECT COUNT(DISTINCT(schemaid))=2 FROM pg_dist_schema + WHERE schemaid IN (SELECT schemaid FROM tenant_10_schemaid UNION SELECT schemaid FROM tenant_11_schemaid) +$$); + +SELECT format( + 'SELECT result FROM run_command_on_workers($$ + SELECT COUNT(DISTINCT(colocationid))=2 FROM pg_dist_colocation WHERE colocationid IN (%s, %s); + $$);', +:tenant_10_colocationid, :tenant_11_colocationid) AS verify_workers_query \gset + +:verify_workers_query + +SET client_min_messages TO WARNING; +DROP SCHEMA tenant_10, tenant_11 CASCADE; +SET client_min_messages TO NOTICE; + +-- (on coordinator) Verify that dropping a tenant schema via non-super-user +-- deletes the associated pg_dist_schema entry. +SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid IN (:tenant_10_schemaid, :tenant_11_schemaid); +SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid IN (:tenant_10_colocationid, :tenant_11_colocationid); + +-- (on workers) Verify that dropping a tenant schema via non-super-user +-- deletes the associated pg_dist_schema entry. +SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_schema + WHERE schemaid IN (SELECT schemaid FROM tenant_10_schemaid UNION SELECT schemaid FROM tenant_11_schemaid) +$$); + +SELECT format( + 'SELECT result FROM run_command_on_workers($$ + SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid IN (%s, %s); + $$);', +:tenant_10_colocationid, :tenant_11_colocationid) AS verify_workers_query \gset + +:verify_workers_query + +SELECT result FROM run_command_on_workers($$ + DROP TABLE tenant_10_schemaid +$$); + +SELECT result FROM run_command_on_workers($$ + DROP TABLE tenant_11_schemaid +$$); + +\c - postgres + +REVOKE CREATE ON DATABASE regression FROM test_non_super_user; +SELECT result FROM run_command_on_workers($$REVOKE CREATE ON DATABASE regression FROM test_non_super_user$$); + +REVOKE CREATE ON SCHEMA public FROM test_non_super_user; + +DROP ROLE test_non_super_user; + +\c - - - :worker_1_port + +-- test creating a tenant table from workers +CREATE TABLE tenant_3.tbl_1(a int, b text); + +-- test creating a tenant schema from workers +SET citus.enable_schema_based_sharding TO ON; +CREATE SCHEMA worker_tenant_schema; +SET citus.enable_schema_based_sharding TO OFF; + +-- Enable the GUC on workers to make sure that the CREATE SCHEMA/ TABLE +-- commands that we send to workers don't recursively try creating a +-- tenant schema / table. +ALTER SYSTEM SET citus.enable_schema_based_sharding TO ON; +SELECT pg_reload_conf(); + +\c - - - :worker_2_port + +ALTER SYSTEM SET citus.enable_schema_based_sharding TO ON; +SELECT pg_reload_conf(); + +-- Verify that citus_internal_unregister_tenant_schema_globally is a no-op +-- on workers. +SELECT citus_internal_unregister_tenant_schema_globally('tenant_3'::regnamespace, 'tenant_3'); + +\c - - - :master_port + +SET search_path TO regular_schema; +SET citus.next_shard_id TO 1950000; +SET citus.shard_count TO 32; +SET citus.shard_replication_factor TO 1; +SET client_min_messages TO NOTICE; + +CREATE TABLE tenant_3.tbl_1(a int, b text); + +SET citus.enable_schema_based_sharding TO ON; +CREATE SCHEMA tenant_6; +CREATE TABLE tenant_6.tbl_1(a int, b text); + +-- verify pg_dist_partition entries for tenant_3.tbl_1 and tenant_6.tbl_1 +SELECT COUNT(*)=2 FROM pg_dist_partition +WHERE logicalrelid IN ('tenant_3.tbl_1'::regclass, 'tenant_6.tbl_1'::regclass) AND + partmethod = 'n' AND repmodel = 's' AND colocationid > 0; + +\c - - - :worker_1_port + +ALTER SYSTEM RESET citus.enable_schema_based_sharding; +SELECT pg_reload_conf(); + +\c - - - :worker_2_port + +ALTER SYSTEM RESET citus.enable_schema_based_sharding; +SELECT pg_reload_conf(); + +\c - - - :master_port +SET search_path TO regular_schema; + +CREATE TABLE type_sing(a INT); + +-- errors out because shard_replication_factor = 2 +SELECT create_distributed_table('type_sing', NULL, colocate_with:='none'); + +SET citus.shard_replication_factor TO 1; +SELECT create_distributed_table('type_sing', NULL, colocate_with:='none'); + +SET citus.enable_schema_based_sharding TO ON; +CREATE SCHEMA type_sch; +CREATE TABLE type_sch.tbl (a INT); + +SELECT table_name, citus_table_type FROM public.citus_tables WHERE table_name::text LIKE 'type_%'; +SELECT table_name, citus_table_type FROM citus_shards WHERE table_name::text LIKE 'type_%' AND nodeport IN (:worker_1_port, :worker_2_port); + +RESET citus.enable_schema_based_sharding; + +-- test citus_schemas +SET citus.enable_schema_based_sharding TO ON; +CREATE USER citus_schema_role SUPERUSER; +SET ROLE citus_schema_role; +CREATE SCHEMA citus_sch1; +CREATE TABLE citus_sch1.tbl1(a INT); +CREATE TABLE citus_sch1.tbl2(a INT); +RESET ROLE; + +CREATE SCHEMA citus_sch2; +CREATE TABLE citus_sch2.tbl1(a INT); +SET citus.enable_schema_based_sharding TO OFF; + +INSERT INTO citus_sch1.tbl1 SELECT * FROM generate_series(1, 10000); +INSERT INTO citus_sch1.tbl2 SELECT * FROM generate_series(1, 5000); + +INSERT INTO citus_sch2.tbl1 SELECT * FROM generate_series(1, 12000); + +SELECT + cs.schema_name, + cs.colocation_id = ctc.colocation_id AS correct_colocation_id, + cs.schema_size = ctc.calculated_size AS correct_size, + cs.schema_owner +FROM public.citus_schemas cs +JOIN +( + SELECT + c.relnamespace, ct.colocation_id, + pg_size_pretty(sum(citus_total_relation_size(ct.table_name))) AS calculated_size + FROM public.citus_tables ct, pg_class c + WHERE ct.table_name::oid = c.oid + GROUP BY 1, 2 +) ctc ON cs.schema_name = ctc.relnamespace +WHERE cs.schema_name::text LIKE 'citus\_sch_' +ORDER BY cs.schema_name::text; + +-- test empty schema and empty tables +SET citus.enable_schema_based_sharding TO ON; +CREATE SCHEMA citus_empty_sch1; + +CREATE SCHEMA citus_empty_sch2; +CREATE TABLE citus_empty_sch2.tbl1(a INT); +SET citus.enable_schema_based_sharding TO OFF; + +SELECT schema_name, schema_size FROM public.citus_schemas +WHERE schema_name::text LIKE 'citus\_empty\_sch_' ORDER BY schema_name::text; + +-- test with non-privileged role +CREATE USER citus_schema_nonpri; +SET ROLE citus_schema_nonpri; + +SET client_min_messages TO ERROR; +SELECT schema_name, colocation_id > 0 AS colocation_id_visible, schema_size IS NOT NULL AS schema_size_visible, schema_owner +FROM public.citus_schemas WHERE schema_name::text LIKE 'citus\_sch_' ORDER BY schema_name::text; + +RESET client_min_messages; +RESET ROLE; + +-- test using citus_tables from workers +\c - - - :worker_1_port +SELECT schema_name, colocation_id > 0 AS colocation_id_visible, schema_size IS NOT NULL AS schema_size_visible, schema_owner +FROM public.citus_schemas WHERE schema_name::text LIKE 'citus\_sch_' ORDER BY schema_name::text; +\c - - - :master_port +SET search_path TO regular_schema; + +-- test we handle create schema with authorization properly for distributed schema +SET citus.enable_schema_based_sharding TO ON; +CREATE ROLE authschema; +CREATE SCHEMA AUTHORIZATION authschema; +SET citus.enable_schema_based_sharding TO OFF; + +SELECT result FROM run_command_on_all_nodes($$ + SELECT COUNT(*)=1 + FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'authschema'; +$$); + +SET client_min_messages TO WARNING; +DROP SCHEMA regular_schema, tenant_3, tenant_5, tenant_7, tenant_6, type_sch, citus_sch1, citus_sch2, citus_empty_sch1, citus_empty_sch2, authschema CASCADE; +DROP ROLE citus_schema_role, citus_schema_nonpri, authschema; + +SELECT citus_remove_node('localhost', :master_port); diff --git a/src/test/regress/sql/sequential_modifications.sql b/src/test/regress/sql/sequential_modifications.sql index 79e0a1211..3d56a20ee 100644 --- a/src/test/regress/sql/sequential_modifications.sql +++ b/src/test/regress/sql/sequential_modifications.sql @@ -25,7 +25,7 @@ $$ DECLARE result bool; BEGIN - SELECT tx_count = worker_count FROM (SELECT count(*) as tx_count FROM pg_dist_transaction WHERE gid LIKE 'citus_%_' || pg_backend_pid() || '%_%') as s1, (SELECT count(*) as worker_count FROM pg_dist_node WHERE noderole = 'primary') as s2 INTO result; + SELECT tx_count = worker_count FROM (SELECT count(*) as tx_count FROM pg_dist_transaction WHERE gid LIKE 'citus_%_' || pg_backend_pid() || '%_%') as s1, (SELECT count(*) as worker_count FROM pg_dist_node WHERE noderole = 'primary' AND groupid <> 0 ) as s2 INTO result; RETURN result; END; $$ diff --git a/src/test/regress/sql/set_operation_and_local_tables.sql b/src/test/regress/sql/set_operation_and_local_tables.sql index cbc024729..ab044d019 100644 --- a/src/test/regress/sql/set_operation_and_local_tables.sql +++ b/src/test/regress/sql/set_operation_and_local_tables.sql @@ -97,6 +97,5 @@ SELECT * FROM ((SELECT x FROM test) UNION (SELECT x FROM (SELECT x FROM local_te -- repartition is recursively planned before the set operation (SELECT x FROM test) INTERSECT (SELECT t1.x FROM test as t1, test as t2 WHERE t1.x = t2.y LIMIT 2) INTERSECT (((SELECT x FROM local_test) UNION ALL (SELECT x FROM test)) INTERSECT (SELECT i FROM generate_series(0, 100) i)) ORDER BY 1 DESC; - -RESET client_min_messages; +SET client_min_messages TO WARNING; DROP SCHEMA recursive_set_local CASCADE; diff --git a/src/test/regress/sql/set_operations.sql b/src/test/regress/sql/set_operations.sql index 9f66f4dfb..633b5c0b5 100644 --- a/src/test/regress/sql/set_operations.sql +++ b/src/test/regress/sql/set_operations.sql @@ -200,5 +200,5 @@ SELECT * FROM set_view_recursive_second ORDER BY 1,2; SELECT * FROM (SELECT * FROM test UNION SELECT * FROM test_not_colocated) u ORDER BY 1,2; SELECT * FROM (SELECT * FROM test UNION ALL SELECT * FROM test_not_colocated) u ORDER BY 1,2; -RESET client_min_messages; +SET client_min_messages TO WARNING; DROP SCHEMA recursive_union CASCADE; diff --git a/src/test/regress/sql/shard_rebalancer.sql b/src/test/regress/sql/shard_rebalancer.sql index da4259f5b..d64fb6826 100644 --- a/src/test/regress/sql/shard_rebalancer.sql +++ b/src/test/regress/sql/shard_rebalancer.sql @@ -5,6 +5,14 @@ SET citus.next_shard_id TO 433000; SET citus.propagate_session_settings_for_loopback_connection TO ON; +-- Because of historic reasons this test was written in a way that assumes that +-- by_shard_count is the default strategy. +SELECT citus_set_default_rebalance_strategy('by_shard_count'); +-- Lower the minimum disk size that a shard group is considered as. Otherwise +-- we need to create shards of more than 100MB. +ALTER SYSTEM SET citus.rebalancer_by_disk_size_base_cost = 0; +SELECT pg_reload_conf(); + CREATE TABLE ref_table_test(a int primary key); SELECT create_reference_table('ref_table_test'); CREATE TABLE dist_table_test(a int primary key); @@ -13,7 +21,9 @@ CREATE TABLE postgres_table_test(a int primary key); -- make sure that all rebalance operations works fine when -- reference tables are replicated to the coordinator +SET client_min_messages TO ERROR; SELECT 1 FROM master_add_node('localhost', :master_port, groupId=>0); +RESET client_min_messages; -- should just be noops even if we add the coordinator to the pg_dist_node SELECT rebalance_table_shards('dist_table_test'); @@ -1226,7 +1236,7 @@ DROP TABLE tab; -- we don't need the coordinator on pg_dist_node anymore SELECT 1 FROM master_remove_node('localhost', :master_port); -SELECT public.wait_until_metadata_sync(30000); +SELECT public.wait_until_metadata_sync(60000); -- -- Make sure that rebalance_table_shards() and replicate_table_shards() replicate @@ -1497,6 +1507,61 @@ SELECT sh.logicalrelid, pl.nodeport DROP TABLE single_shard_colocation_1a, single_shard_colocation_1b, single_shard_colocation_1c, single_shard_colocation_2a, single_shard_colocation_2b CASCADE; +-- test the same with coordinator shouldhaveshards = false and shard_count = 2 +-- so that the shard allowed node count would be 2 when rebalancing +-- for such cases, we only count the nodes that are allowed for shard placements +UPDATE pg_dist_node SET shouldhaveshards=false WHERE nodeport = :master_port; + +create table two_shard_colocation_1a (a int primary key); +create table two_shard_colocation_1b (a int primary key); +SET citus.shard_replication_factor = 1; + +select create_distributed_table('two_shard_colocation_1a','a', colocate_with => 'none', shard_count => 2); +select create_distributed_table('two_shard_colocation_1b','a',colocate_with=>'two_shard_colocation_1a'); + +create table two_shard_colocation_2a (a int primary key); +create table two_shard_colocation_2b (a int primary key); +select create_distributed_table('two_shard_colocation_2a','a', colocate_with => 'none', shard_count => 2); +select create_distributed_table('two_shard_colocation_2b','a',colocate_with=>'two_shard_colocation_2a'); + +-- move shards of colocation group 1 to worker1 +SELECT citus_move_shard_placement(sh.shardid, 'localhost', :worker_2_port, 'localhost', :worker_1_port) + FROM pg_dist_shard sh JOIN pg_dist_shard_placement pl ON sh.shardid = pl.shardid + WHERE sh.logicalrelid = 'two_shard_colocation_1a'::regclass + AND pl.nodeport = :worker_2_port + LIMIT 1; +-- move shards of colocation group 2 to worker2 +SELECT citus_move_shard_placement(sh.shardid, 'localhost', :worker_1_port, 'localhost', :worker_2_port) + FROM pg_dist_shard sh JOIN pg_dist_shard_placement pl ON sh.shardid = pl.shardid + WHERE sh.logicalrelid = 'two_shard_colocation_2a'::regclass + AND pl.nodeport = :worker_1_port + LIMIT 1; + +-- current state: +-- coordinator: [] +-- worker 1: [1_1, 1_2] +-- worker 2: [2_1, 2_2] +SELECT sh.logicalrelid, pl.nodeport + FROM pg_dist_shard sh JOIN pg_dist_shard_placement pl ON sh.shardid = pl.shardid + WHERE sh.logicalrelid::text IN ('two_shard_colocation_1a', 'two_shard_colocation_1b', 'two_shard_colocation_2a', 'two_shard_colocation_2b') + ORDER BY sh.logicalrelid, pl.nodeport; + +-- If we take the coordinator into account, the rebalancer considers this as balanced and does nothing (shard_count < worker_count) +-- but because the coordinator is not allowed for shards, rebalancer will distribute each colocation group to both workers +select rebalance_table_shards(shard_transfer_mode:='block_writes'); + +-- final state: +-- coordinator: [] +-- worker 1: [1_1, 2_1] +-- worker 2: [1_2, 2_2] +SELECT sh.logicalrelid, pl.nodeport + FROM pg_dist_shard sh JOIN pg_dist_shard_placement pl ON sh.shardid = pl.shardid + WHERE sh.logicalrelid::text IN ('two_shard_colocation_1a', 'two_shard_colocation_1b', 'two_shard_colocation_2a', 'two_shard_colocation_2b') + ORDER BY sh.logicalrelid, pl.nodeport; + +-- cleanup +DROP TABLE two_shard_colocation_1a, two_shard_colocation_1b, two_shard_colocation_2a, two_shard_colocation_2b CASCADE; + -- verify we detect if one of the tables do not have a replica identity or primary key -- and error out in case of shard transfer mode = auto SELECT 1 FROM citus_remove_node('localhost', :worker_2_port); @@ -1512,6 +1577,9 @@ select 1 from citus_add_node('localhost', :worker_2_port); select rebalance_table_shards(); DROP TABLE table_with_primary_key, table_without_primary_key; +SELECT citus_set_default_rebalance_strategy('by_disk_size'); +ALTER SYSTEM RESET citus.rebalancer_by_disk_size_base_cost; +SELECT pg_reload_conf(); \c - - - :worker_1_port SET citus.enable_ddl_propagation TO OFF; REVOKE ALL ON SCHEMA public FROM testrole; diff --git a/src/test/regress/sql/single_node.sql b/src/test/regress/sql/single_node.sql index 3419025af..55f244b16 100644 --- a/src/test/regress/sql/single_node.sql +++ b/src/test/regress/sql/single_node.sql @@ -63,8 +63,60 @@ ALTER SYSTEM RESET citus.local_shared_pool_size; ALTER SYSTEM RESET citus.max_cached_conns_per_worker; SELECT pg_reload_conf(); +CREATE TABLE single_node_nullkey_c1(a int, b int); +SELECT create_distributed_table('single_node_nullkey_c1', null, colocate_with=>'none', distribution_type=>null); + +CREATE TABLE single_node_nullkey_c2(a int, b int); +SELECT create_distributed_table('single_node_nullkey_c2', null, colocate_with=>'none', distribution_type=>null); + +-- created on different colocation groups .. +SELECT +( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'single_node.single_node_nullkey_c1'::regclass +) +!= +( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'single_node.single_node_nullkey_c2'::regclass +); + +-- .. but both are associated to coordinator +SELECT groupid = 0 FROM pg_dist_placement +WHERE shardid = ( + SELECT shardid FROM pg_dist_shard + WHERE logicalrelid = 'single_node.single_node_nullkey_c1'::regclass +); + +SELECT groupid = 0 FROM pg_dist_placement +WHERE shardid = ( + SELECT shardid FROM pg_dist_shard + WHERE logicalrelid = 'single_node.single_node_nullkey_c2'::regclass +); + +-- try creating a single-shard table from a shard relation +SELECT shardid AS round_robin_test_c1_shard_id FROM pg_dist_shard WHERE logicalrelid = 'single_node.single_node_nullkey_c1'::regclass \gset +SELECT create_distributed_table('single_node_nullkey_c1_' || :round_robin_test_c1_shard_id , null, colocate_with=>'none', distribution_type=>null); + +-- create a tenant schema on single node setup +SET citus.enable_schema_based_sharding TO ON; + +CREATE SCHEMA tenant_1; +CREATE TABLE tenant_1.tbl_1 (a int); + +-- verify that we recorded tenant_1 in pg_dist_schema +SELECT COUNT(*)=1 FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_1'; + +-- verify that tenant_1.tbl_1 is recorded in pg_dist_partition, as a single-shard table +SELECT COUNT(*)=1 FROM pg_dist_partition +WHERE logicalrelid = 'tenant_1.tbl_1'::regclass AND + partmethod = 'n' AND repmodel = 's' AND colocationid IS NOT NULL; + +RESET citus.enable_schema_based_sharding; + SET client_min_messages TO WARNING; -DROP TABLE failover_to_local; +DROP TABLE failover_to_local, single_node_nullkey_c1, single_node_nullkey_c2; +DROP SCHEMA tenant_1 CASCADE; RESET client_min_messages; -- so that we don't have to update rest of the test output diff --git a/src/test/regress/sql/single_shard_table_prep.sql b/src/test/regress/sql/single_shard_table_prep.sql new file mode 100644 index 000000000..5a9a3ac01 --- /dev/null +++ b/src/test/regress/sql/single_shard_table_prep.sql @@ -0,0 +1,14 @@ +ALTER FUNCTION create_distributed_table RENAME TO create_distributed_table_internal; + +CREATE OR REPLACE FUNCTION pg_catalog.create_distributed_table(table_name regclass, + distribution_column text, + distribution_type citus.distribution_type DEFAULT 'hash', + colocate_with text DEFAULT 'default', + shard_count int DEFAULT NULL) +RETURNS void +LANGUAGE plpgsql +AS $function$ +BEGIN + PERFORM create_distributed_table_internal(table_name, NULL, NULL, colocate_with, NULL); +END; +$function$; diff --git a/src/test/regress/sql/single_shard_table_udfs.sql b/src/test/regress/sql/single_shard_table_udfs.sql new file mode 100644 index 000000000..7566f53e3 --- /dev/null +++ b/src/test/regress/sql/single_shard_table_udfs.sql @@ -0,0 +1,667 @@ +CREATE SCHEMA null_dist_key_udfs; +SET search_path TO null_dist_key_udfs; + +SET citus.next_shard_id TO 1820000; +SET citus.shard_count TO 32; +SET citus.shard_replication_factor TO 1; +ALTER SEQUENCE pg_catalog.pg_dist_colocationid_seq RESTART 198000; +SET client_min_messages TO ERROR; +SELECT 1 FROM citus_add_node('localhost', :master_port, groupid=>0); +RESET client_min_messages; + +CREATE FUNCTION get_referencing_relation_id_list(Oid) +RETURNS SETOF Oid +LANGUAGE C STABLE STRICT +AS 'citus', $$get_referencing_relation_id_list$$; + +CREATE FUNCTION get_referenced_relation_id_list(Oid) +RETURNS SETOF Oid +LANGUAGE C STABLE STRICT +AS 'citus', $$get_referenced_relation_id_list$$; + +CREATE OR REPLACE FUNCTION get_foreign_key_connected_relations(IN table_name regclass) +RETURNS SETOF RECORD +LANGUAGE C STRICT +AS 'citus', $$get_foreign_key_connected_relations$$; + +CREATE OR REPLACE FUNCTION citus_get_all_dependencies_for_object(classid oid, objid oid, objsubid int) +RETURNS SETOF RECORD +LANGUAGE C STRICT +AS 'citus', $$citus_get_all_dependencies_for_object$$; + +CREATE OR REPLACE FUNCTION citus_get_dependencies_for_object(classid oid, objid oid, objsubid int) +RETURNS SETOF RECORD +LANGUAGE C STRICT +AS 'citus', $$citus_get_dependencies_for_object$$; + +CREATE OR REPLACE FUNCTION pg_catalog.is_citus_depended_object(oid,oid) +RETURNS bool +LANGUAGE C +AS 'citus', $$is_citus_depended_object$$; + +CREATE FUNCTION shards_colocated(bigint, bigint) +RETURNS bool +AS 'citus' +LANGUAGE C STRICT; + +-- test some other udf's with single shard tables +CREATE TABLE null_dist_key_table(a int); +SELECT create_distributed_table('null_dist_key_table', null, colocate_with=>'none', distribution_type=>null); + +SELECT truncate_local_data_after_distributing_table('null_dist_key_table'); + +-- should work -- +-- insert some data & create an index for table size udf's +INSERT INTO null_dist_key_table VALUES (1), (2), (3); +CREATE INDEX null_dist_key_idx ON null_dist_key_table(a); + +SELECT citus_table_size('null_dist_key_table'); +SELECT citus_total_relation_size('null_dist_key_table'); +SELECT citus_relation_size('null_dist_key_table'); +SELECT shard_name, shard_size FROM pg_catalog.citus_shard_sizes(), citus_shards +WHERE shardid = shard_id AND shard_name LIKE '%null_dist_key_table%' AND nodeport IN (:worker_1_port, :worker_2_port); + +BEGIN; + SELECT lock_relation_if_exists('null_dist_key_table', 'ACCESS SHARE'); + SELECT count(*) FROM pg_locks where relation='null_dist_key_table'::regclass; +COMMIT; + +SELECT partmethod, repmodel FROM pg_dist_partition WHERE logicalrelid = 'null_dist_key_table'::regclass; +SELECT master_get_table_ddl_events('null_dist_key_table'); + +SELECT column_to_column_name(logicalrelid, partkey) +FROM pg_dist_partition WHERE logicalrelid = 'null_dist_key_table'::regclass; + +SELECT column_name_to_column('null_dist_key_table', 'a'); + +SELECT master_update_shard_statistics(shardid) +FROM (SELECT shardid FROM pg_dist_shard WHERE logicalrelid='null_dist_key_table'::regclass) as shardid; + +SELECT truncate_local_data_after_distributing_table('null_dist_key_table'); + +-- should return a single element array that only includes its own shard id +SELECT shardid=unnest(get_colocated_shard_array(shardid)) +FROM (SELECT shardid FROM pg_dist_shard WHERE logicalrelid='null_dist_key_table'::regclass) as shardid; + +BEGIN; + SELECT master_remove_partition_metadata('null_dist_key_table'::regclass::oid, 'null_dist_key_udfs', 'null_dist_key_table'); + + -- should print 0 + select count(*) from pg_dist_partition where logicalrelid='null_dist_key_table'::regclass; +ROLLBACK; + +SELECT master_create_empty_shard('null_dist_key_table'); + +-- return true +SELECT citus_table_is_visible('null_dist_key_table'::regclass::oid); + +-- return false +SELECT relation_is_a_known_shard('null_dist_key_table'); + +-- return | false | true | +SELECT citus_table_is_visible(tableName::regclass::oid), relation_is_a_known_shard(tableName::regclass) +FROM (SELECT tableName FROM pg_catalog.pg_tables WHERE tablename LIKE 'null_dist_key_table%') as tableName; + +-- should fail, maybe support in the future +SELECT create_reference_table('null_dist_key_table'); +SELECT create_distributed_table('null_dist_key_table', 'a'); +SELECT create_distributed_table_concurrently('null_dist_key_table', 'a'); +SELECT citus_add_local_table_to_metadata('null_dist_key_table'); + +-- test altering distribution column, fails for single shard tables +SELECT alter_distributed_table('null_dist_key_table', distribution_column := 'a'); + +-- test altering shard count, fails for single shard tables +SELECT alter_distributed_table('null_dist_key_table', shard_count := 6); + +-- test shard splitting udf, fails for single shard tables +SELECT nodeid AS worker_1_node FROM pg_dist_node WHERE nodeport=:worker_1_port \gset +SELECT nodeid AS worker_2_node FROM pg_dist_node WHERE nodeport=:worker_2_port \gset +SELECT citus_split_shard_by_split_points( + 1820000, + ARRAY['-1073741826'], + ARRAY[:worker_1_node, :worker_2_node], + 'block_writes'); + +SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE '%null_dist_key_table%'; +-- test alter_table_set_access_method and verify it doesn't change the colocation id +SELECT alter_table_set_access_method('null_dist_key_table', 'columnar'); +SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE '%null_dist_key_table%'; + +-- undistribute +SELECT undistribute_table('null_dist_key_table'); +-- verify that the metadata is gone +SELECT COUNT(*) = 0 FROM pg_dist_partition WHERE logicalrelid::text LIKE '%null_dist_key_table%'; +SELECT COUNT(*) = 0 FROM pg_dist_placement WHERE shardid IN (SELECT shardid FROM pg_dist_shard WHERE logicalrelid::text LIKE '%null_dist_key_table%'); +SELECT COUNT(*) = 0 FROM pg_dist_shard WHERE logicalrelid::text LIKE '%null_dist_key_table%'; + +-- create 7 single shard tables, 3 of them are colocated, for testing shard moves / rebalance on them +CREATE TABLE single_shard_table_col1_1 (a INT PRIMARY KEY); +CREATE TABLE single_shard_table_col1_2 (a TEXT PRIMARY KEY); +CREATE TABLE single_shard_table_col1_3 (a TIMESTAMP PRIMARY KEY); +CREATE TABLE single_shard_table_col2_1 (a INT PRIMARY KEY); +CREATE TABLE single_shard_table_col3_1 (a INT PRIMARY KEY); +CREATE TABLE single_shard_table_col4_1 (a INT PRIMARY KEY); +CREATE TABLE single_shard_table_col5_1 (a INT PRIMARY KEY); +SELECT create_distributed_table('single_shard_table_col1_1', null, colocate_with=>'none'); +SELECT create_distributed_table('single_shard_table_col1_2', null, colocate_with=>'single_shard_table_col1_1'); +SELECT create_distributed_table('single_shard_table_col1_3', null, colocate_with=>'single_shard_table_col1_2'); +SELECT create_distributed_table('single_shard_table_col2_1', null, colocate_with=>'none'); +SELECT create_distributed_table('single_shard_table_col3_1', null, colocate_with=>'none'); +SELECT create_distributed_table('single_shard_table_col4_1', null, colocate_with=>'none'); +SELECT create_distributed_table('single_shard_table_col5_1', null, colocate_with=>'none'); + +-- initial status +SELECT shardid, nodeport FROM pg_dist_shard_placement WHERE shardid > 1820000 ORDER BY shardid; + +-- errors out because streaming replicated +SELECT citus_copy_shard_placement(1820005, 'localhost', :worker_1_port, 'localhost', :worker_2_port); +SELECT master_copy_shard_placement(1820005, 'localhost', :worker_1_port, 'localhost', :worker_2_port); +SELECT citus_copy_shard_placement(1820005, :worker_1_node, :worker_2_node); + +-- no changes because it's already balanced +SELECT rebalance_table_shards(rebalance_strategy := 'by_shard_count'); + +-- same placements +SELECT shardid, nodeport FROM pg_dist_shard_placement WHERE shardid > 1820000 ORDER BY shardid; + +-- manually move 2 shard from 2 colocation groups to make the cluster unbalanced +SELECT citus_move_shard_placement(1820005, 'localhost', :worker_1_port, 'localhost', :worker_2_port); +SELECT citus_move_shard_placement(1820007, :worker_1_node, :worker_2_node); + +-- all placements are located on worker 2 +SELECT shardid, nodeport FROM pg_dist_shard_placement WHERE shardid > 1820000 ORDER BY shardid; + +-- move some of them to worker 1 to balance the cluster +SELECT rebalance_table_shards(); + +-- the final status, balanced +SELECT shardid, nodeport FROM pg_dist_shard_placement WHERE shardid > 1820000 ORDER BY shardid; + +-- verify we didn't break any colocations +SELECT logicalrelid, colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE '%single_shard_table_col%' ORDER BY colocationid, logicalrelid; + +-- drop preexisting tables +-- we can remove the drop commands once the issue is fixed: https://github.com/citusdata/citus/issues/6948 +SET client_min_messages TO ERROR; +DROP TABLE IF EXISTS public.lineitem, public.orders, public.customer_append, public.part_append, public.supplier_single_shard, + public.events, public.users, public.lineitem_hash_part, public.lineitem_subquery, public.orders_hash_part, + public.orders_subquery, public.unlogged_table CASCADE; +DROP SCHEMA IF EXISTS with_basics, subquery_and_ctes CASCADE; +DROP TABLE IF EXISTS public.users_table, public.events_table, public.agg_results, public.agg_results_second, public.agg_results_third, public.agg_results_fourth, public.agg_results_window CASCADE; +-- drain node +SELECT citus_drain_node('localhost', :worker_2_port, 'block_writes'); +SELECT citus_set_node_property('localhost', :worker_2_port, 'shouldhaveshards', true); +RESET client_min_messages; + +-- see the plan for moving 4 shards, 3 of them are in the same colocation group +SELECT * FROM get_rebalance_table_shards_plan(); + +-- move some of them to worker 2 to balance the cluster +SELECT 1 FROM citus_rebalance_start(); + +-- stop it +SELECT * FROM citus_rebalance_stop(); + +-- show rebalance status, see the cancelled job for two moves +SELECT state, details FROM citus_rebalance_status(); + +-- start again +SELECT 1 FROM citus_rebalance_start(); + +-- show rebalance status, scheduled a job for two moves +SELECT state, details FROM citus_rebalance_status(); + +-- wait for rebalance to be completed +SELECT * FROM citus_rebalance_wait(); + +-- the final status, balanced +SELECT shardid, nodeport FROM pg_dist_shard_placement WHERE shardid > 1820000 ORDER BY shardid; + +-- test update_distributed_table_colocation +CREATE TABLE update_col_1 (a INT); +CREATE TABLE update_col_2 (a INT); +CREATE TABLE update_col_3 (a INT); + +-- create colocated single shard distributed tables, so the shards will be +-- in the same worker node +SELECT create_distributed_table ('update_col_1', null, colocate_with:='none'); +SELECT create_distributed_table ('update_col_2', null, colocate_with:='update_col_1'); + +-- now create a third single shard distributed table that is not colocated, +-- with the new colocation id the new table will be in the other worker node +SELECT create_distributed_table ('update_col_3', null, colocate_with:='none'); + +-- make sure nodes are correct and test shards_colocated UDF +SELECT c1.nodeport = c2.nodeport AS same_node, shards_colocated(c1.shardid, c2.shardid) +FROM citus_shards c1, citus_shards c2, pg_dist_node p1, pg_dist_node p2 +WHERE c1.table_name::text = 'update_col_1' AND c2.table_name::text = 'update_col_2' AND + p1.nodeport = c1.nodeport AND p2.nodeport = c2.nodeport AND + p1.noderole = 'primary' AND p2.noderole = 'primary'; + +SELECT c1.nodeport = c2.nodeport AS same_node, shards_colocated(c1.shardid, c2.shardid) +FROM citus_shards c1, citus_shards c2, pg_dist_node p1, pg_dist_node p2 +WHERE c1.table_name::text = 'update_col_1' AND c2.table_name::text = 'update_col_3' AND + p1.nodeport = c1.nodeport AND p2.nodeport = c2.nodeport AND + p1.noderole = 'primary' AND p2.noderole = 'primary'; + +-- and the update_col_1 and update_col_2 are colocated +SELECT c1.colocation_id = c2.colocation_id AS colocated +FROM public.citus_tables c1, public.citus_tables c2 +WHERE c1.table_name::text = 'update_col_1' AND c2.table_name::text = 'update_col_2'; + +-- break the colocation +SELECT update_distributed_table_colocation('update_col_2', colocate_with:='none'); + +SELECT c1.colocation_id = c2.colocation_id AS colocated +FROM public.citus_tables c1, public.citus_tables c2 +WHERE c1.table_name::text = 'update_col_1' AND c2.table_name::text = 'update_col_2'; + +-- test shards_colocated UDF with shards in same node but different colocation groups +SELECT shards_colocated(c1.shardid, c2.shardid) +FROM citus_shards c1, citus_shards c2, pg_dist_node p1, pg_dist_node p2 +WHERE c1.table_name::text = 'update_col_1' AND c2.table_name::text = 'update_col_2' AND + p1.nodeport = c1.nodeport AND p2.nodeport = c2.nodeport AND + p1.noderole = 'primary' AND p2.noderole = 'primary'; + +-- re-colocate, the shards were already in the same node +SELECT update_distributed_table_colocation('update_col_2', colocate_with:='update_col_1'); + +SELECT c1.colocation_id = c2.colocation_id AS colocated +FROM public.citus_tables c1, public.citus_tables c2 +WHERE c1.table_name::text = 'update_col_1' AND c2.table_name::text = 'update_col_2'; + +-- update_col_1 and update_col_3 are not colocated, because they are not in the some node +SELECT c1.colocation_id = c2.colocation_id AS colocated +FROM public.citus_tables c1, public.citus_tables c2 +WHERE c1.table_name::text = 'update_col_1' AND c2.table_name::text = 'update_col_3'; + +-- they should not be able to be colocated since the shards are in different nodes +SELECT update_distributed_table_colocation('update_col_3', colocate_with:='update_col_1'); + +SELECT c1.colocation_id = c2.colocation_id AS colocated +FROM public.citus_tables c1, public.citus_tables c2 +WHERE c1.table_name::text = 'update_col_1' AND c2.table_name::text = 'update_col_3'; + +-- hash distributed and single shard distributed tables cannot be colocated +CREATE TABLE update_col_4 (a INT); +SELECT create_distributed_table ('update_col_4', 'a', colocate_with:='none'); + +SELECT update_distributed_table_colocation('update_col_1', colocate_with:='update_col_4'); +SELECT update_distributed_table_colocation('update_col_4', colocate_with:='update_col_1'); + +-- test columnar UDFs +CREATE TABLE columnar_tbl (a INT) USING COLUMNAR; +SELECT create_distributed_table('columnar_tbl', NULL, colocate_with:='none'); + +SELECT * FROM columnar.options WHERE relation = 'columnar_tbl'::regclass; +SELECT alter_columnar_table_set('columnar_tbl', compression_level => 2); +SELECT * FROM columnar.options WHERE relation = 'columnar_tbl'::regclass; +SELECT alter_columnar_table_reset('columnar_tbl', compression_level => true); +SELECT * FROM columnar.options WHERE relation = 'columnar_tbl'::regclass; + +SELECT columnar_internal.upgrade_columnar_storage(c.oid) +FROM pg_class c, pg_am a +WHERE c.relam = a.oid AND amname = 'columnar' AND relname = 'columnar_tbl'; + +SELECT columnar_internal.downgrade_columnar_storage(c.oid) +FROM pg_class c, pg_am a +WHERE c.relam = a.oid AND amname = 'columnar' AND relname = 'columnar_tbl'; + +CREATE OR REPLACE FUNCTION columnar_storage_info( + rel regclass, + version_major OUT int4, + version_minor OUT int4, + storage_id OUT int8, + reserved_stripe_id OUT int8, + reserved_row_number OUT int8, + reserved_offset OUT int8) + STRICT + LANGUAGE c AS 'citus', $$columnar_storage_info$$; + +SELECT version_major, version_minor, reserved_stripe_id, reserved_row_number, reserved_offset FROM columnar_storage_info('columnar_tbl'); + +SELECT columnar.get_storage_id(oid) = storage_id FROM pg_class, columnar_storage_info('columnar_tbl') WHERE relname = 'columnar_tbl'; + + +-- test time series functions +CREATE TABLE part_tbl (a DATE) PARTITION BY RANGE (a); +CREATE TABLE part_tbl_1 PARTITION OF part_tbl FOR VALUES FROM ('2000-01-01') TO ('2010-01-01'); +CREATE TABLE part_tbl_2 PARTITION OF part_tbl FOR VALUES FROM ('2020-01-01') TO ('2030-01-01'); + +SELECT create_distributed_table('part_tbl', NULL, colocate_with:='none'); + +SELECT * FROM time_partitions WHERE parent_table::text = 'part_tbl'; + +SELECT time_partition_range('part_tbl_2'); + +SELECT get_missing_time_partition_ranges('part_tbl', INTERVAL '10 years', '2050-01-01', '2000-01-01'); + +SELECT create_time_partitions('part_tbl', INTERVAL '10 years', '2050-01-01', '2000-01-01'); + +CALL drop_old_time_partitions('part_tbl', '2030-01-01'); + +SELECT * FROM time_partitions WHERE parent_table::text = 'part_tbl'; + +-- test locking shards +CREATE TABLE lock_tbl_1 (a INT); +SELECT create_distributed_table('lock_tbl_1', NULL, colocate_with:='none'); + +CREATE TABLE lock_tbl_2 (a INT); +SELECT create_distributed_table('lock_tbl_2', NULL, colocate_with:='none'); + +BEGIN; +SELECT lock_shard_metadata(3, array_agg(distinct(shardid))) +FROM citus_shards WHERE table_name::text = 'lock_tbl_1'; + +SELECT lock_shard_metadata(5, array_agg(distinct(shardid))) +FROM citus_shards WHERE table_name::text LIKE 'lock\_tbl\__'; + +SELECT table_name, classid, mode, granted +FROM pg_locks, public.citus_tables +WHERE + locktype = 'advisory' AND + table_name::text LIKE 'lock\_tbl\__' AND + objid = colocation_id + ORDER BY 1, 3; +END; + + +BEGIN; +SELECT lock_shard_resources(3, array_agg(distinct(shardid))) +FROM citus_shards WHERE table_name::text = 'lock_tbl_1'; + +SELECT lock_shard_resources(5, array_agg(distinct(shardid))) +FROM citus_shards WHERE table_name::text LIKE 'lock\_tbl\__'; + +SELECT locktype, table_name, mode, granted +FROM pg_locks, citus_shards, pg_dist_node +WHERE + objid = shardid AND + table_name::text LIKE 'lock\_tbl\__' AND + citus_shards.nodeport = pg_dist_node.nodeport AND + noderole = 'primary' + ORDER BY 2, 3; +END; + +-- test foreign key UDFs +CREATE TABLE fkey_s1 (a INT UNIQUE); +CREATE TABLE fkey_r (a INT UNIQUE); + +CREATE TABLE fkey_s2 (x INT, y INT); +CREATE TABLE fkey_s3 (x INT, y INT); + +SELECT create_distributed_table('fkey_s1', NULL, colocate_with:='none'); +SELECT create_reference_table('fkey_r'); + +SELECT create_distributed_table('fkey_s2', NULL, colocate_with:='fkey_s1'); +SELECT create_distributed_table('fkey_s3', NULL, colocate_with:='fkey_s1'); + +ALTER TABLE fkey_s2 ADD CONSTRAINT f1 FOREIGN KEY (x) REFERENCES fkey_s1 (a); +ALTER TABLE fkey_s2 ADD CONSTRAINT f2 FOREIGN KEY (y) REFERENCES fkey_r (a); + +ALTER TABLE fkey_s3 ADD CONSTRAINT f3 FOREIGN KEY (x) REFERENCES fkey_s1 (a); +ALTER TABLE fkey_s3 ADD CONSTRAINT f4 FOREIGN KEY (y) REFERENCES fkey_r (a); + +SELECT get_referencing_relation_id_list::regclass::text FROM get_referencing_relation_id_list('fkey_s1'::regclass) ORDER BY 1; +SELECT get_referenced_relation_id_list::regclass::text FROM get_referenced_relation_id_list('fkey_s2'::regclass) ORDER BY 1; + +SELECT oid::regclass::text FROM get_foreign_key_connected_relations('fkey_s1'::regclass) AS f(oid oid) ORDER BY 1; + +--test dependency functions +CREATE TYPE dep_type AS (a INT); +CREATE TABLE dep_tbl(a INT, b dep_type); +SELECT create_distributed_table('dep_tbl', NULL, colocate_with:='none'); +CREATE VIEW dep_view AS SELECT * FROM dep_tbl; + +-- find all the dependencies of table dep_tbl +SELECT + pg_identify_object(t.classid, t.objid, t.objsubid) +FROM + (SELECT * FROM pg_get_object_address('table', '{dep_tbl}', '{}')) as addr +JOIN LATERAL + citus_get_all_dependencies_for_object(addr.classid, addr.objid, addr.objsubid) as t(classid oid, objid oid, objsubid int) +ON TRUE + ORDER BY 1; + +-- find all the dependencies of view dep_view +SELECT + pg_identify_object(t.classid, t.objid, t.objsubid) +FROM + (SELECT * FROM pg_get_object_address('view', '{dep_view}', '{}')) as addr +JOIN LATERAL + citus_get_all_dependencies_for_object(addr.classid, addr.objid, addr.objsubid) as t(classid oid, objid oid, objsubid int) +ON TRUE + ORDER BY 1; + +-- find non-distributed dependencies of table dep_tbl +SELECT + pg_identify_object(t.classid, t.objid, t.objsubid) +FROM + (SELECT * FROM pg_get_object_address('table', '{dep_tbl}', '{}')) as addr +JOIN LATERAL + citus_get_dependencies_for_object(addr.classid, addr.objid, addr.objsubid) as t(classid oid, objid oid, objsubid int) +ON TRUE + ORDER BY 1; + +SET citus.hide_citus_dependent_objects TO true; +CREATE TABLE citus_dep_tbl (a noderole); +SELECT create_distributed_table('citus_dep_tbl', NULL, colocate_with:='none'); + +SELECT is_citus_depended_object('pg_class'::regclass, 'citus_dep_tbl'::regclass); +RESET citus.hide_citus_dependent_objects; + +-- test replicate_reference_tables +SET client_min_messages TO WARNING; +DROP SCHEMA null_dist_key_udfs CASCADE; +RESET client_min_messages; +CREATE SCHEMA null_dist_key_udfs; +SET search_path TO null_dist_key_udfs; + +SELECT citus_remove_node('localhost', :worker_2_port); + +CREATE TABLE rep_ref (a INT UNIQUE); +SELECT create_reference_table('rep_ref'); + +CREATE TABLE rep_sing (a INT); +SELECT create_distributed_table('rep_sing', NULL, colocate_with:='none'); + +ALTER TABLE rep_sing ADD CONSTRAINT rep_fkey FOREIGN KEY (a) REFERENCES rep_ref(a); + +SELECT 1 FROM citus_add_node('localhost', :worker_2_port); + +SELECT count(*) FROM citus_shards WHERE table_name = 'rep_ref'::regclass AND nodeport = :worker_2_port; +SELECT replicate_reference_tables('block_writes'); +SELECT count(*) FROM citus_shards WHERE table_name = 'rep_ref'::regclass AND nodeport = :worker_2_port; + +-- test fix_partition_shard_index_names +SET citus.next_shard_id TO 3820000; +CREATE TABLE part_tbl_sing (dist_col int, another_col int, partition_col timestamp) PARTITION BY RANGE (partition_col); +SELECT create_distributed_table('part_tbl_sing', NULL, colocate_with:='none'); + +-- create a partition with a long name and another with a short name +CREATE TABLE partition_table_with_very_long_name PARTITION OF part_tbl_sing FOR VALUES FROM ('2018-01-01') TO ('2019-01-01'); +CREATE TABLE p PARTITION OF part_tbl_sing FOR VALUES FROM ('2019-01-01') TO ('2020-01-01'); + +-- create an index on parent table +-- we will see that it doesn't matter whether we name the index on parent or not +-- indexes auto-generated on partitions will not use this name +-- SELECT fix_partition_shard_index_names('dist_partitioned_table') will be executed +-- automatically at the end of the CREATE INDEX command +CREATE INDEX short ON part_tbl_sing USING btree (another_col, partition_col); + +SELECT tablename, indexname FROM pg_indexes WHERE schemaname = 'null_dist_key_udfs' AND tablename SIMILAR TO 'p%' ORDER BY 1, 2; + +SELECT nodeport AS part_tbl_sing_port +FROM citus_shards +WHERE table_name = 'part_tbl_sing'::regclass AND + nodeport IN (:worker_1_port, :worker_2_port) \gset + +\c - - - :part_tbl_sing_port +-- the names are generated correctly +-- shard id has been appended to all index names which didn't end in shard id +-- this goes in line with Citus's way of naming indexes of shards: always append shardid to the end +SELECT tablename, indexname FROM pg_indexes WHERE schemaname = 'null_dist_key_udfs' AND tablename SIMILAR TO 'p%\_\d*' ORDER BY 1, 2; + +\c - - - :master_port +SET search_path TO null_dist_key_udfs; + +--test isolate_tenant_to_new_shard +CREATE TABLE iso_tbl (a INT); +SET citus.shard_replication_factor TO 1; +SELECT create_distributed_table('iso_tbl', NULL, colocate_with:='none'); +SELECT isolate_tenant_to_new_shard('iso_tbl', 5); + +-- test replicate_table_shards +CREATE TABLE rep_tbl (a INT); +SELECT create_distributed_table('rep_tbl', NULL, colocate_with:='none'); +SELECT replicate_table_shards('rep_tbl'); + +-- test debug_equality_expression +CREATE FUNCTION debug_equality_expression(regclass) +RETURNS cstring +AS 'citus' +LANGUAGE C STRICT; + +CREATE TABLE debug_tbl (a INT); +SELECT create_distributed_table ('debug_tbl', NULL, colocate_with:='none'); +SELECT debug_equality_expression('debug_tbl'::regclass); + +-- test partition_column_id +CREATE FUNCTION partition_column_id(regclass) +RETURNS smallint +AS 'citus' +LANGUAGE C STRICT; + +CREATE TABLE partcol_tbl (a INT); +SELECT create_distributed_table ('partcol_tbl', NULL, colocate_with:='none'); +SELECT partition_column_id('partcol_tbl'::regclass); + +-- test citus_shard_cost_by_disk_size +CREATE TABLE size_tbl_dist (a INT, b TEXT); +SELECT create_distributed_table('size_tbl_dist', 'a', shard_count:=4, colocate_with:='none'); + +CREATE TABLE size_tbl_single (a INT, b TEXT); +SELECT create_distributed_table('size_tbl_single', NULL, colocate_with:='none'); + +INSERT INTO size_tbl_dist SELECT 1, '1234567890' FROM generate_series(1, 10000); +INSERT INTO size_tbl_single SELECT 1, '1234567890' FROM generate_series(1, 10000); + +SELECT citus_shard_cost_by_disk_size(c1.shardid) = citus_shard_cost_by_disk_size(c2.shardid) AS equal_cost +FROM citus_shards c1, citus_shards c2 +WHERE c1.table_name::TEXT = 'size_tbl_dist' AND c2.table_name::TEXT = 'size_tbl_single' +ORDER BY c1.shard_size DESC +LIMIT 1; + +-- test update statistics UDFs +CREATE TABLE update_tbl_stat (a INT, b TEXT); +SELECT create_distributed_table('update_tbl_stat', NULL, colocate_with:='none'); + +SELECT shardid AS update_tbl_stat_shard +FROM citus_shards +WHERE table_name::TEXT = 'update_tbl_stat' +LIMIT 1 \gset + +SELECT shardlength > 0 FROM pg_dist_shard_placement WHERE shardid = :update_tbl_stat_shard LIMIT 1; + +INSERT INTO update_tbl_stat SELECT 1, '1234567890' FROM generate_series(1, 10000); + +SELECT shardlength > 0 FROM pg_dist_shard_placement WHERE shardid = :update_tbl_stat_shard LIMIT 1; + +SELECT citus_update_table_statistics('update_tbl_stat'); + +SELECT shardlength > 0 FROM pg_dist_shard_placement WHERE shardid = :update_tbl_stat_shard LIMIT 1; + +CREATE TABLE update_shard_stat (a INT, b TEXT); +SELECT create_distributed_table('update_shard_stat', NULL, colocate_with:='none'); + +SELECT shardid AS update_shard_stat_shard +FROM citus_shards +WHERE table_name::TEXT = 'update_shard_stat' +LIMIT 1 \gset + +SELECT shardlength > 0 FROM pg_dist_shard_placement WHERE shardid = :update_shard_stat_shard LIMIT 1; + +INSERT INTO update_shard_stat SELECT 1, '1234567890' FROM generate_series(1, 10000); + +SELECT shardlength > 0 FROM pg_dist_shard_placement WHERE shardid = :update_shard_stat_shard LIMIT 1; + +SELECT 1 FROM citus_update_shard_statistics(:update_shard_stat_shard); + +SELECT shardlength > 0 FROM pg_dist_shard_placement WHERE shardid = :update_shard_stat_shard LIMIT 1; + +-- test citus clock +SET citus.enable_cluster_clock TO ON; + +CREATE TABLE clock_single(a INT); +SELECT create_distributed_table('clock_single', NULL, colocate_with:='none'); + +SELECT citus_get_node_clock() AS nc1 \gset +SELECT citus_get_node_clock() AS nc2 \gset +SELECT citus_get_node_clock() AS nc3 \gset + +SELECT citus_is_clock_after(:'nc2', :'nc1'); +SELECT citus_is_clock_after(:'nc3', :'nc2'); + +BEGIN; +SELECT citus_get_node_clock() AS nc4 \gset +COPY clock_single FROM STDIN; +1 +2 +\. +SELECT citus_get_node_clock() AS nc5 \gset +END; + +SELECT citus_is_clock_after(:'nc4', :'nc3'); +SELECT citus_is_clock_after(:'nc5', :'nc4'); + +BEGIN; +SELECT citus_get_transaction_clock(); +END; + +-- Transaction with single shard table access +SELECT nodeport AS clock_shard_nodeport FROM citus_shards +WHERE table_name::text = 'clock_single' AND nodeport IN (:worker_1_port, :worker_2_port) \gset + +BEGIN; +COPY clock_single FROM STDIN; +1 +2 +\. +SELECT get_current_transaction_id() \gset tid +SET client_min_messages TO DEBUG1; +-- Capture the transaction timestamp +SELECT citus_get_transaction_clock() as txnclock \gset +COMMIT; + +-- Check to see if the clock is persisted in the sequence. +SELECT result as logseq from run_command_on_workers($$SELECT last_value FROM pg_dist_clock_logical_seq$$) +WHERE nodeport = :clock_shard_nodeport \gset +SELECT cluster_clock_logical(:'txnclock') as txnlog \gset +SELECT :logseq = :txnlog; + +BEGIN; +COPY clock_single FROM STDIN; +1 +2 +\. +SELECT get_current_transaction_id() \gset tid +SET client_min_messages TO DEBUG1; +-- Capture the transaction timestamp +SELECT citus_get_transaction_clock() as txnclock \gset +ROLLBACK; + +SELECT result as logseq from run_command_on_workers($$SELECT last_value FROM pg_dist_clock_logical_seq$$) +WHERE nodeport = :clock_shard_nodeport \gset +SELECT cluster_clock_logical(:'txnclock') as txnlog \gset +SELECT :logseq = :txnlog; + +-- test table with space in its name in citus_shards +CREATE TABLE "t b l" (a INT); +SELECT create_distributed_table('"t b l"', NULL, colocate_with:='none'); + +SELECT table_name, shard_size FROM citus_shards +WHERE table_name = '"t b l"'::regclass AND nodeport IN (:worker_1_port, :worker_2_port); + +SET client_min_messages TO WARNING; +DROP SCHEMA null_dist_key_udfs CASCADE; diff --git a/src/test/regress/sql/stat_statements.sql b/src/test/regress/sql/stat_statements.sql index 546a5aefa..5afed9215 100644 --- a/src/test/regress/sql/stat_statements.sql +++ b/src/test/regress/sql/stat_statements.sql @@ -3,12 +3,7 @@ -- -- tests citus_stat_statements functionality -SHOW server_version \gset -SELECT substring(:'server_version', '\d+')::int >= 14 AS server_version_ge_14 -\gset -\if :server_version_ge_14 SET compute_query_id = 'on'; -\endif -- check if pg_stat_statements is available SELECT name FROM pg_available_extensions WHERE name = 'pg_stat_statements'; @@ -50,11 +45,7 @@ SELECT create_distributed_table('test','a'); insert into test values(1); select query, calls from citus_stat_statements(); -\if :server_version_ge_14 SET compute_query_id = 'off'; -\else -set citus.stat_statements_track = 'none'; -\endif -- for pg >= 14, since compute_query_id is off, this insert -- shouldn't be tracked @@ -64,11 +55,7 @@ insert into test values(1); select query, calls from citus_stat_statements(); -\if :server_version_ge_14 SET compute_query_id = 'on'; -\else -RESET citus.stat_statements_track; -\endif SELECT citus_stat_statements_reset(); @@ -290,6 +277,4 @@ DROP TABLE stat_test_text, stat_test_bigint, stat_test_bigint_other, stat_test_r DROP FUNCTION normalize_query_string(text); -\if :server_version_ge_14 SET compute_query_id = 'off'; -\endif diff --git a/src/test/regress/sql/subquery_and_cte.sql b/src/test/regress/sql/subquery_and_cte.sql index 47bfe7184..1644c5fcc 100644 --- a/src/test/regress/sql/subquery_and_cte.sql +++ b/src/test/regress/sql/subquery_and_cte.sql @@ -510,7 +510,6 @@ RAISE '(%/3) failed to execute one of the tasks', errors_received; END; $$; -SET client_min_messages TO DEFAULT; - +SET client_min_messages TO WARNING; DROP SCHEMA subquery_and_ctes CASCADE; SET search_path TO public; diff --git a/src/test/regress/sql/subquery_append.sql b/src/test/regress/sql/subquery_append.sql index 4210f61ef..ed456fc19 100644 --- a/src/test/regress/sql/subquery_append.sql +++ b/src/test/regress/sql/subquery_append.sql @@ -87,4 +87,5 @@ SELECT count(*) FROM append_table WHERE extra = 1; UPDATE append_table a sET extra = 1 FROM append_table b WHERE a.key = b.key; END; +SET client_min_messages TO WARNING; DROP SCHEMA subquery_append CASCADE; diff --git a/src/test/regress/sql/subquery_basics.sql b/src/test/regress/sql/subquery_basics.sql index cfc02521f..0acd584fe 100644 --- a/src/test/regress/sql/subquery_basics.sql +++ b/src/test/regress/sql/subquery_basics.sql @@ -391,6 +391,7 @@ WHERE -- sublinks in the targetlist are not supported SELECT (SELECT id FROM dist WHERE dist.id > d1.id GROUP BY id) FROM ref FULL JOIN dist d1 USING (id); +SET client_min_messages TO WARNING; DROP TABLE dist; DROP TABLE ref; DROP TABLE local; diff --git a/src/test/regress/sql/subquery_view.sql b/src/test/regress/sql/subquery_view.sql index 8f57ef5a3..e5bc08c0f 100644 --- a/src/test/regress/sql/subquery_view.sql +++ b/src/test/regress/sql/subquery_view.sql @@ -434,6 +434,17 @@ EXPLAIN (COSTS OFF) WITH cte AS ( ) SELECT * FROM reference_table JOIN cte USING (text_col); $Q$); +CREATE TABLE dist_table(text_col text, int_col int); +SELECT create_distributed_table('dist_table', 'text_col'); + +SELECT public.coordinator_plan_with_subplans($Q$ +EXPLAIN (COSTS OFF) WITH cte AS ( + SELECT application_name AS text_col + FROM pg_stat_activity +) SELECT * FROM dist_table JOIN cte USING (text_col); +$Q$); + + CREATE OR REPLACE VIEW view_on_views AS SELECT pg_stat_activity.application_name, pg_locks.pid FROM pg_stat_activity, pg_locks; SELECT public.coordinator_plan_with_subplans($Q$ @@ -443,5 +454,13 @@ EXPLAIN (COSTS OFF) WITH cte AS ( ) SELECT * FROM reference_table JOIN cte USING (text_col); $Q$); +SELECT public.coordinator_plan_with_subplans($Q$ +EXPLAIN (COSTS OFF) WITH cte AS ( + SELECT application_name AS text_col + FROM view_on_views +) SELECT * FROM dist_table JOIN cte USING (text_col); +$Q$); + +SET client_min_messages TO WARNING; DROP SCHEMA subquery_view CASCADE; SET search_path TO public; diff --git a/src/test/regress/sql/union_pushdown.sql b/src/test/regress/sql/union_pushdown.sql index 884d93600..57099f060 100644 --- a/src/test/regress/sql/union_pushdown.sql +++ b/src/test/regress/sql/union_pushdown.sql @@ -1109,6 +1109,5 @@ SELECT k, COUNT(*) FROM v GROUP BY k ORDER BY k; $$); - -RESET client_min_messages; +SET client_min_messages TO WARNING; DROP SCHEMA union_pushdown CASCADE; diff --git a/src/test/regress/sql/upgrade_basic_after.sql b/src/test/regress/sql/upgrade_basic_after.sql index 03c218a06..f2fc61769 100644 --- a/src/test/regress/sql/upgrade_basic_after.sql +++ b/src/test/regress/sql/upgrade_basic_after.sql @@ -3,24 +3,24 @@ BEGIN; -- We have the tablename filter to avoid adding an alternative output for when the coordinator is in metadata vs when not SELECT * FROM pg_indexes WHERE schemaname = 'upgrade_basic' and tablename NOT LIKE 'r_%' ORDER BY tablename; -SELECT nextval('pg_dist_shardid_seq') = MAX(shardid)+1 FROM pg_dist_shard; -SELECT nextval('pg_dist_placement_placementid_seq') = MAX(placementid)+1 FROM pg_dist_placement; -SELECT nextval('pg_dist_groupid_seq') = MAX(groupid)+1 FROM pg_dist_node; -SELECT nextval('pg_dist_node_nodeid_seq') = MAX(nodeid)+1 FROM pg_dist_node; -SELECT nextval('pg_dist_colocationid_seq') = MAX(colocationid)+1 FROM pg_dist_colocation; +SELECT nextval('pg_dist_shardid_seq') > MAX(shardid) FROM pg_dist_shard; +SELECT nextval('pg_dist_placement_placementid_seq') > MAX(placementid) FROM pg_dist_placement; +SELECT nextval('pg_dist_groupid_seq') > MAX(groupid) FROM pg_dist_node; +SELECT nextval('pg_dist_node_nodeid_seq') > MAX(nodeid) FROM pg_dist_node; +SELECT nextval('pg_dist_colocationid_seq') > MAX(colocationid) FROM pg_dist_colocation; -- while testing sequences on pg_dist_cleanup, they return null in pg upgrade schedule -- but return a valid value in citus upgrade schedule -- that's why we accept both NULL and MAX()+1 here SELECT CASE WHEN MAX(operation_id) IS NULL THEN true - ELSE nextval('pg_dist_operationid_seq') = MAX(operation_id)+1 + ELSE nextval('pg_dist_operationid_seq') > MAX(operation_id) END AS check_operationid FROM pg_dist_cleanup; SELECT CASE WHEN MAX(record_id) IS NULL THEN true - ELSE nextval('pg_dist_cleanup_recordid_seq') = MAX(record_id)+1 + ELSE nextval('pg_dist_cleanup_recordid_seq') > MAX(record_id) END AS check_recordid FROM pg_dist_cleanup; SELECT nextval('pg_dist_background_job_job_id_seq') > COALESCE(MAX(job_id), 0) FROM pg_dist_background_job; diff --git a/src/test/regress/sql/upgrade_citus_finish_citus_upgrade.sql b/src/test/regress/sql/upgrade_citus_finish_citus_upgrade.sql index a326fb0a4..8d0405ea6 100644 --- a/src/test/regress/sql/upgrade_citus_finish_citus_upgrade.sql +++ b/src/test/regress/sql/upgrade_citus_finish_citus_upgrade.sql @@ -1,5 +1,12 @@ -- Citus upgrades are finished by calling a procedure +-- Note that pg_catalog.citus_finish_citus_upgrade() behaves differently +-- when last upgrade citus version is less than 11 +-- so we have two alternative outputs for this test +\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` +SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 11 +AS upgrade_test_old_citus_version_lt_11_0; + -- this is a transactional procedure, so rollback should be fine BEGIN; CALL citus_finish_citus_upgrade(); diff --git a/src/test/regress/sql/upgrade_columnar_metapage_after.sql b/src/test/regress/sql/upgrade_columnar_metapage_after.sql deleted file mode 100644 index d015d0b0d..000000000 --- a/src/test/regress/sql/upgrade_columnar_metapage_after.sql +++ /dev/null @@ -1,78 +0,0 @@ -\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` -SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int >= 10 AND - substring(:'upgrade_test_old_citus_version', 'v\d+\.(\d+)\.\d+')::int >= 0 -AS upgrade_test_old_citus_version_ge_10_0; -\gset -\if :upgrade_test_old_citus_version_ge_10_0 -\else -\q -\endif - --- it's not the best practice to define this here, but we don't want to include --- columnar_test_helpers in upgrade test schedule -CREATE OR REPLACE FUNCTION columnar_storage_info( - rel regclass, - version_major OUT int4, - version_minor OUT int4, - storage_id OUT int8, - reserved_stripe_id OUT int8, - reserved_row_number OUT int8, - reserved_offset OUT int8) -STRICT -LANGUAGE c AS 'citus', 'columnar_storage_info'; - -CREATE VIEW columnar_table_stripe_info AS -SELECT columnar_table_storageids.relname relname, - columnar.stripe.stripe_num stripe_num, - columnar.stripe.row_count row_count, - columnar.stripe.first_row_number first_row_number -FROM columnar.stripe, -( - SELECT c.oid relid, c.relname relname, (columnar_storage_info(c.oid)).storage_id relstorageid - FROM pg_class c, pg_am a - WHERE c.relam = a.oid AND amname = 'columnar' -) columnar_table_storageids -WHERE relstorageid = columnar.stripe.storage_id; - -SET search_path TO upgrade_columnar_metapage, public; - --- show that first_row_number values are equal to MAX(row_count) * stripe_num + COLUMNAR_FIRST_ROW_NUMBER -SELECT * FROM columnar_table_stripe_info ORDER BY relname, stripe_num; - --- should work since we upgrade metapages when upgrading schema version -INSERT INTO columnar_table_1 VALUES (3); - --- state of stripe metadata for columnar_table_1 after post-upgrade insert -SELECT * FROM columnar_table_stripe_info WHERE relname = 'columnar_table_1' ORDER BY stripe_num; - --- show that all columnar relation's metapage's are upgraded to "2.0" -SELECT count(*)=0 -FROM (SELECT (columnar_storage_info(c.oid)).* t - FROM pg_class c, pg_am a - WHERE c.relam = a.oid AND amname = 'columnar') t -WHERE t.version_major != 2 and t.version_minor != 0; - --- print metapage for two of the tables -SELECT version_major, version_minor, reserved_stripe_id, reserved_row_number - FROM columnar_storage_info('columnar_table_1'); -SELECT version_major, version_minor, reserved_stripe_id, reserved_row_number - FROM columnar_storage_info('columnar_table_2'); - --- show that no_data_columnar_table also has metapage after upgrade -SELECT version_major, version_minor, reserved_stripe_id, reserved_row_number - FROM columnar_storage_info('no_data_columnar_table'); - --- table is already upgraded, make sure that upgrade_columnar_metapage is no-op -SELECT columnar_internal.upgrade_columnar_storage(c.oid) -FROM pg_class c, pg_am a -WHERE c.relam = a.oid AND amname = 'columnar' and relname = 'columnar_table_2'; - -SELECT version_major, version_minor, reserved_stripe_id, reserved_row_number - FROM columnar_storage_info('columnar_table_2'); - -VACUUM FULL columnar_table_2; - --- print metapage and stripe metadata after post-upgrade vacuum full -SELECT version_major, version_minor, reserved_stripe_id, reserved_row_number - FROM columnar_storage_info('columnar_table_2'); -SELECT * FROM columnar_table_stripe_info WHERE relname = 'columnar_table_2' ORDER BY stripe_num; diff --git a/src/test/regress/sql/upgrade_columnar_metapage_before.sql b/src/test/regress/sql/upgrade_columnar_metapage_before.sql deleted file mode 100644 index 34e6d4fa4..000000000 --- a/src/test/regress/sql/upgrade_columnar_metapage_before.sql +++ /dev/null @@ -1,26 +0,0 @@ -\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` -SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int >= 10 AND - substring(:'upgrade_test_old_citus_version', 'v\d+\.(\d+)\.\d+')::int >= 0 -AS upgrade_test_old_citus_version_ge_10_0; -\gset -\if :upgrade_test_old_citus_version_ge_10_0 -\else -\q -\endif - -CREATE SCHEMA upgrade_columnar_metapage; -SET search_path TO upgrade_columnar_metapage, public; - -CREATE TABLE columnar_table_1(a INT, b INT) USING columnar; -INSERT INTO columnar_table_1 SELECT i FROM generate_series(160001, 320000) i; - -CREATE TABLE columnar_table_2(b INT) USING columnar; -SELECT alter_columnar_table_set('columnar_table_2', - chunk_group_row_limit => 1000, - stripe_row_limit => 1000); -INSERT INTO columnar_table_2 SELECT i FROM generate_series(1600, 3500) i; - -CREATE TABLE columnar_table_3(b INT) USING columnar; -INSERT INTO columnar_table_3 VALUES (1), (2); - -CREATE TABLE no_data_columnar_table(a INT, b INT, c TEXT) USING columnar; diff --git a/src/test/regress/sql/upgrade_distributed_triggers_after.sql b/src/test/regress/sql/upgrade_distributed_triggers_after.sql index 681f1896b..181833594 100644 --- a/src/test/regress/sql/upgrade_distributed_triggers_after.sql +++ b/src/test/regress/sql/upgrade_distributed_triggers_after.sql @@ -10,9 +10,9 @@ -- SHOW server_version \gset -SELECT substring(:'server_version', '\d+')::int = 15 AS server_version_eq_15 +SELECT substring(:'server_version', '\d+')::int = 15 AND EXISTS (SELECT * FROM pg_namespace WHERE nspname = 'upgrade_distributed_triggers') AS is_14_15_pg_upgrade \gset -\if :server_version_eq_15 +\if :is_14_15_pg_upgrade \else \q \endif diff --git a/src/test/regress/sql/upgrade_partition_constraints_after.sql b/src/test/regress/sql/upgrade_partition_constraints_after.sql deleted file mode 100644 index bf18d6ca4..000000000 --- a/src/test/regress/sql/upgrade_partition_constraints_after.sql +++ /dev/null @@ -1,25 +0,0 @@ --- run this test only when old citus version is earlier than 10.0 -\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` -SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 10 -AS upgrade_test_old_citus_version_lt_10_0; -\gset -\if :upgrade_test_old_citus_version_lt_10_0 -\else -\q -\endif - --- test cases for #3970 -SET search_path = test_3970; - ---5. add a partition --- This command will fail as the child table has a wrong constraint name -CREATE TABLE part_table_p202009 PARTITION OF part_table FOR VALUES FROM ('2020-09-01 00:00:00') TO ('2020-10-01 00:00:00'); - --- fix constraint names on partitioned table shards -SELECT fix_pre_citus10_partitioned_table_constraint_names('part_table'::regclass); - ---5. add a partition -CREATE TABLE part_table_p202009 PARTITION OF part_table FOR VALUES FROM ('2020-09-01 00:00:00') TO ('2020-10-01 00:00:00'); - -RESET search_path; -DROP SCHEMA test_3970 CASCADE; diff --git a/src/test/regress/sql/upgrade_partition_constraints_before.sql b/src/test/regress/sql/upgrade_partition_constraints_before.sql deleted file mode 100644 index a64f8c0e8..000000000 --- a/src/test/regress/sql/upgrade_partition_constraints_before.sql +++ /dev/null @@ -1,39 +0,0 @@ --- run this test only when old citus version is earlier than 10.0 -\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` -SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 10 -AS upgrade_test_old_citus_version_lt_10_0; -\gset -\if :upgrade_test_old_citus_version_lt_10_0 -\else -\q -\endif - --- test cases for #3970 -CREATE SCHEMA test_3970; -SET search_path = test_3970; - ---1. create a partitioned table -CREATE TABLE part_table ( - work_ymdt timestamp without time zone NOT NULL, - seq bigint NOT NULL, - my_seq bigint NOT NULL, - work_memo character varying(150), - CONSTRAINT work_memo_check CHECK ((octet_length((work_memo)::text) <= 150)) -) -PARTITION BY RANGE (work_ymdt); - ---2. perform create_distributed_table -SELECT create_distributed_table('part_table', 'seq'); - ---3. add a partition -CREATE TABLE part_table_p202008 PARTITION OF part_table FOR VALUES FROM ('2020-08-01 00:00:00') TO ('2020-09-01 00:00:00'); - ---4. add a check constraint -ALTER TABLE part_table ADD CONSTRAINT my_seq CHECK (my_seq > 0); - ---5. add a partition --- This command will fail as the child table has a wrong constraint name -CREATE TABLE part_table_p202009 PARTITION OF part_table FOR VALUES FROM ('2020-09-01 00:00:00') TO ('2020-10-01 00:00:00'); - --- Add another constraint with a long name that will get truncated with a hash -ALTER TABLE part_table ADD CONSTRAINT ck_012345678901234567890123456789012345678901234567890123456789 CHECK (my_seq > 0); diff --git a/src/test/regress/sql/upgrade_pg_dist_object_test_after.sql b/src/test/regress/sql/upgrade_pg_dist_object_test_after.sql deleted file mode 100644 index 49926f6fb..000000000 --- a/src/test/regress/sql/upgrade_pg_dist_object_test_after.sql +++ /dev/null @@ -1,28 +0,0 @@ --- run this test only when old citus version is earlier than 10.0 -\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` -SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 10 -AS upgrade_test_old_citus_version_lt_10_0; -\gset -\if :upgrade_test_old_citus_version_lt_10_0 -\else -\q -\endif - --- drop objects from previous test (uprade_basic_after.sql) for a clean test --- drop upgrade_basic schema and switch back to public schema -SET search_path to public; -DROP SCHEMA upgrade_basic CASCADE; - --- as we updated citus to available version, --- "isn" extension --- "new_schema" schema --- "public" schema --- "fooschema" schema --- "footype" type (under schema 'fooschema') --- will now be marked as distributed --- but, --- "seg" extension --- will not be marked as distributed - --- see underlying objects -SELECT i.* FROM pg_catalog.pg_dist_object, pg_identify_object_as_address(classid, objid, objsubid) i ORDER BY 1, 2, 3; diff --git a/src/test/regress/sql/upgrade_pg_dist_object_test_before.sql b/src/test/regress/sql/upgrade_pg_dist_object_test_before.sql deleted file mode 100644 index 9b2cead88..000000000 --- a/src/test/regress/sql/upgrade_pg_dist_object_test_before.sql +++ /dev/null @@ -1,35 +0,0 @@ --- run this test only when old citus version is earlier than 10.0 -\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` -SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 10 -AS upgrade_test_old_citus_version_lt_10_0; -\gset -\if :upgrade_test_old_citus_version_lt_10_0 -\else -\q -\endif - --- schema propagation -- - --- public schema -CREATE TABLE dist_table (a int); -SELECT create_reference_table('dist_table'); - --- custom schema -CREATE SCHEMA new_schema; - -SET search_path to new_schema; - -CREATE TABLE another_dist_table (a int); -SELECT create_reference_table('another_dist_table'); - --- another custom schema and a type - --- create table that depends both on a type & schema here (actually type depends on the schema) --- here we test if schema is marked as distributed successfully. --- This is because tracking the dependencies will hit to the schema for two times - -CREATE SCHEMA fooschema; -CREATE TYPE fooschema.footype AS (x int, y int); - -CREATE TABLE fooschema.footable (f fooschema.footype); -SELECT create_reference_table('fooschema.footable'); diff --git a/src/test/regress/sql/upgrade_post_11_after.sql b/src/test/regress/sql/upgrade_post_11_after.sql index 946c52ae2..ba9b12f3b 100644 --- a/src/test/regress/sql/upgrade_post_11_after.sql +++ b/src/test/regress/sql/upgrade_post_11_after.sql @@ -1,3 +1,13 @@ +-- run this test only when old citus version is earlier than 11.0 +\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` +SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 11 +AS upgrade_test_old_citus_version_lt_11_0; +\gset +\if :upgrade_test_old_citus_version_lt_11_0 +\else +\q +\endif + SET search_path = post_11_upgrade; -- tables, views and their dependencies become objects with Citus 11+ diff --git a/src/test/regress/sql/upgrade_post_11_before.sql b/src/test/regress/sql/upgrade_post_11_before.sql index 9d17c78ac..617adc7b0 100644 --- a/src/test/regress/sql/upgrade_post_11_before.sql +++ b/src/test/regress/sql/upgrade_post_11_before.sql @@ -1,3 +1,12 @@ +-- run this test only when old citus version is earlier than 11.0 +\set upgrade_test_old_citus_version `echo "$CITUS_OLD_VERSION"` +SELECT substring(:'upgrade_test_old_citus_version', 'v(\d+)\.\d+\.\d+')::int < 11 +AS upgrade_test_old_citus_version_lt_11_0; +\gset +\if :upgrade_test_old_citus_version_lt_11_0 +\else +\q +\endif -- test cases for #3970 SET citus.shard_count TO 32; diff --git a/src/test/regress/sql/upgrade_schema_based_sharding_after.sql b/src/test/regress/sql/upgrade_schema_based_sharding_after.sql new file mode 100644 index 000000000..cd8e521ec --- /dev/null +++ b/src/test/regress/sql/upgrade_schema_based_sharding_after.sql @@ -0,0 +1,70 @@ +ALTER SCHEMA "tenant\'_1" RENAME TO tenant_1; +ALTER SCHEMA "tenant\'_2" RENAME TO tenant_2; + +-- verify that colocation id is set even for empty tenant +SELECT colocationid > 0 FROM pg_dist_schema +WHERE schemaid::regnamespace::text = 'tenant_1'; + +-- verify the same on workers +SELECT result FROM run_command_on_workers($$ + SELECT colocationid > 0 FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_1'; +$$); + +-- verify that colocation id is set for non-empty tenant +SELECT colocationid = ( + SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_2.test_table'::regclass +) +FROM pg_dist_schema +WHERE schemaid::regnamespace::text = 'tenant_2'; + +-- verify the same on workers +SELECT result FROM run_command_on_workers($$ + SELECT colocationid = ( + SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_2.test_table'::regclass + ) + FROM pg_dist_schema + WHERE schemaid::regnamespace::text = 'tenant_2'; +$$); + +CREATE TABLE tenant_1.tbl_1(a int, b text); +CREATE TABLE tenant_2.tbl_1(a int, b text); + +-- Show that we can create further tenant tables in the tenant schemas +-- after pg upgrade. +SELECT COUNT(*)=2 FROM pg_dist_partition +WHERE logicalrelid IN ('tenant_1.tbl_1'::regclass, 'tenant_2.tbl_1'::regclass) AND + partmethod = 'n' AND repmodel = 's' AND colocationid > 0; + +SELECT colocationid = ( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'tenant_1.tbl_1'::regclass AND + partmethod = 'n' AND repmodel = 's' +) +FROM pg_dist_schema +WHERE schemaid::regnamespace::text = 'tenant_1'; + +SELECT colocationid = ( + SELECT colocationid FROM pg_dist_partition + WHERE logicalrelid = 'tenant_2.tbl_1'::regclass AND + partmethod = 'n' AND repmodel = 's' +) +FROM pg_dist_schema +WHERE schemaid::regnamespace::text = 'tenant_2'; + +-- rollback the changes made on following schemas to make this test idempotent +DROP TABLE tenant_1.tbl_1, tenant_2.tbl_1; +ALTER SCHEMA tenant_1 RENAME TO "tenant\'_1"; +ALTER SCHEMA tenant_2 RENAME TO "tenant\'_2"; + +SET citus.enable_schema_based_sharding TO ON; + +CREATE SCHEMA tenant_3; + +-- Show that we can create furher tenant schemas after pg upgrade. +SELECT COUNT(*)=1 FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_3'; + +-- drop the schema created in this test to this test idempotent +DROP SCHEMA tenant_3 CASCADE; + +RESET citus.enable_schema_based_sharding; diff --git a/src/test/regress/sql/upgrade_schema_based_sharding_before.sql b/src/test/regress/sql/upgrade_schema_based_sharding_before.sql new file mode 100644 index 000000000..0f3141fe8 --- /dev/null +++ b/src/test/regress/sql/upgrade_schema_based_sharding_before.sql @@ -0,0 +1,14 @@ +SET citus.enable_schema_based_sharding TO ON; + +-- Create tenant tables with schema names that need escaping +-- to verify that citus_prepare_pg_upgrade() correctly saves +-- them into public schema. + +-- empty tenant +CREATE SCHEMA "tenant\'_1"; + +-- non-empty tenant +CREATE SCHEMA "tenant\'_2"; +CREATE TABLE "tenant\'_2".test_table(a int, b text); + +RESET citus.enable_schema_based_sharding; diff --git a/src/test/regress/sql/upgrade_single_shard_table_after.sql b/src/test/regress/sql/upgrade_single_shard_table_after.sql new file mode 100644 index 000000000..9b17b1101 --- /dev/null +++ b/src/test/regress/sql/upgrade_single_shard_table_after.sql @@ -0,0 +1,18 @@ +-- check that we properly retained the single-shard table +SELECT 1 FROM pg_dist_partition +WHERE logicalrelid = 'citus_schema.null_shard_key'::regclass AND + partmethod = 'n' AND repmodel = 's' AND colocationid != 0; + +BEGIN; + INSERT INTO citus_schema.null_shard_key (name) VALUES ('c'); + SELECT * FROM citus_schema.null_shard_key ORDER BY id; +ROLLBACK; + +-- Check that we can create a distributed table with a single-shard +-- after upgrade. +CREATE TABLE citus_schema.null_shard_key_after_upgrade (id bigserial, name text); +SELECT create_distributed_table('citus_schema.null_shard_key_after_upgrade', null); +INSERT INTO citus_schema.null_shard_key_after_upgrade (name) VALUES ('c'); +SELECT * FROM citus_schema.null_shard_key_after_upgrade ORDER BY id; + +DROP TABLE citus_schema.null_shard_key_after_upgrade; diff --git a/src/test/regress/sql/upgrade_single_shard_table_before.sql b/src/test/regress/sql/upgrade_single_shard_table_before.sql new file mode 100644 index 000000000..08eefe78c --- /dev/null +++ b/src/test/regress/sql/upgrade_single_shard_table_before.sql @@ -0,0 +1,3 @@ +CREATE TABLE null_shard_key (id int, name text); +SELECT create_distributed_table('null_shard_key', null); +INSERT INTO null_shard_key (id, name) VALUES (1, 'a'), (2, 'b'); diff --git a/src/test/regress/sql/values.sql b/src/test/regress/sql/values.sql index 4a5bb8352..6ff2282dc 100644 --- a/src/test/regress/sql/values.sql +++ b/src/test/regress/sql/values.sql @@ -393,5 +393,5 @@ BEGIN; COMMIT; -RESET client_min_messages; +SET client_min_messages TO WARNING; DROP SCHEMA values_subquery CASCADE; diff --git a/src/test/regress/sql/view_propagation.sql b/src/test/regress/sql/view_propagation.sql index 97e314375..44bbbf7b0 100644 --- a/src/test/regress/sql/view_propagation.sql +++ b/src/test/regress/sql/view_propagation.sql @@ -1,6 +1,7 @@ -- Tests to check propagation of all view commands CREATE SCHEMA view_prop_schema; SET search_path to view_prop_schema; +SET citus.next_shard_id TO 1420195; -- Check creating views depending on different types of tables -- and from multiple schemas @@ -542,5 +543,7 @@ CREATE OR REPLACE VIEW vv3 as SELECT * FROM vv4; RESET citus.enable_unsupported_feature_messages; RESET citus.enforce_object_restrictions_for_local_objects; SET client_min_messages TO ERROR; +DROP TABLE public.parent_1, public.employees CASCADE; DROP SCHEMA view_prop_schema_inner CASCADE; DROP SCHEMA view_prop_schema, axx CASCADE; +DROP ROLE view_creation_user, alter_view_user, grant_view_user; diff --git a/src/test/regress/sql/window_functions.sql b/src/test/regress/sql/window_functions.sql index de936c95c..2f7ea18d2 100644 --- a/src/test/regress/sql/window_functions.sql +++ b/src/test/regress/sql/window_functions.sql @@ -3,8 +3,6 @@ -- =================================================================== -- test top level window functions that are pushdownable -- =================================================================== --- This test file has an alternative output because of use of --- incremental sort in some explain outputs in PG13 -- -- a very simple window function with an aggregate and a window function diff --git a/src/test/regress/sql/with_dml.sql b/src/test/regress/sql/with_dml.sql index 8602a961b..40f9fe3ed 100644 --- a/src/test/regress/sql/with_dml.sql +++ b/src/test/regress/sql/with_dml.sql @@ -157,5 +157,5 @@ WITH ids_to_delete AS ( ) DELETE FROM reference_table WHERE id = ANY(SELECT id FROM ids_to_delete); -RESET client_min_messages; +SET client_min_messages TO WARNING; DROP SCHEMA with_dml CASCADE; diff --git a/src/test/regress/sql/with_executors.sql b/src/test/regress/sql/with_executors.sql index dec5fcd9b..897c79cdc 100644 --- a/src/test/regress/sql/with_executors.sql +++ b/src/test/regress/sql/with_executors.sql @@ -334,4 +334,5 @@ FROM WHERE users_table.user_id = cte_merge.u_id; +SET client_min_messages TO WARNING; DROP SCHEMA with_executors CASCADE;